rquickjs_macro/
js_lifetime.rs1use 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}