static_reflect_derive_internals/
func.rs

1use syn::{Error, FnArg, ReturnType, ForeignItem, Attribute, ItemFn, Meta, ItemForeignMod, Item, Lit, Type};
2use syn::parse::{self, Parse, ParseStream};
3use proc_macro2::{Ident, TokenStream, Span};
4use quote::{ToTokens, TokenStreamExt};
5use syn::Signature;
6use syn::spanned::Spanned;
7use itertools::Itertools;
8use quote::quote;
9
10
11const FUNC_ATTR_NAME: &str = "reflect_func";
12
13#[derive(Debug)]
14#[non_exhaustive]
15pub struct FuncArgs {
16    /// Link against the hardcoded/absolute address
17    /// instead of using dynamic linking
18    pub absolute: bool,
19}
20
21impl Parse for FuncArgs {
22    fn parse(input: ParseStream) -> parse::Result<Self> {
23        let mut args = FuncArgs {
24            // By default, we want to use dynamic linking
25            absolute: false,
26        };
27        while !input.is_empty() {
28            if input.peek(syn::Ident) {
29                let ident = input.parse::<Ident>()?;
30                match &*ident.to_string() {
31                    "absolute" => {
32                        args.absolute = true;
33                    }
34                    _ => {
35                        return Err(input.error(format_args!("Invalid flag: {}", ident)))
36                    }
37                }
38            } else {
39                return Err(input.error("Unexpected token"))
40            }
41        }
42        Ok(args)
43    }
44}
45
46#[derive(Debug, Clone)]
47struct FunctionDefOpts {
48    /// Assume that the function is already using the C ABI
49    ///
50    /// If this is "false" we require that the signature is explicitly declared `extern "C"`
51    assume_c_abi: bool,
52    /// The location of this function
53    location: FunctionLocation,
54    /// Whether the function is considered unsafe
55    is_unsafe: bool,
56}
57
58/// Ensure that the function is either marked `#[no_mangle]`
59/// or that it has a custom `#[export_name]`
60fn determine_fn_link_name(item: &ItemFn) -> Result<Option<String>, syn::Error> {
61    for attr in &item.attrs {
62        match attr.parse_meta()? {
63            Meta::Path(ref p) if p.is_ident("no_mangle") => {
64                return Ok(None)
65            },
66            Meta::NameValue(ref item) if item.path.is_ident("export_name") => {
67                match item.lit {
68                    Lit::Str(ref s) => {
69                        return Ok(Some(s.value()))
70                    },
71                    _ => {
72                        return Err(syn::Error::new(item.span(), "Expected a string for export_name"))
73                    }
74                }
75            },
76            _ => {}
77        }
78    }
79    Err(syn::Error::new(
80        item.span(),
81        "Function must be #[no_mangle] to support dynamic linking"
82    ))
83}
84
85fn determine_foreign_link_name(attrs: &[Attribute]) -> Result<Option<String>, syn::Error> {
86    for attr in attrs {
87        match attr.parse_meta()? {
88            Meta::NameValue(ref l) if l.path.is_ident("link_name") => {
89                match l.lit {
90                    Lit::Str(ref s) => {
91                        return Ok(Some(s.value()))
92                    },
93                    _ => {
94                        return Err(syn::Error::new(
95                            l.span(),
96                            "Expected a string for #[link_name]"
97                        ))
98                    }
99                }
100            },
101            _ => {}
102        }
103    }
104    Ok(None)
105}
106
107pub fn handle_item(item: &Item, args: FuncArgs) -> Result<TokenStream, syn::Error> {
108    match *item {
109        Item::Fn(ref func) => handle_fn_def(func, args),
110        Item::ForeignMod(ref foreign_mod) => handle_foreign_mod(foreign_mod, args),
111        _ => {
112            Err(syn::Error::new(
113                item.span(),
114                format!("Invalid target for #[{}]", FUNC_ATTR_NAME)
115            ))
116        }
117    }
118}
119
120fn handle_fn_def(item: &ItemFn, args: FuncArgs) -> Result<TokenStream, syn::Error> {
121    let location = if args.absolute {
122        let name = &item.sig.ident;
123        FunctionLocation::AbsoluteAddress(quote!({ #name as *const () }))
124    } else {
125        let name = determine_fn_link_name(&item)?;
126        FunctionLocation::DynamicallyLinked {
127            link_name: name.map(|s| quote!(#s))
128        }
129    };
130    let def = emit_def_from_signature(&item.sig, FunctionDefOpts {
131        assume_c_abi: false, location,
132        is_unsafe: item.sig.unsafety.is_some()
133    })?;
134    let verify_types = types_from_signature(&item.sig);
135    let def_const = def.make_constant(&verify_types);
136    Ok(quote! {
137        #def_const
138        #item
139    })
140}
141
142fn handle_foreign_mod(item: &ItemForeignMod, default_args: FuncArgs) -> Result<TokenStream, syn::Error> {
143    // Handle default args
144    if default_args.absolute {
145        return Err(syn::Error::new(
146            item.span(),
147            "Absolute locations aren't supported in foreign functions"
148        ));
149    }
150    match item.abi.name.as_ref() {
151        Some(abi_name) if &*abi_name.value() == "C" => {},
152        None => {},
153        _ => {
154            return Err(Error::new(item.abi.span(), "Expected C ABI"))
155        }
156    }
157    let mut result_static_defs = Vec::new();
158    let mut result_items = Vec::new();
159    for item in &item.items {
160        match *item {
161            ForeignItem::Fn(ref item) => {
162                let mut result_item = (*item).clone();
163                result_item.attrs.clear();
164                for attr in &item.attrs {
165                    if attr.path.is_ident(FUNC_ATTR_NAME) {
166                        let override_args = syn::parse2::<FuncArgs>(attr.tokens.clone())?;
167                        // Handle overriding args
168                        if override_args.absolute {
169                            return Err(syn::Error::new(
170                                item.span(),
171                                "Absolute locations aren't supported in foreign functions"
172                            ));
173                        }
174                        // NOTE: This is removed from the result_item
175                    } else {
176                        result_item.attrs.push(attr.clone());
177                    }
178                }
179                let link_name = determine_foreign_link_name(&item.attrs)?
180                    .map(|s| quote!(#s));
181                let args = FunctionDefOpts {
182                    location: FunctionLocation::DynamicallyLinked { link_name },
183                    assume_c_abi: true,
184                    is_unsafe: true // All foreign defs are unsafe
185                };
186                let verify_types = types_from_signature(&item.sig);
187                result_static_defs.push((
188                    emit_def_from_signature(&item.sig, args)?,
189                    verify_types
190                ));
191                result_items.push(ForeignItem::Fn(result_item));
192            },
193            _ => {
194                // Passthrough
195                result_items.push((*item).clone());
196            }
197        }
198    }
199    let function_def_consts = result_static_defs.iter()
200        .map(|&(ref def, ref verify_types)| def.make_constant(&verify_types))
201        .collect_vec();
202    Ok(quote! {
203        #(#function_def_consts)*
204        extern "C" {
205            #(#result_items)*
206        }
207    })
208}
209
210fn emit_def_from_signature(
211    item: &Signature,
212    opts: FunctionDefOpts,
213) -> Result<StaticFunctionDef, syn::Error> {
214    match item.abi.as_ref().and_then(|abi| abi.name.as_ref()) {
215        Some(abi_name) if &*abi_name.value() == "C" => {},
216        None if opts.assume_c_abi => {},
217        _ => {
218            return Err(Error::new(item.span(), "Expected C ABI"))
219        }
220    }
221    let mut argument_types = Vec::new();
222    let mut static_arg_types = Vec::new();
223    for input in &item.inputs {
224        match input {
225            FnArg::Receiver(ref item) => {
226                return Err(Error::new(item.span(), "Invalid input"))
227            },
228            FnArg::Typed(ref item) => {
229                let ty = &item.ty;
230                static_arg_types.push(quote!(#ty));
231                argument_types.push(quote!(<#ty as static_reflect::StaticReflect>::TYPE_INFO))
232            },
233        }
234    }
235    let return_type = match item.output {
236        ReturnType::Default => quote!(&static_reflect::types::TypeInfo::Unit),
237        ReturnType::Type(_, ref ty) => {
238            quote!(&<#ty as static_reflect::StaticReflect>::TYPE_INFO)
239        },
240    };
241    let signature = StaticSignatureDef { argument_types, return_type };
242    Ok(StaticFunctionDef {
243        name: item.ident.to_string(),
244        location: opts.location,
245        signature, is_unsafe: opts.is_unsafe,
246        static_return_type: match item.output {
247            ReturnType::Default => quote!(()),
248            ReturnType::Type(_, ref ty) => quote!(#ty),
249        },
250        static_arg_types: quote!((#(#static_arg_types,)*))
251    })
252}
253
254// Get all the types from the signature
255pub fn types_from_signature(sig: &Signature) -> Vec<Type> {
256    sig.inputs.iter().map(|arg| match *arg {
257        FnArg::Receiver(_) => Type::Verbatim(quote!(Self)),
258        FnArg::Typed(ref t) => (*t.ty).clone()
259    }).chain(std::iter::once(match sig.output {
260        ReturnType::Default => Type::Tuple(syn::TypeTuple {
261            paren_token: Default::default(),
262            elems: Default::default()
263        }),
264        ReturnType::Type(_, ref ty) => (**ty).clone(),
265    })).collect()
266}
267
268// Emit
269#[derive(Clone, Debug)]
270struct StaticFunctionDef {
271    name: String,
272    is_unsafe: bool,
273    location: FunctionLocation,
274    signature: StaticSignatureDef,
275    static_return_type: TokenStream,
276    static_arg_types: TokenStream,
277}
278impl StaticFunctionDef {
279    fn make_constant(&self, verify_types: &[Type]) -> TokenStream {
280        let const_name = format!("_FUNC_{}", self.name);
281        let const_name = Ident::new(&const_name, Span::call_site());
282        let def = self;
283        let return_type = &self.static_return_type;
284        let arg_types = &self.static_arg_types;
285        quote! {
286            #[doc(hidden)]
287            #[allow(non_snake_case)]
288            pub const #const_name: static_reflect::funcs::FunctionDeclaration<#return_type, #arg_types> = {
289                // Verify all the types implement [StaticReflect]
290                #(let _ = <#verify_types as static_reflect::StaticReflect>::TYPE_INFO;)*
291                #def
292            };
293        }
294    }
295}
296
297#[derive(Clone, Debug)]
298struct StaticSignatureDef {
299    argument_types: Vec<TokenStream>,
300    return_type: TokenStream
301}
302
303#[derive(Clone, Debug)]
304enum FunctionLocation {
305    DynamicallyLinked {
306        link_name: Option<TokenStream>
307    },
308    AbsoluteAddress(TokenStream),
309}
310
311impl ToTokens for StaticFunctionDef {
312    fn to_tokens(&self, tokens: &mut TokenStream) {
313        let StaticFunctionDef {
314            ref name,
315            ref signature,
316            ref location,
317            ref is_unsafe,
318            ref static_return_type,
319            static_arg_types: ref staitc_arg_types
320        } = *self;
321        tokens.append_all(quote!(static_reflect::funcs::FunctionDeclaration::<'static, #static_return_type, #staitc_arg_types> {
322            name: #name,
323            is_unsafe: #is_unsafe,
324            signature: #signature,
325            location: #location,
326            return_type: ::std::marker::PhantomData,
327            arg_types: ::std::marker::PhantomData,
328        }));
329    }
330}
331
332impl ToTokens for FunctionLocation {
333    fn to_tokens(&self, tokens: &mut TokenStream) {
334        tokens.append_all(match *self {
335            FunctionLocation::DynamicallyLinked { link_name: None } => {
336                quote!(Some(static_reflect::funcs::FunctionLocation::DynamicallyLinked { link_name: None }))
337            },
338            FunctionLocation::DynamicallyLinked { link_name: Some(ref name) } => {
339                quote!(Some(static_reflect::funcs::FunctionLocation::DynamicallyLinked { link_name: Some(#name) }))
340            },
341            FunctionLocation::AbsoluteAddress(ref value) => {
342                quote!(Some(static_reflect::funcs::FunctionLocation::AbsoluteAddress(#value)))
343            },
344        });
345    }
346}
347
348impl ToTokens for StaticSignatureDef {
349    fn to_tokens(&self, tokens: &mut TokenStream) {
350        let StaticSignatureDef {
351            ref argument_types,
352            ref return_type
353        } = *self;
354        tokens.append_all(quote!(static_reflect::funcs::SignatureDef {
355            argument_types: &[#(#argument_types),*],
356            return_type: #return_type,
357            // We use C FFI
358            calling_convention: static_reflect::funcs::CallingConvention::StandardC
359        }))
360    }
361}