mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-27 02:06:57 +00:00
601 lines
19 KiB
Rust
601 lines
19 KiB
Rust
//! 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<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::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<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 = 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<T: HirDisplay>(
|
|
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,
|
|
);
|
|
}
|
|
}
|