rquickjs_macro/
methods.rs

1use std::collections::HashMap;
2
3use proc_macro2::{Span, TokenStream};
4use quote::{format_ident, quote};
5use syn::{
6    parse::{Parse, ParseStream},
7    spanned::Spanned,
8    Error, ItemImpl, LitStr, Result, Token, Type,
9};
10
11use crate::{
12    attrs::{take_attributes, OptionList, ValueOption},
13    common::{add_js_lifetime, crate_ident, kw, Case, BASE_PREFIX, IMPL_PREFIX},
14};
15
16mod accessor;
17use accessor::JsAccessor;
18mod method;
19use method::Method;
20
21#[derive(Default)]
22pub(crate) struct ImplConfig {
23    pub(crate) prefix: Option<String>,
24    pub(crate) crate_: Option<String>,
25    pub(crate) rename_all: Option<Case>,
26}
27
28impl ImplConfig {
29    pub fn apply(&mut self, option: &ImplOption) {
30        match option {
31            ImplOption::Prefix(x) => {
32                self.prefix = Some(x.value.value());
33            }
34            ImplOption::Crate(x) => {
35                self.crate_ = Some(x.value.value());
36            }
37            ImplOption::RenameAll(x) => {
38                self.rename_all = Some(x.value);
39            }
40        }
41    }
42}
43
44pub(crate) enum ImplOption {
45    Prefix(ValueOption<kw::prefix, LitStr>),
46    Crate(ValueOption<Token![crate], LitStr>),
47    RenameAll(ValueOption<kw::rename_all, Case>),
48}
49
50impl Parse for ImplOption {
51    fn parse(input: ParseStream) -> syn::Result<Self> {
52        if input.peek(kw::prefix) {
53            input.parse().map(Self::Prefix)
54        } else if input.peek(Token![crate]) {
55            input.parse().map(Self::Crate)
56        } else if input.peek(kw::rename_all) {
57            input.parse().map(Self::RenameAll)
58        } else {
59            Err(syn::Error::new(input.span(), "invalid impl attribute"))
60        }
61    }
62}
63
64pub fn get_class_name(ty: &Type) -> String {
65    match ty {
66        Type::Array(_) => todo!(),
67        Type::Paren(x) => get_class_name(&x.elem),
68        Type::Path(x) => x.path.segments.first().unwrap().ident.to_string(),
69        Type::Tuple(x) => {
70            let name = x
71                .elems
72                .iter()
73                .map(get_class_name)
74                .collect::<Vec<_>>()
75                .join("_");
76
77            format!("tuple_{name}")
78        }
79        _ => todo!(),
80    }
81}
82
83pub(crate) fn expand(options: OptionList<ImplOption>, item: ItemImpl) -> Result<TokenStream> {
84    let mut config = ImplConfig::default();
85    for option in options.0.iter() {
86        config.apply(option)
87    }
88
89    let ItemImpl {
90        mut attrs,
91        defaultness,
92        unsafety,
93        impl_token,
94        generics,
95        trait_,
96        self_ty,
97        items,
98        ..
99    } = item;
100
101    take_attributes(&mut attrs, |attr| {
102        if !attr.path().is_ident("qjs") {
103            return Ok(false);
104        }
105
106        let options: OptionList<ImplOption> = attr.parse_args()?;
107        for option in options.0.iter() {
108            config.apply(option)
109        }
110
111        Ok(true)
112    })?;
113
114    if let Some(trait_) = trait_.as_ref() {
115        return Err(Error::new(
116            trait_.2.span(),
117            "#[method] can't be applied to a trait implementation",
118        ));
119    }
120
121    if let Some(d) = defaultness {
122        return Err(Error::new(
123            d.span(),
124            "specialized impl's are not supported.",
125        ));
126    }
127    if let Some(u) = unsafety {
128        return Err(Error::new(u.span(), "unsafe impl's are not supported."));
129    }
130
131    let prefix = config.prefix.unwrap_or_else(|| BASE_PREFIX.to_string());
132    let crate_name = format_ident!("{}", config.crate_.map(Ok).unwrap_or_else(crate_ident)?);
133
134    let mut accessors = HashMap::new();
135    let mut functions = Vec::new();
136    let mut constructor: Option<Method> = None;
137    let mut static_span: Option<Span> = None;
138    //let mut consts = Vec::new();
139
140    for item in items {
141        match item {
142            syn::ImplItem::Const(_item) => {}
143            syn::ImplItem::Fn(item) => {
144                let function = Method::parse_impl_fn(item, &self_ty)?;
145                let span = function.attr_span;
146                if function.config.get || function.config.set {
147                    let access = accessors
148                        .entry(function.name(config.rename_all))
149                        .or_insert_with(JsAccessor::new);
150                    if function.config.get {
151                        access.define_get(function, config.rename_all)?;
152                    } else {
153                        access.define_set(function, config.rename_all)?;
154                    }
155                } else if function.config.constructor {
156                    if let Some(first) = constructor.replace(function) {
157                        let first_span = first.attr_span;
158                        let mut error =
159                            Error::new(span, "A class can only have a single constructor");
160                        error.extend(Error::new(first_span, "First constructor defined here"));
161                        return Err(error);
162                    }
163                } else {
164                    if static_span.is_none() && function.config.r#static {
165                        static_span = Some(function.attr_span);
166                    }
167                    functions.push(function)
168                }
169            }
170            _ => {}
171        }
172    }
173
174    // Warn about unused static definitions if no constructor was created.
175    /* if constructor.is_none() {
176        if let Some(span) = static_span {
177            emit_warning!(
178                span,
179                "Static methods are unused if an class doesn't have a constructor.";
180                hint = "Static methods are defined on the class constructor."
181            );
182        }
183    }*/
184
185    let function_impls = functions.iter().map(|func| func.expand_impl());
186    let accessor_impls = accessors.values().map(|access| access.expand_impl());
187    let constructor_impl = constructor.as_ref().map(|constr| constr.expand_impl());
188
189    let function_js_impls = functions
190        .iter()
191        .map(|func| func.expand_js_impl(IMPL_PREFIX, &crate_name));
192    let accessor_js_impls = accessors
193        .values()
194        .map(|access| access.expand_js_impl(&crate_name));
195    let constructor_js_impl = constructor
196        .as_ref()
197        .map(|constr| constr.expand_js_impl(IMPL_PREFIX, &crate_name));
198
199    let associated_types = functions
200        .iter()
201        .map(|func| func.expand_associated_type(&prefix, IMPL_PREFIX));
202
203    let proto_ident = format_ident!("_proto");
204    let function_apply_proto = functions
205        .iter()
206        .filter(|&func| !func.config.r#static)
207        .map(|func| {
208            func.expand_apply_to_object(&prefix, &self_ty, &proto_ident, config.rename_all)
209        });
210    let accessor_apply_proto = accessors
211        .values()
212        .map(|access| access.expand_apply_to_proto(&crate_name, config.rename_all));
213
214    let constructor_ident = format_ident!("constr");
215
216    let constructor_create = if let Some(c) = constructor.as_ref() {
217        let name = c.function.expand_carry_type_name(IMPL_PREFIX);
218
219        let js_added_generics = add_js_lifetime(&generics);
220
221        let static_function_apply =
222            functions
223                .iter()
224                .filter(|&func| func.config.r#static)
225                .map(|func| {
226                    func.expand_apply_to_object(
227                        &prefix,
228                        &self_ty,
229                        &constructor_ident,
230                        config.rename_all,
231                    )
232                });
233
234        quote! {
235            impl #js_added_generics #crate_name::class::impl_::ConstructorCreator<'js,#self_ty> for #crate_name::class::impl_::ConstructorCreate<#self_ty> {
236                fn create_constructor(&self, ctx: &#crate_name::Ctx<'js>) -> #crate_name::Result<Option<#crate_name::function::Constructor<'js>>>{
237                    let constr = #crate_name::function::Constructor::new_class::<#self_ty,_,_>(ctx.clone(),#name)?;
238                    #(#static_function_apply)*
239                    Ok(Some(constr))
240                }
241            }
242        }
243    } else {
244        TokenStream::new()
245    };
246
247    let class_name = get_class_name(&self_ty);
248    let impl_mod_name = format_ident!("__impl_methods_{class_name}__");
249
250    let res = quote! {
251        #(#attrs)*
252        #impl_token #generics #self_ty {
253            #(#function_impls)*
254            #(#accessor_impls)*
255            #constructor_impl
256        }
257
258
259        mod #impl_mod_name{
260            pub use super::*;
261            #(#function_js_impls)*
262            #(#accessor_js_impls)*
263            #constructor_js_impl
264
265            #[allow(non_upper_case_globals)]
266            impl #generics #self_ty{
267                #(#associated_types)*
268            }
269
270            impl #generics #crate_name::class::impl_::MethodImplementor<#self_ty> for #crate_name::class::impl_::MethodImpl<#self_ty> {
271                fn implement(&self, _proto: &#crate_name::Object<'_>) -> #crate_name::Result<()>{
272                    #(#function_apply_proto)*
273                    #(#accessor_apply_proto)*
274                    Ok(())
275                }
276            }
277
278            #constructor_create
279        }
280    };
281
282    Ok(res)
283}