ide/
moniker.rs

1//! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
2//! for LSIF and LSP.
3
4use core::fmt;
5
6use hir::{Adt, AsAssocItem, Crate, HirDisplay, MacroKind, Semantics};
7use ide_db::{
8    FilePosition, RootDatabase,
9    base_db::{CrateOrigin, LangCrateOrigin},
10    defs::{Definition, IdentClass},
11    helpers::pick_best_token,
12};
13use itertools::Itertools;
14use syntax::{AstNode, SyntaxKind::*, T};
15
16use crate::{RangeInfo, doc_links::token_as_doc_comment, parent_module::crates_for};
17
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub enum MonikerDescriptorKind {
20    Namespace,
21    Type,
22    Term,
23    Method,
24    TypeParameter,
25    Parameter,
26    Macro,
27    Meta,
28}
29
30// Subset of scip_types::SymbolInformation::Kind
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum SymbolInformationKind {
33    AssociatedType,
34    Attribute,
35    Constant,
36    Enum,
37    EnumMember,
38    Field,
39    Function,
40    Macro,
41    Method,
42    Module,
43    Parameter,
44    SelfParameter,
45    StaticMethod,
46    StaticVariable,
47    Struct,
48    Trait,
49    TraitMethod,
50    Type,
51    TypeAlias,
52    TypeParameter,
53    Union,
54    Variable,
55}
56
57impl From<SymbolInformationKind> for MonikerDescriptorKind {
58    fn from(value: SymbolInformationKind) -> Self {
59        match value {
60            SymbolInformationKind::AssociatedType => Self::Type,
61            SymbolInformationKind::Attribute => Self::Meta,
62            SymbolInformationKind::Constant => Self::Term,
63            SymbolInformationKind::Enum => Self::Type,
64            SymbolInformationKind::EnumMember => Self::Type,
65            SymbolInformationKind::Field => Self::Term,
66            SymbolInformationKind::Function => Self::Method,
67            SymbolInformationKind::Macro => Self::Macro,
68            SymbolInformationKind::Method => Self::Method,
69            SymbolInformationKind::Module => Self::Namespace,
70            SymbolInformationKind::Parameter => Self::Parameter,
71            SymbolInformationKind::SelfParameter => Self::Parameter,
72            SymbolInformationKind::StaticMethod => Self::Method,
73            SymbolInformationKind::StaticVariable => Self::Term,
74            SymbolInformationKind::Struct => Self::Type,
75            SymbolInformationKind::Trait => Self::Type,
76            SymbolInformationKind::TraitMethod => Self::Method,
77            SymbolInformationKind::Type => Self::Type,
78            SymbolInformationKind::TypeAlias => Self::Type,
79            SymbolInformationKind::TypeParameter => Self::TypeParameter,
80            SymbolInformationKind::Union => Self::Type,
81            SymbolInformationKind::Variable => Self::Term,
82        }
83    }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87pub struct MonikerDescriptor {
88    pub name: String,
89    pub desc: MonikerDescriptorKind,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93pub struct MonikerIdentifier {
94    pub crate_name: String,
95    pub description: Vec<MonikerDescriptor>,
96}
97
98impl fmt::Display for MonikerIdentifier {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        f.write_str(&self.crate_name)?;
101        f.write_fmt(format_args!("::{}", self.description.iter().map(|x| &x.name).join("::")))
102    }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
106pub enum MonikerKind {
107    Import,
108    Export,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Hash)]
112pub enum MonikerResult {
113    /// Uniquely identifies a definition.
114    Moniker(Moniker),
115    /// Specifies that the definition is a local, and so does not have a unique identifier. Provides
116    /// a unique identifier for the container.
117    Local { enclosing_moniker: Option<Moniker> },
118}
119
120impl MonikerResult {
121    pub fn from_def(db: &RootDatabase, def: Definition, from_crate: Crate) -> Option<Self> {
122        def_to_moniker(db, def, from_crate)
123    }
124}
125
126/// Information which uniquely identifies a definition which might be referenceable outside of the
127/// source file. Visibility declarations do not affect presence.
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129pub struct Moniker {
130    pub identifier: MonikerIdentifier,
131    pub kind: MonikerKind,
132    pub package_information: PackageInformation,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
136pub struct PackageInformation {
137    pub name: String,
138    pub repo: Option<String>,
139    pub version: Option<String>,
140}
141
142pub(crate) fn moniker(
143    db: &RootDatabase,
144    FilePosition { file_id, offset }: FilePosition,
145) -> Option<RangeInfo<Vec<MonikerResult>>> {
146    let sema = &Semantics::new(db);
147    let file = sema.parse_guess_edition(file_id).syntax().clone();
148    let current_crate: hir::Crate = crates_for(db, file_id).pop()?.into();
149    let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
150        IDENT
151        | INT_NUMBER
152        | LIFETIME_IDENT
153        | T![self]
154        | T![super]
155        | T![crate]
156        | T![Self]
157        | COMMENT => 2,
158        kind if kind.is_trivia() => 0,
159        _ => 1,
160    })?;
161    if let Some(doc_comment) = token_as_doc_comment(&original_token) {
162        return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
163            let m = def_to_moniker(db, def, current_crate)?;
164            Some(RangeInfo::new(original_token.text_range(), vec![m]))
165        });
166    }
167    let navs = sema
168        .descend_into_macros_exact(original_token.clone())
169        .into_iter()
170        .filter_map(|token| {
171            IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| {
172                it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
173            })
174        })
175        .flatten()
176        .unique()
177        .collect::<Vec<_>>();
178    Some(RangeInfo::new(original_token.text_range(), navs))
179}
180
181pub(crate) fn def_to_kind(db: &RootDatabase, def: Definition) -> SymbolInformationKind {
182    use SymbolInformationKind::*;
183
184    match def {
185        Definition::Macro(it) => match it.kind(db) {
186            MacroKind::Derive
187            | MacroKind::DeriveBuiltIn
188            | MacroKind::AttrBuiltIn
189            | MacroKind::Attr => Attribute,
190            MacroKind::Declarative | MacroKind::DeclarativeBuiltIn | MacroKind::ProcMacro => Macro,
191        },
192        Definition::Field(..) | Definition::TupleField(..) => Field,
193        Definition::Module(..) | Definition::Crate(..) => Module,
194        Definition::Function(it) => {
195            if it.as_assoc_item(db).is_some() {
196                if it.has_self_param(db) {
197                    if it.has_body(db) { Method } else { TraitMethod }
198                } else {
199                    StaticMethod
200                }
201            } else {
202                Function
203            }
204        }
205        Definition::Adt(Adt::Struct(..)) => Struct,
206        Definition::Adt(Adt::Union(..)) => Union,
207        Definition::Adt(Adt::Enum(..)) => Enum,
208        Definition::Variant(..) => EnumMember,
209        Definition::Const(..) => Constant,
210        Definition::Static(..) => StaticVariable,
211        Definition::Trait(..) => Trait,
212        Definition::TraitAlias(..) => Trait,
213        Definition::TypeAlias(it) => {
214            if it.as_assoc_item(db).is_some() {
215                AssociatedType
216            } else {
217                TypeAlias
218            }
219        }
220        Definition::BuiltinType(..) => Type,
221        Definition::BuiltinLifetime(_) => TypeParameter,
222        Definition::SelfType(..) => TypeAlias,
223        Definition::GenericParam(..) => TypeParameter,
224        Definition::Local(it) => {
225            if it.is_self(db) {
226                SelfParameter
227            } else if it.is_param(db) {
228                Parameter
229            } else {
230                Variable
231            }
232        }
233        Definition::Label(..) | Definition::InlineAsmOperand(_) => Variable, // For lack of a better variant
234        Definition::DeriveHelper(..) => Attribute,
235        Definition::BuiltinAttr(..) => Attribute,
236        Definition::ToolModule(..) => Module,
237        Definition::ExternCrateDecl(..) => Module,
238        Definition::InlineAsmRegOrRegClass(..) => Module,
239    }
240}
241
242/// Computes a `MonikerResult` for a definition. Result cases:
243///
244/// * `Some(MonikerResult::Moniker(_))` provides a unique `Moniker` which refers to a definition.
245///
246/// * `Some(MonikerResult::Local { .. })` provides a `Moniker` for the definition enclosing a local.
247///
248/// * `None` is returned for definitions which are not in a module: `BuiltinAttr`, `BuiltinType`,
249///   `BuiltinLifetime`, `TupleField`, `ToolModule`, and `InlineAsmRegOrRegClass`. TODO: it might be
250///   sensible to provide monikers that refer to some non-existent crate of compiler builtin
251///   definitions.
252pub(crate) fn def_to_moniker(
253    db: &RootDatabase,
254    definition: Definition,
255    from_crate: Crate,
256) -> Option<MonikerResult> {
257    match definition {
258        Definition::Local(_) | Definition::Label(_) | Definition::GenericParam(_) => {
259            return Some(MonikerResult::Local {
260                enclosing_moniker: enclosing_def_to_moniker(db, definition, from_crate),
261            });
262        }
263        _ => {}
264    }
265    Some(MonikerResult::Moniker(def_to_non_local_moniker(db, definition, from_crate)?))
266}
267
268fn enclosing_def_to_moniker(
269    db: &RootDatabase,
270    mut def: Definition,
271    from_crate: Crate,
272) -> Option<Moniker> {
273    loop {
274        let enclosing_def = def.enclosing_definition(db)?;
275        if let Some(enclosing_moniker) = def_to_non_local_moniker(db, enclosing_def, from_crate) {
276            return Some(enclosing_moniker);
277        }
278        def = enclosing_def;
279    }
280}
281
282fn def_to_non_local_moniker(
283    db: &RootDatabase,
284    definition: Definition,
285    from_crate: Crate,
286) -> Option<Moniker> {
287    let module = match definition {
288        Definition::Module(module) if module.is_crate_root() => module,
289        _ => definition.module(db)?,
290    };
291    let krate = module.krate();
292    let edition = krate.edition(db);
293
294    // Add descriptors for this definition and every enclosing definition.
295    let mut reverse_description = vec![];
296    let mut def = definition;
297    loop {
298        match def {
299            Definition::SelfType(impl_) => {
300                if let Some(trait_ref) = impl_.trait_ref(db) {
301                    // Trait impls use the trait type for the 2nd parameter.
302                    reverse_description.push(MonikerDescriptor {
303                        name: display(db, module, trait_ref),
304                        desc: MonikerDescriptorKind::TypeParameter,
305                    });
306                }
307                // Both inherent and trait impls use the self type for the first parameter.
308                reverse_description.push(MonikerDescriptor {
309                    name: display(db, module, impl_.self_ty(db)),
310                    desc: MonikerDescriptorKind::TypeParameter,
311                });
312                reverse_description.push(MonikerDescriptor {
313                    name: "impl".to_owned(),
314                    desc: MonikerDescriptorKind::Type,
315                });
316            }
317            _ => {
318                if let Some(name) = def.name(db) {
319                    reverse_description.push(MonikerDescriptor {
320                        name: name.display(db, edition).to_string(),
321                        desc: def_to_kind(db, def).into(),
322                    });
323                } else {
324                    match def {
325                        Definition::Module(module) if module.is_crate_root() => {
326                            // only include `crate` namespace by itself because we prefer
327                            // `rust-analyzer cargo foo . bar/` over `rust-analyzer cargo foo . crate/bar/`
328                            if reverse_description.is_empty() {
329                                reverse_description.push(MonikerDescriptor {
330                                    name: "crate".to_owned(),
331                                    desc: MonikerDescriptorKind::Namespace,
332                                });
333                            }
334                        }
335                        _ => {
336                            tracing::error!(?def, "Encountered enclosing definition with no name");
337                        }
338                    }
339                }
340            }
341        }
342        let Some(next_def) = def.enclosing_definition(db) else {
343            break;
344        };
345        def = next_def;
346    }
347    if reverse_description.is_empty() {
348        return None;
349    }
350    reverse_description.reverse();
351    let description = reverse_description;
352
353    Some(Moniker {
354        identifier: MonikerIdentifier {
355            crate_name: krate.display_name(db)?.crate_name().to_string(),
356            description,
357        },
358        kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
359        package_information: {
360            let (name, repo, version) = match krate.origin(db) {
361                CrateOrigin::Library { repo, name } => (name, repo, krate.version(db)),
362                CrateOrigin::Local { repo, name } => (
363                    name.unwrap_or(krate.display_name(db)?.canonical_name().to_owned()),
364                    repo,
365                    krate.version(db),
366                ),
367                CrateOrigin::Rustc { name } => (
368                    name.clone(),
369                    Some("https://github.com/rust-lang/rust/".to_owned()),
370                    Some(format!("https://github.com/rust-lang/rust/compiler/{name}",)),
371                ),
372                CrateOrigin::Lang(lang) => (
373                    krate.display_name(db)?.canonical_name().to_owned(),
374                    Some("https://github.com/rust-lang/rust/".to_owned()),
375                    Some(match lang {
376                        LangCrateOrigin::Other => {
377                            "https://github.com/rust-lang/rust/library/".into()
378                        }
379                        lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
380                    }),
381                ),
382            };
383            PackageInformation { name: name.as_str().to_owned(), repo, version }
384        },
385    })
386}
387
388fn display<T: HirDisplay>(db: &RootDatabase, module: hir::Module, it: T) -> String {
389    match it.display_source_code(db, module.into(), true) {
390        Ok(result) => result,
391        // Fallback on display variant that always succeeds
392        Err(_) => {
393            let fallback_result = it.display(db, module.krate().to_display_target(db)).to_string();
394            tracing::error!(
395                display = %fallback_result, "`display_source_code` failed; falling back to using display"
396            );
397            fallback_result
398        }
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use crate::{MonikerResult, fixture};
405
406    use super::MonikerKind;
407
408    #[allow(dead_code)]
409    #[track_caller]
410    fn no_moniker(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
411        let (analysis, position) = fixture::position(ra_fixture);
412        if let Some(x) = analysis.moniker(position).unwrap() {
413            assert_eq!(x.info.len(), 0, "Moniker found but no moniker expected: {x:?}");
414        }
415    }
416
417    #[track_caller]
418    fn check_local_moniker(
419        #[rust_analyzer::rust_fixture] ra_fixture: &str,
420        identifier: &str,
421        package: &str,
422        kind: MonikerKind,
423    ) {
424        let (analysis, position) = fixture::position(ra_fixture);
425        let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
426        assert_eq!(x.len(), 1);
427        match x.into_iter().next().unwrap() {
428            MonikerResult::Local { enclosing_moniker: Some(x) } => {
429                assert_eq!(identifier, x.identifier.to_string());
430                assert_eq!(package, format!("{:?}", x.package_information));
431                assert_eq!(kind, x.kind);
432            }
433            MonikerResult::Local { enclosing_moniker: None } => {
434                panic!("Unexpected local with no enclosing moniker");
435            }
436            MonikerResult::Moniker(_) => {
437                panic!("Unexpected non-local moniker");
438            }
439        }
440    }
441
442    #[track_caller]
443    fn check_moniker(
444        #[rust_analyzer::rust_fixture] ra_fixture: &str,
445        identifier: &str,
446        package: &str,
447        kind: MonikerKind,
448    ) {
449        let (analysis, position) = fixture::position(ra_fixture);
450        let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
451        assert_eq!(x.len(), 1);
452        match x.into_iter().next().unwrap() {
453            MonikerResult::Local { enclosing_moniker } => {
454                panic!("Unexpected local enclosed in {enclosing_moniker:?}");
455            }
456            MonikerResult::Moniker(x) => {
457                assert_eq!(identifier, x.identifier.to_string());
458                assert_eq!(package, format!("{:?}", x.package_information));
459                assert_eq!(kind, x.kind);
460            }
461        }
462    }
463
464    #[test]
465    fn basic() {
466        check_moniker(
467            r#"
468//- /lib.rs crate:main deps:foo
469use foo::module::func;
470fn main() {
471    func$0();
472}
473//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
474pub mod module {
475    pub fn func() {}
476}
477"#,
478            "foo::module::func",
479            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
480            MonikerKind::Import,
481        );
482        check_moniker(
483            r#"
484//- /lib.rs crate:main deps:foo
485use foo::module::func;
486fn main() {
487    func();
488}
489//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
490pub mod module {
491    pub fn func$0() {}
492}
493"#,
494            "foo::module::func",
495            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
496            MonikerKind::Export,
497        );
498    }
499
500    #[test]
501    fn moniker_for_trait() {
502        check_moniker(
503            r#"
504//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
505pub mod module {
506    pub trait MyTrait {
507        pub fn func$0() {}
508    }
509}
510"#,
511            "foo::module::MyTrait::func",
512            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
513            MonikerKind::Export,
514        );
515    }
516
517    #[test]
518    fn moniker_for_trait_constant() {
519        check_moniker(
520            r#"
521//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
522pub mod module {
523    pub trait MyTrait {
524        const MY_CONST$0: u8;
525    }
526}
527"#,
528            "foo::module::MyTrait::MY_CONST",
529            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
530            MonikerKind::Export,
531        );
532    }
533
534    #[test]
535    fn moniker_for_trait_type() {
536        check_moniker(
537            r#"
538//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
539pub mod module {
540    pub trait MyTrait {
541        type MyType$0;
542    }
543}
544"#,
545            "foo::module::MyTrait::MyType",
546            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
547            MonikerKind::Export,
548        );
549    }
550
551    #[test]
552    fn moniker_for_trait_impl_function() {
553        check_moniker(
554            r#"
555//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
556pub mod module {
557    pub trait MyTrait {
558        pub fn func() {}
559    }
560    struct MyStruct {}
561    impl MyTrait for MyStruct {
562        pub fn func$0() {}
563    }
564}
565"#,
566            "foo::module::impl::MyStruct::MyTrait::func",
567            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
568            MonikerKind::Export,
569        );
570    }
571
572    #[test]
573    fn moniker_for_field() {
574        check_moniker(
575            r#"
576//- /lib.rs crate:main deps:foo
577use foo::St;
578fn main() {
579    let x = St { a$0: 2 };
580}
581//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
582pub struct St {
583    pub a: i32,
584}
585"#,
586            "foo::St::a",
587            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
588            MonikerKind::Import,
589        );
590    }
591
592    #[test]
593    fn local() {
594        check_local_moniker(
595            r#"
596//- /lib.rs crate:main deps:foo
597use foo::module::func;
598fn main() {
599    func();
600}
601//- /foo/lib.rs crate:[email protected],https://a.b/foo.git library
602pub mod module {
603    pub fn func() {
604        let x$0 = 2;
605    }
606}
607"#,
608            "foo::module::func",
609            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
610            MonikerKind::Export,
611        );
612    }
613}