Skip to content

Commit af7dbfe

Browse files
committed
hir: resolve associated items in docs (incl. traits)
1 parent 6e357da commit af7dbfe

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
};
@@ -1002,6 +1003,20 @@ pub fn iterate_method_candidates_dyn(
10021003
}
10031004
}
10041005

1006+
pub fn iterate_trait_item_candidates(
1007+
ty: &Canonical<Ty>,
1008+
db: &dyn HirDatabase,
1009+
env: Arc<TraitEnvironment>,
1010+
traits_in_scope: &FxHashSet<TraitId>,
1011+
name: Option<&Name>,
1012+
callback: &mut dyn FnMut(AssocItemId, bool) -> ControlFlow<()>,
1013+
) {
1014+
let mut table = InferenceTable::new(db, env);
1015+
let self_ty = table.instantiate_canonical(ty.clone());
1016+
1017+
iterate_trait_item_candidates_(&self_ty, &mut table, traits_in_scope, name, None, callback);
1018+
}
1019+
10051020
fn iterate_method_candidates_with_autoref(
10061021
receiver_ty: &Canonical<Ty>,
10071022
first_adjustment: ReceiverAdjustments,
@@ -1145,6 +1160,26 @@ fn iterate_trait_method_candidates(
11451160
receiver_ty: Option<&Ty>,
11461161
receiver_adjustments: Option<ReceiverAdjustments>,
11471162
callback: &mut dyn FnMut(ReceiverAdjustments, AssocItemId, bool) -> ControlFlow<()>,
1163+
) -> ControlFlow<()> {
1164+
iterate_trait_item_candidates_(
1165+
self_ty,
1166+
table,
1167+
traits_in_scope,
1168+
name,
1169+
receiver_ty,
1170+
&mut move |assoc_item_id, visible| {
1171+
callback(receiver_adjustments.clone().unwrap_or_default(), assoc_item_id, visible)
1172+
},
1173+
)
1174+
}
1175+
1176+
fn iterate_trait_item_candidates_(
1177+
self_ty: &Ty,
1178+
table: &mut InferenceTable<'_>,
1179+
traits_in_scope: &FxHashSet<TraitId>,
1180+
name: Option<&Name>,
1181+
receiver_ty: Option<&Ty>,
1182+
callback: &mut dyn FnMut(AssocItemId, bool) -> ControlFlow<()>,
11481183
) -> ControlFlow<()> {
11491184
let db = table.db;
11501185
let env = table.trait_env.clone();
@@ -1187,7 +1222,7 @@ fn iterate_trait_method_candidates(
11871222
}
11881223
}
11891224
known_implemented = true;
1190-
callback(receiver_adjustments.clone().unwrap_or_default(), item, visible)?;
1225+
callback(item, visible)?;
11911226
}
11921227
}
11931228
ControlFlow::Continue(())
@@ -1382,6 +1417,8 @@ fn is_valid_candidate(
13821417
visible_from_module: Option<ModuleId>,
13831418
) -> IsValidCandidate {
13841419
let db = table.db;
1420+
let def_db = db.upcast();
1421+
13851422
match item {
13861423
AssocItemId::FunctionId(f) => {
13871424
is_valid_fn_candidate(table, f, name, receiver_ty, self_ty, visible_from_module)
@@ -1397,23 +1434,61 @@ fn is_valid_candidate(
13971434
}
13981435
}
13991436
if let ItemContainerId::ImplId(impl_id) = c.lookup(db.upcast()).container {
1400-
let self_ty_matches = table.run_in_snapshot(|table| {
1401-
let expected_self_ty = TyBuilder::impl_self_ty(db, impl_id)
1402-
.fill_with_inference_vars(table)
1403-
.build();
1404-
table.unify(&expected_self_ty, self_ty)
1405-
});
1406-
if !self_ty_matches {
1437+
if !self_ty_matches(table, db, impl_id, self_ty) {
14071438
cov_mark::hit!(const_candidate_self_type_mismatch);
14081439
return IsValidCandidate::No;
14091440
}
14101441
}
14111442
IsValidCandidate::Yes
14121443
}
1413-
_ => IsValidCandidate::No,
1444+
AssocItemId::TypeAliasId(t) => {
1445+
// Q: should this branch be restricted to `iterate_trait_item_candidates()`?
1446+
//
1447+
// the code below does not seem to be called when adding tests to
1448+
// `method_resolution.rs`, so resolution of type aliases is likely performed at some
1449+
// other point. due to the marks below, however, the test which ensures that marks have
1450+
// corresponding checks will fail
1451+
let data = db.type_alias_data(t);
1452+
1453+
check_that!(receiver_ty.is_none());
1454+
check_that!(name.map_or(true, |n| data.name == *n));
1455+
1456+
if let Some(from_module) = visible_from_module {
1457+
// Q: should the resolved visibility be added to the database?
1458+
let visibility = data.visibility.resolve(def_db, &t.resolver(def_db));
1459+
1460+
if !visibility.is_visible_from(def_db, from_module) {
1461+
// cov_mark::hit!(type_alias_candidate_not_visible);
1462+
return IsValidCandidate::NotVisible;
1463+
}
1464+
}
1465+
1466+
// Q: is it correct to use the same check as `ConstId`?
1467+
if let ItemContainerId::ImplId(impl_id) = t.lookup(def_db).container {
1468+
if !self_ty_matches(table, db, impl_id, self_ty) {
1469+
// cov_mark::hit!(type_alias_candidate_self_type_mismatch);
1470+
return IsValidCandidate::No;
1471+
}
1472+
}
1473+
1474+
IsValidCandidate::Yes
1475+
}
14141476
}
14151477
}
14161478

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

crates/hir/src/attrs.rs

+47-14
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,15 @@ fn resolve_assoc_or_field(
205205
}
206206
};
207207

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

213+
if let Some(impl_trait_item_def) = resolve_impl_trait_item(db, resolver, &ty, &name, ns) {
214+
return Some(impl_trait_item_def);
215+
}
216+
212217
let variant_def = match ty.as_adt()? {
213218
Adt::Struct(it) => it.into(),
214219
Adt::Union(it) => it.into(),
@@ -227,23 +232,34 @@ fn resolve_assoc_item(
227232
if assoc_item.name(db)? != *name {
228233
return None;
229234
}
235+
as_module_def_if_namespace_matches(assoc_item, ns)
236+
})
237+
}
230238

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

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

245-
Some(DocLinkDef::ModuleDef(def))
246-
})
260+
as_module_def_if_namespace_matches(assoc_item, ns)
261+
},
262+
)
247263
}
248264

249265
fn resolve_field(
@@ -258,6 +274,23 @@ fn resolve_field(
258274
def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field)
259275
}
260276

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

crates/hir/src/lib.rs

+57
Original file line numberDiff line numberDiff line change
@@ -4387,6 +4387,63 @@ impl Type {
43874387
);
43884388
}
43894389

4390+
// Q: this method takes `krate + resolver` instead of `scope` because the
4391+
// caller doesn't have a scope available. is there a way to make this more
4392+
// consistent with other `iterate_` methods?
4393+
pub fn iterate_trait_item_candidates<T>(
4394+
&self,
4395+
db: &dyn HirDatabase,
4396+
krate: Crate,
4397+
resolver: &Resolver,
4398+
traits_in_scope: &FxHashSet<TraitId>,
4399+
name: Option<&Name>,
4400+
mut callback: impl FnMut(AssocItem) -> Option<T>,
4401+
) -> Option<T> {
4402+
let _p = profile::span("iterate_trait_item_candidates");
4403+
let mut slot = None;
4404+
self.iterate_trait_item_candidates_dyn(
4405+
db,
4406+
krate,
4407+
resolver,
4408+
traits_in_scope,
4409+
name,
4410+
&mut |assoc_item_id| {
4411+
if let Some(res) = callback(assoc_item_id.into()) {
4412+
slot = Some(res);
4413+
return ControlFlow::Break(());
4414+
}
4415+
ControlFlow::Continue(())
4416+
},
4417+
);
4418+
slot
4419+
}
4420+
4421+
fn iterate_trait_item_candidates_dyn(
4422+
&self,
4423+
db: &dyn HirDatabase,
4424+
krate: Crate,
4425+
resolver: &Resolver,
4426+
traits_in_scope: &FxHashSet<TraitId>,
4427+
name: Option<&Name>,
4428+
callback: &mut dyn FnMut(AssocItemId) -> ControlFlow<()>,
4429+
) {
4430+
let canonical = hir_ty::replace_errors_with_variables(&self.ty);
4431+
4432+
let environment = resolver.generic_def().map_or_else(
4433+
|| Arc::new(TraitEnvironment::empty(krate.id)),
4434+
|d| db.trait_environment(d),
4435+
);
4436+
4437+
method_resolution::iterate_trait_item_candidates(
4438+
&canonical,
4439+
db,
4440+
environment,
4441+
traits_in_scope,
4442+
name,
4443+
&mut |id, _| callback(id),
4444+
);
4445+
}
4446+
43904447
pub fn as_adt(&self) -> Option<Adt> {
43914448
let (adt, _subst) = self.ty.as_adt()?;
43924449
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
@@ -482,13 +482,15 @@ fn doc_links_trait_impl_items() {
482482
r#"
483483
trait Trait {
484484
type Type;
485+
// ^^^^ Struct::Type
485486
const CONST: usize;
487+
// ^^^^^ Struct::CONST
486488
fn function();
489+
// ^^^^^^^^ Struct::function
487490
}
488-
// /// [`Struct::Type`]
489-
// /// [`Struct::CONST`]
490-
// /// [`Struct::function`]
491-
/// FIXME #9694
491+
/// [`Struct::Type`]
492+
/// [`Struct::CONST`]
493+
/// [`Struct::function`]
492494
struct Struct$0;
493495
494496
impl Trait for Struct {

0 commit comments

Comments
 (0)