1
1
use convert_case:: { Case , Casing } ;
2
2
use proc_macro2:: { Span , TokenStream } ;
3
- use proc_macro_error2:: { abort, abort_call_site, proc_macro_error} ;
3
+ use proc_macro_error2:: { abort, abort_call_site, proc_macro_error, OptionExt } ;
4
4
use quote:: { quote, ToTokens } ;
5
5
use syn:: {
6
6
parse:: { Parse , ParseStream , Parser } ,
@@ -19,7 +19,7 @@ pub fn derive_store(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
19
19
}
20
20
21
21
#[ proc_macro_error]
22
- #[ proc_macro_derive( Patch , attributes( store) ) ]
22
+ #[ proc_macro_derive( Patch , attributes( store, patch ) ) ]
23
23
pub fn derive_patch ( input : proc_macro:: TokenStream ) -> proc_macro:: TokenStream {
24
24
syn:: parse_macro_input!( input as PatchModel )
25
25
. into_token_stream ( )
@@ -537,61 +537,134 @@ fn variant_to_tokens(
537
537
struct PatchModel {
538
538
pub name : Ident ,
539
539
pub generics : Generics ,
540
- pub fields : Vec < Field > ,
540
+ pub ty : PatchModelTy ,
541
+ }
542
+
543
+ enum PatchModelTy {
544
+ Struct {
545
+ fields : Vec < Field > ,
546
+ } ,
547
+ #[ allow( dead_code) ]
548
+ Enum {
549
+ variants : Vec < Variant > ,
550
+ } ,
541
551
}
542
552
543
553
impl Parse for PatchModel {
544
554
fn parse ( input : ParseStream ) -> Result < Self > {
545
555
let input = syn:: DeriveInput :: parse ( input) ?;
546
556
547
- let syn:: Data :: Struct ( s) = input. data else {
548
- abort_call_site ! ( "only structs can be used with `Patch`" ) ;
549
- } ;
557
+ let ty = match input. data {
558
+ syn:: Data :: Struct ( s) => {
559
+ let fields = match s. fields {
560
+ syn:: Fields :: Unit => {
561
+ abort ! ( s. semi_token, "unit structs are not supported" ) ;
562
+ }
563
+ syn:: Fields :: Named ( fields) => {
564
+ fields. named . into_iter ( ) . collect :: < Vec < _ > > ( )
565
+ }
566
+ syn:: Fields :: Unnamed ( fields) => {
567
+ fields. unnamed . into_iter ( ) . collect :: < Vec < _ > > ( )
568
+ }
569
+ } ;
550
570
551
- let fields = match s. fields {
552
- syn:: Fields :: Unit => {
553
- abort ! ( s. semi_token, "unit structs are not supported" ) ;
571
+ PatchModelTy :: Struct { fields }
554
572
}
555
- syn:: Fields :: Named ( fields) => {
556
- fields. named . into_iter ( ) . collect :: < Vec < _ > > ( )
573
+ syn:: Data :: Enum ( _e) => {
574
+ abort_call_site ! ( "only structs can be used with `Patch`" ) ;
575
+
576
+ // TODO: support enums later on
577
+ // PatchModelTy::Enum {
578
+ // variants: e.variants.into_iter().collect(),
579
+ // }
557
580
}
558
- syn:: Fields :: Unnamed ( fields) => {
559
- fields. unnamed . into_iter ( ) . collect :: < Vec < _ > > ( )
581
+ _ => {
582
+ abort_call_site ! (
583
+ "only structs and enums can be used with `Store`"
584
+ ) ;
560
585
}
561
586
} ;
562
587
563
588
Ok ( Self {
564
589
name : input. ident ,
565
590
generics : input. generics ,
566
- fields ,
591
+ ty ,
567
592
} )
568
593
}
569
594
}
570
595
571
596
impl ToTokens for PatchModel {
572
597
fn to_tokens ( & self , tokens : & mut proc_macro2:: TokenStream ) {
573
598
let library_path = quote ! { reactive_stores } ;
574
- let PatchModel {
575
- name,
576
- generics,
577
- fields,
578
- } = & self ;
599
+ let PatchModel { name, generics, ty } = & self ;
579
600
580
- let fields = fields. iter ( ) . enumerate ( ) . map ( |( idx, field) | {
581
- let field_name = match & field. ident {
582
- Some ( ident) => quote ! { #ident } ,
583
- None => quote ! { #idx } ,
584
- } ;
585
- quote ! {
586
- #library_path:: PatchField :: patch_field(
587
- & mut self . #field_name,
588
- new. #field_name,
589
- & new_path,
590
- notify
591
- ) ;
592
- new_path. replace_last( #idx + 1 ) ;
601
+ let fields = match ty {
602
+ PatchModelTy :: Struct { fields } => {
603
+ fields. iter ( ) . enumerate ( ) . map ( |( idx, field) | {
604
+ let Field {
605
+ attrs, ident, ..
606
+ } = & field;
607
+ let field_name = match & ident {
608
+ Some ( ident) => quote ! { #ident } ,
609
+ None => quote ! { #idx } ,
610
+ } ;
611
+ let closure = attrs
612
+ . iter ( )
613
+ . find_map ( |attr| {
614
+ attr. meta . path ( ) . is_ident ( "patch" ) . then (
615
+ || match & attr. meta {
616
+ Meta :: List ( list) => {
617
+ match Punctuated :: <
618
+ ExprClosure ,
619
+ Comma ,
620
+ > :: parse_terminated
621
+ . parse2 ( list. tokens . clone ( ) )
622
+ {
623
+ Ok ( closures) => {
624
+ let closure = closures. iter ( ) . next ( ) . cloned ( ) . expect_or_abort ( "should have ONE closure" ) ;
625
+ if closure. inputs . len ( ) != 2 {
626
+ abort ! ( closure. inputs, "patch closure should have TWO params as in #[patch(|this, new| ...)]" ) ;
627
+ }
628
+ closure
629
+ } ,
630
+ Err ( e) => abort ! ( list, e) ,
631
+ }
632
+ }
633
+ _ => abort ! ( attr. meta, "needs to be as `#[patch(|this, new| ...)]`" ) ,
634
+ } ,
635
+ )
636
+ } ) ;
637
+
638
+ if let Some ( closure) = closure {
639
+ let params = closure. inputs ;
640
+ let body = closure. body ;
641
+ quote ! {
642
+ if new. #field_name != self . #field_name {
643
+ _ = {
644
+ let ( #params) = ( & mut self . #field_name, new. #field_name) ;
645
+ #body
646
+ } ;
647
+ notify( & new_path) ;
648
+ }
649
+ new_path. replace_last( #idx + 1 ) ;
650
+ }
651
+ } else {
652
+ quote ! {
653
+ #library_path:: PatchField :: patch_field(
654
+ & mut self . #field_name,
655
+ new. #field_name,
656
+ & new_path,
657
+ notify
658
+ ) ;
659
+ new_path. replace_last( #idx + 1 ) ;
660
+ }
661
+ }
662
+ } ) . collect :: < Vec < _ > > ( )
593
663
}
594
- } ) ;
664
+ PatchModelTy :: Enum { variants : _ } => {
665
+ unreachable ! ( "not implemented currently" )
666
+ }
667
+ } ;
595
668
596
669
// read access
597
670
tokens. extend ( quote ! {
0 commit comments