Rollup merge of #121160 - fmease:rustdoc-fix-n-refactor-html-rendering, r=GuillaumeGomez

rustdoc: fix and refactor HTML rendering a bit

* refactoring: get rid of a bunch of manual `f.alternate()` branches
  * not sure why this wasn't done so already, is this perf-sensitive?
* fix an ICE in debug builds of rustdoc
  * rustdoc used to crash on empty outlives-bounds: `where 'a:`
* properly escape const generic defaults
* actually print empty trait and outlives-bounds (doesn't work for cross-crate reexports yet, will fix that at some other point) since they can have semantic significance
  * outlives-bounds: forces lifetime params to be early-bound instead of late-bound which is technically speaking part of the public API
  * trait-bounds: can affect the well-formedness, consider
    * makeshift “const-evaluatable” bounds under `generic_const_exprs`
    * bounds to force wf-checking in light of #100041 (quite artificial I know, I couldn't figure out something better), see https://github.com/rust-lang/rust/pull/121160#discussion_r1491563816
This commit is contained in:
León Orell Valerian Liehr 2024-02-18 05:10:17 +01:00 committed by GitHub
commit 28c0fa87bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 186 additions and 282 deletions

View File

@ -1,6 +1,6 @@
//! HTML formatting module //! HTML formatting module
//! //!
//! This module contains a large number of `fmt::Display` implementations for //! This module contains a large number of `Display` implementations for
//! various types in `rustdoc::clean`. //! various types in `rustdoc::clean`.
//! //!
//! These implementations all emit HTML. As an internal implementation detail, //! These implementations all emit HTML. As an internal implementation detail,
@ -9,7 +9,7 @@
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::Cell; use std::cell::Cell;
use std::fmt::{self, Write}; use std::fmt::{self, Display, Write};
use std::iter::{self, once}; use std::iter::{self, once};
use rustc_ast as ast; use rustc_ast as ast;
@ -150,16 +150,16 @@ pub(crate) fn len(&self) -> usize {
} }
} }
pub(crate) fn comma_sep<T: fmt::Display>( pub(crate) fn comma_sep<T: Display>(
items: impl Iterator<Item = T>, items: impl Iterator<Item = T>,
space_after_comma: bool, space_after_comma: bool,
) -> impl fmt::Display { ) -> impl Display {
display_fn(move |f| { display_fn(move |f| {
for (i, item) in items.enumerate() { for (i, item) in items.enumerate() {
if i != 0 { if i != 0 {
write!(f, ",{}", if space_after_comma { " " } else { "" })?; write!(f, ",{}", if space_after_comma { " " } else { "" })?;
} }
fmt::Display::fmt(&item, f)?; item.fmt(f)?;
} }
Ok(()) Ok(())
}) })
@ -168,7 +168,7 @@ pub(crate) fn comma_sep<T: fmt::Display>(
pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>( pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>(
bounds: &'a [clean::GenericBound], bounds: &'a [clean::GenericBound],
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
let mut bounds_dup = FxHashSet::default(); let mut bounds_dup = FxHashSet::default();
@ -176,7 +176,7 @@ pub(crate) fn print_generic_bounds<'a, 'tcx: 'a>(
if i > 0 { if i > 0 {
f.write_str(" + ")?; f.write_str(" + ")?;
} }
fmt::Display::fmt(&bound.print(cx), f)?; bound.print(cx).fmt(f)?;
} }
Ok(()) Ok(())
}) })
@ -186,7 +186,7 @@ impl clean::GenericParamDef {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match &self.kind { display_fn(move |f| match &self.kind {
clean::GenericParamDefKind::Lifetime { outlives } => { clean::GenericParamDefKind::Lifetime { outlives } => {
write!(f, "{}", self.name)?; write!(f, "{}", self.name)?;
@ -207,35 +207,27 @@ pub(crate) fn print<'a, 'tcx: 'a>(
f.write_str(self.name.as_str())?; f.write_str(self.name.as_str())?;
if !bounds.is_empty() { if !bounds.is_empty() {
if f.alternate() { f.write_str(": ")?;
write!(f, ": {:#}", print_generic_bounds(bounds, cx))?; print_generic_bounds(bounds, cx).fmt(f)?;
} else {
write!(f, ": {}", print_generic_bounds(bounds, cx))?;
}
} }
if let Some(ref ty) = default { if let Some(ref ty) = default {
if f.alternate() { f.write_str(" = ")?;
write!(f, " = {:#}", ty.print(cx))?; ty.print(cx).fmt(f)?;
} else {
write!(f, " = {}", ty.print(cx))?;
}
} }
Ok(()) Ok(())
} }
clean::GenericParamDefKind::Const { ty, default, .. } => { clean::GenericParamDefKind::Const { ty, default, .. } => {
if f.alternate() { write!(f, "const {}: ", self.name)?;
write!(f, "const {}: {:#}", self.name, ty.print(cx))?; ty.print(cx).fmt(f)?;
} else {
write!(f, "const {}: {}", self.name, ty.print(cx))?;
}
if let Some(default) = default { if let Some(default) = default {
f.write_str(" = ")?;
if f.alternate() { if f.alternate() {
write!(f, " = {default:#}")?; write!(f, "{default}")?;
} else { } else {
write!(f, " = {default}")?; write!(f, "{}", Escape(default))?;
} }
} }
@ -249,7 +241,7 @@ impl clean::Generics {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable();
if real_params.peek().is_none() { if real_params.peek().is_none() {
@ -279,63 +271,50 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
indent: usize, indent: usize,
ending: Ending, ending: Ending,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
let mut where_predicates = gens.where_predicates.iter().filter(|pred| { let mut where_predicates = gens
!matches!(pred, clean::WherePredicate::BoundPredicate { bounds, .. } if bounds.is_empty()) .where_predicates
}).map(|pred| { .iter()
display_fn(move |f| { .map(|pred| {
if f.alternate() { display_fn(move |f| {
f.write_str(" ")?; if f.alternate() {
} else { f.write_str(" ")?;
f.write_str("\n")?; } else {
} f.write_str("\n")?;
}
match pred { match pred {
clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
let ty_cx = ty.print(cx); print_higher_ranked_params_with_space(bound_params, cx).fmt(f)?;
let generic_bounds = print_generic_bounds(bounds, cx); ty.print(cx).fmt(f)?;
f.write_str(":")?;
if bound_params.is_empty() { if !bounds.is_empty() {
if f.alternate() { f.write_str(" ")?;
write!(f, "{ty_cx:#}: {generic_bounds:#}") print_generic_bounds(bounds, cx).fmt(f)?;
} else {
write!(f, "{ty_cx}: {generic_bounds}")
} }
} else { Ok(())
}
clean::WherePredicate::RegionPredicate { lifetime, bounds } => {
// We don't need to check `alternate` since we can be certain that neither
// the lifetime nor the bounds contain any characters which need escaping.
write!(f, "{}:", lifetime.print())?;
if !bounds.is_empty() {
write!(f, " {}", print_generic_bounds(bounds, cx))?;
}
Ok(())
}
clean::WherePredicate::EqPredicate { lhs, rhs } => {
if f.alternate() { if f.alternate() {
write!( write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
f,
"for<{:#}> {ty_cx:#}: {generic_bounds:#}",
comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
)
} else { } else {
write!( write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
f,
"for&lt;{}&gt; {ty_cx}: {generic_bounds}",
comma_sep(bound_params.iter().map(|lt| lt.print(cx)), true)
)
} }
} }
} }
clean::WherePredicate::RegionPredicate { lifetime, bounds } => { })
let mut bounds_display = String::new();
for bound in bounds.iter().map(|b| b.print(cx)) {
write!(bounds_display, "{bound} + ")?;
}
bounds_display.truncate(bounds_display.len() - " + ".len());
write!(f, "{}: {bounds_display}", lifetime.print())
}
clean::WherePredicate::EqPredicate { lhs, rhs } => {
if f.alternate() {
write!(f, "{:#} == {:#}", lhs.print(cx), rhs.print(cx))
} else {
write!(f, "{} == {}", lhs.print(cx), rhs.print(cx))
}
}
}
}) })
}).peekable(); .peekable();
if where_predicates.peek().is_none() { if where_predicates.peek().is_none() {
return Ok(()); return Ok(());
@ -392,13 +371,13 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>(
} }
impl clean::Lifetime { impl clean::Lifetime {
pub(crate) fn print(&self) -> impl fmt::Display + '_ { pub(crate) fn print(&self) -> impl Display + '_ {
self.0.as_str() self.0.as_str()
} }
} }
impl clean::Constant { impl clean::Constant {
pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl fmt::Display + '_ { pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl Display + '_ {
let expr = self.expr(tcx); let expr = self.expr(tcx);
display_fn( display_fn(
move |f| { move |f| {
@ -409,31 +388,10 @@ pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl fmt::Display + '_ {
} }
impl clean::PolyTrait { impl clean::PolyTrait {
fn print<'a, 'tcx: 'a>( fn print<'a, 'tcx: 'a>(&'a self, cx: &'a Context<'tcx>) -> impl Display + 'a + Captures<'tcx> {
&'a self,
cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
if !self.generic_params.is_empty() { print_higher_ranked_params_with_space(&self.generic_params, cx).fmt(f)?;
if f.alternate() { self.trait_.print(cx).fmt(f)
write!(
f,
"for<{:#}> ",
comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true)
)?;
} else {
write!(
f,
"for&lt;{}&gt; ",
comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true)
)?;
}
}
if f.alternate() {
write!(f, "{:#}", self.trait_.print(cx))
} else {
write!(f, "{}", self.trait_.print(cx))
}
}) })
} }
} }
@ -442,32 +400,25 @@ impl clean::GenericBound {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match self { display_fn(move |f| match self {
clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()), clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()),
clean::GenericBound::TraitBound(ty, modifier) => { clean::GenericBound::TraitBound(ty, modifier) => {
let modifier_str = match modifier { f.write_str(match modifier {
hir::TraitBoundModifier::None => "", hir::TraitBoundModifier::None => "",
hir::TraitBoundModifier::Maybe => "?", hir::TraitBoundModifier::Maybe => "?",
hir::TraitBoundModifier::Negative => "!", hir::TraitBoundModifier::Negative => "!",
// `const` and `~const` trait bounds are experimental; don't render them. // `const` and `~const` trait bounds are experimental; don't render them.
hir::TraitBoundModifier::Const | hir::TraitBoundModifier::MaybeConst => "", hir::TraitBoundModifier::Const | hir::TraitBoundModifier::MaybeConst => "",
}; })?;
if f.alternate() { ty.print(cx).fmt(f)
write!(f, "{modifier_str}{ty:#}", ty = ty.print(cx))
} else {
write!(f, "{modifier_str}{ty}", ty = ty.print(cx))
}
} }
}) })
} }
} }
impl clean::GenericArgs { impl clean::GenericArgs {
fn print<'a, 'tcx: 'a>( fn print<'a, 'tcx: 'a>(&'a self, cx: &'a Context<'tcx>) -> impl Display + 'a + Captures<'tcx> {
&'a self,
cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
match self { match self {
clean::GenericArgs::AngleBracketed { args, bindings } => { clean::GenericArgs::AngleBracketed { args, bindings } => {
@ -515,11 +466,7 @@ fn print<'a, 'tcx: 'a>(
f.write_str(", ")?; f.write_str(", ")?;
} }
comma = true; comma = true;
if f.alternate() { ty.print(cx).fmt(f)?;
write!(f, "{:#}", ty.print(cx))?;
} else {
write!(f, "{}", ty.print(cx))?;
}
} }
f.write_str(")")?; f.write_str(")")?;
if let Some(ref ty) = *output { if let Some(ref ty) = *output {
@ -973,31 +920,43 @@ fn primitive_link_fragment(
None => {} None => {}
} }
} }
std::fmt::Display::fmt(&name, f)?; Display::fmt(&name, f)?;
if needs_termination { if needs_termination {
write!(f, "</a>")?; write!(f, "</a>")?;
} }
Ok(()) Ok(())
} }
/// Helper to render type parameters
fn tybounds<'a, 'tcx: 'a>( fn tybounds<'a, 'tcx: 'a>(
bounds: &'a [clean::PolyTrait], bounds: &'a [clean::PolyTrait],
lt: &'a Option<clean::Lifetime>, lt: &'a Option<clean::Lifetime>,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
for (i, bound) in bounds.iter().enumerate() { for (i, bound) in bounds.iter().enumerate() {
if i > 0 { if i > 0 {
write!(f, " + ")?; write!(f, " + ")?;
} }
bound.print(cx).fmt(f)?;
fmt::Display::fmt(&bound.print(cx), f)?;
} }
if let Some(lt) = lt { if let Some(lt) = lt {
write!(f, " + ")?; // We don't need to check `alternate` since we can be certain that
fmt::Display::fmt(&lt.print(), f)?; // the lifetime doesn't contain any characters which need escaping.
write!(f, " + {}", lt.print())?;
}
Ok(())
})
}
fn print_higher_ranked_params_with_space<'a, 'tcx: 'a>(
params: &'a [clean::GenericParamDef],
cx: &'a Context<'tcx>,
) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| {
if !params.is_empty() {
f.write_str(if f.alternate() { "for<" } else { "for&lt;" })?;
comma_sep(params.iter().map(|lt| lt.print(cx)), true).fmt(f)?;
f.write_str(if f.alternate() { "> " } else { "&gt; " })?;
} }
Ok(()) Ok(())
}) })
@ -1007,7 +966,7 @@ pub(crate) fn anchor<'a, 'cx: 'a>(
did: DefId, did: DefId,
text: Symbol, text: Symbol,
cx: &'cx Context<'_>, cx: &'cx Context<'_>,
) -> impl fmt::Display + 'a { ) -> impl Display + 'a {
let parts = href(did, cx); let parts = href(did, cx);
display_fn(move |f| { display_fn(move |f| {
if let Ok((url, short_ty, fqp)) = parts { if let Ok((url, short_ty, fqp)) = parts {
@ -1039,7 +998,7 @@ fn fmt_type<'cx>(
} }
clean::DynTrait(ref bounds, ref lt) => { clean::DynTrait(ref bounds, ref lt) => {
f.write_str("dyn ")?; f.write_str("dyn ")?;
fmt::Display::fmt(&tybounds(bounds, lt, cx), f) tybounds(bounds, lt, cx).fmt(f)
} }
clean::Infer => write!(f, "_"), clean::Infer => write!(f, "_"),
clean::Primitive(clean::PrimitiveType::Never) => { clean::Primitive(clean::PrimitiveType::Never) => {
@ -1049,80 +1008,62 @@ fn fmt_type<'cx>(
primitive_link(f, prim, format_args!("{}", prim.as_sym().as_str()), cx) primitive_link(f, prim, format_args!("{}", prim.as_sym().as_str()), cx)
} }
clean::BareFunction(ref decl) => { clean::BareFunction(ref decl) => {
print_higher_ranked_params_with_space(&decl.generic_params, cx).fmt(f)?;
decl.unsafety.print_with_space().fmt(f)?;
print_abi_with_space(decl.abi).fmt(f)?;
if f.alternate() { if f.alternate() {
write!( f.write_str("fn")?;
f,
"{:#}{}{:#}fn{:#}",
decl.print_hrtb_with_space(cx),
decl.unsafety.print_with_space(),
print_abi_with_space(decl.abi),
decl.decl.print(cx),
)
} else { } else {
write!(
f,
"{}{}{}",
decl.print_hrtb_with_space(cx),
decl.unsafety.print_with_space(),
print_abi_with_space(decl.abi)
)?;
primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?; primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?;
write!(f, "{}", decl.decl.print(cx))
} }
decl.decl.print(cx).fmt(f)
} }
clean::Tuple(ref typs) => { clean::Tuple(ref typs) => match &typs[..] {
match &typs[..] { &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx),
&[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx), [one] => {
[one] => { if let clean::Generic(name) = one {
if let clean::Generic(name) = one { primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx)
primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx) } else {
} else { write!(f, "(")?;
write!(f, "(")?; one.print(cx).fmt(f)?;
// Carry `f.alternate()` into this display w/o branching manually. write!(f, ",)")
fmt::Display::fmt(&one.print(cx), f)?;
write!(f, ",)")
}
} }
many => { }
let generic_names: Vec<Symbol> = many many => {
.iter() let generic_names: Vec<Symbol> = many
.filter_map(|t| match t { .iter()
clean::Generic(name) => Some(*name), .filter_map(|t| match t {
_ => None, clean::Generic(name) => Some(*name),
}) _ => None,
.collect(); })
let is_generic = generic_names.len() == many.len(); .collect();
if is_generic { let is_generic = generic_names.len() == many.len();
primitive_link( if is_generic {
f, primitive_link(
PrimitiveType::Tuple, f,
format_args!( PrimitiveType::Tuple,
"({})", format_args!("({})", generic_names.iter().map(|s| s.as_str()).join(", ")),
generic_names.iter().map(|s| s.as_str()).join(", ") cx,
), )
cx, } else {
) write!(f, "(")?;
} else { for (i, item) in many.iter().enumerate() {
write!(f, "(")?; if i != 0 {
for (i, item) in many.iter().enumerate() { write!(f, ", ")?;
if i != 0 {
write!(f, ", ")?;
}
// Carry `f.alternate()` into this display w/o branching manually.
fmt::Display::fmt(&item.print(cx), f)?;
} }
write!(f, ")") item.print(cx).fmt(f)?;
} }
write!(f, ")")
} }
} }
} },
clean::Slice(ref t) => match **t { clean::Slice(ref t) => match **t {
clean::Generic(name) => { clean::Generic(name) => {
primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx) primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx)
} }
_ => { _ => {
write!(f, "[")?; write!(f, "[")?;
fmt::Display::fmt(&t.print(cx), f)?; t.print(cx).fmt(f)?;
write!(f, "]") write!(f, "]")
} }
}, },
@ -1135,7 +1076,7 @@ fn fmt_type<'cx>(
), ),
_ => { _ => {
write!(f, "[")?; write!(f, "[")?;
fmt::Display::fmt(&t.print(cx), f)?; t.print(cx).fmt(f)?;
if f.alternate() { if f.alternate() {
write!(f, "; {n}")?; write!(f, "; {n}")?;
} else { } else {
@ -1175,7 +1116,7 @@ fn fmt_type<'cx>(
} }
} else { } else {
primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?; primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?;
fmt::Display::fmt(&t.print(cx), f) t.print(cx).fmt(f)
} }
} }
clean::BorrowedRef { lifetime: ref l, mutability, type_: ref ty } => { clean::BorrowedRef { lifetime: ref l, mutability, type_: ref ty } => {
@ -1216,11 +1157,8 @@ fn fmt_type<'cx>(
Ok(()) Ok(())
} }
clean::ImplTrait(ref bounds) => { clean::ImplTrait(ref bounds) => {
if f.alternate() { f.write_str("impl ")?;
write!(f, "impl {:#}", print_generic_bounds(bounds, cx)) print_generic_bounds(bounds, cx).fmt(f)
} else {
write!(f, "impl {}", print_generic_bounds(bounds, cx))
}
} }
clean::QPath(box clean::QPathData { clean::QPath(box clean::QPathData {
ref assoc, ref assoc,
@ -1292,8 +1230,7 @@ fn fmt_type<'cx>(
write!(f, "{}", assoc.name) write!(f, "{}", assoc.name)
}?; }?;
// Carry `f.alternate()` into this display w/o branching manually. assoc.args.print(cx).fmt(f)
fmt::Display::fmt(&assoc.args.print(cx), f)
} }
} }
} }
@ -1302,7 +1239,7 @@ impl clean::Type {
pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'b + Captures<'tcx> { ) -> impl Display + 'b + Captures<'tcx> {
display_fn(move |f| fmt_type(self, f, false, cx)) display_fn(move |f| fmt_type(self, f, false, cx))
} }
} }
@ -1311,7 +1248,7 @@ impl clean::Path {
pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'b + Captures<'tcx> { ) -> impl Display + 'b + Captures<'tcx> {
display_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx)) display_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx))
} }
} }
@ -1321,20 +1258,18 @@ pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
use_absolute: bool, use_absolute: bool,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
if f.alternate() { f.write_str("impl")?;
write!(f, "impl{:#} ", self.generics.print(cx))?; self.generics.print(cx).fmt(f)?;
} else { f.write_str(" ")?;
write!(f, "impl{} ", self.generics.print(cx))?;
}
if let Some(ref ty) = self.trait_ { if let Some(ref ty) = self.trait_ {
match self.polarity { match self.polarity {
ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => {} ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => {}
ty::ImplPolarity::Negative => write!(f, "!")?, ty::ImplPolarity::Negative => write!(f, "!")?,
} }
fmt::Display::fmt(&ty.print(cx), f)?; ty.print(cx).fmt(f)?;
write!(f, " for ")?; write!(f, " for ")?;
} }
@ -1359,14 +1294,9 @@ pub(crate) fn print<'a, 'tcx: 'a>(
// Hardcoded anchor library/core/src/primitive_docs.rs // Hardcoded anchor library/core/src/primitive_docs.rs
// Link should match `# Trait implementations` // Link should match `# Trait implementations`
let hrtb = bare_fn.print_hrtb_with_space(cx); print_higher_ranked_params_with_space(&bare_fn.generic_params, cx).fmt(f)?;
let unsafety = bare_fn.unsafety.print_with_space(); bare_fn.unsafety.print_with_space().fmt(f)?;
let abi = print_abi_with_space(bare_fn.abi); print_abi_with_space(bare_fn.abi).fmt(f)?;
if f.alternate() {
write!(f, "{hrtb:#}{unsafety}{abi:#}",)?;
} else {
write!(f, "{hrtb}{unsafety}{abi}",)?;
}
let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" }; let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" };
primitive_link_fragment( primitive_link_fragment(
f, f,
@ -1386,8 +1316,7 @@ pub(crate) fn print<'a, 'tcx: 'a>(
fmt_type(&self.for_, f, use_absolute, cx)?; fmt_type(&self.for_, f, use_absolute, cx)?;
} }
fmt::Display::fmt(&print_where_clause(&self.generics, cx, 0, Ending::Newline), f)?; print_where_clause(&self.generics, cx, 0, Ending::Newline).fmt(f)
Ok(())
}) })
} }
} }
@ -1396,16 +1325,11 @@ impl clean::Arguments {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
for (i, input) in self.values.iter().enumerate() { for (i, input) in self.values.iter().enumerate() {
write!(f, "{}: ", input.name)?; write!(f, "{}: ", input.name)?;
input.type_.print(cx).fmt(f)?;
if f.alternate() {
write!(f, "{:#}", input.type_.print(cx))?;
} else {
write!(f, "{}", input.type_.print(cx))?;
}
if i + 1 < self.values.len() { if i + 1 < self.values.len() {
write!(f, ", ")?; write!(f, ", ")?;
} }
@ -1415,25 +1339,6 @@ pub(crate) fn print<'a, 'tcx: 'a>(
} }
} }
impl clean::BareFunctionDecl {
fn print_hrtb_with_space<'a, 'tcx: 'a>(
&'a self,
cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> {
display_fn(move |f| {
if !self.generic_params.is_empty() {
write!(
f,
"for&lt;{}&gt; ",
comma_sep(self.generic_params.iter().map(|g| g.print(cx)), true)
)
} else {
Ok(())
}
})
}
}
// Implements Write but only counts the bytes "written". // Implements Write but only counts the bytes "written".
struct WriteCounter(usize); struct WriteCounter(usize);
@ -1447,7 +1352,7 @@ fn write_str(&mut self, s: &str) -> fmt::Result {
// Implements Display by emitting the given number of spaces. // Implements Display by emitting the given number of spaces.
struct Indent(usize); struct Indent(usize);
impl fmt::Display for Indent { impl Display for Indent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(0..self.0).for_each(|_| { (0..self.0).for_each(|_| {
f.write_char(' ').unwrap(); f.write_char(' ').unwrap();
@ -1460,7 +1365,7 @@ impl clean::FnDecl {
pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>( pub(crate) fn print<'b, 'a: 'b, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'b + Captures<'tcx> { ) -> impl Display + 'b + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
let ellipsis = if self.c_variadic { ", ..." } else { "" }; let ellipsis = if self.c_variadic { ", ..." } else { "" };
if f.alternate() { if f.alternate() {
@ -1494,7 +1399,7 @@ pub(crate) fn full_print<'a, 'tcx: 'a>(
header_len: usize, header_len: usize,
indent: usize, indent: usize,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
// First, generate the text form of the declaration, with no line wrapping, and count the bytes. // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
let mut counter = WriteCounter(0); let mut counter = WriteCounter(0);
@ -1554,7 +1459,7 @@ fn inner_full_print(
} }
clean::SelfExplicit(ref typ) => { clean::SelfExplicit(ref typ) => {
write!(f, "self: ")?; write!(f, "self: ")?;
fmt::Display::fmt(&typ.print(cx), f)?; typ.print(cx).fmt(f)?;
} }
} }
} else { } else {
@ -1562,7 +1467,7 @@ fn inner_full_print(
write!(f, "const ")?; write!(f, "const ")?;
} }
write!(f, "{}: ", input.name)?; write!(f, "{}: ", input.name)?;
fmt::Display::fmt(&input.type_.print(cx), f)?; input.type_.print(cx).fmt(f)?;
} }
} }
@ -1578,14 +1483,13 @@ fn inner_full_print(
Some(n) => write!(f, "\n{})", Indent(n))?, Some(n) => write!(f, "\n{})", Indent(n))?,
}; };
fmt::Display::fmt(&self.print_output(cx), f)?; self.print_output(cx).fmt(f)
Ok(())
} }
fn print_output<'a, 'tcx: 'a>( fn print_output<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match &self.output { display_fn(move |f| match &self.output {
clean::Tuple(tys) if tys.is_empty() => Ok(()), clean::Tuple(tys) if tys.is_empty() => Ok(()),
ty if f.alternate() => { ty if f.alternate() => {
@ -1600,7 +1504,7 @@ pub(crate) fn visibility_print_with_space<'a, 'tcx: 'a>(
visibility: Option<ty::Visibility<DefId>>, visibility: Option<ty::Visibility<DefId>>,
item_did: ItemId, item_did: ItemId,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
use std::fmt::Write as _; use std::fmt::Write as _;
let to_print: Cow<'static, str> = match visibility { let to_print: Cow<'static, str> = match visibility {
@ -1648,7 +1552,7 @@ pub(crate) fn visibility_to_src_with_space<'a, 'tcx: 'a>(
visibility: Option<ty::Visibility<DefId>>, visibility: Option<ty::Visibility<DefId>>,
tcx: TyCtxt<'tcx>, tcx: TyCtxt<'tcx>,
item_did: DefId, item_did: DefId,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
let to_print: Cow<'static, str> = match visibility { let to_print: Cow<'static, str> = match visibility {
None => "".into(), None => "".into(),
Some(ty::Visibility::Public) => "pub ".into(), Some(ty::Visibility::Public) => "pub ".into(),
@ -1727,7 +1631,7 @@ impl clean::Import {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match self.kind { display_fn(move |f| match self.kind {
clean::ImportKind::Simple(name) => { clean::ImportKind::Simple(name) => {
if name == self.source.path.last() { if name == self.source.path.last() {
@ -1751,7 +1655,7 @@ impl clean::ImportSource {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match self.did { display_fn(move |f| match self.did {
Some(did) => resolved_path(f, did, &self.path, true, false, cx), Some(did) => resolved_path(f, did, &self.path, true, false, cx),
_ => { _ => {
@ -1779,29 +1683,19 @@ impl clean::TypeBinding {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| { display_fn(move |f| {
f.write_str(self.assoc.name.as_str())?; f.write_str(self.assoc.name.as_str())?;
if f.alternate() { self.assoc.args.print(cx).fmt(f)?;
write!(f, "{:#}", self.assoc.args.print(cx))?;
} else {
write!(f, "{}", self.assoc.args.print(cx))?;
}
match self.kind { match self.kind {
clean::TypeBindingKind::Equality { ref term } => { clean::TypeBindingKind::Equality { ref term } => {
if f.alternate() { f.write_str(" = ")?;
write!(f, " = {:#}", term.print(cx))?; term.print(cx).fmt(f)?;
} else {
write!(f, " = {}", term.print(cx))?;
}
} }
clean::TypeBindingKind::Constraint { ref bounds } => { clean::TypeBindingKind::Constraint { ref bounds } => {
if !bounds.is_empty() { if !bounds.is_empty() {
if f.alternate() { f.write_str(": ")?;
write!(f, ": {:#}", print_generic_bounds(bounds, cx))?; print_generic_bounds(bounds, cx).fmt(f)?;
} else {
write!(f, ": {}", print_generic_bounds(bounds, cx))?;
}
} }
} }
} }
@ -1810,7 +1704,7 @@ pub(crate) fn print<'a, 'tcx: 'a>(
} }
} }
pub(crate) fn print_abi_with_space(abi: Abi) -> impl fmt::Display { pub(crate) fn print_abi_with_space(abi: Abi) -> impl Display {
display_fn(move |f| { display_fn(move |f| {
let quot = if f.alternate() { "\"" } else { "&quot;" }; let quot = if f.alternate() { "\"" } else { "&quot;" };
match abi { match abi {
@ -1828,34 +1722,32 @@ impl clean::GenericArg {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match self { display_fn(move |f| match self {
clean::GenericArg::Lifetime(lt) => fmt::Display::fmt(&lt.print(), f), clean::GenericArg::Lifetime(lt) => lt.print().fmt(f),
clean::GenericArg::Type(ty) => fmt::Display::fmt(&ty.print(cx), f), clean::GenericArg::Type(ty) => ty.print(cx).fmt(f),
clean::GenericArg::Const(ct) => fmt::Display::fmt(&ct.print(cx.tcx()), f), clean::GenericArg::Const(ct) => ct.print(cx.tcx()).fmt(f),
clean::GenericArg::Infer => fmt::Display::fmt("_", f), clean::GenericArg::Infer => Display::fmt("_", f),
}) })
} }
} }
impl clean::types::Term { impl clean::Term {
pub(crate) fn print<'a, 'tcx: 'a>( pub(crate) fn print<'a, 'tcx: 'a>(
&'a self, &'a self,
cx: &'a Context<'tcx>, cx: &'a Context<'tcx>,
) -> impl fmt::Display + 'a + Captures<'tcx> { ) -> impl Display + 'a + Captures<'tcx> {
display_fn(move |f| match self { display_fn(move |f| match self {
clean::types::Term::Type(ty) => fmt::Display::fmt(&ty.print(cx), f), clean::Term::Type(ty) => ty.print(cx).fmt(f),
clean::types::Term::Constant(ct) => fmt::Display::fmt(&ct.print(cx.tcx()), f), clean::Term::Constant(ct) => ct.print(cx.tcx()).fmt(f),
}) })
} }
} }
pub(crate) fn display_fn( pub(crate) fn display_fn(f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result) -> impl Display {
f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
) -> impl fmt::Display {
struct WithFormatter<F>(Cell<Option<F>>); struct WithFormatter<F>(Cell<Option<F>>);
impl<F> fmt::Display for WithFormatter<F> impl<F> Display for WithFormatter<F>
where where
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result, F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,
{ {

View File

@ -18,3 +18,15 @@ pub trait T2 {
fn f<T: Eq>() fn f<T: Eq>()
where Self: Eq, Self: Eq2, T: Eq2; where Self: Eq, Self: Eq2, T: Eq2;
} }
// Checking that we support empty bounds (we used to crash on empty outlives-bounds).
// Note that we don't want to hide them since they have a semantic effect.
// For outlives-bounds, they force the lifetime param to be early-bound instead of late-bound.
// For trait bounds, it can affect well-formedness (see `ClauseKind::WellFormed`).
// @has 'foo/fn.empty.html'
// @has - '//pre[@class="rust item-decl"]' "empty<'a, T>()where T:, 'a:,"
pub fn empty<'a, T>()
where
T:,
'a:,
{}

View File

@ -3,5 +3,5 @@
#![allow(incomplete_features)] #![allow(incomplete_features)]
// make sure that `ConstEvaluatable` predicates dont cause rustdoc to ICE #77647 // make sure that `ConstEvaluatable` predicates dont cause rustdoc to ICE #77647
// @has foo/struct.Ice.html '//pre[@class="rust item-decl"]' \ // @has foo/struct.Ice.html '//pre[@class="rust item-decl"]' \
// 'pub struct Ice<const N: usize>;' // 'pub struct Ice<const N: usize> where [(); { _ }]:;'
pub struct Ice<const N: usize> where [(); N + 1]:; pub struct Ice<const N: usize> where [(); N + 1]:;