Merge pull request #18758 from mgsloan/scip-unique-symbols

Improve SCIP symbols
This commit is contained in:
Lukas Wirth 2024-12-31 09:25:30 +00:00 committed by GitHub
commit add0963033
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 617 additions and 322 deletions

View file

@ -96,8 +96,8 @@ pub use crate::{
join_lines::JoinLinesConfig,
markup::Markup,
moniker::{
MonikerDescriptorKind, MonikerKind, MonikerResult, PackageInformation,
SymbolInformationKind,
Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerKind, MonikerResult,
PackageInformation, SymbolInformationKind,
},
move_item::Direction,
navigation_target::{NavigationTarget, TryToNav, UpmappingResult},

View file

@ -3,7 +3,7 @@
use core::fmt;
use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics};
use hir::{Adt, AsAssocItem, Crate, HirDisplay, MacroKind, Semantics};
use ide_db::{
base_db::{CrateOrigin, LangCrateOrigin},
defs::{Definition, IdentClass},
@ -11,6 +11,7 @@ use ide_db::{
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};
@ -57,8 +58,8 @@ pub enum SymbolInformationKind {
impl From<SymbolInformationKind> for MonikerDescriptorKind {
fn from(value: SymbolInformationKind) -> Self {
match value {
SymbolInformationKind::AssociatedType => Self::TypeParameter,
SymbolInformationKind::Attribute => Self::Macro,
SymbolInformationKind::AssociatedType => Self::Type,
SymbolInformationKind::Attribute => Self::Meta,
SymbolInformationKind::Constant => Self::Term,
SymbolInformationKind::Enum => Self::Type,
SymbolInformationKind::EnumMember => Self::Type,
@ -70,7 +71,7 @@ impl From<SymbolInformationKind> for MonikerDescriptorKind {
SymbolInformationKind::Parameter => Self::Parameter,
SymbolInformationKind::SelfParameter => Self::Parameter,
SymbolInformationKind::StaticMethod => Self::Method,
SymbolInformationKind::StaticVariable => Self::Meta,
SymbolInformationKind::StaticVariable => Self::Term,
SymbolInformationKind::Struct => Self::Type,
SymbolInformationKind::Trait => Self::Type,
SymbolInformationKind::TraitMethod => Self::Method,
@ -109,10 +110,12 @@ pub enum MonikerKind {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MonikerResult {
pub identifier: MonikerIdentifier,
pub kind: MonikerKind,
pub package_information: PackageInformation,
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 {
@ -121,6 +124,15 @@ impl MonikerResult {
}
}
/// 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,
@ -232,157 +244,106 @@ pub(crate) fn def_to_kind(db: &RootDatabase, def: Definition) -> SymbolInformati
}
}
/// 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,
def: Definition,
definition: Definition,
from_crate: Crate,
) -> Option<MonikerResult> {
if matches!(
def,
Definition::GenericParam(_)
| Definition::Label(_)
| Definition::DeriveHelper(_)
| Definition::BuiltinAttr(_)
| Definition::ToolModule(_)
) {
return None;
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)?))
}
let module = def.module(db)?;
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);
let mut description = vec![];
description.extend(module.path_to_root(db).into_iter().filter_map(|x| {
Some(MonikerDescriptor {
name: x.name(db)?.display(db, edition).to_string(),
desc: def_to_kind(db, x.into()).into(),
})
}));
// Handle associated items within a trait
if let Some(assoc) = def.as_assoc_item(db) {
let container = assoc.container(db);
match container {
AssocItemContainer::Trait(trait_) => {
// Because different traits can have functions with the same name,
// we have to include the trait name as part of the moniker for uniqueness.
description.push(MonikerDescriptor {
name: trait_.name(db).display(db, edition).to_string(),
desc: def_to_kind(db, trait_.into()).into(),
});
}
AssocItemContainer::Impl(impl_) => {
// Because a struct can implement multiple traits, for implementations
// we add both the struct name and the trait name to the path
if let Some(adt) = impl_.self_ty(db).as_adt() {
description.push(MonikerDescriptor {
name: adt.name(db).display(db, edition).to_string(),
desc: def_to_kind(db, adt.into()).into(),
// 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,
});
}
if let Some(trait_) = impl_.trait_(db) {
description.push(MonikerDescriptor {
name: trait_.name(db).display(db, edition).to_string(),
desc: def_to_kind(db, trait_.into()).into(),
// 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;
if let Definition::Field(it) = def {
description.push(MonikerDescriptor {
name: it.parent_def(db).name(db).display(db, edition).to_string(),
desc: def_to_kind(db, it.parent_def(db).into()).into(),
});
}
// Qualify locals/parameters by their parent definition name.
if let Definition::Local(it) = def {
let parent = Definition::try_from(it.parent(db)).ok();
if let Some(parent) = parent {
let parent_name = parent.name(db);
if let Some(name) = parent_name {
description.push(MonikerDescriptor {
name: name.display(db, edition).to_string(),
desc: def_to_kind(db, parent).into(),
});
}
}
}
let desc = def_to_kind(db, def).into();
let name_desc = match def {
// These are handled by top-level guard (for performance).
Definition::GenericParam(_)
| Definition::Label(_)
| Definition::DeriveHelper(_)
| Definition::BuiltinLifetime(_)
| Definition::BuiltinAttr(_)
| Definition::ToolModule(_)
| Definition::InlineAsmRegOrRegClass(_)
| Definition::InlineAsmOperand(_) => return None,
Definition::Local(local) => {
if !local.is_param(db) {
return None;
}
MonikerDescriptor { name: local.name(db).display(db, edition).to_string(), desc }
}
Definition::Macro(m) => {
MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc }
}
Definition::Function(f) => {
MonikerDescriptor { name: f.name(db).display(db, edition).to_string(), desc }
}
Definition::Variant(v) => {
MonikerDescriptor { name: v.name(db).display(db, edition).to_string(), desc }
}
Definition::Const(c) => {
MonikerDescriptor { name: c.name(db)?.display(db, edition).to_string(), desc }
}
Definition::Trait(trait_) => {
MonikerDescriptor { name: trait_.name(db).display(db, edition).to_string(), desc }
}
Definition::TraitAlias(ta) => {
MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc }
}
Definition::TypeAlias(ta) => {
MonikerDescriptor { name: ta.name(db).display(db, edition).to_string(), desc }
}
Definition::Module(m) => {
MonikerDescriptor { name: m.name(db)?.display(db, edition).to_string(), desc }
}
Definition::BuiltinType(b) => {
MonikerDescriptor { name: b.name().display(db, edition).to_string(), desc }
}
Definition::SelfType(imp) => MonikerDescriptor {
name: imp.self_ty(db).as_adt()?.name(db).display(db, edition).to_string(),
desc,
},
Definition::Field(it) => {
MonikerDescriptor { name: it.name(db).display(db, edition).to_string(), desc }
}
Definition::TupleField(it) => {
MonikerDescriptor { name: it.name().display(db, edition).to_string(), desc }
}
Definition::Adt(adt) => {
MonikerDescriptor { name: adt.name(db).display(db, edition).to_string(), desc }
}
Definition::Static(s) => {
MonikerDescriptor { name: s.name(db).display(db, edition).to_string(), desc }
}
Definition::ExternCrateDecl(m) => {
MonikerDescriptor { name: m.name(db).display(db, edition).to_string(), desc }
}
};
description.push(name_desc);
Some(MonikerResult {
Some(Moniker {
identifier: MonikerIdentifier {
crate_name: krate.display_name(db)?.crate_name().to_string(),
description,
@ -417,17 +378,57 @@ pub(crate) fn def_to_moniker(
})
}
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;
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 founded but no moniker expected: {x:?}");
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");
}
}
}
@ -436,10 +437,16 @@ mod tests {
let (analysis, position) = fixture::position(ra_fixture);
let x = analysis.moniker(position).unwrap().expect("no moniker found").info;
assert_eq!(x.len(), 1);
let x = x.into_iter().next().unwrap();
assert_eq!(identifier, x.identifier.to_string());
assert_eq!(package, format!("{:?}", x.package_information));
assert_eq!(kind, x.kind);
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]
@ -538,15 +545,13 @@ pub mod module {
pub trait MyTrait {
pub fn func() {}
}
struct MyStruct {}
impl MyTrait for MyStruct {
pub fn func$0() {}
}
}
"#,
"foo::module::MyStruct::MyTrait::func",
"foo::module::impl::MyStruct::MyTrait::func",
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
MonikerKind::Export,
);
@ -573,8 +578,8 @@ pub struct St {
}
#[test]
fn no_moniker_for_local() {
no_moniker(
fn local() {
check_local_moniker(
r#"
//- /lib.rs crate:main deps:foo
use foo::module::func;
@ -588,6 +593,9 @@ pub mod module {
}
}
"#,
"foo::module::func",
r#"PackageInformation { name: "foo", repo: Some("https://a.b/foo.git"), version: Some("0.1.0") }"#,
MonikerKind::Export,
);
}
}

View file

@ -48,7 +48,6 @@ pub struct TokenStaticData {
pub references: Vec<ReferenceData>,
pub moniker: Option<MonikerResult>,
pub display_name: Option<String>,
pub enclosing_moniker: Option<MonikerResult>,
pub signature: Option<String>,
pub kind: SymbolInformationKind,
}
@ -225,9 +224,6 @@ impl StaticIndex<'_> {
display_name: def
.name(self.db)
.map(|name| name.display(self.db, edition).to_string()),
enclosing_moniker: current_crate
.zip(def.enclosing_definition(self.db))
.and_then(|(cc, enclosing_def)| def_to_moniker(self.db, enclosing_def, cc)),
signature: Some(def.label(self.db, edition)),
kind: def_to_kind(self.db, def),
});