From 0477367a5fbc3990db218e1b56f68cef5d25e6ab Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 1 Aug 2026 23:30:06 -0700 Subject: [PATCH 1/7] rustdoc: notable trait badge color attribute --- compiler/rustc_attr_ir/src/data_structures.rs | 37 +++++- .../rustc_attr_parsing/src/attributes/doc.rs | 124 +++++++++++++++++- .../rustc_attr_parsing/src/diagnostics.rs | 7 + compiler/rustc_middle/src/queries.rs | 8 +- compiler/rustc_middle/src/ty/util.rs | 14 +- compiler/rustc_span/src/symbol.rs | 1 + library/alloc/src/io/read.rs | 2 +- library/core/src/future/future.rs | 2 +- library/core/src/io/write.rs | 2 +- library/core/src/iter/traits/iterator.rs | 2 +- src/doc/rustdoc/src/unstable-features.md | 108 ++++++++++++++- src/librustdoc/clean/types.rs | 2 +- src/librustdoc/html/render/mod.rs | 12 +- src/librustdoc/html/render/print_item.rs | 26 ++-- src/librustdoc/html/static/css/noscript.css | 20 +-- src/librustdoc/html/static/css/rustdoc.css | 62 +++++---- src/librustdoc/html/templates/print_item.html | 18 +-- src/librustdoc/json/conversions.rs | 8 +- src/librustdoc/passes/collect_trait_impls.rs | 2 +- tests/rustdoc-gui/notable-trait-colors.goml | 54 ++++++++ tests/rustdoc-gui/src/lib2/lib.rs | 31 ++++- .../lints/doc-notable_trait-check.rs | 44 +++++++ .../lints/doc-notable_trait-check.stderr | 55 ++++++++ 23 files changed, 554 insertions(+), 87 deletions(-) create mode 100644 tests/rustdoc-gui/notable-trait-colors.goml create mode 100644 tests/rustdoc-ui/lints/doc-notable_trait-check.rs create mode 100644 tests/rustdoc-ui/lints/doc-notable_trait-check.stderr diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index d722d515582dc..4a0ccb059f3b5 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -505,7 +505,7 @@ pub struct DocAttribute { pub keyword: Option<(Symbol, Span)>, pub attribute: Option<(Symbol, Span)>, pub masked: Option, - pub notable_trait: Option, + pub notable_trait: Option<(Option<(NotableTraitColor, Span)>, Span)>, pub search_unbox: Option, // valid on crate @@ -522,6 +522,41 @@ pub struct DocAttribute { pub no_crate_inject: Option, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(StableHash, Encodable, Decodable, PrintAttribute)] +pub enum NotableTraitColor { + Grey, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + Transparent, +} + +impl Into<&'static str> for NotableTraitColor { + fn into(self) -> &'static str { + use NotableTraitColor::*; + match self { + Grey => "grey", + Red => "red", + Green => "green", + Yellow => "yellow", + Blue => "blue", + Magenta => "magenta", + Cyan => "cyan", + Transparent => "transparent", + } + } +} + +impl std::fmt::Display for NotableTraitColor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(>::into(*self), f) + } +} + impl rustc_serialize::Encodable for DocAttribute { fn encode(&self, encoder: &mut E) { let DocAttribute { diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 7aeedc1493027..e384961b0fe05 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -2,7 +2,7 @@ use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; use rustc_attr_ir::target::Target; use rustc_attr_ir::{ AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue, - DocInline, HideOrShow, + DocInline, HideOrShow, NotableTraitColor, }; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; use rustc_errors::Applicability; @@ -18,10 +18,10 @@ use crate::diagnostics::{ DocAliasStartEnd, DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow, DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues, DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues, - DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral, - DocTestTakesList, DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, - DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, - IllFormedAttributeInput, MalformedDoc, UnusedDuplicate, + DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, + DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, + DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, + InvalidNotableTraitAttr, MalformedDoc, UnusedDuplicate, }; use crate::parser::{ ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser, @@ -92,6 +92,110 @@ fn expected_string_literal( cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span); } +fn parse_notable_trait( + cx: &mut AcceptContext<'_, '_>, + path: &OwnedPathParser, + args: &ArgParser, + attr_value: &mut Option<(Option<(NotableTraitColor, Span)>, Span)>, + attr_name: Symbol, +) { + let span = path.span(); + + let notable_trait_color_attr = match args { + ArgParser::NoArgs => None, + ArgParser::List(meta_item_list_parser) => { + if meta_item_list_parser.is_empty() { + None + } else if let Some(meta_item) = meta_item_list_parser.as_single() { + Some(meta_item) + } else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + } + } + ArgParser::NameValue(_) => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + } + }; + + let notable_trait_color_and_span = + if let Some(notable_trait_color_attr) = notable_trait_color_attr { + let Some(notable_trait_color_attr) = notable_trait_color_attr.meta_item() else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + }; + if !notable_trait_color_attr.path().word_is(sym::color) { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + } + let Some(notable_trait_color) = notable_trait_color_attr.args().as_name_value() else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + }; + let Some(notable_trait_color) = notable_trait_color.value_as_str() else { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + }; + let notable_trait_color = match notable_trait_color.as_str() { + "grey" => NotableTraitColor::Grey, + "red" => NotableTraitColor::Red, + "green" => NotableTraitColor::Green, + "yellow" => NotableTraitColor::Yellow, + "blue" => NotableTraitColor::Blue, + "magenta" => NotableTraitColor::Magenta, + "cyan" => NotableTraitColor::Cyan, + "transparent" => NotableTraitColor::Transparent, + _ => { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + InvalidNotableTraitAttr, + span, + ); + return; + } + }; + Some((notable_trait_color, notable_trait_color_attr.span())) + } else { + None + }; + + if cx.shared.target != Target::Trait { + cx.emit_lint( + rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, + DocAttrTraitLevel { span, attr_name }, + span, + ); + return; + } + + *attr_value = Some((notable_trait_color_and_span, span)); +} + fn parse_keyword_and_attribute( cx: &mut AcceptContext<'_, '_>, path: &OwnedPathParser, @@ -594,8 +698,14 @@ impl DocParser { } Some(sym::notable_trait) => { gated!(doc_notable_trait); - no_args!(notable_trait) - } + parse_notable_trait( + cx, + path, + args, + &mut self.attribute.notable_trait, + sym::notable_trait, + ) + }, Some(sym::keyword) => { gated!(rustdoc_internals); parse_keyword_and_attribute( diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index e5f49690f71dc..b7b0c61e3e4cc 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -336,6 +336,13 @@ pub(crate) struct ExpectedNoArgs; )] pub(crate) struct ExpectedNameValue; +#[derive(Diagnostic)] +#[diag("expected either `doc(notable_trait)` or `doc(notable_trait=\"...\")`")] +#[warning( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" +)] +pub(crate) struct InvalidNotableTraitAttr; + #[derive(Diagnostic)] #[diag("malformed `{$attribute}` attribute")] #[help("{$options}")] diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 706d89ca52749..6eca9a777af00 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -54,7 +54,7 @@ use rustc_ast as ast; use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::tokenstream::TokenStream; use rustc_attr_ir::lang_items::{LangItem, LanguageItems}; -use rustc_attr_ir::{CanonicalSymbols, EiiDecl, EiiImpl, StrippedCfgItem}; +use rustc_attr_ir::{CanonicalSymbols, EiiDecl, EiiImpl, NotableTraitColor, StrippedCfgItem}; use rustc_crate_store::{ CrateDepKind, CrateSource, ExternCrate, ForeignModule, LinkagePreference, NativeLib, }; @@ -1530,8 +1530,10 @@ rustc_queries! { separate_provide_extern } - /// Determines whether an item is annotated with `#[doc(notable_trait)]`. - query is_doc_notable_trait(def_id: DefId) -> bool { + /// If an item is annotated with `#[doc(notable_trait)]`, + /// returns the color used to render its pill. If the crate specifies + /// no color, `Transparent` is used. + query doc_notable_trait(def_id: DefId) -> Option<&'tcx NotableTraitColor> { desc { "checking whether `{}` is `doc(notable_trait)`", tcx.def_path_str(def_id) } } diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index db983038e1af9..45c971632fd65 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -8,6 +8,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::stable_hash::{StableHash, StableHasher}; use rustc_errors::ErrorGuaranteed; use rustc_hashes::Hash128; +use rustc_hir::attrs::NotableTraitColor; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; use rustc_hir::{self as hir, find_attr}; @@ -1712,8 +1713,15 @@ fn is_doc_hidden(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { } /// Determines whether an item is annotated with `doc(notable_trait)`. -pub fn is_doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool { - find_attr!(tcx, def_id, Doc(doc) if doc.notable_trait.is_some()) +pub fn doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&'_ NotableTraitColor> { + find_attr!(tcx, def_id, Doc(doc) if doc.notable_trait.is_some() => { + let (color, _span) = doc.notable_trait.as_ref()?; + if let Some((color, _span)) = color { + color + } else { + &NotableTraitColor::Transparent + } + }) } /// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute). @@ -1743,7 +1751,7 @@ pub fn provide(providers: &mut Providers) { *providers = Providers { reveal_opaque_types_in_bounds, is_doc_hidden, - is_doc_notable_trait, + doc_notable_trait, intrinsic_raw, ..*providers } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 39d9d2b7b05ce..8f35d43201a2b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -665,6 +665,7 @@ symbols! { cold, cold_path, collapse_debuginfo, + color, column, common, compare_bytes, diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index a05dade2bdcf6..e751d3d59e617 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -81,7 +81,7 @@ use crate::vec::Vec; /// [`&str`]: prim@str /// [`std::io`]: crate::io #[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait)] +#[doc(notable_trait(color = "grey"))] #[cfg_attr(not(test), rustc_diagnostic_item = "IoRead")] #[rustc_must_implement_one_of(read_buf, read)] // Keep this order, it's important for rust-analyzer (the preferred-to-implement method should come first). pub trait Read { diff --git a/library/core/src/future/future.rs b/library/core/src/future/future.rs index fab13bb7c8535..0c101fbca8ee9 100644 --- a/library/core/src/future/future.rs +++ b/library/core/src/future/future.rs @@ -25,7 +25,7 @@ use crate::task::{Context, Poll}; /// /// [`async`]: ../../std/keyword.async.html /// [`Waker`]: crate::task::Waker -#[doc(notable_trait)] +#[doc(notable_trait(color = "blue"))] #[doc(search_unbox)] #[must_use = "futures do nothing unless you `.await` or poll them"] #[stable(feature = "futures_api", since = "1.36.0")] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index cdddd380885f4..24566c22881c1 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -47,7 +47,7 @@ use crate::io::{Error, IoSlice, Result}; /// /// [`write_all`]: Write::write_all #[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait)] +#[doc(notable_trait(color = "grey"))] #[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] pub trait Write { /// Writes a buffer into this writer, returning how many bytes were written. diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index cc4077d0ee26f..0f60ec7b58911 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -34,7 +34,7 @@ fn _assert_is_dyn_compatible(_: &dyn Iterator) {} label = "`{Self}` is not an iterator", message = "`{Self}` is not an iterator" )] -#[doc(notable_trait)] +#[doc(notable_trait(color = "blue"))] #[lang = "iterator"] #[rustc_diagnostic_item = "Iterator"] #[must_use = "iterators are lazy and do nothing unless consumed"] diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index ab317ba1048a4..6797418f58681 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -75,8 +75,114 @@ on them: `#[doc(notable_trait)]`. This means that you can apply this attribute to your own trait to include it in the "Notable traits" dialog in documentation. In addition to the "Notable traits" dialog, every type that implements a -`#[doc(notable_trait)]` trait renders a colored badge for that trait at the top +`#[doc(notable_trait)]` trait renders a badge for that trait at the top of its page, making the relationship easy to spot when browsing the type. +To set a color for the badge, write `#[doc(notable_trait(color="red"))]` or +one of the other colors in the list: + + + +| Name | Light | Dark | Ayu | +| ----------- | ----- | ---- | --- | +| grey |
Trait
|
Trait
|
Trait
+| red |
Trait
|
Trait
|
Trait
+| green |
Trait
|
Trait
|
Trait
+| yellow |
Trait
|
Trait
|
Trait
+| blue |
Trait
|
Trait
|
Trait
+| magenta |
Trait
|
Trait
|
Trait
+| cyan |
Trait
|
Trait
|
Trait
+| transparent |
Trait
|
Trait
|
Trait
The `#[doc(notable_trait)]` attribute currently requires the `#![feature(doc_notable_trait)]` feature gate. For more information, see [its chapter in the Unstable Book][unstable-notable_trait] diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 744c7eb288dfa..47b2d8d5879a1 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1349,7 +1349,7 @@ impl Trait { tcx.trait_is_auto(self.def_id) } pub(crate) fn is_notable_trait(&self, tcx: TyCtxt<'_>) -> bool { - tcx.is_doc_notable_trait(self.def_id) + tcx.doc_notable_trait(self.def_id).is_some() } pub(crate) fn safety(&self, tcx: TyCtxt<'_>) -> hir::Safety { tcx.trait_def(self.def_id).safety diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index a9ecf6d66d001..6fed5ae6228ed 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -53,7 +53,9 @@ use itertools::Either; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir as hir; -use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, RustcVersion}; +use rustc_hir::attrs::{ + AttributeKind, DeprecatedSince, Deprecation, NotableTraitColor, RustcVersion, +}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ConstStability, Mutability, StabilityLevel, StableSince}; @@ -1799,6 +1801,8 @@ pub(crate) struct NotableTraitBadge { pub full_path: String, /// Relative URL to the trait page, or `None` if it cannot be linked. pub href: Option, + /// One of the color names. + pub color: NotableTraitColor, } /// Returns all `#[doc(notable_trait)]` traits that `item` implements, to be @@ -1816,15 +1820,15 @@ pub(crate) fn notable_trait_badges(item: &clean::Item, cx: &Context<'_>) -> Vec< .filter_map(|impl_| { if let Some(trait_) = &impl_.trait_ && let trait_did = trait_.def_id() - && let Some(trait_) = cx.cache().traits.get(&trait_did) - && trait_.is_notable_trait(tcx) + && cx.cache().traits.contains_key(&trait_did) + && let Some(&color) = tcx.doc_notable_trait(trait_did) { let name = tcx.item_name(trait_did).to_string(); let (full_path, href) = match href(trait_did, cx) { Ok(info) => (join_path_syms(&info.rust_path), Some(info.url)), Err(_) => (tcx.def_path_str(trait_did), None), }; - Some((name.clone(), NotableTraitBadge { name, full_path, href })) + Some((name.clone(), NotableTraitBadge { name, full_path, href, color })) } else { None } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6f66dcf9eae83..b452c23f86223 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,8 +1,6 @@ use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::hash_map::DefaultHasher; use std::fmt::{self, Display, Write as _}; -use std::hash::{Hash, Hasher}; use std::iter; use askama::Template; @@ -10,6 +8,7 @@ use rustc_abi::VariantIdx; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_hir as hir; +use rustc_hir::attrs::NotableTraitColor; use rustc_hir::def::{CtorKind, MacroKinds}; use rustc_hir::def_id::DefId; use rustc_index::IndexVec; @@ -58,8 +57,8 @@ struct NotableTraitBadgeVars { full_path: String, /// Relative URL to the trait page, or `None` when not linkable. href: Option, - /// Index of the `.notable-trait-badge-{n}` color class. - color_index: u8, + /// Name of the `.notable-trait-badge-{color}` color class. + color: NotableTraitColor, } #[derive(Template)] @@ -126,20 +125,11 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp let notable_trait_badges: Vec = notable_trait_badges(item, cx) .into_iter() - .map(|info| { - // Stable per-trait color from a hash of the trait path so the - // same trait gets the same badge color across pages. - // This won't be stable between releases though. - let mut h = DefaultHasher::new(); - info.full_path.hash(&mut h); - const BADGE_COLORS: u8 = 6; - let color_index = (h.finish() as u8) % BADGE_COLORS; - NotableTraitBadgeVars { - name: info.name, - full_path: info.full_path, - href: info.href, - color_index, - } + .map(|info| NotableTraitBadgeVars { + name: info.name, + full_path: info.full_path, + href: info.href, + color: info.color, }) .collect(); diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index 085880b215d14..2f1c84f5b86f0 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -141,12 +141,14 @@ nav.sub { --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); --sidebar-resizer-hover: hsl(207, 90%, 66%); --sidebar-resizer-active: hsl(207, 90%, 54%); - --notable-badge-pink: oklch(0.88 0.21 0); - --notable-badge-red: oklch(0.88 0.21 40); - --notable-badge-orange: oklch(0.88 0.21 70); + --notable-badge-grey: oklch(0.88 0 0); + --notable-badge-red: oklch(0.88 0.21 20); --notable-badge-green: oklch(0.88 0.21 150); + --notable-badge-yellow: oklch(0.88 0.21 70); --notable-badge-blue: oklch(0.88 0.21 240); - --notable-badge-violet: oklch(0.88 0.21 300); + --notable-badge-magenta: oklch(0.88 0.21 320); + --notable-badge-cyan: oklch(0.88 0.21 180); + --notable-badge-transparent: white; } /* End theme: light */ @@ -258,12 +260,14 @@ nav.sub { --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); --sidebar-resizer-hover: hsl(207, 30%, 54%); --sidebar-resizer-active: hsl(207, 90%, 54%); - --notable-badge-pink: oklch(0.55 0.21 0); - --notable-badge-red: oklch(0.55 0.21 40); - --notable-badge-orange: oklch(0.55 0.21 70); + --notable-badge-grey: oklch(0.55 0 0); + --notable-badge-red: oklch(0.55 0.21 20); --notable-badge-green: oklch(0.55 0.21 150); + --notable-badge-yellow: oklch(0.55 0.21 100); --notable-badge-blue: oklch(0.55 0.21 240); - --notable-badge-violet: oklch(0.55 0.21 300); + --notable-badge-magenta: oklch(0.55 0.21 320); + --notable-badge-cyan: oklch(0.55 0.21 180); + --notable-badge-transparent: transparent; } /* End theme: dark */ } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 6e7a6daa307cd..ee6006c05de75 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1648,10 +1648,10 @@ so that we can apply CSS-filters to change the arrow color in themes */ } .notable-trait-badge-container { - padding: 0.5rem 0; + padding-top: 8px; display: flex; flex-wrap: wrap; - gap: 0.5rem; + gap: 8px; } .notable-trait-badge-container > a { @@ -1659,23 +1659,31 @@ so that we can apply CSS-filters to change the arrow color in themes */ align-items: center; width: fit-content; height: 1.5rem; - padding: 0 0.5rem; - border-radius: 0.75rem; + padding: 0 14px; + border-radius: var(--code-block-border-radius); font-size: 1rem; font-weight: normal; color: var(--main-color); + border: solid 1px transparent; } .notable-trait-badge-container > a:hover { text-decoration: none; -} - -.notable-trait-badge-container > .badge-0 { background: var(--notable-badge-pink); } -.notable-trait-badge-container > .badge-1 { background: var(--notable-badge-red); } -.notable-trait-badge-container > .badge-2 { background: var(--notable-badge-orange); } -.notable-trait-badge-container > .badge-3 { background: var(--notable-badge-green); } -.notable-trait-badge-container > .badge-4 { background: var(--notable-badge-blue); } -.notable-trait-badge-container > .badge-5 { background: var(--notable-badge-violet); } + border: solid 1px var(--settings-button-border-focus); +} + +/* this list of colors is lifted from the ANSI escape codes list: + https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit + This way, CLI-based tooling can adapt them without having to do + complex platform support dances. */ +.notable-trait-badge-grey { background: var(--notable-badge-grey); } +.notable-trait-badge-red { background: var(--notable-badge-red); } +.notable-trait-badge-green { background: var(--notable-badge-green); } +.notable-trait-badge-yellow { background: var(--notable-badge-yellow); } +.notable-trait-badge-blue { background: var(--notable-badge-blue); } +.notable-trait-badge-magenta { background: var(--notable-badge-magenta); } +.notable-trait-badge-cyan { background: var(--notable-badge-cyan); } +.notable-trait-badge-transparent { background: var(--notable-badge-transparent); } .rightside { padding-left: 12px; @@ -3310,12 +3318,14 @@ by default. --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); --sidebar-resizer-hover: hsl(207, 90%, 66%); --sidebar-resizer-active: hsl(207, 90%, 54%); - --notable-badge-pink: oklch(0.88 0.21 0); - --notable-badge-red: oklch(0.88 0.21 40); - --notable-badge-orange: oklch(0.88 0.21 70); + --notable-badge-grey: oklch(0.88 0 0); + --notable-badge-red: oklch(0.88 0.21 20); --notable-badge-green: oklch(0.88 0.21 150); + --notable-badge-yellow: oklch(0.88 0.21 70); --notable-badge-blue: oklch(0.88 0.21 240); - --notable-badge-violet: oklch(0.88 0.21 300); + --notable-badge-magenta: oklch(0.88 0.21 320); + --notable-badge-cyan: oklch(0.88 0.21 180); + --notable-badge-transparent: white; } /* End theme: light */ @@ -3426,12 +3436,14 @@ by default. --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); --sidebar-resizer-hover: hsl(207, 30%, 54%); --sidebar-resizer-active: hsl(207, 90%, 54%); - --notable-badge-pink: oklch(0.55 0.21 0); - --notable-badge-red: oklch(0.55 0.21 40); - --notable-badge-orange: oklch(0.55 0.21 70); + --notable-badge-grey: oklch(0.55 0 0); + --notable-badge-red: oklch(0.55 0.21 20); --notable-badge-green: oklch(0.55 0.21 150); + --notable-badge-yellow: oklch(0.55 0.21 100); --notable-badge-blue: oklch(0.55 0.21 240); - --notable-badge-violet: oklch(0.55 0.21 300); + --notable-badge-magenta: oklch(0.55 0.21 320); + --notable-badge-cyan: oklch(0.55 0.21 180); + --notable-badge-transparent: transparent; } /* End theme: dark */ @@ -3546,12 +3558,14 @@ Original by Dempfi (https://github.com/dempfi/ayu) --scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0); --sidebar-resizer-hover: hsl(34, 50%, 33%); --sidebar-resizer-active: hsl(34, 100%, 66%); - --notable-badge-pink: oklch(0.49 0.21 0); - --notable-badge-red: oklch(0.49 0.21 40); - --notable-badge-orange: oklch(0.49 0.21 70); + --notable-badge-grey: oklch(0.49 0 0); + --notable-badge-red: oklch(0.49 0.21 20); --notable-badge-green: oklch(0.49 0.21 150); + --notable-badge-yellow: oklch(0.49 0.21 100); --notable-badge-blue: oklch(0.49 0.21 240); - --notable-badge-violet: oklch(0.49 0.21 300); + --notable-badge-magenta: oklch(0.49 0.21 320); + --notable-badge-cyan: oklch(0.49 0.21 180); + --notable-badge-transparent: transparent; } :root[data-theme="ayu"] h1, diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index 4a257923dc53c..0b3a75a078298 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -20,15 +20,6 @@

{# #} {# #} - {% if !notable_trait_badges.is_empty() %} -
- {% for badge in notable_trait_badges.iter() %} - {{badge.name}} - {% endfor %} -
- {% endif %} {% if !stability_since_raw.is_empty() %} {{ stability_since_raw|safe +}} {% endif %} @@ -38,5 +29,14 @@

Source {#+ #} {% else %} {% endmatch %} + {% if !notable_trait_badges.is_empty() %} +
+ {% for badge in notable_trait_badges.iter() %} + {{badge.name}} + {% endfor %} +
+ {% endif %} {# #} {# #} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 588a61f8ad3cc..039bb6250df3d 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -1218,7 +1218,13 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) name_value_attr(&mut ret, "keyword", keyword); name_value_attr(&mut ret, "attribute", attribute); toggle_attr(&mut ret, "masked", masked); - toggle_attr(&mut ret, "notable_trait", notable_trait); + if let Some((notable_trait, _span)) = notable_trait { + if let Some((color, _span)) = notable_trait { + ret.push(Attribute::Other(format!("#[doc(notable_trait = \"{color}\")]"))); + } else { + ret.push(Attribute::Other(format!("#[doc(notable_trait)]"))); + } + } toggle_attr(&mut ret, "search_unbox", search_unbox); name_value_attr(&mut ret, "html_favicon_url", html_favicon_url); name_value_attr(&mut ret, "html_logo_url", html_logo_url); diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index 1bfac8e67748c..117da9244aacf 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -49,7 +49,7 @@ pub(super) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> debug!("considering extern trait impl {trait_ref:?}"); if crate_items.contains(&ItemId::DefId(trait_ref.def_id())) || Some(trait_ref.def_id()) == tcx.lang_items().deref_trait() - || tcx.is_doc_notable_trait(trait_ref.def_id()) + || tcx.doc_notable_trait(trait_ref.def_id()).is_some() { debug!("-> inlining due to trait"); cx.with_param_env(impl_def_id, |cx| { diff --git a/tests/rustdoc-gui/notable-trait-colors.goml b/tests/rustdoc-gui/notable-trait-colors.goml new file mode 100644 index 0000000000000..d80cd9c038048 --- /dev/null +++ b/tests/rustdoc-gui/notable-trait-colors.goml @@ -0,0 +1,54 @@ +// Ensures that the theme change is working as expected. +include: "utils.goml" +go-to: "file://" + |DOC_PATH| + "/lib2/notable_trait_colors/struct.NotableTraitColors.html" + +define-function: ( + "test-colors", + [grey, red, green, yellow, blue, magenta, cyan, transparent], + block { + assert-css: (".notable-trait-badge-grey", {"background-color": |grey|}) + assert-css: (".notable-trait-badge-red", {"background-color": |red|}) + assert-css: (".notable-trait-badge-green", {"background-color": |green|}) + assert-css: (".notable-trait-badge-yellow", {"background-color": |yellow|}) + assert-css: (".notable-trait-badge-blue", {"background-color": |blue|}) + assert-css: (".notable-trait-badge-magenta", {"background-color": |magenta|}) + assert-css: (".notable-trait-badge-cyan", {"background-color": |cyan|}) + assert-css: (".notable-trait-badge-transparent", {"background-color": |transparent|}) + } +) + +call-function: ("switch-theme", {"theme": "dark"}) +call-function: ("test-colors", { + "grey": "oklch(0.55 0 0)", + "red": "oklch(0.55 0.21 20)", + "green": "oklch(0.55 0.21 150)", + "yellow": "oklch(0.55 0.21 100)", + "blue": "oklch(0.55 0.21 240)", + "magenta": "oklch(0.55 0.21 320)", + "cyan": "oklch(0.55 0.21 180)", + "transparent": "transparent", +}) + +call-function: ("switch-theme", {"theme": "light"}) +call-function: ("test-colors", { + "grey": "oklch(0.88 0 0)", + "red": "oklch(0.88 0.21 20)", + "green": "oklch(0.88 0.21 150)", + "yellow": "oklch(0.88 0.21 70)", + "blue": "oklch(0.88 0.21 240)", + "magenta": "oklch(0.88 0.21 320)", + "cyan": "oklch(0.88 0.21 180)", + "transparent": "white", +}) + +call-function: ("switch-theme", {"theme": "ayu"}) +call-function: ("test-colors", { + "grey": "oklch(0.49 0 0)", + "red": "oklch(0.49 0.21 20)", + "green": "oklch(0.49 0.21 150)", + "yellow": "oklch(0.49 0.21 100)", + "blue": "oklch(0.49 0.21 240)", + "magenta": "oklch(0.49 0.21 320)", + "cyan": "oklch(0.49 0.21 180)", + "transparent": "transparent", +}) diff --git a/tests/rustdoc-gui/src/lib2/lib.rs b/tests/rustdoc-gui/src/lib2/lib.rs index 28dfa56ea1ad4..bf6d45411baa3 100644 --- a/tests/rustdoc-gui/src/lib2/lib.rs +++ b/tests/rustdoc-gui/src/lib2/lib.rs @@ -1,7 +1,6 @@ // ignore-tidy-file-linelength -#![feature(doc_cfg)] -#![feature(negative_impls)] +#![feature(doc_cfg, negative_impls, doc_notable_trait)] pub mod another_folder; pub mod another_mod; @@ -411,3 +410,31 @@ pub mod deprecated { fn depr_deprecated_fn() {} } } + +pub mod notable_trait_colors { + pub struct NotableTraitColors; + #[doc(notable_trait(color="grey"))] + pub trait Grey {} + impl Grey for NotableTraitColors {} + #[doc(notable_trait(color="red"))] + pub trait Red {} + impl Red for NotableTraitColors {} + #[doc(notable_trait(color="green"))] + pub trait Green {} + impl Green for NotableTraitColors {} + #[doc(notable_trait(color="yellow"))] + pub trait Yellow {} + impl Yellow for NotableTraitColors {} + #[doc(notable_trait(color="blue"))] + pub trait Blue {} + impl Blue for NotableTraitColors {} + #[doc(notable_trait(color="magenta"))] + pub trait Magenta {} + impl Magenta for NotableTraitColors {} + #[doc(notable_trait(color="cyan"))] + pub trait Cyan {} + impl Cyan for NotableTraitColors {} + #[doc(notable_trait(color="transparent"))] + pub trait Transparent {} + impl Transparent for NotableTraitColors {} +} diff --git a/tests/rustdoc-ui/lints/doc-notable_trait-check.rs b/tests/rustdoc-ui/lints/doc-notable_trait-check.rs new file mode 100644 index 0000000000000..e56ecd573ede1 --- /dev/null +++ b/tests/rustdoc-ui/lints/doc-notable_trait-check.rs @@ -0,0 +1,44 @@ +#![feature(doc_notable_trait)] +#![deny(invalid_doc_attributes)] + +#[doc(notable_trait)] +trait NoColor {} + +#[doc(notable_trait())] +trait NoColor2 {} + +#[doc(notable_trait(color="transparent"))] +trait Transparent {} + +#[doc(notable_trait(color="red"))] +trait Red {} + +#[doc(notable_trait="red")] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidKV {} + +#[doc(notable_trait)] +//~^ ERROR +//~| WARN previously accepted by the compiler +struct InvalidNotTrait; + +#[doc(notable_trait(check="red"))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidKVArg {} + +#[doc(notable_trait(color))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidAtomArg {} + +#[doc(notable_trait(color="invalid_color"))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidColor {} + +#[doc(notable_trait(color="transparent", color="red"))] +//~^ ERROR +//~| WARN previously accepted by the compiler +trait InvalidMultiple {} diff --git a/tests/rustdoc-ui/lints/doc-notable_trait-check.stderr b/tests/rustdoc-ui/lints/doc-notable_trait-check.stderr new file mode 100644 index 0000000000000..1d53e44d12f86 --- /dev/null +++ b/tests/rustdoc-ui/lints/doc-notable_trait-check.stderr @@ -0,0 +1,55 @@ +error: expected either `doc(notable_trait)` or `doc(notable_trait="...")` + --> $DIR/doc-notable_trait-check.rs:16:7 + | +LL | #[doc(notable_trait="red")] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! +note: the lint level is defined here + --> $DIR/doc-notable_trait-check.rs:2:9 + | +LL | #![deny(invalid_doc_attributes)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: `#![doc(notable_trait)]` must be a trait attribute + --> $DIR/doc-notable_trait-check.rs:21:7 + | +LL | #[doc(notable_trait)] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait="...")` + --> $DIR/doc-notable_trait-check.rs:26:7 + | +LL | #[doc(notable_trait(check="red"))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait="...")` + --> $DIR/doc-notable_trait-check.rs:31:7 + | +LL | #[doc(notable_trait(color))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait="...")` + --> $DIR/doc-notable_trait-check.rs:36:7 + | +LL | #[doc(notable_trait(color="invalid_color"))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: expected either `doc(notable_trait)` or `doc(notable_trait="...")` + --> $DIR/doc-notable_trait-check.rs:41:7 + | +LL | #[doc(notable_trait(color="transparent", color="red"))] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + +error: aborting due to 6 previous errors + From 136b3991af5ba91eb040b0ce88672b0bd2344117 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sun, 2 Aug 2026 09:38:22 -0700 Subject: [PATCH 2/7] Update compiler/rustc_hir/src/attrs/data_structures.rs Co-authored-by: Guillaume Gomez --- compiler/rustc_attr_ir/src/data_structures.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 4a0ccb059f3b5..76144b2786e3e 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -553,7 +553,7 @@ impl Into<&'static str> for NotableTraitColor { impl std::fmt::Display for NotableTraitColor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - std::fmt::Display::fmt(>::into(*self), f) + f.write_str(self.into()) } } From 025f6d2fcfa2ffdf44e9c57d29bb4829613d4347 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 18 Sep 2026 17:02:52 -0700 Subject: [PATCH 3/7] Remove unnecessary repetition and indent --- compiler/rustc_attr_ir/src/data_structures.rs | 8 +- compiler/rustc_middle/src/queries.rs | 2 +- src/doc/rustdoc/src/unstable-features.md | 117 ++---------------- src/librustdoc/html/templates/print_item.html | 14 +-- 4 files changed, 24 insertions(+), 117 deletions(-) diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 76144b2786e3e..1f27d1de01f91 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -535,10 +535,10 @@ pub enum NotableTraitColor { Transparent, } -impl Into<&'static str> for NotableTraitColor { - fn into(self) -> &'static str { +impl From for &'static str { + fn from(color: NotableTraitColor) -> &'static str { use NotableTraitColor::*; - match self { + match color { Grey => "grey", Red => "red", Green => "green", @@ -553,7 +553,7 @@ impl Into<&'static str> for NotableTraitColor { impl std::fmt::Display for NotableTraitColor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.into()) + f.write_str((*self).into()) } } diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 6eca9a777af00..166d34a46f248 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -1531,7 +1531,7 @@ rustc_queries! { } /// If an item is annotated with `#[doc(notable_trait)]`, - /// returns the color used to render its pill. If the crate specifies + /// returns the color used to render its badge. If the crate specifies /// no color, `Transparent` is used. query doc_notable_trait(def_id: DefId) -> Option<&'tcx NotableTraitColor> { desc { "checking whether `{}` is `doc(notable_trait)`", tcx.def_path_str(def_id) } diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 6797418f58681..9bd795b759ec4 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -78,111 +78,18 @@ In addition to the "Notable traits" dialog, every type that implements a `#[doc(notable_trait)]` trait renders a badge for that trait at the top of its page, making the relationship easy to spot when browsing the type. To set a color for the badge, write `#[doc(notable_trait(color="red"))]` or -one of the other colors in the list: - - - -| Name | Light | Dark | Ayu | -| ----------- | ----- | ---- | --- | -| grey |
Trait
|
Trait
|
Trait
-| red |
Trait
|
Trait
|
Trait
-| green |
Trait
|
Trait
|
Trait
-| yellow |
Trait
|
Trait
|
Trait
-| blue |
Trait
|
Trait
|
Trait
-| magenta |
Trait
|
Trait
|
Trait
-| cyan |
Trait
|
Trait
|
Trait
-| transparent |
Trait
|
Trait
|
Trait
+one of the other colors in the list (from the [ANSI 3 bit terminal palette][]): + +[ANSI 3 bit terminal palette]: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit + +- grey +- red +- green +- yellow +- blue +- magenta +- cyan +- transparent The `#[doc(notable_trait)]` attribute currently requires the `#![feature(doc_notable_trait)]` feature gate. For more information, see [its chapter in the Unstable Book][unstable-notable_trait] diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index 0b3a75a078298..776d7f25eaee4 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -30,13 +30,13 @@

{% else %} {% endmatch %} {% if !notable_trait_badges.is_empty() %} -
- {% for badge in notable_trait_badges.iter() %} - {{badge.name}} - {% endfor %} -
+
+ {% for badge in notable_trait_badges.iter() %} + {{badge.name}} + {% endfor %} +
{% endif %} {# #} {# #} From cba5899fdf6284d1f326a3081e1a1ec5cf174fd0 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 18 Sep 2026 17:25:31 -0700 Subject: [PATCH 4/7] Give `transparent` badge a visible border --- library/alloc/src/io/read.rs | 2 +- library/core/src/future/future.rs | 2 +- library/core/src/io/write.rs | 2 +- library/core/src/iter/traits/iterator.rs | 2 +- src/librustdoc/html/static/css/rustdoc.css | 53 +++++++++++++------ ...-colors.goml => notable-trait-badges.goml} | 34 ++++++------ 6 files changed, 61 insertions(+), 34 deletions(-) rename tests/rustdoc-gui/{notable-trait-colors.goml => notable-trait-badges.goml} (70%) diff --git a/library/alloc/src/io/read.rs b/library/alloc/src/io/read.rs index e751d3d59e617..145cee958aeb3 100644 --- a/library/alloc/src/io/read.rs +++ b/library/alloc/src/io/read.rs @@ -81,7 +81,7 @@ use crate::vec::Vec; /// [`&str`]: prim@str /// [`std::io`]: crate::io #[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait(color = "grey"))] +#[doc(notable_trait(color = "transparent"))] #[cfg_attr(not(test), rustc_diagnostic_item = "IoRead")] #[rustc_must_implement_one_of(read_buf, read)] // Keep this order, it's important for rust-analyzer (the preferred-to-implement method should come first). pub trait Read { diff --git a/library/core/src/future/future.rs b/library/core/src/future/future.rs index 0c101fbca8ee9..b84b00510c41e 100644 --- a/library/core/src/future/future.rs +++ b/library/core/src/future/future.rs @@ -25,7 +25,7 @@ use crate::task::{Context, Poll}; /// /// [`async`]: ../../std/keyword.async.html /// [`Waker`]: crate::task::Waker -#[doc(notable_trait(color = "blue"))] +#[doc(notable_trait(color = "transparent"))] #[doc(search_unbox)] #[must_use = "futures do nothing unless you `.await` or poll them"] #[stable(feature = "futures_api", since = "1.36.0")] diff --git a/library/core/src/io/write.rs b/library/core/src/io/write.rs index 24566c22881c1..1318d4bc0ee8a 100644 --- a/library/core/src/io/write.rs +++ b/library/core/src/io/write.rs @@ -47,7 +47,7 @@ use crate::io::{Error, IoSlice, Result}; /// /// [`write_all`]: Write::write_all #[stable(feature = "rust1", since = "1.0.0")] -#[doc(notable_trait(color = "grey"))] +#[doc(notable_trait(color = "transparent"))] #[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")] pub trait Write { /// Writes a buffer into this writer, returning how many bytes were written. diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index 0f60ec7b58911..a5165217aca43 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -34,7 +34,7 @@ fn _assert_is_dyn_compatible(_: &dyn Iterator) {} label = "`{Self}` is not an iterator", message = "`{Self}` is not an iterator" )] -#[doc(notable_trait(color = "blue"))] +#[doc(notable_trait(color = "transparent"))] #[lang = "iterator"] #[rustc_diagnostic_item = "Iterator"] #[must_use = "iterators are lazy and do nothing unless consumed"] diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index ee6006c05de75..b1e9f7b564fa2 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1664,26 +1664,49 @@ so that we can apply CSS-filters to change the arrow color in themes */ font-size: 1rem; font-weight: normal; color: var(--main-color); - border: solid 1px transparent; -} - -.notable-trait-badge-container > a:hover { - text-decoration: none; - border: solid 1px var(--settings-button-border-focus); } /* this list of colors is lifted from the ANSI escape codes list: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit This way, CLI-based tooling can adapt them without having to do complex platform support dances. */ -.notable-trait-badge-grey { background: var(--notable-badge-grey); } -.notable-trait-badge-red { background: var(--notable-badge-red); } -.notable-trait-badge-green { background: var(--notable-badge-green); } -.notable-trait-badge-yellow { background: var(--notable-badge-yellow); } -.notable-trait-badge-blue { background: var(--notable-badge-blue); } -.notable-trait-badge-magenta { background: var(--notable-badge-magenta); } -.notable-trait-badge-cyan { background: var(--notable-badge-cyan); } -.notable-trait-badge-transparent { background: var(--notable-badge-transparent); } +.notable-trait-badge-container > .notable-trait-badge-grey { + background: var(--notable-badge-grey); + border: solid 1px var(--notable-badge-grey); +} +.notable-trait-badge-container > .notable-trait-badge-red { + background: var(--notable-badge-red); + border: solid 1px var(--notable-badge-red); +} +.notable-trait-badge-container > .notable-trait-badge-green { + background: var(--notable-badge-green); + border: solid 1px var(--notable-badge-green); +} +.notable-trait-badge-container > .notable-trait-badge-yellow { + background: var(--notable-badge-yellow); + border: solid 1px var(--notable-badge-yellow); +} +.notable-trait-badge-container > .notable-trait-badge-blue { + background: var(--notable-badge-blue); + border: solid 1px var(--notable-badge-blue); +} +.notable-trait-badge-container > .notable-trait-badge-magenta { + background: var(--notable-badge-magenta); + border: solid 1px var(--notable-badge-magenta); +} +.notable-trait-badge-container > .notable-trait-badge-cyan { + background: var(--notable-badge-cyan); + border: solid 1px var(--notable-badge-cyan); +} +.notable-trait-badge-container > .notable-trait-badge-transparent { + background: var(--notable-badge-transparent); + border: solid 1px var(--border-color); +} + +.notable-trait-badge-container > a:hover { + text-decoration: none; + border: solid 1px var(--settings-button-border-focus); +} .rightside { padding-left: 12px; @@ -3325,7 +3348,7 @@ by default. --notable-badge-blue: oklch(0.88 0.21 240); --notable-badge-magenta: oklch(0.88 0.21 320); --notable-badge-cyan: oklch(0.88 0.21 180); - --notable-badge-transparent: white; + --notable-badge-transparent: transparent; } /* End theme: light */ diff --git a/tests/rustdoc-gui/notable-trait-colors.goml b/tests/rustdoc-gui/notable-trait-badges.goml similarity index 70% rename from tests/rustdoc-gui/notable-trait-colors.goml rename to tests/rustdoc-gui/notable-trait-badges.goml index d80cd9c038048..45bff3b84301b 100644 --- a/tests/rustdoc-gui/notable-trait-colors.goml +++ b/tests/rustdoc-gui/notable-trait-badges.goml @@ -3,22 +3,23 @@ include: "utils.goml" go-to: "file://" + |DOC_PATH| + "/lib2/notable_trait_colors/struct.NotableTraitColors.html" define-function: ( - "test-colors", - [grey, red, green, yellow, blue, magenta, cyan, transparent], + "test-badges", + [ + grey, red, green, yellow, blue, magenta, cyan, transparent, transparent_border, + ], block { - assert-css: (".notable-trait-badge-grey", {"background-color": |grey|}) - assert-css: (".notable-trait-badge-red", {"background-color": |red|}) - assert-css: (".notable-trait-badge-green", {"background-color": |green|}) - assert-css: (".notable-trait-badge-yellow", {"background-color": |yellow|}) - assert-css: (".notable-trait-badge-blue", {"background-color": |blue|}) - assert-css: (".notable-trait-badge-magenta", {"background-color": |magenta|}) - assert-css: (".notable-trait-badge-cyan", {"background-color": |cyan|}) - assert-css: (".notable-trait-badge-transparent", {"background-color": |transparent|}) + assert-css: (".notable-trait-badge-grey", {"background-color": |grey|, "border-color": |grey|}) + assert-css: (".notable-trait-badge-red", {"background-color": |red|, "border-color": |red|}) + assert-css: (".notable-trait-badge-green", {"background-color": |green|, "border-color": |green|}) + assert-css: (".notable-trait-badge-yellow", {"background-color": |yellow|, "border-color": |yellow|}) + assert-css: (".notable-trait-badge-blue", {"background-color": |blue|, "border-color": |blue|}) + assert-css: (".notable-trait-badge-magenta", {"background-color": |magenta|, "border-color": |magenta|}) + assert-css: (".notable-trait-badge-cyan", {"background-color": |cyan|, "border-color": |cyan|}) + assert-css: (".notable-trait-badge-transparent", {"background-color": |transparent|, "border-color": |transparent_border|}) } ) - call-function: ("switch-theme", {"theme": "dark"}) -call-function: ("test-colors", { +call-function: ("test-badges", { "grey": "oklch(0.55 0 0)", "red": "oklch(0.55 0.21 20)", "green": "oklch(0.55 0.21 150)", @@ -27,10 +28,11 @@ call-function: ("test-colors", { "magenta": "oklch(0.55 0.21 320)", "cyan": "oklch(0.55 0.21 180)", "transparent": "transparent", + "transparent_border": "#e0e0e0", }) call-function: ("switch-theme", {"theme": "light"}) -call-function: ("test-colors", { +call-function: ("test-badges", { "grey": "oklch(0.88 0 0)", "red": "oklch(0.88 0.21 20)", "green": "oklch(0.88 0.21 150)", @@ -38,11 +40,12 @@ call-function: ("test-colors", { "blue": "oklch(0.88 0.21 240)", "magenta": "oklch(0.88 0.21 320)", "cyan": "oklch(0.88 0.21 180)", - "transparent": "white", + "transparent": "transparent", + "transparent_border": "#e0e0e0", }) call-function: ("switch-theme", {"theme": "ayu"}) -call-function: ("test-colors", { +call-function: ("test-badges", { "grey": "oklch(0.49 0 0)", "red": "oklch(0.49 0.21 20)", "green": "oklch(0.49 0.21 150)", @@ -51,4 +54,5 @@ call-function: ("test-colors", { "magenta": "oklch(0.49 0.21 320)", "cyan": "oklch(0.49 0.21 180)", "transparent": "transparent", + "transparent_border": "#5c6773", }) From 17ce95572feff8ba0c766cc18ee37a7ed9a7b60c Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 18 Sep 2026 19:06:36 -0700 Subject: [PATCH 5/7] Clean up and enhance notable trait badges Add test cases, and the same detailed info block that you get for return position notable traits. --- .../rustc_attr_parsing/src/attributes/doc.rs | 65 +++----- .../rustc_attr_parsing/src/diagnostics.rs | 11 ++ src/librustdoc/html/render/mod.rs | 144 ++++++++++++------ src/librustdoc/html/render/print_item.rs | 5 +- src/librustdoc/html/static/css/noscript.css | 2 +- src/librustdoc/html/static/css/rustdoc.css | 18 ++- src/librustdoc/html/templates/print_item.html | 3 +- tests/rustdoc-gui/notable-trait-badges.goml | 25 ++- tests/rustdoc-gui/notable-traits.goml | 7 +- tests/rustdoc-gui/src/lib2/lib.rs | 1 + 10 files changed, 173 insertions(+), 108 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index e384961b0fe05..37d5bb4431d49 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -15,13 +15,14 @@ use super::{AcceptMapping, AttributeParser, template}; use crate::context::{AcceptContext, FinalizeContext}; use crate::diagnostics::{ AttrCrateLevelOnly, DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed, - DocAliasStartEnd, DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow, - DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues, - DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues, - DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, - DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, - DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, - InvalidNotableTraitAttr, MalformedDoc, UnusedDuplicate, + DocAliasStartEnd, DocAttrNotCrateLevel, DocAttrNotTraitLevel, DocAttributeNotAttribute, + DocAutoCfgExpectsHideOrShow, DocAutoCfgHideShowExpectsList, + DocAutoCfgHideShowNoIdentBeforeValues, DocAutoCfgHideShowUnexpectedItem, + DocAutoCfgHideShowUnexpectedItemAfterValues, DocAutoCfgHideShowValuesMix, + DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, DocTestUnknown, + DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight, + ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, InvalidNotableTraitAttr, + MalformedDoc, UnusedDuplicate, }; use crate::parser::{ ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser, @@ -109,20 +110,12 @@ fn parse_notable_trait( } else if let Some(meta_item) = meta_item_list_parser.as_single() { Some(meta_item) } else { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; } } ArgParser::NameValue(_) => { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; } }; @@ -130,35 +123,19 @@ fn parse_notable_trait( let notable_trait_color_and_span = if let Some(notable_trait_color_attr) = notable_trait_color_attr { let Some(notable_trait_color_attr) = notable_trait_color_attr.meta_item() else { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; }; if !notable_trait_color_attr.path().word_is(sym::color) { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; } let Some(notable_trait_color) = notable_trait_color_attr.args().as_name_value() else { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; }; let Some(notable_trait_color) = notable_trait_color.value_as_str() else { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; }; let notable_trait_color = match notable_trait_color.as_str() { @@ -171,11 +148,7 @@ fn parse_notable_trait( "cyan" => NotableTraitColor::Cyan, "transparent" => NotableTraitColor::Transparent, _ => { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - InvalidNotableTraitAttr, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span); return; } }; @@ -185,11 +158,7 @@ fn parse_notable_trait( }; if cx.shared.target != Target::Trait { - cx.emit_lint( - rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, - DocAttrTraitLevel { span, attr_name }, - span, - ); + cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAttrNotTraitLevel { span, attr_name }, span); return; } @@ -705,7 +674,7 @@ impl DocParser { &mut self.attribute.notable_trait, sym::notable_trait, ) - }, + } Some(sym::keyword) => { gated!(rustdoc_internals); parse_keyword_and_attribute( diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index b7b0c61e3e4cc..e667d1fd457a6 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -988,6 +988,17 @@ pub(crate) struct DocAttributeNotAttribute { pub attribute: Symbol, } +#[derive(Diagnostic)] +#[diag("`#![doc({$attr_name})]` must be a trait attribute")] +#[warning( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" +)] +pub(crate) struct DocAttrNotTraitLevel { + #[primary_span] + pub span: Span, + pub attr_name: Symbol, +} + #[derive(Diagnostic)] #[diag( "`#[target_feature]` cannot be applied to a {$kind -> diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 6fed5ae6228ed..6b278316c6153 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1714,13 +1714,64 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option) -> (String, String) { +fn notable_traits_decl_write_html<'cx>( + notable_impls: impl Iterator, + f: &mut fmt::Formatter<'_>, + cx: &Context<'_>, +) -> Result<(), fmt::Error> { + let mut notable_impls = notable_impls.peekable(); + let has_notable_impl = if let Some((impl_, _)) = notable_impls.peek() { + write!( + f, + "

Notable traits for {}

\ +
",
+            print_type(&impl_.for_, cx),
+        )?;
+        true
+    } else {
+        false
+    };
+
+    for (impl_, trait_did) in notable_impls {
+        write!(f, "
{}
", print_impl(impl_, false, cx))?; + for it in &impl_.items { + let clean::AssocTypeItem(tydef, ..) = &it.kind else { + continue; + }; + + let empty_set = FxIndexSet::default(); + let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); + + write!( + f, + "
{};
", + assoc_type( + it, + &tydef.generics, + &[], // intentionally leaving out bounds + Some(&tydef.type_), + src_link, + 0, + cx, + ) + )?; + } + } + + if !has_notable_impl { + f.write_str("
")?; + } + + Ok(()) +} + +fn notable_traits_decl_for_type(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { let did = ty.def_id(cx.cache()).expect("notable_traits_button already checked this"); let impls = cx.cache().impls.get(&did).expect("notable_traits_button already checked this"); let out = fmt::from_fn(|f| { - let mut notable_impls = impls + let notable_impls = impls .iter() .map(|impl_| impl_.inner_impl()) .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) @@ -1738,60 +1789,57 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { } else { None } - }) - .peekable(); - - let has_notable_impl = if let Some((impl_, _)) = notable_impls.peek() { - write!( - f, - "

Notable traits for {}

\ -
",
-                print_type(&impl_.for_, cx),
-            )?;
-            true
-        } else {
-            false
-        };
-
-        for (impl_, trait_did) in notable_impls {
-            write!(f, "
{}
", print_impl(impl_, false, cx))?; - for it in &impl_.items { - let clean::AssocTypeItem(tydef, ..) = &it.kind else { - continue; - }; - - let empty_set = FxIndexSet::default(); - let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); + }); - write!( - f, - "
{};
", - assoc_type( - it, - &tydef.generics, - &[], // intentionally leaving out bounds - Some(&tydef.type_), - src_link, - 0, - cx, - ) - )?; - } - } + notable_traits_decl_write_html(notable_impls, f, cx) + }) + .to_string(); - if !has_notable_impl { - f.write_str("
")?; - } + (format!("{:#}", print_type(ty, cx)), out) +} - Ok(()) +fn notable_traits_decl_for_item(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> { + let out = fmt::from_fn(|f| { + let notable_impls = if let Some(def_id) = item.def_id() + && !is_notable_trait_passthrough(def_id, cx) + && let Some(impls) = cx.cache().impls.get(&def_id) + { + impls + .iter() + .map(Impl::inner_impl) + .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) + .filter_map(|impl_| { + if let Some(trait_) = &impl_.trait_ + && let trait_did = trait_.def_id() + && let Some(trait_) = cx.cache().traits.get(&trait_did) + && trait_.is_notable_trait(cx.tcx()) + { + Some((impl_, trait_did)) + } else { + None + } + }) + } else { + return Ok(()); + }; + notable_traits_decl_write_html(notable_impls, f, cx) }) .to_string(); - (format!("{:#}", print_type(ty, cx)), out) + Some((item.name?.to_string(), out)) } -fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String { - let mut mp = tys.map(|ty| notable_traits_decl(ty, cx)).collect::>(); +fn notable_traits_json<'a>( + tys: impl Iterator, + item: &clean::Item, + cx: &Context<'_>, +) -> String { + let mut mp = tys.map(|ty| notable_traits_decl_for_type(ty, cx)).collect::>(); + if let Some((item_name, item_decl)) = notable_traits_decl_for_item(item, cx) + && !item_decl.is_empty() + { + mp.insert(item_name, item_decl); + } mp.sort_unstable_keys(); serde_json::to_string(&mp).expect("serialize (string, string) -> json object cannot fail") } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index b452c23f86223..68003f7751333 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -132,6 +132,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp color: info.color, }) .collect(); + let has_notable_trait_badges = !notable_trait_badges.is_empty(); let path_components = if item.is_fake_item() { vec![] @@ -209,11 +210,11 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp // Render notable-traits.js used for all methods in this module. let mut types_with_notable_traits = cx.types_with_notable_traits.borrow_mut(); - if !types_with_notable_traits.is_empty() { + if !types_with_notable_traits.is_empty() || has_notable_trait_badges { write!( buf, r#""#, - notable_traits_json(types_with_notable_traits.iter(), cx), + notable_traits_json(types_with_notable_traits.iter(), item, cx), )?; types_with_notable_traits.clear(); } diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index 2f1c84f5b86f0..564e785a19f93 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -148,7 +148,7 @@ nav.sub { --notable-badge-blue: oklch(0.88 0.21 240); --notable-badge-magenta: oklch(0.88 0.21 320); --notable-badge-cyan: oklch(0.88 0.21 180); - --notable-badge-transparent: white; + --notable-badge-transparent: transparent; } /* End theme: light */ diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index b1e9f7b564fa2..e7479c030b630 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1652,15 +1652,25 @@ so that we can apply CSS-filters to change the arrow color in themes */ display: flex; flex-wrap: wrap; gap: 8px; + margin-left: var(--docblock-indent); + position: relative; +} + +.notable-trait-badge-container > a.tooltip { + position: absolute; + width: 16px; + line-height: 1.5rem; + vertical-align: center; + left: calc(-1 * var(--docblock-indent)); } -.notable-trait-badge-container > a { +.notable-trait-badge-container > a:not(.tooltip) { display: flex; align-items: center; width: fit-content; height: 1.5rem; - padding: 0 14px; - border-radius: var(--code-block-border-radius); + padding: 0 0.5rem; + border-radius: 0.75rem; font-size: 1rem; font-weight: normal; color: var(--main-color); @@ -1703,7 +1713,7 @@ so that we can apply CSS-filters to change the arrow color in themes */ border: solid 1px var(--border-color); } -.notable-trait-badge-container > a:hover { +.notable-trait-badge-container > a:hover:not(.tooltip) { text-decoration: none; border: solid 1px var(--settings-button-border-focus); } diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index 776d7f25eaee4..e5d3d1adbdcb2 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -30,7 +30,8 @@

{% else %} {% endmatch %} {% if !notable_trait_badges.is_empty() %} -
+
{# #} + {% for badge in notable_trait_badges.iter() %} .docblock", {"margin-left": top_docblock_margin_left}) + assert-css: (".notable-trait-badge-container", {"margin-left": |top_docblock_margin_left|}) + store-position: (".top-doc > .docblock", {"x": top_docblock_x}) + assert-position: (".notable-trait-badge-container", {"x": |top_docblock_x|}) + store-position: (".top-doc > summary::before", {"x": top_docblock_toggle_x}) + assert-position: (".notable-trait-badge-container > a.tooltip", {"x": |top_docblock_toggle_x|}) } ) call-function: ("switch-theme", {"theme": "dark"}) @@ -56,3 +65,17 @@ call-function: ("test-badges", { "transparent": "transparent", "transparent_border": "#5c6773", }) + +assert-false: ".tooltip.popover" +click: ".notable-trait-badge-container > a.tooltip" +wait-for: ".tooltip.popover" +assert-text: (".tooltip.popover h3", "Notable traits for NotableTraitColors") +assert-count: (".tooltip.popover .where", 8) +assert-text: (".tooltip.popover .where:nth-child(1)", "impl Grey for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(2)", "impl Red for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(3)", "impl Green for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(4)", "impl Yellow for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(5)", "impl Blue for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(6)", "impl Magenta for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(7)", "impl Cyan for NotableTraitColors") +assert-text: (".tooltip.popover .where:nth-child(8)", "impl Transparent for NotableTraitColors") diff --git a/tests/rustdoc-gui/notable-traits.goml b/tests/rustdoc-gui/notable-traits.goml index f31b83e12edf0..60a66b219b2f8 100644 --- a/tests/rustdoc-gui/notable-traits.goml +++ b/tests/rustdoc-gui/notable-traits.goml @@ -2,11 +2,12 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/notable/struct.Wrapper.html" show-text: true store-css: (".notable-trait-badge-container", {"background-color": background_color}) -// The background color should be different. -assert-css-false: (".notable-trait-badge-container > a", {"background-color": |background_color|}) +// The border color should be different. +assert-css-false: (".notable-trait-badge-container > a:not(.tooltip)", {"border-color": |background_color|}) +assert-css-false: (".notable-trait-badge-container > a:not(.tooltip)", {"border-color": "transparent"}) store-css: ("#trait-implementations", {"color": text_color}) -assert-css: (".notable-trait-badge-container > a", { +assert-css: (".notable-trait-badge-container > a:not(.tooltip)", { // The color should be the same as the "main color". "color": |text_color|, // Checking some additional CSS values. diff --git a/tests/rustdoc-gui/src/lib2/lib.rs b/tests/rustdoc-gui/src/lib2/lib.rs index bf6d45411baa3..3104b88b183e3 100644 --- a/tests/rustdoc-gui/src/lib2/lib.rs +++ b/tests/rustdoc-gui/src/lib2/lib.rs @@ -412,6 +412,7 @@ pub mod deprecated { } pub mod notable_trait_colors { + /// Top doc pub struct NotableTraitColors; #[doc(notable_trait(color="grey"))] pub trait Grey {} From f0cf8b028aa7bc94a1e1a6517701096792dfee18 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 18 Sep 2026 20:35:51 -0700 Subject: [PATCH 6/7] Fix feature gate test case This still produces the feature gate error, so it's fine, but it also produces the "trait attribute" warning --- .../feature-gates/feature-gate-doc_notable_trait.rs | 2 ++ .../feature-gate-doc_notable_trait.stderr | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs b/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs index 1bc1028b9e0ef..b06b17650da30 100644 --- a/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs +++ b/tests/ui/feature-gates/feature-gate-doc_notable_trait.rs @@ -4,5 +4,7 @@ trait SomeTrait {} fn main() { #[doc(notable_trait)] //~^ ERROR the `doc(notable_trait)` attribute is experimental [E0658] + //~| WARN `#![doc(notable_trait)]` must be a trait attribute + //~| WARN this was previously accepted println!(); } diff --git a/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr b/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr index 632c91b929185..a7be03385fc3f 100644 --- a/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr +++ b/tests/ui/feature-gates/feature-gate-doc_notable_trait.stderr @@ -8,6 +8,15 @@ LL | #[doc(notable_trait)] = help: add `#![feature(doc_notable_trait)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +warning: `#![doc(notable_trait)]` must be a trait attribute + --> $DIR/feature-gate-doc_notable_trait.rs:5:11 + | +LL | #[doc(notable_trait)] + | ^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: `#[warn(invalid_doc_attributes)]` on by default + error[E0658]: the `doc(notable_trait)` attribute is experimental --> $DIR/feature-gate-doc_notable_trait.rs:1:7 | @@ -18,6 +27,6 @@ LL | #[doc(notable_trait)] = help: add `#![feature(doc_notable_trait)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 2 previous errors +error: aborting due to 2 previous errors; 1 warning emitted For more information about this error, try `rustc --explain E0658`. From 03fbe38b11f2855a67f0bb4785b8cb64e3e55ff3 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 18 Sep 2026 21:24:06 -0700 Subject: [PATCH 7/7] Fix tests --- .../notable-trait/notable-trait-badge-negative.rs | 3 ++- .../notable-trait/notable-trait-badge-supertrait.rs | 3 ++- .../notable-trait-badge-unlinkable-cross-crate.rs | 3 ++- tests/rustdoc-html/notable-trait/notable-trait-badge.rs | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs index 225572513c40f..dc7b349ede2f3 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-negative.rs @@ -8,7 +8,8 @@ pub trait Pos {} // A negative impl must not produce a badge. //@ has 'foo/struct.T.html' -//@ count - '//div[@class="notable-trait-badge-container"]/a' 1 +//@ count - '//div[@class="notable-trait-badge-container"]/a' 2 +//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Pos.html"]' 'Pos' pub struct T; impl !Neg for T {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs index 9bb3226099a51..2d29fbeedf80e 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-supertrait.rs @@ -9,7 +9,8 @@ pub trait Derived: Base {} //@ has 'foo/struct.S.html' // Implementing `Derived` requires implementing the notable supertrait `Base`, // so its badge shows up. -//@ count - '//div[@class="notable-trait-badge-container"]/a' 1 +//@ count - '//div[@class="notable-trait-badge-container"]/a' 2 +//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Base.html"]' 'Base' pub struct S; impl Base for S {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs index de7008cf5ab0c..a249a100e6541 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge-unlinkable-cross-crate.rs @@ -12,6 +12,7 @@ use notable_dep::Spaceship; // The badge is present... //@ has - '//div[@class="notable-trait-badge-container"]/a' 'Spaceship' // ...but unlinked: no badge carries an `href`. -//@ count - '//div[@class="notable-trait-badge-container"]/a[@href]' 0 +//@ count - '//div[@class="notable-trait-badge-container"]/a[@href]' 1 +//@ has - '//div[@class="notable-trait-badge-container"]/a[@href="#"]' 'ⓘ' pub struct Rocket; impl Spaceship for Rocket {} diff --git a/tests/rustdoc-html/notable-trait/notable-trait-badge.rs b/tests/rustdoc-html/notable-trait/notable-trait-badge.rs index b53d989d0f819..04e7a52918384 100644 --- a/tests/rustdoc-html/notable-trait/notable-trait-badge.rs +++ b/tests/rustdoc-html/notable-trait/notable-trait-badge.rs @@ -12,8 +12,9 @@ pub trait Plain {} //@ has 'foo/struct.Tagged.html' //@ has - '//div[@class="notable-trait-badge-container"]/a[@href="trait.Labeled.html"][@title="foo::Labeled"]' 'Labeled' // Badges are sorted by trait name, so `AlsoLabeled` precedes `Labeled`. -//@ has - '//div[@class="notable-trait-badge-container"]/a[1]' 'AlsoLabeled' -//@ has - '//div[@class="notable-trait-badge-container"]/a[2]' 'Labeled' +//@ has - '//div[@class="notable-trait-badge-container"]/a[1]' 'ⓘ' +//@ has - '//div[@class="notable-trait-badge-container"]/a[2]' 'AlsoLabeled' +//@ has - '//div[@class="notable-trait-badge-container"]/a[3]' 'Labeled' pub struct Tagged; impl Labeled for Tagged {} impl AlsoLabeled for Tagged {}