1use proc_macro2::{Ident, Span, TokenStream};
8use quote::{ToTokens as _, quote, quote_spanned};
9use syn::spanned::Spanned as _;
10use syn::{Error, FnArg, ImplItem, ImplItemFn, ItemFn, ItemImpl, Result, Type, Visibility, parse_quote, parse2};
11
12use crate::attribute::ParseAttribute as _;
13use crate::attribute::class::MethodAttribute;
14use crate::attribute::krate::crate_from_attributes;
15use crate::attribute::name::Name;
16use crate::class::accessor::{get_accessor_name, impl_accessor, insert_accessor};
17use crate::class::constructor::impl_constructor;
18use crate::class::r#impl::spec::PrototypeSpecs;
19use crate::class::method::{Method, MethodKind, MethodReceiver, impl_method};
20use crate::class::property::{Property, PropertyType};
21
22mod spec;
23
24pub(super) fn impl_js_class_impl(r#impl: &mut ItemImpl) -> Result<[ItemImpl; 2]> {
25 let ion = &crate_from_attributes(&mut r#impl.attrs);
26
27 if !r#impl.generics.params.is_empty() {
28 return Err(Error::new(
29 r#impl.generics.span(),
30 "Native Class Impls cannot have generics.",
31 ));
32 }
33
34 if let Some(r#trait) = &r#impl.trait_ {
35 return Err(Error::new(
36 r#trait.1.span(),
37 "Native Class Impls cannot be for a trait.",
38 ));
39 }
40
41 let r#type = *r#impl.self_ty.clone();
42 let mut constructor: Option<Method> = None;
43 let mut specs = PrototypeSpecs::default();
44
45 for item in &mut r#impl.items {
46 match item {
47 ImplItem::Const(r#const) => {
48 if let Some((property, r#static)) = Property::from_const(r#const)? {
49 if r#static {
50 specs.properties.1.push(property);
51 } else {
52 specs.properties.0.push(property);
53 }
54 }
55 }
56 ImplItem::Fn(r#fn) => {
57 r#fn.attrs.push(parse_quote!(#[allow(clippy::needless_pass_by_value)]));
58 if let Some(parsed_constructor) = parse_class_method(ion, r#fn, &mut specs, &r#type)? {
59 if let Some(constructor) = constructor.as_ref() {
60 return Err(Error::new(
61 r#fn.span(),
62 format!(
63 "Received multiple constructor implementations: {} and {}.",
64 constructor.method.sig.ident, parsed_constructor.method.sig.ident
65 ),
66 ));
67 }
68 r#fn.attrs.push(parse_quote!(#[allow(clippy::same_name_method)]));
69 constructor = Some(parsed_constructor);
70 }
71 }
72 _ => (),
73 }
74 }
75 specs.properties.0.push(Property {
76 ty: PropertyType::String,
77 ident: parse_quote!(__ION_TO_STRING_TAG),
78 names: vec![Name::Symbol(
79 parse_quote!(#ion::symbol::WellKnownSymbolCode::ToStringTag),
80 )],
81 });
82
83 let ident: Ident = parse2(quote_spanned!(r#type.span() => #r#type))?;
84 class_definition(ion, r#impl.span(), &r#type, &ident, constructor, specs)
85}
86
87fn parse_class_method(
88 ion: &TokenStream, r#fn: &mut ImplItemFn, specs: &mut PrototypeSpecs, r#type: &Type,
89) -> Result<Option<Method>> {
90 match &r#fn.vis {
91 Visibility::Public(_) => (),
92 _ => return Ok(None),
93 }
94
95 let mut names = vec![];
96
97 let attribute = MethodAttribute::from_attributes("ion", &mut r#fn.attrs, ())?;
98 let MethodAttribute { name, aliases, kind, skip } = attribute;
99 for alias in aliases {
100 names.push(Name::String(alias));
101 }
102 if skip {
103 return Ok(None);
104 }
105
106 let name = name.unwrap_or_else(|| {
107 if kind == Some(MethodKind::Getter) || kind == Some(MethodKind::Setter) {
108 Name::from_string(
109 get_accessor_name(r#fn.sig.ident.to_string(), kind == Some(MethodKind::Setter)),
110 r#fn.sig.ident.span(),
111 )
112 } else {
113 Name::from_string(r#fn.sig.ident.to_string(), r#fn.sig.ident.span())
114 }
115 });
116 names.insert(0, name.clone());
117
118 let method: ItemFn = parse2(r#fn.to_token_stream())?;
119
120 for input in &mut r#fn.sig.inputs {
121 let attrs = match input {
122 FnArg::Receiver(arg) => &mut arg.attrs,
123 FnArg::Typed(arg) => &mut arg.attrs,
124 };
125 attrs.clear();
126 }
127
128 let class = if let Type::Path(ty) = r#type {
129 ty.path.segments.last().unwrap().ident.to_string()
130 } else {
131 return Err(Error::new(r#type.span(), "Invalid Type for Impl"));
132 };
133
134 match kind {
135 Some(MethodKind::Constructor) => {
136 let constructor = impl_constructor(ion, method, r#type, &class)?;
137 return Ok(Some(Method { names, ..constructor }));
138 }
139 Some(MethodKind::Getter) => {
140 let (getter, parameters) = impl_accessor(ion, method, r#type, false, &class)?;
141 let getter = Method { names, ..getter };
142
143 if parameters.this.is_some() {
144 insert_accessor(&mut specs.accessors.0, name.as_string(), Some(getter), None);
145 } else {
146 insert_accessor(&mut specs.accessors.1, name.as_string(), Some(getter), None);
147 }
148 }
149 Some(MethodKind::Setter) => {
150 let (setter, parameters) = impl_accessor(ion, method, r#type, true, &class)?;
151 let setter = Method { names, ..setter };
152
153 if parameters.this.is_some() {
154 insert_accessor(&mut specs.accessors.0, name.as_string(), None, Some(setter));
155 } else {
156 insert_accessor(&mut specs.accessors.1, name.as_string(), None, Some(setter));
157 }
158 }
159 None => {
160 let (method, _) = impl_method(ion, method, r#type, &class, |_| Ok(()))?;
161 let method = Method { names, ..method };
162
163 if method.receiver == MethodReceiver::Dynamic {
164 specs.methods.0.push(method);
165 } else {
166 specs.methods.1.push(method);
167 }
168 }
169 }
170
171 Ok(None)
172}
173
174fn class_definition(
175 ion: &TokenStream, span: Span, r#type: &Type, ident: &Ident, constructor: Option<Method>, specs: PrototypeSpecs,
176) -> Result<[ItemImpl; 2]> {
177 let (spec_fns, def_fns) = specs.to_impl_fns(ion, span, ident)?;
178 let constructor_function = constructor.as_ref().map(|c| &c.method);
179 let functions = specs.into_functions().into_iter().map(|method| method.method);
180
181 let mut spec_impls: ItemImpl = parse2(quote_spanned!(span => impl #r#type {
182 #constructor_function
183 #(#functions)*
184 #(#spec_fns)*
185 }))?;
186 spec_impls.attrs.push(parse_quote!(#[doc(hidden)]));
187
188 let (constructor_function, constructor_nargs) = constructor.map_or((quote!(::std::option::Option::None), 0), |c| {
189 (
190 quote!(::std::option::Option::Some(Self::__ion_bindings_constructor)),
191 u32::from(c.nargs),
192 )
193 });
194 let class_definition = parse2(quote_spanned!(span => impl #ion::ClassDefinition for #r#type {
195 fn class() -> &'static #ion::class::NativeClass {
196 static __ION_NATIVE_CLASS: &#ion::class::NativeClass = #r#type::__ion_native_class();
197 __ION_NATIVE_CLASS
198 }
199
200 fn parent_prototype(cx: &#ion::Context) -> ::std::option::Option<#ion::Local<'_, *mut ::mozjs::jsapi::JSObject>> {
201 #r#type::__ion_parent_prototype(cx)
202 }
203
204 fn constructor() -> (::std::option::Option<#ion::function::NativeFunction>, ::core::primitive::u32) {
205 (#constructor_function, #constructor_nargs)
206 }
207
208 #(#def_fns)*
209 }))?;
210
211 Ok([spec_impls, class_definition])
212}