ui4_macros/
lib.rs

1use proc_macro2::{Span, TokenStream};
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput, Fields};
4
5#[proc_macro_derive(Lens)]
6pub fn my_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
7    let input = parse_macro_input!(input as DeriveInput);
8
9    let lensed_ident = input.ident;
10    let lens_vis = input.vis;
11    let generics = input.generics;
12    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
13
14    let (inner_impls, outer_impls) = match input.data {
15        syn::Data::Struct(s) => match s.fields {
16            Fields::Named(fields) => fields
17                .named
18                .into_iter()
19                .map(|field| (field.ident.unwrap(), field.ty))
20                .map(|(ident, ty)| {
21                    let outer = quote! {
22                        #[derive(Copy, Clone)]
23                        #[allow(non_camel_case_types)]
24                        #lens_vis struct #ident;
25                        impl ::ui4::lens::Lens for #ident {
26                            type In = #lensed_ident;
27                            type Out = #ty;
28
29                            fn get<'a>(&self, v: &'a #lensed_ident) -> &'a #ty {
30                                &v.#ident
31                            }
32
33                            fn get_mut<'a>(&self, v: &'a mut #lensed_ident) -> &'a mut #ty {
34                                &mut v.#ident
35                            }
36                        }
37                    };
38                    let inner = quote! {
39                        #[allow(non_upper_case_globals)]
40                        #lens_vis const #ident: #ident = #ident;
41                    };
42                    (inner, outer)
43                })
44                .unzip::<_, _, TokenStream, TokenStream>(),
45            Fields::Unnamed(fields) => {
46                let lens_name =
47                    syn::Ident::new(&format!("{}Lens", lensed_ident), Span::call_site());
48                let (inner_impls, outer_impls) = fields
49                    .unnamed
50                    .into_iter()
51                    .map(|field| field.ty)
52                    .enumerate()
53                    .map(|(i, ty)| {
54                        let index = syn::Index::from(i);
55                        let outer = quote! {
56                            impl ::ui4::lens::Lens for #lens_name<#i> {
57                                type In = #lensed_ident;
58                                type Out = #ty;
59
60                                fn get<'a>(&self, v: &'a #lensed_ident) -> &'a #ty {
61                                    &v.#index
62                                }
63
64                                fn get_mut<'a>(&self, v: &'a mut #lensed_ident) -> &'a mut #ty {
65                                    &mut v.#index
66                                }
67                            }
68                        };
69                        let n_ident = syn::Ident::new(&format!("F{}", i), Span::call_site());
70                        let inner = quote! {
71                            #lens_vis const #n_ident: #lens_name::<#i> = #lens_name::<#i>;
72                        };
73                        (inner, outer)
74                    })
75                    .unzip::<_, _, TokenStream, TokenStream>();
76                let outer = quote! {
77                    #[derive(Copy, Clone)]
78                    #lens_vis struct #lens_name<const N: usize>;
79                    #outer_impls
80                };
81                let inner = quote! {
82                    #inner_impls
83                };
84                (inner, outer)
85            }
86            Fields::Unit => unimplemented!("Unit structs not supported"),
87        },
88        _ => unimplemented!("Only structs are supported"),
89    };
90
91    // Build the output, possibly using quasi-quotation
92    let expanded = quote! {
93        impl #impl_generics #lensed_ident #ty_generics #where_clause {
94            #inner_impls
95        }
96
97        #outer_impls
98    };
99
100    // Hand the output tokens back to the compiler
101    expanded.into()
102}