//! 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::{ base_db::{CrateOrigin, LangCrateOrigin}, defs::{Definition, IdentClass}, helpers::pick_best_token, FilePosition, RootDatabase, }; use itertools::Itertools; use span::Edition; use syntax::{AstNode, SyntaxKind::*, T}; use crate::{doc_links::token_as_doc_comment, parent_module::crates_for, RangeInfo}; #[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 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, } 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 }, } impl MonikerResult { pub fn from_def(db: &RootDatabase, def: Definition, from_crate: Crate) -> Option { 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, pub version: Option, } pub(crate) fn moniker( db: &RootDatabase, FilePosition { file_id, offset }: FilePosition, ) -> Option>> { 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::>(); 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::Declarative => Macro, MacroKind::Derive => Attribute, MacroKind::BuiltIn => Macro, MacroKind::Attr => Attribute, MacroKind::ProcMacro => Macro, }, Definition::Field(..) | Definition::TupleField(..) => Field, Definition::Module(..) => 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::TraitAlias(..) => 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 { 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 { 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 { let 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, edition, 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, edition, 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 if reverse_description.is_empty() { // Don't allow the last descriptor to be absent. return None; } else { match def { Definition::Module(module) if module.is_crate_root() => {} _ => { tracing::error!(?def, "Encountered enclosing definition with no name"); } } } } } let Some(next_def) = def.enclosing_definition(db) else { break; }; def = next_def; } 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( db: &RootDatabase, edition: Edition, 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, edition).to_string(); tracing::error!( display = %fallback_result, "`display_source_code` failed; falling back to using display" ); fallback_result } } } #[cfg(test)] mod tests { use crate::{fixture, MonikerResult}; use super::MonikerKind; #[allow(dead_code)] #[track_caller] fn no_moniker(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(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(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, ); } }