1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use syn::parse_quote;
use synstructure::{decl_derive, quote};

decl_derive!([JSTraceable, attributes(no_trace, custom_trace)] =>
/// Implements `JSTraceable` on structs and enums
///
/// Example:
/// ```rust
/// #[derive(JSTraceable)]
/// struct S {
///   js_managed: JSManagedType,
///   #[no_trace]
///   non_js: NonJSManagedType,
///   #[custom_trace] // Extern type implements CustomTraceable that is in servo => no problem with orphan rules
///   extern_managed_type: Extern<JSManagedType>,
/// }
/// ```
///
/// creates:
///
/// ```rust
/// unsafe impl JSTraceable for S {
///     #[inline]
///     unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
///         match *self {
///             S {
///                 js_managed: ref __binding_0,
///                 non_js: ref __binding_1,
///                 extern_managed_type: ref __binding_2,
///             } => {
///                 {
///                     __binding_0.trace(tracer);
///                 }
///                 {
///                     // __binding_1 is not traceable so we do not need to trace it
///                 }
///                 {
///                     <crate::dom::bindings::trace::CustomTraceable>::trace(__binding_2, tracer);
///                 }
///             },
///         }
///     }
/// }
/// ```
///
/// In cases where there is a need to make type (empty) traceable (`HashMap<NoTraceable, Traceable>`),
/// NoTrace wrapper can be used, because it implements empty traceble:
/// ```rust
/// unsafe impl<T> JSTraceable for NoTrace<T> {
///     unsafe fn trace(&self, _: *mut ::js::jsapi::JSTracer) { /* nop */}
/// }
/// ```
///
/// ## SAFETY
/// Puting `#[no_trace]` on fields is safe if there are no types that are JS managed in that field.
/// `#[no_trace]` should NOT be put on field that does implement (non-empty) `JSTraceable` (is JS managed).
/// There are safeguards in place to prevent such mistakes. Example error:
///
/// ```console
/// error[E0282]: type annotations needed
/// |
/// | #[derive(JSTraceable, MallocSizeOf)]
/// |          ^^^^^^^^^^^ cannot infer type of the type parameter `Self` declared on the trait `NoTraceOnJSTraceable`
/// |
/// = note: this error originates in the derive macro `JSTraceable`
/// ```
///
/// If you can assure that type has empty JSTraceable impl, you can bypass guards, providing your reasoning:
/// ```rust
/// #[derive(JSTraceable)]
/// struct S {
///   #[no_trace = "Safe because both u32 and u64 are empty traceable"]
///   field: HashMap<u32, u64>,
/// }
/// ```
js_traceable_derive);

// based off https://docs.rs/static_assertions/latest/src/static_assertions/assert_impl.rs.html#263
fn assert_not_impl_traceable(ty: &syn::Type) -> proc_macro2::TokenStream {
    quote!(
        const _: fn() = || {
            // Generic trait with a blanket impl over `()` for all types.
            // becomes ambiguous if impl
            trait NoTraceOnJSTraceable<A> {
                // Required for actually being able to reference the trait.
                fn some_item() {}
            }

            impl<T: ?Sized> NoTraceOnJSTraceable<()> for T {}

            // Used for the specialized impl when JSTraceable is implemented.
            #[allow(dead_code)]
            struct Invalid0;
            // forbids JSTraceable
            impl<T> NoTraceOnJSTraceable<Invalid0> for T where T: ?Sized + crate::JSTraceable {}

            #[allow(dead_code)]
            struct Invalid2;
            // forbids HashMap<JSTraceble, _>
            impl<K, V, S> NoTraceOnJSTraceable<Invalid2> for std::collections::HashMap<K, V, S>
            where
                K: crate::JSTraceable + std::cmp::Eq + std::hash::Hash,
                S: std::hash::BuildHasher,
            {
            }

            #[allow(dead_code)]
            struct Invalid3;
            // forbids HashMap<_, JSTraceble>
            impl<K, V, S> NoTraceOnJSTraceable<Invalid3> for std::collections::HashMap<K, V, S>
            where
                K: std::cmp::Eq + std::hash::Hash,
                V: crate::JSTraceable,
                S: std::hash::BuildHasher,
            {
            }

            #[allow(dead_code)]
            struct Invalid4;
            // forbids BTreeMap<_, JSTraceble>
            impl<K, V> NoTraceOnJSTraceable<Invalid4> for std::collections::BTreeMap<K, V> where
                K: crate::JSTraceable + std::cmp::Eq + std::hash::Hash
            {
            }

            #[allow(dead_code)]
            struct Invalid5;
            // forbids BTreeMap<_, JSTraceble>
            impl<K, V> NoTraceOnJSTraceable<Invalid5> for std::collections::BTreeMap<K, V>
            where
                K: std::cmp::Eq + std::hash::Hash,
                V: crate::JSTraceable,
            {
            }

            // If there is only one specialized trait impl, type inference with
            // `_` can be resolved and this can compile. Fails to compile if
            // ty implements `NoTraceOnJSTraceable<InvalidX>`.
            let _ = <#ty as NoTraceOnJSTraceable<_>>::some_item;
        };
    )
}

fn js_traceable_derive(s: synstructure::Structure) -> proc_macro2::TokenStream {
    let mut asserts = quote!();
    let match_body = s.each(|binding| {
        for attr in binding.ast().attrs.iter() {
            if attr.path().is_ident("no_trace") {
                // If no reason argument is provided to `no_trace` (ie `#[no_trace="This types does not need..."]`),
                // assert that the type in this bound field does not implement traceable.
                if !matches!(attr.meta, syn::Meta::NameValue(_)) {
                    asserts.extend(assert_not_impl_traceable(&binding.ast().ty));
                }
                return None;
            } else if attr.path().is_ident("custom_trace") {
                return Some(quote!(<dyn crate::CustomTraceable>::trace(#binding, tracer);));
            }
        }
        Some(quote!(#binding.trace(tracer);))
    });

    let ast = s.ast();
    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
    let mut where_clause = where_clause.unwrap_or(&parse_quote!(where)).clone();
    for param in ast.generics.type_params() {
        let ident = &param.ident;
        where_clause
            .predicates
            .push(parse_quote!(#ident: crate::JSTraceable))
    }

    let tokens = quote! {
        #asserts

        #[allow(unsafe_code)]
        unsafe impl #impl_generics crate::JSTraceable for #name #ty_generics #where_clause {
            #[inline]
            #[allow(unused_variables, unused_imports)]
            unsafe fn trace(&self, tracer: *mut js::jsapi::JSTracer) {
                use crate::JSTraceable;
                match *self {
                    #match_body
                }
            }
        }
    };

    tokens
}