rustdoc: Correctly resolve variant and struct fields on alias

This commit is contained in:
Noah Lev 2026-01-02 00:57:49 -08:00
parent 2c98517fae
commit 2b618ed6da
3 changed files with 113 additions and 36 deletions

View file

@ -462,7 +462,7 @@ fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option<DefId>)) -> Res {
}
/// Given a primitive type, try to resolve an associated item.
fn resolve_primitive_associated_item<'tcx>(
fn resolve_primitive_inherent_assoc_item<'tcx>(
tcx: TyCtxt<'tcx>,
prim_ty: PrimitiveType,
ns: Namespace,
@ -597,33 +597,30 @@ fn resolve_associated_item<'tcx>(
let item_ident = Ident::with_dummy_span(item_name);
match root_res {
Res::Primitive(prim) => {
let items = resolve_primitive_associated_item(tcx, prim, ns, item_ident);
if !items.is_empty() {
items
// Inherent associated items take precedence over items that come from trait impls.
} else {
primitive_type_to_ty(tcx, prim)
.map(|ty| {
resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx)
.iter()
.map(|item| (root_res, item.def_id))
.collect::<Vec<_>>()
})
.unwrap_or_default()
}
}
Res::Def(DefKind::TyAlias, did) => {
Res::Def(DefKind::TyAlias, alias_did) => {
// Resolve the link on the type the alias points to.
// FIXME: if the associated item is defined directly on the type alias,
// it will show up on its documentation page, we should link there instead.
let Some(res) = ty_to_res(tcx, tcx.type_of(did).instantiate_identity()) else {
return Vec::new();
let Some(aliased_res) = ty_to_res(tcx, tcx.type_of(alias_did).instantiate_identity())
else {
return vec![];
};
resolve_associated_item(tcx, res, item_name, ns, disambiguator, module_id)
let aliased_items =
resolve_associated_item(tcx, aliased_res, item_name, ns, disambiguator, module_id);
aliased_items
.into_iter()
.map(|(res, assoc_did)| {
if is_assoc_item_on_alias_page(tcx, assoc_did) {
(root_res, assoc_did)
} else {
(res, assoc_did)
}
})
.collect()
}
Res::Primitive(prim) => resolve_assoc_on_primitive(tcx, prim, ns, item_ident, module_id),
Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, did) => {
resolve_assoc_on_adt(tcx, did, item_name, ns, disambiguator, module_id)
resolve_assoc_on_adt(tcx, did, item_ident, ns, disambiguator, module_id)
}
Res::Def(DefKind::ForeignTy, did) => {
resolve_assoc_on_simple_type(tcx, did, item_ident, ns, module_id)
@ -640,23 +637,56 @@ fn resolve_associated_item<'tcx>(
}
}
// FIXME: make this fully complete by also including ALL inherent impls
// and trait impls BUT ONLY if on alias directly
fn is_assoc_item_on_alias_page<'tcx>(tcx: TyCtxt<'tcx>, assoc_did: DefId) -> bool {
match tcx.def_kind(assoc_did) {
// Variants and fields always have docs on the alias page.
DefKind::Variant | DefKind::Field => true,
_ => false,
}
}
fn resolve_assoc_on_primitive<'tcx>(
tcx: TyCtxt<'tcx>,
prim: PrimitiveType,
ns: Namespace,
item_ident: Ident,
module_id: DefId,
) -> Vec<(Res, DefId)> {
let root_res = Res::Primitive(prim);
let items = resolve_primitive_inherent_assoc_item(tcx, prim, ns, item_ident);
if !items.is_empty() {
items
// Inherent associated items take precedence over items that come from trait impls.
} else {
primitive_type_to_ty(tcx, prim)
.map(|ty| {
resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx)
.iter()
.map(|item| (root_res, item.def_id))
.collect::<Vec<_>>()
})
.unwrap_or_default()
}
}
fn resolve_assoc_on_adt<'tcx>(
tcx: TyCtxt<'tcx>,
adt_def_id: DefId,
item_name: Symbol,
item_ident: Ident,
ns: Namespace,
disambiguator: Option<Disambiguator>,
module_id: DefId,
) -> Vec<(Res, DefId)> {
debug!("looking for associated item named {item_name} for item {adt_def_id:?}");
debug!("looking for associated item named {item_ident} for item {adt_def_id:?}");
let root_res = Res::from_def_id(tcx, adt_def_id);
let adt_ty = tcx.type_of(adt_def_id).instantiate_identity();
let adt_def = adt_ty.ty_adt_def().expect("must be ADT");
let item_ident = Ident::with_dummy_span(item_name);
// Checks if item_name is a variant of the `SomeItem` enum
if ns == TypeNS && adt_def.is_enum() {
for variant in adt_def.variants() {
if variant.name == item_name {
if variant.name == item_ident.name {
return vec![(root_res, variant.def_id)];
}
}
@ -665,7 +695,7 @@ fn resolve_assoc_on_adt<'tcx>(
if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator
&& (adt_def.is_struct() || adt_def.is_union())
{
return resolve_structfield(adt_def, item_name)
return resolve_structfield(adt_def, item_ident.name)
.into_iter()
.map(|did| (root_res, did))
.collect();
@ -677,7 +707,7 @@ fn resolve_assoc_on_adt<'tcx>(
}
if ns == Namespace::ValueNS && (adt_def.is_struct() || adt_def.is_union()) {
return resolve_structfield(adt_def, item_name)
return resolve_structfield(adt_def, item_ident.name)
.into_iter()
.map(|did| (root_res, did))
.collect();

View file

@ -3,12 +3,35 @@
//! [`TheStructAlias::the_field`]
//! [`TheEnumAlias::TheVariant`]
//! [`TheEnumAlias::TheVariant::the_field`]
//! [`TheUnionAlias::f1`]
//!
//! [`TheStruct::trait_`]
//! [`TheStructAlias::trait_`]
//! [`TheEnum::trait_`]
//! [`TheEnumAlias::trait_`]
//!
//! [`TheStruct::inherent`]
//! [`TheStructAlias::inherent`]
//! [`TheEnum::inherent`]
//! [`TheEnumAlias::inherent`]
// FIXME: this should resolve to the alias's version
//@ has foo/index.html '//a[@href="struct.TheStruct.html#structfield.the_field"]' 'TheStructAlias::the_field'
// FIXME: this should resolve to the alias's version
//@ has foo/index.html '//a[@href="enum.TheEnum.html#variant.TheVariant"]' 'TheEnumAlias::TheVariant'
//@ has foo/index.html '//a[@href="type.TheStructAlias.html#structfield.the_field"]' 'TheStructAlias::the_field'
//@ has foo/index.html '//a[@href="type.TheEnumAlias.html#variant.TheVariant"]' 'TheEnumAlias::TheVariant'
//@ has foo/index.html '//a[@href="type.TheEnumAlias.html#variant.TheVariant.field.the_field"]' 'TheEnumAlias::TheVariant::the_field'
//@ has foo/index.html '//a[@href="type.TheUnionAlias.html#structfield.f1"]' 'TheUnionAlias::f1'
//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.trait_"]' 'TheStruct::trait_'
//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.trait_"]' 'TheStructAlias::trait_'
//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.trait_"]' 'TheEnum::trait_'
// FIXME: this one should resolve to alias since it's impl Trait for TheEnumAlias
//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.trait_"]' 'TheEnumAlias::trait_'
//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.inherent"]' 'TheStruct::inherent'
// FIXME: this one should resolve to alias
//@ has foo/index.html '//a[@href="struct.TheStruct.html#method.inherent"]' 'TheStructAlias::inherent'
//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.inherent"]' 'TheEnum::inherent'
// FIXME: this one should resolve to alias
//@ has foo/index.html '//a[@href="enum.TheEnum.html#method.inherent"]' 'TheEnumAlias::inherent'
pub struct TheStruct {
pub the_field: i32,
@ -22,4 +45,27 @@ pub enum TheEnum {
pub type TheEnumAlias = TheEnum;
pub trait Trait {
fn trait_() {}
}
impl Trait for TheStruct {}
impl Trait for TheEnumAlias {}
impl TheStruct {
pub fn inherent() {}
}
impl TheEnumAlias {
pub fn inherent() {}
}
pub union TheUnion {
pub f1: usize,
pub f2: isize,
}
pub type TheUnionAlias = TheUnion;
fn main() {}

View file

@ -13,7 +13,9 @@ pub fn foo() {}
//@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from struct'
//@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.clone"]' 'MyStruct::clone'
//@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#associatedtype.Input"]' 'MyStruct::Input'
pub struct MyStruct { foo: () }
pub struct MyStruct {
foo: (),
}
impl Clone for MyStruct {
fn clone(&self) -> Self {
@ -31,8 +33,7 @@ impl T for MyStruct {
/// [link from method][MyStruct::method] on method
//@ has 'associated_items/struct.MyStruct.html' '//a[@href="struct.MyStruct.html#method.method"]' 'link from method'
fn method(i: usize) {
}
fn method(i: usize) {}
}
/// Ambiguity between which trait to use
@ -57,7 +58,7 @@ impl T2 for S {
fn ambiguous_method() {}
}
//@ has associated_items/enum.MyEnum.html '//a/@href' 'enum.MyEnum.html#variant.MyVariant'
//@ has associated_items/enum.MyEnum.html '//a/@href' 'type.MyEnumAlias.html#variant.MyVariant'
/// Link to [MyEnumAlias::MyVariant]
pub enum MyEnum {
MyVariant,