Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
119 changes: 115 additions & 4 deletions compiler/rustc_attr_parsing/src/attributes/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_feature::AttributeStability;
use rustc_hir::Target;
use rustc_hir::attrs::{
AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,
DocInline, HideOrShow,
DocInline, HideOrShow, NotableTraitColor,
};
use rustc_session::diagnostics::feature_err;
use rustc_span::{Span, Symbol, edition, sym};
Expand All @@ -19,14 +19,15 @@ use crate::diagnostics::{
DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues,
DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocTestLiteral, DocTestTakesList,
DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins,
DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, MalformedDoc,
DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput,
InvalidNotableTraitAttr, MalformedDoc,
};
use crate::parser::{
ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
};
use crate::session_diagnostics::{
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel,
DocAttributeNotAttribute, DocKeywordNotKeyword, UnusedDuplicate,
DocAttrTraitLevel, DocAttributeNotAttribute, DocKeywordNotKeyword, UnusedDuplicate,
};

fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {
Expand Down Expand Up @@ -98,6 +99,110 @@ fn expected_string_literal(
cx.emit_lint(rustc_session::lint::builtin::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,
Expand Down Expand Up @@ -610,7 +715,13 @@ impl DocParser {
Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
Some(sym::masked) => no_args!(masked),
Some(sym::cfg) => self.parse_cfg(cx, args),
Some(sym::notable_trait) => no_args!(notable_trait),
Some(sym::notable_trait) => parse_notable_trait(
cx,
path,
args,
&mut self.attribute.notable_trait,
sym::notable_trait,
),
Some(sym::keyword) => parse_keyword_and_attribute(
cx,
path,
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,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
11 changes: 11 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ pub(crate) struct DocAttrNotCrateLevel {
pub attr_name: 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 DocAttrTraitLevel {
#[primary_span]
pub span: Span,
pub attr_name: Symbol,
}

#[derive(Diagnostic)]
#[diag("nonexistent keyword `{$keyword}` used in `#[doc(keyword = \"...\")]`")]
#[help("only existing keywords are allowed in core/std")]
Expand Down
37 changes: 36 additions & 1 deletion compiler/rustc_hir/src/attrs/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,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 @@ -613,6 +613,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 Into<&'static str> for NotableTraitColor {

@ThierryBerger ThierryBerger Aug 3, 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.

Should we prefer From ?

Also, should we consider having the opposite direction ? Simplifying the parsing code and grouping similar behaviour in here.

View changes since the review

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(<NotableTraitColor as Into<&str>>::into(*self), f)
Comment thread
notriddle marked this conversation as resolved.
Outdated
}
}

impl<E: rustc_span::SpanEncoder> rustc_serialize::Encodable<E> for DocAttribute {
fn encode(&self, encoder: &mut E) {
let DocAttribute {
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 @@ -60,7 +60,7 @@ use rustc_data_structures::svh::Svh;
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::{ErrorGuaranteed, catch_fatal_errors};
use rustc_hir as hir;
use rustc_hir::attrs::{CanonicalSymbols, EiiDecl, EiiImpl, StrippedCfgItem};
use rustc_hir::attrs::{CanonicalSymbols, EiiDecl, EiiImpl, NotableTraitColor, StrippedCfgItem};
use rustc_hir::def::{DefKind, DocLinkResMap};
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalDefIdSet, LocalModId};
use rustc_hir::lang_items::{LangItem, LanguageItems};
Expand Down Expand Up @@ -1504,8 +1504,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

@ThierryBerger ThierryBerger Aug 3, 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.

Is it a pill or a badge ? We should have consistent naming

View changes since the review

/// 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 @@ -10,6 +10,7 @@ use rustc_data_structures::stable_hash::{StableHash, StableHasher};
use rustc_data_structures::stack::ensure_sufficient_stack;
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 @@ -1700,8 +1701,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 @@ -1731,7 +1739,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 @@ -641,6 +641,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 @@ -82,7 +82,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")]
pub trait Read {
/// Pull some bytes from this source into the specified buffer, returning
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 = "blue"))]
#[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 = "grey"))]
#[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 = "blue"))]
#[lang = "iterator"]
#[rustc_diagnostic_item = "Iterator"]
#[must_use = "iterators are lazy and do nothing unless consumed"]
Expand Down
Loading
Loading