Skip to content

Commit bba78b8

Browse files
committed
Suppress ?Sized bounds from rustdoc when Sized is implied.
1 parent ca98d4d commit bba78b8

File tree

10 files changed

+401
-15
lines changed

10 files changed

+401
-15
lines changed

src/librustdoc/clean/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,12 +538,15 @@ fn clean_generic_param_def(
538538
} else {
539539
None
540540
};
541+
let param_ty = ty::ParamTy::for_def(def);
542+
let allow_unsized = !param_ty.to_ty(cx.tcx).is_sized(cx.tcx, cx.typing_env());
541543
(
542544
def.name,
543545
GenericParamDefKind::Type {
544546
bounds: ThinVec::new(), // These are filled in from the where-clauses.
545547
default: default.map(Box::new),
546548
synthetic,
549+
allow_unsized,
547550
},
548551
)
549552
}
@@ -617,12 +620,35 @@ fn clean_generic_param<'tcx>(
617620
} else {
618621
ThinVec::new()
619622
};
623+
624+
// If this ends up being slow, then optimize it by reading the local bounds
625+
// (from all predicate origins) and check if a bound on `?Sized` is present.
626+
// If there's no `?Sized` bound, then definitely `allow_unsized = false`.
627+
let allow_unsized = {
628+
let parent = cx.tcx.hir_ty_param_owner(param.def_id);
629+
let index = cx
630+
.tcx
631+
.generics_of(parent)
632+
.param_def_id_to_index(cx.tcx, param.def_id.to_def_id());
633+
634+
if let Some(index) = index {
635+
let param_ty = ty::ParamTy::new(index, param.name.ident().name);
636+
!param_ty.to_ty(cx.tcx).is_sized(cx.tcx, cx.typing_env())
637+
} else {
638+
// AFAIU this should never happen:
639+
// it would mean the generic parameter wasn't found.
640+
// Advice on how to handle this would be appreciated :)
641+
unreachable!()
642+
}
643+
};
644+
620645
(
621646
param.name.ident().name,
622647
GenericParamDefKind::Type {
623648
bounds,
624649
default: default.map(|t| clean_ty(t, cx)).map(Box::new),
625650
synthetic,
651+
allow_unsized,
626652
},
627653
)
628654
}
@@ -3201,6 +3227,7 @@ fn clean_bound_vars<'tcx>(
32013227
bounds: ThinVec::new(),
32023228
default: None,
32033229
synthetic: false,
3230+
allow_unsized: false, // If `for<T>` could support `T: ?Sized`, fix this.
32043231
},
32053232
})
32063233
}

src/librustdoc/clean/types.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{fmt, iter};
66
use arrayvec::ArrayVec;
77
use itertools::Either;
88
use rustc_abi::{ExternAbi, VariantIdx};
9+
use rustc_ast::BoundPolarity;
910
use rustc_attr_data_structures::{
1011
AttributeKind, ConstStability, Deprecation, Stability, StableSince, find_attr,
1112
};
@@ -1295,6 +1296,16 @@ impl GenericBound {
12951296
matches!(self, Self::TraitBound(..))
12961297
}
12971298

1299+
pub(crate) fn is_maybe_sized_bound(&self, tcx: TyCtxt<'_>) -> bool {
1300+
if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, modifiers) = *self
1301+
&& matches!(modifiers.polarity, BoundPolarity::Maybe(_))
1302+
&& tcx.is_lang_item(trait_.def_id(), LangItem::Sized)
1303+
{
1304+
return true;
1305+
}
1306+
false
1307+
}
1308+
12981309
pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool {
12991310
self.is_bounded_by_lang_item(cx, LangItem::Sized)
13001311
}
@@ -1371,10 +1382,21 @@ impl WherePredicate {
13711382

13721383
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
13731384
pub(crate) enum GenericParamDefKind {
1374-
Lifetime { outlives: ThinVec<Lifetime> },
1375-
Type { bounds: ThinVec<GenericBound>, default: Option<Box<Type>>, synthetic: bool },
1385+
Lifetime {
1386+
outlives: ThinVec<Lifetime>,
1387+
},
1388+
Type {
1389+
bounds: ThinVec<GenericBound>,
1390+
default: Option<Box<Type>>,
1391+
synthetic: bool,
1392+
allow_unsized: bool,
1393+
},
13761394
// Option<Box<String>> makes this type smaller than `Option<String>` would.
1377-
Const { ty: Box<Type>, default: Option<Box<String>>, synthetic: bool },
1395+
Const {
1396+
ty: Box<Type>,
1397+
default: Option<Box<String>>,
1398+
synthetic: bool,
1399+
},
13781400
}
13791401

13801402
impl GenericParamDefKind {
@@ -1406,6 +1428,13 @@ impl GenericParamDef {
14061428
self.kind.is_type()
14071429
}
14081430

1431+
pub(crate) fn allow_unsized(&self) -> bool {
1432+
match &self.kind {
1433+
GenericParamDefKind::Type { allow_unsized, .. } => *allow_unsized,
1434+
_ => false,
1435+
}
1436+
}
1437+
14091438
pub(crate) fn get_bounds(&self) -> Option<&[GenericBound]> {
14101439
match self.kind {
14111440
GenericParamDefKind::Type { ref bounds, .. } => Some(bounds),

src/librustdoc/html/format.rs

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,21 @@ impl clean::GenericParamDef {
6868

6969
Ok(())
7070
}
71-
clean::GenericParamDefKind::Type { bounds, default, .. } => {
71+
clean::GenericParamDefKind::Type { bounds, default, allow_unsized, .. } => {
7272
f.write_str(self.name.as_str())?;
7373

74+
let filtered: Vec<_>;
75+
let bounds = if !allow_unsized {
76+
filtered = bounds
77+
.iter()
78+
.filter(|b| !b.is_maybe_sized_bound(cx.tcx()))
79+
.cloned()
80+
.collect();
81+
filtered.as_slice()
82+
} else {
83+
bounds.as_slice()
84+
};
85+
7486
if !bounds.is_empty() {
7587
f.write_str(": ")?;
7688
print_generic_bounds(bounds, cx).fmt(f)?;
@@ -127,14 +139,45 @@ pub(crate) enum Ending {
127139
NoNewline,
128140
}
129141

130-
fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display {
142+
fn print_where_predicate(
143+
predicate: &clean::WherePredicate,
144+
disallow_unsized: &FxHashSet<Symbol>,
145+
cx: &Context<'_>,
146+
) -> impl Display {
131147
fmt::from_fn(move |f| {
132148
match predicate {
133149
clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => {
134-
print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
135-
ty.print(cx).fmt(f)?;
136-
f.write_str(":")?;
150+
let filtered: Vec<_>;
151+
let mut bounds_slice = bounds.as_slice();
152+
// Check if the bound is on a generic type parameter that
153+
// does not accept unsized types.
154+
if let clean::Type::Generic(symbol) = ty
155+
&& disallow_unsized.contains(symbol)
156+
{
157+
// Check if the predicate contains a `?Sized` bound on that generic type.
158+
// Even though the bound is syntactically present, we know that
159+
// another bound requires this generic to be `Sized`.
160+
// We omit the `?Sized` bound from the representation.
161+
// See: https://github.com/rust-lang/rust/issues/143197
162+
//
163+
// We do two passes to avoid allocating a new `Vec` and copying it
164+
// in the most common case: when `?Sized` is *not* present.
165+
if bounds.iter().any(|b| b.is_maybe_sized_bound(cx.tcx())) {
166+
filtered = bounds
167+
.iter()
168+
.filter(|b| !b.is_maybe_sized_bound(cx.tcx()))
169+
.cloned()
170+
.collect();
171+
bounds_slice = filtered.as_slice();
172+
}
173+
};
174+
let bounds = bounds_slice;
175+
137176
if !bounds.is_empty() {
177+
print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?;
178+
ty.print(cx).fmt(f)?;
179+
f.write_str(":")?;
180+
138181
f.write_str(" ")?;
139182
print_generic_bounds(bounds, cx).fmt(f)?;
140183
}
@@ -172,6 +215,8 @@ pub(crate) fn print_where_clause(
172215
if gens.where_predicates.is_empty() {
173216
return None;
174217
}
218+
let disallow_unsized: FxHashSet<_> =
219+
gens.params.iter().filter(|p| p.is_type() && !p.allow_unsized()).map(|p| p.name).collect();
175220

176221
Some(fmt::from_fn(move |f| {
177222
let where_preds = fmt::from_fn(|f| {
@@ -184,7 +229,7 @@ pub(crate) fn print_where_clause(
184229
} else {
185230
f.write_str("\n")?;
186231
}
187-
print_where_predicate(predicate, cx).fmt(f)
232+
print_where_predicate(predicate, &disallow_unsized, cx).fmt(f)
188233
})
189234
})
190235
.joined(",", f)
@@ -1017,6 +1062,8 @@ fn fmt_type(
10171062
}
10181063
clean::ImplTrait(bounds) => {
10191064
f.write_str("impl ")?;
1065+
// TODO: Figure out if one of the bounds here is `?Sized`
1066+
// and whether another bound implies `Sized`.
10201067
print_generic_bounds(bounds, cx).fmt(f)
10211068
}
10221069
clean::QPath(qpath) => qpath.print(cx).fmt(f),

src/librustdoc/json/conversions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,11 @@ impl FromClean<clean::GenericParamDefKind> for GenericParamDefKind {
440440
Lifetime { outlives } => {
441441
GenericParamDefKind::Lifetime { outlives: outlives.into_json(renderer) }
442442
}
443-
Type { bounds, default, synthetic } => GenericParamDefKind::Type {
443+
Type { bounds, default, synthetic, allow_unsized } => GenericParamDefKind::Type {
444444
bounds: bounds.into_json(renderer),
445445
default: default.into_json(renderer),
446446
is_synthetic: *synthetic,
447+
allow_unsized: *allow_unsized,
447448
},
448449
Const { ty, default, synthetic: _ } => GenericParamDefKind::Const {
449450
type_: ty.into_json(renderer),

src/rustdoc-json-types/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
3737
// will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
3838
// are deliberately not in a doc comment, because they need not be in public docs.)
3939
//
40-
// Latest feature: Pretty printing of no_mangle attributes changed
41-
pub const FORMAT_VERSION: u32 = 53;
40+
// Latest feature: add `allow_unsized` field to `GenericParamDefKind::Type`
41+
pub const FORMAT_VERSION: u32 = 54;
4242

4343
/// The root of the emitted JSON blob.
4444
///
@@ -893,6 +893,11 @@ pub enum GenericParamDefKind {
893893
/// is bound by `Trait`) is synthetic, because it was not originally in
894894
/// the Rust source text.
895895
is_synthetic: bool,
896+
/// Whether this type parameter can be instantiated with an unsized type.
897+
///
898+
/// This is `true` if the parameter has a `?Sized` bound without any
899+
/// additional bounds that imply `Sized`.
900+
allow_unsized: bool,
896901
},
897902

898903
/// Denotes a constant parameter.

src/tools/jsondoclint/src/validator.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,12 @@ impl<'a> Validator<'a> {
332332
fn check_generic_param_def(&mut self, gpd: &'a GenericParamDef) {
333333
match &gpd.kind {
334334
rustdoc_json_types::GenericParamDefKind::Lifetime { outlives: _ } => {}
335-
rustdoc_json_types::GenericParamDefKind::Type { bounds, default, is_synthetic: _ } => {
335+
rustdoc_json_types::GenericParamDefKind::Type {
336+
bounds,
337+
default,
338+
is_synthetic: _,
339+
allow_unsized: _,
340+
} => {
336341
bounds.iter().for_each(|b| self.check_generic_bound(b));
337342
if let Some(ty) = default {
338343
self.check_type(ty);

0 commit comments

Comments
 (0)