| //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports) |
| //! for LSIF and LSP. |
| |
| use core::fmt; |
| |
| use hir::{Adt, AsAssocItem, Crate, HirDisplay, MacroKind, Semantics}; |
| use ide_db::{ |
| FilePosition, RootDatabase, |
| base_db::{CrateOrigin, LangCrateOrigin}, |
| defs::{Definition, IdentClass}, |
| helpers::pick_best_token, |
| }; |
| use itertools::Itertools; |
| use syntax::{AstNode, SyntaxKind::*, T}; |
| |
| use crate::{RangeInfo, doc_links::token_as_doc_comment, parent_module::crates_for}; |
| |
| #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub enum MonikerDescriptorKind { |
| Namespace, |
| Type, |
| Term, |
| Method, |
| TypeParameter, |
| Parameter, |
| Macro, |
| Meta, |
| } |
| |
| // Subset of scip_types::SymbolInformation::Kind |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub enum SymbolInformationKind { |
| AssociatedType, |
| Attribute, |
| Constant, |
| Enum, |
| EnumMember, |
| Field, |
| Function, |
| Macro, |
| Method, |
| Module, |
| Parameter, |
| SelfParameter, |
| StaticMethod, |
| StaticVariable, |
| Struct, |
| Trait, |
| TraitMethod, |
| Type, |
| TypeAlias, |
| TypeParameter, |
| Union, |
| Variable, |
| } |
| |
| impl From<SymbolInformationKind> for MonikerDescriptorKind { |
| fn from(value: SymbolInformationKind) -> Self { |
| match value { |
| SymbolInformationKind::AssociatedType => Self::Type, |
| SymbolInformationKind::Attribute => Self::Meta, |
| SymbolInformationKind::Constant => Self::Term, |
| SymbolInformationKind::Enum => Self::Type, |
| SymbolInformationKind::EnumMember => Self::Type, |
| SymbolInformationKind::Field => Self::Term, |
| SymbolInformationKind::Function => Self::Method, |
| SymbolInformationKind::Macro => Self::Macro, |
| SymbolInformationKind::Method => Self::Method, |
| SymbolInformationKind::Module => Self::Namespace, |
| SymbolInformationKind::Parameter => Self::Parameter, |
| SymbolInformationKind::SelfParameter => Self::Parameter, |
| SymbolInformationKind::StaticMethod => Self::Method, |
| SymbolInformationKind::StaticVariable => Self::Term, |
| SymbolInformationKind::Struct => Self::Type, |
| SymbolInformationKind::Trait => Self::Type, |
| SymbolInformationKind::TraitMethod => Self::Method, |
| SymbolInformationKind::Type => Self::Type, |
| SymbolInformationKind::TypeAlias => Self::Type, |
| SymbolInformationKind::TypeParameter => Self::TypeParameter, |
| SymbolInformationKind::Union => Self::Type, |
| SymbolInformationKind::Variable => Self::Term, |
| } |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct MonikerDescriptor { |
| pub name: String, |
| pub desc: MonikerDescriptorKind, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct MonikerIdentifier { |
| pub crate_name: String, |
| pub description: Vec<MonikerDescriptor>, |
| } |
| |
| impl fmt::Display for MonikerIdentifier { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| f.write_str(&self.crate_name)?; |
| f.write_fmt(format_args!("::{}", self.description.iter().map(|x| &x.name).join("::"))) |
| } |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub enum MonikerKind { |
| Import, |
| Export, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub enum MonikerResult { |
| /// Uniquely identifies a definition. |
| Moniker(Moniker), |
| /// Specifies that the definition is a local, and so does not have a unique identifier. Provides |
| /// a unique identifier for the container. |
| Local { enclosing_moniker: Option<Moniker> }, |
| } |
| |
| impl MonikerResult { |
| pub fn from_def(db: &RootDatabase, def: Definition, from_crate: Crate) -> Option<Self> { |
| def_to_moniker(db, def, from_crate) |
| } |
| } |
| |
| /// Information which uniquely identifies a definition which might be referenceable outside of the |
| /// source file. Visibility declarations do not affect presence. |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct Moniker { |
| pub identifier: MonikerIdentifier, |
| pub kind: MonikerKind, |
| pub package_information: PackageInformation, |
| } |
| |
| #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| pub struct PackageInformation { |
| pub name: String, |
| pub repo: Option<String>, |
| pub version: Option<String>, |
| } |
| |
| pub(crate) fn moniker( |
| db: &RootDatabase, |
| FilePosition { file_id, offset }: FilePosition, |
| ) -> Option<RangeInfo<Vec<MonikerResult>>> { |
| let sema = &Semantics::new(db); |
| let file = sema.parse_guess_edition(file_id).syntax().clone(); |
| let current_crate: hir::Crate = crates_for(db, file_id).pop()?.into(); |
| let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind { |
| IDENT |
| | INT_NUMBER |
| | LIFETIME_IDENT |
| | T![self] |
| | T![super] |
| | T![crate] |
| | T![Self] |
| | COMMENT => 2, |
| kind if kind.is_trivia() => 0, |
| _ => 1, |
| })?; |
| if let Some(doc_comment) = token_as_doc_comment(&original_token) { |
| return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, _| { |
| let m = def_to_moniker(db, def, current_crate)?; |
| Some(RangeInfo::new(original_token.text_range(), vec![m])) |
| }); |
| } |
| let navs = sema |
| .descend_into_macros_exact(original_token.clone()) |
| .into_iter() |
| .filter_map(|token| { |
| IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| { |
| it.into_iter().flat_map(|def| def_to_moniker(sema.db, def, current_crate)) |
| }) |
| }) |
| .flatten() |
| .unique() |
| .collect::<Vec<_>>(); |
| Some(RangeInfo::new(original_token.text_range(), navs)) |
| } |
| |
| pub(crate) fn def_to_kind(db: &RootDatabase, def: Definition) -> SymbolInformationKind { |
| use SymbolInformationKind::*; |
| |
| match def { |
| Definition::Macro(it) => match it.kind(db) { |
| MacroKind::Derive |
| | MacroKind::DeriveBuiltIn |
| | MacroKind::AttrBuiltIn |
| | MacroKind::Attr => Attribute, |
| MacroKind::Declarative | MacroKind::DeclarativeBuiltIn | MacroKind::ProcMacro => Macro, |
| }, |
| Definition::Field(..) | Definition::TupleField(..) => Field, |
| Definition::Module(..) | Definition::Crate(..) => Module, |
| Definition::Function(it) => { |
| if it.as_assoc_item(db).is_some() { |
| if it.has_self_param(db) { |
| if it.has_body(db) { Method } else { TraitMethod } |
| } else { |
| StaticMethod |
| } |
| } else { |
| Function |
| } |
| } |
| Definition::Adt(Adt::Struct(..)) => Struct, |
| Definition::Adt(Adt::Union(..)) => Union, |
| Definition::Adt(Adt::Enum(..)) => Enum, |
| Definition::Variant(..) => EnumMember, |
| Definition::Const(..) => Constant, |
| Definition::Static(..) => StaticVariable, |
| Definition::Trait(..) => Trait, |
| Definition::TypeAlias(it) => { |
| if it.as_assoc_item(db).is_some() { |
| AssociatedType |
| } else { |
| TypeAlias |
| } |
| } |
| Definition::BuiltinType(..) => Type, |
| Definition::BuiltinLifetime(_) => TypeParameter, |
| Definition::SelfType(..) => TypeAlias, |
| Definition::GenericParam(..) => TypeParameter, |
| Definition::Local(it) => { |
| if it.is_self(db) { |
| SelfParameter |
| } else if it.is_param(db) { |
| Parameter |
| } else { |
| Variable |
| } |
| } |
| Definition::Label(..) | Definition::InlineAsmOperand(_) => Variable, // For lack of a better variant |
| Definition::DeriveHelper(..) => Attribute, |
| Definition::BuiltinAttr(..) => Attribute, |
| Definition::ToolModule(..) => Module, |
| Definition::ExternCrateDecl(..) => Module, |
| Definition::InlineAsmRegOrRegClass(..) => Module, |
| } |
| } |
| |
| /// Computes a `MonikerResult` for a definition. Result cases: |
| /// |
| /// * `Some(MonikerResult::Moniker(_))` provides a unique `Moniker` which refers to a definition. |
| /// |
| /// * `Some(MonikerResult::Local { .. })` provides a `Moniker` for the definition enclosing a local. |
| /// |
| /// * `None` is returned for definitions which are not in a module: `BuiltinAttr`, `BuiltinType`, |
| /// `BuiltinLifetime`, `TupleField`, `ToolModule`, and `InlineAsmRegOrRegClass`. TODO: it might be |
| /// sensible to provide monikers that refer to some non-existent crate of compiler builtin |
| /// definitions. |
| pub(crate) fn def_to_moniker( |
| db: &RootDatabase, |
| definition: Definition, |
| from_crate: Crate, |
| ) -> Option<MonikerResult> { |
| match definition { |
| Definition::Local(_) | Definition::Label(_) | Definition::GenericParam(_) => { |
| return Some(MonikerResult::Local { |
| enclosing_moniker: enclosing_def_to_moniker(db, definition, from_crate), |
| }); |
| } |
| _ => {} |
| } |
| Some(MonikerResult::Moniker(def_to_non_local_moniker(db, definition, from_crate)?)) |
| } |
| |
| fn enclosing_def_to_moniker( |
| db: &RootDatabase, |
| mut def: Definition, |
| from_crate: Crate, |
| ) -> Option<Moniker> { |
| loop { |
| let enclosing_def = def.enclosing_definition(db)?; |
| if let Some(enclosing_moniker) = def_to_non_local_moniker(db, enclosing_def, from_crate) { |
| return Some(enclosing_moniker); |
| } |
| def = enclosing_def; |
| } |
| } |
| |
| fn def_to_non_local_moniker( |
| db: &RootDatabase, |
| definition: Definition, |
| from_crate: Crate, |
| ) -> Option<Moniker> { |
| let module = match definition { |
| Definition::Module(module) if module.is_crate_root() => module, |
| _ => definition.module(db)?, |
| }; |
| let krate = module.krate(); |
| let edition = krate.edition(db); |
| |
| // Add descriptors for this definition and every enclosing definition. |
| let mut reverse_description = vec![]; |
| let mut def = definition; |
| loop { |
| match def { |
| Definition::SelfType(impl_) => { |
| if let Some(trait_ref) = impl_.trait_ref(db) { |
| // Trait impls use the trait type for the 2nd parameter. |
| reverse_description.push(MonikerDescriptor { |
| name: display(db, module, trait_ref), |
| desc: MonikerDescriptorKind::TypeParameter, |
| }); |
| } |
| // Both inherent and trait impls use the self type for the first parameter. |
| reverse_description.push(MonikerDescriptor { |
| name: display(db, module, impl_.self_ty(db)), |
| desc: MonikerDescriptorKind::TypeParameter, |
| }); |
| reverse_description.push(MonikerDescriptor { |
| name: "impl".to_owned(), |
| desc: MonikerDescriptorKind::Type, |
| }); |
| } |
| _ => { |
| if let Some(name) = def.name(db) { |
| reverse_description.push(MonikerDescriptor { |
| name: name.display(db, edition).to_string(), |
| desc: def_to_kind(db, def).into(), |
| }); |
| } else { |
| match def { |
| Definition::Module(module) if module.is_crate_root() => { |
| // only include `crate` namespace by itself because we prefer |
| // `rust-analyzer cargo foo . bar/` over `rust-analyzer cargo foo . crate/bar/` |
| if reverse_description.is_empty() { |
| reverse_description.push(MonikerDescriptor { |
| name: "crate".to_owned(), |
| desc: MonikerDescriptorKind::Namespace, |
| }); |
| } |
| } |
| _ => { |
| tracing::error!(?def, "Encountered enclosing definition with no name"); |
| } |
| } |
| } |
| } |
| } |
| let Some(next_def) = def.enclosing_definition(db) else { |
| break; |
| }; |
| def = next_def; |
| } |
| if reverse_description.is_empty() { |
| return None; |
| } |
| reverse_description.reverse(); |
| let description = reverse_description; |
| |
| Some(Moniker { |
| identifier: MonikerIdentifier { |
| crate_name: krate.display_name(db)?.crate_name().to_string(), |
| description, |
| }, |
| kind: if krate == from_crate { MonikerKind::Export } else { MonikerKind::Import }, |
| package_information: { |
| let (name, repo, version) = match krate.origin(db) { |
| CrateOrigin::Library { repo, name } => (name, repo, krate.version(db)), |
| CrateOrigin::Local { repo, name } => ( |
| name.unwrap_or(krate.display_name(db)?.canonical_name().to_owned()), |
| repo, |
| krate.version(db), |
| ), |
| CrateOrigin::Rustc { name } => ( |
| name.clone(), |
| Some("https://github.com/rust-lang/rust/".to_owned()), |
| Some(format!("https://github.com/rust-lang/rust/compiler/{name}",)), |
| ), |
| CrateOrigin::Lang(lang) => ( |
| krate.display_name(db)?.canonical_name().to_owned(), |
| Some("https://github.com/rust-lang/rust/".to_owned()), |
| Some(match lang { |
| LangCrateOrigin::Other => { |
| "https://github.com/rust-lang/rust/library/".into() |
| } |
| lang => format!("https://github.com/rust-lang/rust/library/{lang}",), |
| }), |
| ), |
| }; |
| PackageInformation { name: name.as_str().to_owned(), repo, version } |
| }, |
| }) |
| } |
| |
| fn display<T: HirDisplay>(db: &RootDatabase, module: hir::Module, it: T) -> String { |
| match it.display_source_code(db, module.into(), true) { |
| Ok(result) => result, |
| // Fallback on display variant that always succeeds |
| Err(_) => { |
| let fallback_result = it.display(db, module.krate().to_display_target(db)).to_string(); |
| tracing::error!( |
| display = %fallback_result, "`display_source_code` failed; falling back to using display" |
| ); |
| fallback_result |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::{MonikerResult, fixture}; |
| |
| use super::MonikerKind; |
| |
| #[allow(dead_code)] |
| #[track_caller] |
| fn no_moniker(#[rust_analyzer::rust_fixture] ra_fixture: &str) { |
| let (analysis, position) = fixture::position(ra_fixture); |
| if let Some(x) = analysis.moniker(position).unwrap() { |
| assert_eq!(x.info.len(), 0, "Moniker found but no moniker expected: {x:?}"); |
| } |
| } |
| |
| #[track_caller] |
| fn check_local_moniker( |
| #[rust_analyzer::rust_fixture] ra_fixture: &str, |
| identifier: &str, |
| package: &str, |
| kind: MonikerKind, |
| ) { |
| let (analysis, position) = fixture::position(ra_fixture); |
| let x = analysis.moniker(position).unwrap().expect("no moniker found").info; |
| assert_eq!(x.len(), 1); |
| match x.into_iter().next().unwrap() { |
| MonikerResult::Local { enclosing_moniker: Some(x) } => { |
| assert_eq!(identifier, x.identifier.to_string()); |
| assert_eq!(package, format!("{:?}", x.package_information)); |
| assert_eq!(kind, x.kind); |
| } |
| MonikerResult::Local { enclosing_moniker: None } => { |
| panic!("Unexpected local with no enclosing moniker"); |
| } |
| MonikerResult::Moniker(_) => { |
| panic!("Unexpected non-local moniker"); |
| } |
| } |
| } |
| |
| #[track_caller] |
| fn check_moniker( |
| #[rust_analyzer::rust_fixture] ra_fixture: &str, |
| identifier: &str, |
| package: &str, |
| kind: MonikerKind, |
| ) { |
| let (analysis, position) = fixture::position(ra_fixture); |
| let x = analysis.moniker(position).unwrap().expect("no moniker found").info; |
| assert_eq!(x.len(), 1); |
| match x.into_iter().next().unwrap() { |
| MonikerResult::Local { enclosing_moniker } => { |
| panic!("Unexpected local enclosed in {enclosing_moniker:?}"); |
| } |
| MonikerResult::Moniker(x) => { |
| assert_eq!(identifier, x.identifier.to_string()); |
| assert_eq!(package, format!("{:?}", x.package_information)); |
| assert_eq!(kind, x.kind); |
| } |
| } |
| } |
| |
| #[test] |
| fn basic() { |
| check_moniker( |
| r#" |
| //- /lib.rs crate:main deps:foo |
| use foo::module::func; |
| fn main() { |
| func$0(); |
| } |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub fn func() {} |
| } |
| "#, |
| "foo::module::func", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Import, |
| ); |
| check_moniker( |
| r#" |
| //- /lib.rs crate:main deps:foo |
| use foo::module::func; |
| fn main() { |
| func(); |
| } |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub fn func$0() {} |
| } |
| "#, |
| "foo::module::func", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Export, |
| ); |
| } |
| |
| #[test] |
| fn moniker_for_trait() { |
| check_moniker( |
| r#" |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub trait MyTrait { |
| pub fn func$0() {} |
| } |
| } |
| "#, |
| "foo::module::MyTrait::func", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Export, |
| ); |
| } |
| |
| #[test] |
| fn moniker_for_trait_constant() { |
| check_moniker( |
| r#" |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub trait MyTrait { |
| const MY_CONST$0: u8; |
| } |
| } |
| "#, |
| "foo::module::MyTrait::MY_CONST", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Export, |
| ); |
| } |
| |
| #[test] |
| fn moniker_for_trait_type() { |
| check_moniker( |
| r#" |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub trait MyTrait { |
| type MyType$0; |
| } |
| } |
| "#, |
| "foo::module::MyTrait::MyType", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Export, |
| ); |
| } |
| |
| #[test] |
| fn moniker_for_trait_impl_function() { |
| check_moniker( |
| r#" |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub trait MyTrait { |
| pub fn func() {} |
| } |
| struct MyStruct {} |
| impl MyTrait for MyStruct { |
| pub fn func$0() {} |
| } |
| } |
| "#, |
| "foo::module::impl::MyStruct::MyTrait::func", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Export, |
| ); |
| } |
| |
| #[test] |
| fn moniker_for_field() { |
| check_moniker( |
| r#" |
| //- /lib.rs crate:main deps:foo |
| use foo::St; |
| fn main() { |
| let x = St { a$0: 2 }; |
| } |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub struct St { |
| pub a: i32, |
| } |
| "#, |
| "foo::St::a", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Import, |
| ); |
| } |
| |
| #[test] |
| fn local() { |
| check_local_moniker( |
| r#" |
| //- /lib.rs crate:main deps:foo |
| use foo::module::func; |
| fn main() { |
| func(); |
| } |
| //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library |
| pub mod module { |
| pub fn func() { |
| let x$0 = 2; |
| } |
| } |
| "#, |
| "foo::module::func", |
| r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#, |
| MonikerKind::Export, |
| ); |
| } |
| } |