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 http://mozilla.org/MPL/2.0/. */
4 
5 use crate::fnsig::FnSignature;
6 use proc_macro::TokenStream;
7 use proc_macro2::{Ident, Span};
8 use quote::ToTokens;
9 
10 use super::attributes::{
11     ExportFnArgs, ExportImplArgs, ExportStructArgs, ExportTraitArgs, ExportedImplFnArgs,
12     ExportedImplFnAttributes,
13 };
14 use crate::util::extract_docstring;
15 use uniffi_meta::UniffiTraitDiscriminants;
16 
17 pub(super) enum ExportItem {
18     Function {
19         sig: FnSignature,
20         args: ExportFnArgs,
21     },
22     Impl {
23         self_ident: Ident,
24         items: Vec<ImplItem>,
25         args: ExportImplArgs,
26     },
27     Trait {
28         self_ident: Ident,
29         items: Vec<ImplItem>,
30         with_foreign: bool,
31         callback_interface_only: bool,
32         docstring: String,
33         args: ExportTraitArgs,
34     },
35     Struct {
36         self_ident: Ident,
37         uniffi_traits: Vec<UniffiTraitDiscriminants>,
38     },
39 }
40 
41 impl ExportItem {
new(item: syn::Item, attr_args: TokenStream) -> syn::Result<Self>42     pub fn new(item: syn::Item, attr_args: TokenStream) -> syn::Result<Self> {
43         match item {
44             syn::Item::Fn(item) => {
45                 let args: ExportFnArgs = syn::parse(attr_args)?;
46                 let docstring = extract_docstring(&item.attrs)?;
47                 let sig = FnSignature::new_function(item.sig, args.clone(), docstring)?;
48                 Ok(Self::Function { sig, args })
49             }
50             syn::Item::Impl(item) => Self::from_impl(item, attr_args),
51             syn::Item::Trait(item) => Self::from_trait(item, attr_args),
52             syn::Item::Struct(item) => Self::from_struct(item, attr_args),
53             // FIXME: Support const / static?
54             _ => Err(syn::Error::new(
55                 Span::call_site(),
56                 "unsupported item: only functions and impl \
57                  blocks may be annotated with this attribute",
58             )),
59         }
60     }
61 
from_impl(item: syn::ItemImpl, attr_args: TokenStream) -> syn::Result<Self>62     pub fn from_impl(item: syn::ItemImpl, attr_args: TokenStream) -> syn::Result<Self> {
63         let args: ExportImplArgs = syn::parse(attr_args)?;
64         if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
65             return Err(syn::Error::new_spanned(
66                 &item.generics,
67                 "generic impls are not currently supported by uniffi::export",
68             ));
69         }
70 
71         let type_path = type_as_type_path(&item.self_ty)?;
72 
73         if type_path.qself.is_some() {
74             return Err(syn::Error::new_spanned(
75                 type_path,
76                 "qualified self types are not currently supported by uniffi::export",
77             ));
78         }
79 
80         let self_ident = match type_path.path.get_ident() {
81             Some(id) => id,
82             None => {
83                 return Err(syn::Error::new_spanned(
84                     type_path,
85                     "qualified paths in self-types are not currently supported by uniffi::export",
86                 ));
87             }
88         };
89 
90         let items = item
91             .items
92             .into_iter()
93             .map(|item| {
94                 let impl_fn = match item {
95                     syn::ImplItem::Fn(m) => m,
96                     _ => {
97                         return Err(syn::Error::new_spanned(
98                             item,
99                             "only fn's are supported in impl blocks annotated with uniffi::export",
100                         ));
101                     }
102                 };
103 
104                 let docstring = extract_docstring(&impl_fn.attrs)?;
105                 let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?;
106                 let item = if attrs.constructor {
107                     ImplItem::Constructor(FnSignature::new_constructor(
108                         self_ident.clone(),
109                         impl_fn.sig,
110                         attrs.args,
111                         docstring,
112                     )?)
113                 } else {
114                     ImplItem::Method(FnSignature::new_method(
115                         self_ident.clone(),
116                         impl_fn.sig,
117                         attrs.args,
118                         docstring,
119                     )?)
120                 };
121 
122                 Ok(item)
123             })
124             .collect::<syn::Result<_>>()?;
125 
126         Ok(Self::Impl {
127             items,
128             self_ident: self_ident.to_owned(),
129             args,
130         })
131     }
132 
from_trait(item: syn::ItemTrait, attr_args: TokenStream) -> syn::Result<Self>133     fn from_trait(item: syn::ItemTrait, attr_args: TokenStream) -> syn::Result<Self> {
134         let args: ExportTraitArgs = syn::parse(attr_args)?;
135         let with_foreign = args.callback_interface.is_some() || args.with_foreign.is_some();
136         let callback_interface_only = args.callback_interface.is_some();
137 
138         if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
139             return Err(syn::Error::new_spanned(
140                 &item.generics,
141                 "generic impls are not currently supported by uniffi::export",
142             ));
143         }
144 
145         let self_ident = item.ident.to_owned();
146         let docstring = extract_docstring(&item.attrs)?;
147         let items = item
148             .items
149             .into_iter()
150             .enumerate()
151             .map(|(i, item)| {
152                 let tim = match item {
153                     syn::TraitItem::Fn(tim) => tim,
154                     _ => {
155                         return Err(syn::Error::new_spanned(
156                             item,
157                             "only fn's are supported in traits annotated with uniffi::export",
158                         ));
159                     }
160                 };
161 
162                 let docstring = extract_docstring(&tim.attrs)?;
163                 let attrs = ExportedImplFnAttributes::new(&tim.attrs)?;
164                 let item = if attrs.constructor {
165                     return Err(syn::Error::new_spanned(
166                         tim,
167                         "exported traits can not have constructors",
168                     ));
169                 } else {
170                     ImplItem::Method(FnSignature::new_trait_method(
171                         self_ident.clone(),
172                         tim.sig,
173                         ExportedImplFnArgs::default(),
174                         i as u32,
175                         docstring,
176                     )?)
177                 };
178 
179                 Ok(item)
180             })
181             .collect::<syn::Result<_>>()?;
182 
183         Ok(Self::Trait {
184             items,
185             self_ident,
186             with_foreign,
187             callback_interface_only,
188             docstring,
189             args,
190         })
191     }
192 
from_struct(item: syn::ItemStruct, attr_args: TokenStream) -> syn::Result<Self>193     fn from_struct(item: syn::ItemStruct, attr_args: TokenStream) -> syn::Result<Self> {
194         let args: ExportStructArgs = syn::parse(attr_args)?;
195         let uniffi_traits: Vec<UniffiTraitDiscriminants> = args.traits.into_iter().collect();
196         if uniffi_traits.is_empty() {
197             Err(syn::Error::new(Span::call_site(),
198                 "uniffi::export on a struct must supply a builtin trait name. Did you mean `#[derive(uniffi::Object)]`?"
199             ))
200         } else {
201             Ok(Self::Struct {
202                 self_ident: item.ident,
203                 uniffi_traits,
204             })
205         }
206     }
207 }
208 
209 pub(super) enum ImplItem {
210     Constructor(FnSignature),
211     Method(FnSignature),
212 }
213 
type_as_type_path(ty: &syn::Type) -> syn::Result<&syn::TypePath>214 fn type_as_type_path(ty: &syn::Type) -> syn::Result<&syn::TypePath> {
215     match ty {
216         syn::Type::Group(g) => type_as_type_path(&g.elem),
217         syn::Type::Paren(p) => type_as_type_path(&p.elem),
218         syn::Type::Path(p) => Ok(p),
219         _ => Err(type_not_supported(ty)),
220     }
221 }
222 
type_not_supported(ty: &impl ToTokens) -> syn::Error223 fn type_not_supported(ty: &impl ToTokens) -> syn::Error {
224     syn::Error::new_spanned(
225         ty,
226         "this type is not currently supported by uniffi::export in this position",
227     )
228 }
229