diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index fd0070bd9c529..81e0531bf012a 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -171,6 +171,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { auto_cfg => doc_cfg masked => doc_masked notable_trait => doc_notable_trait + label_trait => doc_label_trait } "meant for internal use only" { attribute => rustdoc_internals diff --git a/compiler/rustc_attr_parsing/src/attributes/doc.rs b/compiler/rustc_attr_parsing/src/attributes/doc.rs index 42024a4426683..111924136215a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/doc.rs +++ b/compiler/rustc_attr_parsing/src/attributes/doc.rs @@ -505,6 +505,7 @@ impl DocParser { match path.word_sym() { Some(sym::alias) => self.parse_alias(cx, path, args), Some(sym::hidden) => no_args!(hidden), + Some(sym::label_trait) => no_args!(label_trait), Some(sym::html_favicon_url) => string_arg_and_crate_level!(html_favicon_url), Some(sym::html_logo_url) => string_arg_and_crate_level!(html_logo_url), Some(sym::html_no_source) => no_args_and_crate_level!(html_no_source), @@ -690,6 +691,7 @@ impl AttributeParser for DocParser { "masked", "cfg", "notable_trait", + "label_trait", "keyword", "fake_variadic", "search_unbox", diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index be98544d89ecd..fb33064acd7d1 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -313,6 +313,8 @@ declare_features! ( (unstable, box_patterns, "1.0.0", Some(29641)), /// Allows builtin # foo() syntax (internal, builtin_syntax, "1.71.0", Some(110680)), + /// Allows `#[doc(label_trait)]`. + (unstable, doc_label_trait, "CURRENT_RUSTC_VERSION", Some(156865)), /// Allows `#[doc(notable_trait)]`. /// Renamed from `doc_spotlight`. (unstable, doc_notable_trait, "1.52.0", Some(45040)), diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 4ff56a640c19e..5e1f1f422d666 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -528,6 +528,7 @@ pub struct DocAttribute { pub aliases: FxIndexMap, pub hidden: Option, + pub label_trait: Option, // Because we need to emit the error if there is more than one `inline` attribute on an item // at the same time as the other doc attributes, we store a list instead of using `Option`. pub inline: ThinVec<(DocInline, Span)>, @@ -566,6 +567,7 @@ impl rustc_serialize::Encodable for DocAttribute first_span, aliases, hidden, + label_trait, inline, cfg, auto_cfg, @@ -589,6 +591,7 @@ impl rustc_serialize::Encodable for DocAttribute rustc_serialize::Encodable::::encode(first_span, encoder); rustc_serialize::Encodable::::encode(aliases, encoder); rustc_serialize::Encodable::::encode(hidden, encoder); + rustc_serialize::Encodable::::encode(label_trait, encoder); // FIXME: The `doc(inline)` attribute is never encoded, but is it actually the right thing // to do? I suspect the condition was broken, should maybe instead not encode anything if we diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 1e0abdcc196fa..44c2821e31612 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -1486,6 +1486,11 @@ rustc_queries! { separate_provide_extern } + /// Determines whether an item is annotated with `#[doc(label_trait)]`. + query is_doc_label_trait(def_id: DefId) -> bool { + desc { "checking whether `{}` is `doc(label_trait)`", tcx.def_path_str(def_id) } + } + /// Determines whether an item is annotated with `#[doc(notable_trait)]`. query is_doc_notable_trait(def_id: DefId) -> bool { 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 9f6d1170a85f1..5b3682953e249 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -1666,6 +1666,11 @@ 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()) } +/// Determines whether an item is annotated with `doc(notable_trait)`. +pub fn is_doc_label_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool { + find_attr!(tcx, def_id, Doc(doc) if doc.label_trait.is_some()) +} + /// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute). /// /// We double check the feature gate here because whether a function may be defined as an intrinsic causes @@ -1693,6 +1698,7 @@ pub fn provide(providers: &mut Providers) { *providers = Providers { reveal_opaque_types_in_bounds, is_doc_hidden, + is_doc_label_trait, is_doc_notable_trait, intrinsic_raw, ..*providers diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 1b162151adfb1..32f98fcf588c3 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -989,6 +989,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // valid pretty much anywhere, not checked here? // FIXME: should we? hidden: _, + // FIXME: valid for traits, should be checked in attr_parsing + label_trait: _, inline, // FIXME: currently unchecked cfg: _, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index fb433aef68cf8..892d9c57913f2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -826,6 +826,7 @@ symbols! { doc_cfg, doc_cfg_hide, doc_keyword, + doc_label_trait, doc_masked, doc_notable_trait, doc_primitive, @@ -1140,6 +1141,7 @@ symbols! { kreg0, label, label_break_value, + label_trait, lahfsahf_target_feature, lang, lang_items, diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index f16f375a5a84b..2dc8fe5885bc8 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -56,6 +56,18 @@ It is also not emitted for foreign items, aliases, extern crates and imports. These features operate by extending the `#[doc]` attribute, and thus can be caught by the compiler and enabled with a `#![feature(...)]` attribute in your crate. +### Making your trait more discoverable + + * Tracking issue: [#156865](https://github.com/rust-lang/rust/issues/156865) + +Important traits can be difficult to discover when lost in the noise. +This `#![feature(doc_label_trait)]` allows you to tag traits important for your code base. + +The traits with the attribute #![doc(label_trait)]` are rendered with a colored badge at the top of their dedicated page. + +Consider lookint into the `notable_trait` unstable attribure, which help with +discoverability in other ways. + ### Adding your trait to the "Notable traits" dialog * Tracking issue: [#45040](https://github.com/rust-lang/rust/issues/45040) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 7442ec99d90eb..e7d30411c61c2 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2814,6 +2814,7 @@ fn add_without_unwanted_attributes<'hir>( first_span: _, aliases, hidden, + label_trait: _, inline, cfg, auto_cfg: _, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 9c757cf857925..2c3cade8a3a1c 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1368,6 +1368,9 @@ impl Trait { pub(crate) fn is_auto(&self, tcx: TyCtxt<'_>) -> bool { tcx.trait_is_auto(self.def_id) } + pub(crate) fn is_label_trait(&self, tcx: TyCtxt<'_>) -> bool { + tcx.is_doc_label_trait(self.def_id) + } pub(crate) fn is_notable_trait(&self, tcx: TyCtxt<'_>) -> bool { tcx.is_doc_notable_trait(self.def_id) } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index fd6d389542b99..261a06340d37a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -40,7 +40,7 @@ mod type_layout; mod write_shared; use std::borrow::Cow; -use std::collections::VecDeque; +use std::collections::{BTreeMap, VecDeque}; use std::fmt::{self, Display as _, Write}; use std::iter::Peekable; use std::path::PathBuf; @@ -1797,6 +1797,47 @@ fn notable_traits_json<'a>(tys: impl Iterator, cx: &Cont serde_json::to_string(&mp).expect("serialize (string, string) -> json object cannot fail") } +pub(crate) struct LabelTraitInfo { + pub name: String, + pub full_path: String, + /// Relative URL to the trait page, or `None` if it cannot be linked. + pub href: Option, +} + +/// Returns all `#[doc(label_trait)]` traits that `item` implements. +pub(crate) fn label_traits_for_item(item: &clean::Item, cx: &Context<'_>) -> Vec { + let Some(did) = item.def_id() else { return Vec::new() }; + + if Some(did) == cx.tcx().lang_items().owned_box() + || Some(did) == cx.tcx().lang_items().pin_type() + { + return Vec::new(); + } + + let Some(impls) = cx.cache().impls.get(&did) else { return Vec::new() }; + + impls + .iter() + .map(Impl::inner_impl) + .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) + .filter_map(|impl_| { + let path_ = impl_.trait_.as_ref()?; + let trait_did = path_.def_id(); + if !cx.cache().traits.get(&trait_did)?.is_label_trait(cx.tcx()) { + return None; + } + let name = cx.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(_) => (cx.tcx().def_path_str(trait_did), None), + }; + Some((name.clone(), LabelTraitInfo { name, full_path, href })) + }) + .collect::>() + .into_values() + .collect() +} + #[derive(Clone, Copy, Debug)] struct ImplRenderingParameters { show_def_docs: bool, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 8385e691ed385..6fb026c3a817d 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,5 +1,7 @@ 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; @@ -37,7 +39,7 @@ use crate::html::format::{ }; use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; use crate::html::render::sidebar::filters; -use crate::html::render::{document_full, document_item_info}; +use crate::html::render::{document_full, document_item_info, label_traits_for_item}; use crate::html::url_parts_builder::UrlPartsBuilder; const ITEM_TABLE_OPEN: &str = "
"; @@ -50,6 +52,15 @@ struct PathComponent { name: Symbol, } +struct LabelTraitVars { + name: String, + full_path: String, + /// Relative URL to the trait page, or empty when not linkable. + href: String, + /// Pre-rendered `style="..."` attribute. + style_attr: String, +} + #[derive(Template)] #[template(path = "print_item.html")] struct ItemVars<'a> { @@ -58,6 +69,7 @@ struct ItemVars<'a> { item_type: &'a str, path_components: Vec, stability_since_raw: &'a str, + impl_label_traits: Vec, src_href: Option<&'a str>, } @@ -111,6 +123,30 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp let src_href = if cx.info.include_sources && !item.is_primitive() { cx.src_href(item) } else { None }; + let impl_label_traits: Vec = label_traits_for_item(item, cx) + .into_iter() + .map(|info| { + // Stable per-trait color from a hash of the DefId 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); + let v = h.finish(); + let style_attr = format!( + "style=\"background: rgb({}, {}, {})\"", + v as u8, + (v >> 8) as u8, + (v >> 16) as u8, + ); + LabelTraitVars { + name: info.name, + full_path: info.full_path, + href: info.href.unwrap_or_default(), + style_attr, + } + }) + .collect(); + let path_components = if item.is_fake_item() { vec![] } else { @@ -134,6 +170,7 @@ pub(super) fn print_item(cx: &Context<'_>, item: &clean::Item) -> impl fmt::Disp item_type: &item.type_().to_string(), path_components, stability_since_raw: &stability_since_raw, + impl_label_traits, src_href: src_href.as_deref(), }; diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index da34deeefb197..72b9f8d367dea 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1644,6 +1644,25 @@ so that we can apply CSS-filters to change the arrow color in themes */ font-size: initial; } +.impl-label-trait-full-badge-container { + padding: 0.5rem 0; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.impl-label-trait-full-badge { + display: flex; + align-items: center; + width: fit-content; + height: 1.5rem; + padding: 0 0.5rem; + border-radius: 0.75rem; + font-size: 1rem; + font-weight: normal; + color: white; +} + .rightside { padding-left: 12px; float: right; diff --git a/src/librustdoc/html/templates/print_item.html b/src/librustdoc/html/templates/print_item.html index 640fd3dfee498..306c13fbb5cf5 100644 --- a/src/librustdoc/html/templates/print_item.html +++ b/src/librustdoc/html/templates/print_item.html @@ -3,7 +3,7 @@
{% for (i, component) in path_components.iter().enumerate() %} {% if i != 0 %} - :: + :: {% endif %} {{component.name}} {% endfor %} @@ -11,7 +11,7 @@ {% endif %}

{{typ}} - + {{name|wrapped|safe}}  {# #}