ion_proc/class/
struct.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::{quote, quote_spanned};
9use syn::punctuated::Punctuated;
10use syn::spanned::Spanned as _;
11use syn::{Error, ItemImpl, ItemStruct, Member, Path, Result, Token, Type, parse_quote, parse2};
12
13use crate::attribute::ParseAttribute as _;
14use crate::attribute::class::ClassAttribute;
15use crate::attribute::krate::crate_from_attributes;
16use crate::utils::{new_token, path_ends_with};
17
18pub(super) fn impl_js_class_struct(r#struct: &mut ItemStruct) -> Result<[ItemImpl; 7]> {
19	let ion = &crate_from_attributes(&mut r#struct.attrs);
20
21	let repr_c = r#struct.attrs.iter().fold(Ok(false), |acc, attr| {
22		if attr.path().is_ident("repr") {
23			return match attr.parse_args::<Ident>() {
24				Ok(ident) if ident == "C" => Ok(true),
25				_ => Err(Error::new(attr.span(), "Only C Representations are allowed.")),
26			};
27		}
28		acc
29	})?;
30	if !repr_c {
31		r#struct.attrs.push(parse_quote!(#[repr(C)]));
32	}
33
34	let traceable = r#struct.attrs.iter().any(|attr| {
35		if attr.path().is_ident("derive")
36			&& let Ok(paths) = attr.parse_args_with(Punctuated::<Path, Token![,]>::parse_terminated)
37		{
38			return paths.iter().any(|path| path_ends_with(path, "Traceable"));
39		}
40		false
41	});
42	if !traceable {
43		r#struct.attrs.push(parse_quote!(#[derive(#ion::Traceable)]));
44	}
45
46	let attribute = ClassAttribute::from_attributes("ion", &mut r#struct.attrs, ())?;
47
48	if !r#struct.generics.params.is_empty() {
49		return Err(Error::new(
50			r#struct.generics.span(),
51			"Native Class Structs cannot have generics.",
52		));
53	}
54
55	let name = if let Some(name) = attribute.name {
56		name.value()
57	} else {
58		r#struct.ident.to_string()
59	};
60
61	let ident = &r#struct.ident;
62	let r#type: Type = parse2(quote_spanned!(ident.span() => #ident))?;
63
64	let (super_field, super_type) = if let Some(field) = r#struct.fields.iter().next() {
65		(Member::Named(field.ident.as_ref().unwrap().clone()), field.ty.clone())
66	} else {
67		return Err(Error::new(
68			r#struct.span(),
69			"Native Class Structs must have at least a reflector field.",
70		));
71	};
72
73	if let Type::Path(ty) = &super_type {
74		if ty.path.segments.iter().any(|segment| !segment.arguments.is_empty()) {
75			return Err(Error::new(super_type.span(), "Superclass Type must not have generics."));
76		}
77	} else {
78		return Err(Error::new(super_type.span(), "Superclass Type must be a path."));
79	}
80
81	class_impls(ion, r#struct.span(), &name, &r#type, &super_field, &super_type)
82}
83
84fn class_impls(
85	ion: &TokenStream, span: Span, name: &str, r#type: &Type, super_field: &Member, super_type: &Type,
86) -> Result<[ItemImpl; 7]> {
87	let from_value = impl_from_value(ion, span, r#type, false)?;
88	let from_value_mut = impl_from_value(ion, span, r#type, true)?;
89
90	let derived_from =
91		parse2(quote_spanned!(span => unsafe impl #ion::class::DerivedFrom<#super_type> for #r#type {}))?;
92	let castable = parse2(quote_spanned!(span => impl #ion::class::Castable for #r#type {}))?;
93
94	let native_object = parse2(quote_spanned!(span => impl #ion::class::NativeObject for #r#type {
95		fn reflector(&self) -> &#ion::class::Reflector {
96			#ion::class::NativeObject::reflector(&self.#super_field)
97		}
98	}))?;
99
100	let none = quote!(::std::option::Option::None);
101	let name = format!("{name}\0");
102
103	let mut class_impl: ItemImpl = parse2(quote_spanned!(span => impl #r#type {
104		pub const fn __ion_native_prototype_chain() -> #ion::class::PrototypeChain {
105			const ION_TYPE_ID: #ion::class::TypeIdWrapper<#r#type> = #ion::class::TypeIdWrapper::new();
106			#super_type::__ion_native_prototype_chain().push(&ION_TYPE_ID)
107		}
108
109		pub const fn __ion_native_class() -> &'static #ion::class::NativeClass {
110			const ION_CLASS_OPERATIONS: ::mozjs::jsapi::JSClassOps = ::mozjs::jsapi::JSClassOps {
111				addProperty: #none,
112				delProperty: #none,
113				enumerate: #none,
114				newEnumerate: #none,
115				resolve: #none,
116				mayResolve: #none,
117				finalize: ::std::option::Option::Some(#ion::class::finalise_native_object_operation::<#r#type>),
118				call: #none,
119				construct: #none,
120				trace: ::std::option::Option::Some(#ion::class::trace_native_object_operation::<#r#type>),
121			};
122
123			const ION_NATIVE_CLASS: #ion::class::NativeClass = #ion::class::NativeClass {
124				base: ::mozjs::jsapi::JSClass {
125					name: #name.as_ptr().cast(),
126					flags: #ion::object::class_reserved_slots(1) | ::mozjs::jsapi::JSCLASS_BACKGROUND_FINALIZE,
127					cOps: ::std::ptr::from_ref(&ION_CLASS_OPERATIONS),
128					spec: ::std::ptr::null_mut(),
129					ext: ::std::ptr::null_mut(),
130					oOps: ::std::ptr::null_mut(),
131				},
132				prototype_chain: #r#type::__ion_native_prototype_chain(),
133			};
134
135			&ION_NATIVE_CLASS
136		}
137
138		pub const __ION_TO_STRING_TAG: &'static str = #name;
139	}))?;
140	class_impl.attrs.push(parse_quote!(#[doc(hidden)]));
141
142	let is_reflector = matches!(super_type, Type::Path(ty) if path_ends_with(&ty.path, "Reflector"));
143	let parent_proto = if is_reflector {
144		quote_spanned!(super_type.span() => ::std::option::Option::None)
145	} else {
146		quote_spanned!(super_type.span() =>
147			let infos = unsafe { &mut (*cx.get_inner_data().as_ptr()).class_infos };
148			let info = infos.get(&::core::any::TypeId::of::<#super_type>()).expect("Uninitialised Class");
149			::std::option::Option::Some(cx.root(info.prototype.get()))
150		)
151	};
152	let mut parent_impl: ItemImpl = parse2(quote_spanned!(super_type.span() => impl #r#type {
153		#[allow(unused_variables)]
154		pub fn __ion_parent_prototype(cx: &#ion::Context) -> ::std::option::Option<#ion::Local<'_, *mut ::mozjs::jsapi::JSObject>> {
155			#parent_proto
156		}
157	}))?;
158	parent_impl.attrs.push(parse_quote!(#[doc(hidden)]));
159
160	Ok([
161		from_value,
162		from_value_mut,
163		derived_from,
164		castable,
165		native_object,
166		class_impl,
167		parent_impl,
168	])
169}
170
171fn impl_from_value(ion: &TokenStream, span: Span, r#type: &Type, mutable: bool) -> Result<ItemImpl> {
172	let (function, mutable) = if mutable {
173		(quote!(get_mut_private), Some(new_token![mut]))
174	} else {
175		(quote!(get_private), None)
176	};
177
178	parse2(
179		quote_spanned!(span => impl<'cx> #ion::conversions::FromValue<'cx> for &'cx #mutable #r#type {
180			type Config = ();
181
182			fn from_value(cx: &'cx #ion::Context, value: &#ion::Value, strict: ::core::primitive::bool, _: ()) -> #ion::Result<&'cx #mutable #r#type> {
183				let object = #ion::Object::from_value(cx, value, strict, ())?;
184				<#r#type as #ion::class::ClassDefinition>::#function(cx, &object)
185			}
186		}),
187	)
188}