rustdoc: show tuple impls as `impl Trait for (T, ...)`

This commit adds a new unstable attribute, `#[doc(tuple_varadic)]`, that
shows a 1-tuple as `(T, ...)` instead of just `(T,)`, and links to a section
in the tuple primitive docs that talks about these.
This commit is contained in:
Michael Howell 2022-06-08 19:26:51 -07:00
parent 7a93567005
commit 6950f144cf
15 changed files with 111 additions and 44 deletions

View File

@ -401,6 +401,11 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
let msg = "`#[doc(keyword)]` is meant for internal use only";
gate_feature_post!(self, rustdoc_internals, attr.span, msg);
}
if nested_meta.has_name(sym::tuple_varadic) {
let msg = "`#[doc(tuple_varadic)]` is meant for internal use only";
gate_feature_post!(self, rustdoc_internals, attr.span, msg);
}
}
}

View File

@ -804,6 +804,37 @@ impl CheckAttrVisitor<'_> {
true
}
fn check_doc_tuple_varadic(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool {
match self.tcx.hir().find(hir_id).and_then(|node| match node {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
}) {
Some(ItemKind::Impl(ref i)) => {
if !matches!(&i.self_ty.kind, hir::TyKind::Tup([_])) {
self.tcx
.sess
.struct_span_err(
meta.span(),
"`#[doc(tuple_varadic)]` can only be used on unary tuples",
)
.emit();
return false;
}
}
_ => {
self.tcx
.sess
.struct_span_err(
meta.span(),
"`#[doc(keyword = \"...\")]` can only be used on impl blocks",
)
.emit();
return false;
}
}
true
}
/// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid.
///
/// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
@ -1064,6 +1095,13 @@ impl CheckAttrVisitor<'_> {
is_valid = false
}
sym::tuple_varadic
if !self.check_attr_not_crate_level(meta, hir_id, "tuple_varadic")
|| !self.check_doc_tuple_varadic(meta, hir_id) =>
{
is_valid = false
}
sym::html_favicon_url
| sym::html_logo_url
| sym::html_playground_url
@ -1117,7 +1155,8 @@ impl CheckAttrVisitor<'_> {
| sym::no_inline
| sym::notable_trait
| sym::passes
| sym::plugins => {}
| sym::plugins
| sym::tuple_varadic => {}
sym::test => {
if !self.check_test_attr(meta, hir_id) {

View File

@ -1439,6 +1439,7 @@ symbols! {
tuple,
tuple_from_req,
tuple_indexing,
tuple_varadic,
two_phase,
ty,
type_alias_enum_variants,

View File

@ -2335,6 +2335,7 @@ macro_rules! tuple {
macro_rules! maybe_tuple_doc {
($a:ident @ #[$meta:meta] $item:item) => {
#[cfg_attr(not(bootstrap), doc(tuple_varadic))]
#[doc = "This trait is implemented for tuples up to twelve items long."]
#[$meta]
$item

View File

@ -900,6 +900,7 @@ mod impls {
macro_rules! maybe_tuple_doc {
($a:ident @ #[$meta:meta] $item:item) => {
#[cfg_attr(not(bootstrap), doc(tuple_varadic))]
#[doc = "This trait is implemented for tuples up to twelve items long."]
#[$meta]
$item

View File

@ -916,24 +916,11 @@ mod prim_str {}
///
/// # Trait implementations
///
/// If every type inside a tuple implements one of the following traits, then a
/// tuple itself also implements it.
///
/// * [`Clone`]
/// * [`Copy`]
/// * [`PartialEq`]
/// * [`Eq`]
/// * [`PartialOrd`]
/// * [`Ord`]
/// * [`Debug`]
/// * [`Default`]
/// * [`Hash`]
///
/// [`Debug`]: fmt::Debug
/// [`Hash`]: hash::Hash
///
/// Due to a temporary restriction in Rust's type system, these traits are only
/// implemented on tuples of arity 12 or less. In the future, this may change.
/// In this documentation the shorthand `(T, ...)` is used to represent all
/// tuples up to length twelve. When that is used, any trait bounds expressed
/// on `T` applies to each field of the tuple independently. Note that this is
/// a convenience notation to avoid repetitive documentation, not valid
/// Rust syntax.
///
/// # Examples
///
@ -978,6 +965,7 @@ impl<T, U> (T, U) {}
// Fake impl that's only really used for docs.
#[cfg(doc)]
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(tuple_varadic)]
/// This trait is implemented on arbitrary-length tuples.
impl<T: Clone> Clone for (T,) {
fn clone(&self) -> Self {
@ -988,6 +976,7 @@ impl<T: Clone> Clone for (T,) {
// Fake impl that's only really used for docs.
#[cfg(doc)]
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(tuple_varadic)]
/// This trait is implemented on arbitrary-length tuples.
impl<T: Copy> Copy for (T,) {
// empty

View File

@ -105,6 +105,7 @@ macro_rules! tuple_impls {
macro_rules! maybe_tuple_doc {
($a:ident @ #[$meta:meta] $item:item) => {
#[cfg_attr(not(bootstrap), doc(tuple_varadic))]
#[doc = "This trait is implemented for tuples up to twelve items long."]
#[$meta]
$item

View File

@ -916,24 +916,11 @@ mod prim_str {}
///
/// # Trait implementations
///
/// If every type inside a tuple implements one of the following traits, then a
/// tuple itself also implements it.
///
/// * [`Clone`]
/// * [`Copy`]
/// * [`PartialEq`]
/// * [`Eq`]
/// * [`PartialOrd`]
/// * [`Ord`]
/// * [`Debug`]
/// * [`Default`]
/// * [`Hash`]
///
/// [`Debug`]: fmt::Debug
/// [`Hash`]: hash::Hash
///
/// Due to a temporary restriction in Rust's type system, these traits are only
/// implemented on tuples of arity 12 or less. In the future, this may change.
/// In this documentation the shorthand `(T, ...)` is used to represent all
/// tuples up to length twelve. When that is used, any trait bounds expressed
/// on `T` applies to each field of the tuple independently. Note that this is
/// a convenience notation to avoid repetitive documentation, not valid
/// Rust syntax.
///
/// # Examples
///
@ -978,6 +965,7 @@ impl<T, U> (T, U) {}
// Fake impl that's only really used for docs.
#[cfg(doc)]
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(tuple_varadic)]
/// This trait is implemented on arbitrary-length tuples.
impl<T: Clone> Clone for (T,) {
fn clone(&self) -> Self {
@ -988,6 +976,7 @@ impl<T: Clone> Clone for (T,) {
// Fake impl that's only really used for docs.
#[cfg(doc)]
#[stable(feature = "rust1", since = "1.0.0")]
#[doc(tuple_varadic)]
/// This trait is implemented on arbitrary-length tuples.
impl<T: Copy> Copy for (T,) {
// empty

View File

@ -500,7 +500,11 @@ pub(crate) fn build_impl(
for_,
items: trait_items,
polarity,
kind: ImplKind::Normal,
kind: if utils::has_doc_flag(tcx, did, sym::tuple_varadic) {
ImplKind::TupleVaradic
} else {
ImplKind::Normal
},
}),
box merged_attrs,
cx,

View File

@ -1999,7 +1999,11 @@ fn clean_impl<'tcx>(
for_,
items,
polarity: tcx.impl_polarity(def_id),
kind: ImplKind::Normal,
kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), sym::tuple_varadic) {
ImplKind::TupleVaradic
} else {
ImplKind::Normal
},
});
Item::from_hir_id_and_parts(hir_id, None, kind, cx)
};

View File

@ -2394,6 +2394,7 @@ impl Impl {
pub(crate) enum ImplKind {
Normal,
Auto,
TupleVaradic,
Blanket(Box<Type>),
}
@ -2406,6 +2407,10 @@ impl ImplKind {
matches!(self, ImplKind::Blanket(_))
}
pub(crate) fn is_tuple_varadic(&self) -> bool {
matches!(self, ImplKind::TupleVaradic)
}
pub(crate) fn as_blanket_ty(&self) -> Option<&Type> {
match self {
ImplKind::Blanket(ty) => Some(ty),

View File

@ -713,6 +713,16 @@ fn primitive_link(
prim: clean::PrimitiveType,
name: &str,
cx: &Context<'_>,
) -> fmt::Result {
primitive_link_fragment(f, prim, name, "", cx)
}
fn primitive_link_fragment(
f: &mut fmt::Formatter<'_>,
prim: clean::PrimitiveType,
name: &str,
fragment: &str,
cx: &Context<'_>,
) -> fmt::Result {
let m = &cx.cache();
let mut needs_termination = false;
@ -723,7 +733,7 @@ fn primitive_link(
let len = if len == 0 { 0 } else { len - 1 };
write!(
f,
"<a class=\"primitive\" href=\"{}primitive.{}.html\">",
"<a class=\"primitive\" href=\"{}primitive.{}.html{fragment}\">",
"../".repeat(len),
prim.as_sym()
)?;
@ -754,7 +764,7 @@ fn primitive_link(
};
if let Some(mut loc) = loc {
loc.push_fmt(format_args!("primitive.{}.html", prim.as_sym()));
write!(f, "<a class=\"primitive\" href=\"{}\">", loc.finish())?;
write!(f, "<a class=\"primitive\" href=\"{}{fragment}\">", loc.finish())?;
needs_termination = true;
}
}
@ -1064,7 +1074,11 @@ impl clean::Impl {
write!(f, " for ")?;
}
if let Some(ty) = self.kind.as_blanket_ty() {
if let clean::Type::Tuple(types) = &self.for_ &&
let [clean::Type::Generic(name)] = &types[..] &&
(self.kind.is_tuple_varadic() || self.kind.is_auto()) {
primitive_link_fragment(f, PrimitiveType::Tuple, &format!("({name}, ...)"), "#trait-implementations-1", cx)?;
} else if let Some(ty) = self.kind.as_blanket_ty() {
fmt_type(ty, f, use_absolute, cx)?;
} else {
fmt_type(&self.for_, f, use_absolute, cx)?;

View File

@ -552,7 +552,7 @@ impl FromWithTcx<clean::Impl> for Impl {
let trait_ = trait_.map(|path| clean::Type::Path { path }.into_tcx(tcx));
// FIXME: use something like ImplKind in JSON?
let (synthetic, blanket_impl) = match kind {
clean::ImplKind::Normal => (false, None),
clean::ImplKind::Normal | clean::ImplKind::TupleVaradic => (false, None),
clean::ImplKind::Auto => (true, None),
clean::ImplKind::Blanket(ty) => (false, Some(*ty)),
};

View File

@ -2,4 +2,9 @@
/// wonderful
mod foo {}
trait Mine {}
#[doc(tuple_varadic)] //~ ERROR: `#[doc(tuple_varadic)]` is meant for internal use only
impl<T> Mine for (T,) {}
fn main() {}

View File

@ -7,6 +7,15 @@ LL | #[doc(keyword = "match")]
= note: see issue #90418 <https://github.com/rust-lang/rust/issues/90418> for more information
= help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable
error: aborting due to previous error
error[E0658]: `#[doc(tuple_varadic)]` is meant for internal use only
--> $DIR/feature-gate-rustdoc_internals.rs:7:1
|
LL | #[doc(tuple_varadic)]
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #90418 <https://github.com/rust-lang/rust/issues/90418> for more information
= help: add `#![feature(rustdoc_internals)]` to the crate attributes to enable
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.