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