Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion compiler/rustc_attr_ir/src/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ pub struct DocAttribute {
pub keyword: Option<(Symbol, Span)>,
pub attribute: Option<(Symbol, Span)>,
pub masked: Option<Span>,
pub notable_trait: Option<Span>,
pub notable_trait: Option<(Option<(NotableTraitColor, Span)>, Span)>,
pub search_unbox: Option<Span>,

// valid on crate
Expand All @@ -522,6 +522,41 @@ pub struct DocAttribute {
pub no_crate_inject: Option<Span>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(StableHash, Encodable, Decodable, PrintAttribute)]
pub enum NotableTraitColor {
Grey,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
Transparent,
}

impl From<NotableTraitColor> for &'static str {
fn from(color: NotableTraitColor) -> &'static str {
use NotableTraitColor::*;
match color {
Grey => "grey",

@lolbinarycat lolbinarycat Sep 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to support both spellings of gray/grey? if not, i believe "gray" is the more common in US english, which is what rustdoc uses (otherwise this would be colour)

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought gray was only a name until now. I'd been in favour of only using grey, as for foreigners, it's how we learn the color name (or colour :p).

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 {
f.write_str((*self).into())
}
}

impl<E: rustc_span::SpanEncoder> rustc_serialize::Encodable<E> for DocAttribute {
fn encode(&self, encoder: &mut E) {
let DocAttribute {
Expand Down
97 changes: 88 additions & 9 deletions compiler/rustc_attr_parsing/src/attributes/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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, 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,
Expand Down Expand Up @@ -92,6 +93,78 @@ 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(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
return;
}
}
ArgParser::NameValue(_) => {
cx.emit_lint(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(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
return;
};
if !notable_trait_color_attr.path().word_is(sym::color) {
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(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);
return;
};
let Some(notable_trait_color) = notable_trait_color.value_as_str() else {
cx.emit_lint(INVALID_DOC_ATTRIBUTES, InvalidNotableTraitAttr, span);

@GuillaumeGomez GuillaumeGomez Sep 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to give some extra info because they all emit the same lint with the same span. Or was it on purpose while waiting for team's approval?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured that sort of thing could be handled in a follow-up, after we decide on the syntax we want at all.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then add a fixme comment and open an issue.

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(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(INVALID_DOC_ATTRIBUTES, DocAttrNotTraitLevel { span, attr_name }, span);
return;
}

*attr_value = Some((notable_trait_color_and_span, span));
}

fn parse_keyword_and_attribute(
cx: &mut AcceptContext<'_, '_>,
path: &OwnedPathParser,
Expand Down Expand Up @@ -594,7 +667,13 @@ 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);
Expand Down
18 changes: 18 additions & 0 deletions compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down Expand Up @@ -981,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 ->
Expand Down
8 changes: 5 additions & 3 deletions compiler/rustc_middle/src/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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 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) }
}

Expand Down
14 changes: 11 additions & 3 deletions compiler/rustc_middle/src/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ symbols! {
cold,
cold_path,
collapse_debuginfo,
color,
column,
common,
compare_bytes,
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/io/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "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 {
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/future/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "transparent"))]
#[doc(search_unbox)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
#[stable(feature = "futures_api", since = "1.36.0")]
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/io/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "transparent"))]
#[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")]
pub trait Write {
/// Writes a buffer into this writer, returning how many bytes were written.
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn _assert_is_dyn_compatible(_: &dyn Iterator<Item = ()>) {}
label = "`{Self}` is not an iterator",
message = "`{Self}` is not an iterator"
)]
#[doc(notable_trait)]
#[doc(notable_trait(color = "transparent"))]
#[lang = "iterator"]
#[rustc_diagnostic_item = "Iterator"]
#[must_use = "iterators are lazy and do nothing unless consumed"]
Expand Down
15 changes: 14 additions & 1 deletion src/doc/rustdoc/src/unstable-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,21 @@ 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 (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]
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading