Merge pull request #18707 from ChayimFriedman2/subst

feat: Show substitution where hovering over generic things
This commit is contained in:
Lukas Wirth 2024-12-24 14:16:16 +00:00 committed by GitHub
commit e30ce42671
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1019 additions and 190 deletions

View file

@ -3574,6 +3574,61 @@ impl GenericDef {
}
}
// We cannot call this `Substitution` unfortunately...
#[derive(Debug)]
pub struct GenericSubstitution {
def: GenericDefId,
subst: Substitution,
env: Arc<TraitEnvironment>,
}
impl GenericSubstitution {
fn new(def: GenericDefId, subst: Substitution, env: Arc<TraitEnvironment>) -> Self {
Self { def, subst, env }
}
pub fn types(&self, db: &dyn HirDatabase) -> Vec<(Symbol, Type)> {
let container = match self.def {
GenericDefId::ConstId(id) => Some(id.lookup(db.upcast()).container),
GenericDefId::FunctionId(id) => Some(id.lookup(db.upcast()).container),
GenericDefId::TypeAliasId(id) => Some(id.lookup(db.upcast()).container),
_ => None,
};
let container_type_params = container
.and_then(|container| match container {
ItemContainerId::ImplId(container) => Some(container.into()),
ItemContainerId::TraitId(container) => Some(container.into()),
_ => None,
})
.map(|container| {
db.generic_params(container)
.iter_type_or_consts()
.filter_map(|param| match param.1 {
TypeOrConstParamData::TypeParamData(param) => Some(param.name.clone()),
TypeOrConstParamData::ConstParamData(_) => None,
})
.collect::<Vec<_>>()
});
let generics = db.generic_params(self.def);
let type_params = generics.iter_type_or_consts().filter_map(|param| match param.1 {
TypeOrConstParamData::TypeParamData(param) => Some(param.name.clone()),
TypeOrConstParamData::ConstParamData(_) => None,
});
// The `Substitution` is first self then container, we want the reverse order.
let self_params = self.subst.type_parameters(Interner).zip(type_params);
let container_params = self.subst.as_slice(Interner)[generics.len()..]
.iter()
.filter_map(|param| param.ty(Interner).cloned())
.zip(container_type_params.into_iter().flatten());
container_params
.chain(self_params)
.filter_map(|(ty, name)| {
Some((name?.symbol().clone(), Type { ty, env: self.env.clone() }))
})
.collect()
}
}
/// A single local definition.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Local {

View file

@ -49,10 +49,10 @@ use crate::{
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{name_hygiene, resolve_hir_path, SourceAnalyzer},
Access, Adjust, Adjustment, Adt, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const,
ConstParam, Crate, DeriveHelper, Enum, Field, Function, HasSource, HirFileId, Impl, InFile,
InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name,
OverloadedDeref, Path, ScopeDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField,
Type, TypeAlias, TypeParam, Union, Variant, VariantDef,
ConstParam, Crate, DeriveHelper, Enum, Field, Function, GenericSubstitution, HasSource,
HirFileId, Impl, InFile, InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro,
Module, ModuleDef, Name, OverloadedDeref, Path, ScopeDef, Static, Struct, ToolModule, Trait,
TraitAlias, TupleField, Type, TypeAlias, TypeParam, Union, Variant, VariantDef,
};
const CONTINUE_NO_BREAKS: ControlFlow<Infallible, ()> = ControlFlow::Continue(());
@ -1413,7 +1413,7 @@ impl<'db> SemanticsImpl<'db> {
pub fn resolve_method_call_fallback(
&self,
call: &ast::MethodCallExpr,
) -> Option<Either<Function, Field>> {
) -> Option<(Either<Function, Field>, Option<GenericSubstitution>)> {
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
}
@ -1456,7 +1456,7 @@ impl<'db> SemanticsImpl<'db> {
pub fn resolve_field_fallback(
&self,
field: &ast::FieldExpr,
) -> Option<Either<Either<Field, TupleField>, Function>> {
) -> Option<(Either<Either<Field, TupleField>, Function>, Option<GenericSubstitution>)> {
self.analyze(field.syntax())?.resolve_field_fallback(self.db, field)
}
@ -1464,10 +1464,25 @@ impl<'db> SemanticsImpl<'db> {
&self,
field: &ast::RecordExprField,
) -> Option<(Field, Option<Local>, Type)> {
self.resolve_record_field_with_substitution(field)
.map(|(field, local, ty, _)| (field, local, ty))
}
pub fn resolve_record_field_with_substitution(
&self,
field: &ast::RecordExprField,
) -> Option<(Field, Option<Local>, Type, GenericSubstitution)> {
self.analyze(field.syntax())?.resolve_record_field(self.db, field)
}
pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> {
self.resolve_record_pat_field_with_subst(field).map(|(field, ty, _)| (field, ty))
}
pub fn resolve_record_pat_field_with_subst(
&self,
field: &ast::RecordPatField,
) -> Option<(Field, Type, GenericSubstitution)> {
self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field)
}
@ -1523,6 +1538,13 @@ impl<'db> SemanticsImpl<'db> {
}
pub fn resolve_path(&self, path: &ast::Path) -> Option<PathResolution> {
self.resolve_path_with_subst(path).map(|(it, _)| it)
}
pub fn resolve_path_with_subst(
&self,
path: &ast::Path,
) -> Option<(PathResolution, Option<GenericSubstitution>)> {
self.analyze(path.syntax())?.resolve_path(self.db, path)
}

View file

@ -9,8 +9,8 @@ use std::iter::{self, once};
use crate::{
db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static,
Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant,
BuiltinType, Callable, Const, DeriveHelper, Field, Function, GenericSubstitution, Local, Macro,
ModuleDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant,
};
use either::Either;
use hir_def::{
@ -18,15 +18,15 @@ use hir_def::{
scope::{ExprScopes, ScopeId},
Body, BodySourceMap, HygieneId,
},
hir::{BindingId, ExprId, ExprOrPatId, Pat, PatId},
hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId},
lang_item::LangItem,
lower::LowerCtx,
nameres::MacroSubNs,
path::{ModPath, Path, PathKind},
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
type_ref::{Mutability, TypesMap, TypesSourceMap},
AsMacroCall, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, ItemContainerId,
LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId,
AsMacroCall, AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId,
ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId,
};
use hir_expand::{
mod_path::path,
@ -38,9 +38,10 @@ use hir_ty::{
record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
InsideUnsafeBlock,
},
from_assoc_type_id,
lang_items::lang_items_for_bin_op,
method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind,
TyLoweringContext,
method_resolution, Adjustment, InferenceResult, Interner, Substitution, TraitEnvironment, Ty,
TyExt, TyKind, TyLoweringContext,
};
use intern::sym;
use itertools::Itertools;
@ -120,6 +121,13 @@ impl SourceAnalyzer {
self.def.as_ref().map(|(_, body, _)| &**body)
}
fn trait_environment(&self, db: &dyn HirDatabase) -> Arc<TraitEnvironment> {
self.def.as_ref().map(|(def, ..)| *def).map_or_else(
|| TraitEnvironment::empty(self.resolver.krate()),
|def| db.trait_environment_for_body(def),
)
}
fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprOrPatId> {
let src = match expr {
ast::Expr::MacroExpr(expr) => {
@ -294,18 +302,23 @@ impl SourceAnalyzer {
&self,
db: &dyn HirDatabase,
call: &ast::MethodCallExpr,
) -> Option<Either<Function, Field>> {
) -> Option<(Either<Function, Field>, Option<GenericSubstitution>)> {
let expr_id = self.expr_id(db, &call.clone().into())?.as_expr()?;
let inference_result = self.infer.as_ref()?;
match inference_result.method_resolution(expr_id) {
Some((f_in_trait, substs)) => Some(Either::Left(
self.resolve_impl_method_or_trait_def(db, f_in_trait, substs).into(),
)),
None => inference_result
.field_resolution(expr_id)
.and_then(Either::left)
.map(Into::into)
.map(Either::Right),
Some((f_in_trait, substs)) => {
let (fn_, subst) =
self.resolve_impl_method_or_trait_def_with_subst(db, f_in_trait, substs);
Some((
Either::Left(fn_.into()),
Some(GenericSubstitution::new(fn_.into(), subst, self.trait_environment(db))),
))
}
None => {
inference_result.field_resolution(expr_id).and_then(Either::left).map(|field| {
(Either::Right(field.into()), self.field_subst(expr_id, inference_result, db))
})
}
}
}
@ -330,22 +343,53 @@ impl SourceAnalyzer {
})
}
fn field_subst(
&self,
field_expr: ExprId,
infer: &InferenceResult,
db: &dyn HirDatabase,
) -> Option<GenericSubstitution> {
let body = self.body()?;
if let Expr::Field { expr: object_expr, name: _ } = body[field_expr] {
let (adt, subst) = type_of_expr_including_adjust(infer, object_expr)?.as_adt()?;
return Some(GenericSubstitution::new(
adt.into(),
subst.clone(),
self.trait_environment(db),
));
}
None
}
pub(crate) fn resolve_field_fallback(
&self,
db: &dyn HirDatabase,
field: &ast::FieldExpr,
) -> Option<Either<Either<Field, TupleField>, Function>> {
) -> Option<(Either<Either<Field, TupleField>, Function>, Option<GenericSubstitution>)> {
let &(def, ..) = self.def.as_ref()?;
let expr_id = self.expr_id(db, &field.clone().into())?.as_expr()?;
let inference_result = self.infer.as_ref()?;
match inference_result.field_resolution(expr_id) {
Some(field) => Some(Either::Left(field.map_either(Into::into, |f| TupleField {
Some(field) => match field {
Either::Left(field) => Some((
Either::Left(Either::Left(field.into())),
self.field_subst(expr_id, inference_result, db),
)),
Either::Right(field) => Some((
Either::Left(Either::Right(TupleField {
owner: def,
tuple: f.tuple,
index: f.index,
}))),
tuple: field.tuple,
index: field.index,
})),
None,
)),
},
None => inference_result.method_resolution(expr_id).map(|(f, substs)| {
Either::Right(self.resolve_impl_method_or_trait_def(db, f, substs).into())
let (f, subst) = self.resolve_impl_method_or_trait_def_with_subst(db, f, substs);
(
Either::Right(f.into()),
Some(GenericSubstitution::new(f.into(), subst, self.trait_environment(db))),
)
}),
}
}
@ -557,7 +601,7 @@ impl SourceAnalyzer {
&self,
db: &dyn HirDatabase,
field: &ast::RecordExprField,
) -> Option<(Field, Option<Local>, Type)> {
) -> Option<(Field, Option<Local>, Type, GenericSubstitution)> {
let record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
let expr = ast::Expr::from(record_expr);
let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?;
@ -583,30 +627,39 @@ impl SourceAnalyzer {
_ => None,
}
};
let (_, subst) = self.infer.as_ref()?.type_of_expr_or_pat(expr_id)?.as_adt()?;
let (adt, subst) = self.infer.as_ref()?.type_of_expr_or_pat(expr_id)?.as_adt()?;
let variant = self.infer.as_ref()?.variant_resolution_for_expr_or_pat(expr_id)?;
let variant_data = variant.variant_data(db.upcast());
let field = FieldId { parent: variant, local_id: variant_data.field(&local_name)? };
let field_ty =
db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst);
Some((field.into(), local, Type::new_with_resolver(db, &self.resolver, field_ty)))
Some((
field.into(),
local,
Type::new_with_resolver(db, &self.resolver, field_ty),
GenericSubstitution::new(adt.into(), subst.clone(), self.trait_environment(db)),
))
}
pub(crate) fn resolve_record_pat_field(
&self,
db: &dyn HirDatabase,
field: &ast::RecordPatField,
) -> Option<(Field, Type)> {
) -> Option<(Field, Type, GenericSubstitution)> {
let field_name = field.field_name()?.as_name();
let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
let pat_id = self.pat_id(&record_pat.into())?;
let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?;
let variant_data = variant.variant_data(db.upcast());
let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? };
let (_, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?;
let (adt, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?;
let field_ty =
db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst);
Some((field.into(), Type::new_with_resolver(db, &self.resolver, field_ty)))
Some((
field.into(),
Type::new_with_resolver(db, &self.resolver, field_ty),
GenericSubstitution::new(adt.into(), subst.clone(), self.trait_environment(db)),
))
}
pub(crate) fn resolve_macro_call(
@ -654,7 +707,7 @@ impl SourceAnalyzer {
&self,
db: &dyn HirDatabase,
path: &ast::Path,
) -> Option<PathResolution> {
) -> Option<(PathResolution, Option<GenericSubstitution>)> {
let parent = path.syntax().parent();
let parent = || parent.clone();
@ -664,60 +717,106 @@ impl SourceAnalyzer {
if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) {
let expr_id = self.expr_id(db, &path_expr.into())?;
if let Some((assoc, subs)) = infer.assoc_resolutions_for_expr_or_pat(expr_id) {
let assoc = match assoc {
let (assoc, subst) = match assoc {
AssocItemId::FunctionId(f_in_trait) => {
match infer.type_of_expr_or_pat(expr_id) {
None => assoc,
None => {
let subst = GenericSubstitution::new(
f_in_trait.into(),
subs,
self.trait_environment(db),
);
(assoc, subst)
}
Some(func_ty) => {
if let TyKind::FnDef(_fn_def, subs) = func_ty.kind(Interner) {
self.resolve_impl_method_or_trait_def(
let (fn_, subst) = self
.resolve_impl_method_or_trait_def_with_subst(
db,
f_in_trait,
subs.clone(),
)
.into()
);
let subst = GenericSubstitution::new(
fn_.into(),
subst,
self.trait_environment(db),
);
(fn_.into(), subst)
} else {
assoc
let subst = GenericSubstitution::new(
f_in_trait.into(),
subs,
self.trait_environment(db),
);
(assoc, subst)
}
}
}
}
AssocItemId::ConstId(const_id) => {
self.resolve_impl_const_or_trait_def(db, const_id, subs).into()
let (konst, subst) =
self.resolve_impl_const_or_trait_def_with_subst(db, const_id, subs);
let subst = GenericSubstitution::new(
konst.into(),
subst,
self.trait_environment(db),
);
(konst.into(), subst)
}
assoc => assoc,
AssocItemId::TypeAliasId(type_alias) => (
assoc,
GenericSubstitution::new(
type_alias.into(),
subs,
self.trait_environment(db),
),
),
};
return Some(PathResolution::Def(AssocItem::from(assoc).into()));
return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst)));
}
if let Some(VariantId::EnumVariantId(variant)) =
infer.variant_resolution_for_expr_or_pat(expr_id)
{
return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None));
}
prefer_value_ns = true;
} else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) {
let pat_id = self.pat_id(&path_pat.into())?;
if let Some((assoc, subs)) = infer.assoc_resolutions_for_pat(pat_id) {
let assoc = match assoc {
let (assoc, subst) = match assoc {
AssocItemId::ConstId(const_id) => {
self.resolve_impl_const_or_trait_def(db, const_id, subs).into()
let (konst, subst) =
self.resolve_impl_const_or_trait_def_with_subst(db, const_id, subs);
let subst = GenericSubstitution::new(
konst.into(),
subst,
self.trait_environment(db),
);
(konst.into(), subst)
}
assoc => assoc,
assoc => (
assoc,
GenericSubstitution::new(
assoc.into(),
subs,
self.trait_environment(db),
),
),
};
return Some(PathResolution::Def(AssocItem::from(assoc).into()));
return Some((PathResolution::Def(AssocItem::from(assoc).into()), Some(subst)));
}
if let Some(VariantId::EnumVariantId(variant)) =
infer.variant_resolution_for_pat(pat_id)
{
return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None));
}
} else if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) {
let expr_id = self.expr_id(db, &rec_lit.into())?;
if let Some(VariantId::EnumVariantId(variant)) =
infer.variant_resolution_for_expr_or_pat(expr_id)
{
return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
return Some((PathResolution::Def(ModuleDef::Variant(variant.into())), None));
}
} else {
let record_pat = parent().and_then(ast::RecordPat::cast).map(ast::Pat::from);
@ -727,7 +826,10 @@ impl SourceAnalyzer {
let pat_id = self.pat_id(&pat)?;
let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id);
if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat {
return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
return Some((
PathResolution::Def(ModuleDef::Variant(variant.into())),
None,
));
}
}
}
@ -747,7 +849,8 @@ impl SourceAnalyzer {
// trying to resolve foo::bar.
if let Some(use_tree) = parent().and_then(ast::UseTree::cast) {
if use_tree.coloncolon_token().is_some() {
return resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map);
return resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map)
.map(|it| (it, None));
}
}
@ -765,13 +868,18 @@ impl SourceAnalyzer {
// trying to resolve foo::bar.
if path.parent_path().is_some() {
return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) {
None if meta_path.is_some() => {
path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| {
None if meta_path.is_some() => path
.first_segment()
.and_then(|it| it.name_ref())
.and_then(|name_ref| {
ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text())
.map(PathResolution::ToolModule)
})
}
res => res,
.map(|it| (it, None)),
// FIXME: We do not show substitutions for parts of path, because this is really complex
// due to the interactions with associated items of `impl`s and associated items of associated
// types.
res => res.map(|it| (it, None)),
};
} else if let Some(meta_path) = meta_path {
// Case where we are resolving the final path segment of a path in an attribute
@ -781,7 +889,7 @@ impl SourceAnalyzer {
let builtin =
BuiltinAttr::by_name(db, self.resolver.krate().into(), &name_ref.text());
if builtin.is_some() {
return builtin.map(PathResolution::BuiltinAttr);
return builtin.map(|it| (PathResolution::BuiltinAttr(it), None));
}
if let Some(attr) = meta_path.parent_attr() {
@ -814,10 +922,13 @@ impl SourceAnalyzer {
{
if let Some(idx) = helpers.position(|(name, ..)| *name == name_ref)
{
return Some(PathResolution::DeriveHelper(DeriveHelper {
return Some((
PathResolution::DeriveHelper(DeriveHelper {
derive: *macro_id,
idx: idx as u32,
}));
}),
None,
));
}
}
}
@ -825,27 +936,80 @@ impl SourceAnalyzer {
}
}
return match resolve_hir_path_as_attr_macro(db, &self.resolver, &hir_path) {
Some(m) => Some(PathResolution::Def(ModuleDef::Macro(m))),
Some(m) => Some((PathResolution::Def(ModuleDef::Macro(m)), None)),
// this labels any path that starts with a tool module as the tool itself, this is technically wrong
// but there is no benefit in differentiating these two cases for the time being
None => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| {
None => path
.first_segment()
.and_then(|it| it.name_ref())
.and_then(|name_ref| {
ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text())
.map(PathResolution::ToolModule)
}),
})
.map(|it| (it, None)),
};
}
if parent().map_or(false, |it| ast::Visibility::can_cast(it.kind())) {
// No substitution because only modules can be inside visibilities, and those have no generics.
resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map)
.map(|it| (it, None))
} else {
resolve_hir_path_(
// Probably a type, no need to show substitutions for those.
let res = resolve_hir_path_(
db,
&self.resolver,
&hir_path,
prefer_value_ns,
name_hygiene(db, InFile::new(self.file_id, path.syntax())),
&types_map,
)?;
let subst = (|| {
let parent = parent()?;
let ty = if let Some(expr) = ast::Expr::cast(parent.clone()) {
let expr_id = self.expr_id(db, &expr)?;
self.infer.as_ref()?.type_of_expr_or_pat(expr_id)?
} else if let Some(pat) = ast::Pat::cast(parent) {
let pat_id = self.pat_id(&pat)?;
&self.infer.as_ref()?[pat_id]
} else {
return None;
};
let env = self.trait_environment(db);
let (subst, expected_resolution) = match ty.kind(Interner) {
TyKind::Adt(adt_id, subst) => (
GenericSubstitution::new(adt_id.0.into(), subst.clone(), env),
PathResolution::Def(ModuleDef::Adt(adt_id.0.into())),
),
TyKind::AssociatedType(assoc_id, subst) => {
let assoc_id = from_assoc_type_id(*assoc_id);
(
GenericSubstitution::new(assoc_id.into(), subst.clone(), env),
PathResolution::Def(ModuleDef::TypeAlias(assoc_id.into())),
)
}
TyKind::FnDef(fn_id, subst) => {
let fn_id = hir_ty::db::InternedCallableDefId::from(*fn_id);
let fn_id = db.lookup_intern_callable_def(fn_id);
let generic_def_id = match fn_id {
CallableDefId::StructId(id) => id.into(),
CallableDefId::FunctionId(id) => id.into(),
CallableDefId::EnumVariantId(_) => return None,
};
(
GenericSubstitution::new(generic_def_id, subst.clone(), env),
PathResolution::Def(ModuleDefId::from(fn_id).into()),
)
}
_ => return None,
};
if res != expected_resolution {
// The user will not understand where we're coming from. This can happen (I think) with type aliases.
return None;
}
Some(subst)
})();
Some((res, subst))
}
}
pub(crate) fn record_literal_missing_fields(
@ -1041,26 +1205,35 @@ impl SourceAnalyzer {
func: FunctionId,
substs: Substitution,
) -> FunctionId {
let owner = match self.resolver.body_owner() {
Some(it) => it,
None => return func,
};
let env = db.trait_environment_for_body(owner);
db.lookup_impl_method(env, func, substs).0
self.resolve_impl_method_or_trait_def_with_subst(db, func, substs).0
}
fn resolve_impl_const_or_trait_def(
fn resolve_impl_method_or_trait_def_with_subst(
&self,
db: &dyn HirDatabase,
func: FunctionId,
substs: Substitution,
) -> (FunctionId, Substitution) {
let owner = match self.resolver.body_owner() {
Some(it) => it,
None => return (func, substs),
};
let env = db.trait_environment_for_body(owner);
db.lookup_impl_method(env, func, substs)
}
fn resolve_impl_const_or_trait_def_with_subst(
&self,
db: &dyn HirDatabase,
const_id: ConstId,
subs: Substitution,
) -> ConstId {
) -> (ConstId, Substitution) {
let owner = match self.resolver.body_owner() {
Some(it) => it,
None => return const_id,
None => return (const_id, subs),
};
let env = db.trait_environment_for_body(owner);
method_resolution::lookup_impl_const(db, env, const_id, subs).0
method_resolution::lookup_impl_const(db, env, const_id, subs)
}
fn lang_trait_fn(
@ -1413,3 +1586,10 @@ pub(crate) fn name_hygiene(db: &dyn HirDatabase, name: InFile<&SyntaxNode>) -> H
let ctx = db.lookup_intern_syntax_context(ctx);
HygieneId::new(ctx.opaque_and_semitransparent)
}
fn type_of_expr_including_adjust(infer: &InferenceResult, id: ExprId) -> Option<&Ty> {
match infer.expr_adjustments.get(&id).and_then(|adjustments| adjustments.last()) {
Some(adjustment) => Some(&adjustment.target),
None => infer.type_of_expr.get(id),
}
}

View file

@ -69,7 +69,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
let ident = name_ref.ident_token()?;
let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
NameRefClass::Definition(def) => def,
NameRefClass::Definition(def, _) => def,
NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => {
return None
}

View file

@ -103,7 +103,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti
ast::Expr::PathExpr(path) => {
let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?;
match NameRefClass::classify(&ctx.sema, &name_ref)? {
NameRefClass::Definition(Definition::Local(local)) => {
NameRefClass::Definition(Definition::Local(local), _) => {
let source =
local.sources(ctx.db()).into_iter().map(|x| x.into_ident_pat()?.name());
source.collect()

View file

@ -163,8 +163,8 @@ fn edit_struct_references(
// this also includes method calls like Foo::new(42), we should skip them
if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
match NameRefClass::classify(&ctx.sema, &name_ref) {
Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
Some(NameRefClass::Definition(def)) if def == strukt_def => {},
Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {},
Some(NameRefClass::Definition(def, _)) if def == strukt_def => {},
_ => return None,
};
}

View file

@ -275,6 +275,7 @@ fn find_imported_defs(ctx: &AssistContext<'_>, star: SyntaxToken) -> Option<Vec<
| Definition::Static(_)
| Definition::Trait(_)
| Definition::TypeAlias(_)),
_,
) => Some(def),
_ => None,
})

View file

@ -800,8 +800,8 @@ impl FunctionBody {
let local_ref =
match name_ref.and_then(|name_ref| NameRefClass::classify(sema, &name_ref)) {
Some(
NameRefClass::Definition(Definition::Local(local_ref))
| NameRefClass::FieldShorthand { local_ref, field_ref: _ },
NameRefClass::Definition(Definition::Local(local_ref), _)
| NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ },
) => local_ref,
_ => return,
};

View file

@ -425,7 +425,9 @@ impl Module {
})
} else if let Some(name_ref) = ast::NameRef::cast(x) {
NameRefClass::classify(&ctx.sema, &name_ref).and_then(|nc| match nc {
NameRefClass::Definition(def) => Some((name_ref.syntax().clone(), def)),
NameRefClass::Definition(def, _) => {
Some((name_ref.syntax().clone(), def))
}
_ => None,
})
} else {

View file

@ -64,7 +64,7 @@ pub(crate) fn generate_constant(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
let name_ref_value = name_ref?;
let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value);
match name_ref_class {
Some(NameRefClass::Definition(Definition::Module(m))) => {
Some(NameRefClass::Definition(Definition::Module(m), _)) => {
if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) {
return None;
}

View file

@ -1077,7 +1077,7 @@ fn fn_arg_name(sema: &Semantics<'_, RootDatabase>, arg_expr: &ast::Expr) -> Stri
.filter_map(ast::NameRef::cast)
.filter(|name| name.ident_token().is_some())
.last()?;
if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_))) =
if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_), _)) =
NameRefClass::classify(sema, &name_ref)
{
return Some(name_ref.to_string().to_lowercase());

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
) =>

View file

@ -47,7 +47,7 @@ pub(crate) fn incoming_calls(
.find_nodes_at_offset_with_descend(file, offset)
.filter_map(move |node| match node {
ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
NameRefClass::Definition(def @ Definition::Function(_)) => Some(def),
NameRefClass::Definition(def @ Definition::Function(_), _) => Some(def),
_ => None,
},
ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {

View file

@ -147,8 +147,8 @@ pub(crate) fn external_docs(
let definition = match_ast! {
match node {
ast::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? {
NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
NameRefClass::Definition(def, _) => def,
NameRefClass::FieldShorthand { local_ref: _, field_ref, adt_subst: _ } => {
Definition::Field(field_ref)
}
NameRefClass::ExternCrateShorthand { decl, .. } => {
@ -157,7 +157,7 @@ pub(crate) fn external_docs(
},
ast::Name(name) => match NameClass::classify(sema, &name)? {
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::PatFieldShorthand { local_def: _, field_ref } => Definition::Field(field_ref),
NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ } => Definition::Field(field_ref),
},
_ => return None
}

View file

@ -36,7 +36,7 @@ pub(crate) fn goto_declaration(
let def = match_ast! {
match parent {
ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
NameRefClass::Definition(it) => Some(it),
NameRefClass::Definition(it, _) => Some(it),
NameRefClass::FieldShorthand { field_ref, .. } =>
return field_ref.try_to_nav(db),
NameRefClass::ExternCrateShorthand { decl, .. } =>

View file

@ -103,7 +103,7 @@ pub(crate) fn goto_definition(
IdentClass::classify_node(sema, &parent)?
.definitions()
.into_iter()
.flat_map(|def| {
.flat_map(|(def, _)| {
if let Definition::ExternCrateDecl(crate_def) = def {
return crate_def
.resolved_crate(db)

View file

@ -48,7 +48,7 @@ pub(crate) fn goto_implementation(
}
ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
.and_then(|class| match class {
NameRefClass::Definition(def) => Some(def),
NameRefClass::Definition(def, _) => Some(def),
NameRefClass::FieldShorthand { .. }
| NameRefClass::ExternCrateShorthand { .. } => None,
}),

View file

@ -6,7 +6,7 @@ mod tests;
use std::{iter, ops::Not};
use either::Either;
use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics};
use hir::{db::DefDatabase, GenericSubstitution, HasCrate, HasSource, LangItem, Semantics};
use ide_db::{
defs::{Definition, IdentClass, NameRefClass, OperatorClass},
famous_defs::FamousDefs,
@ -35,6 +35,14 @@ pub struct HoverConfig {
pub max_trait_assoc_items_count: Option<usize>,
pub max_fields_count: Option<usize>,
pub max_enum_variants_count: Option<usize>,
pub max_subst_ty_len: SubstTyLen,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SubstTyLen {
Unlimited,
LimitTo(usize),
Hide,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -158,7 +166,8 @@ fn hover_offset(
if let Some(doc_comment) = token_as_doc_comment(&original_token) {
cov_mark::hit!(no_highlight_on_comment_hover);
return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
let res = hover_for_definition(sema, file_id, def, &node, None, false, config, edition);
let res =
hover_for_definition(sema, file_id, def, None, &node, None, false, config, edition);
Some(RangeInfo::new(range, res))
});
}
@ -170,6 +179,7 @@ fn hover_offset(
sema,
file_id,
Definition::from(resolution?),
None,
&original_token.parent()?,
None,
false,
@ -217,7 +227,7 @@ fn hover_offset(
{
if let Some(macro_) = sema.resolve_macro_call(&macro_call) {
break 'a vec![(
Definition::Macro(macro_),
(Definition::Macro(macro_), None),
sema.resolve_macro_call_arm(&macro_call),
false,
node,
@ -236,7 +246,7 @@ fn hover_offset(
decl,
..
}) => {
vec![(Definition::ExternCrateDecl(decl), None, false, node)]
vec![((Definition::ExternCrateDecl(decl), None), None, false, node)]
}
class => {
@ -252,12 +262,13 @@ fn hover_offset(
}
}
.into_iter()
.unique_by(|&(def, _, _, _)| def)
.map(|(def, macro_arm, hovered_definition, node)| {
.unique_by(|&((def, _), _, _, _)| def)
.map(|((def, subst), macro_arm, hovered_definition, node)| {
hover_for_definition(
sema,
file_id,
def,
subst,
&node,
macro_arm,
hovered_definition,
@ -381,6 +392,7 @@ pub(crate) fn hover_for_definition(
sema: &Semantics<'_, RootDatabase>,
file_id: FileId,
def: Definition,
subst: Option<GenericSubstitution>,
scope_node: &SyntaxNode,
macro_arm: Option<u32>,
hovered_definition: bool,
@ -408,6 +420,7 @@ pub(crate) fn hover_for_definition(
_ => None,
};
let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default();
let subst_types = subst.map(|subst| subst.types(db));
let markup = render::definition(
sema.db,
@ -416,6 +429,7 @@ pub(crate) fn hover_for_definition(
&notable_traits,
macro_arm,
hovered_definition,
subst_types,
config,
edition,
);

View file

@ -5,7 +5,7 @@ use either::Either;
use hir::{
db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, AssocItemContainer, CaptureKind,
DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError,
MethodViolationCode, Name, Semantics, Trait, Type, TypeInfo,
MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo,
};
use ide_db::{
base_db::SourceDatabase,
@ -27,7 +27,7 @@ use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T}
use crate::{
doc_links::{remove_links, rewrite_links},
hover::{notable_traits, walk_and_push_ty},
hover::{notable_traits, walk_and_push_ty, SubstTyLen},
interpret::render_const_eval_error,
HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig,
MemoryLayoutHoverRenderKind,
@ -274,7 +274,7 @@ pub(super) fn keyword(
let markup = process_markup(
sema.db,
Definition::Module(doc_owner),
&markup(Some(docs.into()), description, None, None),
&markup(Some(docs.into()), description, None, None, String::new()),
config,
);
Some(HoverResult { markup, actions })
@ -421,6 +421,7 @@ pub(super) fn definition(
notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
macro_arm: Option<u32>,
hovered_definition: bool,
subst_types: Option<Vec<(Symbol, Type)>>,
config: &HoverConfig,
edition: Edition,
) -> Markup {
@ -604,7 +605,38 @@ pub(super) fn definition(
desc.push_str(&value);
}
markup(docs.map(Into::into), desc, extra.is_empty().not().then_some(extra), mod_path)
let subst_types = match config.max_subst_ty_len {
SubstTyLen::Hide => String::new(),
SubstTyLen::LimitTo(_) | SubstTyLen::Unlimited => {
let limit = if let SubstTyLen::LimitTo(limit) = config.max_subst_ty_len {
Some(limit)
} else {
None
};
subst_types
.map(|subst_type| {
subst_type
.iter()
.filter(|(_, ty)| !ty.is_unknown())
.format_with(", ", |(name, ty), fmt| {
fmt(&format_args!(
"`{name}` = `{}`",
ty.display_truncated(db, limit, edition)
))
})
.to_string()
})
.unwrap_or_default()
}
};
markup(
docs.map(Into::into),
desc,
extra.is_empty().not().then_some(extra),
mod_path,
subst_types,
)
}
pub(super) fn literal(
@ -872,6 +904,7 @@ fn markup(
rust: String,
extra: Option<String>,
mod_path: Option<String>,
subst_types: String,
) -> Markup {
let mut buf = String::new();
@ -886,6 +919,10 @@ fn markup(
buf.push_str(&extra);
}
if !subst_types.is_empty() {
format_to!(buf, "\n___\n{subst_types}");
}
if let Some(doc) = docs {
format_to!(buf, "\n___\n\n{}", doc);
}

View file

@ -20,6 +20,7 @@ const HOVER_BASE_CONFIG: HoverConfig = HoverConfig {
max_trait_assoc_items_count: None,
max_fields_count: Some(5),
max_enum_variants_count: Some(5),
max_subst_ty_len: super::SubstTyLen::Unlimited,
};
fn check_hover_no_result(ra_fixture: &str) {
@ -5176,6 +5177,10 @@ fn main() {
---
`Self` = `()`
---
false
"#]],
);
@ -5208,6 +5213,10 @@ fn main() {
---
`Self` = `i32`
---
false
"#]],
);
@ -9501,3 +9510,409 @@ fn main() {
"#]],
);
}
#[test]
fn subst_fn() {
check(
r#"
struct Foo<T>(T);
impl<T> Foo<T> {
fn foo<U>(v: T, u: U) {}
}
fn bar() {
Foo::fo$0o(123, false);
}
"#,
expect![[r#"
*foo*
```rust
ra_test_fixture::Foo
```
```rust
impl<T> Foo<T>
fn foo<U>(v: T, u: U)
```
---
`T` = `i32`, `U` = `bool`
"#]],
);
check(
r#"
fn foo<T>(v: T) {}
fn bar() {
fo$0o(123);
}
"#,
expect![[r#"
*foo*
```rust
ra_test_fixture
```
```rust
fn foo<T>(v: T)
```
---
`T` = `i32`
"#]],
);
}
#[test]
fn subst_record_constructor() {
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let v = $0Foo { field: 123 };
}
"#,
expect![[r#"
*Foo*
```rust
ra_test_fixture
```
```rust
struct Foo<T> {
field: T,
}
```
---
`T` = `i32`
"#]],
);
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let v = Foo { field: 123 };
let $0Foo { field: _ } = v;
}
"#,
expect![[r#"
*Foo*
```rust
ra_test_fixture
```
```rust
struct Foo<T> {
field: T,
}
```
---
`T` = `i32`
"#]],
);
}
#[test]
fn subst_method_call() {
check(
r#"
struct Foo<T>(T);
impl<U> Foo<U> {
fn bar<T>(self, v: T) {}
}
fn baz() {
Foo(123).bar$0("hello");
}
"#,
expect![[r#"
*bar*
```rust
ra_test_fixture::Foo
```
```rust
impl<U> Foo<U>
fn bar<T>(self, v: T)
```
---
`U` = `i32`, `T` = `&str`
"#]],
);
}
#[test]
fn subst_type_alias_do_not_work() {
// It is very hard to support subst for type aliases properly in all places because they are eagerly evaluated.
// We can show the user the subst for the underlying type instead but that'll be very confusing.
check(
r#"
struct Foo<T, U> { a: T, b: U }
type Alias<T> = Foo<T, i32>;
fn foo() {
let _ = Alias$0 { a: true, b: 123 };
}
"#,
expect![[r#"
*Alias*
```rust
ra_test_fixture
```
```rust
type Alias<T> = Foo<T, i32>
```
"#]],
);
}
#[test]
fn subst_self() {
check(
r#"
trait Trait<T> {
fn foo<U>(&self, v: U) {}
}
struct Struct<T>(T);
impl<T> Trait<i64> for Struct<T> {}
fn bar() {
Struct(123).foo$0(true);
}
"#,
expect![[r#"
*foo*
```rust
ra_test_fixture::Trait
```
```rust
trait Trait<T>
fn foo<U>(&self, v: U)
```
---
`Self` = `Struct<i32>`, `T` = `i64`, `U` = `bool`
"#]],
);
}
#[test]
fn subst_with_lifetimes_and_consts() {
check(
r#"
struct Foo<'a, const N: usize, T>(&[T; N]);
impl<'a, T, const N: usize> Foo<'a, N, T> {
fn foo<'b, const Z: u32, U>(&self, v: U) {}
}
fn bar() {
Foo(&[1i8]).fo$0o::<456, _>("");
}
"#,
expect![[r#"
*foo*
```rust
ra_test_fixture::Foo
```
```rust
impl<'a, T, const N: usize> Foo<'a, N, T>
fn foo<'b, const Z: u32, U>(&self, v: U)
```
---
`T` = `i8`, `U` = `&str`
"#]],
);
}
#[test]
fn subst_field() {
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let v = Foo { $0field: 123 };
}
"#,
expect![[r#"
*field*
```rust
ra_test_fixture::Foo
```
```rust
field: T
```
---
`T` = `i32`
"#]],
);
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let field = 123;
let v = Foo { field$0 };
}
"#,
expect![[r#"
*field*
```rust
let field: i32
```
---
```rust
ra_test_fixture::Foo
```
```rust
field: T
```
---
`T` = `i32`
"#]],
);
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let v = Foo { field: 123 };
let Foo { field$0 } = v;
}
"#,
expect![[r#"
*field*
```rust
let field: i32
```
---
size = 4, align = 4
---
```rust
ra_test_fixture::Foo
```
```rust
field: T
```
---
`T` = `i32`
"#]],
);
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let v = Foo { field: 123 };
let Foo { field$0: _ } = v;
}
"#,
expect![[r#"
*field*
```rust
ra_test_fixture::Foo
```
```rust
field: T
```
---
`T` = `i32`
"#]],
);
check(
r#"
struct Foo<T> { field: T }
fn bar() {
let v = Foo { field: 123 };
let _ = (&v).$0field;
}
"#,
expect![[r#"
*field*
```rust
ra_test_fixture::Foo
```
```rust
field: T
```
---
`T` = `i32`
"#]],
);
check(
r#"
struct Foo<T>(T);
fn bar() {
let v = Foo(123);
let _ = v.$00;
}
"#,
expect![[r#"
*0*
```rust
ra_test_fixture::Foo
```
```rust
0: T
```
---
`T` = `i32`
"#]],
);
}

View file

@ -86,7 +86,7 @@ pub use crate::{
highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{
HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult,
MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind,
MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, SubstTyLen,
},
inlay_hints::{
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,

View file

@ -112,7 +112,7 @@ pub(crate) fn find_all_refs(
Some(name) => {
let def = match NameClass::classify(sema, &name)? {
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::PatFieldShorthand { local_def: _, field_ref } => {
NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ } => {
Definition::Field(field_ref)
}
};
@ -156,10 +156,12 @@ pub(crate) fn find_defs<'a>(
let def = match name_like {
ast::NameLike::NameRef(name_ref) => {
match NameRefClass::classify(sema, &name_ref)? {
NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
Definition::Local(local_ref)
}
NameRefClass::Definition(def, _) => def,
NameRefClass::FieldShorthand {
local_ref,
field_ref: _,
adt_subst: _,
} => Definition::Local(local_ref),
NameRefClass::ExternCrateShorthand { decl, .. } => {
Definition::ExternCrateDecl(decl)
}
@ -167,14 +169,14 @@ pub(crate) fn find_defs<'a>(
}
ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {
Definition::Local(local_def)
}
},
ast::NameLike::Lifetime(lifetime) => {
NameRefClass::classify_lifetime(sema, &lifetime)
.and_then(|class| match class {
NameRefClass::Definition(it) => Some(it),
NameRefClass::Definition(it, _) => Some(it),
_ => None,
})
.or_else(|| {

View file

@ -242,7 +242,7 @@ fn find_definitions(
ast::NameLike::Name(name) => NameClass::classify(sema, name)
.map(|class| match class {
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {
Definition::Local(local_def)
}
})
@ -250,8 +250,8 @@ fn find_definitions(
ast::NameLike::NameRef(name_ref) => {
NameRefClass::classify(sema, name_ref)
.map(|class| match class {
NameRefClass::Definition(def) => def,
NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
NameRefClass::Definition(def, _) => def,
NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {
Definition::Local(local_ref)
}
NameRefClass::ExternCrateShorthand { decl, .. } => {
@ -276,7 +276,7 @@ fn find_definitions(
ast::NameLike::Lifetime(lifetime) => {
NameRefClass::classify_lifetime(sema, lifetime)
.and_then(|class| match class {
NameRefClass::Definition(def) => Some(def),
NameRefClass::Definition(def, _) => Some(def),
_ => None,
})
.or_else(|| {

View file

@ -13,11 +13,10 @@ use ide_db::{
use span::Edition;
use syntax::{AstNode, SyntaxKind::*, SyntaxNode, TextRange, T};
use crate::inlay_hints::InlayFieldsToResolve;
use crate::navigation_target::UpmappingResult;
use crate::{
hover::hover_for_definition,
inlay_hints::AdjustmentHintsMode,
hover::{hover_for_definition, SubstTyLen},
inlay_hints::{AdjustmentHintsMode, InlayFieldsToResolve},
moniker::{def_to_kind, def_to_moniker, MonikerResult, SymbolInformationKind},
parent_module::crates_for,
Analysis, Fold, HoverConfig, HoverResult, InlayHint, InlayHintsConfig, TryToNav,
@ -186,6 +185,7 @@ impl StaticIndex<'_> {
max_trait_assoc_items_count: None,
max_fields_count: Some(5),
max_enum_variants_count: Some(5),
max_subst_ty_len: SubstTyLen::Unlimited,
};
let tokens = tokens.filter(|token| {
matches!(
@ -210,6 +210,7 @@ impl StaticIndex<'_> {
&sema,
file_id,
def,
None,
&node,
None,
false,

View file

@ -76,7 +76,7 @@ pub(super) fn name_like(
Some(IdentClass::NameClass(NameClass::Definition(def))) => {
highlight_def(sema, krate, def) | HlMod::Definition
}
Some(IdentClass::NameRefClass(NameRefClass::Definition(def))) => {
Some(IdentClass::NameRefClass(NameRefClass::Definition(def, _))) => {
highlight_def(sema, krate, def)
}
// FIXME: Fallback for 'static and '_, as we do not resolve these yet
@ -260,7 +260,7 @@ fn highlight_name_ref(
None => return HlTag::UnresolvedReference.into(),
};
let mut h = match name_class {
NameRefClass::Definition(def) => {
NameRefClass::Definition(def, _) => {
if let Definition::Local(local) = &def {
let name = local.name(db);
let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();

View file

@ -50,6 +50,14 @@ mod patch_old_style;
// - Don't use abbreviations unless really necessary
// - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum MaxSubstitutionLength {
Hide,
#[serde(untagged)]
Limit(usize),
}
// Defines the server-side configuration of the rust-analyzer. We generate
// *parts* of VS Code's `package.json` config from this. Run `cargo test` to
// re-generate that file.
@ -119,6 +127,12 @@ config_data! {
hover_documentation_keywords_enable: bool = true,
/// Use markdown syntax for links on hover.
hover_links_enable: bool = true,
/// Whether to show what types are used as generic arguments in calls etc. on hover, and what is their max length to show such types, beyond it they will be shown with ellipsis.
///
/// This can take three values: `null` means "unlimited", the string `"hide"` means to not show generic substitutions at all, and a number means to limit them to X characters.
///
/// The default is 20 characters.
hover_maxSubstitutionLength: Option<MaxSubstitutionLength> = Some(MaxSubstitutionLength::Limit(20)),
/// How to render the align information in a memory layout hover.
hover_memoryLayout_alignment: Option<MemoryLayoutHoverRenderKindDef> = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal),
/// Whether to show memory layout data on hover.
@ -1533,6 +1547,11 @@ impl Config {
max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(),
max_fields_count: self.hover_show_fields().to_owned(),
max_enum_variants_count: self.hover_show_enumVariants().to_owned(),
max_subst_ty_len: match self.hover_maxSubstitutionLength() {
Some(MaxSubstitutionLength::Hide) => ide::SubstTyLen::Hide,
Some(MaxSubstitutionLength::Limit(limit)) => ide::SubstTyLen::LimitTo(*limit),
None => ide::SubstTyLen::Unlimited,
},
}
}
@ -3434,6 +3453,20 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"Use `cargo metadata` to query sysroot metadata."
],
},
"Option<MaxSubstitutionLength>" => set! {
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"enum": ["hide"]
},
{
"type": "integer"
}
]
},
_ => panic!("missing entry for {ty}: {default} (field {field})"),
}

View file

@ -513,6 +513,15 @@ Whether to show keyword hover popups. Only applies when
--
Use markdown syntax for links on hover.
--
[[rust-analyzer.hover.maxSubstitutionLength]]rust-analyzer.hover.maxSubstitutionLength (default: `20`)::
+
--
Whether to show what types are used as generic arguments in calls etc. on hover, and what is their max length to show such types, beyond it they will be shown with ellipsis.
This can take three values: `null` means "unlimited", the string `"hide"` means to not show generic substitutions at all, and a number means to limit them to X characters.
The default is 20 characters.
--
[[rust-analyzer.hover.memoryLayout.alignment]]rust-analyzer.hover.memoryLayout.alignment (default: `"hexadecimal"`)::
+
--

View file

@ -1545,6 +1545,29 @@
}
}
},
{
"title": "hover",
"properties": {
"rust-analyzer.hover.maxSubstitutionLength": {
"markdownDescription": "Whether to show what types are used as generic arguments in calls etc. on hover, and what is their max length to show such types, beyond it they will be shown with ellipsis.\n\nThis can take three values: `null` means \"unlimited\", the string `\"hide\"` means to not show generic substitutions at all, and a number means to limit them to X characters.\n\nThe default is 20 characters.",
"default": 20,
"anyOf": [
{
"type": "null"
},
{
"type": "string",
"enum": [
"hide"
]
},
{
"type": "integer"
}
]
}
}
},
{
"title": "hover",
"properties": {