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, AstPtr, 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 self.loc.name_ptr.map(AstPtr::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(SymbolKind::from_module_def(db, self.def)),
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 let kind = if self.is_crate_root(db) { SymbolKind::CrateRoot } else { SymbolKind::Module };
484
485 orig_range_with_focus(db, file_id, syntax, focus).map(
486 |(FileRange { file_id, range: full_range }, focus_range)| {
487 NavigationTarget::from_syntax(file_id, name.clone(), focus_range, full_range, kind)
488 },
489 )
490 }
491}
492
493impl ToNav for hir::Crate {
494 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
495 self.root_module(db).to_nav(db)
496 }
497}
498
499impl TryToNav for hir::Impl {
500 fn try_to_nav(
501 &self,
502 sema: &Semantics<'_, RootDatabase>,
503 ) -> Option<UpmappingResult<NavigationTarget>> {
504 let db = sema.db;
505 let InFile { file_id, value: (full_range, source) } = self.source_with_range(db)?;
506
507 Some(
508 orig_range_with_focus_r(
509 db,
510 file_id,
511 full_range,
512 source.and_then(|source| Some(source.self_ty()?.syntax().text_range())),
513 )
514 .map(|(FileRange { file_id, range: full_range }, focus_range)| {
515 NavigationTarget::from_syntax(
516 file_id,
517 sym::kw_impl,
518 focus_range,
519 full_range,
520 SymbolKind::Impl,
521 )
522 }),
523 )
524 }
525}
526
527impl TryToNav for hir::ExternCrateDecl {
528 fn try_to_nav(
529 &self,
530 sema: &Semantics<'_, RootDatabase>,
531 ) -> Option<UpmappingResult<NavigationTarget>> {
532 let db = sema.db;
533 let src = self.source(db)?;
534 let InFile { file_id, value } = src;
535 let focus = value
536 .rename()
537 .map_or_else(|| value.name_ref().map(Either::Left), |it| it.name().map(Either::Right));
538 let krate = self.module(db).krate(db);
539
540 Some(orig_range_with_focus(db, file_id, value.syntax(), focus).map(
541 |(FileRange { file_id, range: full_range }, focus_range)| {
542 let mut res = NavigationTarget::from_syntax(
543 file_id,
544 self.alias_or_name(db).unwrap_or_else(|| self.name(db)).symbol().clone(),
545 focus_range,
546 full_range,
547 SymbolKind::CrateRoot,
548 );
549
550 res.docs = self.docs(db).map(Documentation::into_owned);
551 res.description = Some(self.display(db, krate.to_display_target(db)).to_string());
552 res.container_name = container_name(db, *self);
553 res
554 },
555 ))
556 }
557}
558
559impl TryToNav for hir::Field {
560 fn try_to_nav(
561 &self,
562 sema: &Semantics<'_, RootDatabase>,
563 ) -> Option<UpmappingResult<NavigationTarget>> {
564 let db = sema.db;
565 let src = self.source(db)?;
566 let krate = self.parent_def(db).module(db).krate(db);
567
568 let field_source = match &src.value {
569 FieldSource::Named(it) => {
570 NavigationTarget::from_named(db, src.with_value(it), SymbolKind::Field).map(
571 |mut res| {
572 res.docs = self.docs(db).map(Documentation::into_owned);
573 res.description =
574 Some(self.display(db, krate.to_display_target(db)).to_string());
575 res
576 },
577 )
578 }
579 FieldSource::Pos(it) => orig_range(db, src.file_id, it.syntax()).map(
580 |(FileRange { file_id, range: full_range }, focus_range)| {
581 NavigationTarget::from_syntax(
582 file_id,
583 Symbol::integer(self.index()),
584 focus_range,
585 full_range,
586 SymbolKind::Field,
587 )
588 },
589 ),
590 };
591 Some(field_source)
592 }
593}
594
595impl TryToNav for hir::Macro {
596 fn try_to_nav(
597 &self,
598 sema: &Semantics<'_, RootDatabase>,
599 ) -> Option<UpmappingResult<NavigationTarget>> {
600 let db = sema.db;
601 let src = self.source(db)?;
602 let name_owner: &dyn ast::HasName = match &src.value {
603 Either::Left(it) => it,
604 Either::Right(it) => it,
605 };
606 Some(
607 NavigationTarget::from_named(
608 db,
609 src.as_ref().with_value(name_owner),
610 self.kind(db).into(),
611 )
612 .map(|mut res| {
613 res.docs = self.docs(db).map(Documentation::into_owned);
614 res
615 }),
616 )
617 }
618}
619
620impl TryToNav for hir::Adt {
621 fn try_to_nav(
622 &self,
623 sema: &Semantics<'_, RootDatabase>,
624 ) -> Option<UpmappingResult<NavigationTarget>> {
625 match self {
626 hir::Adt::Struct(it) => it.try_to_nav(sema),
627 hir::Adt::Union(it) => it.try_to_nav(sema),
628 hir::Adt::Enum(it) => it.try_to_nav(sema),
629 }
630 }
631}
632
633impl TryToNav for hir::AssocItem {
634 fn try_to_nav(
635 &self,
636 sema: &Semantics<'_, RootDatabase>,
637 ) -> Option<UpmappingResult<NavigationTarget>> {
638 match self {
639 AssocItem::Function(it) => it.try_to_nav(sema),
640 AssocItem::Const(it) => it.try_to_nav(sema),
641 AssocItem::TypeAlias(it) => it.try_to_nav(sema),
642 }
643 }
644}
645
646impl TryToNav for hir::GenericParam {
647 fn try_to_nav(
648 &self,
649 sema: &Semantics<'_, RootDatabase>,
650 ) -> Option<UpmappingResult<NavigationTarget>> {
651 match self {
652 hir::GenericParam::TypeParam(it) => it.try_to_nav(sema),
653 hir::GenericParam::ConstParam(it) => it.try_to_nav(sema),
654 hir::GenericParam::LifetimeParam(it) => it.try_to_nav(sema),
655 }
656 }
657}
658
659impl ToNav for LocalSource {
660 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
661 let InFile { file_id, value } = &self.source;
662 let file_id = *file_id;
663 let local = self.local;
664 let (node, name) = match &value {
665 Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
666 Either::Right(it) => (it.syntax(), it.name()),
667 };
668
669 orig_range_with_focus(db, file_id, node, name).map(
670 |(FileRange { file_id, range: full_range }, focus_range)| {
671 let name = local.name(db).symbol().clone();
672 let kind = if local.is_self(db) {
673 SymbolKind::SelfParam
674 } else if local.is_param(db) {
675 SymbolKind::ValueParam
676 } else {
677 SymbolKind::Local
678 };
679 NavigationTarget {
680 file_id,
681 name,
682 alias: None,
683 kind: Some(kind),
684 full_range,
685 focus_range,
686 container_name: None,
687 description: None,
688 docs: None,
689 }
690 },
691 )
692 }
693}
694
695impl ToNav for hir::Local {
696 fn to_nav(&self, db: &RootDatabase) -> UpmappingResult<NavigationTarget> {
697 self.primary_source(db).to_nav(db)
698 }
699}
700
701impl TryToNav for hir::Label {
702 fn try_to_nav(
703 &self,
704 sema: &Semantics<'_, RootDatabase>,
705 ) -> Option<UpmappingResult<NavigationTarget>> {
706 let db = sema.db;
707 let InFile { file_id, value } = self.source(db)?;
708 let name = self.name(db).symbol().clone();
709
710 Some(orig_range_with_focus(db, file_id, value.syntax(), value.lifetime()).map(
711 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
712 file_id,
713 name: name.clone(),
714 alias: None,
715 kind: Some(SymbolKind::Label),
716 full_range,
717 focus_range,
718 container_name: None,
719 description: None,
720 docs: None,
721 },
722 ))
723 }
724}
725
726impl TryToNav for hir::TypeParam {
727 fn try_to_nav(
728 &self,
729 sema: &Semantics<'_, RootDatabase>,
730 ) -> Option<UpmappingResult<NavigationTarget>> {
731 let db = sema.db;
732 let InFile { file_id, value } = self.merge().source(db)?;
733 let name = self.name(db).symbol().clone();
734
735 let value = match value {
736 Either::Left(ast::TypeOrConstParam::Type(x)) => Either::Left(x),
737 Either::Left(ast::TypeOrConstParam::Const(_)) => {
738 never!();
739 return None;
740 }
741 Either::Right(x) => Either::Right(x),
742 };
743
744 let syntax = match &value {
745 Either::Left(type_param) => type_param.syntax(),
746 Either::Right(trait_) => trait_.syntax(),
747 };
748 let focus = value.as_ref().either(|it| it.name(), |it| it.name());
749
750 Some(orig_range_with_focus(db, file_id, syntax, focus).map(
751 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
752 file_id,
753 name: name.clone(),
754 alias: None,
755 kind: Some(SymbolKind::TypeParam),
756 full_range,
757 focus_range,
758 container_name: None,
759 description: None,
760 docs: None,
761 },
762 ))
763 }
764}
765
766impl TryToNav for hir::TypeOrConstParam {
767 fn try_to_nav(
768 &self,
769 sema: &Semantics<'_, RootDatabase>,
770 ) -> Option<UpmappingResult<NavigationTarget>> {
771 self.split(sema.db).try_to_nav(sema)
772 }
773}
774
775impl TryToNav for hir::LifetimeParam {
776 fn try_to_nav(
777 &self,
778 sema: &Semantics<'_, RootDatabase>,
779 ) -> Option<UpmappingResult<NavigationTarget>> {
780 let db = sema.db;
781 let InFile { file_id, value } = self.source(db)?;
782 let name = self.name(db).symbol().clone();
783
784 Some(orig_range(db, file_id, value.syntax()).map(
785 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
786 file_id,
787 name: name.clone(),
788 alias: None,
789 kind: Some(SymbolKind::LifetimeParam),
790 full_range,
791 focus_range,
792 container_name: None,
793 description: None,
794 docs: None,
795 },
796 ))
797 }
798}
799
800impl TryToNav for hir::ConstParam {
801 fn try_to_nav(
802 &self,
803 sema: &Semantics<'_, RootDatabase>,
804 ) -> Option<UpmappingResult<NavigationTarget>> {
805 let db = sema.db;
806 let InFile { file_id, value } = self.merge().source(db)?;
807 let name = self.name(db).symbol().clone();
808
809 let value = match value {
810 Either::Left(ast::TypeOrConstParam::Const(x)) => x,
811 _ => {
812 never!();
813 return None;
814 }
815 };
816
817 Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
818 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
819 file_id,
820 name: name.clone(),
821 alias: None,
822 kind: Some(SymbolKind::ConstParam),
823 full_range,
824 focus_range,
825 container_name: None,
826 description: None,
827 docs: None,
828 },
829 ))
830 }
831}
832
833impl TryToNav for hir::InlineAsmOperand {
834 fn try_to_nav(
835 &self,
836 sema: &Semantics<'_, RootDatabase>,
837 ) -> Option<UpmappingResult<NavigationTarget>> {
838 let db = sema.db;
839 let InFile { file_id, value } = &self.source(db)?;
840 let file_id = *file_id;
841 Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
842 |(FileRange { file_id, range: full_range }, focus_range)| NavigationTarget {
843 file_id,
844 name:
845 self.name(db).map_or_else(|| sym::underscore.clone(), |it| it.symbol().clone()),
846 alias: None,
847 kind: Some(SymbolKind::Local),
848 full_range,
849 focus_range,
850 container_name: None,
851 description: None,
852 docs: None,
853 },
854 ))
855 }
856}
857
858impl TryToNav for hir::BuiltinType {
859 fn try_to_nav(
860 &self,
861 sema: &Semantics<'_, RootDatabase>,
862 ) -> Option<UpmappingResult<NavigationTarget>> {
863 let db = sema.db;
864 let krate = db
865 .all_crates()
866 .iter()
867 .copied()
868 .find(|&krate| matches!(krate.data(db).origin, CrateOrigin::Lang(LangCrateOrigin::Std)))
869 .map(Crate::from)?;
870 let edition = krate.edition(db);
871
872 let fd = FamousDefs(sema, krate);
873 let primitive_mod = format!("prim_{}", self.name().display(fd.0.db, edition));
874 let doc_owner = find_std_module(&fd, &primitive_mod, edition)?;
875
876 Some(doc_owner.to_nav(db))
877 }
878}
879
880#[derive(Debug)]
881pub struct UpmappingResult<T> {
882 pub call_site: T,
884 pub def_site: Option<T>,
886}
887
888impl<T> UpmappingResult<T> {
889 pub fn call_site(self) -> T {
890 self.call_site
891 }
892
893 pub fn collect<FI: FromIterator<T>>(self) -> FI {
894 FI::from_iter(self)
895 }
896}
897
898impl<T> IntoIterator for UpmappingResult<T> {
899 type Item = T;
900
901 type IntoIter = <ArrayVec<T, 2> as IntoIterator>::IntoIter;
902
903 fn into_iter(self) -> Self::IntoIter {
904 self.def_site
905 .into_iter()
906 .chain(Some(self.call_site))
907 .collect::<ArrayVec<_, 2>>()
908 .into_iter()
909 }
910}
911
912impl<T> UpmappingResult<T> {
913 pub(crate) fn map<U>(self, f: impl Fn(T) -> U) -> UpmappingResult<U> {
914 UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) }
915 }
916}
917
918fn orig_range_with_focus(
922 db: &RootDatabase,
923 hir_file: HirFileId,
924 value: &SyntaxNode,
925 name: Option<impl AstNode>,
926) -> UpmappingResult<(FileRange, Option<TextRange>)> {
927 orig_range_with_focus_r(
928 db,
929 hir_file,
930 value.text_range(),
931 name.map(|it| it.syntax().text_range()),
932 )
933}
934
935pub(crate) fn orig_range_with_focus_r(
936 db: &RootDatabase,
937 hir_file: HirFileId,
938 value: TextRange,
939 focus_range: Option<TextRange>,
940) -> UpmappingResult<(FileRange, Option<TextRange>)> {
941 let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) };
942
943 let call = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap());
944
945 let def_range =
946 || db.lookup_intern_macro_call(hir_file.macro_file().unwrap()).def.definition_range(db);
947
948 let value_range = InFile::new(hir_file, value).original_node_file_range_opt(db);
950 let ((call_site_range, call_site_focus), def_site) =
951 match InFile::new(hir_file, name).original_node_file_range_opt(db) {
952 Some((focus_range, ctxt)) if ctxt.is_root() => {
954 (
956 (
957 match value_range {
958 Some((range, ctxt))
960 if ctxt.is_root()
961 && range.file_id == focus_range.file_id
962 && range.range.contains_range(focus_range.range) =>
963 {
964 range
965 }
966 _ => {
969 let call = call();
970 let kind = call.kind;
971 let range = kind.clone().original_call_range_with_input(db);
972 if range.file_id == focus_range.file_id
978 && range.range.contains_range(focus_range.range)
979 {
980 range
981 } else {
982 kind.original_call_range(db, call.krate)
983 }
984 }
985 },
986 Some(focus_range),
987 ),
988 None,
990 )
991 }
992
993 Some((focus_range, _ctxt)) => {
996 match value_range {
997 Some((range, ctxt)) if ctxt.is_root() => (
999 (range, None),
1001 {
1004 let (def_site, _) = def_range().original_node_file_range(db);
1005 (def_site.file_id == focus_range.file_id
1006 && def_site.range.contains_range(focus_range.range))
1007 .then_some((def_site, Some(focus_range)))
1008 },
1009 ),
1010 _ => {
1012 let call = call();
1013 (
1014 (call.kind.original_call_range(db, call.krate), None),
1016 Some((focus_range, Some(focus_range))),
1017 )
1018 }
1019 }
1020 }
1021 None => return orig_range_r(db, hir_file, value),
1023 };
1024
1025 UpmappingResult {
1026 call_site: (
1027 call_site_range.into_file_id(db),
1028 call_site_focus.and_then(|hir::FileRange { file_id, range }| {
1029 if call_site_range.file_id == file_id && call_site_range.range.contains_range(range)
1030 {
1031 Some(range)
1032 } else {
1033 None
1034 }
1035 }),
1036 ),
1037 def_site: def_site.map(|(def_site_range, def_site_focus)| {
1038 (
1039 def_site_range.into_file_id(db),
1040 def_site_focus.and_then(|hir::FileRange { file_id, range }| {
1041 if def_site_range.file_id == file_id
1042 && def_site_range.range.contains_range(range)
1043 {
1044 Some(range)
1045 } else {
1046 None
1047 }
1048 }),
1049 )
1050 }),
1051 }
1052}
1053
1054fn orig_range(
1055 db: &RootDatabase,
1056 hir_file: HirFileId,
1057 value: &SyntaxNode,
1058) -> UpmappingResult<(FileRange, Option<TextRange>)> {
1059 UpmappingResult {
1060 call_site: (
1061 InFile::new(hir_file, value).original_file_range_rooted(db).into_file_id(db),
1062 None,
1063 ),
1064 def_site: None,
1065 }
1066}
1067
1068fn orig_range_r(
1069 db: &RootDatabase,
1070 hir_file: HirFileId,
1071 value: TextRange,
1072) -> UpmappingResult<(FileRange, Option<TextRange>)> {
1073 UpmappingResult {
1074 call_site: (
1075 InFile::new(hir_file, value).original_node_file_range(db).0.into_file_id(db),
1076 None,
1077 ),
1078 def_site: None,
1079 }
1080}
1081
1082#[cfg(test)]
1083mod tests {
1084 use expect_test::expect;
1085
1086 use crate::{Query, fixture};
1087
1088 #[test]
1089 fn test_nav_for_symbol() {
1090 let (analysis, _) = fixture::file(
1091 r#"
1092enum FooInner { }
1093fn foo() { enum FooInner { } }
1094"#,
1095 );
1096
1097 let navs = analysis.symbol_search(Query::new("FooInner".to_owned()), !0).unwrap();
1098 expect![[r#"
1099 [
1100 NavigationTarget {
1101 file_id: FileId(
1102 0,
1103 ),
1104 full_range: 0..17,
1105 focus_range: 5..13,
1106 name: "FooInner",
1107 kind: Enum,
1108 description: "enum FooInner",
1109 },
1110 NavigationTarget {
1111 file_id: FileId(
1112 0,
1113 ),
1114 full_range: 29..46,
1115 focus_range: 34..42,
1116 name: "FooInner",
1117 kind: Enum,
1118 container_name: "foo",
1119 description: "enum FooInner",
1120 },
1121 ]
1122 "#]]
1123 .assert_debug_eq(&navs);
1124 }
1125
1126 #[test]
1127 fn test_world_symbols_are_case_sensitive() {
1128 let (analysis, _) = fixture::file(
1129 r#"
1130fn foo() {}
1131struct Foo;
1132"#,
1133 );
1134
1135 let navs = analysis.symbol_search(Query::new("foo".to_owned()), !0).unwrap();
1136 assert_eq!(navs.len(), 2)
1137 }
1138
1139 #[test]
1140 fn test_ensure_hidden_symbols_are_not_returned() {
1141 let (analysis, _) = fixture::file(
1142 r#"
1143fn foo() {}
1144struct Foo;
1145static __FOO_CALLSITE: () = ();
1146"#,
1147 );
1148
1149 let navs = analysis.symbol_search(Query::new("foo".to_owned()), !0).unwrap();
1151 assert_eq!(navs.len(), 2);
1152 let navs = analysis.symbol_search(Query::new("_foo".to_owned()), !0).unwrap();
1153 assert_eq!(navs.len(), 0);
1154
1155 let query = Query::new("__foo".to_owned());
1157 let navs = analysis.symbol_search(query, !0).unwrap();
1158 assert_eq!(navs.len(), 1);
1159 }
1160}