mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-28 18:43:01 +00:00
Improve SCIP symbols
In particular, the symbol generation before this change creates a lot
of symbols with the same name for different definitions. This change
makes progress on symbol uniqueness, but does not fix a couple cases
where it was unclear to me how to fix (see TODOs in `scip.rs`)
Behavior changes:
* `scip` command now reports symbol information omitted due to symbol
collisions. Iterating with this on a large codebase (Zed!) resulted in
the other improvements in this change.
* Generally fixes providing the path to nested definitions in
symbols. Instead of having special cases for a couple limited cases of
nesting, implements `Definition::enclosing_definition` and uses this
to walk definitions.
* Parameter variables are now treated like locals.
- This fixes a bug where closure captures also received symbols
scoped to the containing function. To bring back parameter
symbols I would want a way to filter these out, since they can
cause symbol collisions.
- Having symbols for them seems to be intentional in
27e2eea54f, but no particular use is
specified there. For the typical indexing purposes of SCIP I don't see
why parameter symbols are useful or sensible, as function parameters
are not referencable by anything but position. I can imagine they
might be useful in representing diagnostics or something.
* Inherent impls are now represented as `impl#[SelfType]` - a type
named `impl` which takes a single type parameter.
* Trait impls are now represented as `impl#[SelfType][TraitType]` - a
type named `impl` which takes two type parameters.
* Associated types in traits and impls are now treated like types
instead of type parameters, and so are now suffixed with `#` instead
of wrapped with `[]`. Treating them as type parameters seems to have
been intentional in 73d9c77f2a but it
doesn't make sense to me, so changing it.
* Static variables are now treated as terms instead of `Meta`, and so
receive `.` suffix instead of `:`.
* Attributes are now treated as `Meta` instead of `Macro`, and so
receive `:` suffix instead of `!`.
* `enclosing_symbol` is now provided for labels and generic params,
which are local symbols.
* Fixes a bug where presence of `'` causes a descriptor name to get
double wrapped in backticks, since both `fn new_descriptor` and
`scip::symbol::format_symbol` have logic for wrapping in
backticks. Solution is to simply delete the redundant logic.
* Deletes a couple tests in moniker.rs because the cases are
adequeately covered in scip.rs and the format for identifiers used in
moniker.rs is clunky with the new representation for trait impls
This commit is contained in:
parent
bfc223e857
commit
17c90f71bf
9 changed files with 520 additions and 268 deletions
|
|
@ -3,7 +3,10 @@
|
|||
|
||||
use core::fmt;
|
||||
|
||||
use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics};
|
||||
use hir::{
|
||||
Adt, AsAssocItem, Crate, HirDisplay, MacroKind, Semantics, TraitRefDisplayWrapper,
|
||||
TraitRefFormat,
|
||||
};
|
||||
use ide_db::{
|
||||
base_db::{CrateOrigin, LangCrateOrigin},
|
||||
defs::{Definition, IdentClass},
|
||||
|
|
@ -11,6 +14,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 +61,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 +74,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 +113,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 +127,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 +247,129 @@ 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 in the following cases:
|
||||
///
|
||||
/// * Inherent impl definitions, as they cannot be uniquely identified (multiple are allowed for the
|
||||
/// same type).
|
||||
///
|
||||
/// * 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 {
|
||||
// Not possible to give sensible unique symbols for inherent impls, as multiple can be
|
||||
// defined for the same type.
|
||||
Definition::SelfType(impl_) if impl_.trait_(db).is_none() => {
|
||||
return None;
|
||||
}
|
||||
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> {
|
||||
match definition {
|
||||
// Not possible to give sensible unique symbols for inherent impls, as multiple can be
|
||||
// defined for the same type.
|
||||
Definition::SelfType(impl_) if impl_.trait_(db).is_none() => {
|
||||
return None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let module = def.module(db)?;
|
||||
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 `trait_type` constraint syntax for the 2nd parameter.
|
||||
let trait_ref_for_display =
|
||||
TraitRefDisplayWrapper { trait_ref, format: TraitRefFormat::OnlyTrait };
|
||||
reverse_description.push(MonikerDescriptor {
|
||||
name: display(db, edition, module, trait_ref_for_display),
|
||||
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 `self_type` as 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!(
|
||||
"Encountered enclosing definition with no name: {:?}",
|
||||
def
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 +404,58 @@ 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_source_code failed. Falling back to using display, which has result: {}",
|
||||
fallback_result
|
||||
);
|
||||
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 +464,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 +572,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 +605,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 +620,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue