Skip to content
/ rust Public
forked from rust-lang/rust

Commit bc6f531

Browse files
authored
Rollup merge of rust-lang#158468 - obi1kenobi:pg/default-body-stability, r=GuillaumeGomez
Include default-stability info in rustdoc JSON. Add a `default_unstable` field on associated constants, associated types, and functions. The field is populated only when those items appear inside a trait, only when there's a default present, and when that default is not stable as designated by `#[rustc_default_body_unstable]`. In such a case, the field contains the name of the feature required to use the unstable default. The purpose of this info is to allow `cargo-semver-checks` to lint the standard library for accidental breakage of stable APIs. Removing a stable default is an example of such breakage, while removing an _unstable_ default is not. The field is boxed to minimize the size impact on its enclosing type, since for regular crates it will always be `None`. I also updated `jsondoclint` to assert that it's an error to have a populated `default_unstable` when there's no function body, no default const value, or no default associated type. In the process, I noticed that `jsondoclint` and `jsondocck` are both on edition 2021 — I plan to upgrade them to 2024 in separate PRs. r? @GuillaumeGomez **AI disclosure:** This PR is the product of a combination of manual work and AI tools. I secured approval in advance from the designated reviewer. I stand behind the quality of the code I'm submitting, and I vouch it's as good or better compared to if I had written every line by my own hand.
2 parents 435e80d + bd938c5 commit bc6f531

5 files changed

Lines changed: 364 additions & 17 deletions

File tree

src/librustdoc/json/conversions.rs

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,18 @@ impl FromClean<hir::ConstStability> for Stability {
270270
}
271271
}
272272

273+
impl FromClean<hir::DefaultBodyStability> for Box<ProvidedDefaultUnstable> {
274+
fn from_clean(stab: &hir::DefaultBodyStability, _renderer: &JsonRenderer<'_>) -> Self {
275+
let hir::StabilityLevel::Unstable { .. } = stab.level else {
276+
bug!(
277+
"unexpected stable default-body stability, \
278+
there's no stable equivalent of `#[rustc_default_body_unstable]`"
279+
)
280+
};
281+
Box::new(ProvidedDefaultUnstable { feature: stab.feature.to_string() })
282+
}
283+
}
284+
273285
impl FromClean<clean::GenericArgs> for Option<Box<GenericArgs>> {
274286
fn from_clean(generic_args: &clean::GenericArgs, renderer: &JsonRenderer<'_>) -> Self {
275287
use clean::GenericArgs::*;
@@ -353,18 +365,23 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum
353365
EnumItem(e) => ItemEnum::Enum(e.into_json(renderer)),
354366
VariantItem(v) => ItemEnum::Variant(v.into_json(renderer)),
355367
FunctionItem(f) => {
356-
ItemEnum::Function(from_clean_function(f, true, header.unwrap(), renderer))
368+
ItemEnum::Function(from_clean_function(f, true, None, header.unwrap(), renderer))
357369
}
358370
ForeignFunctionItem(f, _) => {
359-
ItemEnum::Function(from_clean_function(f, false, header.unwrap(), renderer))
371+
ItemEnum::Function(from_clean_function(f, false, None, header.unwrap(), renderer))
360372
}
361373
TraitItem(t) => ItemEnum::Trait(t.into_json(renderer)),
362374
TraitAliasItem(t) => ItemEnum::TraitAlias(t.into_json(renderer)),
363-
MethodItem(m, _) => {
364-
ItemEnum::Function(from_clean_function(m, true, header.unwrap(), renderer))
365-
}
375+
MethodItem(m, _) => ItemEnum::Function(from_clean_function(
376+
m,
377+
true,
378+
default_body_stability_for_def_id(renderer.tcx, item.item_id.expect_def_id())
379+
.map(|stab| stab.into_json(renderer)),
380+
header.unwrap(),
381+
renderer,
382+
)),
366383
RequiredMethodItem(m, _) => {
367-
ItemEnum::Function(from_clean_function(m, false, header.unwrap(), renderer))
384+
ItemEnum::Function(from_clean_function(m, false, None, header.unwrap(), renderer))
368385
}
369386
ImplItem(i) => ItemEnum::Impl(i.into_json(renderer)),
370387
StaticItem(s) => ItemEnum::Static(from_clean_static(s, rustc_hir::Safety::Safe, renderer)),
@@ -385,23 +402,41 @@ fn from_clean_item(item: &clean::Item, renderer: &JsonRenderer<'_>) -> ItemEnum
385402
})
386403
}
387404
// FIXME(generic_const_items): Add support for generic associated consts.
388-
RequiredAssocConstItem(_generics, ty) => {
389-
ItemEnum::AssocConst { type_: ty.into_json(renderer), value: None }
390-
}
405+
RequiredAssocConstItem(_generics, ty) => ItemEnum::AssocConst {
406+
type_: ty.into_json(renderer),
407+
value: None,
408+
default_unstable: None,
409+
},
391410
// FIXME(generic_const_items): Add support for generic associated consts.
392-
ProvidedAssocConstItem(ci) | ImplAssocConstItem(ci) => ItemEnum::AssocConst {
411+
ProvidedAssocConstItem(ci) => ItemEnum::AssocConst {
393412
type_: ci.type_.into_json(renderer),
394413
value: Some(ci.kind.expr(renderer.tcx)),
414+
default_unstable: default_body_stability_for_def_id(
415+
renderer.tcx,
416+
item.item_id.expect_def_id(),
417+
)
418+
.map(|stab| stab.into_json(renderer)),
419+
},
420+
ImplAssocConstItem(ci) => ItemEnum::AssocConst {
421+
type_: ci.type_.into_json(renderer),
422+
value: Some(ci.kind.expr(renderer.tcx)),
423+
default_unstable: None,
395424
},
396425
RequiredAssocTypeItem(g, b) => ItemEnum::AssocType {
397426
generics: g.into_json(renderer),
398427
bounds: b.into_json(renderer),
399428
type_: None,
429+
default_unstable: None,
400430
},
401431
AssocTypeItem(t, b) => ItemEnum::AssocType {
402432
generics: t.generics.into_json(renderer),
403433
bounds: b.into_json(renderer),
404434
type_: Some(t.item_type.as_ref().unwrap_or(&t.type_).into_json(renderer)),
435+
default_unstable: default_body_stability_for_def_id(
436+
renderer.tcx,
437+
item.item_id.expect_def_id(),
438+
)
439+
.map(|stab| stab.into_json(renderer)),
405440
},
406441
// `convert_item` early returns `None` for stripped items, keywords, attributes and
407442
// "special" macro rules.
@@ -815,6 +850,7 @@ impl FromClean<clean::Impl> for Impl {
815850
pub(crate) fn from_clean_function(
816851
clean::Function { decl, generics }: &clean::Function,
817852
has_body: bool,
853+
default_unstable: Option<Box<ProvidedDefaultUnstable>>,
818854
header: rustc_hir::FnHeader,
819855
renderer: &JsonRenderer<'_>,
820856
) -> Function {
@@ -823,6 +859,7 @@ pub(crate) fn from_clean_function(
823859
generics: generics.into_json(renderer),
824860
header: header.into_json(renderer),
825861
has_body,
862+
default_unstable,
826863
}
827864
}
828865

@@ -972,6 +1009,17 @@ impl FromClean<ItemType> for ItemKind {
9721009
}
9731010
}
9741011

1012+
fn default_body_stability_for_def_id(
1013+
tcx: TyCtxt<'_>,
1014+
def_id: DefId,
1015+
) -> Option<hir::DefaultBodyStability> {
1016+
let stability = tcx.lookup_default_body_stability(def_id)?;
1017+
match stability.level {
1018+
hir::StabilityLevel::Unstable { .. } => Some(stability),
1019+
hir::StabilityLevel::Stable { .. } => None,
1020+
}
1021+
}
1022+
9751023
fn const_stability_for_def_id(tcx: TyCtxt<'_>, def_id: DefId) -> Option<hir::ConstStability> {
9761024
if !tcx.is_conditionally_const(def_id) {
9771025
// The item cannot be conditionally-const. No const stability here.
@@ -1040,6 +1088,7 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>)
10401088
AK::Deprecated { .. } => return Vec::new(), // Handled separately into Item::deprecation.
10411089
AK::Stability { .. } => return Vec::new(), // Handled separately into Item::stability
10421090
AK::RustcConstStability { .. } => return Vec::new(), // Handled separately into Item::const_stability.
1091+
AK::RustcBodyStability { .. } => return Vec::new(), // Handled separately by `default_unstable`.
10431092

10441093
AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"),
10451094

src/rustdoc-json-types/lib.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
114114
// will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
115115
// are deliberately not in a doc comment, because they need not be in public docs.)
116116
//
117-
// Latest feature: Add `Item::const_stability`.
118-
pub const FORMAT_VERSION: u32 = 59;
117+
// Latest feature: Add default-body stability metadata.
118+
pub const FORMAT_VERSION: u32 = 60;
119119

120120
/// The root of the emitted JSON blob.
121121
///
@@ -288,6 +288,9 @@ pub struct Item {
288288
/// - `#[stable]` and `#[unstable]` attributes: see the [`Self::stability`] field instead.
289289
/// - `#[rustc_const_stable]` and `#[rustc_const_unstable]` attributes:
290290
/// see the [`Self::const_stability`] field instead.
291+
/// - `#[rustc_default_body_unstable]` attributes: instead see `default_unstable` fields on
292+
/// item kinds that can have unstable default values, such as [`Function::default_unstable`],
293+
/// [`ItemEnum::AssocConst::default_unstable`], and [`ItemEnum::AssocType::default_unstable`].
291294
///
292295
/// Attributes appear in pretty-printed Rust form, regardless of their formatting
293296
/// in the original source code. For example:
@@ -367,6 +370,19 @@ pub enum StabilityLevel {
367370
Unstable,
368371
}
369372

373+
/// Information about an unstable default provided by a trait item.
374+
///
375+
/// Example unstable defaults include:
376+
/// - a stable trait function or method whose body is not stable
377+
/// - a stable trait associated type or const whose default value is not stable
378+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
379+
#[cfg_attr(feature = "rkyv_0_8", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
380+
#[cfg_attr(feature = "rkyv_0_8", rkyv(derive(Debug)))]
381+
pub struct ProvidedDefaultUnstable {
382+
/// The feature that must be enabled to use the provided default.
383+
pub feature: String,
384+
}
385+
370386
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
371387
#[cfg_attr(feature = "rkyv_0_8", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
372388
#[cfg_attr(feature = "rkyv_0_8", rkyv(derive(Debug)))]
@@ -379,6 +395,9 @@ pub enum StabilityLevel {
379395
/// - `#[stable]` and `#[unstable]`. These are in [`Item::stability`] instead.
380396
/// - `#[rustc_const_stable]` and `#[rustc_const_unstable]`. These are in
381397
/// [`Item::const_stability`] instead.
398+
/// - `#[rustc_default_body_unstable]`. These are in the `default_unstable` field on the appropriate
399+
/// item kinds: [`Function::default_unstable`], [`ItemEnum::AssocConst::default_unstable`],
400+
/// and [`ItemEnum::AssocType::default_unstable`].
382401
pub enum Attribute {
383402
/// `#[non_exhaustive]`
384403
NonExhaustive,
@@ -875,6 +894,11 @@ pub enum ItemEnum {
875894
/// // ^^^^^^^^^^
876895
/// ```
877896
value: Option<String>,
897+
/// Metadata about an unstable default value provided for the associated constant, if any.
898+
///
899+
/// Empty if the associated constant has no default (see [`ItemEnum::AssocConst::value`]),
900+
/// or if the default value is stable.
901+
default_unstable: Option<Box<ProvidedDefaultUnstable>>,
878902
},
879903
/// An associated type of a trait or a type.
880904
AssocType {
@@ -899,6 +923,11 @@ pub enum ItemEnum {
899923
/// ```
900924
#[serde(rename = "type")]
901925
type_: Option<Type>,
926+
/// Metadata about an unstable default value provided for the associated type, if any.
927+
///
928+
/// Empty if the associated type has no default (see [`ItemEnum::AssocType::type_`]),
929+
/// or if the default value is stable.
930+
default_unstable: Option<Box<ProvidedDefaultUnstable>>,
902931
},
903932
}
904933

@@ -1188,6 +1217,12 @@ pub struct Function {
11881217
pub header: FunctionHeader,
11891218
/// Whether the function has a body, i.e. an implementation.
11901219
pub has_body: bool,
1220+
/// Metadata about a possible unstable provided default implementation for trait methods.
1221+
///
1222+
/// Only populated for function items inside traits. Empty if the trait method
1223+
/// does not have a default implementation (see [`Function::has_body`]),
1224+
/// or if its default implementation is stable.
1225+
pub default_unstable: Option<Box<ProvidedDefaultUnstable>>,
11911226
}
11921227

11931228
/// Generic parameters accepted by an item and `where` clauses imposed on it and the parameters.

src/tools/jsondoclint/src/validator.rs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl<'a> Validator<'a> {
9797
ItemEnum::StructField(x) => self.check_struct_field(x),
9898
ItemEnum::Enum(x) => self.check_enum(x),
9999
ItemEnum::Variant(x) => self.check_variant(x, id),
100-
ItemEnum::Function(x) => self.check_function(x),
100+
ItemEnum::Function(x) => self.check_function(x, id),
101101
ItemEnum::Trait(x) => self.check_trait(x, id),
102102
ItemEnum::TraitAlias(x) => self.check_trait_alias(x),
103103
ItemEnum::Impl(x) => self.check_impl(x, id),
@@ -114,12 +114,35 @@ impl<'a> Validator<'a> {
114114
ItemEnum::Module(x) => self.check_module(x, id),
115115
// FIXME: Why don't these have their own structs?
116116
ItemEnum::ExternCrate { .. } => {}
117-
ItemEnum::AssocConst { type_, value: _ } => self.check_type(type_),
118-
ItemEnum::AssocType { generics, bounds, type_ } => {
117+
ItemEnum::AssocConst { type_, value, default_unstable } => {
118+
self.check_type(type_);
119+
if value.is_none()
120+
&& let Some(default_unstable) = default_unstable
121+
{
122+
self.fail(
123+
id,
124+
ErrorKind::Custom(format!(
125+
"`default_unstable` must be `None` when `value` is `None`, but \
126+
assoc const id {} had `default_unstable` with feature `{}`",
127+
id.0, default_unstable.feature
128+
)),
129+
);
130+
}
131+
}
132+
ItemEnum::AssocType { generics, bounds, type_, default_unstable } => {
119133
self.check_generics(generics);
120134
bounds.iter().for_each(|b| self.check_generic_bound(b));
121135
if let Some(ty) = type_ {
122136
self.check_type(ty);
137+
} else if let Some(default_unstable) = default_unstable {
138+
self.fail(
139+
id,
140+
ErrorKind::Custom(format!(
141+
"`default_unstable` must be `None` when `type_` is `None`, but \
142+
assoc type id {} had `default_unstable` with feature `{}`",
143+
id.0, default_unstable.feature
144+
)),
145+
);
123146
}
124147
}
125148
}
@@ -194,9 +217,21 @@ impl<'a> Validator<'a> {
194217
}
195218
}
196219

197-
fn check_function(&mut self, x: &'a Function) {
220+
fn check_function(&mut self, x: &'a Function, id: &Id) {
198221
self.check_generics(&x.generics);
199222
self.check_function_signature(&x.sig);
223+
if !x.has_body
224+
&& let Some(default_unstable) = &x.default_unstable
225+
{
226+
self.fail(
227+
id,
228+
ErrorKind::Custom(format!(
229+
"`default_unstable` must be `None` when `has_body == false`, but \
230+
function item id {} had `default_unstable` with feature `{}`",
231+
id.0, default_unstable.feature
232+
)),
233+
);
234+
}
200235
}
201236

202237
fn check_trait(&mut self, x: &'a Trait, id: &Id) {

0 commit comments

Comments
 (0)