Skip to content

Commit e17a2ea

Browse files
committed
Explain no-op traversable impls
1 parent 047330b commit e17a2ea

File tree

6 files changed

+84
-9
lines changed

6 files changed

+84
-9
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(but_impl_despite_boring_because = "
3261+
`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
@@ -88,10 +88,21 @@ decl_derive!(
8888
/// that may be of interest to folders (thus preventing fields from being left unchanged
8989
/// erroneously).
9090
///
91+
/// By default, `TypeFoldable` cannot be derived on types that contain nothing that may be of
92+
/// interest to folders as such an implementation is wholly superfluous and probably in error.
93+
/// However, on occasion it may nevertheless be necessary to implement `TypeFoldable` for such
94+
/// types even though any such fold will always be a noop (e.g. so that instances can be used
95+
/// in a generic context that is constrained to implementors of the trait); in such situations
96+
/// one can add a `#[skip_traversal(but_impl_despite_boring_because = "<reason>"]` attribute.
97+
///
9198
/// In some rare situations, it may be desirable to skip folding of an item or field (or
92-
/// variant) that might otherwise be of interest to folders: **this is dangerous and could lead
93-
/// to miscompilation if user expectations are not met!** Nevertheless, such can be achieved
94-
/// via a `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
99+
/// variant) that might otherwise be of interest to folders. This can be achieved via a
100+
/// `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
101+
/// Whereas the preceding usages of the `#[skip_traversal]` attribute are guaranteed to be
102+
/// sound by constraining the relevant type to implementors of the `BoringTraversable`
103+
/// trait, use of `despite_potential_miscompilation_because` does not add such constraint or
104+
/// provide any such guarantee. **It is therefore dangerous and could lead to miscompilation
105+
/// if user expectations are not met!**
95106
///
96107
/// The derived implementation will use `TyCtxt<'tcx>` as the interner iff the annotated type
97108
/// has a `'tcx` lifetime parameter; otherwise it will be generic over all interners. It
@@ -119,10 +130,21 @@ decl_derive!(
119130
/// fields do not contain anything that may be of interest to visitors (thus preventing fields
120131
/// from being so skipped erroneously).
121132
///
133+
/// By default, `TypeVisitable` cannot be derived on types that contain nothing that may be of
134+
/// interest to visitors as such an implementation is wholly superfluous and probably in error.
135+
/// However, on occasion it may nevertheless be necessary to implement `TypeVisitable` for such
136+
/// types even though any such visit will always be a noop (e.g. so that instances can be used
137+
/// in a generic context that is constrained to implementors of the trait); in such situations
138+
/// one can add a `#[skip_traversal(but_impl_despite_boring_because = "<reason>"]` attribute.
139+
///
122140
/// In some rare situations, it may be desirable to skip visiting of an item or field (or
123-
/// variant) that might otherwise be of interest to visitors: **this is dangerous and could lead
124-
/// to miscompilation if user expectations are not met!** Nevertheless, such can be achieved
125-
/// via a `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
141+
/// variant) that might otherwise be of interest to visitors. This can be achieved via a
142+
/// `#[skip_traversal(despite_potential_miscompilation_because = "<reason>"]` attribute.
143+
/// Whereas the preceding usages of the `#[skip_traversal]` attribute are guaranteed to be
144+
/// sound by constraining the relevant type to implementors of the `BoringTraversable`
145+
/// trait, use of `despite_potential_miscompilation_because` does not add such constraint or
146+
/// provide any such guarantee. **It is therefore dangerous and could lead to miscompilation
147+
/// if user expectations are not met!**
126148
///
127149
/// The derived implementation will use `TyCtxt<'tcx>` as the interner iff the annotated type
128150
/// has a `'tcx` lifetime parameter; otherwise it will be generic over all interners. It

compiler/rustc_macros/src/traversable.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ fn gen_param(suffix: impl ToString, existing: &Generics) -> Ident {
2626

2727
mod kw {
2828
syn::custom_keyword!(because_boring); // only applicable on internable fields
29+
syn::custom_keyword!(but_impl_despite_boring_because); // only applicable on non-internable types
2930
syn::custom_keyword!(despite_potential_miscompilation_because); // always applicable on internable types and fields
3031
}
3132

@@ -35,6 +36,9 @@ trait SkipReasonKeyword: Default + Parse + ToTokens {
3536
impl SkipReasonKeyword for kw::because_boring {
3637
const HAS_EXPLANATION: bool = false;
3738
}
39+
impl SkipReasonKeyword for kw::but_impl_despite_boring_because {
40+
const HAS_EXPLANATION: bool = true;
41+
}
3842
impl SkipReasonKeyword for kw::despite_potential_miscompilation_because {
3943
const HAS_EXPLANATION: bool = true;
4044
}
@@ -99,7 +103,7 @@ impl WhenToSkip {
99103
attr.meta
100104
.require_list()
101105
.and_then(|list| match (IS_TYPE, ty) {
102-
(true, Boring) => Err(Error::new_spanned(attr, "boring types are always skipped, so this attribute is superfluous")),
106+
(true, Boring) => list.parse_args::<SkipReason<kw::but_impl_despite_boring_because>>().and_then(|_| Ok(Self::Always(attr.span()))),
103107
(true, _) | (false, NotGeneric) => list.parse_args::<SkipReason<kw::despite_potential_miscompilation_because>>().and(Ok(Self::Forced)),
104108
(false, Generic) => list.parse_args::<SkipReason<kw::because_boring>>().and_then(|_| Ok(Self::Always(attr.span())))
105109
.or_else(|_| list.parse_args::<SkipReason<kw::despite_potential_miscompilation_because>>().and(Ok(Self::Forced)))
@@ -332,6 +336,15 @@ pub fn traversable_derive<T: Traversable>(
332336
structure.add_where_predicate(parse_quote! { Self: #skip_traversal });
333337
}
334338
T::traverse(quote! { self }, true)
339+
} else if ty == Type::Boring {
340+
return Err(Error::new(
341+
Span::call_site(),
342+
"\
343+
Traversal of boring types are no-ops by default, so explicitly deriving the traversable traits for them is rarely necessary.\n\
344+
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\
345+
otherwise add `#[skip_traversal(but_impl_despite_boring_because = \"<reason for implementation>\")]` to this type.\
346+
",
347+
));
335348
} else {
336349
// We add predicates to each generic field type, rather than to our generic type parameters.
337350
// This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations

compiler/rustc_macros/src/traversable/tests.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,40 @@ fn interesting_fields_are_constrained() {
194194
}
195195

196196
#[test]
197-
fn skipping_boring_type_is_superfluous() {
197+
fn boring_type_requires_justification() {
198198
expect! {
199+
{
200+
struct NothingInteresting<'a>;
201+
} => "Traversal of boring types are no-ops by default"
202+
199203
{
200204
#[skip_traversal()]
201205
struct NothingInteresting<'a>;
202-
} => "boring types are always skipped, so this attribute is superfluous"
206+
} => "unexpected end of input, expected `but_impl_despite_boring_because`"
207+
208+
{
209+
#[skip_traversal(despite_potential_miscompilation_because = ".")]
210+
struct NothingInteresting<'a>;
211+
} => "expected `but_impl_despite_boring_because`"
212+
213+
{
214+
#[skip_traversal(but_impl_despite_boring_because = ".", despite_potential_miscompilation_because = ".")]
215+
struct NothingInteresting<'a>;
216+
} => "unexpected token"
217+
218+
{
219+
#[skip_traversal(but_impl_despite_boring_because = ".")]
220+
struct NothingInteresting<'a>;
221+
} => {
222+
impl<'a, I: Interner> TypeFoldable<I> for NothingInteresting<'a>
223+
where
224+
Self: BoringTraversable // impl only applies when type actually contains nothing interesting
225+
{
226+
fn try_fold_with<T: FallibleTypeFolder<I>>(self, folder: &mut T) -> Result<Self, T::Error> {
227+
Ok(self) // no attempt to fold
228+
}
229+
}
230+
}
203231
}
204232
}
205233

compiler/rustc_middle/src/ty/mod.rs

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

251251
#[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable, Debug)]
252252
#[derive(TypeFoldable, TypeVisitable)]
253+
#[skip_traversal(but_impl_despite_boring_because = "
254+
`ImplPolarity` impls `Relate`, which is a subtrait of `TypeFoldable`.
255+
")]
253256
pub enum ImplPolarity {
254257
/// `impl Trait for Type`
255258
Positive,
@@ -305,6 +308,9 @@ pub enum Visibility<Id = LocalDefId> {
305308

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

compiler/rustc_target/src/spec/abi.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(but_impl_despite_boring_because = "
13+
`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)