ori_macro/
rebuild.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::ParseStream, punctuated::Punctuated};

use crate::find_core;

syn::custom_keyword!(layout);
syn::custom_keyword!(draw);

enum FieldAttribute {
    Layout,
    Draw,
}

impl syn::parse::Parse for FieldAttribute {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();

        if lookahead.peek(layout) {
            input.parse::<layout>()?;
            Ok(Self::Layout)
        } else if lookahead.peek(draw) {
            input.parse::<draw>()?;
            Ok(Self::Draw)
        } else {
            Err(lookahead.error())
        }
    }
}

#[derive(Default)]
pub struct FieldAttributes {
    pub styled: bool,
    pub layout: bool,
    pub draw: bool,
}

impl FieldAttributes {
    pub fn new(attrs: &[syn::Attribute]) -> manyhow::Result<Self> {
        let mut this = Self::default();

        for attr in attrs {
            if attr.path().is_ident("rebuild") {
                let updates = attr.parse_args_with(|input: ParseStream| {
                    Punctuated::<FieldAttribute, syn::Token![,]>::parse_terminated(input)
                })?;

                for update in updates {
                    match update {
                        FieldAttribute::Layout => this.layout = true,
                        FieldAttribute::Draw => this.draw = true,
                    }
                }
            }

            if attr.path().is_ident("style") {
                this.styled = true;
            }
        }

        Ok(this)
    }

    pub fn is_empty(&self) -> bool {
        !self.layout && !self.draw
    }

    fn updates(&self) -> TokenStream {
        let mut tokens = TokenStream::new();

        if self.layout {
            tokens.extend(quote!(cx.layout();));
        }

        if self.draw {
            tokens.extend(quote!(cx.draw();));
        }

        tokens
    }
}

pub fn derive_rebuild(input: proc_macro::TokenStream) -> manyhow::Result<proc_macro::TokenStream> {
    let input: syn::DeriveInput = syn::parse(input)?;

    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    let ori_core = find_core();

    let rebuild_impl = rebuild_impl(&input)?;

    let expanded = quote! {
        #[automatically_derived]
        impl #impl_generics #ori_core::rebuild::Rebuild for #name #ty_generics #where_clause {
            #[allow(unused)]
            fn rebuild(&self, cx: &mut #ori_core::context::RebuildCx, old: &Self) {
                #rebuild_impl
            }
        }
    };

    Ok(expanded.into())
}

fn rebuild_impl(input: &syn::DeriveInput) -> manyhow::Result<TokenStream> {
    match input.data {
        syn::Data::Struct(ref data) => match data.fields {
            syn::Fields::Named(ref fields) => {
                let names = named_fields(fields);
                rebuild_fields(names, fields.named.iter())
            }
            syn::Fields::Unnamed(ref fields) => {
                let names = unnamed_fields(fields);
                rebuild_fields(names, fields.unnamed.iter())
            }
            syn::Fields::Unit => Ok(quote!()),
        },
        syn::Data::Enum(_) => manyhow::bail!("enums are not supported"),
        syn::Data::Union(_) => manyhow::bail!("unions are not supported"),
    }
}

fn named_fields(fields: &syn::FieldsNamed) -> impl Iterator<Item = TokenStream> + '_ {
    fields.named.iter().map(|field| {
        let name = field.ident.as_ref().unwrap();
        quote!(#name)
    })
}

fn unnamed_fields(fields: &syn::FieldsUnnamed) -> impl Iterator<Item = TokenStream> + '_ {
    fields.unnamed.iter().enumerate().map(|(i, _)| {
        let i = syn::Index::from(i);
        quote!(#i)
    })
}

fn rebuild_fields<'a>(
    names: impl Iterator<Item = TokenStream>,
    fields: impl Iterator<Item = &'a syn::Field>,
) -> manyhow::Result<TokenStream> {
    let mut tokens = TokenStream::new();

    for (name, field) in names.zip(fields) {
        tokens.extend(rebuild_field(name, field)?);
    }

    Ok(tokens)
}

fn rebuild_field(name: TokenStream, field: &syn::Field) -> manyhow::Result<TokenStream> {
    let attributes = FieldAttributes::new(&field.attrs)?;

    if attributes.is_empty() || attributes.styled {
        return Ok(quote!());
    }

    let updates = attributes.updates();
    Ok(quote! {
        if self.#name != old.#name {
            #updates
        }
    })
}