jstraceable_derive/lib.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use syn::parse_quote;
6use synstructure::{decl_derive, quote};
7
8decl_derive!([JSTraceable, attributes(no_trace, custom_trace)] =>
9/// Implements `JSTraceable` on structs and enums
10///
11/// Example:
12/// ```rust
13/// #[derive(JSTraceable)]
14/// struct S {
15/// js_managed: JSManagedType,
16/// #[no_trace]
17/// non_js: NonJSManagedType,
18/// #[custom_trace] // Extern type implements CustomTraceable that is in servo => no problem with orphan rules
19/// extern_managed_type: Extern<JSManagedType>,
20/// }
21/// ```
22///
23/// creates:
24///
25/// ```rust
26/// unsafe impl JSTraceable for S {
27/// #[inline]
28/// unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
29/// match *self {
30/// S {
31/// js_managed: ref __binding_0,
32/// non_js: ref __binding_1,
33/// extern_managed_type: ref __binding_2,
34/// } => {
35/// {
36/// __binding_0.trace(tracer);
37/// }
38/// {
39/// // __binding_1 is not traceable so we do not need to trace it
40/// }
41/// {
42/// <crate::dom::bindings::trace::CustomTraceable>::trace(__binding_2, tracer);
43/// }
44/// },
45/// }
46/// }
47/// }
48/// ```
49///
50/// In cases where there is a need to make type (empty) traceable (`HashMap<NoTraceable, Traceable>`),
51/// NoTrace wrapper can be used, because it implements empty traceble:
52/// ```rust
53/// unsafe impl<T> JSTraceable for NoTrace<T> {
54/// unsafe fn trace(&self, _: *mut ::js::jsapi::JSTracer) { /* nop */}
55/// }
56/// ```
57///
58/// ## SAFETY
59/// Puting `#[no_trace]` on fields is safe if there are no types that are JS managed in that field.
60/// `#[no_trace]` should NOT be put on field that does implement (non-empty) `JSTraceable` (is JS managed).
61/// There are safeguards in place to prevent such mistakes. Example error:
62///
63/// ```console
64/// error[E0282]: type annotations needed
65/// |
66/// | #[derive(JSTraceable, MallocSizeOf)]
67/// | ^^^^^^^^^^^ cannot infer type of the type parameter `Self` declared on the trait `NoTraceOnJSTraceable`
68/// |
69/// = note: this error originates in the derive macro `JSTraceable`
70/// ```
71///
72/// If you can assure that type has empty JSTraceable impl, you can bypass guards, providing your reasoning:
73/// ```rust
74/// #[derive(JSTraceable)]
75/// struct S {
76/// #[no_trace = "Safe because both u32 and u64 are empty traceable"]
77/// field: HashMap<u32, u64>,
78/// }
79/// ```
80js_traceable_derive);
81
82// based off https://docs.rs/static_assertions/latest/src/static_assertions/assert_impl.rs.html#263
83fn assert_not_impl_traceable(ty: &syn::Type) -> proc_macro2::TokenStream {
84 quote!(
85 const _: fn() = || {
86 // Generic trait with a blanket impl over `()` for all types.
87 // becomes ambiguous if impl
88 trait NoTraceOnJSTraceable<A> {
89 // Required for actually being able to reference the trait.
90 fn some_item() {}
91 }
92
93 impl<T: ?Sized> NoTraceOnJSTraceable<()> for T {}
94
95 // Used for the specialized impl when JSTraceable is implemented.
96 #[expect(dead_code)]
97 struct Invalid0;
98 // forbids JSTraceable
99 impl<T> NoTraceOnJSTraceable<Invalid0> for T where T: ?Sized + crate::JSTraceable {}
100
101 #[expect(dead_code)]
102 struct Invalid2;
103 // forbids HashMap<JSTraceble, _>
104 impl<K, V, S> NoTraceOnJSTraceable<Invalid2> for std::collections::HashMap<K, V, S>
105 where
106 K: crate::JSTraceable + std::cmp::Eq + std::hash::Hash,
107 S: std::hash::BuildHasher,
108 {
109 }
110
111 #[expect(dead_code)]
112 struct Invalid3;
113 // forbids HashMap<_, JSTraceble>
114 impl<K, V, S> NoTraceOnJSTraceable<Invalid3> for std::collections::HashMap<K, V, S>
115 where
116 K: std::cmp::Eq + std::hash::Hash,
117 V: crate::JSTraceable,
118 S: std::hash::BuildHasher,
119 {
120 }
121
122 #[expect(dead_code)]
123 struct Invalid4;
124 // forbids BTreeMap<_, JSTraceble>
125 impl<K, V> NoTraceOnJSTraceable<Invalid4> for std::collections::BTreeMap<K, V> where
126 K: crate::JSTraceable + std::cmp::Eq + std::hash::Hash
127 {
128 }
129
130 #[expect(dead_code)]
131 struct Invalid5;
132 // forbids BTreeMap<_, JSTraceble>
133 impl<K, V> NoTraceOnJSTraceable<Invalid5> for std::collections::BTreeMap<K, V>
134 where
135 K: std::cmp::Eq + std::hash::Hash,
136 V: crate::JSTraceable,
137 {
138 }
139
140 // If there is only one specialized trait impl, type inference with
141 // `_` can be resolved and this can compile. Fails to compile if
142 // ty implements `NoTraceOnJSTraceable<InvalidX>`.
143 let _ = <#ty as NoTraceOnJSTraceable<_>>::some_item;
144 };
145 )
146}
147
148fn js_traceable_derive(s: synstructure::Structure) -> proc_macro2::TokenStream {
149 let mut asserts = quote!();
150 let match_body = s.each(|binding| {
151 for attr in binding.ast().attrs.iter() {
152 if attr.path().is_ident("no_trace") {
153 // If no reason argument is provided to `no_trace` (ie `#[no_trace="This types does not need..."]`),
154 // assert that the type in this bound field does not implement traceable.
155 if !matches!(attr.meta, syn::Meta::NameValue(_)) {
156 asserts.extend(assert_not_impl_traceable(&binding.ast().ty));
157 }
158 return None;
159 } else if attr.path().is_ident("custom_trace") {
160 return Some(quote!(<dyn crate::CustomTraceable>::trace(#binding, tracer);));
161 }
162 }
163 Some(quote!(#binding.trace(tracer);))
164 });
165
166 let ast = s.ast();
167 let name = &ast.ident;
168 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
169 let mut where_clause = where_clause.unwrap_or(&parse_quote!(where)).clone();
170 for param in ast.generics.type_params() {
171 let ident = ¶m.ident;
172 where_clause
173 .predicates
174 .push(parse_quote!(#ident: crate::JSTraceable))
175 }
176
177 let tokens = quote! {
178 #asserts
179
180 #[expect(unsafe_code)]
181 unsafe impl #impl_generics crate::JSTraceable for #name #ty_generics #where_clause {
182 #[inline]
183 #[expect(unused_variables, unused_imports)]
184 unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
185 use crate::JSTraceable;
186 match *self {
187 #match_body
188 }
189 }
190 }
191 };
192
193 tokens
194}