ion_proc/class/impl/
mod.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}