Skip to content

Commit bc4df58

Browse files
committed
Require explanation of trivial traversable derives
1 parent 1a88b49 commit bc4df58

File tree

8 files changed

+96
-11
lines changed

8 files changed

+96
-11
lines changed

compiler/rustc_hir/src/hir.rs

+3
Original file line numberDiff line numberDiff line change
@@ -3257,6 +3257,9 @@ impl<'hir> Item<'hir> {
32573257

32583258
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
32593259
#[derive(Encodable, Decodable, HashStable_Generic, TypeFoldable, TypeVisitable)]
3260+
#[skip_traversal(
3261+
but_impl_despite_trivial_because = "`Unsafety` impls `Relate`, which is a subtrait of `TypeFoldable`."
3262+
)]
32603263
pub enum Unsafety {
32613264
Unsafe,
32623265
Normal,

compiler/rustc_macros/src/lib.rs

+28-6
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,21 @@ decl_derive!(
9090
/// only be applicable if `T` does not contain anything that may be of interest to folders
9191
/// (thus preventing fields from being so-skipped erroneously).
9292
///
93+
/// By default, `TypeFoldable` cannot be derived on types that contain nothing that may be of
94+
/// interest to folders as such an implementation is wholly superfluous and probably in error.
95+
/// However, on occasion it may nevertheless be necessary to implement `TypeFoldable` for such
96+
/// types even though any such fold will always be a noop (e.g. so that instances can be used
97+
/// in a generic context that is constrained to implementors of the trait); in such situations
98+
/// one can add a `#[skip_traversal(but_impl_despite_trivial_because = "<reason>"]` attribute.
99+
///
93100
/// In some rare situations, it may be desirable to skip folding of an item or field (or
94-
/// variant) that might otherwise be of interest to folders: **this is dangerous and could lead
95-
/// to miscompilation if user expectations are not met!** Nevertheless, such can be achieved
96-
/// via a `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
101+
/// variant) that might otherwise be of interest to folders. This can be achieved via a
102+
/// `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
103+
/// Whereas the preceding usages of the `#[skip_traversal]` attribute are guaranteed to be
104+
/// sound by constraining the interner to implementors of the `TriviallyTraverses<T>` trait,
105+
/// use of `despite_potential_miscompilation_because` does not add such constraint or provide
106+
/// any such guarantee. **It is therefore dangerous and could lead to miscompilation if user
107+
/// expectations are not met!**
97108
///
98109
/// The derived implementation will use `TyCtxt<'tcx>` as the interner iff the annotated type
99110
/// has a `'tcx` lifetime parameter; otherwise it will be generic over all interners. It
@@ -124,10 +135,21 @@ decl_derive!(
124135
/// only be applicable if `T` does not contain anything that may be of interest to visitors
125136
/// (thus preventing fields from being so-skipped erroneously).
126137
///
138+
/// By default, `TypeVisitable` cannot be derived on types that contain nothing that may be of
139+
/// interest to visitors as such an implementation is wholly superfluous and probably in error.
140+
/// However, on occasion it may nevertheless be necessary to implement `TypeVisitable` for such
141+
/// types even though any such visit will always be a noop (e.g. so that instances can be used
142+
/// in a generic context that is constrained to implementors of the trait); in such situations
143+
/// one can add a `#[skip_traversal(but_impl_despite_trivial_because = "<reason>"]` attribute.
144+
///
127145
/// In some rare situations, it may be desirable to skip visiting of an item or field (or
128-
/// variant) that might otherwise be of interest to visitors: **this is dangerous and could lead
129-
/// to miscompilation if user expectations are not met!** Nevertheless, such can be achieved
130-
/// via a `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
146+
/// variant) that might otherwise be of interest to visitors. This can be achieved via a
147+
/// `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
148+
/// Whereas the preceding usages of the `#[skip_traversal]` attribute are guaranteed to be
149+
/// sound by constraining the interner to implementors of the `TriviallyTraverses<T>` trait,
150+
/// use of `despite_potential_miscompilation_because` does not add such constraint or provide
151+
/// any such guarantee. **It is therefore dangerous and could lead to miscompilation if user
152+
/// expectations are not met!**
131153
///
132154
/// The derived implementation will use `TyCtxt<'tcx>` as the interner iff the annotated type
133155
/// has a `'tcx` lifetime parameter; otherwise it will be generic over all interners. It

compiler/rustc_macros/src/traversable.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ impl WhenToSkip {
9090
if attr.path().is_ident("skip_traversal") {
9191
found = Some(attr);
9292
attr.parse_nested_meta(|meta| {
93+
if IS_TYPE
94+
&& ty == Trivial
95+
&& meta.path.is_ident("but_impl_despite_trivial_because")
96+
{
97+
parse_reason(&meta)?;
98+
*self |= Self::Always(meta.error("").span());
99+
return Ok(());
100+
}
101+
93102
if meta.path.is_ident("despite_potential_miscompilation_because") {
94103
parse_reason(&meta)?;
95104
*self |= Self::Forced;
@@ -111,9 +120,7 @@ impl WhenToSkip {
111120
attr,
112121
if IS_TYPE {
113122
match ty {
114-
Trivial => {
115-
"trivially traversable types are always skipped, so this attribute is superfluous"
116-
}
123+
Trivial => return Ok(()), // triggers error in caller
117124
_ => {
118125
"\
119126
Justification must be provided for skipping this potentially interesting type, by specifying\n\
@@ -358,6 +365,15 @@ pub fn traversable_derive<T: Traversable>(
358365
structure.add_where_predicate(skip_traversal(&<Token![Self]>::default()));
359366
}
360367
T::traverse(quote! { self }, true, &interner)
368+
} else if ty == Trivial {
369+
return Err(Error::new(
370+
Span::call_site(),
371+
"\
372+
Traversal of trivial types are no-ops by default, so explicitly deriving the traversable traits for them is rarely necessary.\n\
373+
If the need has arisen to due the appearance of this type in an anonymous tuple, consider replacing that tuple with a named struct;\n\
374+
otherwise add `#[skip_traversal(but_impl_despite_trivial_because = \"<reason for implementation>\")]` to this type.\
375+
",
376+
));
361377
} else {
362378
// We add predicates to each generic field type, rather than to our generic type parameters.
363379
// This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations

compiler/rustc_macros/src/traversable/tests.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,41 @@ fn interesting_fields_are_constrained() {
203203
}
204204

205205
#[test]
206-
fn skipping_trivial_type_is_superfluous() {
206+
fn skipping_trivial_type_requires_justification() {
207207
expect! {
208+
{
209+
struct NothingInteresting<'a>;
210+
} => "Traversal of trivial types are no-ops by default"
211+
208212
{
209213
#[skip_traversal()]
210214
struct NothingInteresting<'a>;
211-
} => "trivially traversable types are always skipped, so this attribute is superfluous"
215+
} => "Traversal of trivial types are no-ops by default"
216+
217+
{
218+
#[skip_traversal(but_impl_despite_trivial_because = ".", despite_potential_miscompilation_because = ".")]
219+
struct NothingInteresting<'a>;
220+
} => {
221+
impl<'a, I: Interner> TypeFoldable<I> for NothingInteresting<'a> {
222+
fn try_fold_with<T: FallibleTypeFolder<I>>(self, folder: &mut T) -> Result<Self, T::Error> {
223+
Ok(self) // no attempt to fold
224+
}
225+
}
226+
}
227+
228+
{
229+
#[skip_traversal(but_impl_despite_trivial_because = ".")]
230+
struct NothingInteresting<'a>;
231+
} => {
232+
impl<'a, I: Interner> TypeFoldable<I> for NothingInteresting<'a>
233+
where
234+
I: TriviallyTraverses<Self> // impl only applies when type actually contains nothing interesting
235+
{
236+
fn try_fold_with<T: FallibleTypeFolder<I>>(self, folder: &mut T) -> Result<Self, T::Error> {
237+
Ok(self) // no attempt to fold
238+
}
239+
}
240+
}
212241
}
213242
}
214243

compiler/rustc_middle/src/ty/closure.rs

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ pub type MinCaptureList<'tcx> = Vec<CapturedPlace<'tcx>>;
7878
/// `tcx.closure_env_ty()`.
7979
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, TyEncodable, TyDecodable)]
8080
#[derive(HashStable, TypeFoldable, TypeVisitable)]
81+
#[skip_traversal(
82+
but_impl_despite_trivial_because = "traversed generically in `rustc_type_ir::PredicateKind<TyCtxt>`"
83+
)]
8184
pub enum ClosureKind {
8285
// Warning: Ordering is significant here! The ordering is chosen
8386
// because the trait Fn is a subtrait of FnMut and so in turn, and

compiler/rustc_middle/src/ty/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ pub enum ImplSubject<'tcx> {
251251

252252
#[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable, Debug)]
253253
#[derive(TypeFoldable, TypeVisitable)]
254+
#[skip_traversal(but_impl_despite_trivial_because = "
255+
`ImplPolarity` impls `Relate`, which is a subtrait of `TypeFoldable`.
256+
")]
254257
pub enum ImplPolarity {
255258
/// `impl Trait for Type`
256259
Positive,
@@ -306,6 +309,9 @@ pub enum Visibility<Id = LocalDefId> {
306309

307310
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable, TyEncodable, TyDecodable)]
308311
#[derive(TypeFoldable, TypeVisitable)]
312+
#[skip_traversal(but_impl_despite_trivial_because = "
313+
`BoundConstness` impls `Relate`, which is a subtrait of `TypeFoldable`.
314+
")]
309315
pub enum BoundConstness {
310316
/// `T: Trait`
311317
NotConst,

compiler/rustc_span/src/def_id.rs

+3
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ impl<D: Decoder> Decodable<D> for DefIndex {
239239
///
240240
/// You can create a `DefId` from a `LocalDefId` using `local_def_id.to_def_id()`.
241241
#[derive(Clone, PartialEq, Eq, Copy, TypeFoldable, TypeVisitable)]
242+
#[skip_traversal(
243+
but_impl_despite_trivial_because = "traversed generically in `rustc_type_ir::PredicateKind<TyCtxt>`"
244+
)]
242245
// Don't derive order on 64-bit big-endian, so we can be consistent regardless of field order.
243246
#[cfg_attr(not(all(target_pointer_width = "64", target_endian = "big")), derive(PartialOrd, Ord))]
244247
// On below-64 bit systems we can simply use the derived `Hash` impl

compiler/rustc_target/src/spec/abi/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ mod tests;
99

1010
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
1111
#[derive(HashStable_Generic, Encodable, Decodable, TypeFoldable, TypeVisitable)]
12+
#[skip_traversal(
13+
but_impl_despite_trivial_because = "`Abi` impls `Relate`, which is a subtrait of `TypeFoldable`."
14+
)]
1215
pub enum Abi {
1316
// Some of the ABIs come first because every time we add a new ABI, we have to re-bless all the
1417
// hashing tests. These are used in many places, so giving them stable values reduces test

0 commit comments

Comments
 (0)