rquickjs_macro/
js_lifetime.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    spanned::Spanned as _, visit::Visit, Data, DeriveInput, Error, Field, Fields, GenericArgument,
5    GenericParam, Generics, Lifetime, Result, Type,
6};
7
8use crate::{
9    attrs::{take_attributes, OptionList},
10    common::crate_ident,
11    trace::{ImplConfig, TraceOption},
12};
13
14pub fn retrieve_lifetime(generics: &Generics) -> Result<Option<&Lifetime>> {
15    let mut lifetime: Option<&Lifetime> = None;
16    for p in generics.params.iter() {
17        if let GenericParam::Lifetime(x) = p {
18            if let Some(x) = lifetime.as_ref() {
19                return Err(Error::new(x.span(),"Type has multiple lifetimes, this is not supported by the JsLifetime derive macro"));
20            }
21            lifetime = Some(&x.lifetime);
22        }
23    }
24
25    Ok(lifetime)
26}
27
28pub fn extract_types_need_checking(lt: &Lifetime, data: &Data) -> Result<Vec<Type>> {
29    let mut res = Vec::new();
30
31    match data {
32        Data::Struct(s) => {
33            for f in s.fields.iter() {
34                extract_types_need_checking_fields(lt, f, &mut res)?;
35            }
36        }
37        Data::Enum(e) => {
38            for v in e.variants.iter() {
39                let fields = match v.fields {
40                    Fields::Unit => continue,
41                    Fields::Named(ref x) => &x.named,
42                    Fields::Unnamed(ref x) => &x.unnamed,
43                };
44
45                for f in fields {
46                    extract_types_need_checking_fields(lt, f, &mut res)?;
47                }
48            }
49        }
50        Data::Union(u) => {
51            return Err(Error::new(
52                u.union_token.span(),
53                "Union types are not supported",
54            ))
55        }
56    }
57
58    Ok(res)
59}
60
61pub struct LtTypeVisitor<'a>(Result<bool>, &'a Lifetime);
62
63impl<'ast> Visit<'ast> for LtTypeVisitor<'ast> {
64    fn visit_generic_argument(&mut self, i: &'ast syn::GenericArgument) {
65        if self.0.is_err() {
66            return;
67        }
68
69        if let GenericArgument::Lifetime(lt) = i {
70            if lt.ident == "static" || lt == self.1 {
71                self.0 = Ok(true)
72            } else {
73                self.0 = Err(Error::new(
74                    lt.span(),
75                    "Type contained lifetime which was not static or the 'js lifetime",
76                ));
77            }
78        }
79
80        syn::visit::visit_generic_argument(self, i);
81    }
82
83    fn visit_type(&mut self, i: &'ast syn::Type) {
84        if self.0.is_err() {
85            return;
86        }
87
88        syn::visit::visit_type(self, i)
89    }
90}
91
92pub fn extract_types_need_checking_fields(
93    lt: &Lifetime,
94    field: &Field,
95    types: &mut Vec<Type>,
96) -> Result<()> {
97    let mut visitor = LtTypeVisitor(Ok(false), lt);
98    visitor.visit_type(&field.ty);
99
100    if visitor.0? {
101        types.push(field.ty.clone());
102    }
103    Ok(())
104}
105
106pub fn extract_bounds(generics: &Generics) -> Result<Vec<TokenStream>> {
107    let mut res = Vec::new();
108
109    for p in generics.params.iter() {
110        match p {
111            GenericParam::Lifetime(_) => {}
112            GenericParam::Type(x) => res.push(quote! {
113                #x: JsLifetime<'js>
114            }),
115            GenericParam::Const(_) => {}
116        }
117    }
118
119    Ok(res)
120}
121
122pub(crate) fn expand(mut input: DeriveInput) -> Result<TokenStream> {
123    let name = input.ident;
124
125    let mut config = ImplConfig::default();
126    take_attributes(&mut input.attrs, |attr| {
127        if !attr.path().is_ident("qjs") {
128            return Ok(false);
129        }
130
131        let options: OptionList<TraceOption> = attr.parse_args()?;
132        options.0.iter().for_each(|x| config.apply(x));
133        Ok(true)
134    })?;
135
136    let crate_name = if let Some(x) = config.crate_.clone() {
137        format_ident!("{x}")
138    } else {
139        format_ident!("{}", crate_ident()?)
140    };
141
142    let generics = &input.generics;
143    let lt = retrieve_lifetime(generics)?;
144    let bounds = extract_bounds(generics)?;
145
146    let Some(lt) = lt else {
147        let res = quote! {
148            unsafe impl<'js> #crate_name::JsLifetime<'js> for #name #generics
149                where #(#bounds),*
150            {
151                type Changed<'to> = #name;
152            }
153        };
154        return Ok(res);
155    };
156
157    let types = extract_types_need_checking(lt, &input.data)?;
158
159    let const_name = format_ident!("__{}__LT_TYPE_CHECK", name.to_string().to_uppercase());
160
161    let res = quote! {
162        const #const_name: () = const {
163            trait ValidJsLifetimeImpl{};
164
165            impl<#lt> ValidJsLifetimeImpl for #name<#lt>
166                where
167                    #(
168                        #types: JsLifetime<#lt>
169                    ),*
170            {
171            }
172
173            const fn assert_js_lifetime_impl_is_valid<T: ValidJsLifetimeImpl>(){}
174
175            assert_js_lifetime_impl_is_valid::<#name>()
176        };
177
178        unsafe impl<#lt> #crate_name::JsLifetime<#lt> for #name #generics
179            where #(#bounds),*
180        {
181            type Changed<'to> = #name<'to>;
182        }
183    };
184    Ok(res)
185}