rune_macros/
const_value.rs

1use core::fmt;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{quote, ToTokens};
5use syn::parse::{Parse, ParseStream};
6use syn::spanned::Spanned;
7use syn::DeriveInput;
8
9use crate::context::{Context, Tokens};
10
11/// An internal call to the macro.
12pub(super) struct Derive {
13    input: DeriveInput,
14}
15
16impl Parse for Derive {
17    fn parse(input: ParseStream) -> syn::Result<Self> {
18        Ok(Self {
19            input: input.parse()?,
20        })
21    }
22}
23
24pub(super) struct ConstBuilder<T> {
25    ident: T,
26    tokens: Tokens,
27    body: TokenStream,
28    variables: Vec<syn::Ident>,
29    members: Vec<syn::Member>,
30    from_const_fields: Vec<TokenStream>,
31    from_value_fields: Vec<TokenStream>,
32}
33
34impl Derive {
35    pub(super) fn into_builder(self, cx: &Context) -> Result<ConstBuilder<syn::Ident>, ()> {
36        let attr = cx.const_value_type_attrs(&self.input.attrs);
37        let tokens = cx.tokens_with_module(attr.module.as_ref());
38        let body;
39
40        let Tokens {
41            const_value,
42            from_const_value_t,
43            to_const_value_t,
44            type_hash_t,
45            from_value,
46            value,
47            ..
48        } = &tokens;
49
50        let mut variables = Vec::new();
51        let mut members = Vec::new();
52        let mut from_const_fields = Vec::new();
53        let mut from_value_fields = Vec::new();
54
55        match self.input.data {
56            syn::Data::Struct(data) => {
57                let mut fields = Vec::new();
58
59                for (index, field) in data.fields.iter().enumerate() {
60                    let attr = cx.const_value_field_attrs(&field.attrs);
61
62                    let member = match &field.ident {
63                        Some(ident) => syn::Member::Named(ident.clone()),
64                        None => syn::Member::Unnamed(syn::Index::from(index)),
65                    };
66
67                    let ty = &field.ty;
68
69                    let var = syn::Ident::new(&format!("v{index}"), Span::call_site());
70
71                    if let Some(path) = &attr.with {
72                        let to_const_value: syn::Path =
73                            syn::parse_quote_spanned!(path.span() => #path::to_const_value);
74                        let from_const_value: syn::Path =
75                            syn::parse_quote_spanned!(path.span() => #path::from_const_value);
76                        let from_value: syn::Path =
77                            syn::parse_quote_spanned!(path.span() => #path::from_value);
78
79                        fields.push(quote!(#to_const_value(self.#member)?));
80                        from_const_fields.push(quote!(#from_const_value(#var)?));
81                        from_value_fields.push(quote!(#from_value(#value::take(#var))?));
82                    } else {
83                        fields.push(quote! {
84                            <#ty as #to_const_value_t>::to_const_value(self.#member)?
85                        });
86
87                        from_const_fields.push(quote! {
88                            <#ty as #from_const_value_t>::from_const_value(#var)?
89                        });
90
91                        from_value_fields.push(quote! {
92                            <#ty as #from_value>::from_value(#value::take(#var))?
93                        });
94                    }
95
96                    variables.push(var);
97                    members.push(member);
98                }
99
100                body = quote! {
101                    #const_value::for_struct(<Self as #type_hash_t>::HASH, [#(#fields),*])
102                };
103            }
104            syn::Data::Enum(..) => {
105                cx.error(syn::Error::new(
106                    Span::call_site(),
107                    "ToConstValue: enums are not supported",
108                ));
109                return Err(());
110            }
111            syn::Data::Union(..) => {
112                cx.error(syn::Error::new(
113                    Span::call_site(),
114                    "ToConstValue: unions are not supported",
115                ));
116                return Err(());
117            }
118        }
119
120        Ok(ConstBuilder {
121            ident: self.input.ident,
122            tokens,
123            body,
124            variables,
125            members,
126            from_const_fields,
127            from_value_fields,
128        })
129    }
130}
131
132impl<T> ConstBuilder<T>
133where
134    T: ToTokens + fmt::Display,
135{
136    pub(super) fn expand(self) -> TokenStream {
137        let Tokens {
138            arc,
139            const_construct_t,
140            const_value,
141            option,
142            result,
143            runtime_error,
144            to_const_value_t,
145            value,
146            ..
147        } = &self.tokens;
148
149        let ident = self.ident;
150        let construct = syn::Ident::new(&format!("{ident}Construct"), Span::call_site());
151        let body = self.body;
152        let members = &self.members;
153        let variables = &self.variables;
154        let from_const_fields = &self.from_const_fields;
155        let from_value_fields = &self.from_value_fields;
156
157        let expected = self.members.len();
158
159        quote! {
160            #[automatically_derived]
161            impl #to_const_value_t for #ident {
162                #[inline]
163                fn to_const_value(self) -> #result<#const_value, #runtime_error> {
164                    #body
165                }
166
167                #[inline]
168                fn construct() -> #option<#arc<dyn #const_construct_t>> {
169                    struct #construct;
170
171                    impl #const_construct_t for #construct {
172                        #[inline]
173                        fn const_construct(&self, values: &[#const_value]) -> #result<#value, #runtime_error> {
174                            let [#(#variables),*] = values else {
175                                return #result::Err(#runtime_error::bad_argument_count(values.len(), #expected));
176                            };
177
178                            let value = #ident {
179                                #(#members: #from_const_fields,)*
180                            };
181
182                            #result::Ok(#value::new(value)?)
183                        }
184
185                        #[inline]
186                        fn runtime_construct(&self, values: &mut [#value]) -> #result<#value, #runtime_error> {
187                            let [#(#variables),*] = values else {
188                                return #result::Err(#runtime_error::bad_argument_count(values.len(), #expected));
189                            };
190
191                            let value = #ident {
192                                #(#members: #from_value_fields,)*
193                            };
194
195                            #result::Ok(#value::new(value)?)
196                        }
197                    }
198
199                    #option::Some(#arc::new(#construct))
200                }
201            }
202        }
203    }
204}