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