From 98d3026c416a2a588bf271414a4695436cd7ef25 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 20:19:21 -0800 Subject: [PATCH 1/6] Resolve intra-doc links to variant fields behind type aliases Previously, these failed to resolve, despite them working for struct fields or enum variants that were behind aliases. However, there is now an inconsistency where enum variant fields behind an alias resolve to the entry on the alias's page, while enum variants and struct fields resolve to the page of the backing ADT. --- .../passes/collect_intra_doc_links.rs | 35 ++++++++++--------- .../intra-doc/adt-through-alias.rs | 25 +++++++++++++ 2 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 tests/rustdoc-html/intra-doc/adt-through-alias.rs diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 3abf0fee3959a..44affba9a8c07 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -308,24 +308,27 @@ impl<'tcx> LinkCollector<'_, 'tcx> { let ty_res = self.resolve_path(path, TypeNS, item_id, module_id).ok_or_else(no_res)?; match ty_res { - Res::Def(DefKind::Enum, did) => match tcx.type_of(did).instantiate_identity().kind() { - ty::Adt(def, _) if def.is_enum() => { - if let Some(variant) = def.variants().iter().find(|v| v.name == variant_name) - && let Some(field) = - variant.fields.iter().find(|f| f.name == variant_field_name) - { - Ok((ty_res, field.did)) - } else { - Err(UnresolvedPath { - item_id, - module_id, - partial_res: Some(Res::Def(DefKind::Enum, def.did())), - unresolved: variant_field_name.to_string().into(), - }) + Res::Def(DefKind::Enum | DefKind::TyAlias, did) => { + match tcx.type_of(did).instantiate_identity().kind() { + ty::Adt(def, _) if def.is_enum() => { + if let Some(variant) = + def.variants().iter().find(|v| v.name == variant_name) + && let Some(field) = + variant.fields.iter().find(|f| f.name == variant_field_name) + { + Ok((ty_res, field.did)) + } else { + Err(UnresolvedPath { + item_id, + module_id, + partial_res: Some(Res::Def(DefKind::Enum, def.did())), + unresolved: variant_field_name.to_string().into(), + }) + } } + _ => unreachable!(), } - _ => unreachable!(), - }, + } _ => Err(UnresolvedPath { item_id, module_id, diff --git a/tests/rustdoc-html/intra-doc/adt-through-alias.rs b/tests/rustdoc-html/intra-doc/adt-through-alias.rs new file mode 100644 index 0000000000000..58e0f37edbabd --- /dev/null +++ b/tests/rustdoc-html/intra-doc/adt-through-alias.rs @@ -0,0 +1,25 @@ +#![crate_name = "foo"] + +//! [`TheStructAlias::the_field`] +//! [`TheEnumAlias::TheVariant`] +//! [`TheEnumAlias::TheVariant::the_field`] + +// 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.TheEnumAlias.html#variant.TheVariant.field.the_field"]' 'TheEnumAlias::TheVariant::the_field' + +pub struct TheStruct { + pub the_field: i32, +} + +pub type TheStructAlias = TheStruct; + +pub enum TheEnum { + TheVariant { the_field: i32 }, +} + +pub type TheEnumAlias = TheEnum; + +fn main() {} From a90e0418c69e2f46000e918c404d4490cd5f2b4e Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 20:29:43 -0800 Subject: [PATCH 2/6] rustdoc: Refactor `def_id_to_res` as `ty_to_res` The old name and API were confusing. In my opinion, looking up the type at the call site is clearer. --- src/librustdoc/passes/collect_intra_doc_links.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 44affba9a8c07..512f0f418702a 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -384,7 +384,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { }; match tcx.def_kind(self_id) { - DefKind::Impl { .. } => self.def_id_to_res(self_id), + DefKind::Impl { .. } => self.ty_to_res(tcx.type_of(self_id).instantiate_identity()), DefKind::Use => None, def_kind => Some(Res::Def(def_kind, self_id)), } @@ -506,12 +506,12 @@ impl<'tcx> LinkCollector<'_, 'tcx> { } } - /// Convert a DefId to a Res, where possible. + /// Convert a Ty to a Res, where possible. /// /// This is used for resolving type aliases. - fn def_id_to_res(&self, ty_id: DefId) -> Option { + fn ty_to_res(&self, ty: Ty<'tcx>) -> Option { use PrimitiveType::*; - Some(match *self.cx.tcx.type_of(ty_id).instantiate_identity().kind() { + Some(match *ty.kind() { ty::Bool => Res::Primitive(Bool), ty::Char => Res::Primitive(Char), ty::Int(ity) => Res::Primitive(ity.into()), @@ -614,7 +614,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { // 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) = self.def_id_to_res(did) else { return Vec::new() }; + let Some(res) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { return Vec::new() }; self.resolve_associated_item(res, item_name, ns, disambiguator, module_id) } Res::Def( From 945f3f91712b2ccbfce0bde5c1d0f4ad36a69876 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 23:46:46 -0800 Subject: [PATCH 3/6] rustdoc: Use trait as root `Res` instead of assoc item All the other parts of this function return the Res for the containing page, but for some reason, this returns the associated item itself. It doesn't seem to affect the behavior of rustdoc because e.g. the href functions use the parent if the DefId is for an assoc item. But it's clearer and simpler to be consistent. --- src/librustdoc/passes/collect_intra_doc_links.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 512f0f418702a..4233f0903b232 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -614,7 +614,9 @@ impl<'tcx> LinkCollector<'_, 'tcx> { // 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) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { return Vec::new() }; + let Some(res) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { + return Vec::new(); + }; self.resolve_associated_item(res, item_name, ns, disambiguator, module_id) } Res::Def( @@ -720,10 +722,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { Ident::with_dummy_span(item_name), ns, ) - .map(|item| { - let res = Res::Def(item.as_def_kind(), item.def_id); - (res, item.def_id) - }) + .map(|item| (root_res, item.def_id)) .collect::>(), _ => Vec::new(), } From f503b894e912532299fd7669844efbdebaedcfd7 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Thu, 1 Jan 2026 23:54:43 -0800 Subject: [PATCH 4/6] rustdoc: Extract helper function for resolving assoc items on ADTs --- .../passes/collect_intra_doc_links.rs | 200 ++++++++++-------- 1 file changed, 106 insertions(+), 94 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 4233f0903b232..cb3fd72bc1963 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -622,100 +622,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { Res::Def( def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy), did, - ) => { - debug!("looking for associated item named {item_name} for item {did:?}"); - // Checks if item_name is a variant of the `SomeItem` enum - if ns == TypeNS && def_kind == DefKind::Enum { - match tcx.type_of(did).instantiate_identity().kind() { - ty::Adt(adt_def, _) => { - for variant in adt_def.variants() { - if variant.name == item_name { - return vec![(root_res, variant.def_id)]; - } - } - } - _ => unreachable!(), - } - } - - let search_for_field = || { - let (DefKind::Struct | DefKind::Union) = def_kind else { return vec![] }; - debug!("looking for fields named {item_name} for {did:?}"); - // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) - // NOTE: it's different from variant_field because it only resolves struct fields, - // not variant fields (2 path segments, not 3). - // - // We need to handle struct (and union) fields in this code because - // syntactically their paths are identical to associated item paths: - // `module::Type::field` and `module::Type::Assoc`. - // - // On the other hand, variant fields can't be mistaken for associated - // items because they look like this: `module::Type::Variant::field`. - // - // Variants themselves don't need to be handled here, even though - // they also look like associated items (`module::Type::Variant`), - // because they are real Rust syntax (unlike the intra-doc links - // field syntax) and are handled by the compiler's resolver. - let ty::Adt(def, _) = tcx.type_of(did).instantiate_identity().kind() else { - unreachable!() - }; - def.non_enum_variant() - .fields - .iter() - .filter(|field| field.name == item_name) - .map(|field| (root_res, field.did)) - .collect::>() - }; - - if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator { - return search_for_field(); - } - - // Checks if item_name belongs to `impl SomeItem` - let mut assoc_items: Vec<_> = tcx - .inherent_impls(did) - .iter() - .flat_map(|&imp| { - filter_assoc_items_by_name_and_namespace( - tcx, - imp, - Ident::with_dummy_span(item_name), - ns, - ) - }) - .map(|item| (root_res, item.def_id)) - .collect(); - - if assoc_items.is_empty() { - // Check if item_name belongs to `impl SomeTrait for SomeItem` - // FIXME(#74563): This gives precedence to `impl SomeItem`: - // Although having both would be ambiguous, use impl version for compatibility's sake. - // To handle that properly resolve() would have to support - // something like [`ambi_fn`](::ambi_fn) - assoc_items = resolve_associated_trait_item( - tcx.type_of(did).instantiate_identity(), - module_id, - item_name, - ns, - self.cx, - ) - .into_iter() - .map(|item| (root_res, item.def_id)) - .collect::>(); - } - - debug!("got associated item {assoc_items:?}"); - - if !assoc_items.is_empty() { - return assoc_items; - } - - if ns != Namespace::ValueNS { - return Vec::new(); - } - - search_for_field() - } + ) => self.resolve_assoc_on_adt(), Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace( tcx, did, @@ -727,6 +634,111 @@ impl<'tcx> LinkCollector<'_, 'tcx> { _ => Vec::new(), } } + + fn resolve_assoc_on_adt( + &mut self, + adt_def_kind: DefKind, + adt_def_id: DefId, + item_name: Symbol, + ns: Namespace, + disambiguator: Option, + module_id: DefId, + ) -> Vec<(Res, DefId)> { + let tcx = self.cx.tcx; + let root_res = Res::Def(adt_def_kind, adt_def_id); + debug!("looking for associated item named {item_name} for item {adt_def_id:?}"); + // Checks if item_name is a variant of the `SomeItem` enum + if ns == TypeNS && adt_def_kind == DefKind::Enum { + match tcx.type_of(adt_def_id).instantiate_identity().kind() { + ty::Adt(adt_def, _) => { + for variant in adt_def.variants() { + if variant.name == item_name { + return vec![(root_res, variant.def_id)]; + } + } + } + _ => unreachable!(), + } + } + + let search_for_field = || { + let (DefKind::Struct | DefKind::Union) = adt_def_kind else { return vec![] }; + debug!("looking for fields named {item_name} for {adt_def_id:?}"); + // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) + // NOTE: it's different from variant_field because it only resolves struct fields, + // not variant fields (2 path segments, not 3). + // + // We need to handle struct (and union) fields in this code because + // syntactically their paths are identical to associated item paths: + // `module::Type::field` and `module::Type::Assoc`. + // + // On the other hand, variant fields can't be mistaken for associated + // items because they look like this: `module::Type::Variant::field`. + // + // Variants themselves don't need to be handled here, even though + // they also look like associated items (`module::Type::Variant`), + // because they are real Rust syntax (unlike the intra-doc links + // field syntax) and are handled by the compiler's resolver. + let ty::Adt(def, _) = tcx.type_of(adt_def_id).instantiate_identity().kind() else { + unreachable!() + }; + def.non_enum_variant() + .fields + .iter() + .filter(|field| field.name == item_name) + .map(|field| (root_res, field.did)) + .collect::>() + }; + + if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator { + return search_for_field(); + } + + // Checks if item_name belongs to `impl SomeItem` + let mut assoc_items: Vec<_> = tcx + .inherent_impls(adt_def_id) + .iter() + .flat_map(|&imp| { + filter_assoc_items_by_name_and_namespace( + tcx, + imp, + Ident::with_dummy_span(item_name), + ns, + ) + }) + .map(|item| (root_res, item.def_id)) + .collect(); + + if assoc_items.is_empty() { + // Check if item_name belongs to `impl SomeTrait for SomeItem` + // FIXME(#74563): This gives precedence to `impl SomeItem`: + // Although having both would be ambiguous, use impl version for compatibility's sake. + // To handle that properly resolve() would have to support + // something like [`ambi_fn`](::ambi_fn) + assoc_items = resolve_associated_trait_item( + tcx.type_of(adt_def_id).instantiate_identity(), + module_id, + item_name, + ns, + self.cx, + ) + .into_iter() + .map(|item| (root_res, item.def_id)) + .collect::>(); + } + + debug!("got associated item {assoc_items:?}"); + + if !assoc_items.is_empty() { + return assoc_items; + } + + if ns != Namespace::ValueNS { + return Vec::new(); + } + + search_for_field() + } } fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { From 2c98517faea2f0dc5b96282c2677cdb4628abd34 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Fri, 2 Jan 2026 00:36:30 -0800 Subject: [PATCH 5/6] Refactor out many free functions from intra-doc link code --- .../passes/collect_intra_doc_links.rs | 564 +++++++++--------- 1 file changed, 272 insertions(+), 292 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index cb3fd72bc1963..553d8636c85ba 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -338,58 +338,6 @@ impl<'tcx> LinkCollector<'_, 'tcx> { } } - /// Given a primitive type, try to resolve an associated item. - fn resolve_primitive_associated_item( - &self, - prim_ty: PrimitiveType, - ns: Namespace, - item_name: Symbol, - ) -> Vec<(Res, DefId)> { - let tcx = self.cx.tcx; - - prim_ty - .impls(tcx) - .flat_map(|impl_| { - filter_assoc_items_by_name_and_namespace( - tcx, - impl_, - Ident::with_dummy_span(item_name), - ns, - ) - .map(|item| (Res::Primitive(prim_ty), item.def_id)) - }) - .collect::>() - } - - fn resolve_self_ty(&self, path_str: &str, ns: Namespace, item_id: DefId) -> Option { - if ns != TypeNS || path_str != "Self" { - return None; - } - - let tcx = self.cx.tcx; - let self_id = match tcx.def_kind(item_id) { - def_kind @ (DefKind::AssocFn - | DefKind::AssocConst - | DefKind::AssocTy - | DefKind::Variant - | DefKind::Field) => { - let parent_def_id = tcx.parent(item_id); - if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant { - tcx.parent(parent_def_id) - } else { - parent_def_id - } - } - _ => item_id, - }; - - match tcx.def_kind(self_id) { - DefKind::Impl { .. } => self.ty_to_res(tcx.type_of(self_id).instantiate_identity()), - DefKind::Use => None, - def_kind => Some(Res::Def(def_kind, self_id)), - } - } - /// Convenience wrapper around `doc_link_resolutions`. /// /// This also handles resolving `true` and `false` as booleans. @@ -402,7 +350,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { item_id: DefId, module_id: DefId, ) -> Option { - if let res @ Some(..) = self.resolve_self_ty(path_str, ns, item_id) { + if let res @ Some(..) = resolve_self_ty(self.cx.tcx, path_str, ns, item_id) { return res; } @@ -432,13 +380,15 @@ impl<'tcx> LinkCollector<'_, 'tcx> { /// Resolves a string as a path within a particular namespace. Returns an /// optional URL fragment in the case of variants and methods. fn resolve<'path>( - &mut self, + &self, path_str: &'path str, ns: Namespace, disambiguator: Option, item_id: DefId, module_id: DefId, ) -> Result)>, UnresolvedPath<'path>> { + let tcx = self.cx.tcx; + if let Some(res) = self.resolve_path(path_str, ns, item_id, module_id) { return Ok(match res { Res::Def( @@ -484,7 +434,7 @@ impl<'tcx> LinkCollector<'_, 'tcx> { match resolve_primitive(path_root, TypeNS) .or_else(|| self.resolve_path(path_root, TypeNS, item_id, module_id)) .map(|ty_res| { - self.resolve_associated_item(ty_res, item_name, ns, disambiguator, module_id) + resolve_associated_item(tcx, ty_res, item_name, ns, disambiguator, module_id) .into_iter() .map(|(res, def_id)| (res, Some(def_id))) .collect::>() @@ -505,244 +455,280 @@ impl<'tcx> LinkCollector<'_, 'tcx> { } } } +} - /// Convert a Ty to a Res, where possible. - /// - /// This is used for resolving type aliases. - fn ty_to_res(&self, ty: Ty<'tcx>) -> Option { - use PrimitiveType::*; - Some(match *ty.kind() { - ty::Bool => Res::Primitive(Bool), - ty::Char => Res::Primitive(Char), - ty::Int(ity) => Res::Primitive(ity.into()), - ty::Uint(uty) => Res::Primitive(uty.into()), - ty::Float(fty) => Res::Primitive(fty.into()), - ty::Str => Res::Primitive(Str), - ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit), - ty::Tuple(_) => Res::Primitive(Tuple), - ty::Pat(..) => Res::Primitive(Pat), - ty::Array(..) => Res::Primitive(Array), - ty::Slice(_) => Res::Primitive(Slice), - ty::RawPtr(_, _) => Res::Primitive(RawPointer), - ty::Ref(..) => Res::Primitive(Reference), - ty::FnDef(..) => panic!("type alias to a function definition"), - ty::FnPtr(..) => Res::Primitive(Fn), - ty::Never => Res::Primitive(Never), - ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => { - Res::from_def_id(self.cx.tcx, did) - } - ty::Alias(..) - | ty::Closure(..) - | ty::CoroutineClosure(..) - | ty::Coroutine(..) - | ty::CoroutineWitness(..) - | ty::Dynamic(..) - | ty::UnsafeBinder(_) - | ty::Param(_) - | ty::Bound(..) - | ty::Placeholder(_) - | ty::Infer(_) - | ty::Error(_) => return None, - }) - } - - /// Convert a PrimitiveType to a Ty, where possible. - /// - /// This is used for resolving trait impls for primitives - fn primitive_type_to_ty(&mut self, prim: PrimitiveType) -> Option> { - use PrimitiveType::*; - let tcx = self.cx.tcx; +fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { + assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id)) +} - // FIXME: Only simple types are supported here, see if we can support - // other types such as Tuple, Array, Slice, etc. - // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455 - Some(match prim { - Bool => tcx.types.bool, - Str => tcx.types.str_, - Char => tcx.types.char, - Never => tcx.types.never, - I8 => tcx.types.i8, - I16 => tcx.types.i16, - I32 => tcx.types.i32, - I64 => tcx.types.i64, - I128 => tcx.types.i128, - Isize => tcx.types.isize, - F16 => tcx.types.f16, - F32 => tcx.types.f32, - F64 => tcx.types.f64, - F128 => tcx.types.f128, - U8 => tcx.types.u8, - U16 => tcx.types.u16, - U32 => tcx.types.u32, - U64 => tcx.types.u64, - U128 => tcx.types.u128, - Usize => tcx.types.usize, - _ => return None, +/// Given a primitive type, try to resolve an associated item. +fn resolve_primitive_associated_item<'tcx>( + tcx: TyCtxt<'tcx>, + prim_ty: PrimitiveType, + ns: Namespace, + item_ident: Ident, +) -> Vec<(Res, DefId)> { + prim_ty + .impls(tcx) + .flat_map(|impl_| { + filter_assoc_items_by_name_and_namespace(tcx, impl_, item_ident, ns) + .map(|item| (Res::Primitive(prim_ty), item.def_id)) }) - } + .collect::>() +} - /// Resolve an associated item, returning its containing page's `Res` - /// and the fragment targeting the associated item on its page. - fn resolve_associated_item( - &mut self, - root_res: Res, - item_name: Symbol, - ns: Namespace, - disambiguator: Option, - module_id: DefId, - ) -> Vec<(Res, DefId)> { - let tcx = self.cx.tcx; +fn resolve_self_ty<'tcx>( + tcx: TyCtxt<'tcx>, + path_str: &str, + ns: Namespace, + item_id: DefId, +) -> Option { + if ns != TypeNS || path_str != "Self" { + return None; + } - match root_res { - Res::Primitive(prim) => { - let items = self.resolve_primitive_associated_item(prim, ns, item_name); - if !items.is_empty() { - items - // Inherent associated items take precedence over items that come from trait impls. - } else { - self.primitive_type_to_ty(prim) - .map(|ty| { - resolve_associated_trait_item(ty, module_id, item_name, ns, self.cx) - .iter() - .map(|item| (root_res, item.def_id)) - .collect::>() - }) - .unwrap_or_default() - } - } - Res::Def(DefKind::TyAlias, 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) = self.ty_to_res(tcx.type_of(did).instantiate_identity()) else { - return Vec::new(); - }; - self.resolve_associated_item(res, item_name, ns, disambiguator, module_id) + let self_id = match tcx.def_kind(item_id) { + def_kind @ (DefKind::AssocFn + | DefKind::AssocConst + | DefKind::AssocTy + | DefKind::Variant + | DefKind::Field) => { + let parent_def_id = tcx.parent(item_id); + if def_kind == DefKind::Field && tcx.def_kind(parent_def_id) == DefKind::Variant { + tcx.parent(parent_def_id) + } else { + parent_def_id } - Res::Def( - def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy), - did, - ) => self.resolve_assoc_on_adt(), - Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace( - tcx, - did, - Ident::with_dummy_span(item_name), - ns, - ) - .map(|item| (root_res, item.def_id)) - .collect::>(), - _ => Vec::new(), } + _ => item_id, + }; + + match tcx.def_kind(self_id) { + DefKind::Impl { .. } => ty_to_res(tcx, tcx.type_of(self_id).instantiate_identity()), + DefKind::Use => None, + def_kind => Some(Res::Def(def_kind, self_id)), } +} - fn resolve_assoc_on_adt( - &mut self, - adt_def_kind: DefKind, - adt_def_id: DefId, - item_name: Symbol, - ns: Namespace, - disambiguator: Option, - module_id: DefId, - ) -> Vec<(Res, DefId)> { - let tcx = self.cx.tcx; - let root_res = Res::Def(adt_def_kind, adt_def_id); - debug!("looking for associated item named {item_name} for item {adt_def_id:?}"); - // Checks if item_name is a variant of the `SomeItem` enum - if ns == TypeNS && adt_def_kind == DefKind::Enum { - match tcx.type_of(adt_def_id).instantiate_identity().kind() { - ty::Adt(adt_def, _) => { - for variant in adt_def.variants() { - if variant.name == item_name { - return vec![(root_res, variant.def_id)]; - } - } - } - _ => unreachable!(), - } +/// Convert a Ty to a Res, where possible. +/// +/// This is used for resolving type aliases. +fn ty_to_res<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option { + use PrimitiveType::*; + Some(match *ty.kind() { + ty::Bool => Res::Primitive(Bool), + ty::Char => Res::Primitive(Char), + ty::Int(ity) => Res::Primitive(ity.into()), + ty::Uint(uty) => Res::Primitive(uty.into()), + ty::Float(fty) => Res::Primitive(fty.into()), + ty::Str => Res::Primitive(Str), + ty::Tuple(tys) if tys.is_empty() => Res::Primitive(Unit), + ty::Tuple(_) => Res::Primitive(Tuple), + ty::Pat(..) => Res::Primitive(Pat), + ty::Array(..) => Res::Primitive(Array), + ty::Slice(_) => Res::Primitive(Slice), + ty::RawPtr(_, _) => Res::Primitive(RawPointer), + ty::Ref(..) => Res::Primitive(Reference), + ty::FnDef(..) => panic!("type alias to a function definition"), + ty::FnPtr(..) => Res::Primitive(Fn), + ty::Never => Res::Primitive(Never), + ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) | ty::Foreign(did) => { + Res::from_def_id(tcx, did) } + ty::Alias(..) + | ty::Closure(..) + | ty::CoroutineClosure(..) + | ty::Coroutine(..) + | ty::CoroutineWitness(..) + | ty::Dynamic(..) + | ty::UnsafeBinder(_) + | ty::Param(_) + | ty::Bound(..) + | ty::Placeholder(_) + | ty::Infer(_) + | ty::Error(_) => return None, + }) +} - let search_for_field = || { - let (DefKind::Struct | DefKind::Union) = adt_def_kind else { return vec![] }; - debug!("looking for fields named {item_name} for {adt_def_id:?}"); - // FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?) - // NOTE: it's different from variant_field because it only resolves struct fields, - // not variant fields (2 path segments, not 3). - // - // We need to handle struct (and union) fields in this code because - // syntactically their paths are identical to associated item paths: - // `module::Type::field` and `module::Type::Assoc`. - // - // On the other hand, variant fields can't be mistaken for associated - // items because they look like this: `module::Type::Variant::field`. - // - // Variants themselves don't need to be handled here, even though - // they also look like associated items (`module::Type::Variant`), - // because they are real Rust syntax (unlike the intra-doc links - // field syntax) and are handled by the compiler's resolver. - let ty::Adt(def, _) = tcx.type_of(adt_def_id).instantiate_identity().kind() else { - unreachable!() - }; - def.non_enum_variant() - .fields - .iter() - .filter(|field| field.name == item_name) - .map(|field| (root_res, field.did)) - .collect::>() - }; +/// Convert a PrimitiveType to a Ty, where possible. +/// +/// This is used for resolving trait impls for primitives +fn primitive_type_to_ty<'tcx>(tcx: TyCtxt<'tcx>, prim: PrimitiveType) -> Option> { + use PrimitiveType::*; + + // FIXME: Only simple types are supported here, see if we can support + // other types such as Tuple, Array, Slice, etc. + // See https://github.com/rust-lang/rust/issues/90703#issuecomment-1004263455 + Some(match prim { + Bool => tcx.types.bool, + Str => tcx.types.str_, + Char => tcx.types.char, + Never => tcx.types.never, + I8 => tcx.types.i8, + I16 => tcx.types.i16, + I32 => tcx.types.i32, + I64 => tcx.types.i64, + I128 => tcx.types.i128, + Isize => tcx.types.isize, + F16 => tcx.types.f16, + F32 => tcx.types.f32, + F64 => tcx.types.f64, + F128 => tcx.types.f128, + U8 => tcx.types.u8, + U16 => tcx.types.u16, + U32 => tcx.types.u32, + U64 => tcx.types.u64, + U128 => tcx.types.u128, + Usize => tcx.types.usize, + _ => return None, + }) +} - if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator { - return search_for_field(); +/// Resolve an associated item, returning its containing page's `Res` +/// and the fragment targeting the associated item on its page. +fn resolve_associated_item<'tcx>( + tcx: TyCtxt<'tcx>, + root_res: Res, + item_name: Symbol, + ns: Namespace, + disambiguator: Option, + module_id: DefId, +) -> Vec<(Res, DefId)> { + 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::>() + }) + .unwrap_or_default() + } + } + Res::Def(DefKind::TyAlias, 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(); + }; + resolve_associated_item(tcx, res, item_name, ns, disambiguator, module_id) + } + Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, did) => { + resolve_assoc_on_adt(tcx, did, item_name, ns, disambiguator, module_id) } + Res::Def(DefKind::ForeignTy, did) => { + resolve_assoc_on_simple_type(tcx, did, item_ident, ns, module_id) + } + Res::Def(DefKind::Trait, did) => filter_assoc_items_by_name_and_namespace( + tcx, + did, + Ident::with_dummy_span(item_name), + ns, + ) + .map(|item| (root_res, item.def_id)) + .collect::>(), + _ => Vec::new(), + } +} - // Checks if item_name belongs to `impl SomeItem` - let mut assoc_items: Vec<_> = tcx - .inherent_impls(adt_def_id) - .iter() - .flat_map(|&imp| { - filter_assoc_items_by_name_and_namespace( - tcx, - imp, - Ident::with_dummy_span(item_name), - ns, - ) - }) - .map(|item| (root_res, item.def_id)) - .collect(); +fn resolve_assoc_on_adt<'tcx>( + tcx: TyCtxt<'tcx>, + adt_def_id: DefId, + item_name: Symbol, + ns: Namespace, + disambiguator: Option, + module_id: DefId, +) -> Vec<(Res, DefId)> { + debug!("looking for associated item named {item_name} 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 { + return vec![(root_res, variant.def_id)]; + } + } + } - if assoc_items.is_empty() { - // Check if item_name belongs to `impl SomeTrait for SomeItem` - // FIXME(#74563): This gives precedence to `impl SomeItem`: - // Although having both would be ambiguous, use impl version for compatibility's sake. - // To handle that properly resolve() would have to support - // something like [`ambi_fn`](::ambi_fn) - assoc_items = resolve_associated_trait_item( - tcx.type_of(adt_def_id).instantiate_identity(), - module_id, - item_name, - ns, - self.cx, - ) + if let Some(Disambiguator::Kind(DefKind::Field)) = disambiguator + && (adt_def.is_struct() || adt_def.is_union()) + { + return resolve_structfield(adt_def, item_name) .into_iter() - .map(|item| (root_res, item.def_id)) - .collect::>(); - } + .map(|did| (root_res, did)) + .collect(); + } - debug!("got associated item {assoc_items:?}"); + let assoc_items = resolve_assoc_on_simple_type(tcx, adt_def_id, item_ident, ns, module_id); + if !assoc_items.is_empty() { + return assoc_items; + } - if !assoc_items.is_empty() { - return assoc_items; - } + if ns == Namespace::ValueNS && (adt_def.is_struct() || adt_def.is_union()) { + return resolve_structfield(adt_def, item_name) + .into_iter() + .map(|did| (root_res, did)) + .collect(); + } - if ns != Namespace::ValueNS { - return Vec::new(); - } + vec![] +} - search_for_field() +/// "Simple" i.e. an ADT, foreign type, etc. -- not a type alias, primitive type, or other trickier type. +fn resolve_assoc_on_simple_type<'tcx>( + tcx: TyCtxt<'tcx>, + ty_def_id: DefId, + item_ident: Ident, + ns: Namespace, + module_id: DefId, +) -> Vec<(Res, DefId)> { + let root_res = Res::from_def_id(tcx, ty_def_id); + // Checks if item_name belongs to `impl SomeItem` + let inherent_assoc_items: Vec<_> = tcx + .inherent_impls(ty_def_id) + .iter() + .flat_map(|&imp| filter_assoc_items_by_name_and_namespace(tcx, imp, item_ident, ns)) + .map(|item| (root_res, item.def_id)) + .collect(); + debug!("got inherent assoc items {inherent_assoc_items:?}"); + if !inherent_assoc_items.is_empty() { + return inherent_assoc_items; } + + // Check if item_name belongs to `impl SomeTrait for SomeItem` + // FIXME(#74563): This gives precedence to `impl SomeItem`: + // Although having both would be ambiguous, use impl version for compatibility's sake. + // To handle that properly resolve() would have to support + // something like [`ambi_fn`](::ambi_fn) + let ty = tcx.type_of(ty_def_id).instantiate_identity(); + let trait_assoc_items = resolve_associated_trait_item(ty, module_id, item_ident, ns, tcx) + .into_iter() + .map(|item| (root_res, item.def_id)) + .collect::>(); + debug!("got trait assoc items {trait_assoc_items:?}"); + trait_assoc_items } -fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { - assoc_item.map_or(base, |def_id| Res::from_def_id(tcx, def_id)) +fn resolve_structfield<'tcx>(adt_def: ty::AdtDef<'tcx>, item_name: Symbol) -> Option { + debug!("looking for fields named {item_name} for {adt_def:?}"); + adt_def + .non_enum_variant() + .fields + .iter() + .find(|field| field.name == item_name) + .map(|field| field.did) } /// Look to see if a resolved item has an associated item named `item_name`. @@ -750,12 +736,12 @@ fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> Res { /// Given `[std::io::Error::source]`, where `source` is unresolved, this would /// find `std::error::Error::source` and return /// `::source`. -fn resolve_associated_trait_item<'a>( - ty: Ty<'a>, +fn resolve_associated_trait_item<'tcx>( + ty: Ty<'tcx>, module: DefId, - item_name: Symbol, + item_ident: Ident, ns: Namespace, - cx: &mut DocContext<'a>, + tcx: TyCtxt<'tcx>, ) -> Vec { // FIXME: this should also consider blanket impls (`impl X for T`). Unfortunately // `get_auto_trait_and_blanket_impls` is broken because the caching behavior is wrong. In the @@ -763,22 +749,17 @@ fn resolve_associated_trait_item<'a>( // Next consider explicit impls: `impl MyTrait for MyType` // Give precedence to inherent impls. - let traits = trait_impls_for(cx, ty, module); - let tcx = cx.tcx; + let traits = trait_impls_for(tcx, ty, module); debug!("considering traits {traits:?}"); let candidates = traits .iter() .flat_map(|&(impl_, trait_)| { - filter_assoc_items_by_name_and_namespace( - tcx, - trait_, - Ident::with_dummy_span(item_name), - ns, + filter_assoc_items_by_name_and_namespace(tcx, trait_, item_ident, ns).map( + move |trait_assoc| { + trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id) + .unwrap_or(*trait_assoc) + }, ) - .map(move |trait_assoc| { - trait_assoc_to_impl_assoc_item(tcx, impl_, trait_assoc.def_id) - .unwrap_or(*trait_assoc) - }) }) .collect::>(); // FIXME(#74563): warn about ambiguity @@ -813,13 +794,12 @@ fn trait_assoc_to_impl_assoc_item<'tcx>( /// /// NOTE: this cannot be a query because more traits could be available when more crates are compiled! /// So it is not stable to serialize cross-crate. -#[instrument(level = "debug", skip(cx))] -fn trait_impls_for<'a>( - cx: &mut DocContext<'a>, - ty: Ty<'a>, +#[instrument(level = "debug", skip(tcx))] +fn trait_impls_for<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, module: DefId, ) -> FxIndexSet<(DefId, DefId)> { - let tcx = cx.tcx; let mut impls = FxIndexSet::default(); for &trait_ in tcx.doc_link_traits_in_scope(module) { @@ -1535,7 +1515,7 @@ impl LinkCollector<'_, '_> { } None => { // Try everything! - let mut candidate = |ns| { + let candidate = |ns| { self.resolve(path_str, ns, None, item_id, module_id) .map_err(ResolutionFailure::NotResolved) }; @@ -1921,7 +1901,7 @@ fn report_diagnostic( /// handled earlier. For example, if passed `Item::Crate(std)` and `path_str` /// `std::io::Error::x`, this will resolve `std::io::Error`. fn resolution_failure( - collector: &mut LinkCollector<'_, '_>, + collector: &LinkCollector<'_, '_>, diag_info: DiagnosticInfo<'_>, path_str: &str, disambiguator: Option, From 2b618ed6da5f0e55b2c00e32c767ba3dec6d7951 Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Fri, 2 Jan 2026 00:57:49 -0800 Subject: [PATCH 6/6] rustdoc: Correctly resolve variant and struct fields on alias --- .../passes/collect_intra_doc_links.rs | 86 +++++++++++++------ .../intra-doc/adt-through-alias.rs | 54 +++++++++++- .../intra-doc/associated-items.rs | 9 +- 3 files changed, 113 insertions(+), 36 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 553d8636c85ba..9e86781224d38 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -462,7 +462,7 @@ fn full_res(tcx: TyCtxt<'_>, (base, assoc_item): (Res, Option)) -> 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::>() - }) - .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::>() + }) + .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, 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(); diff --git a/tests/rustdoc-html/intra-doc/adt-through-alias.rs b/tests/rustdoc-html/intra-doc/adt-through-alias.rs index 58e0f37edbabd..6d5454bbaf200 100644 --- a/tests/rustdoc-html/intra-doc/adt-through-alias.rs +++ b/tests/rustdoc-html/intra-doc/adt-through-alias.rs @@ -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() {} diff --git a/tests/rustdoc-html/intra-doc/associated-items.rs b/tests/rustdoc-html/intra-doc/associated-items.rs index 84cfd06111df7..b3bed196aded0 100644 --- a/tests/rustdoc-html/intra-doc/associated-items.rs +++ b/tests/rustdoc-html/intra-doc/associated-items.rs @@ -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,