Show substitution where hovering over generic things

There are few things to note in the implementation:

First, this is a best-effort implementation. Mainly, type aliases may not be shown (due to their eager nature it's harder) and partial pathes (aka. hovering over `Struct` in `Struct::method`) are not supported at all.

Second, we only need to show substitutions in expression and pattern position, because in type position all generic arguments always have to be written explicitly.
This commit is contained in:
Chayim Refael Friedman 2024-12-18 00:57:38 +02:00
parent 27e824fad4
commit b5486ffc42
29 changed files with 1019 additions and 190 deletions

View file

@ -13,9 +13,10 @@ use either::Either;
use hir::{
Adt, AsAssocItem, AsExternAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType,
Const, Crate, DefWithBody, DeriveHelper, DocLinkDef, ExternAssocItem, ExternCrateDecl, Field,
Function, GenericParam, HasVisibility, HirDisplay, Impl, InlineAsmOperand, Label, Local, Macro,
Module, ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, Struct, ToolModule,
Trait, TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility,
Function, GenericParam, GenericSubstitution, HasVisibility, HirDisplay, Impl, InlineAsmOperand,
Label, Local, Macro, Module, ModuleDef, Name, PathResolution, Semantics, Static,
StaticLifetime, Struct, ToolModule, Trait, TraitAlias, TupleField, TypeAlias, Variant,
VariantDef, Visibility,
};
use span::Edition;
use stdx::{format_to, impl_from};
@ -359,24 +360,32 @@ impl IdentClass {
.or_else(|| NameClass::classify_lifetime(sema, lifetime).map(IdentClass::NameClass))
}
pub fn definitions(self) -> ArrayVec<Definition, 2> {
pub fn definitions(self) -> ArrayVec<(Definition, Option<GenericSubstitution>), 2> {
let mut res = ArrayVec::new();
match self {
IdentClass::NameClass(NameClass::Definition(it) | NameClass::ConstReference(it)) => {
res.push(it)
res.push((it, None))
}
IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, field_ref }) => {
res.push(Definition::Local(local_def));
res.push(Definition::Field(field_ref));
IdentClass::NameClass(NameClass::PatFieldShorthand {
local_def,
field_ref,
adt_subst,
}) => {
res.push((Definition::Local(local_def), None));
res.push((Definition::Field(field_ref), Some(adt_subst)));
}
IdentClass::NameRefClass(NameRefClass::Definition(it)) => res.push(it),
IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, field_ref }) => {
res.push(Definition::Local(local_ref));
res.push(Definition::Field(field_ref));
IdentClass::NameRefClass(NameRefClass::Definition(it, subst)) => res.push((it, subst)),
IdentClass::NameRefClass(NameRefClass::FieldShorthand {
local_ref,
field_ref,
adt_subst,
}) => {
res.push((Definition::Local(local_ref), None));
res.push((Definition::Field(field_ref), Some(adt_subst)));
}
IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand { decl, krate }) => {
res.push(Definition::ExternCrateDecl(decl));
res.push(Definition::Module(krate.root_module()));
res.push((Definition::ExternCrateDecl(decl), None));
res.push((Definition::Module(krate.root_module()), None));
}
IdentClass::Operator(
OperatorClass::Await(func)
@ -384,9 +393,9 @@ impl IdentClass {
| OperatorClass::Bin(func)
| OperatorClass::Index(func)
| OperatorClass::Try(func),
) => res.push(Definition::Function(func)),
) => res.push((Definition::Function(func), None)),
IdentClass::Operator(OperatorClass::Range(struct0)) => {
res.push(Definition::Adt(Adt::Struct(struct0)))
res.push((Definition::Adt(Adt::Struct(struct0)), None))
}
}
res
@ -398,12 +407,20 @@ impl IdentClass {
IdentClass::NameClass(NameClass::Definition(it) | NameClass::ConstReference(it)) => {
res.push(it)
}
IdentClass::NameClass(NameClass::PatFieldShorthand { local_def, field_ref }) => {
IdentClass::NameClass(NameClass::PatFieldShorthand {
local_def,
field_ref,
adt_subst: _,
}) => {
res.push(Definition::Local(local_def));
res.push(Definition::Field(field_ref));
}
IdentClass::NameRefClass(NameRefClass::Definition(it)) => res.push(it),
IdentClass::NameRefClass(NameRefClass::FieldShorthand { local_ref, field_ref }) => {
IdentClass::NameRefClass(NameRefClass::Definition(it, _)) => res.push(it),
IdentClass::NameRefClass(NameRefClass::FieldShorthand {
local_ref,
field_ref,
adt_subst: _,
}) => {
res.push(Definition::Local(local_ref));
res.push(Definition::Field(field_ref));
}
@ -437,6 +454,7 @@ pub enum NameClass {
PatFieldShorthand {
local_def: Local,
field_ref: Field,
adt_subst: GenericSubstitution,
},
}
@ -446,7 +464,7 @@ impl NameClass {
let res = match self {
NameClass::Definition(it) => it,
NameClass::ConstReference(_) => return None,
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {
Definition::Local(local_def)
}
};
@ -517,10 +535,13 @@ impl NameClass {
let pat_parent = ident_pat.syntax().parent();
if let Some(record_pat_field) = pat_parent.and_then(ast::RecordPatField::cast) {
if record_pat_field.name_ref().is_none() {
if let Some((field, _)) = sema.resolve_record_pat_field(&record_pat_field) {
if let Some((field, _, adt_subst)) =
sema.resolve_record_pat_field_with_subst(&record_pat_field)
{
return Some(NameClass::PatFieldShorthand {
local_def: local,
field_ref: field,
adt_subst,
});
}
}
@ -629,10 +650,11 @@ impl OperatorClass {
/// reference to point to two different defs.
#[derive(Debug)]
pub enum NameRefClass {
Definition(Definition),
Definition(Definition, Option<GenericSubstitution>),
FieldShorthand {
local_ref: Local,
field_ref: Field,
adt_subst: GenericSubstitution,
},
/// The specific situation where we have an extern crate decl without a rename
/// Here we have both a declaration and a reference.
@ -657,12 +679,16 @@ impl NameRefClass {
let parent = name_ref.syntax().parent()?;
if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) {
if let Some((field, local, _)) = sema.resolve_record_field(&record_field) {
if let Some((field, local, _, adt_subst)) =
sema.resolve_record_field_with_substitution(&record_field)
{
let res = match local {
None => NameRefClass::Definition(Definition::Field(field)),
Some(local) => {
NameRefClass::FieldShorthand { field_ref: field, local_ref: local }
}
None => NameRefClass::Definition(Definition::Field(field), Some(adt_subst)),
Some(local) => NameRefClass::FieldShorthand {
field_ref: field,
local_ref: local,
adt_subst,
},
};
return Some(res);
}
@ -674,44 +700,43 @@ impl NameRefClass {
// Only use this to resolve to macro calls for last segments as qualifiers resolve
// to modules below.
if let Some(macro_def) = sema.resolve_macro_call(&macro_call) {
return Some(NameRefClass::Definition(Definition::Macro(macro_def)));
return Some(NameRefClass::Definition(Definition::Macro(macro_def), None));
}
}
}
return sema.resolve_path(&path).map(Into::into).map(NameRefClass::Definition);
return sema
.resolve_path_with_subst(&path)
.map(|(res, subst)| NameRefClass::Definition(res.into(), subst));
}
match_ast! {
match parent {
ast::MethodCallExpr(method_call) => {
sema.resolve_method_call_fallback(&method_call)
.map(|it| {
it.map_left(Definition::Function)
.map_right(Definition::Field)
.either(NameRefClass::Definition, NameRefClass::Definition)
.map(|(def, subst)| {
match def {
Either::Left(def) => NameRefClass::Definition(def.into(), subst),
Either::Right(def) => NameRefClass::Definition(def.into(), subst),
}
})
},
ast::FieldExpr(field_expr) => {
sema.resolve_field_fallback(&field_expr)
.map(|it| {
NameRefClass::Definition(match it {
Either::Left(Either::Left(field)) => Definition::Field(field),
Either::Left(Either::Right(field)) => Definition::TupleField(field),
Either::Right(fun) => Definition::Function(fun),
.map(|(def, subst)| {
match def {
Either::Left(Either::Left(def)) => NameRefClass::Definition(def.into(), subst),
Either::Left(Either::Right(def)) => NameRefClass::Definition(Definition::TupleField(def), subst),
Either::Right(def) => NameRefClass::Definition(def.into(), subst),
}
})
})
},
ast::RecordPatField(record_pat_field) => {
sema.resolve_record_pat_field(&record_pat_field)
.map(|(field, ..)|field)
.map(Definition::Field)
.map(NameRefClass::Definition)
sema.resolve_record_pat_field_with_subst(&record_pat_field)
.map(|(field, _, subst)| NameRefClass::Definition(Definition::Field(field), Some(subst)))
},
ast::RecordExprField(record_expr_field) => {
sema.resolve_record_field(&record_expr_field)
.map(|(field, ..)|field)
.map(Definition::Field)
.map(NameRefClass::Definition)
sema.resolve_record_field_with_substitution(&record_expr_field)
.map(|(field, _, _, subst)| NameRefClass::Definition(Definition::Field(field), Some(subst)))
},
ast::AssocTypeArg(_) => {
// `Trait<Assoc = Ty>`
@ -728,28 +753,30 @@ impl NameRefClass {
})
.find(|alias| alias.name(sema.db).eq_ident(name_ref.text().as_str()))
{
return Some(NameRefClass::Definition(Definition::TypeAlias(ty)));
// No substitution, this can only occur in type position.
return Some(NameRefClass::Definition(Definition::TypeAlias(ty), None));
}
}
None
},
ast::UseBoundGenericArgs(_) => {
// No substitution, this can only occur in type position.
sema.resolve_use_type_arg(name_ref)
.map(GenericParam::TypeParam)
.map(Definition::GenericParam)
.map(NameRefClass::Definition)
.map(|it| NameRefClass::Definition(it, None))
},
ast::ExternCrate(extern_crate_ast) => {
let extern_crate = sema.to_def(&extern_crate_ast)?;
let krate = extern_crate.resolved_crate(sema.db)?;
Some(if extern_crate_ast.rename().is_some() {
NameRefClass::Definition(Definition::Module(krate.root_module()))
NameRefClass::Definition(Definition::Module(krate.root_module()), None)
} else {
NameRefClass::ExternCrateShorthand { krate, decl: extern_crate }
})
},
ast::AsmRegSpec(_) => {
Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(())))
Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(()), None))
},
_ => None
}
@ -762,13 +789,17 @@ impl NameRefClass {
) -> Option<NameRefClass> {
let _p = tracing::info_span!("NameRefClass::classify_lifetime", ?lifetime).entered();
if lifetime.text() == "'static" {
return Some(NameRefClass::Definition(Definition::BuiltinLifetime(StaticLifetime)));
return Some(NameRefClass::Definition(
Definition::BuiltinLifetime(StaticLifetime),
None,
));
}
let parent = lifetime.syntax().parent()?;
match parent.kind() {
SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => {
sema.resolve_label(lifetime).map(Definition::Label).map(NameRefClass::Definition)
}
SyntaxKind::BREAK_EXPR | SyntaxKind::CONTINUE_EXPR => sema
.resolve_label(lifetime)
.map(Definition::Label)
.map(|it| NameRefClass::Definition(it, None)),
SyntaxKind::LIFETIME_ARG
| SyntaxKind::USE_BOUND_GENERIC_ARGS
| SyntaxKind::SELF_PARAM
@ -778,7 +809,7 @@ impl NameRefClass {
.resolve_lifetime_param(lifetime)
.map(GenericParam::LifetimeParam)
.map(Definition::GenericParam)
.map(NameRefClass::Definition),
.map(|it| NameRefClass::Definition(it, None)),
_ => None,
}
}

View file

@ -1081,7 +1081,7 @@ impl<'a> FindUsages<'a> {
};
match NameRefClass::classify(self.sema, name_ref) {
Some(NameRefClass::Definition(Definition::SelfType(impl_)))
Some(NameRefClass::Definition(Definition::SelfType(impl_), _))
if ty_eq(impl_.self_ty(self.sema.db)) =>
{
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
@ -1102,7 +1102,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool {
match NameRefClass::classify(self.sema, name_ref) {
Some(NameRefClass::Definition(def @ Definition::Module(_))) if def == self.def => {
Some(NameRefClass::Definition(def @ Definition::Module(_), _)) if def == self.def => {
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let category = if is_name_ref_in_import(name_ref) {
ReferenceCategory::IMPORT
@ -1147,7 +1147,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool {
match NameRefClass::classify_lifetime(self.sema, lifetime) {
Some(NameRefClass::Definition(def)) if def == self.def => {
Some(NameRefClass::Definition(def, _)) if def == self.def => {
let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
let reference = FileReference {
range,
@ -1166,7 +1166,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool {
match NameRefClass::classify(self.sema, name_ref) {
Some(NameRefClass::Definition(def))
Some(NameRefClass::Definition(def, _))
if self.def == def
// is our def a trait assoc item? then we want to find all assoc items from trait impls of our trait
|| matches!(self.assoc_item_container, Some(hir::AssocItemContainer::Trait(_)))
@ -1182,7 +1182,7 @@ impl<'a> FindUsages<'a> {
}
// FIXME: special case type aliases, we can't filter between impl and trait defs here as we lack the substitutions
// so we always resolve all assoc type aliases to both their trait def and impl defs
Some(NameRefClass::Definition(def))
Some(NameRefClass::Definition(def, _))
if self.assoc_item_container.is_some()
&& matches!(self.def, Definition::TypeAlias(_))
&& convert_to_def_in_trait(self.sema.db, def)
@ -1196,7 +1196,7 @@ impl<'a> FindUsages<'a> {
};
sink(file_id, reference)
}
Some(NameRefClass::Definition(def)) if self.include_self_kw_refs.is_some() => {
Some(NameRefClass::Definition(def, _)) if self.include_self_kw_refs.is_some() => {
if self.include_self_kw_refs == def_to_ty(self.sema, &def) {
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let reference = FileReference {
@ -1209,7 +1209,11 @@ impl<'a> FindUsages<'a> {
false
}
}
Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
Some(NameRefClass::FieldShorthand {
local_ref: local,
field_ref: field,
adt_subst: _,
}) => {
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let field = Definition::Field(field);
@ -1240,7 +1244,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool {
match NameClass::classify(self.sema, name) {
Some(NameClass::PatFieldShorthand { local_def: _, field_ref })
Some(NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ })
if matches!(
self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def
) =>