Skip to content

Commit 4b6b0b7

Browse files
committed
hir: resolve associated items in docs (incl. traits)
1 parent 1d64673 commit 4b6b0b7

File tree

7 files changed

+195
-51
lines changed

7 files changed

+195
-51
lines changed

crates/hir-ty/src/method_resolution.rs

+84-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use hir_def::{
1010
data::{adt::StructFlags, ImplData},
1111
item_scope::ItemScope,
1212
nameres::DefMap,
13+
resolver::HasResolver,
1314
AssocItemId, BlockId, ConstId, FunctionId, HasModule, ImplId, ItemContainerId, Lookup,
1415
ModuleDefId, ModuleId, TraitId,
1516
};
@@ -1004,6 +1005,20 @@ pub fn iterate_method_candidates_dyn(
10041005
}
10051006
}
10061007

1008+
pub fn iterate_trait_item_candidates(
1009+
ty: &Canonical<Ty>,
1010+
db: &dyn HirDatabase,
1011+
env: Arc<TraitEnvironment>,
1012+
traits_in_scope: &FxHashSet<TraitId>,
1013+
name: Option<&Name>,
1014+
callback: &mut dyn FnMut(AssocItemId, bool) -> ControlFlow<()>,
1015+
) {
1016+
let mut table = InferenceTable::new(db, env);
1017+
let self_ty = table.instantiate_canonical(ty.clone());
1018+
1019+
iterate_trait_item_candidates_(&self_ty, &mut table, traits_in_scope, name, None, callback);
1020+
}
1021+
10071022
fn iterate_method_candidates_with_autoref(
10081023
receiver_ty: &Canonical<Ty>,
10091024
first_adjustment: ReceiverAdjustments,
@@ -1147,6 +1162,26 @@ fn iterate_trait_method_candidates(
11471162
receiver_ty: Option<&Ty>,
11481163
receiver_adjustments: Option<ReceiverAdjustments>,
11491164
callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
1165+
) -> ControlFlow<()> {
1166+
iterate_trait_item_candidates_(
1167+
self_ty,
1168+
table,
1169+
traits_in_scope,
1170+
name,
1171+
receiver_ty,
1172+
&mut move |assoc_item_id, visible| {
1173+
callback(receiver_adjustments.clone().unwrap_or_default(), assoc_item_id, visible)
1174+
},
1175+
)
1176+
}
1177+
1178+
fn iterate_trait_item_candidates_(
1179+
self_ty: &Ty,
1180+
table: &mut InferenceTable<'_>,
1181+
traits_in_scope: &FxHashSet<TraitId>,
1182+
name: Option<&Name>,
1183+
receiver_ty: Option<&Ty>,
1184+
callback: &mut dyn FnMut(AssocItemId, bool) -> ControlFlow<()>,
11501185
) -> ControlFlow<()> {
11511186
let db = table.db;
11521187
let env = table.trait_env.clone();
@@ -1189,7 +1224,7 @@ fn iterate_trait_method_candidates(
11891224
}
11901225
}
11911226
known_implemented = true;
1192-
callback(receiver_adjustments.clone().unwrap_or_default(), item, visible)?;
1227+
callback(item, visible)?;
11931228
}
11941229
}
11951230
ControlFlow::Continue(())
@@ -1384,6 +1419,8 @@ fn is_valid_candidate(
13841419
visible_from_module: Option<ModuleId>,
13851420
) -> IsValidCandidate {
13861421
let db = table.db;
1422+
let def_db = db.upcast();
1423+
13871424
match item {
13881425
AssocItemId::FunctionId(f) => {
13891426
is_valid_fn_candidate(table, f, name, receiver_ty, self_ty, visible_from_module)
@@ -1399,23 +1436,61 @@ fn is_valid_candidate(
13991436
}
14001437
}
14011438
if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
1402-
let self_ty_matches = table.run_in_snapshot(|table| {
1403-
let expected_self_ty = TyBuilder::impl_self_ty(db, impl_id)
1404-
.fill_with_inference_vars(table)
1405-
.build();
1406-
table.unify(&expected_self_ty, self_ty)
1407-
});
1408-
if !self_ty_matches {
1439+
if !self_ty_matches(table, db, impl_id, self_ty) {
14091440
cov_mark::hit!(const_candidate_self_type_mismatch);
14101441
return IsValidCandidate::No;
14111442
}
14121443
}
14131444
IsValidCandidate::Yes
14141445
}
1415-
_ => IsValidCandidate::No,
1446+
AssocItemId::TypeAliasId(t) => {
1447+
// Q: should this branch be restricted to `iterate_trait_item_candidates()`?
1448+
//
1449+
// the code below does not seem to be called when adding tests to
1450+
// `method_resolution.rs`, so resolution of type aliases is likely performed at some
1451+
// other point. due to the marks below, however, the test which ensures that marks have
1452+
// corresponding checks will fail
1453+
let data = db.type_alias_data(t);
1454+
1455+
check_that!(receiver_ty.is_none());
1456+
check_that!(name.map_or(true, |n| data.name == *n));
1457+
1458+
if let Some(from_module) = visible_from_module {
1459+
// Q: should the resolved visibility be added to the database?
1460+
let visibility = data.visibility.resolve(def_db, &t.resolver(def_db));
1461+
1462+
if !visibility.is_visible_from(def_db, from_module) {
1463+
// cov_mark::hit!(type_alias_candidate_not_visible);
1464+
return IsValidCandidate::NotVisible;
1465+
}
1466+
}
1467+
1468+
// Q: is it correct to use the same check as `ConstId`?
1469+
if let ItemContainerId::ImplId(impl_id) = t.lookup(def_db).container {
1470+
if !self_ty_matches(table, db, impl_id, self_ty) {
1471+
// cov_mark::hit!(type_alias_candidate_self_type_mismatch);
1472+
return IsValidCandidate::No;
1473+
}
1474+
}
1475+
1476+
IsValidCandidate::Yes
1477+
}
14161478
}
14171479
}
14181480

1481+
fn self_ty_matches(
1482+
table: &mut InferenceTable<'_>,
1483+
db: &dyn HirDatabase,
1484+
impl_id: ImplId,
1485+
self_ty: &Ty,
1486+
) -> bool {
1487+
table.run_in_snapshot(|table| {
1488+
let expected_self_ty =
1489+
TyBuilder::impl_self_ty(db, impl_id).fill_with_inference_vars(table).build();
1490+
table.unify(&expected_self_ty, self_ty)
1491+
})
1492+
}
1493+
14191494
enum IsValidCandidate {
14201495
Yes,
14211496
No,

crates/hir/src/attrs.rs

+47-14
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,15 @@ fn resolve_assoc_or_field(
201201
}
202202
};
203203

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

209+
if let Some(impl_trait_item_def) = resolve_impl_trait_item(db, resolver, &ty, &name, ns) {
210+
return Some(impl_trait_item_def);
211+
}
212+
208213
let variant_def = match ty.as_adt()? {
209214
Adt::Struct(it) => it.into(),
210215
Adt::Union(it) => it.into(),
@@ -223,23 +228,34 @@ fn resolve_assoc_item(
223228
if assoc_item.name(db)? != *name {
224229
return None;
225230
}
231+
as_module_def_if_namespace_matches(assoc_item, ns)
232+
})
233+
}
226234

227-
let (def, expected_ns) = match assoc_item {
228-
AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
229-
AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
230-
AssocItem::TypeAlias(it) => {
231-
// Inherent associated types are supported in nightly:
232-
// https://github.com/rust-lang/rust/issues/8995
233-
(ModuleDef::TypeAlias(it), Namespace::Types)
234-
}
235-
};
235+
fn resolve_impl_trait_item(
236+
db: &dyn HirDatabase,
237+
resolver: Resolver,
238+
ty: &Type,
239+
name: &Name,
240+
ns: Option<Namespace>,
241+
) -> Option<DocLinkDef> {
242+
let traits_in_scope = resolver.traits_in_scope(db.upcast());
236243

237-
if ns.unwrap_or(expected_ns) != expected_ns {
238-
return None;
239-
}
244+
// If two traits in scope define the same item, Rustdoc links to no specific trait (for
245+
// instance, given two methods `a`, Rustdoc simply links to `method.a` with no
246+
// disambiguation) so we just pick the first one we find as well.
247+
ty.iterate_trait_item_candidates(
248+
db,
249+
ty.krate(db),
250+
&resolver,
251+
&traits_in_scope,
252+
Some(name),
253+
move |assoc_item| {
254+
debug_assert_eq!(assoc_item.name(db).as_ref(), Some(name));
240255

241-
Some(DocLinkDef::ModuleDef(def))
242-
})
256+
as_module_def_if_namespace_matches(assoc_item, ns)
257+
},
258+
)
243259
}
244260

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

273+
fn as_module_def_if_namespace_matches(
274+
assoc_item: AssocItem,
275+
ns: Option<Namespace>,
276+
) -> Option<DocLinkDef> {
277+
let (def, expected_ns) = match assoc_item {
278+
AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
279+
AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
280+
AssocItem::TypeAlias(it) => {
281+
// Inherent associated types are supported in nightly:
282+
// https://github.com/rust-lang/rust/issues/8995
283+
(ModuleDef::TypeAlias(it), Namespace::Types)
284+
}
285+
};
286+
287+
(ns.unwrap_or(expected_ns) == expected_ns).then(|| DocLinkDef::ModuleDef(def))
288+
}
289+
257290
fn modpath_from_str(db: &dyn HirDatabase, link: &str) -> Option<ModPath> {
258291
// FIXME: this is not how we should get a mod path here.
259292
let try_get_modpath = |link: &str| {

crates/hir/src/lib.rs

+57
Original file line numberDiff line numberDiff line change
@@ -4321,6 +4321,63 @@ impl Type {
43214321
);
43224322
}
43234323

4324+
// Q: this method takes `krate + resolver` instead of `scope` because the
4325+
// caller doesn't have a scope available. is there a way to make this more
4326+
// consistent with other `iterate_` methods?
4327+
pub fn iterate_trait_item_candidates<T>(
4328+
&self,
4329+
db: &dyn HirDatabase,
4330+
krate: Crate,
4331+
resolver: &Resolver,
4332+
traits_in_scope: &FxHashSet<TraitId>,
4333+
name: Option<&Name>,
4334+
mut callback: impl FnMut(AssocItem) -> Option<T>,
4335+
) -> Option<T> {
4336+
let _p = profile::span("iterate_trait_item_candidates");
4337+
let mut slot = None;
4338+
self.iterate_trait_item_candidates_dyn(
4339+
db,
4340+
krate,
4341+
resolver,
4342+
traits_in_scope,
4343+
name,
4344+
&mut |assoc_item_id| {
4345+
if let Some(res) = callback(assoc_item_id.into()) {
4346+
slot = Some(res);
4347+
return ControlFlow::Break(());
4348+
}
4349+
ControlFlow::Continue(())
4350+
},
4351+
);
4352+
slot
4353+
}
4354+
4355+
fn iterate_trait_item_candidates_dyn(
4356+
&self,
4357+
db: &dyn HirDatabase,
4358+
krate: Crate,
4359+
resolver: &Resolver,
4360+
traits_in_scope: &FxHashSet<TraitId>,
4361+
name: Option<&Name>,
4362+
callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
4363+
) {
4364+
let canonical = hir_ty::replace_errors_with_variables(&self.ty);
4365+
4366+
let environment = resolver.generic_def().map_or_else(
4367+
|| Arc::new(TraitEnvironment::empty(krate.id)),
4368+
|d| db.trait_environment(d),
4369+
);
4370+
4371+
method_resolution::iterate_trait_item_candidates(
4372+
&canonical,
4373+
db,
4374+
environment,
4375+
traits_in_scope,
4376+
name,
4377+
&mut |id, _| callback(id),
4378+
);
4379+
}
4380+
43244381
pub fn as_adt(&self) -> Option<Adt> {
43254382
let (adt, _subst) = self.ty.as_adt()?;
43264383
Some(adt.into())

crates/ide-completion/src/completions/expr.rs

-21
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,9 @@ pub(crate) fn complete_expr_path(
6767
ctx.iterate_path_candidates(ty, |item| {
6868
add_assoc_item(acc, item);
6969
});
70-
71-
// Iterate assoc types separately
72-
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
73-
if let hir::AssocItem::TypeAlias(ty) = item {
74-
acc.add_type_alias(ctx, ty)
75-
}
76-
None::<()>
77-
});
7870
}
7971
Qualified::With { resolution: None, .. } => {}
8072
Qualified::With { resolution: Some(resolution), .. } => {
81-
// Add associated types on type parameters and `Self`.
82-
ctx.scope.assoc_type_shorthand_candidates(resolution, |_, alias| {
83-
acc.add_type_alias(ctx, alias);
84-
None::<()>
85-
});
8673
match resolution {
8774
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
8875
let module_scope = module.scope(ctx.db, Some(ctx.module));
@@ -124,14 +111,6 @@ pub(crate) fn complete_expr_path(
124111
ctx.iterate_path_candidates(&ty, |item| {
125112
add_assoc_item(acc, item);
126113
});
127-
128-
// Iterate assoc types separately
129-
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
130-
if let hir::AssocItem::TypeAlias(ty) = item {
131-
acc.add_type_alias(ctx, ty)
132-
}
133-
None::<()>
134-
});
135114
}
136115
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
137116
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.

crates/ide-completion/src/completions/pattern.rs

-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ pub(crate) fn complete_pattern_path(
154154
}
155155

156156
ctx.iterate_path_candidates(&ty, |item| match item {
157-
AssocItem::TypeAlias(ta) => acc.add_type_alias(ctx, ta),
158157
AssocItem::Const(c) => acc.add_const(ctx, c),
159158
_ => {}
160159
});

crates/ide-completion/src/completions/type.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ pub(crate) fn complete_type_path(
4545
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArg { .. }) => {
4646
acc.add_const(ctx, ct)
4747
}
48-
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
49-
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
48+
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) | hir::AssocItem::TypeAlias(_) => (),
5049
};
5150

5251
match qualified {

crates/ide/src/doc_links/tests.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,15 @@ fn doc_links_trait_impl_items() {
480480
r#"
481481
trait Trait {
482482
type Type;
483+
// ^^^^ Struct::Type
483484
const CONST: usize;
485+
// ^^^^^ Struct::CONST
484486
fn function();
487+
// ^^^^^^^^ Struct::function
485488
}
486-
// /// [`Struct::Type`]
487-
// /// [`Struct::CONST`]
488-
// /// [`Struct::function`]
489-
/// FIXME #9694
489+
/// [`Struct::Type`]
490+
/// [`Struct::CONST`]
491+
/// [`Struct::function`]
490492
struct Struct$0;
491493
492494
impl Trait for Struct {

0 commit comments

Comments
 (0)