1use quote::{TokenStreamExt, quote};
6
7pub(crate) fn expand_dom_object(
10 input: syn::ItemStruct,
11 associated_memory: bool,
12) -> proc_macro2::TokenStream {
13 let fields = input.fields.iter().collect::<Vec<&syn::Field>>();
14 let (first_field, fields) = fields
15 .split_first()
16 .expect("#[dom_struct] should not be applied on empty structs");
17
18 let first_field_name = first_field.ident.as_ref().unwrap();
19 let reflector_type = if associated_memory {
20 quote! { crate::AssociatedMemory }
21 } else {
22 quote! { () }
23 };
24 let mut field_types_and_cfgs = vec![];
25 for field in fields {
26 if field_types_and_cfgs.contains(&(&field.ty, vec![])) {
27 continue;
28 }
29 let cfgs = field
30 .attrs
31 .iter()
32 .filter(|a| a.path().is_ident("cfg"))
33 .collect::<Vec<_>>();
34 field_types_and_cfgs.push((&field.ty, cfgs));
35 }
36
37 let name = &input.ident;
38 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
39 let items = quote! {
40 impl #impl_generics ::js::conversions::ToJSValConvertible for #name #ty_generics #where_clause {
41 #[expect(unsafe_code)]
42 unsafe fn to_jsval(&self,
43 cx: *mut js::jsapi::JSContext,
44 rval: js::rust::MutableHandleValue) {
45 let object = crate::DomObject::reflector(self).get_jsobject();
46 object.to_jsval(cx, rval)
47 }
48 }
49
50 impl #impl_generics crate::DomObject for #name #ty_generics #where_clause {
51 type ReflectorType = #reflector_type;
52
53 #[inline]
54 fn reflector(&self) -> &crate::Reflector<Self::ReflectorType> {
55 self.#first_field_name.reflector()
56 }
57 }
58
59 impl #impl_generics crate::MutDomObject for #name #ty_generics #where_clause {
60 unsafe fn init_reflector<Actual>(&self, obj: *mut js::jsapi::JSObject) {
61 self.#first_field_name.init_reflector::<Actual>(obj);
62 }
63 }
64
65 impl #impl_generics Eq for #name #ty_generics #where_clause {}
66
67 impl #impl_generics PartialEq for #name #ty_generics #where_clause {
68 fn eq(&self, other: &Self) -> bool {
69 crate::DomObject::reflector(self) == crate::DomObject::reflector(other)
70 }
71 }
72 };
73
74 let mut params = proc_macro2::TokenStream::new();
75 params.append_separated(
76 input.generics.type_params().map(|param| ¶m.ident),
77 quote! {,},
78 );
79
80 let mut dummy_items = quote! {
81 trait NoDomObjectInDomObject<A> {
84 fn some_item() {}
86 }
87
88 impl<T: ?Sized> NoDomObjectInDomObject<()> for T {}
89
90 #[expect(dead_code)]
92 struct Invalid;
93 impl<T> NoDomObjectInDomObject<Invalid> for T where T: ?Sized + crate::DomObject {}
95 };
96
97 dummy_items.append_all(
98 field_types_and_cfgs
99 .iter()
100 .enumerate()
101 .map(|(i, (ty, cfgs))| {
102 let s = syn::Ident::new(&format!("S{i}"), proc_macro2::Span::call_site());
103 quote! {
104 struct #s<#params>(#params);
105
106 impl #impl_generics #s<#params> #where_clause {
107 #(#cfgs)*
108 fn f() {
109 let _ = <#ty as NoDomObjectInDomObject<_>>::some_item;
113 }
114 }
115 }
116 }),
117 );
118
119 let dummy_const = syn::Ident::new(
120 &format!("_IMPL_DOMOBJECT_FOR_{}", name),
121 proc_macro2::Span::call_site(),
122 );
123 let tokens = quote! {
124 #[expect(non_upper_case_globals)]
125 const #dummy_const: () = { #dummy_items };
126 #items
127 };
128
129 tokens
130}