Skip to content

Commit

Permalink
hir: resolve associated items in docs (incl. traits)
Browse files Browse the repository at this point in the history
  • Loading branch information
71 committed Nov 19, 2023
1 parent 1d64673 commit 4b6b0b7
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 51 deletions.
93 changes: 84 additions & 9 deletions crates/hir-ty/src/method_resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use hir_def::{
data::{adt::StructFlags, ImplData},
item_scope::ItemScope,
nameres::DefMap,
resolver::HasResolver,
AssocItemId, BlockId, ConstId, FunctionId, HasModule, ImplId, ItemContainerId, Lookup,
ModuleDefId, ModuleId, TraitId,
};
Expand Down Expand Up @@ -1004,6 +1005,20 @@ pub fn iterate_method_candidates_dyn(
}
}

pub fn iterate_trait_item_candidates(
ty: &Canonical<Ty>,
db: &dyn HirDatabase,
env: Arc<TraitEnvironment>,
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
callback: &mut dyn FnMut(AssocItemId, bool) -> ControlFlow<()>,
) {
let mut table = InferenceTable::new(db, env);
let self_ty = table.instantiate_canonical(ty.clone());

iterate_trait_item_candidates_(&self_ty, &mut table, traits_in_scope, name, None, callback);
}

fn iterate_method_candidates_with_autoref(
receiver_ty: &Canonical<Ty>,
first_adjustment: ReceiverAdjustments,
Expand Down Expand Up @@ -1147,6 +1162,26 @@ fn iterate_trait_method_candidates(
receiver_ty: Option<&Ty>,
receiver_adjustments: Option<ReceiverAdjustments>,
callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
iterate_trait_item_candidates_(
self_ty,
table,
traits_in_scope,
name,
receiver_ty,
&mut move |assoc_item_id, visible| {
callback(receiver_adjustments.clone().unwrap_or_default(), assoc_item_id, visible)
},
)
}

fn iterate_trait_item_candidates_(
self_ty: &Ty,
table: &mut InferenceTable<'_>,
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
receiver_ty: Option<&Ty>,
callback: &mut dyn FnMut(AssocItemId, bool) -> ControlFlow<()>,
) -> ControlFlow<()> {
let db = table.db;
let env = table.trait_env.clone();
Expand Down Expand Up @@ -1189,7 +1224,7 @@ fn iterate_trait_method_candidates(
}
}
known_implemented = true;
callback(receiver_adjustments.clone().unwrap_or_default(), item, visible)?;
callback(item, visible)?;
}
}
ControlFlow::Continue(())
Expand Down Expand Up @@ -1384,6 +1419,8 @@ fn is_valid_candidate(
visible_from_module: Option<ModuleId>,
) -> IsValidCandidate {
let db = table.db;
let def_db = db.upcast();

match item {
AssocItemId::FunctionId(f) => {
is_valid_fn_candidate(table, f, name, receiver_ty, self_ty, visible_from_module)
Expand All @@ -1399,23 +1436,61 @@ fn is_valid_candidate(
}
}
if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
let self_ty_matches = table.run_in_snapshot(|table| {
let expected_self_ty = TyBuilder::impl_self_ty(db, impl_id)
.fill_with_inference_vars(table)
.build();
table.unify(&expected_self_ty, self_ty)
});
if !self_ty_matches {
if !self_ty_matches(table, db, impl_id, self_ty) {
cov_mark::hit!(const_candidate_self_type_mismatch);
return IsValidCandidate::No;
}
}
IsValidCandidate::Yes
}
_ => IsValidCandidate::No,
AssocItemId::TypeAliasId(t) => {
// Q: should this branch be restricted to `iterate_trait_item_candidates()`?
//
// the code below does not seem to be called when adding tests to
// `method_resolution.rs`, so resolution of type aliases is likely performed at some
// other point. due to the marks below, however, the test which ensures that marks have
// corresponding checks will fail
let data = db.type_alias_data(t);

check_that!(receiver_ty.is_none());
check_that!(name.map_or(true, |n| data.name == *n));

if let Some(from_module) = visible_from_module {
// Q: should the resolved visibility be added to the database?
let visibility = data.visibility.resolve(def_db, &t.resolver(def_db));

if !visibility.is_visible_from(def_db, from_module) {
// cov_mark::hit!(type_alias_candidate_not_visible);
return IsValidCandidate::NotVisible;
}
}

// Q: is it correct to use the same check as `ConstId`?
if let ItemContainerId::ImplId(impl_id) = t.lookup(def_db).container {
if !self_ty_matches(table, db, impl_id, self_ty) {
// cov_mark::hit!(type_alias_candidate_self_type_mismatch);
return IsValidCandidate::No;
}
}

IsValidCandidate::Yes
}
}
}

fn self_ty_matches(
table: &mut InferenceTable<'_>,
db: &dyn HirDatabase,
impl_id: ImplId,
self_ty: &Ty,
) -> bool {
table.run_in_snapshot(|table| {
let expected_self_ty =
TyBuilder::impl_self_ty(db, impl_id).fill_with_inference_vars(table).build();
table.unify(&expected_self_ty, self_ty)
})
}

enum IsValidCandidate {
Yes,
No,
Expand Down
61 changes: 47 additions & 14 deletions crates/hir/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,15 @@ fn resolve_assoc_or_field(
}
};

// Resolve inherent items first, then trait items, then fields.
if let Some(assoc_item_def) = resolve_assoc_item(db, &ty, &name, ns) {
return Some(assoc_item_def);
}

if let Some(impl_trait_item_def) = resolve_impl_trait_item(db, resolver, &ty, &name, ns) {
return Some(impl_trait_item_def);
}

let variant_def = match ty.as_adt()? {
Adt::Struct(it) => it.into(),
Adt::Union(it) => it.into(),
Expand All @@ -223,23 +228,34 @@ fn resolve_assoc_item(
if assoc_item.name(db)? != *name {
return None;
}
as_module_def_if_namespace_matches(assoc_item, ns)
})
}

let (def, expected_ns) = match assoc_item {
AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
AssocItem::TypeAlias(it) => {
// Inherent associated types are supported in nightly:
// https://github.com/rust-lang/rust/issues/8995
(ModuleDef::TypeAlias(it), Namespace::Types)
}
};
fn resolve_impl_trait_item(
db: &dyn HirDatabase,
resolver: Resolver,
ty: &Type,
name: &Name,
ns: Option<Namespace>,
) -> Option<DocLinkDef> {
let traits_in_scope = resolver.traits_in_scope(db.upcast());

if ns.unwrap_or(expected_ns) != expected_ns {
return None;
}
// If two traits in scope define the same item, Rustdoc links to no specific trait (for
// instance, given two methods `a`, Rustdoc simply links to `method.a` with no
// disambiguation) so we just pick the first one we find as well.
ty.iterate_trait_item_candidates(
db,
ty.krate(db),
&resolver,
&traits_in_scope,
Some(name),
move |assoc_item| {
debug_assert_eq!(assoc_item.name(db).as_ref(), Some(name));

Some(DocLinkDef::ModuleDef(def))
})
as_module_def_if_namespace_matches(assoc_item, ns)
},
)
}

fn resolve_field(
Expand All @@ -254,6 +270,23 @@ fn resolve_field(
def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field)
}

fn as_module_def_if_namespace_matches(
assoc_item: AssocItem,
ns: Option<Namespace>,
) -> Option<DocLinkDef> {
let (def, expected_ns) = match assoc_item {
AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
AssocItem::TypeAlias(it) => {
// Inherent associated types are supported in nightly:
// https://github.com/rust-lang/rust/issues/8995
(ModuleDef::TypeAlias(it), Namespace::Types)
}
};

(ns.unwrap_or(expected_ns) == expected_ns).then(|| DocLinkDef::ModuleDef(def))
}

fn modpath_from_str(db: &dyn HirDatabase, link: &str) -> Option<ModPath> {
// FIXME: this is not how we should get a mod path here.
let try_get_modpath = |link: &str| {
Expand Down
57 changes: 57 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4321,6 +4321,63 @@ impl Type {
);
}

// Q: this method takes `krate + resolver` instead of `scope` because the
// caller doesn't have a scope available. is there a way to make this more
// consistent with other `iterate_` methods?
pub fn iterate_trait_item_candidates<T>(
&self,
db: &dyn HirDatabase,
krate: Crate,
resolver: &Resolver,
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
mut callback: impl FnMut(AssocItem) -> Option<T>,
) -> Option<T> {
let _p = profile::span("iterate_trait_item_candidates");
let mut slot = None;
self.iterate_trait_item_candidates_dyn(
db,
krate,
resolver,
traits_in_scope,
name,
&mut |assoc_item_id| {
if let Some(res) = callback(assoc_item_id.into()) {
slot = Some(res);
return ControlFlow::Break(());
}
ControlFlow::Continue(())
},
);
slot
}

fn iterate_trait_item_candidates_dyn(
&self,
db: &dyn HirDatabase,
krate: Crate,
resolver: &Resolver,
traits_in_scope: &FxHashSet<TraitId>,
name: Option<&Name>,
callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
) {
let canonical = hir_ty::replace_errors_with_variables(&self.ty);

let environment = resolver.generic_def().map_or_else(
|| Arc::new(TraitEnvironment::empty(krate.id)),
|d| db.trait_environment(d),
);

method_resolution::iterate_trait_item_candidates(
&canonical,
db,
environment,
traits_in_scope,
name,
&mut |id, _| callback(id),
);
}

pub fn as_adt(&self) -> Option<Adt> {
let (adt, _subst) = self.ty.as_adt()?;
Some(adt.into())
Expand Down
21 changes: 0 additions & 21 deletions crates/ide-completion/src/completions/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,9 @@ pub(crate) fn complete_expr_path(
ctx.iterate_path_candidates(ty, |item| {
add_assoc_item(acc, item);
});

// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
if let hir::AssocItem::TypeAlias(ty) = item {
acc.add_type_alias(ctx, ty)
}
None::<()>
});
}
Qualified::With { resolution: None, .. } => {}
Qualified::With { resolution: Some(resolution), .. } => {
// Add associated types on type parameters and `Self`.
ctx.scope.assoc_type_shorthand_candidates(resolution, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, Some(ctx.module));
Expand Down Expand Up @@ -124,14 +111,6 @@ pub(crate) fn complete_expr_path(
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});

// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
if let hir::AssocItem::TypeAlias(ty) = item {
acc.add_type_alias(ctx, ty)
}
None::<()>
});
}
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
Expand Down
1 change: 0 additions & 1 deletion crates/ide-completion/src/completions/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ pub(crate) fn complete_pattern_path(
}

ctx.iterate_path_candidates(&ty, |item| match item {
AssocItem::TypeAlias(ta) => acc.add_type_alias(ctx, ta),
AssocItem::Const(c) => acc.add_const(ctx, c),
_ => {}
});
Expand Down
3 changes: 1 addition & 2 deletions crates/ide-completion/src/completions/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ pub(crate) fn complete_type_path(
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArg { .. }) => {
acc.add_const(ctx, ct)
}
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) | hir::AssocItem::TypeAlias(_) => (),
};

match qualified {
Expand Down
10 changes: 6 additions & 4 deletions crates/ide/src/doc_links/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,13 +480,15 @@ fn doc_links_trait_impl_items() {
r#"
trait Trait {
type Type;
// ^^^^ Struct::Type
const CONST: usize;
// ^^^^^ Struct::CONST
fn function();
// ^^^^^^^^ Struct::function
}
// /// [`Struct::Type`]
// /// [`Struct::CONST`]
// /// [`Struct::function`]
/// FIXME #9694
/// [`Struct::Type`]
/// [`Struct::CONST`]
/// [`Struct::function`]
struct Struct$0;
impl Trait for Struct {
Expand Down

0 comments on commit 4b6b0b7

Please sign in to comment.