1
1
use proc_macro2:: { Span , TokenStream } ;
2
2
use quote:: { quote, quote_spanned, ToTokens } ;
3
3
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
+ }
5
28
6
29
/// Generate a type parameter with the given `suffix` that does not conflict with
7
30
/// any of the `existing` generics.
@@ -31,9 +54,9 @@ fn gen_interner(structure: &mut synstructure::Structure<'_>) -> TokenStream {
31
54
} )
32
55
}
33
56
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" ) )
37
60
}
38
61
39
62
pub struct Foldable ;
@@ -160,23 +183,30 @@ pub fn traversable_derive<T: Traversable>(
160
183
structure. add_where_predicate ( parse_quote ! { Self : #supertraits } ) ;
161
184
}
162
185
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 ( ) ;
169
189
}
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.
170
192
structure. add_where_predicate ( parse_quote ! { Self : #skip_traversal } ) ;
171
193
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
+ } )
172
202
} else {
173
203
// We add predicates to each generic field type, rather than to our generic type parameters.
174
204
// This results in a "perfect derive" that avoids having to propagate `#[skip_traversal]` annotations
175
205
// into wrapping types, but it can result in trait solver cycles if any type parameters are involved
176
206
// in recursive type definitions; fortunately that is not the case (yet).
177
207
let mut predicates = HashMap :: new ( ) ;
178
208
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 ) ;
180
210
if variant. referenced_ty_params ( ) . is_empty ( ) {
181
211
if let Some ( span) = skipped_variant_span {
182
212
return quote_spanned ! ( span => {
@@ -186,7 +216,7 @@ pub fn traversable_derive<T: Traversable>(
186
216
}
187
217
T :: arm ( variant, |bind| {
188
218
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 ) ) ;
190
220
if bind. referenced_ty_params ( ) . is_empty ( ) {
191
221
if skipped_variant_span. is_none ( ) && let Some ( span) = skipped_span {
192
222
return quote_spanned ! ( span => {
0 commit comments