1use std::fmt;
4
5use arrayvec::ArrayVec;
6use either::Either;
7use hir::{
8 AssocItem, Crate, FieldSource, HasContainer, HasCrate, HasSource, HirDisplay, HirFileId,
9 InFile, LocalSource, ModuleSource, Name, Semantics, Symbol, sym, symbols::FileSymbol,
10};
11use ide_db::{
12 FileId, FileRange, RootDatabase, SymbolKind,
13 base_db::{CrateOrigin, LangCrateOrigin, all_crates},
14 defs::{Definition, find_std_module},
15 documentation::{Documentation, HasDocs},
16 famous_defs::FamousDefs,
17 ra_fixture::UpmapFromRaFixture,
18};
19use stdx::never;
20use syntax::{
21 AstNode, AstPtr, SyntaxNode, TextRange,
22 ast::{self, HasName},
23};
24
25#[derive(Clone, PartialEq, Eq, Hash)]
31pub struct NavigationTarget {
32 pub file_id: FileId,
33 pub full_range: TextRange,
40 pub focus_range: Option<TextRange>,
49 pub name: Symbol,
50 pub kind: Option<SymbolKind>,
51 pub container_name: Option<Symbol>,
52 pub description: Option<String>,
53 pub docs: Option<Documentation<'static>>,
55 pub alias: Option<Symbol>,
58}
59
60impl fmt::Debug for NavigationTarget {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 let mut f = f.debug_struct("NavigationTarget");
63 macro_rules! opt {
64 ($($name:ident)*) => {$(
65 if let Some(it) = &self.$name {
66 f.field(stringify!($name), it);
67 }
68 )*}
69 }
70 f.field("file_id", &self.file_id).field("full_range", &self.full_range);
71 opt!(focus_range);
72 f.field("name", &self.name);
73 opt!(kind container_name description docs);
74 f.finish()
75 }
76}
77
78impl UpmapFromRaFixture for NavigationTarget {
79 fn upmap_from_ra_fixture(
80 self,
81 analysis: &ide_db::ra_fixture::RaFixtureAnalysis,
82 _virtual_file_id: FileId,
83 real_file_id: FileId,
84 ) -> Result<Self, ()> {
85 let virtual_file_id = self.file_id;
86 Ok(NavigationTarget {
87 file_id: real_file_id,
88 full_range: self.full_range.upmap_from_ra_fixture(
89 analysis,
90 virtual_file_id,
91 real_file_id,
92 )?,
93 focus_range: self.focus_range.upmap_from_ra_fixture(
94 analysis,
95 virtual_file_id,
96 real_file_id,
97 )?,
98 name: self.name.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
99 kind: self.kind.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
100 container_name: self.container_name.upmap_from_ra_fixture(
101 analysis,
102 virtual_file_id,
103 real_file_id,
104 )?,
105 description: self.description.upmap_from_ra_fixture(
106 analysis,
107 virtual_file_id,
108 real_file_id,
109 )?,
110 docs: self.docs.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
111 alias: self.alias.upmap_from_ra_fixture(analysis, virtual_file_id, real_file_id)?,
112 })
113 }
114}
115
116pub(crate) trait ToNav {
117 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget>;
118}
119
120pub trait TryToNav {
121 fn try_to_nav(
122 &self,
123 sema: &Semantics<'_, RootDatabase>,
124 ) -> Option<UpmappingResult<NavigationTarget>>;
125}
126
127impl<T: TryToNav, U: TryToNav> TryToNav for Either<T, U> {
128 fn try_to_nav(
129 &self,
130 sema: &Semantics<'_, RootDatabase>,
131 ) -> Option<UpmappingResult<NavigationTarget>> {
132 match self {
133 Either::Left(it) => it.try_to_nav(sema),
134 Either::Right(it) => it.try_to_nav(sema),
135 }
136 }
137}
138
139impl NavigationTarget {
140 pub fn focus_or_full_range(&self) -> TextRange {
141 self.focus_range.unwrap_or(self.full_range)
142 }
143
144 pub(crate) fn from_module_to_decl(
145 db: &RootDatabase,
146 module: hir::Module,
147 ) -> UpmappingResult<NavigationTarget> {
148 let name = module.name(db).map(|it| it.symbol().clone()).unwrap_or_else(|| sym::underscore);
149 match module.declaration_source(db) {
150 Some(InFile { value, file_id }) => {
151 orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
152 |(FileRange { file_id, range: full_range }, focus_range)| {
153 let mut res = NavigationTarget::from_syntax(
154 file_id,
155 name.clone(),
156 focus_range,
157 full_range,
158 SymbolKind::Module,
159 );
160 res.docs = module.docs(db).map(Documentation::into_owned);
161 res.description = Some(
162 module.display(db, module.krate(db).to_display_target(db)).to_string(),
163 );
164 res
165 },
166 )
167 }
168 _ => module.to_nav(db),
169 }
170 }
171
172 #[cfg(test)]
173 pub(crate) fn debug_render(&self) -> String {
174 let mut buf = format!(
175 "{} {:?} {:?} {:?}",
176 self.name,
177 self.kind.unwrap(),
178 self.file_id,
179 self.full_range
180 );
181 if let Some(focus_range) = self.focus_range {
182 buf.push_str(&format!(" {focus_range:?}"))
183 }
184 if let Some(container_name) = &self.container_name {
185 buf.push_str(&format!(" {container_name}"))
186 }
187 buf
188 }
189
190 pub(crate) fn from_named(
192 db: &RootDatabase,
193 InFile { file_id, value }: InFile<&dyn ast::HasName>,
194 kind: SymbolKind,
195 ) -> UpmappingResult<NavigationTarget> {
196 let name =
197 value.name().map(|it| Symbol::intern(&it.text())).unwrap_or_else(|| sym::underscore);
198
199 orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
200 |(FileRange { file_id, range: full_range }, focus_range)| {
201 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
202 },
203 )
204 }
205
206 pub(crate) fn from_named_with_range(
207 db: &RootDatabase,
208 ranges: InFile<(TextRange, Option<TextRange>)>,
209 name: Option<Name>,
210 kind: SymbolKind,
211 ) -> UpmappingResult<NavigationTarget> {
212 let InFile { file_id, value: (full_range, focus_range) } = ranges;
213 let name = name.map(|name| name.symbol().clone()).unwrap_or_else(|| sym::underscore);
214
215 orig_range_with_focus_r(db, file_id, full_range, focus_range).map(
216 |(FileRange { file_id, range: full_range }, focus_range)| {
217 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
218 },
219 )
220 }
221
222 pub(crate) fn from_syntax(
223 file_id: FileId,
224 name: Symbol,
225 focus_range: Option<TextRange>,
226 full_range: TextRange,
227 kind: SymbolKind,
228 ) -> NavigationTarget {
229 NavigationTarget {
230 file_id,
231 name,
232 kind: Some(kind),
233 full_range,
234 focus_range,
235 container_name: None,
236 description: None,
237 docs: None,
238 alias: None,
239 }
240 }
241}
242
243impl<'db> TryToNav for FileSymbol<'db> {
244 fn try_to_nav(
245 &self,
246 sema: &Semantics<'_, RootDatabase>,
247 ) -> Option<UpmappingResult<NavigationTarget>> {
248 let db = sema.db;
249 let display_target = self.def.krate(db).to_display_target(db);
250 Some(
251 orig_range_with_focus_r(
252 db,
253 self.loc.hir_file_id,
254 self.loc.ptr.text_range(),
255 self.loc.name_ptr.map(AstPtr::text_range),
256 )
257 .map(|(FileRange { file_id, range: full_range }, focus_range)| {
258 NavigationTarget {
259 file_id,
260 name: self
261 .is_alias
262 .then(|| self.def.name(db))
263 .flatten()
264 .map_or_else(|| self.name.clone(), |it| it.symbol().clone()),
265 alias: self.is_alias.then(|| self.name.clone()),
266 kind: Some(SymbolKind::from_module_def(db, self.def)),
267 full_range,
268 focus_range,
269 container_name: self.container_name.clone(),
270 description: match self.def {
271 hir::ModuleDef::Module(it) => {
272 Some(it.display(db, display_target).to_string())
273 }
274 hir::ModuleDef::Function(it) => {
275 Some(it.display(db, display_target).to_string())
276 }
277 hir::ModuleDef::Adt(it) => Some(it.display(db, display_target).to_string()),
278 hir::ModuleDef::EnumVariant(it) => {
279 Some(it.display(db, display_target).to_string())
280 }
281 hir::ModuleDef::Const(it) => {
282 Some(it.display(db, display_target).to_string())
283 }
284 hir::ModuleDef::Static(it) => {
285 Some(it.display(db, display_target).to_string())
286 }
287 hir::ModuleDef::Trait(it) => {
288 Some(it.display(db, display_target).to_string())
289 }
290 hir::ModuleDef::TypeAlias(it) => {
291 Some(it.display(db, display_target).to_string())
292 }
293 hir::ModuleDef::Macro(it) => {
294 Some(it.display(db, display_target).to_string())
295 }
296 hir::ModuleDef::BuiltinType(_) => None,
297 },
298 docs: None,
299 }
300 }),
301 )
302 }
303}
304
305impl TryToNav for Definition {
306 fn try_to_nav(
307 &self,
308 sema: &Semantics<'_, RootDatabase>,
309 ) -> Option<UpmappingResult<NavigationTarget>> {
310 match self {
311 Definition::Local(it) => Some(it.to_nav(sema.db)),
312 Definition::Label(it) => it.try_to_nav(sema),
313 Definition::Module(it) => Some(it.to_nav(sema.db)),
314 Definition::Crate(it) => Some(it.to_nav(sema.db)),
315 Definition::Macro(it) => it.try_to_nav(sema),
316 Definition::Field(it) => it.try_to_nav(sema),
317 Definition::SelfType(it) => it.try_to_nav(sema),
318 Definition::GenericParam(it) => it.try_to_nav(sema),
319 Definition::Function(it) => it.try_to_nav(sema),
320 Definition::Adt(it) => it.try_to_nav(sema),
321 Definition::EnumVariant(it) => it.try_to_nav(sema),
322 Definition::Const(it) => it.try_to_nav(sema),
323 Definition::Static(it) => it.try_to_nav(sema),
324 Definition::Trait(it) => it.try_to_nav(sema),
325 Definition::TypeAlias(it) => it.try_to_nav(sema),
326 Definition::ExternCrateDecl(it) => it.try_to_nav(sema),
327 Definition::InlineAsmOperand(it) => it.try_to_nav(sema),
328 Definition::BuiltinType(it) => it.try_to_nav(sema),
329 Definition::BuiltinLifetime(_)
330 | Definition::TupleField(_)
331 | Definition::ToolModule(_)
332 | Definition::InlineAsmRegOrRegClass(_)
333 | Definition::BuiltinAttr(_) => None,
334 Definition::DeriveHelper(it) => it.derive().try_to_nav(sema),
336 }
337 }
338}
339
340impl TryToNav for hir::ModuleDef {
341 fn try_to_nav(
342 &self,
343 sema: &Semantics<'_, RootDatabase>,
344 ) -> Option<UpmappingResult<NavigationTarget>> {
345 match self {
346 hir::ModuleDef::Module(it) => Some(it.to_nav(sema.db)),
347 hir::ModuleDef::Function(it) => it.try_to_nav(sema),
348 hir::ModuleDef::Adt(it) => it.try_to_nav(sema),
349 hir::ModuleDef::EnumVariant(it) => it.try_to_nav(sema),
350 hir::ModuleDef::Const(it) => it.try_to_nav(sema),
351 hir::ModuleDef::Static(it) => it.try_to_nav(sema),
352 hir::ModuleDef::Trait(it) => it.try_to_nav(sema),
353 hir::ModuleDef::TypeAlias(it) => it.try_to_nav(sema),
354 hir::ModuleDef::Macro(it) => it.try_to_nav(sema),
355 hir::ModuleDef::BuiltinType(_) => None,
356 }
357 }
358}
359
360pub(crate) trait ToNavFromAst: Sized {
361 const KIND: SymbolKind;
362 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
363 _ = db;
364 None
365 }
366}
367
368fn container_name(db: &RootDatabase, t: impl HasContainer) -> Option<Symbol> {
369 match t.container(db) {
370 hir::ItemContainer::Trait(it) => Some(it.name(db).symbol().clone()),
371 hir::ItemContainer::Module(it) => it.name(db).map(|name| name.symbol().clone()),
373 _ => None,
374 }
375}
376
377impl ToNavFromAst for hir::Function {
378 const KIND: SymbolKind = SymbolKind::Function;
379 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
380 container_name(db, self)
381 }
382}
383
384impl ToNavFromAst for hir::Const {
385 const KIND: SymbolKind = SymbolKind::Const;
386 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
387 container_name(db, self)
388 }
389}
390impl ToNavFromAst for hir::Static {
391 const KIND: SymbolKind = SymbolKind::Static;
392 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
393 container_name(db, self)
394 }
395}
396impl ToNavFromAst for hir::Struct {
397 const KIND: SymbolKind = SymbolKind::Struct;
398 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
399 container_name(db, self)
400 }
401}
402impl ToNavFromAst for hir::Enum {
403 const KIND: SymbolKind = SymbolKind::Enum;
404 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
405 container_name(db, self)
406 }
407}
408impl ToNavFromAst for hir::EnumVariant {
409 const KIND: SymbolKind = SymbolKind::Variant;
410}
411impl ToNavFromAst for hir::Union {
412 const KIND: SymbolKind = SymbolKind::Union;
413 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
414 container_name(db, self)
415 }
416}
417impl ToNavFromAst for hir::TypeAlias {
418 const KIND: SymbolKind = SymbolKind::TypeAlias;
419 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
420 container_name(db, self)
421 }
422}
423impl ToNavFromAst for hir::Trait {
424 const KIND: SymbolKind = SymbolKind::Trait;
425 fn container_name(self, db: &RootDatabase) -> Option<Symbol> {
426 container_name(db, self)
427 }
428}
429
430impl<D> TryToNav for D
431where
432 D: HasSource
433 + ToNavFromAst
434 + Copy
435 + HasDocs
436 + for<'db> HirDisplay<'db>
437 + HasCrate
438 + hir::HasName,
439 D::Ast: ast::HasName,
440{
441 fn try_to_nav(
442 &self,
443 sema: &Semantics<'_, RootDatabase>,
444 ) -> Option<UpmappingResult<NavigationTarget>> {
445 let db = sema.db;
446 let src = self.source_with_range(db)?;
447 Some(
448 NavigationTarget::from_named_with_range(
449 db,
450 src.map(|(full_range, node)| {
451 (
452 full_range,
453 node.and_then(|node| {
454 Some(ast::HasName::name(&node)?.syntax().text_range())
455 }),
456 )
457 }),
458 self.name(db),
459 D::KIND,
460 )
461 .map(|mut res| {
462 res.docs = self.docs(db).map(Documentation::into_owned);
463 res.description =
464 Some(self.display(db, self.krate(db).to_display_target(db)).to_string());
465 res.container_name = self.container_name(db);
466 res
467 }),
468 )
469 }
470}
471
472impl ToNav for hir::Module {
473 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
474 let InFile { file_id, value } = self.definition_source(db);
475
476 let name = self.name(db).map(|it| it.symbol().clone()).unwrap_or_else(|| sym::underscore);
477 let (syntax, focus) = match &value {
478 ModuleSource::SourceFile(node) => (node.syntax(), None),
479 ModuleSource::Module(node) => (node.syntax(), node.name()),
480 ModuleSource::BlockExpr(node) => (node.syntax(), None),
481 };
482 let kind = if self.is_crate_root(db) { SymbolKind::CrateRoot } else { SymbolKind::Module };
483
484 orig_range_with_focus(db, file_id, syntax, focus).map(
485 |(FileRange { file_id, range: full_range }, focus_range)| {
486 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
487 },
488 )
489 }
490}
491
492impl ToNav for hir::Crate {
493 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
494 self.root_module(db).to_nav(db)
495 }
496}
497
498impl TryToNav for hir::Impl {
499 fn try_to_nav(
500 &self,
501 sema: &Semantics<'_, RootDatabase>,
502 ) -> Option<UpmappingResult<NavigationTarget>> {
503 let db = sema.db;
504 let InFile { file_id, value: (full_range, source) } = self.source_with_range(db)?;
505
506 Some(
507 orig_range_with_focus_r(
508 db,
509 file_id,
510 full_range,
511 source.and_then(|source| Some(source.self_ty()?.syntax().text_range())),
512 )
513 .map(|(FileRange { file_id, range: full_range }, focus_range)| {
514 NavigationTarget::from_syntax(
515 file_id,
516 sym::kw_impl,
517 focus_range,
518 full_range,
519 SymbolKind::Impl,
520 )
521 }),
522 )
523 }
524}
525
526impl TryToNav for hir::ExternCrateDecl {
527 fn try_to_nav(
528 &self,
529 sema: &Semantics<'_, RootDatabase>,
530 ) -> Option<UpmappingResult<NavigationTarget>> {
531 let db = sema.db;
532 let src = self.source(db)?;
533 let InFile { file_id, value } = src;
534 let focus = value
535 .rename()
536 .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right));
537 let krate = self.module(db).krate(db);
538
539 Some(orig_range_with_focus(db, file_id, value.syntax(), focus).map(
540 |(FileRange { file_id, range: full_range }, focus_range)| {
541 let mut res = NavigationTarget::from_syntax(
542 file_id,
543 self.alias_or_name(db).unwrap_or_else(|| self.name(db)).symbol().clone(),
544 focus_range,
545 full_range,
546 SymbolKind::CrateRoot,
547 );
548
549 res.docs = self.docs(db).map(Documentation::into_owned);
550 res.description = Some(self.display(db, krate.to_display_target(db)).to_string());
551 res.container_name = container_name(db, *self);
552 res
553 },
554 ))
555 }
556}
557
558impl TryToNav for hir::Field {
559 fn try_to_nav(
560 &self,
561 sema: &Semantics<'_, RootDatabase>,
562 ) -> Option<UpmappingResult<NavigationTarget>> {
563 let db = sema.db;
564 let src = self.source(db)?;
565 let krate = self.parent_def(db).module(db).krate(db);
566
567 let field_source = match &src.value {
568 FieldSource::Named(it) => {
569 NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map(
570 |mut res| {
571 res.docs = self.docs(db).map(Documentation::into_owned);
572 res.description =
573 Some(self.display(db, krate.to_display_target(db)).to_string());
574 res
575 },
576 )
577 }
578 FieldSource::Pos(it) => orig_range(db, src.file_id, it.syntax()).map(
579 |(FileRange { file_id, range: full_range }, focus_range)| {
580 NavigationTarget::from_syntax(
581 file_id,
582 sym::Integer::get(self.index()),
583 focus_range,
584 full_range,
585 SymbolKind::Field,
586 )
587 },
588 ),
589 };
590 Some(field_source)
591 }
592}
593
594impl TryToNav for hir::Macro {
595 fn try_to_nav(
596 &self,
597 sema: &Semantics<'_, RootDatabase>,
598 ) -> Option<UpmappingResult<NavigationTarget>> {
599 let db = sema.db;
600 let src = self.source(db)?;
601 let name_owner: &dyn ast::HasName = match &src.value {
602 Either::Left(it) => it,
603 Either::Right(it) => it,
604 };
605 Some(
606 NavigationTarget::from_named(
607 db,
608 src.as_ref().with_value(name_owner),
609 self.kind(db).into(),
610 )
611 .map(|mut res| {
612 res.docs = self.docs(db).map(Documentation::into_owned);
613 res
614 }),
615 )
616 }
617}
618
619impl TryToNav for hir::Adt {
620 fn try_to_nav(
621 &self,
622 sema: &Semantics<'_, RootDatabase>,
623 ) -> Option<UpmappingResult<NavigationTarget>> {
624 match self {
625 hir::Adt::Struct(it) => it.try_to_nav(sema),
626 hir::Adt::Union(it) => it.try_to_nav(sema),
627 hir::Adt::Enum(it) => it.try_to_nav(sema),
628 }
629 }
630}
631
632impl TryToNav for hir::AssocItem {
633 fn try_to_nav(
634 &self,
635 sema: &Semantics<'_, RootDatabase>,
636 ) -> Option<UpmappingResult<NavigationTarget>> {
637 match self {
638 AssocItem::Function(it) => it.try_to_nav(sema),
639 AssocItem::Const(it) => it.try_to_nav(sema),
640 AssocItem::TypeAlias(it) => it.try_to_nav(sema),
641 }
642 }
643}
644
645impl TryToNav for hir::GenericParam {
646 fn try_to_nav(
647 &self,
648 sema: &Semantics<'_, RootDatabase>,
649 ) -> Option<UpmappingResult<NavigationTarget>> {
650 match self {
651 hir::GenericParam::TypeParam(it) => it.try_to_nav(sema),
652 hir::GenericParam::ConstParam(it) => it.try_to_nav(sema),
653 hir::GenericParam::LifetimeParam(it) => it.try_to_nav(sema),
654 }
655 }
656}
657
658impl ToNav for LocalSource {
659 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
660 let InFile { file_id, value } = &self.source;
661 let file_id = *file_id;
662 let local = self.local;
663 let (node, name) = match &value {
664 Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
665 Either::Right(it) => (it.syntax(), it.name()),
666 };
667
668 orig_range_with_focus(db, file_id, node, name).map(
669 |(FileRange { file_id, range: full_range }, focus_range)| {
670 let name = local.name(db).symbol().clone();
671 let kind = if local.is_self(db) {
672 SymbolKind::SelfParam
673 } else if local.is_param(db) {
674 SymbolKind::ValueParam
675 } else {
676 SymbolKind::Local
677 };
678 NavigationTarget {
679 file_id,
680 name,
681 alias: None,
682 kind: Some(kind),
683 full_range,
684 focus_range,
685 container_name: None,
686 description: None,
687 docs: None,
688 }
689 },
690 )
691 }
692}
693
694impl ToNav for hir::Local {
695 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
696 self.primary_source(db).to_nav(db)
697 }
698}
699
700impl TryToNav for hir::Label {
701 fn try_to_nav(
702 &self,
703 sema: &Semantics<'_, RootDatabase>,
704 ) -> Option<UpmappingResult<NavigationTarget>> {
705 let db = sema.db;
706 let InFile { file_id, value } = self.source(db)?;
707 let name = self.name(db).symbol().clone();
708
709 Some(orig_range_with_focus(db, file_id, value.syntax(), value.lifetime()).map(
710 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
711 file_id,
712 name: name.clone(),
713 alias: None,
714 kind: Some(SymbolKind::Label),
715 full_range,
716 focus_range,
717 container_name: None,
718 description: None,
719 docs: None,
720 },
721 ))
722 }
723}
724
725impl TryToNav for hir::TypeParam {
726 fn try_to_nav(
727 &self,
728 sema: &Semantics<'_, RootDatabase>,
729 ) -> Option<UpmappingResult<NavigationTarget>> {
730 let db = sema.db;
731 let InFile { file_id, value } = self.merge().source(db)?;
732 let name = self.name(db).symbol().clone();
733
734 let value = match value {
735 Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
736 Either::Left(ast::TypeOrConstParam::Const(_)) => {
737 never!();
738 return None;
739 }
740 Either::Right(x) => Either::Right(x),
741 };
742
743 let syntax = match &value {
744 Either::Left(type_param) => type_param.syntax(),
745 Either::Right(trait_) => trait_.syntax(),
746 };
747 let focus = value.as_ref().either(|it| it.name(), |it| it.name());
748
749 Some(orig_range_with_focus(db, file_id, syntax, focus).map(
750 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
751 file_id,
752 name: name.clone(),
753 alias: None,
754 kind: Some(SymbolKind::TypeParam),
755 full_range,
756 focus_range,
757 container_name: None,
758 description: None,
759 docs: None,
760 },
761 ))
762 }
763}
764
765impl TryToNav for hir::TypeOrConstParam {
766 fn try_to_nav(
767 &self,
768 sema: &Semantics<'_, RootDatabase>,
769 ) -> Option<UpmappingResult<NavigationTarget>> {
770 self.split(sema.db).try_to_nav(sema)
771 }
772}
773
774impl TryToNav for hir::LifetimeParam {
775 fn try_to_nav(
776 &self,
777 sema: &Semantics<'_, RootDatabase>,
778 ) -> Option<UpmappingResult<NavigationTarget>> {
779 let db = sema.db;
780 let InFile { file_id, value } = self.source(db)?;
781 let name = self.name(db).symbol().clone();
782
783 Some(orig_range(db, file_id, value.syntax()).map(
784 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
785 file_id,
786 name: name.clone(),
787 alias: None,
788 kind: Some(SymbolKind::LifetimeParam),
789 full_range,
790 focus_range,
791 container_name: None,
792 description: None,
793 docs: None,
794 },
795 ))
796 }
797}
798
799impl TryToNav for hir::ConstParam {
800 fn try_to_nav(
801 &self,
802 sema: &Semantics<'_, RootDatabase>,
803 ) -> Option<UpmappingResult<NavigationTarget>> {
804 let db = sema.db;
805 let InFile { file_id, value } = self.merge().source(db)?;
806 let name = self.name(db).symbol().clone();
807
808 let value = match value {
809 Either::Left(ast::TypeOrConstParam::Const(x)) => x,
810 _ => {
811 never!();
812 return None;
813 }
814 };
815
816 Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
817 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
818 file_id,
819 name: name.clone(),
820 alias: None,
821 kind: Some(SymbolKind::ConstParam),
822 full_range,
823 focus_range,
824 container_name: None,
825 description: None,
826 docs: None,
827 },
828 ))
829 }
830}
831
832impl TryToNav for hir::InlineAsmOperand {
833 fn try_to_nav(
834 &self,
835 sema: &Semantics<'_, RootDatabase>,
836 ) -> Option<UpmappingResult<NavigationTarget>> {
837 let db = sema.db;
838 let InFile { file_id, value } = &self.source(db)?;
839 let file_id = *file_id;
840 Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
841 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
842 file_id,
843 name:
844 self.name(db).map_or_else(|| sym::underscore.clone(), |it| it.symbol().clone()),
845 alias: None,
846 kind: Some(SymbolKind::Local),
847 full_range,
848 focus_range,
849 container_name: None,
850 description: None,
851 docs: None,
852 },
853 ))
854 }
855}
856
857impl TryToNav for hir::BuiltinType {
858 fn try_to_nav(
859 &self,
860 sema: &Semantics<'_, RootDatabase>,
861 ) -> Option<UpmappingResult<NavigationTarget>> {
862 let db = sema.db;
863 let krate = all_crates(db)
864 .iter()
865 .copied()
866 .find(|&krate| matches!(krate.data(db).origin, CrateOrigin::Lang(LangCrateOrigin::Std)))
867 .map(Crate::from)?;
868 let edition = krate.edition(db);
869
870 let fd = FamousDefs(sema, krate);
871 let primitive_mod = format!("prim_{}", self.name().display(fd.0.db, edition));
872 let doc_owner = find_std_module(&fd, &primitive_mod, edition)?;
873
874 Some(doc_owner.to_nav(db))
875 }
876}
877
878#[derive(Debug)]
879pub struct UpmappingResult<T> {
880 pub call_site: T,
882 pub def_site: Option<T>,
884}
885
886impl<T> UpmappingResult<T> {
887 pub fn call_site(self) -> T {
888 self.call_site
889 }
890
891 pub fn collect<FI: FromIterator<T>>(self) -> FI {
892 FI::from_iter(self)
893 }
894}
895
896impl<T> IntoIterator for UpmappingResult<T> {
897 type Item = T;
898
899 type IntoIter = <ArrayVec<T, 2> as IntoIterator>::IntoIter;
900
901 fn into_iter(self) -> Self::IntoIter {
902 self.def_site
903 .into_iter()
904 .chain(Some(self.call_site))
905 .collect::<ArrayVec<_, 2>>()
906 .into_iter()
907 }
908}
909
910impl<T> UpmappingResult<T> {
911 pub(crate) fn map<U>(self, f: impl Fn(T) -> U) -> UpmappingResult<U> {
912 UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) }
913 }
914}
915
916fn orig_range_with_focus(
920 db: &RootDatabase,
921 hir_file: HirFileId,
922 value: &SyntaxNode,
923 name: Option<impl AstNode>,
924) -> UpmappingResult<(FileRange, Option<TextRange>)> {
925 orig_range_with_focus_r(
926 db,
927 hir_file,
928 value.text_range(),
929 name.map(|it| it.syntax().text_range()),
930 )
931}
932
933pub(crate) fn orig_range_with_focus_r(
934 db: &RootDatabase,
935 hir_file: HirFileId,
936 value: TextRange,
937 focus_range: Option<TextRange>,
938) -> UpmappingResult<(FileRange, Option<TextRange>)> {
939 let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) };
940
941 let call = || hir_file.macro_file().unwrap().loc(db);
942
943 let def_range = || hir_file.macro_file().unwrap().loc(db).def.definition_range(db);
944
945 let value_range = InFile::new(hir_file, value).original_node_file_range_opt(db);
947 let ((call_site_range, call_site_focus), def_site) =
948 match InFile::new(hir_file, name).original_node_file_range_opt(db) {
949 Some((focus_range, ctxt)) if ctxt.is_root() => {
951 (
953 (
954 match value_range {
955 Some((range, ctxt))
957 if ctxt.is_root()
958 && range.file_id == focus_range.file_id
959 && range.range.contains_range(focus_range.range) =>
960 {
961 range
962 }
963 _ => {
966 let call = call();
967 let kind = &call.kind;
968 let range = kind.clone().original_call_range_with_input(db);
969 if range.file_id == focus_range.file_id
975 && range.range.contains_range(focus_range.range)
976 {
977 range
978 } else {
979 kind.original_call_range(db, call.krate)
980 }
981 }
982 },
983 Some(focus_range),
984 ),
985 None,
987 )
988 }
989
990 Some((focus_range, _ctxt)) => {
993 match value_range {
994 Some((range, ctxt)) if ctxt.is_root() => (
996 (range, None),
998 {
1001 let (def_site, _) = def_range().original_node_file_range(db);
1002 (def_site.file_id == focus_range.file_id
1003 && def_site.range.contains_range(focus_range.range))
1004 .then_some((def_site, Some(focus_range)))
1005 },
1006 ),
1007 _ => {
1009 let call = call();
1010 (
1011 (call.kind.original_call_range(db, call.krate), None),
1013 Some((focus_range, Some(focus_range))),
1014 )
1015 }
1016 }
1017 }
1018 None => return orig_range_r(db, hir_file, value),
1020 };
1021
1022 UpmappingResult {
1023 call_site: (
1024 call_site_range.into_file_id(db),
1025 call_site_focus.and_then(|hir::FileRange { file_id, range }| {
1026 if call_site_range.file_id == file_id && call_site_range.range.contains_range(range)
1027 {
1028 Some(range)
1029 } else {
1030 None
1031 }
1032 }),
1033 ),
1034 def_site: def_site.map(|(def_site_range, def_site_focus)| {
1035 (
1036 def_site_range.into_file_id(db),
1037 def_site_focus.and_then(|hir::FileRange { file_id, range }| {
1038 if def_site_range.file_id == file_id
1039 && def_site_range.range.contains_range(range)
1040 {
1041 Some(range)
1042 } else {
1043 None
1044 }
1045 }),
1046 )
1047 }),
1048 }
1049}
1050
1051fn orig_range(
1052 db: &RootDatabase,
1053 hir_file: HirFileId,
1054 value: &SyntaxNode,
1055) -> UpmappingResult<(FileRange, Option<TextRange>)> {
1056 UpmappingResult {
1057 call_site: (
1058 InFile::new(hir_file, value).original_file_range_rooted(db).into_file_id(db),
1059 None,
1060 ),
1061 def_site: None,
1062 }
1063}
1064
1065fn orig_range_r(
1066 db: &RootDatabase,
1067 hir_file: HirFileId,
1068 value: TextRange,
1069) -> UpmappingResult<(FileRange, Option<TextRange>)> {
1070 UpmappingResult {
1071 call_site: (
1072 InFile::new(hir_file, value).original_node_file_range(db).0.into_file_id(db),
1073 None,
1074 ),
1075 def_site: None,
1076 }
1077}
1078
1079#[cfg(test)]
1080mod tests {
1081 use expect_test::expect;
1082
1083 use crate::{Query, fixture};
1084
1085 #[test]
1086 fn test_nav_for_symbol() {
1087 let (analysis, _) = fixture::file(
1088 r#"
1089enum FooInner { }
1090fn foo() { enum FooInner { } }
1091"#,
1092 );
1093
1094 let navs = analysis.symbol_search(Query::new("FooInner".to_owned()), !0).unwrap();
1095 expect![[r#"
1096 [
1097 NavigationTarget {
1098 file_id: FileId(
1099 0,
1100 ),
1101 full_range: 0..17,
1102 focus_range: 5..13,
1103 name: "FooInner",
1104 kind: Enum,
1105 description: "enum FooInner",
1106 },
1107 NavigationTarget {
1108 file_id: FileId(
1109 0,
1110 ),
1111 full_range: 29..46,
1112 focus_range: 34..42,
1113 name: "FooInner",
1114 kind: Enum,
1115 container_name: "foo",
1116 description: "enum FooInner",
1117 },
1118 ]
1119 "#]]
1120 .assert_debug_eq(&navs);
1121 }
1122
1123 #[test]
1124 fn test_world_symbols_are_case_sensitive() {
1125 let (analysis, _) = fixture::file(
1126 r#"
1127fn foo() {}
1128struct Foo;
1129"#,
1130 );
1131
1132 let navs = analysis.symbol_search(Query::new("foo".to_owned()), !0).unwrap();
1133 assert_eq!(navs.len(), 2)
1134 }
1135
1136 #[test]
1137 fn test_ensure_hidden_symbols_are_not_returned() {
1138 let (analysis, _) = fixture::file(
1139 r#"
1140fn foo() {}
1141struct Foo;
1142static __FOO_CALLSITE: () = ();
1143"#,
1144 );
1145
1146 let navs = analysis.symbol_search(Query::new("foo".to_owned()), !0).unwrap();
1148 assert_eq!(navs.len(), 2);
1149 let navs = analysis.symbol_search(Query::new("_foo".to_owned()), !0).unwrap();
1150 assert_eq!(navs.len(), 0);
1151
1152 let query = Query::new("__foo".to_owned());
1154 let navs = analysis.symbol_search(query, !0).unwrap();
1155 assert_eq!(navs.len(), 1);
1156 }
1157}