11use proc_macro2:: { Span , TokenStream } ;
22use quote:: { quote, quote_spanned, ToTokens } ;
33use 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
3962pub 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 => {
0 commit comments