ion_proc/class/
accessor.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;
8use std::collections::hash_map::Entry;
9
10use convert_case::{Case, Casing as _};
11use proc_macro2::{Ident, TokenStream};
12use quote::{format_ident, quote};
13use syn::spanned::Spanned as _;
14use syn::{Error, ItemFn, Result, Type};
15
16use crate::class::method::{Method, impl_method};
17use crate::function::parameter::Parameters;
18
19pub(super) struct Accessor(pub(super) Option<Method>, Option<Method>);
20
21impl Accessor {
22	pub(super) fn to_specs(&self, ion: &TokenStream, class: &Ident) -> Vec<TokenStream> {
23		let names = self.0.as_ref().or(self.1.as_ref()).map(|method| &*method.names).unwrap_or_default();
24		names
25			.iter()
26			.map(|name| {
27				let mut function_ident = format_ident!("property_spec");
28
29				let (key, flags) = name.to_property_spec(ion, &mut function_ident);
30
31				match self {
32					Accessor(Some(getter), Some(setter)) => {
33						let getter = getter.method.sig.ident.clone();
34						let setter = setter.method.sig.ident.clone();
35
36						function_ident = format_ident!("{}_getter_setter", function_ident);
37						quote!(#ion::#function_ident!(#class::#getter, #class::#setter, #key, #flags))
38					}
39					Accessor(Some(getter), None) => {
40						let getter = getter.method.sig.ident.clone();
41
42						function_ident = format_ident!("{}_getter", function_ident);
43						quote!(#ion::#function_ident!(#class::#getter, #key, #flags))
44					}
45					Accessor(None, Some(setter)) => {
46						let setter = setter.method.sig.ident.clone();
47						function_ident = format_ident!("{}_getter", function_ident);
48						quote!(#ion::#function_ident!(#class::#setter, #key, #flags))
49					}
50					Accessor(None, None) => {
51						function_ident = format_ident!("create_{}_accessor", function_ident);
52						quote!(
53							#ion::spec::#function_ident(
54								#key,
55								::mozjs::jsapi::JSNativeWrapper { op: None, info: ::std::ptr::null_mut() },
56								::mozjs::jsapi::JSNativeWrapper { op: None, info: ::std::ptr::null_mut() },
57								#flags,
58							)
59						)
60					}
61				}
62			})
63			.collect()
64	}
65}
66
67pub(super) fn get_accessor_name(mut name: String, is_setter: bool) -> String {
68	let pat_snake = if is_setter { "set_" } else { "get_" };
69	let pat_camel = if is_setter { "set" } else { "get" };
70	if name.starts_with(pat_snake) {
71		name.drain(0..4);
72		if name.is_case(Case::Snake) {
73			name = name.to_case(Case::Camel);
74		}
75	} else if name.starts_with(pat_camel) {
76		name.drain(0..3);
77		if name.is_case(Case::Pascal) {
78			name = name.to_case(Case::Camel);
79		}
80	}
81	name
82}
83
84pub(super) fn impl_accessor(
85	ion: &TokenStream, method: ItemFn, ty: &Type, is_setter: bool, class: &str,
86) -> Result<(Method, Parameters)> {
87	let expected_args = i32::from(is_setter);
88	let error_message = if is_setter {
89		format!("Expected Setter to have {expected_args} argument")
90	} else {
91		format!("Expected Getter to have {expected_args} arguments")
92	};
93	let error = Error::new(method.sig.span(), error_message);
94
95	let ident = if is_setter {
96		format_ident!("__ion_bindings_setter_{}", method.sig.ident)
97	} else {
98		format_ident!("__ion_bindings_getter_{}", method.sig.ident)
99	};
100	let (mut accessor, parameters) = impl_method(ion, method, ty, class, |sig| {
101		let parameters = Parameters::parse(&sig.inputs, Some(ty))?;
102		let nargs: i32 = parameters
103			.parameters
104			.iter()
105			.map(|param| i32::from(matches!(&*param.pat_ty.ty, Type::Path(_))))
106			.sum();
107		(nargs == expected_args).then_some(()).ok_or(error)
108	})?;
109	accessor.method.sig.ident = ident;
110
111	Ok((accessor, parameters))
112}
113
114pub(super) fn insert_accessor(
115	accessors: &mut HashMap<String, Accessor>, name: String, getter: Option<Method>, setter: Option<Method>,
116) {
117	match accessors.entry(name) {
118		Entry::Occupied(mut o) => match (getter, setter) {
119			(Some(g), Some(s)) => *o.get_mut() = Accessor(Some(g), Some(s)),
120			(Some(g), None) => o.get_mut().0 = Some(g),
121			(None, Some(s)) => o.get_mut().1 = Some(s),
122			(None, None) => {}
123		},
124		Entry::Vacant(v) => {
125			v.insert(Accessor(getter, setter));
126		}
127	}
128}
129
130pub(super) fn flatten_accessors(accessors: HashMap<String, Accessor>) -> impl Iterator<Item = Method> {
131	accessors.into_iter().flat_map(|(_, Accessor(getter, setter))| [getter, setter]).flatten()
132}