stdweb_derive/
lib.rs

1#![recursion_limit="512"]
2
3extern crate proc_macro;
4extern crate syn;
5#[macro_use]
6extern crate quote;
7extern crate proc_macro2;
8
9use proc_macro::TokenStream;
10use syn::DeriveInput;
11use quote::ToTokens;
12
13fn get_meta_items( attr: &syn::Attribute ) -> Option< Vec< syn::NestedMeta > > {
14    if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "reference" {
15        match attr.parse_meta() {
16            Ok( syn::Meta::List( meta ) ) => Some( meta.nested.into_iter().collect() ),
17            _ => {
18                panic!( "Unrecognized meta item type!" );
19            }
20        }
21    } else {
22        None
23    }
24}
25
26/// A derive macro for defining custom reference types.
27///
28/// For example:
29///
30/// ```rust
31/// #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
32/// #[reference(instance_of = "Error")]
33/// pub struct Error( Reference );
34///
35/// #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
36/// #[reference(instance_of = "TypeError")]
37/// #[reference(subclass_of(Error))]
38/// pub struct TypeError( Reference );
39/// ```
40///
41/// And then you can do:
42///
43/// ```rust
44/// // You can use `try_into` to cast a `Value` to your type.
45/// let error: TypeError = js!( return new TypeError(); ).try_into().unwrap();
46///
47/// // You can also pass your type freely into the `js!` macro:
48/// js!( console.log( @{error} ); );
49/// ```
50#[proc_macro_derive(ReferenceType, attributes(reference))]
51pub fn derive_reference_type( input: TokenStream ) -> TokenStream {
52    let input: proc_macro2::TokenStream = input.into();
53    let input: DeriveInput = syn::parse2( input ).unwrap();
54
55    let name = input.ident;
56    let generics_params = &input.generics.params;
57
58    let mut instance_of = None;
59    let mut event = None;
60    let mut subclass_of = Vec::new();
61
62    for meta_items in input.attrs.iter().filter_map( get_meta_items ) {
63        for meta in meta_items {
64            match meta {
65                syn::NestedMeta::Meta( syn::Meta::NameValue( ref meta ) ) if meta.path.to_token_stream().to_string() == "instance_of" => {
66                    if instance_of.is_some() {
67                        panic!( "Duplicate '#[reference(instance_of)]'!" );
68                    }
69
70                    if let syn::Lit::Str( ref str ) = meta.lit {
71                        instance_of = Some( str.value() );
72                    } else {
73                        panic!( "The value of '#[reference(instance_of = ...)]' is not a string!" );
74                    }
75                },
76                syn::NestedMeta::Meta( syn::Meta::NameValue( ref meta ) ) if meta.path.to_token_stream().to_string() == "event" => {
77                    if event.is_some() {
78                        panic!( "Duplicate '#[reference(event)]'!" );
79                    }
80
81                    if let syn::Lit::Str( ref str ) = meta.lit {
82                        event = Some( str.value() );
83                    } else {
84                        panic!( "The value of '#[reference(event = ...)]' is not a string!" );
85                    }
86                },
87                syn::NestedMeta::Meta( syn::Meta::List( ref meta ) ) if meta.path.to_token_stream().to_string() == "subclass_of" => {
88                    for nested in &meta.nested {
89                        match *nested {
90                            syn::NestedMeta::Meta( ref nested ) => {
91                                match *nested {
92                                    syn::Meta::Path( ref path ) => {
93                                        if let Some( ident ) = path.get_ident() {
94                                            subclass_of.push( ident.clone() );
95                                        } else {
96                                            panic!( "The value of '#[reference(subclass_of(...))]' is invalid!" )
97                                        }
98                                    },
99                                    _ => panic!( "The value of '#[reference(subclass_of(...))]' is invalid!" )
100                                }
101                            },
102                            _ => panic!( "The value of '#[reference(subclass_of(...))]' is invalid!" )
103                        }
104                    }
105                },
106                syn::NestedMeta::Meta( ref meta ) => {
107                    panic!( "Unrecognized attribute: '#[reference({})]'", meta.path().to_token_stream().to_string() );
108                },
109                _ => panic!( "Unrecognized attribute!" )
110            }
111        }
112    }
113
114    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
115
116    let mut default_args = Vec::new();
117    match input.data {
118        syn::Data::Struct( ref data ) => {
119            match data.fields {
120                syn::Fields::Unnamed( ref fields ) => {
121                    fn invalid_structure() {
122                        panic!( "The structure can only have either (Reference) or (Reference, PhantomData)!" );
123                    }
124
125                    let fields = &fields.unnamed;
126                    match fields.len() {
127                        1 => {},
128                        2 => {
129                            default_args.push( quote! {
130                                std::default::Default::default()
131                            });
132                        },
133                        _ => invalid_structure()
134                    }
135
136                    let mut fields_iter = fields.iter();
137                    match fields_iter.next().unwrap().ty {
138                        syn::Type::Path( ref ty_path ) => {
139                            if ty_path.qself.is_some() {
140                                invalid_structure();
141                            }
142
143                            let segs: Vec< _ > = ty_path.path.segments.iter().collect();
144                            if segs.last().unwrap().ident != "Reference" || !segs.last().unwrap().arguments.is_empty() {
145                                invalid_structure();
146                            }
147
148                            match segs.len() {
149                                1 => {},
150                                2 => {
151                                    if segs.first().unwrap().ident != "stdweb" {
152                                        invalid_structure();
153                                    }
154                                },
155                                _ => invalid_structure()
156                            }
157                        },
158                        _ => invalid_structure()
159                    }
160
161                    match fields_iter.next().map( |field| &field.ty ) {
162                        Some( &syn::Type::Path( ref ty_path ) ) => {
163                            if ty_path.qself.is_some() {
164                                invalid_structure();
165                            }
166
167                            let segs: Vec< _ > = ty_path.path.segments.iter().collect();
168                            if segs.last().unwrap().ident != "PhantomData" {
169                                invalid_structure();
170                            }
171                        },
172                        Some( _ ) => invalid_structure(),
173                        None => {}
174                    }
175                },
176                _ => panic!( "Only tuple structures are supported!" )
177            }
178        },
179        _ => panic!( "Only tuple structures are supported!" )
180    }
181
182    let default_args = quote! { #(#default_args),* };
183
184    let mut instance_of_code = Vec::new();
185    if let Some( js_name ) = instance_of {
186        let code = format!( "o instanceof {}", js_name );
187        instance_of_code.push( code );
188    }
189
190    if let Some( ref event_name ) = event {
191        let code = format!( "o.type === \"{}\"", event_name );
192        instance_of_code.push( code );
193    }
194
195    let instance_of_impl = if !instance_of_code.is_empty() {
196        let mut code = String::new();
197        code.push_str( "var o = Module.STDWEB_PRIVATE.acquire_js_reference( $0 );" );
198        code.push_str( "return (" );
199        code.push_str( &instance_of_code.join( " && " ) );
200        code.push_str( ");" );
201
202        quote! {
203            impl #impl_generics ::stdweb::InstanceOf for #name #ty_generics #where_clause {
204                #[inline]
205                fn instance_of( reference: &::stdweb::Reference ) -> bool {
206                    __js_raw_asm_bool!(
207                        #code,
208                        reference.as_raw()
209                    )
210                }
211            }
212        }
213    } else {
214        quote! {}
215    };
216
217    let concrete_event_impl = if let Some( event_name ) = event {
218        quote! {
219            impl #impl_generics ::stdweb::web::event::ConcreteEvent for #name #ty_generics {
220                const EVENT_TYPE: &'static str = #event_name;
221            }
222        }
223    } else {
224        quote! {}
225    };
226
227    let subclass_of_impl: Vec< _ > = subclass_of.into_iter().map( |target| {
228        let target: syn::Ident = target.into();
229        quote! {
230            impl #impl_generics From< #name #ty_generics > for #target #where_clause {
231                #[inline]
232                fn from( value: #name #ty_generics ) -> Self {
233                    let reference: ::stdweb::Reference = value.into();
234                    unsafe {
235                        <#target as ::stdweb::ReferenceType>::from_reference_unchecked( reference )
236                    }
237                }
238            }
239
240            impl #impl_generics ::stdweb::unstable::TryFrom< #target > for #name #ty_generics #where_clause {
241                type Error = ::stdweb::private::ConversionError;
242
243                #[inline]
244                fn try_from( value: #target ) -> Result< Self, Self::Error > {
245                    use ::stdweb::unstable::TryInto;
246                    let reference: ::stdweb::Reference = value.into();
247                    reference.try_into()
248                }
249            }
250        }
251    }).collect();
252
253    let expanded = quote! {
254        #(#subclass_of_impl)*
255        #instance_of_impl
256        #concrete_event_impl
257
258        impl #impl_generics AsRef< ::stdweb::Reference > for #name #ty_generics #where_clause {
259            #[inline]
260            fn as_ref( &self ) -> &::stdweb::Reference {
261                &self.0
262            }
263        }
264
265        impl #impl_generics ::stdweb::ReferenceType for #name #ty_generics #where_clause {
266            #[inline]
267            unsafe fn from_reference_unchecked( reference: ::stdweb::Reference ) -> Self {
268                #name( reference, #default_args )
269            }
270        }
271
272        impl #impl_generics From< #name #ty_generics > for ::stdweb::Reference #where_clause {
273            #[inline]
274            fn from( value: #name #ty_generics ) -> Self {
275                value.0
276            }
277        }
278
279        impl #impl_generics ::stdweb::unstable::TryFrom< #name #ty_generics > for ::stdweb::Reference #where_clause {
280            type Error = ::stdweb::unstable::Void;
281
282            #[inline]
283            fn try_from( value: #name #ty_generics ) -> Result< Self, Self::Error > {
284                Ok( value.0 )
285            }
286        }
287
288        impl #impl_generics ::stdweb::unstable::TryFrom< ::stdweb::Reference > for #name #ty_generics #where_clause {
289            type Error = ::stdweb::private::ConversionError;
290
291            #[inline]
292            fn try_from( reference: ::stdweb::Reference ) -> Result< Self, Self::Error > {
293                reference.downcast()
294                    .ok_or_else( || ::stdweb::private::ConversionError::Custom( "reference is of a different type".into() ) )
295            }
296        }
297
298        impl< '_r, #generics_params > ::stdweb::unstable::TryFrom< &'_r ::stdweb::Reference > for #name #ty_generics #where_clause {
299            type Error = ::stdweb::private::ConversionError;
300
301            #[inline]
302            fn try_from( reference: &::stdweb::Reference ) -> Result< Self, Self::Error > {
303                use ::stdweb::unstable::TryInto;
304                reference.clone().try_into()
305            }
306        }
307
308        impl #impl_generics ::stdweb::unstable::TryFrom< ::stdweb::Value > for #name #ty_generics #where_clause {
309            type Error = ::stdweb::private::ConversionError;
310
311            #[inline]
312            fn try_from( value: ::stdweb::Value ) -> Result< Self, Self::Error > {
313                use ::stdweb::unstable::TryInto;
314                let reference: ::stdweb::Reference = value.try_into()?;
315                reference.downcast()
316                    .ok_or_else( || ::stdweb::private::ConversionError::Custom( "reference is of a different type".into() ) )
317            }
318        }
319
320        impl< '_r, #generics_params > ::stdweb::unstable::TryFrom< &'_r ::stdweb::Value > for #name #ty_generics #where_clause {
321            type Error = ::stdweb::private::ConversionError;
322
323            #[inline]
324            fn try_from( value: &::stdweb::Value ) -> Result< Self, Self::Error > {
325                use ::stdweb::unstable::TryInto;
326                let reference: &::stdweb::Reference =
327                    value.as_reference()
328                    .ok_or_else( || ::stdweb::private::ConversionError::Custom( "not a reference".into() ) )?;
329
330                reference.try_into()
331            }
332        }
333
334        impl #impl_generics ::stdweb::private::JsSerialize for #name #ty_generics #where_clause {
335            #[doc(hidden)]
336            #[inline]
337            fn _into_js< 'a >( &'a self ) -> ::stdweb::private::SerializedValue< 'a > {
338                self.0._into_js()
339            }
340        }
341
342        impl #impl_generics ::stdweb::private::JsSerializeOwned for #name #ty_generics #where_clause {
343            #[inline]
344            fn into_js_owned< '_a >( value: &'_a mut Option< Self > ) -> ::stdweb::private::SerializedValue< '_a > {
345                ::stdweb::private::JsSerialize::_into_js( value.as_ref().unwrap() )
346            }
347        }
348
349        impl< '_r, #generics_params > ::stdweb::private::JsSerializeOwned for &'_r #name #ty_generics #where_clause {
350            #[inline]
351            fn into_js_owned< '_a >( value: &'_a mut Option< Self > ) -> ::stdweb::private::SerializedValue< '_a > {
352                ::stdweb::private::JsSerialize::_into_js( value.unwrap() )
353            }
354        }
355    };
356
357    expanded.into()
358}