From bba78b863ebd8db2e384fdeecd9fae25e948de53 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski Date: Mon, 7 Jul 2025 02:23:51 +0000 Subject: [PATCH] Suppress `?Sized` bounds from rustdoc when `Sized` is implied. --- src/librustdoc/clean/mod.rs | 27 ++++ src/librustdoc/clean/types.rs | 35 +++++- src/librustdoc/html/format.rs | 59 ++++++++- src/librustdoc/json/conversions.rs | 3 +- src/rustdoc-json-types/lib.rs | 9 +- src/tools/jsondoclint/src/validator.rs | 7 +- tests/rustdoc-json/allow_unsized.rs | 139 +++++++++++++++++++++ tests/rustdoc-json/fns/generic_args.rs | 2 +- tests/rustdoc-json/non_lifetime_binders.rs | 2 +- tests/rustdoc/allow_unsized.rs | 133 ++++++++++++++++++++ 10 files changed, 401 insertions(+), 15 deletions(-) create mode 100644 tests/rustdoc-json/allow_unsized.rs create mode 100644 tests/rustdoc/allow_unsized.rs diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 5cd5c434521c0..d7ef27a140566 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -538,12 +538,15 @@ fn clean_generic_param_def( } else { None }; + let param_ty = ty::ParamTy::for_def(def); + let allow_unsized = !param_ty.to_ty(cx.tcx).is_sized(cx.tcx, cx.typing_env()); ( def.name, GenericParamDefKind::Type { bounds: ThinVec::new(), // These are filled in from the where-clauses. default: default.map(Box::new), synthetic, + allow_unsized, }, ) } @@ -617,12 +620,35 @@ fn clean_generic_param<'tcx>( } else { ThinVec::new() }; + + // If this ends up being slow, then optimize it by reading the local bounds + // (from all predicate origins) and check if a bound on `?Sized` is present. + // If there's no `?Sized` bound, then definitely `allow_unsized = false`. + let allow_unsized = { + let parent = cx.tcx.hir_ty_param_owner(param.def_id); + let index = cx + .tcx + .generics_of(parent) + .param_def_id_to_index(cx.tcx, param.def_id.to_def_id()); + + if let Some(index) = index { + let param_ty = ty::ParamTy::new(index, param.name.ident().name); + !param_ty.to_ty(cx.tcx).is_sized(cx.tcx, cx.typing_env()) + } else { + // AFAIU this should never happen: + // it would mean the generic parameter wasn't found. + // Advice on how to handle this would be appreciated :) + unreachable!() + } + }; + ( param.name.ident().name, GenericParamDefKind::Type { bounds, default: default.map(|t| clean_ty(t, cx)).map(Box::new), synthetic, + allow_unsized, }, ) } @@ -3201,6 +3227,7 @@ fn clean_bound_vars<'tcx>( bounds: ThinVec::new(), default: None, synthetic: false, + allow_unsized: false, // If `for` could support `T: ?Sized`, fix this. }, }) } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index a05aab22f1e16..0cb910dc39fa4 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -6,6 +6,7 @@ use std::{fmt, iter}; use arrayvec::ArrayVec; use itertools::Either; use rustc_abi::{ExternAbi, VariantIdx}; +use rustc_ast::BoundPolarity; use rustc_attr_data_structures::{ AttributeKind, ConstStability, Deprecation, Stability, StableSince, find_attr, }; @@ -1295,6 +1296,16 @@ impl GenericBound { matches!(self, Self::TraitBound(..)) } + pub(crate) fn is_maybe_sized_bound(&self, tcx: TyCtxt<'_>) -> bool { + if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, modifiers) = *self + && matches!(modifiers.polarity, BoundPolarity::Maybe(_)) + && tcx.is_lang_item(trait_.def_id(), LangItem::Sized) + { + return true; + } + false + } + pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool { self.is_bounded_by_lang_item(cx, LangItem::Sized) } @@ -1371,10 +1382,21 @@ impl WherePredicate { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) enum GenericParamDefKind { - Lifetime { outlives: ThinVec }, - Type { bounds: ThinVec, default: Option>, synthetic: bool }, + Lifetime { + outlives: ThinVec, + }, + Type { + bounds: ThinVec, + default: Option>, + synthetic: bool, + allow_unsized: bool, + }, // Option> makes this type smaller than `Option` would. - Const { ty: Box, default: Option>, synthetic: bool }, + Const { + ty: Box, + default: Option>, + synthetic: bool, + }, } impl GenericParamDefKind { @@ -1406,6 +1428,13 @@ impl GenericParamDef { self.kind.is_type() } + pub(crate) fn allow_unsized(&self) -> bool { + match &self.kind { + GenericParamDefKind::Type { allow_unsized, .. } => *allow_unsized, + _ => false, + } + } + pub(crate) fn get_bounds(&self) -> Option<&[GenericBound]> { match self.kind { GenericParamDefKind::Type { ref bounds, .. } => Some(bounds), diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index bcb3e57c84428..dc0c2874eba11 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -68,9 +68,21 @@ impl clean::GenericParamDef { Ok(()) } - clean::GenericParamDefKind::Type { bounds, default, .. } => { + clean::GenericParamDefKind::Type { bounds, default, allow_unsized, .. } => { f.write_str(self.name.as_str())?; + let filtered: Vec<_>; + let bounds = if !allow_unsized { + filtered = bounds + .iter() + .filter(|b| !b.is_maybe_sized_bound(cx.tcx())) + .cloned() + .collect(); + filtered.as_slice() + } else { + bounds.as_slice() + }; + if !bounds.is_empty() { f.write_str(": ")?; print_generic_bounds(bounds, cx).fmt(f)?; @@ -127,14 +139,45 @@ pub(crate) enum Ending { NoNewline, } -fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> impl Display { +fn print_where_predicate( + predicate: &clean::WherePredicate, + disallow_unsized: &FxHashSet, + cx: &Context<'_>, +) -> impl Display { fmt::from_fn(move |f| { match predicate { clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { - print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?; - ty.print(cx).fmt(f)?; - f.write_str(":")?; + let filtered: Vec<_>; + let mut bounds_slice = bounds.as_slice(); + // Check if the bound is on a generic type parameter that + // does not accept unsized types. + if let clean::Type::Generic(symbol) = ty + && disallow_unsized.contains(symbol) + { + // Check if the predicate contains a `?Sized` bound on that generic type. + // Even though the bound is syntactically present, we know that + // another bound requires this generic to be `Sized`. + // We omit the `?Sized` bound from the representation. + // See: https://github.com/rust-lang/rust/issues/143197 + // + // We do two passes to avoid allocating a new `Vec` and copying it + // in the most common case: when `?Sized` is *not* present. + if bounds.iter().any(|b| b.is_maybe_sized_bound(cx.tcx())) { + filtered = bounds + .iter() + .filter(|b| !b.is_maybe_sized_bound(cx.tcx())) + .cloned() + .collect(); + bounds_slice = filtered.as_slice(); + } + }; + let bounds = bounds_slice; + if !bounds.is_empty() { + print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?; + ty.print(cx).fmt(f)?; + f.write_str(":")?; + f.write_str(" ")?; print_generic_bounds(bounds, cx).fmt(f)?; } @@ -172,6 +215,8 @@ pub(crate) fn print_where_clause( if gens.where_predicates.is_empty() { return None; } + let disallow_unsized: FxHashSet<_> = + gens.params.iter().filter(|p| p.is_type() && !p.allow_unsized()).map(|p| p.name).collect(); Some(fmt::from_fn(move |f| { let where_preds = fmt::from_fn(|f| { @@ -184,7 +229,7 @@ pub(crate) fn print_where_clause( } else { f.write_str("\n")?; } - print_where_predicate(predicate, cx).fmt(f) + print_where_predicate(predicate, &disallow_unsized, cx).fmt(f) }) }) .joined(",", f) @@ -1017,6 +1062,8 @@ fn fmt_type( } clean::ImplTrait(bounds) => { f.write_str("impl ")?; + // TODO: Figure out if one of the bounds here is `?Sized` + // and whether another bound implies `Sized`. print_generic_bounds(bounds, cx).fmt(f) } clean::QPath(qpath) => qpath.print(cx).fmt(f), diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index f51b35097f66a..177394b3486ea 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -440,10 +440,11 @@ impl FromClean for GenericParamDefKind { Lifetime { outlives } => { GenericParamDefKind::Lifetime { outlives: outlives.into_json(renderer) } } - Type { bounds, default, synthetic } => GenericParamDefKind::Type { + Type { bounds, default, synthetic, allow_unsized } => GenericParamDefKind::Type { bounds: bounds.into_json(renderer), default: default.into_json(renderer), is_synthetic: *synthetic, + allow_unsized: *allow_unsized, }, Const { ty, default, synthetic: _ } => GenericParamDefKind::Const { type_: ty.into_json(renderer), diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 0e72ddd9db1ed..4a63f4ddd41f6 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -37,8 +37,8 @@ pub type FxHashMap = HashMap; // re-export for use in src/librustdoc // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line // are deliberately not in a doc comment, because they need not be in public docs.) // -// Latest feature: Pretty printing of no_mangle attributes changed -pub const FORMAT_VERSION: u32 = 53; +// Latest feature: add `allow_unsized` field to `GenericParamDefKind::Type` +pub const FORMAT_VERSION: u32 = 54; /// The root of the emitted JSON blob. /// @@ -893,6 +893,11 @@ pub enum GenericParamDefKind { /// is bound by `Trait`) is synthetic, because it was not originally in /// the Rust source text. is_synthetic: bool, + /// Whether this type parameter can be instantiated with an unsized type. + /// + /// This is `true` if the parameter has a `?Sized` bound without any + /// additional bounds that imply `Sized`. + allow_unsized: bool, }, /// Denotes a constant parameter. diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index 0a4051fcbe8cd..c71e7df02c06a 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -332,7 +332,12 @@ impl<'a> Validator<'a> { fn check_generic_param_def(&mut self, gpd: &'a GenericParamDef) { match &gpd.kind { rustdoc_json_types::GenericParamDefKind::Lifetime { outlives: _ } => {} - rustdoc_json_types::GenericParamDefKind::Type { bounds, default, is_synthetic: _ } => { + rustdoc_json_types::GenericParamDefKind::Type { + bounds, + default, + is_synthetic: _, + allow_unsized: _, + } => { bounds.iter().for_each(|b| self.check_generic_bound(b)); if let Some(ty) = default { self.check_type(ty); diff --git a/tests/rustdoc-json/allow_unsized.rs b/tests/rustdoc-json/allow_unsized.rs new file mode 100644 index 0000000000000..58d5b70ac35b0 --- /dev/null +++ b/tests/rustdoc-json/allow_unsized.rs @@ -0,0 +1,139 @@ +#![crate_name = "allow_unsized"] + +use std::fmt::Debug; +use std::marker::PhantomData; + +pub trait CustomSized: Sized {} +impl CustomSized for u8 {} + +// Generic functions +//@ is "$.index[?(@.name=='func_custom')].inner.function.generics.params[0].kind.type.allow_unsized" false +pub fn func_custom() {} + +//@ is "$.index[?(@.name=='func_custom')].inner.function.generics.params[0].kind.type.allow_unsized" false +pub fn func_custom_where_denies() +where + T: CustomSized, +{ +} + +//@ is "$.index[?(@.name=='func_custom')].inner.function.generics.params[0].kind.type.allow_unsized" false +pub fn func_custom_where_allows() +where + T: ?Sized, +{ +} + +//@ is "$.index[?(@.name=='func_custom')].inner.function.generics.params[0].kind.type.allow_unsized" false +pub fn func_custom_where_both() +where + T: ?Sized + CustomSized, +{ +} + +//@ is "$.index[?(@.name=='func_unsized')].inner.function.generics.params[0].kind.type.allow_unsized" true +pub fn func_unsized() {} + +//@ is "$.index[?(@.name=='func_clone')].inner.function.generics.params[0].kind.type.allow_unsized" false +pub fn func_clone() {} + +//@ is "$.index[?(@.name=='func_debug')].inner.function.generics.params[0].kind.type.allow_unsized" true +pub fn func_debug() {} + +// Generic structs +//@ is "$.index[?(@.name=='StructCustom')].inner.struct.generics.params[0].kind.type.allow_unsized" false +pub struct StructCustom(PhantomData); + +//@ is "$.index[?(@.name=='StructUnsized')].inner.struct.generics.params[0].kind.type.allow_unsized" true +pub struct StructUnsized(PhantomData); + +//@ is "$.index[?(@.name=='StructClone')].inner.struct.generics.params[0].kind.type.allow_unsized" false +pub struct StructClone(PhantomData); + +//@ is "$.index[?(@.name=='StructDebug')].inner.struct.generics.params[0].kind.type.allow_unsized" true +pub struct StructDebug(PhantomData); + +// Struct with `?Sized` bound, and impl blocks that add additional bounds +//@ is "$.index[?(@.name=='Wrapper')].inner.struct.generics.params[0].kind.type.allow_unsized" true +pub struct Wrapper(PhantomData); + +//@ is "$.index[?(@.docs=='impl custom')].inner.impl.generics.params[0].kind.type.allow_unsized" false +/// impl custom +impl Wrapper { + pub fn impl_custom() {} +} + +//@ is "$.index[?(@.docs=='impl clone')].inner.impl.generics.params[0].kind.type.allow_unsized" false +/// impl clone +impl Wrapper { + pub fn impl_clone() {} +} + +//@ is "$.index[?(@.docs=='impl debug')].inner.impl.generics.params[0].kind.type.allow_unsized" true +/// impl debug +impl Wrapper { + pub fn impl_debug() {} +} + +impl Wrapper { + //@ is "$.index[?(@.name=='assoc_custom')].inner.function.generics.params[0].kind.type.allow_unsized" false + pub fn assoc_custom(&self) {} + + //@ is "$.index[?(@.name=='assoc_unsized')].inner.function.generics.params[0].kind.type.allow_unsized" true + pub fn assoc_unsized(&self) {} + + //@ is "$.index[?(@.name=='assoc_clone')].inner.function.generics.params[0].kind.type.allow_unsized" false + pub fn assoc_clone(&self) {} + + //@ is "$.index[?(@.name=='assoc_debug')].inner.function.generics.params[0].kind.type.allow_unsized" true + pub fn assoc_debug(&self) {} +} + +// Traits with generic parameters +//@ is "$.index[?(@.name=='TraitCustom')].inner.trait.generics.params[0].kind.type.allow_unsized" false +pub trait TraitCustom {} + +//@ is "$.index[?(@.name=='TraitUnsized')].inner.trait.generics.params[0].kind.type.allow_unsized" true +pub trait TraitUnsized {} + +//@ is "$.index[?(@.name=='TraitClone')].inner.trait.generics.params[0].kind.type.allow_unsized" false +pub trait TraitClone {} + +//@ is "$.index[?(@.name=='TraitDebug')].inner.trait.generics.params[0].kind.type.allow_unsized" true +pub trait TraitDebug {} + +pub trait TraitMethods { + //@ is "$.index[?(@.name=='method_custom')].inner.function.generics.params[0].kind.type.allow_unsized" false + fn method_custom(); + + //@ is "$.index[?(@.name=='method_unsized')].inner.function.generics.params[0].kind.type.allow_unsized" true + fn method_unsized(); + + //@ is "$.index[?(@.name=='method_clone')].inner.function.generics.params[0].kind.type.allow_unsized" false + fn method_clone(); + + //@ is "$.index[?(@.name=='method_debug')].inner.function.generics.params[0].kind.type.allow_unsized" true + fn method_debug(); +} + +// `where` clauses on trait functions, which only affect `T` for that function +//@ is "$.index[?(@.name=='OuterDebug')].inner.trait.generics.params[0].kind.type.allow_unsized" true +pub trait OuterDebug { + fn foo() + where + T: Debug; +} + +//@ is "$.index[?(@.name=='OuterClone')].inner.trait.generics.params[0].kind.type.allow_unsized" true +pub trait OuterClone { + fn foo() + where + T: Clone; +} + +// Synthetic generic parameters +//@ is "$.index[?(@.name=='synth_clone')].inner.function.generics.params[0].kind.type.allow_unsized" false +pub fn synth_clone(_: impl Clone + ?Sized) {} + +//@ is "$.index[?(@.name=='synth_debug')].inner.function.generics.params[0].kind.type.allow_unsized" true +pub fn synth_debug(_: impl Debug + ?Sized) {} diff --git a/tests/rustdoc-json/fns/generic_args.rs b/tests/rustdoc-json/fns/generic_args.rs index 2ea68173d68d8..e1d6c64bb774c 100644 --- a/tests/rustdoc-json/fns/generic_args.rs +++ b/tests/rustdoc-json/fns/generic_args.rs @@ -27,7 +27,7 @@ pub fn impl_trait(f: impl Foo) {} //@ count "$.index[?(@.name=='where_clase')].inner.function.generics.params[*]" 3 //@ is "$.index[?(@.name=='where_clase')].inner.function.generics.params[0].name" '"F"' -//@ is "$.index[?(@.name=='where_clase')].inner.function.generics.params[0].kind" '{"type": {"bounds": [], "default": null, "is_synthetic": false}}' +//@ is "$.index[?(@.name=='where_clase')].inner.function.generics.params[0].kind" '{"type": {"bounds": [], "default": null, "is_synthetic": false, "allow_unsized": false}}' //@ count "$.index[?(@.name=='where_clase')].inner.function.sig.inputs[*]" 3 //@ is "$.index[?(@.name=='where_clase')].inner.function.sig.inputs[0][0]" '"f"' //@ is "$.index[?(@.name=='where_clase')].inner.function.sig.inputs[0][1].generic" '"F"' diff --git a/tests/rustdoc-json/non_lifetime_binders.rs b/tests/rustdoc-json/non_lifetime_binders.rs index 84318821c5084..fbe647ee88727 100644 --- a/tests/rustdoc-json/non_lifetime_binders.rs +++ b/tests/rustdoc-json/non_lifetime_binders.rs @@ -9,7 +9,7 @@ pub struct Wrapper(std::marker::PhantomData); //@ is "$.index[?(@.name=='foo')].inner.function.generics.where_predicates[0].bound_predicate.generic_params[0].name" \"\'a\" //@ is "$.index[?(@.name=='foo')].inner.function.generics.where_predicates[0].bound_predicate.generic_params[0].kind" '{ "lifetime": { "outlives": [] } }' //@ is "$.index[?(@.name=='foo')].inner.function.generics.where_predicates[0].bound_predicate.generic_params[1].name" \"T\" -//@ is "$.index[?(@.name=='foo')].inner.function.generics.where_predicates[0].bound_predicate.generic_params[1].kind" '{ "type": { "bounds": [], "default": null, "is_synthetic": false } }' +//@ is "$.index[?(@.name=='foo')].inner.function.generics.where_predicates[0].bound_predicate.generic_params[1].kind" '{ "type": { "bounds": [], "default": null, "is_synthetic": false, "allow_unsized": false } }' pub fn foo() where for<'a, T> &'a Wrapper: Trait, diff --git a/tests/rustdoc/allow_unsized.rs b/tests/rustdoc/allow_unsized.rs new file mode 100644 index 0000000000000..7cc73ce5acd45 --- /dev/null +++ b/tests/rustdoc/allow_unsized.rs @@ -0,0 +1,133 @@ +#![crate_name = "allow_unsized"] + +use std::fmt::Debug; +use std::marker::PhantomData; + +pub trait CustomSized: Sized {} +impl CustomSized for u8 {} + + +// Generic functions +//@ !has allow_unsized/fn.func_custom.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_custom() {} + +//@ !has allow_unsized/fn.func_custom_where_denies.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_custom_where_denies() where T: CustomSized {} + +//@ !has allow_unsized/fn.func_custom_where_allows.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_custom_where_allows() where T: ?Sized {} + +//@ !has allow_unsized/fn.func_custom_where_both.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_custom_where_both() where T: ?Sized + CustomSized {} + +//@ has allow_unsized/fn.func_unsized.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_unsized() {} + +//@ !has allow_unsized/fn.func_clone.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_clone() {} + +//@ has allow_unsized/fn.func_debug.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn func_debug() {} + +// Generic structs +//@ !has allow_unsized/struct.StructCustom.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct StructCustom(PhantomData); + +//@ has allow_unsized/struct.StructUnsized.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct StructUnsized(PhantomData); + +//@ !has allow_unsized/struct.StructClone.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct StructClone(PhantomData); + +//@ has allow_unsized/struct.StructDebug.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct StructDebug(PhantomData); + + +// Structs with `?Sized` bound, and impl blocks that add additional bounds. +// The structs have to be different due to limitations in the XPath matching syntax. +//@ has allow_unsized/struct.CustomSizedWrapper.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct CustomSizedWrapper(PhantomData); + +//@ !has allow_unsized/struct.CustomSizedWrapper.html "//div[@id='implementations-list']//section[@class='impl']//h3" '?Sized' +impl CustomSizedWrapper { + pub fn impl_custom() {} +} + +//@ has allow_unsized/struct.CloneWrapper.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct CloneWrapper(PhantomData); + +//@ !has allow_unsized/struct.CloneWrapper.html "//div[@id='implementations-list']//section[@class='impl']//h3" '?Sized' +impl CloneWrapper { + pub fn impl_clone() {} +} + +//@ has allow_unsized/struct.DebugWrapper.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct DebugWrapper(PhantomData); + +//@ has allow_unsized/struct.DebugWrapper.html "//div[@id='implementations-list']//section[@class='impl']//h3" '?Sized' +impl DebugWrapper { + pub fn impl_debug() {} +} + +//@ has allow_unsized/struct.Wrapper.html '//pre[@class="rust item-decl"]' '?Sized' +pub struct Wrapper(PhantomData); + +impl Wrapper { + //@ !has allow_unsized/struct.Wrapper.html "//*[@id='method.assoc_custom']"//h4 '?Sized' + pub fn assoc_custom(&self) {} + + //@ has allow_unsized/struct.Wrapper.html "//*[@id='method.assoc_unsized']"//h4 '?Sized' + pub fn assoc_unsized(&self) {} + + //@ !has allow_unsized/struct.Wrapper.html "//*[@id='method.assoc_clone']"//h4 '?Sized' + pub fn assoc_clone(&self) {} + + //@ has allow_unsized/struct.Wrapper.html "//*[@id='method.assoc_debug']"//h4 '?Sized' + pub fn assoc_debug(&self) {} +} + + +// Traits with generic parameters +//@ !has allow_unsized/trait.TraitCustom.html '//pre[@class="rust item-decl"]' '?Sized' +pub trait TraitCustom {} + +//@ has allow_unsized/trait.TraitUnsized.html '//pre[@class="rust item-decl"]' '?Sized' +pub trait TraitUnsized {} + +//@ !has allow_unsized/trait.TraitClone.html '//pre[@class="rust item-decl"]' '?Sized' +pub trait TraitClone {} + +//@ has allow_unsized/trait.TraitDebug.html '//pre[@class="rust item-decl"]' '?Sized' +pub trait TraitDebug {} + +pub trait TraitMethods { + //@ !has allow_unsized/trait.TraitMethods.html "//*[@id='tymethod.method_custom']"//h4 '?Sized' + fn method_custom(); + + //@ has allow_unsized/trait.TraitMethods.html "//*[@id='tymethod.method_unsized']"//h4 '?Sized' + fn method_unsized(); + + //@ !has allow_unsized/trait.TraitMethods.html "//*[@id='tymethod.method_clone']"//h4 '?Sized' + fn method_clone(); + + //@ has allow_unsized/trait.TraitMethods.html "//*[@id='tymethod.method_debug']"//h4 '?Sized' + fn method_debug(); +} + + +// `where` clauses on trait functions, which only affect `T` for that function +//@ has allow_unsized/trait.OuterDebug.html '//pre[@class="rust item-decl"]' '?Sized' +pub trait OuterDebug { + fn foo() where T: Debug; +} + +//@ has allow_unsized/trait.OuterClone.html '//pre[@class="rust item-decl"]' '?Sized' +pub trait OuterClone { + fn foo() where T: Clone; +} + +//@ !has allow_unsized/fn.synth_clone.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn synth_clone(_: impl Clone + ?Sized) {} + +//@ has allow_unsized/fn.synth_debug.html '//pre[@class="rust item-decl"]' '?Sized' +pub fn synth_debug(_: impl Debug + ?Sized) {}