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