Render final associated functions correctly in rustdoc

Co-authored-by: Michael Goulet <michael@errs.io>
This commit is contained in:
mu001999 2026-01-28 22:44:01 +08:00
parent f0e1c8f416
commit f0a019bf90
9 changed files with 88 additions and 39 deletions

View file

@ -1222,11 +1222,11 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
}
hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => {
let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Body(body));
MethodItem(m, None)
MethodItem(m, Defaultness::from_trait_item(trait_item.defaultness))
}
hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => {
let m = clean_function(cx, sig, trait_item.generics, ParamsSrc::Idents(idents));
RequiredMethodItem(m)
RequiredMethodItem(m, Defaultness::from_trait_item(trait_item.defaultness))
}
hir::TraitItemKind::Type(bounds, Some(default)) => {
let generics = enter_impl_trait(cx, |cx| clean_generics(trait_item.generics, cx));
@ -1271,7 +1271,7 @@ pub(crate) fn clean_impl_item<'tcx>(
hir::ImplItemImplKind::Inherent { .. } => hir::Defaultness::Final,
hir::ImplItemImplKind::Trait { defaultness, .. } => defaultness,
};
MethodItem(m, Some(defaultness))
MethodItem(m, Defaultness::from_impl_item(defaultness))
}
hir::ImplItemKind::Type(hir_ty) => {
let type_ = clean_ty(hir_ty, cx);
@ -1353,18 +1353,20 @@ pub(crate) fn clean_middle_assoc_item(assoc_item: &ty::AssocItem, cx: &mut DocCo
}
}
let provided = match assoc_item.container {
ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => true,
ty::AssocContainer::Trait => assoc_item.defaultness(tcx).has_value(),
let defaultness = assoc_item.defaultness(tcx);
let (provided, defaultness) = match assoc_item.container {
ty::AssocContainer::Trait => {
(defaultness.has_value(), Defaultness::from_trait_item(defaultness))
}
ty::AssocContainer::InherentImpl | ty::AssocContainer::TraitImpl(_) => {
(true, Defaultness::from_impl_item(defaultness))
}
};
if provided {
let defaultness = match assoc_item.container {
ty::AssocContainer::TraitImpl(_) => Some(assoc_item.defaultness(tcx)),
ty::AssocContainer::InherentImpl | ty::AssocContainer::Trait => None,
};
MethodItem(item, defaultness)
} else {
RequiredMethodItem(item)
RequiredMethodItem(item, defaultness)
}
}
ty::AssocKind::Type { .. } => {

View file

@ -61,6 +61,29 @@ pub(crate) enum ItemId {
Blanket { impl_id: DefId, for_: DefId },
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum Defaultness {
Implicit,
Default,
Final,
}
impl Defaultness {
pub(crate) fn from_trait_item(defaultness: hir::Defaultness) -> Self {
match defaultness {
hir::Defaultness::Default { .. } => Self::Implicit,
hir::Defaultness::Final => Self::Final,
}
}
pub(crate) fn from_impl_item(defaultness: hir::Defaultness) -> Self {
match defaultness {
hir::Defaultness::Default { .. } => Self::Default,
hir::Defaultness::Final => Self::Implicit,
}
}
}
impl ItemId {
#[inline]
pub(crate) fn is_local(self) -> bool {
@ -703,12 +726,12 @@ impl Item {
ItemType::from(self)
}
pub(crate) fn is_default(&self) -> bool {
pub(crate) fn defaultness(&self) -> Option<Defaultness> {
match self.kind {
ItemKind::MethodItem(_, Some(defaultness)) => {
defaultness.has_value() && !defaultness.is_final()
ItemKind::MethodItem(_, defaultness) | ItemKind::RequiredMethodItem(_, defaultness) => {
Some(defaultness)
}
_ => false,
_ => None,
}
}
@ -770,8 +793,8 @@ impl Item {
}
}
ItemKind::FunctionItem(_)
| ItemKind::MethodItem(_, _)
| ItemKind::RequiredMethodItem(_) => {
| ItemKind::MethodItem(..)
| ItemKind::RequiredMethodItem(..) => {
let def_id = self.def_id().unwrap();
build_fn_header(def_id, tcx, tcx.asyncness(def_id))
}
@ -855,11 +878,11 @@ pub(crate) enum ItemKind {
TraitAliasItem(TraitAlias),
ImplItem(Box<Impl>),
/// A required method in a trait declaration meaning it's only a function signature.
RequiredMethodItem(Box<Function>),
RequiredMethodItem(Box<Function>, Defaultness),
/// A method in a trait impl or a provided method in a trait declaration.
///
/// Compared to [RequiredMethodItem], it also contains a method body.
MethodItem(Box<Function>, Option<hir::Defaultness>),
MethodItem(Box<Function>, Defaultness),
StructFieldItem(Type),
VariantItem(Variant),
/// `fn`s from an extern block
@ -917,8 +940,8 @@ impl ItemKind {
| StaticItem(_)
| ConstantItem(_)
| TraitAliasItem(_)
| RequiredMethodItem(_)
| MethodItem(_, _)
| RequiredMethodItem(..)
| MethodItem(..)
| StructFieldItem(_)
| ForeignFunctionItem(_, _)
| ForeignStaticItem(_, _)

View file

@ -82,8 +82,8 @@ pub(crate) trait DocFolder: Sized {
| StaticItem(_)
| ConstantItem(..)
| TraitAliasItem(_)
| RequiredMethodItem(_)
| MethodItem(_, _)
| RequiredMethodItem(..)
| MethodItem(..)
| StructFieldItem(_)
| ForeignFunctionItem(..)
| ForeignStaticItem(..)

View file

@ -1566,10 +1566,6 @@ pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display {
})
}
pub(crate) fn print_default_space(v: bool) -> &'static str {
if v { "default " } else { "" }
}
fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl Display {
fmt::from_fn(move |f| match generic_arg {
clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)),

View file

@ -66,7 +66,7 @@ use tracing::{debug, info};
pub(crate) use self::context::*;
pub(crate) use self::span_map::{LinkFromSrc, collect_spans_and_sources};
pub(crate) use self::write_shared::*;
use crate::clean::{self, ItemId, RenderedLink};
use crate::clean::{self, Defaultness, ItemId, RenderedLink};
use crate::display::{Joined as _, MaybeDisplay as _};
use crate::error::Error;
use crate::formats::Impl;
@ -75,8 +75,8 @@ use crate::formats::item_type::ItemType;
use crate::html::escape::Escape;
use crate::html::format::{
Ending, HrefError, HrefInfo, PrintWithSpace, full_print_fn_decl, href, print_abi_with_space,
print_constness_with_space, print_default_space, print_generic_bounds, print_generics,
print_impl, print_path, print_type, print_where_clause, visibility_print_with_space,
print_constness_with_space, print_generic_bounds, print_generics, print_impl, print_path,
print_type, print_where_clause, visibility_print_with_space,
};
use crate::html::markdown::{
HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine,
@ -1109,7 +1109,11 @@ fn assoc_method(
let header = meth.fn_header(tcx).expect("Trying to get header from a non-function item");
let name = meth.name.as_ref().unwrap();
let vis = visibility_print_with_space(meth, cx).to_string();
let defaultness = print_default_space(meth.is_default());
let defaultness = match meth.defaultness().expect("Expected assoc method to have defaultness") {
Defaultness::Implicit => "",
Defaultness::Final => "final ",
Defaultness::Default => "default ",
};
// FIXME: Once https://github.com/rust-lang/rust/issues/143874 is implemented, we can remove
// this condition.
let constness = match render_mode {
@ -1260,7 +1264,7 @@ fn render_assoc_item(
) -> impl fmt::Display {
fmt::from_fn(move |f| match &item.kind {
clean::StrippedItem(..) => Ok(()),
clean::RequiredMethodItem(m) | clean::MethodItem(m, _) => {
clean::RequiredMethodItem(m, _) | clean::MethodItem(m, _) => {
assoc_method(item, &m.generics, &m.decl, link, parent, cx, render_mode).fmt(f)
}
clean::RequiredAssocConstItem(generics, ty) => assoc_const(
@ -1585,7 +1589,7 @@ fn render_deref_methods(
fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool {
let self_type_opt = match item.kind {
clean::MethodItem(ref method, _) => method.decl.receiver_type(),
clean::RequiredMethodItem(ref method) => method.decl.receiver_type(),
clean::RequiredMethodItem(ref method, _) => method.decl.receiver_type(),
_ => None,
};
@ -1855,7 +1859,7 @@ fn render_impl(
deprecation_class = "";
}
match &item.kind {
clean::MethodItem(..) | clean::RequiredMethodItem(_) => {
clean::MethodItem(..) | clean::RequiredMethodItem(..) => {
// Only render when the method is not static or we allow static methods
if render_method_item {
let id = cx.derive_id(format!("{item_type}.{name}"));
@ -2033,7 +2037,9 @@ fn render_impl(
if !impl_.is_negative_trait_impl() {
for impl_item in &impl_.items {
match impl_item.kind {
clean::MethodItem(..) | clean::RequiredMethodItem(_) => methods.push(impl_item),
clean::MethodItem(..) | clean::RequiredMethodItem(..) => {
methods.push(impl_item)
}
clean::RequiredAssocTypeItem(..) | clean::AssocTypeItem(..) => {
assoc_types.push(impl_item)
}

View file

@ -1968,7 +1968,7 @@ pub(crate) fn get_function_type_for_search(
clean::ForeignFunctionItem(ref f, _)
| clean::FunctionItem(ref f)
| clean::MethodItem(ref f, _)
| clean::RequiredMethodItem(ref f) => {
| clean::RequiredMethodItem(ref f, _) => {
get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache)
}
clean::ConstantItem(ref c) => make_nullary_fn(&c.type_),

View file

@ -296,7 +296,7 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum
MethodItem(m, _) => {
ItemEnum::Function(from_clean_function(m, true, header.unwrap(), renderer))
}
RequiredMethodItem(m) => {
RequiredMethodItem(m, _) => {
ItemEnum::Function(from_clean_function(m, false, header.unwrap(), renderer))
}
ImplItem(i) => ItemEnum::Impl(i.into_json(renderer)),

View file

@ -35,8 +35,8 @@ pub(crate) trait DocVisitor<'a>: Sized {
| StaticItem(_)
| ConstantItem(..)
| TraitAliasItem(_)
| RequiredMethodItem(_)
| MethodItem(_, _)
| RequiredMethodItem(..)
| MethodItem(..)
| StructFieldItem(_)
| ForeignFunctionItem(..)
| ForeignStaticItem(..)

View file

@ -0,0 +1,22 @@
#![feature(final_associated_functions)]
//@ has final_trait_method/trait.Item.html
pub trait Item {
//@ has - '//*[@id="method.foo"]' 'final fn foo()'
//@ !has - '//*[@id="method.foo"]' 'default fn foo()'
final fn foo() {}
//@ has - '//*[@id="method.bar"]' 'fn bar()'
//@ !has - '//*[@id="method.bar"]' 'default fn bar()'
//@ !has - '//*[@id="method.bar"]' 'final fn bar()'
fn bar() {}
}
//@ has final_trait_method/struct.Foo.html
pub struct Foo;
impl Item for Foo {
//@ has - '//*[@id="method.bar"]' 'fn bar()'
//@ !has - '//*[@id="method.bar"]' 'final fn bar()'
//@ !has - '//*[@id="method.bar"]' 'default fn bar()'
fn bar() {}
}