Skip to content

Commit bf618ac

Browse files
committed
Explain no-op traversable impls
1 parent 11296fa commit bf618ac

File tree

5 files changed

+64
-14
lines changed

5 files changed

+64
-14
lines changed

Diff for: compiler/rustc_hir/src/hir.rs

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

31923192
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
31933193
#[derive(Encodable, Decodable, HashStable_Generic, TypeFoldable, TypeVisitable)]
3194+
#[skip_traversal(but_impl_because = "
3195+
`Unsafety` impls `Relate`, which is a subtrait of `TypeFoldable`.
3196+
")]
31943197
pub enum Unsafety {
31953198
Unsafe,
31963199
Normal,

Diff for: compiler/rustc_macros/src/lib.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,11 @@ decl_derive!(
139139
/// accomplished via method calls that might not get optimised away. One can therefore annotate
140140
/// the type itself with `#[skip_traversal]` to immediately return `self` instead; as above,
141141
/// such derived implementations are only applicable if the annotated type does not contain
142-
/// anything that may be of interest to a folder.
142+
/// anything that may be of interest to a folder. Moreover, since such implementations should
143+
/// rarely be necessary, a descriptive reason for the implementation must also be provided:
144+
/// e.g. `#[skip_traversal(but_impl_because = "<explanation here>")]`. If applied to a
145+
/// non-generic type (which cannot ever contain anything that may be of interest to folders),
146+
/// this attribute is mandatory.
143147
///
144148
/// The derived implementation will use `TyCtxt<'tcx>` as the interner iff the annotated type
145149
/// has a `'tcx` lifetime parameter; otherwise it will be generic over all interners. It
@@ -173,7 +177,11 @@ decl_derive!(
173177
/// accomplished via method calls that might not get optimised away. One can therefore annotate
174178
/// the type itself with `#[skip_traversal]` to immediately return `Continue(())` instead; as
175179
/// above, such derived implementations are only applicable if the annotated type does not
176-
/// contain anything that may be of interest to visitors.
180+
/// contain anything that may be of interest to visitors. Moreover, since such implementations
181+
/// should rarely be necessary, a descriptive reason for the implementation must also be
182+
/// provided: e.g. `#[skip_traversal(but_impl_because = "<explanation here>")]`. If applied to
183+
/// a non-generic type (which cannot ever contain anything that may be of interest to
184+
/// visitors), this attribute is mandatory.
177185
///
178186
/// The derived implementation will use `TyCtxt<'tcx>` as the interner iff the annotated type
179187
/// has a `'tcx` lifetime parameter; otherwise it will be generic over all interners. It

Diff for: compiler/rustc_macros/src/traversable.rs

+42-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,30 @@
11
use proc_macro2::{Span, TokenStream};
22
use quote::{quote, quote_spanned, ToTokens};
33
use std::collections::HashMap;
4-
use syn::{parse_quote, spanned::Spanned, Attribute, Generics, Ident};
4+
use syn::{
5+
parse::{Error, ParseStream},
6+
parse_quote,
7+
spanned::Spanned,
8+
Attribute, Generics, Ident, LitStr, Token,
9+
};
10+
11+
mod kw {
12+
syn::custom_keyword!(but_impl_because);
13+
}
14+
15+
fn parse_skip_reason(input: ParseStream<'_>) -> Result<(), Error> {
16+
input.parse::<kw::but_impl_because>()?;
17+
input.parse::<Token![=]>()?;
18+
let reason = input.parse::<LitStr>()?;
19+
if reason.value().trim().is_empty() {
20+
Err(Error::new_spanned(
21+
reason,
22+
"the value of `but_impl_because` must be a non-empty string",
23+
))
24+
} else {
25+
Ok(())
26+
}
27+
}
528

629
/// Generate a type parameter with the given `suffix` that does not conflict with
730
/// any of the `existing` generics.
@@ -31,9 +54,9 @@ fn gen_interner(structure: &mut synstructure::Structure<'_>) -> TokenStream {
3154
})
3255
}
3356

34-
/// Returns the `Span` of the first `#[skip_traversal]` attribute in `attrs`.
35-
fn find_skip_traversal_attribute(attrs: &[Attribute]) -> Option<Span> {
36-
attrs.iter().find(|&attr| *attr == parse_quote! { #[skip_traversal] }).map(Spanned::span)
57+
/// Returns the first `#[skip_traversal]` attribute in `attrs`.
58+
fn find_skip_traversal_attribute(attrs: &[Attribute]) -> Option<&Attribute> {
59+
attrs.iter().find(|&attr| attr.path.is_ident("skip_traversal"))
3760
}
3861

3962
pub struct Foldable;
@@ -160,23 +183,30 @@ pub fn traversable_derive<T: Traversable>(
160183
structure.add_where_predicate(parse_quote! { Self: #supertraits });
161184
}
162185

163-
let body = if let Some(_span) = find_skip_traversal_attribute(&ast.attrs) {
164-
if !is_generic {
165-
// FIXME: spanned error causes ICE: "suggestion must not have overlapping parts"
166-
return quote!({
167-
::core::compile_error!("non-generic types are automatically skipped where possible");
168-
});
186+
let body = if let Some(attr) = find_skip_traversal_attribute(&ast.attrs) {
187+
if let Err(err) = attr.parse_args_with(parse_skip_reason) {
188+
return err.into_compile_error();
169189
}
190+
// If `is_generic`, it's possible that this no-op impl may not be applicable; but that's fine as it
191+
// will cause a compilation error forcing removal of the inappropriate `#[skip_traversal]` attribute.
170192
structure.add_where_predicate(parse_quote! { Self: #skip_traversal });
171193
T::traverse(quote! { self }, true)
194+
} else if !is_generic {
195+
quote_spanned!(ast.ident.span() => {
196+
::core::compile_error!("\
197+
traversal of non-generic types are no-ops by default, so explicitly deriving the traversable traits for them is rarely necessary\n\
198+
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\
199+
otherwise add `#[skip_traversal(but_impl_because = \"<reason for implementation>\")]` to this type\
200+
")
201+
})
172202
} else {
173203
// We add predicates to each generic field type, rather than to our generic type parameters.
174204
// This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations
175205
// into wrapping types, but it can result in trait solver cycles if any type parameters are involved
176206
// in recursive type definitions; fortunately that is not the case (yet).
177207
let mut predicates = HashMap::new();
178208
let arms = structure.each_variant(|variant| {
179-
let skipped_variant_span = find_skip_traversal_attribute(&variant.ast().attrs);
209+
let skipped_variant_span = find_skip_traversal_attribute(&variant.ast().attrs).map(Spanned::span);
180210
if variant.referenced_ty_params().is_empty() {
181211
if let Some(span) = skipped_variant_span {
182212
return quote_spanned!(span => {
@@ -186,7 +216,7 @@ pub fn traversable_derive<T: Traversable>(
186216
}
187217
T::arm(variant, |bind| {
188218
let ast = bind.ast();
189-
let skipped_span = skipped_variant_span.or_else(|| find_skip_traversal_attribute(&ast.attrs));
219+
let skipped_span = skipped_variant_span.or_else(|| find_skip_traversal_attribute(&ast.attrs).map(Spanned::span));
190220
if bind.referenced_ty_params().is_empty() {
191221
if skipped_variant_span.is_none() && let Some(span) = skipped_span {
192222
return quote_spanned!(span => {

Diff for: compiler/rustc_middle/src/ty/mod.rs

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

250250
#[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable, Debug)]
251251
#[derive(TypeFoldable, TypeVisitable)]
252+
#[skip_traversal(but_impl_because = "
253+
`ImplPolarity` impls `Relate`, which is a subtrait of `TypeFoldable`.
254+
")]
252255
pub enum ImplPolarity {
253256
/// `impl Trait for Type`
254257
Positive,
@@ -292,6 +295,9 @@ pub enum Visibility<Id = LocalDefId> {
292295

293296
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, HashStable, TyEncodable, TyDecodable)]
294297
#[derive(TypeFoldable, TypeVisitable)]
298+
#[skip_traversal(but_impl_because = "
299+
`BoundConstness` impls `Relate`, which is a subtrait of `TypeFoldable`.
300+
")]
295301
pub enum BoundConstness {
296302
/// `T: Trait`
297303
NotConst,

Diff for: 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_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)