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