1
1
use proc_macro:: TokenStream ;
2
2
use proc_macro2:: Span ;
3
3
use quote:: { quote, quote_spanned, ToTokens } ;
4
+ use syn:: parse:: Parser ;
5
+
6
+ // syn::AttributeArgs does not implement syn::Parse
7
+ type AttributeArgs = syn:: punctuated:: Punctuated < syn:: NestedMeta , syn:: Token ![ , ] > ;
4
8
5
9
#[ derive( Clone , Copy , PartialEq ) ]
6
10
enum RuntimeFlavor {
@@ -27,6 +31,13 @@ struct FinalConfig {
27
31
start_paused : Option < bool > ,
28
32
}
29
33
34
+ /// Config used in case of the attribute not being able to build a valid config
35
+ const DEFAULT_ERROR_CONFIG : FinalConfig = FinalConfig {
36
+ flavor : RuntimeFlavor :: CurrentThread ,
37
+ worker_threads : None ,
38
+ start_paused : None ,
39
+ } ;
40
+
30
41
struct Configuration {
31
42
rt_multi_thread_available : bool ,
32
43
default_flavor : RuntimeFlavor ,
@@ -184,13 +195,13 @@ fn parse_bool(bool: syn::Lit, span: Span, field: &str) -> Result<bool, syn::Erro
184
195
}
185
196
}
186
197
187
- fn parse_knobs (
188
- mut input : syn:: ItemFn ,
189
- args : syn :: AttributeArgs ,
198
+ fn build_config (
199
+ input : syn:: ItemFn ,
200
+ args : AttributeArgs ,
190
201
is_test : bool ,
191
202
rt_multi_thread : bool ,
192
- ) -> Result < TokenStream , syn:: Error > {
193
- if input. sig . asyncness . take ( ) . is_none ( ) {
203
+ ) -> Result < FinalConfig , syn:: Error > {
204
+ if input. sig . asyncness . is_none ( ) {
194
205
let msg = "the `async` keyword is missing from the function declaration" ;
195
206
return Err ( syn:: Error :: new_spanned ( input. sig . fn_token , msg) ) ;
196
207
}
@@ -278,7 +289,11 @@ fn parse_knobs(
278
289
}
279
290
}
280
291
281
- let config = config. build ( ) ?;
292
+ config. build ( )
293
+ }
294
+
295
+ fn parse_knobs ( mut input : syn:: ItemFn , is_test : bool , config : FinalConfig ) -> TokenStream {
296
+ input. sig . asyncness = None ;
282
297
283
298
// If type mismatch occurs, the current rustc points to the last statement.
284
299
let ( last_stmt_start_span, last_stmt_end_span) = {
@@ -353,36 +368,58 @@ fn parse_knobs(
353
368
#input
354
369
} ;
355
370
356
- Ok ( result. into ( ) )
371
+ result. into ( )
372
+ }
373
+
374
+ fn token_stream_with_error ( mut tokens : TokenStream , error : syn:: Error ) -> TokenStream {
375
+ tokens. extend ( TokenStream :: from ( error. into_compile_error ( ) ) ) ;
376
+ tokens
357
377
}
358
378
359
379
#[ cfg( not( test) ) ] // Work around for rust-lang/rust#62127
360
380
pub ( crate ) fn main ( args : TokenStream , item : TokenStream , rt_multi_thread : bool ) -> TokenStream {
361
- let input = syn:: parse_macro_input!( item as syn:: ItemFn ) ;
362
- let args = syn:: parse_macro_input!( args as syn:: AttributeArgs ) ;
381
+ // If any of the steps for this macro fail, we still want to expand to an item that is as close
382
+ // to the expected output as possible. This helps out IDEs such that completions and other
383
+ // related features keep working.
384
+ let input: syn:: ItemFn = match syn:: parse ( item. clone ( ) ) {
385
+ Ok ( it) => it,
386
+ Err ( e) => return token_stream_with_error ( item, e) ,
387
+ } ;
363
388
364
- if input. sig . ident == "main" && !input. sig . inputs . is_empty ( ) {
389
+ let config = if input. sig . ident == "main" && !input. sig . inputs . is_empty ( ) {
365
390
let msg = "the main function cannot accept arguments" ;
366
- return syn:: Error :: new_spanned ( & input. sig . ident , msg)
367
- . to_compile_error ( )
368
- . into ( ) ;
369
- }
391
+ Err ( syn:: Error :: new_spanned ( & input. sig . ident , msg) )
392
+ } else {
393
+ AttributeArgs :: parse_terminated
394
+ . parse ( args)
395
+ . and_then ( |args| build_config ( input. clone ( ) , args, false , rt_multi_thread) )
396
+ } ;
370
397
371
- parse_knobs ( input, args, false , rt_multi_thread) . unwrap_or_else ( |e| e. to_compile_error ( ) . into ( ) )
398
+ match config {
399
+ Ok ( config) => parse_knobs ( input, false , config) ,
400
+ Err ( e) => token_stream_with_error ( parse_knobs ( input, false , DEFAULT_ERROR_CONFIG ) , e) ,
401
+ }
372
402
}
373
403
374
404
pub ( crate ) fn test ( args : TokenStream , item : TokenStream , rt_multi_thread : bool ) -> TokenStream {
375
- let input = syn:: parse_macro_input!( item as syn:: ItemFn ) ;
376
- let args = syn:: parse_macro_input!( args as syn:: AttributeArgs ) ;
377
-
378
- for attr in & input. attrs {
379
- if attr. path . is_ident ( "test" ) {
380
- let msg = "second test attribute is supplied" ;
381
- return syn:: Error :: new_spanned ( & attr, msg)
382
- . to_compile_error ( )
383
- . into ( ) ;
384
- }
385
- }
405
+ // If any of the steps for this macro fail, we still want to expand to an item that is as close
406
+ // to the expected output as possible. This helps out IDEs such that completions and other
407
+ // related features keep working.
408
+ let input: syn:: ItemFn = match syn:: parse ( item. clone ( ) ) {
409
+ Ok ( it) => it,
410
+ Err ( e) => return token_stream_with_error ( item, e) ,
411
+ } ;
412
+ let config = if let Some ( attr) = input. attrs . iter ( ) . find ( |attr| attr. path . is_ident ( "test" ) ) {
413
+ let msg = "second test attribute is supplied" ;
414
+ Err ( syn:: Error :: new_spanned ( & attr, msg) )
415
+ } else {
416
+ AttributeArgs :: parse_terminated
417
+ . parse ( args)
418
+ . and_then ( |args| build_config ( input. clone ( ) , args, true , rt_multi_thread) )
419
+ } ;
386
420
387
- parse_knobs ( input, args, true , rt_multi_thread) . unwrap_or_else ( |e| e. to_compile_error ( ) . into ( ) )
421
+ match config {
422
+ Ok ( config) => parse_knobs ( input, true , config) ,
423
+ Err ( e) => token_stream_with_error ( parse_knobs ( input, true , DEFAULT_ERROR_CONFIG ) , e) ,
424
+ }
388
425
}
0 commit comments