Skip to content

Commit c3c07c6

Browse files
committed
Auto merge of #15933 - 71:inherent-items-in-docs, r=Veykril
feat: resolve inherent and implemented associated items in docs This partially fixes #9694. Supported: - Trait methods and constants. * Due to resolution differences pointed out during the review of the PR, trait associated types are _not_ supported. - Inherent methods, constants and associated types. * Inherent associated types are a [nightly feature](rust-lang/rust#8995), and are supported with no additional work in this PR. Screenshot of VS Code running with the change: <img width="513" alt="image" src="https://github.com/rust-lang/rust-analyzer/assets/7189784/c37ed8b7-b572-4684-8e81-2a817b0027c4"> You can see that the items are resolved (excl. trait associated types) since they are semantically highlighted in the doc comment.
2 parents 3fe6ff7 + fe6f931 commit c3c07c6

File tree

3 files changed

+102
-12
lines changed

3 files changed

+102
-12
lines changed

crates/hir/src/attrs.rs

+89-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Attributes & documentation for hir types.
22
3+
use std::ops::ControlFlow;
4+
35
use base_db::FileId;
46
use hir_def::{
57
attr::AttrsWithOwner,
@@ -13,13 +15,13 @@ use hir_expand::{
1315
name::Name,
1416
span_map::{RealSpanMap, SpanMapRef},
1517
};
16-
use hir_ty::db::HirDatabase;
18+
use hir_ty::{db::HirDatabase, method_resolution};
1719
use syntax::{ast, AstNode};
1820

1921
use crate::{
2022
Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl,
21-
Field, Function, GenericParam, Impl, LifetimeParam, Macro, Module, ModuleDef, Static, Struct,
22-
Trait, TraitAlias, TypeAlias, TypeParam, Union, Variant, VariantDef,
23+
Field, Function, GenericParam, HasCrate, Impl, LifetimeParam, Macro, Module, ModuleDef, Static,
24+
Struct, Trait, TraitAlias, Type, TypeAlias, TypeParam, Union, Variant, VariantDef,
2325
};
2426

2527
pub trait HasAttrs {
@@ -205,8 +207,14 @@ fn resolve_assoc_or_field(
205207
}
206208
};
207209

208-
// FIXME: Resolve associated items here, e.g. `Option::map`. Note that associated items take
209-
// precedence over fields.
210+
// Resolve inherent items first, then trait items, then fields.
211+
if let Some(assoc_item_def) = resolve_assoc_item(db, &ty, &name, ns) {
212+
return Some(assoc_item_def);
213+
}
214+
215+
if let Some(impl_trait_item_def) = resolve_impl_trait_item(db, resolver, &ty, &name, ns) {
216+
return Some(impl_trait_item_def);
217+
}
210218

211219
let variant_def = match ty.as_adt()? {
212220
Adt::Struct(it) => it.into(),
@@ -216,6 +224,69 @@ fn resolve_assoc_or_field(
216224
resolve_field(db, variant_def, name, ns)
217225
}
218226

227+
fn resolve_assoc_item(
228+
db: &dyn HirDatabase,
229+
ty: &Type,
230+
name: &Name,
231+
ns: Option<Namespace>,
232+
) -> Option<DocLinkDef> {
233+
ty.iterate_assoc_items(db, ty.krate(db), move |assoc_item| {
234+
if assoc_item.name(db)? != *name {
235+
return None;
236+
}
237+
as_module_def_if_namespace_matches(assoc_item, ns)
238+
})
239+
}
240+
241+
fn resolve_impl_trait_item(
242+
db: &dyn HirDatabase,
243+
resolver: Resolver,
244+
ty: &Type,
245+
name: &Name,
246+
ns: Option<Namespace>,
247+
) -> Option<DocLinkDef> {
248+
let canonical = ty.canonical();
249+
let krate = ty.krate(db);
250+
let environment = resolver.generic_def().map_or_else(
251+
|| crate::TraitEnvironment::empty(krate.id).into(),
252+
|d| db.trait_environment(d),
253+
);
254+
let traits_in_scope = resolver.traits_in_scope(db.upcast());
255+
256+
let mut result = None;
257+
258+
// `ty.iterate_path_candidates()` require a scope, which is not available when resolving
259+
// attributes here. Use path resolution directly instead.
260+
//
261+
// FIXME: resolve type aliases (which are not yielded by iterate_path_candidates)
262+
method_resolution::iterate_path_candidates(
263+
&canonical,
264+
db,
265+
environment,
266+
&traits_in_scope,
267+
method_resolution::VisibleFromModule::None,
268+
Some(name),
269+
&mut |assoc_item_id| {
270+
let assoc_item: AssocItem = assoc_item_id.into();
271+
272+
debug_assert_eq!(assoc_item.name(db).as_ref(), Some(name));
273+
274+
// If two traits in scope define the same item, Rustdoc links to no specific trait (for
275+
// instance, given two methods `a`, Rustdoc simply links to `method.a` with no
276+
// disambiguation) so we just pick the first one we find as well.
277+
result = as_module_def_if_namespace_matches(assoc_item, ns);
278+
279+
if result.is_some() {
280+
ControlFlow::Break(())
281+
} else {
282+
ControlFlow::Continue(())
283+
}
284+
},
285+
);
286+
287+
result
288+
}
289+
219290
fn resolve_field(
220291
db: &dyn HirDatabase,
221292
def: VariantDef,
@@ -228,6 +299,19 @@ fn resolve_field(
228299
def.fields(db).into_iter().find(|f| f.name(db) == name).map(DocLinkDef::Field)
229300
}
230301

302+
fn as_module_def_if_namespace_matches(
303+
assoc_item: AssocItem,
304+
ns: Option<Namespace>,
305+
) -> Option<DocLinkDef> {
306+
let (def, expected_ns) = match assoc_item {
307+
AssocItem::Function(it) => (ModuleDef::Function(it), Namespace::Values),
308+
AssocItem::Const(it) => (ModuleDef::Const(it), Namespace::Values),
309+
AssocItem::TypeAlias(it) => (ModuleDef::TypeAlias(it), Namespace::Types),
310+
};
311+
312+
(ns.unwrap_or(expected_ns) == expected_ns).then(|| DocLinkDef::ModuleDef(def))
313+
}
314+
231315
fn modpath_from_str(db: &dyn HirDatabase, link: &str) -> Option<ModPath> {
232316
// FIXME: this is not how we should get a mod path here.
233317
let try_get_modpath = |link: &str| {

crates/hir/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -4121,6 +4121,10 @@ impl Type {
41214121
}
41224122
}
41234123

4124+
pub(crate) fn canonical(&self) -> Canonical<Ty> {
4125+
hir_ty::replace_errors_with_variables(&self.ty)
4126+
}
4127+
41244128
/// Returns types that this type dereferences to (including this type itself). The returned
41254129
/// iterator won't yield the same type more than once even if the deref chain contains a cycle.
41264130
pub fn autoderef(&self, db: &dyn HirDatabase) -> impl Iterator<Item = Type> + '_ {

crates/ide/src/doc_links/tests.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -462,14 +462,15 @@ mod module {}
462462
fn doc_links_inherent_impl_items() {
463463
check_doc_links(
464464
r#"
465-
// /// [`Struct::CONST`]
466-
// /// [`Struct::function`]
467-
/// FIXME #9694
465+
/// [`Struct::CONST`]
466+
/// [`Struct::function`]
468467
struct Struct$0;
469468
470469
impl Struct {
471470
const CONST: () = ();
471+
// ^^^^^ Struct::CONST
472472
fn function() {}
473+
// ^^^^^^^^ Struct::function
473474
}
474475
"#,
475476
)
@@ -482,12 +483,13 @@ fn doc_links_trait_impl_items() {
482483
trait Trait {
483484
type Type;
484485
const CONST: usize;
486+
// ^^^^^ Struct::CONST
485487
fn function();
488+
// ^^^^^^^^ Struct::function
486489
}
487-
// /// [`Struct::Type`]
488-
// /// [`Struct::CONST`]
489-
// /// [`Struct::function`]
490-
/// FIXME #9694
490+
// FIXME #9694: [`Struct::Type`]
491+
/// [`Struct::CONST`]
492+
/// [`Struct::function`]
491493
struct Struct$0;
492494
493495
impl Trait for Struct {

0 commit comments

Comments
 (0)