diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 4ff94cc6f3b64..229b521097116 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -548,12 +548,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, }, ) } @@ -627,12 +630,38 @@ 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 { + // The type is introduced by a `for` binder. + // This is an unstable, incomplete feature + // gated behind `#![feature(non_lifetime_binders)]`. + // + // When the details of `for` are worked out and + // the feature is closer to stabilization, add appropriate logic here. + false + } + }; + ( param.name.ident().name, GenericParamDefKind::Type { bounds, default: default.map(|t| clean_ty(t, cx)).map(Box::new), synthetic, + allow_unsized, }, ) } @@ -3210,6 +3239,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 46aaa0068dee6..8de83b0d75dc1 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1357,10 +1357,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 { diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index f966d9265628b..9a02763b3b227 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -458,10 +458,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 40f89009a431b..473298a46be20 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: Add Attribute::MacroUse -pub const FORMAT_VERSION: u32 = 55; +// Latest feature: Add `allow_unsized` field to `GenericParamDefKind::Type` +pub const FORMAT_VERSION: u32 = 56; /// The root of the emitted JSON blob. /// @@ -977,6 +977,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,