Handle Self::EnumVariant and Self on traits in doclinks

This commit is contained in:
Chayim Refael Friedman 2026-01-26 18:40:51 +02:00
parent 45ef76624e
commit d02e33db2d
2 changed files with 120 additions and 18 deletions

View file

@ -3,7 +3,8 @@
use cfg::CfgExpr;
use either::Either;
use hir_def::{
AssocItemId, AttrDefId, FieldId, LifetimeParamId, ModuleDefId, TypeOrConstParamId,
AssocItemId, AttrDefId, FieldId, GenericDefId, ItemContainerId, LifetimeParamId, ModuleDefId,
TraitId, TypeOrConstParamId,
attrs::{AttrFlags, Docs, IsInnerDoc},
expr_store::path::Path,
item_scope::ItemInNs,
@ -22,6 +23,7 @@ use hir_ty::{
next_solver::{DbInterner, TypingMode, infer::DbInternerInferExt},
};
use intern::Symbol;
use stdx::never;
use crate::{
Adt, AsAssocItem, AssocItem, BuiltinType, Const, ConstParam, DocLinkDef, Enum, ExternCrateDecl,
@ -357,13 +359,46 @@ fn resolve_assoc_or_field(
ns: Option<Namespace>,
) -> Option<DocLinkDef> {
let path = Path::from_known_path_with_no_generic(path);
// FIXME: This does not handle `Self` on trait definitions, which we should resolve to the
// trait itself.
let base_def = resolver.resolve_path_in_type_ns_fully(db, &path)?;
let handle_trait = |id: TraitId| {
// Doc paths in this context may only resolve to an item of this trait
// (i.e. no items of its supertraits), so we need to handle them here
// independently of others.
id.trait_items(db).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| {
let def = match *assoc_id {
AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()),
AssocItemId::ConstId(it) => ModuleDef::Const(it.into()),
AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()),
};
DocLinkDef::ModuleDef(def)
})
};
let ty = match base_def {
TypeNs::SelfType(id) => Impl::from(id).self_ty(db),
TypeNs::GenericParam(_) => {
TypeNs::GenericParam(param) => {
let generic_params = db.generic_params(param.parent());
if generic_params[param.local_id()].is_trait_self() {
// `Self::assoc` in traits should refer to the trait itself.
let parent_trait = |container| match container {
ItemContainerId::TraitId(trait_) => handle_trait(trait_),
_ => {
never!("container {container:?} should be a trait");
None
}
};
return match param.parent() {
GenericDefId::TraitId(trait_) => handle_trait(trait_),
GenericDefId::ConstId(it) => parent_trait(it.loc(db).container),
GenericDefId::FunctionId(it) => parent_trait(it.loc(db).container),
GenericDefId::TypeAliasId(it) => parent_trait(it.loc(db).container),
_ => {
never!("type param {param:?} should belong to a trait");
None
}
};
}
// Even if this generic parameter has some trait bounds, rustdoc doesn't
// resolve `name` to trait items.
return None;
@ -384,19 +419,7 @@ fn resolve_assoc_or_field(
alias.ty(db)
}
TypeNs::BuiltinType(id) => BuiltinType::from(id).ty(db),
TypeNs::TraitId(id) => {
// Doc paths in this context may only resolve to an item of this trait
// (i.e. no items of its supertraits), so we need to handle them here
// independently of others.
return id.trait_items(db).items.iter().find(|it| it.0 == name).map(|(_, assoc_id)| {
let def = match *assoc_id {
AssocItemId::FunctionId(it) => ModuleDef::Function(it.into()),
AssocItemId::ConstId(it) => ModuleDef::Const(it.into()),
AssocItemId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()),
};
DocLinkDef::ModuleDef(def)
});
}
TypeNs::TraitId(id) => return handle_trait(id),
TypeNs::ModuleId(_) => {
return None;
}
@ -414,7 +437,14 @@ fn resolve_assoc_or_field(
let variant_def = match ty.as_adt()? {
Adt::Struct(it) => it.into(),
Adt::Union(it) => it.into(),
Adt::Enum(_) => return None,
Adt::Enum(enum_) => {
// Can happen on `Self::Variant` (otherwise would be fully resolved by the resolver).
return enum_
.id
.enum_variants(db)
.variant(&name)
.map(|variant| DocLinkDef::ModuleDef(ModuleDef::Variant(variant.into())));
}
};
resolve_field(db, variant_def, name, ns)
}

View file

@ -11239,3 +11239,75 @@ impl<T> Foo<i32, u64> for T {
"#]],
);
}
#[test]
fn doc_link_enum_self_variant() {
check(
r#"
/// - [`VariantOne$0`](Self::One)
pub enum MyEnum {
One,
Two,
}
"#,
expect![[r#"
*[`VariantOne`](Self::One)*
```rust
ra_test_fixture::MyEnum
```
```rust
One = 0
```
"#]],
);
}
#[test]
fn doc_link_trait_self() {
check(
r#"
/// - [`do_something$0`](Self::do_something)
pub trait MyTrait {
fn do_something(&self);
}
"#,
expect![[r#"
*[`do_something`](Self::do_something)*
```rust
ra_test_fixture::MyTrait
```
```rust
pub trait MyTrait
pub fn do_something(&self)
```
"#]],
);
check(
r#"
pub trait MyTrait {
/// - [`do_something$0`](Self::do_something)
fn do_something(&self);
}
"#,
expect![[r#"
*[`do_something`](Self::do_something)*
```rust
ra_test_fixture::MyTrait
```
```rust
pub trait MyTrait
pub fn do_something(&self)
```
---
* [`do_something`](https://docs.rs/ra_test_fixture/*/ra_test_fixture/trait.MyTrait.html#tymethod.do_something)
"#]],
);
}