Skip to main content

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, TargetKind},
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
158        | INNER_DOC_COMMENT
159        | OUTER_DOC_COMMENT => 2,
160        kind if kind.is_trivia() => 0,
161        _ => 1,
162    })?;
163    if let Some(doc_comment) = token_as_doc_comment(&original_token) {
164        return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| {
165            let m = def_to_moniker(db, def, current_crate)?;
166            Some(RangeInfo::new(original_token.text_range(), vec![m]))
167        });
168    }
169    let navs = sema
170        .descend_into_macros_exact(original_token.clone())
171        .into_iter()
172        .filter_map(|token| {
173            IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| {
174                it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate))
175            })
176        })
177        .flatten()
178        .unique()
179        .collect::<Vec<_>>();
180    Some(RangeInfo::new(original_token.text_range(), navs))
181}
182
183pub(crate) fn def_to_kind(db: &RootDatabase, def: Definition<'_>) -> SymbolInformationKind {
184    use SymbolInformationKind::*;
185
186    match def {
187        Definition::Macro(it) => match it.kind(db) {
188            MacroKind::Derive
189            | MacroKind::DeriveBuiltIn
190            | MacroKind::AttrBuiltIn
191            | MacroKind::Attr => Attribute,
192            MacroKind::Declarative | MacroKind::DeclarativeBuiltIn | MacroKind::ProcMacro => Macro,
193        },
194        Definition::Field(..) | Definition::TupleField(..) => Field,
195        Definition::Module(..) | Definition::Crate(..) => Module,
196        Definition::Function(it) => {
197            if it.as_assoc_item(db).is_some() {
198                if it.has_self_param(db) {
199                    if it.has_body(db) { Method } else { TraitMethod }
200                } else {
201                    StaticMethod
202                }
203            } else {
204                Function
205            }
206        }
207        Definition::Adt(Adt::Struct(..)) => Struct,
208        Definition::Adt(Adt::Union(..)) => Union,
209        Definition::Adt(Adt::Enum(..)) => Enum,
210        Definition::EnumVariant(..) => EnumMember,
211        Definition::Const(..) => Constant,
212        Definition::Static(..) => StaticVariable,
213        Definition::Trait(..) => Trait,
214        Definition::TypeAlias(it) => {
215            if it.as_assoc_item(db).is_some() {
216                AssociatedType
217            } else {
218                TypeAlias
219            }
220        }
221        Definition::BuiltinType(..) => Type,
222        Definition::BuiltinLifetime(_) => TypeParameter,
223        Definition::SelfType(..) => TypeAlias,
224        Definition::GenericParam(..) => TypeParameter,
225        Definition::Local(it) => {
226            if it.is_self(db) {
227                SelfParameter
228            } else if it.is_param(db) {
229                Parameter
230            } else {
231                Variable
232            }
233        }
234        Definition::Label(..) | Definition::InlineAsmOperand(_) => Variable, // For lack of a better variant
235        Definition::DeriveHelper(..) => Attribute,
236        Definition::BuiltinAttr(..) => Attribute,
237        Definition::ToolModule(..) => Module,
238        Definition::ExternCrateDecl(..) => Module,
239        Definition::InlineAsmRegOrRegClass(..) => Module,
240    }
241}
242
243/// Computes a `MonikerResult` for a definition. Result cases:
244///
245/// * `Some(MonikerResult::Moniker(_))` provides a unique `Moniker` which refers to a definition.
246///
247/// * `Some(MonikerResult::Local { .. })` provides a `Moniker` for the definition enclosing a local.
248///
249/// * `None` is returned for definitions which are not in a module: `BuiltinAttr`, `BuiltinType`,
250///   `BuiltinLifetime`, `TupleField`, `ToolModule`, and `InlineAsmRegOrRegClass`. TODO: it might be
251///   sensible to provide monikers that refer to some non-existent crate of compiler builtin
252///   definitions.
253pub(crate) fn def_to_moniker(
254    db: &RootDatabase,
255    definition: Definition<'_>,
256    from_crate: Crate,
257) -> Option<MonikerResult> {
258    match definition {
259        Definition::Local(_) | Definition::Label(_) | Definition::GenericParam(_) => {
260            return Some(MonikerResult::Local {
261                enclosing_moniker: enclosing_def_to_moniker(db, definition, from_crate),
262            });
263        }
264        _ => {}
265    }
266    Some(MonikerResult::Moniker(def_to_non_local_moniker(db, definition, from_crate)?))
267}
268
269fn enclosing_def_to_moniker(
270    db: &RootDatabase,
271    mut def: Definition<'_>,
272    from_crate: Crate,
273) -> Option<Moniker> {
274    loop {
275        let enclosing_def = def.enclosing_definition(db)?;
276        if let Some(enclosing_moniker) = def_to_non_local_moniker(db, enclosing_def, from_crate) {
277            return Some(enclosing_moniker);
278        }
279        def = enclosing_def;
280    }
281}
282
283fn def_to_non_local_moniker(
284    db: &RootDatabase,
285    definition: Definition<'_>,
286    from_crate: Crate,
287) -> Option<Moniker> {
288    let module = match definition {
289        Definition::Module(module) if module.is_crate_root(db) => module,
290        _ => definition.module(db)?,
291    };
292    let krate = module.krate(db);
293    let edition = krate.edition(db);
294
295    // Add descriptors for this definition and every enclosing definition.
296    let mut reverse_description = vec![];
297    let mut def = definition;
298    loop {
299        match def {
300            Definition::SelfType(impl_) => {
301                if let Some(trait_ref) = impl_.trait_ref(db) {
302                    // Trait impls use the trait type for the 2nd parameter.
303                    reverse_description.push(MonikerDescriptor {
304                        name: display(db, module, trait_ref),
305                        desc: MonikerDescriptorKind::TypeParameter,
306                    });
307                }
308                // Both inherent and trait impls use the self type for the first parameter.
309                reverse_description.push(MonikerDescriptor {
310                    name: display(db, module, impl_.self_ty(db)),
311                    desc: MonikerDescriptorKind::TypeParameter,
312                });
313                reverse_description.push(MonikerDescriptor {
314                    name: "impl".to_owned(),
315                    desc: MonikerDescriptorKind::Type,
316                });
317            }
318            _ => {
319                if let Some(name) = def.name(db) {
320                    reverse_description.push(MonikerDescriptor {
321                        name: name.display(db, edition).to_string(),
322                        desc: def_to_kind(db, def).into(),
323                    });
324                } else {
325                    match def {
326                        Definition::Module(module) if module.is_crate_root(db) => {
327                            let krate = module.krate(db);
328
329                            // There can be only one lib, proc-macro or build script per crates, so we don't need to specify the display name in these cases.
330                            if !matches!(
331                                krate.target_kind(db),
332                                TargetKind::Lib { .. } | TargetKind::BuildScript
333                            ) && let Some(display_name) = krate.display_name(db)
334                            {
335                                reverse_description.push(MonikerDescriptor {
336                                    name: display_name.to_string(),
337                                    desc: MonikerDescriptorKind::Namespace,
338                                });
339                            }
340
341                            if let Some(target_type) = krate.target_kind(db).as_cargo_target() {
342                                reverse_description.push(MonikerDescriptor {
343                                    name: target_type.to_owned(),
344                                    desc: MonikerDescriptorKind::Namespace,
345                                });
346                            }
347
348                            // Fallback in case we didn't get anything: when we have a target_kind of Other and no display name.
349                            if reverse_description.is_empty() {
350                                reverse_description.push(MonikerDescriptor {
351                                    name: "crate".to_owned(),
352                                    desc: MonikerDescriptorKind::Namespace,
353                                });
354                            }
355                        }
356                        _ => {
357                            tracing::error!(?def, "Encountered enclosing definition with no name");
358                        }
359                    }
360                }
361            }
362        }
363        let Some(next_def) = def.enclosing_definition(db) else {
364            break;
365        };
366        def = next_def;
367    }
368    if reverse_description.is_empty() {
369        return None;
370    }
371    reverse_description.reverse();
372    let description = reverse_description;
373
374    Some(Moniker {
375        identifier: MonikerIdentifier {
376            crate_name: krate.display_name(db)?.crate_name().to_string(),
377            description,
378        },
379        kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import },
380        package_information: {
381            let (name, repo, version) = match krate.origin(db) {
382                CrateOrigin::Library { repo, name } => (name, repo, krate.version(db)),
383                CrateOrigin::Local { repo, name } => (
384                    name.unwrap_or(krate.display_name(db)?.canonical_name().to_owned()),
385                    repo,
386                    krate.version(db),
387                ),
388                CrateOrigin::Rustc { name } => (
389                    name.clone(),
390                    Some("https://github.com/rust-lang/rust/".to_owned()),
391                    Some(format!("https://github.com/rust-lang/rust/compiler/{name}",)),
392                ),
393                CrateOrigin::Lang(lang) => (
394                    krate.display_name(db)?.canonical_name().to_owned(),
395                    Some("https://github.com/rust-lang/rust/".to_owned()),
396                    Some(match lang {
397                        LangCrateOrigin::Other => {
398                            "https://github.com/rust-lang/rust/library/".into()
399                        }
400                        lang => format!("https://github.com/rust-lang/rust/library/{lang}",),
401                    }),
402                ),
403            };
404            PackageInformation { name: name.as_str().to_owned(), repo, version }
405        },
406    })
407}
408
409fn display<'db, T: HirDisplay<'db>>(db: &'db RootDatabase, module: hir::Module, it: T) -> String {
410    match it.display_source_code(db, module.into(), true) {
411        Ok(result) => result,
412        // Fallback on display variant that always succeeds
413        Err(_) => {
414            let fallback_result =
415                it.display(db, module.krate(db).to_display_target(db)).to_string();
416            tracing::error!(
417                display = %fallback_result, "`display_source_code` failed; falling back to using display"
418            );
419            fallback_result
420        }
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use crate::{MonikerResult, fixture};
427
428    use super::MonikerKind;
429
430    #[allow(dead_code)]
431    #[track_caller]
432    fn no_moniker(#[rust_analyzer::rust_fixture] ra_fixture: &str) {
433        let (analysis, position) = fixture::position(ra_fixture);
434        if let Some(x) = analysis.moniker(position).unwrap() {
435            assert_eq!(x.info.len(), 0, "Moniker found but no moniker expected: {x:?}");
436        }
437    }
438
439    #[track_caller]
440    fn check_local_moniker(
441        #[rust_analyzer::rust_fixture] ra_fixture: &str,
442        identifier: &str,
443        package: &str,
444        kind: MonikerKind,
445    ) {
446        let (analysis, position) = fixture::position(ra_fixture);
447        let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
448        assert_eq!(x.len(), 1);
449        match x.into_iter().next().unwrap() {
450            MonikerResult::Local { enclosing_moniker: Some(x) } => {
451                assert_eq!(identifier, x.identifier.to_string());
452                assert_eq!(package, format!("{:?}", x.package_information));
453                assert_eq!(kind, x.kind);
454            }
455            MonikerResult::Local { enclosing_moniker: None } => {
456                panic!("Unexpected local with no enclosing moniker");
457            }
458            MonikerResult::Moniker(_) => {
459                panic!("Unexpected non-local moniker");
460            }
461        }
462    }
463
464    #[track_caller]
465    fn check_moniker(
466        #[rust_analyzer::rust_fixture] ra_fixture: &str,
467        identifier: &str,
468        package: &str,
469        kind: MonikerKind,
470    ) {
471        let (analysis, position) = fixture::position(ra_fixture);
472        let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
473        assert_eq!(x.len(), 1);
474        match x.into_iter().next().unwrap() {
475            MonikerResult::Local { enclosing_moniker } => {
476                panic!("Unexpected local enclosed in {enclosing_moniker:?}");
477            }
478            MonikerResult::Moniker(x) => {
479                assert_eq!(identifier, x.identifier.to_string());
480                assert_eq!(package, format!("{:?}", x.package_information));
481                assert_eq!(kind, x.kind);
482            }
483        }
484    }
485
486    #[test]
487    fn basic() {
488        check_moniker(
489            r#"
490//- /lib.rs crate:main deps:foo
491use foo::module::func;
492fn main() {
493    func$0();
494}
495//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
496pub mod module {
497    pub fn func() {}
498}
499"#,
500            "foo::lib::module::func",
501            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
502            MonikerKind::Import,
503        );
504        check_moniker(
505            r#"
506//- /lib.rs crate:main deps:foo
507use foo::module::func;
508fn main() {
509    func();
510}
511//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
512pub mod module {
513    pub fn func$0() {}
514}
515"#,
516            "foo::lib::module::func",
517            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
518            MonikerKind::Export,
519        );
520    }
521
522    #[test]
523    fn moniker_for_trait() {
524        check_moniker(
525            r#"
526//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
527pub mod module {
528    pub trait MyTrait {
529        pub fn func$0() {}
530    }
531}
532"#,
533            "foo::lib::module::MyTrait::func",
534            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
535            MonikerKind::Export,
536        );
537    }
538
539    #[test]
540    fn moniker_for_trait_constant() {
541        check_moniker(
542            r#"
543//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
544pub mod module {
545    pub trait MyTrait {
546        const MY_CONST$0: u8;
547    }
548}
549"#,
550            "foo::lib::module::MyTrait::MY_CONST",
551            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
552            MonikerKind::Export,
553        );
554    }
555
556    #[test]
557    fn moniker_for_trait_type() {
558        check_moniker(
559            r#"
560//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
561pub mod module {
562    pub trait MyTrait {
563        type MyType$0;
564    }
565}
566"#,
567            "foo::lib::module::MyTrait::MyType",
568            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
569            MonikerKind::Export,
570        );
571    }
572
573    #[test]
574    fn moniker_for_trait_impl_function() {
575        check_moniker(
576            r#"
577//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
578pub mod module {
579    pub trait MyTrait {
580        pub fn func() {}
581    }
582    struct MyStruct {}
583    impl MyTrait for MyStruct {
584        pub fn func$0() {}
585    }
586}
587"#,
588            "foo::lib::module::impl::MyStruct::MyTrait::func",
589            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
590            MonikerKind::Export,
591        );
592    }
593
594    #[test]
595    fn moniker_for_field() {
596        check_moniker(
597            r#"
598//- /lib.rs crate:main deps:foo
599use foo::St;
600fn main() {
601    let x = St { a$0: 2 };
602}
603//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
604pub struct St {
605    pub a: i32,
606}
607"#,
608            "foo::lib::St::a",
609            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
610            MonikerKind::Import,
611        );
612    }
613
614    #[test]
615    fn local() {
616        check_local_moniker(
617            r#"
618//- /lib.rs crate:main deps:foo
619use foo::module::func;
620fn main() {
621    func();
622}
623//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
624pub mod module {
625    pub fn func() {
626        let x$0 = 2;
627    }
628}
629"#,
630            "foo::lib::module::func",
631            r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
632            MonikerKind::Export,
633        );
634    }
635}