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

@ -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. /// A single local definition.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Local { pub struct Local {

View file

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

View file

@ -9,8 +9,8 @@ use std::iter::{self, once};
use crate::{ use crate::{
db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr, db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static, BuiltinType, Callable, Const, DeriveHelper, Field, Function, GenericSubstitution, Local, Macro,
Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant, ModuleDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant,
}; };
use either::Either; use either::Either;
use hir_def::{ use hir_def::{
@ -18,15 +18,15 @@ use hir_def::{
scope::{ExprScopes, ScopeId}, scope::{ExprScopes, ScopeId},
Body, BodySourceMap, HygieneId, Body, BodySourceMap, HygieneId,
}, },
hir::{BindingId, ExprId, ExprOrPatId, Pat, PatId}, hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat, PatId},
lang_item::LangItem, lang_item::LangItem,
lower::LowerCtx, lower::LowerCtx,
nameres::MacroSubNs, nameres::MacroSubNs,
path::{ModPath, Path, PathKind}, path::{ModPath, Path, PathKind},
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
type_ref::{Mutability, TypesMap, TypesSourceMap}, type_ref::{Mutability, TypesMap, TypesSourceMap},
AsMacroCall, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, ItemContainerId, AsMacroCall, AssocItemId, CallableDefId, ConstId, DefWithBodyId, FieldId, FunctionId,
LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId,
}; };
use hir_expand::{ use hir_expand::{
mod_path::path, mod_path::path,
@ -38,9 +38,10 @@ use hir_ty::{
record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions, record_literal_missing_fields, record_pattern_missing_fields, unsafe_expressions,
InsideUnsafeBlock, InsideUnsafeBlock,
}, },
from_assoc_type_id,
lang_items::lang_items_for_bin_op, lang_items::lang_items_for_bin_op,
method_resolution, Adjustment, InferenceResult, Interner, Substitution, Ty, TyExt, TyKind, method_resolution, Adjustment, InferenceResult, Interner, Substitution, TraitEnvironment, Ty,
TyLoweringContext, TyExt, TyKind, TyLoweringContext,
}; };
use intern::sym; use intern::sym;
use itertools::Itertools; use itertools::Itertools;
@ -120,6 +121,13 @@ impl SourceAnalyzer {
self.def.as_ref().map(|(_, body, _)| &**body) 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> { fn expr_id(&self, db: &dyn HirDatabase, expr: &ast::Expr) -> Option<ExprOrPatId> {
let src = match expr { let src = match expr {
ast::Expr::MacroExpr(expr) => { ast::Expr::MacroExpr(expr) => {
@ -294,18 +302,23 @@ impl SourceAnalyzer {
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
call: &ast::MethodCallExpr, 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 expr_id = self.expr_id(db, &call.clone().into())?.as_expr()?;
let inference_result = self.infer.as_ref()?; let inference_result = self.infer.as_ref()?;
match inference_result.method_resolution(expr_id) { match inference_result.method_resolution(expr_id) {
Some((f_in_trait, substs)) => Some(Either::Left( Some((f_in_trait, substs)) => {
self.resolve_impl_method_or_trait_def(db, f_in_trait, substs).into(), let (fn_, subst) =
)), self.resolve_impl_method_or_trait_def_with_subst(db, f_in_trait, substs);
None => inference_result Some((
.field_resolution(expr_id) Either::Left(fn_.into()),
.and_then(Either::left) Some(GenericSubstitution::new(fn_.into(), subst, self.trait_environment(db))),
.map(Into::into) ))
.map(Either::Right), }
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( pub(crate) fn resolve_field_fallback(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
field: &ast::FieldExpr, field: &ast::FieldExpr,
) -> Option<Either<Either<Field, TupleField>, Function>> { ) -> Option<(Either<Either<Field, TupleField>, Function>, Option<GenericSubstitution>)> {
let &(def, ..) = self.def.as_ref()?; let &(def, ..) = self.def.as_ref()?;
let expr_id = self.expr_id(db, &field.clone().into())?.as_expr()?; let expr_id = self.expr_id(db, &field.clone().into())?.as_expr()?;
let inference_result = self.infer.as_ref()?; let inference_result = self.infer.as_ref()?;
match inference_result.field_resolution(expr_id) { match inference_result.field_resolution(expr_id) {
Some(field) => Some(Either::Left(field.map_either(Into::into, |f| TupleField { Some(field) => match field {
owner: def, Either::Left(field) => Some((
tuple: f.tuple, Either::Left(Either::Left(field.into())),
index: f.index, self.field_subst(expr_id, inference_result, db),
}))), )),
Either::Right(field) => Some((
Either::Left(Either::Right(TupleField {
owner: def,
tuple: field.tuple,
index: field.index,
})),
None,
)),
},
None => inference_result.method_resolution(expr_id).map(|(f, substs)| { 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, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
field: &ast::RecordExprField, 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 record_expr = ast::RecordExpr::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
let expr = ast::Expr::from(record_expr); let expr = ast::Expr::from(record_expr);
let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?; let expr_id = self.body_source_map()?.node_expr(InFile::new(self.file_id, &expr))?;
@ -583,30 +627,39 @@ impl SourceAnalyzer {
_ => None, _ => 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 = self.infer.as_ref()?.variant_resolution_for_expr_or_pat(expr_id)?;
let variant_data = variant.variant_data(db.upcast()); let variant_data = variant.variant_data(db.upcast());
let field = FieldId { parent: variant, local_id: variant_data.field(&local_name)? }; let field = FieldId { parent: variant, local_id: variant_data.field(&local_name)? };
let field_ty = let field_ty =
db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst); 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( pub(crate) fn resolve_record_pat_field(
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
field: &ast::RecordPatField, field: &ast::RecordPatField,
) -> Option<(Field, Type)> { ) -> Option<(Field, Type, GenericSubstitution)> {
let field_name = field.field_name()?.as_name(); let field_name = field.field_name()?.as_name();
let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?; let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?;
let pat_id = self.pat_id(&record_pat.into())?; let pat_id = self.pat_id(&record_pat.into())?;
let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?; let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?;
let variant_data = variant.variant_data(db.upcast()); let variant_data = variant.variant_data(db.upcast());
let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? }; 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 = let field_ty =
db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst); 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( pub(crate) fn resolve_macro_call(
@ -654,7 +707,7 @@ impl SourceAnalyzer {
&self, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
path: &ast::Path, path: &ast::Path,
) -> Option<PathResolution> { ) -> Option<(PathResolution, Option<GenericSubstitution>)> {
let parent = path.syntax().parent(); let parent = path.syntax().parent();
let parent = || parent.clone(); let parent = || parent.clone();
@ -664,60 +717,106 @@ impl SourceAnalyzer {
if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) { if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) {
let expr_id = self.expr_id(db, &path_expr.into())?; let expr_id = self.expr_id(db, &path_expr.into())?;
if let Some((assoc, subs)) = infer.assoc_resolutions_for_expr_or_pat(expr_id) { 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) => { AssocItemId::FunctionId(f_in_trait) => {
match infer.type_of_expr_or_pat(expr_id) { 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) => { Some(func_ty) => {
if let TyKind::FnDef(_fn_def, subs) = func_ty.kind(Interner) { if let TyKind::FnDef(_fn_def, subs) = func_ty.kind(Interner) {
self.resolve_impl_method_or_trait_def( let (fn_, subst) = self
db, .resolve_impl_method_or_trait_def_with_subst(
f_in_trait, db,
subs.clone(), f_in_trait,
) subs.clone(),
.into() );
let subst = GenericSubstitution::new(
fn_.into(),
subst,
self.trait_environment(db),
);
(fn_.into(), subst)
} else { } else {
assoc let subst = GenericSubstitution::new(
f_in_trait.into(),
subs,
self.trait_environment(db),
);
(assoc, subst)
} }
} }
} }
} }
AssocItemId::ConstId(const_id) => { 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)) = if let Some(VariantId::EnumVariantId(variant)) =
infer.variant_resolution_for_expr_or_pat(expr_id) 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; prefer_value_ns = true;
} else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) { } else if let Some(path_pat) = parent().and_then(ast::PathPat::cast) {
let pat_id = self.pat_id(&path_pat.into())?; let pat_id = self.pat_id(&path_pat.into())?;
if let Some((assoc, subs)) = infer.assoc_resolutions_for_pat(pat_id) { 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) => { 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)) = if let Some(VariantId::EnumVariantId(variant)) =
infer.variant_resolution_for_pat(pat_id) 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) { } else if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) {
let expr_id = self.expr_id(db, &rec_lit.into())?; let expr_id = self.expr_id(db, &rec_lit.into())?;
if let Some(VariantId::EnumVariantId(variant)) = if let Some(VariantId::EnumVariantId(variant)) =
infer.variant_resolution_for_expr_or_pat(expr_id) 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 { } else {
let record_pat = parent().and_then(ast::RecordPat::cast).map(ast::Pat::from); 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 pat_id = self.pat_id(&pat)?;
let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id); let variant_res_for_pat = infer.variant_resolution_for_pat(pat_id);
if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat { 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. // trying to resolve foo::bar.
if let Some(use_tree) = parent().and_then(ast::UseTree::cast) { if let Some(use_tree) = parent().and_then(ast::UseTree::cast) {
if use_tree.coloncolon_token().is_some() { 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. // trying to resolve foo::bar.
if path.parent_path().is_some() { if path.parent_path().is_some() {
return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) { return match resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map) {
None if meta_path.is_some() => { None if meta_path.is_some() => path
path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| { .first_segment()
.and_then(|it| it.name_ref())
.and_then(|name_ref| {
ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text())
.map(PathResolution::ToolModule) .map(PathResolution::ToolModule)
}) })
} .map(|it| (it, None)),
res => res, // 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 { } else if let Some(meta_path) = meta_path {
// Case where we are resolving the final path segment of a path in an attribute // Case where we are resolving the final path segment of a path in an attribute
@ -781,7 +889,7 @@ impl SourceAnalyzer {
let builtin = let builtin =
BuiltinAttr::by_name(db, self.resolver.krate().into(), &name_ref.text()); BuiltinAttr::by_name(db, self.resolver.krate().into(), &name_ref.text());
if builtin.is_some() { 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() { if let Some(attr) = meta_path.parent_attr() {
@ -814,10 +922,13 @@ impl SourceAnalyzer {
{ {
if let Some(idx) = helpers.position(|(name, ..)| *name == name_ref) if let Some(idx) = helpers.position(|(name, ..)| *name == name_ref)
{ {
return Some(PathResolution::DeriveHelper(DeriveHelper { return Some((
derive: *macro_id, PathResolution::DeriveHelper(DeriveHelper {
idx: idx as u32, derive: *macro_id,
})); idx: idx as u32,
}),
None,
));
} }
} }
} }
@ -825,26 +936,79 @@ impl SourceAnalyzer {
} }
} }
return match resolve_hir_path_as_attr_macro(db, &self.resolver, &hir_path) { 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 // 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 // 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
ToolModule::by_name(db, self.resolver.krate().into(), &name_ref.text()) .first_segment()
.map(PathResolution::ToolModule) .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())) { 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) resolve_hir_path_qualifier(db, &self.resolver, &hir_path, &types_map)
.map(|it| (it, None))
} else { } else {
resolve_hir_path_( // Probably a type, no need to show substitutions for those.
let res = resolve_hir_path_(
db, db,
&self.resolver, &self.resolver,
&hir_path, &hir_path,
prefer_value_ns, prefer_value_ns,
name_hygiene(db, InFile::new(self.file_id, path.syntax())), name_hygiene(db, InFile::new(self.file_id, path.syntax())),
&types_map, &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))
} }
} }
@ -1041,26 +1205,35 @@ impl SourceAnalyzer {
func: FunctionId, func: FunctionId,
substs: Substitution, substs: Substitution,
) -> FunctionId { ) -> FunctionId {
let owner = match self.resolver.body_owner() { self.resolve_impl_method_or_trait_def_with_subst(db, func, substs).0
Some(it) => it,
None => return func,
};
let env = db.trait_environment_for_body(owner);
db.lookup_impl_method(env, 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, &self,
db: &dyn HirDatabase, db: &dyn HirDatabase,
const_id: ConstId, const_id: ConstId,
subs: Substitution, subs: Substitution,
) -> ConstId { ) -> (ConstId, Substitution) {
let owner = match self.resolver.body_owner() { let owner = match self.resolver.body_owner() {
Some(it) => it, Some(it) => it,
None => return const_id, None => return (const_id, subs),
}; };
let env = db.trait_environment_for_body(owner); 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( 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); let ctx = db.lookup_intern_syntax_context(ctx);
HygieneId::new(ctx.opaque_and_semitransparent) 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 ident = name_ref.ident_token()?;
let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
NameRefClass::Definition(def) => def, NameRefClass::Definition(def, _) => def,
NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => { NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => {
return None return None
} }

View file

@ -103,7 +103,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti
ast::Expr::PathExpr(path) => { ast::Expr::PathExpr(path) => {
let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?; let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?;
match NameRefClass::classify(&ctx.sema, &name_ref)? { match NameRefClass::classify(&ctx.sema, &name_ref)? {
NameRefClass::Definition(Definition::Local(local)) => { NameRefClass::Definition(Definition::Local(local), _) => {
let source = let source =
local.sources(ctx.db()).into_iter().map(|x| x.into_ident_pat()?.name()); local.sources(ctx.db()).into_iter().map(|x| x.into_ident_pat()?.name());
source.collect() 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 // 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()) { if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
match NameRefClass::classify(&ctx.sema, &name_ref) { match NameRefClass::classify(&ctx.sema, &name_ref) {
Some(NameRefClass::Definition(Definition::SelfType(_))) => {}, Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {},
Some(NameRefClass::Definition(def)) if def == strukt_def => {}, Some(NameRefClass::Definition(def, _)) if def == strukt_def => {},
_ => return None, _ => return None,
}; };
} }

View file

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

View file

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

View file

@ -425,7 +425,9 @@ impl Module {
}) })
} else if let Some(name_ref) = ast::NameRef::cast(x) { } else if let Some(name_ref) = ast::NameRef::cast(x) {
NameRefClass::classify(&ctx.sema, &name_ref).and_then(|nc| match nc { 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, _ => None,
}) })
} else { } 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_value = name_ref?;
let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value); let name_ref_class = NameRefClass::classify(&ctx.sema, &name_ref_value);
match name_ref_class { 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()) { if !m.visibility(ctx.sema.db).is_visible_from(ctx.sema.db, constant_module.into()) {
return None; 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_map(ast::NameRef::cast)
.filter(|name| name.ident_token().is_some()) .filter(|name| name.ident_token().is_some())
.last()?; .last()?;
if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_))) = if let Some(NameRefClass::Definition(Definition::Const(_) | Definition::Static(_), _)) =
NameRefClass::classify(sema, &name_ref) NameRefClass::classify(sema, &name_ref)
{ {
return Some(name_ref.to_string().to_lowercase()); return Some(name_ref.to_string().to_lowercase());

View file

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

View file

@ -1081,7 +1081,7 @@ impl<'a> FindUsages<'a> {
}; };
match NameRefClass::classify(self.sema, name_ref) { 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)) => if ty_eq(impl_.self_ty(self.sema.db)) =>
{ {
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); 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, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool { ) -> bool {
match NameRefClass::classify(self.sema, name_ref) { 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 FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let category = if is_name_ref_in_import(name_ref) { let category = if is_name_ref_in_import(name_ref) {
ReferenceCategory::IMPORT ReferenceCategory::IMPORT
@ -1147,7 +1147,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool { ) -> bool {
match NameRefClass::classify_lifetime(self.sema, lifetime) { 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 FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
let reference = FileReference { let reference = FileReference {
range, range,
@ -1166,7 +1166,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool { ) -> bool {
match NameRefClass::classify(self.sema, name_ref) { match NameRefClass::classify(self.sema, name_ref) {
Some(NameRefClass::Definition(def)) Some(NameRefClass::Definition(def, _))
if self.def == 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 // 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(_))) || 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 // 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 // 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() if self.assoc_item_container.is_some()
&& matches!(self.def, Definition::TypeAlias(_)) && matches!(self.def, Definition::TypeAlias(_))
&& convert_to_def_in_trait(self.sema.db, def) && convert_to_def_in_trait(self.sema.db, def)
@ -1196,7 +1196,7 @@ impl<'a> FindUsages<'a> {
}; };
sink(file_id, reference) 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) { 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 FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let reference = FileReference { let reference = FileReference {
@ -1209,7 +1209,11 @@ impl<'a> FindUsages<'a> {
false 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 FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
let field = Definition::Field(field); let field = Definition::Field(field);
@ -1240,7 +1244,7 @@ impl<'a> FindUsages<'a> {
sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool, sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
) -> bool { ) -> bool {
match NameClass::classify(self.sema, name) { match NameClass::classify(self.sema, name) {
Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) Some(NameClass::PatFieldShorthand { local_def: _, field_ref, adt_subst: _ })
if matches!( if matches!(
self.def, Definition::Field(_) if Definition::Field(field_ref) == self.def 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) .find_nodes_at_offset_with_descend(file, offset)
.filter_map(move |node| match node { .filter_map(move |node| match node {
ast::NameLike::NameRef(name_ref) => match NameRefClass::classify(sema, &name_ref)? { 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, _ => None,
}, },
ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? { ast::NameLike::Name(name) => match NameClass::classify(sema, &name)? {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ use either::Either;
use hir::{ use hir::{
db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, AssocItemContainer, CaptureKind, db::ExpandDatabase, Adt, AsAssocItem, AsExternAssocItem, AssocItemContainer, CaptureKind,
DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError, DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError,
MethodViolationCode, Name, Semantics, Trait, Type, TypeInfo, MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo,
}; };
use ide_db::{ use ide_db::{
base_db::SourceDatabase, base_db::SourceDatabase,
@ -27,7 +27,7 @@ use syntax::{algo, ast, match_ast, AstNode, AstToken, Direction, SyntaxToken, T}
use crate::{ use crate::{
doc_links::{remove_links, rewrite_links}, 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, interpret::render_const_eval_error,
HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig, HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig,
MemoryLayoutHoverRenderKind, MemoryLayoutHoverRenderKind,
@ -274,7 +274,7 @@ pub(super) fn keyword(
let markup = process_markup( let markup = process_markup(
sema.db, sema.db,
Definition::Module(doc_owner), Definition::Module(doc_owner),
&markup(Some(docs.into()), description, None, None), &markup(Some(docs.into()), description, None, None, String::new()),
config, config,
); );
Some(HoverResult { markup, actions }) Some(HoverResult { markup, actions })
@ -421,6 +421,7 @@ pub(super) fn definition(
notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)], notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
macro_arm: Option<u32>, macro_arm: Option<u32>,
hovered_definition: bool, hovered_definition: bool,
subst_types: Option<Vec<(Symbol, Type)>>,
config: &HoverConfig, config: &HoverConfig,
edition: Edition, edition: Edition,
) -> Markup { ) -> Markup {
@ -604,7 +605,38 @@ pub(super) fn definition(
desc.push_str(&value); 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( pub(super) fn literal(
@ -872,6 +904,7 @@ fn markup(
rust: String, rust: String,
extra: Option<String>, extra: Option<String>,
mod_path: Option<String>, mod_path: Option<String>,
subst_types: String,
) -> Markup { ) -> Markup {
let mut buf = String::new(); let mut buf = String::new();
@ -886,6 +919,10 @@ fn markup(
buf.push_str(&extra); buf.push_str(&extra);
} }
if !subst_types.is_empty() {
format_to!(buf, "\n___\n{subst_types}");
}
if let Some(doc) = docs { if let Some(doc) = docs {
format_to!(buf, "\n___\n\n{}", doc); 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_trait_assoc_items_count: None,
max_fields_count: Some(5), max_fields_count: Some(5),
max_enum_variants_count: Some(5), max_enum_variants_count: Some(5),
max_subst_ty_len: super::SubstTyLen::Unlimited,
}; };
fn check_hover_no_result(ra_fixture: &str) { fn check_hover_no_result(ra_fixture: &str) {
@ -5176,6 +5177,10 @@ fn main() {
--- ---
`Self` = `()`
---
false false
"#]], "#]],
); );
@ -5208,6 +5213,10 @@ fn main() {
--- ---
`Self` = `i32`
---
false 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}, highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{ hover::{
HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult, HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult,
MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, SubstTyLen,
}, },
inlay_hints::{ inlay_hints::{
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,

View file

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

View file

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

View file

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

View file

@ -76,7 +76,7 @@ pub(super) fn name_like(
Some(IdentClass::NameClass(NameClass::Definition(def))) => { Some(IdentClass::NameClass(NameClass::Definition(def))) => {
highlight_def(sema, krate, def) | HlMod::Definition highlight_def(sema, krate, def) | HlMod::Definition
} }
Some(IdentClass::NameRefClass(NameRefClass::Definition(def))) => { Some(IdentClass::NameRefClass(NameRefClass::Definition(def, _))) => {
highlight_def(sema, krate, def) highlight_def(sema, krate, def)
} }
// FIXME: Fallback for 'static and '_, as we do not resolve these yet // 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(), None => return HlTag::UnresolvedReference.into(),
}; };
let mut h = match name_class { let mut h = match name_class {
NameRefClass::Definition(def) => { NameRefClass::Definition(def, _) => {
if let Definition::Local(local) = &def { if let Definition::Local(local) = &def {
let name = local.name(db); let name = local.name(db);
let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); 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 // - Don't use abbreviations unless really necessary
// - foo_command = overrides the subcommand, foo_overrideCommand allows full overwriting, extra args only applies for foo_command // - 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 // 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 // *parts* of VS Code's `package.json` config from this. Run `cargo test` to
// re-generate that file. // re-generate that file.
@ -119,6 +127,12 @@ config_data! {
hover_documentation_keywords_enable: bool = true, hover_documentation_keywords_enable: bool = true,
/// Use markdown syntax for links on hover. /// Use markdown syntax for links on hover.
hover_links_enable: bool = true, 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. /// How to render the align information in a memory layout hover.
hover_memoryLayout_alignment: Option<MemoryLayoutHoverRenderKindDef> = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal), hover_memoryLayout_alignment: Option<MemoryLayoutHoverRenderKindDef> = Some(MemoryLayoutHoverRenderKindDef::Hexadecimal),
/// Whether to show memory layout data on hover. /// Whether to show memory layout data on hover.
@ -1532,6 +1546,11 @@ impl Config {
max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(), max_trait_assoc_items_count: self.hover_show_traitAssocItems().to_owned(),
max_fields_count: self.hover_show_fields().to_owned(), max_fields_count: self.hover_show_fields().to_owned(),
max_enum_variants_count: self.hover_show_enumVariants().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,
},
} }
} }
@ -3433,6 +3452,20 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
"Use `cargo metadata` to query sysroot metadata." "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})"), _ => panic!("missing entry for {ty}: {default} (field {field})"),
} }

View file

@ -512,6 +512,15 @@ Whether to show keyword hover popups. Only applies when
-- --
Use markdown syntax for links on hover. 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"`):: [[rust-analyzer.hover.memoryLayout.alignment]]rust-analyzer.hover.memoryLayout.alignment (default: `"hexadecimal"`)::
+ +
-- --

View file

@ -1530,6 +1530,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", "title": "hover",
"properties": { "properties": {