| /* 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 = ¶m.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 |
| } |