@@ -52,7 +52,22 @@ use proc_macro::{Span, TokenStream};
52
52
use proc_macro2:: Ident ;
53
53
use proc_macro_crate:: { crate_name, FoundCrate } ;
54
54
use proc_macro_error2:: abort;
55
- use syn:: { parse, parse:: Error as ParseError , spanned:: Spanned , Item , ItemFn , ReturnType , Type } ;
55
+ use quote:: { format_ident, quote} ;
56
+ use syn:: {
57
+ parse,
58
+ parse:: Error as ParseError ,
59
+ spanned:: Spanned ,
60
+ Data ,
61
+ DataStruct ,
62
+ GenericArgument ,
63
+ Item ,
64
+ ItemFn ,
65
+ Path ,
66
+ PathArguments ,
67
+ PathSegment ,
68
+ ReturnType ,
69
+ Type ,
70
+ } ;
56
71
57
72
use self :: interrupt:: { check_attr_whitelist, WhiteListCaller } ;
58
73
@@ -238,8 +253,8 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
238
253
/// esp_hal::interrupt::Priority::Priority2)]`.
239
254
///
240
255
/// If no priority is given, `Priority::min()` is assumed
241
- #[ proc_macro_error2:: proc_macro_error]
242
256
#[ proc_macro_attribute]
257
+ #[ proc_macro_error2:: proc_macro_error]
243
258
pub fn handler ( args : TokenStream , input : TokenStream ) -> TokenStream {
244
259
#[ derive( Debug , FromMeta ) ]
245
260
struct MacroArgs {
@@ -341,8 +356,8 @@ pub fn load_lp_code(input: TokenStream) -> TokenStream {
341
356
342
357
/// Marks the entry function of a LP core / ULP program.
343
358
#[ cfg( any( feature = "is-lp-core" , feature = "is-ulp-core" ) ) ]
344
- #[ proc_macro_error2:: proc_macro_error]
345
359
#[ proc_macro_attribute]
360
+ #[ proc_macro_error2:: proc_macro_error]
346
361
pub fn entry ( args : TokenStream , input : TokenStream ) -> TokenStream {
347
362
lp_core:: entry ( args, input)
348
363
}
@@ -381,3 +396,127 @@ pub fn main(args: TokenStream, item: TokenStream) -> TokenStream {
381
396
382
397
run ( & args. meta , f, main ( ) ) . unwrap_or_else ( |x| x) . into ( )
383
398
}
399
+
400
+ /// Automatically implement the [Builder Lite] pattern for a struct.
401
+ ///
402
+ /// This will create an `impl` which contains methods for each field of a
403
+ /// struct, allowing users to easily set the values. The generated methods will
404
+ /// be the field name prefixed with `with_`, and calls to these methods can be
405
+ /// chained as needed.
406
+ ///
407
+ /// ## Example
408
+ ///
409
+ /// ```rust, no_run
410
+ /// #[derive(Default)]
411
+ /// enum MyEnum {
412
+ /// #[default]
413
+ /// A,
414
+ /// B,
415
+ /// }
416
+ ///
417
+ /// #[derive(Default, BuilderLite)]
418
+ /// #[non_exhaustive]
419
+ /// struct MyStruct {
420
+ /// enum_field: MyEnum,
421
+ /// bool_field: bool,
422
+ /// option_field: Option<i32>,
423
+ /// }
424
+ ///
425
+ /// MyStruct::default()
426
+ /// .with_enum_field(MyEnum::B)
427
+ /// .with_bool_field(true)
428
+ /// .with_option_field(-5);
429
+ /// ```
430
+ ///
431
+ /// [Builder Lite]: https://matklad.github.io/2022/05/29/builder-lite.html
432
+ #[ proc_macro_derive( BuilderLite ) ]
433
+ pub fn builder_lite_derive ( item : TokenStream ) -> TokenStream {
434
+ let input = syn:: parse_macro_input!( item as syn:: DeriveInput ) ;
435
+
436
+ let span = input. span ( ) ;
437
+ let ident = input. ident ;
438
+
439
+ let mut fns = Vec :: new ( ) ;
440
+ if let Data :: Struct ( DataStruct { fields, .. } ) = & input. data {
441
+ for field in fields {
442
+ let field_ident = field. ident . as_ref ( ) . unwrap ( ) ;
443
+ let field_type = & field. ty ;
444
+
445
+ let function_ident = format_ident ! ( "with_{}" , field_ident) ;
446
+
447
+ let maybe_path_type = extract_type_path ( field_type)
448
+ . and_then ( |path| extract_option_segment ( path) )
449
+ . and_then ( |path_seg| match path_seg. arguments {
450
+ PathArguments :: AngleBracketed ( ref params) => params. args . first ( ) ,
451
+ _ => None ,
452
+ } )
453
+ . and_then ( |generic_arg| match * generic_arg {
454
+ GenericArgument :: Type ( ref ty) => Some ( ty) ,
455
+ _ => None ,
456
+ } ) ;
457
+
458
+ let ( field_type, field_assigns) = if let Some ( inner_type) = maybe_path_type {
459
+ ( inner_type, quote ! { Some ( #field_ident) } )
460
+ } else {
461
+ ( field_type, quote ! { #field_ident } )
462
+ } ;
463
+
464
+ fns. push ( quote ! {
465
+ #[ doc = concat!( " Assign the given value to the `" , stringify!( #field_ident) , "` field." ) ]
466
+ pub fn #function_ident( mut self , #field_ident: #field_type) -> Self {
467
+ self . #field_ident = #field_assigns;
468
+ self
469
+ }
470
+ } ) ;
471
+
472
+ if maybe_path_type. is_some ( ) {
473
+ let function_ident = format_ident ! ( "with_{}_none" , field_ident) ;
474
+ fns. push ( quote ! {
475
+ #[ doc = concat!( " Set the value of `" , stringify!( #field_ident) , "` to `None`." ) ]
476
+ pub fn #function_ident( mut self ) -> Self {
477
+ self . #field_ident = None ;
478
+ self
479
+ }
480
+ } ) ;
481
+ }
482
+ }
483
+ } else {
484
+ return ParseError :: new (
485
+ span,
486
+ "#[derive(Builder)] is only defined for structs, not for enums or unions!" ,
487
+ )
488
+ . to_compile_error ( )
489
+ . into ( ) ;
490
+ }
491
+
492
+ let implementation = quote ! {
493
+ #[ automatically_derived]
494
+ impl #ident {
495
+ #( #fns) *
496
+ }
497
+ } ;
498
+
499
+ implementation. into ( )
500
+ }
501
+
502
+ // https://stackoverflow.com/a/56264023
503
+ fn extract_type_path ( ty : & Type ) -> Option < & Path > {
504
+ match * ty {
505
+ Type :: Path ( ref typepath) if typepath. qself . is_none ( ) => Some ( & typepath. path ) ,
506
+ _ => None ,
507
+ }
508
+ }
509
+
510
+ // https://stackoverflow.com/a/56264023
511
+ fn extract_option_segment ( path : & Path ) -> Option < & PathSegment > {
512
+ let idents_of_path = path. segments . iter ( ) . fold ( String :: new ( ) , |mut acc, v| {
513
+ acc. push_str ( & v. ident . to_string ( ) ) ;
514
+ acc. push ( '|' ) ;
515
+ acc
516
+ } ) ;
517
+
518
+ vec ! [ "Option|" , "std|option|Option|" , "core|option|Option|" ]
519
+ . into_iter ( )
520
+ . find ( |s| idents_of_path == * s)
521
+ . and_then ( |_| path. segments . last ( ) )
522
+ }
0 commit comments