1use 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}