ion_proc/class/impl/
spec.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 std::collections::HashMap;
8
9use proc_macro2::{Ident, Span, TokenStream};
10use quote::{format_ident, quote_spanned};
11use syn::{ImplItemFn, Result, Type, parse_quote, parse2};
12
13use crate::class::accessor::{Accessor, flatten_accessors};
14use crate::class::method::Method;
15use crate::class::property::Property;
16
17#[derive(Default)]
18pub(super) struct PrototypeSpecs {
19	pub(super) methods: (Vec<Method>, Vec<Method>),
20	pub(super) properties: (Vec<Property>, Vec<Property>),
21	pub(super) accessors: (HashMap<String, Accessor>, HashMap<String, Accessor>),
22}
23
24impl PrototypeSpecs {
25	pub(super) fn to_impl_fns(
26		&self, ion: &TokenStream, span: Span, ident: &Ident,
27	) -> Result<(Vec<ImplItemFn>, Vec<ImplItemFn>)> {
28		let mut impl_fns = Vec::with_capacity(4);
29
30		if !self.methods.0.is_empty() {
31			impl_fns.push(methods_to_impl_fn(ion, span, ident, &self.methods.0, false)?);
32		}
33		if !self.methods.1.is_empty() {
34			impl_fns.push(methods_to_impl_fn(ion, span, ident, &self.methods.1, true)?);
35		}
36
37		if !self.properties.0.is_empty() || !self.accessors.0.is_empty() {
38			impl_fns.push(properties_to_spec_function(
39				ion,
40				span,
41				ident,
42				&self.properties.0,
43				&self.accessors.0,
44				false,
45			)?);
46		}
47		if !self.properties.1.is_empty() || !self.accessors.1.is_empty() {
48			impl_fns.push(properties_to_spec_function(
49				ion,
50				span,
51				ident,
52				&self.properties.1,
53				&self.accessors.1,
54				true,
55			)?);
56		}
57
58		Ok(impl_fns.into_iter().unzip())
59	}
60
61	pub(super) fn into_functions(self) -> Vec<Method> {
62		let len = self.methods.0.len() + self.methods.1.len() + self.accessors.0.len() + self.accessors.1.len();
63		let mut functions = Vec::with_capacity(len);
64
65		functions.extend(self.methods.0);
66		functions.extend(self.methods.1);
67		functions.extend(flatten_accessors(self.accessors.0));
68		functions.extend(flatten_accessors(self.accessors.1));
69
70		functions
71	}
72}
73
74fn methods_to_impl_fn(
75	ion: &TokenStream, span: Span, class: &Ident, methods: &[Method], r#static: bool,
76) -> Result<(ImplItemFn, ImplItemFn)> {
77	let mut ident = parse_quote!(functions);
78	if r#static {
79		ident = format_ident!("static_{}", ident);
80	}
81	let function_ident = format_ident!("__ion_{}_specs", ident);
82
83	let specs: Vec<_> = methods.iter().flat_map(|method| method.to_specs(ion, class)).collect();
84
85	let ty = parse_quote!(::mozjs::jsapi::JSFunctionSpec);
86	Ok((
87		spec_function(span, &function_ident, &specs, &ty)?,
88		def_function(span, &ident, &function_ident, &ty)?,
89	))
90}
91
92fn properties_to_spec_function(
93	ion: &TokenStream, span: Span, class: &Ident, properties: &[Property], accessors: &HashMap<String, Accessor>,
94	r#static: bool,
95) -> Result<(ImplItemFn, ImplItemFn)> {
96	let mut ident = parse_quote!(properties);
97	if r#static {
98		ident = format_ident!("static_{}", ident);
99	}
100	let function_ident = format_ident!("__ion_{}_specs", ident);
101
102	let mut specs: Vec<_> = properties.iter().flat_map(|property| property.to_specs(ion, class)).collect();
103	for accessor in accessors.values() {
104		specs.extend(accessor.to_specs(ion, class));
105	}
106
107	let ty = parse_quote!(::mozjs::jsapi::JSPropertySpec);
108	Ok((
109		spec_function(span, &function_ident, &specs, &ty)?,
110		def_function(span, &ident, &function_ident, &ty)?,
111	))
112}
113
114fn spec_function(span: Span, function_ident: &Ident, specs: &[TokenStream], ty: &Type) -> Result<ImplItemFn> {
115	assert!(!specs.is_empty());
116	parse2(quote_spanned!(span => fn #function_ident() -> &'static [#ty] {
117		static SPECS: &[#ty] = &[
118			#(#specs,)*
119			#ty::ZERO,
120		];
121		SPECS
122	}))
123}
124
125fn def_function(span: Span, ident: &Ident, function_ident: &Ident, ty: &Type) -> Result<ImplItemFn> {
126	parse2(
127		quote_spanned!(span => fn #ident() -> ::std::option::Option<&'static [#ty]> {
128			::std::option::Option::Some(Self::#function_ident())
129		}),
130	)
131}