diplomat_core/ast/
modules.rs

1use std::collections::{BTreeMap, HashSet};
2use std::fmt::Write as _;
3
4use quote::ToTokens;
5use serde::Serialize;
6use syn::{ImplItem, Item, ItemMod, UseTree, Visibility};
7
8use super::{
9    AttrInheritContext, Attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability,
10    OpaqueStruct, Path, PathType, RustLink, Struct,
11};
12use crate::environment::*;
13
14/// Custom Diplomat attribute that can be placed on a struct definition.
15#[derive(Debug)]
16enum DiplomatStructAttribute {
17    /// The `#[diplomat::out]` attribute, used for non-opaque structs that
18    /// contain an owned opaque in the form of a `Box`.
19    Out,
20    /// The `#[diplomat::opaque]` attribute, used for marking a struct as opaque.
21    /// Note that opaque structs can be borrowed in return types, but cannot
22    /// be passed into a function behind a mutable reference.
23    Opaque,
24    /// The `#[diplomat::opaque_mut]` attribute, used for marking a struct as
25    /// opaque and mutable.
26    /// Note that mutable opaque structs can never be borrowed in return types
27    /// (even immutably!), but can be passed into a function behind a mutable
28    /// reference.
29    OpaqueMut,
30}
31
32impl DiplomatStructAttribute {
33    /// Parses a [`DiplomatStructAttribute`] from an array of [`syn::Attribute`]s.
34    /// If more than one kind is found, an error is returned containing all the
35    /// ones encountered, since all the current attributes are disjoint.
36    fn parse(attrs: &[syn::Attribute]) -> Result<Option<Self>, Vec<Self>> {
37        let mut buf = String::with_capacity(32);
38        let mut res = Ok(None);
39        for attr in attrs {
40            buf.clear();
41            write!(&mut buf, "{}", attr.path().to_token_stream()).unwrap();
42            let parsed = match buf.as_str() {
43                "diplomat :: out" => Some(Self::Out),
44                "diplomat :: opaque" => Some(Self::Opaque),
45                "diplomat :: opaque_mut" => Some(Self::OpaqueMut),
46                _ => None,
47            };
48
49            if let Some(parsed) = parsed {
50                match res {
51                    Ok(None) => res = Ok(Some(parsed)),
52                    Ok(Some(first)) => res = Err(vec![first, parsed]),
53                    Err(ref mut errors) => errors.push(parsed),
54                }
55            }
56        }
57
58        res
59    }
60}
61
62#[derive(Clone, Serialize, Debug)]
63#[non_exhaustive]
64pub struct Module {
65    pub name: Ident,
66    pub imports: Vec<(Path, Ident)>,
67    pub declared_types: BTreeMap<Ident, CustomType>,
68    pub sub_modules: Vec<Module>,
69    pub attrs: Attrs,
70}
71
72impl Module {
73    pub fn all_rust_links(&self) -> HashSet<&RustLink> {
74        let mut rust_links = self
75            .declared_types
76            .values()
77            .flat_map(|t| t.all_rust_links())
78            .collect::<HashSet<_>>();
79
80        self.sub_modules.iter().for_each(|m| {
81            rust_links.extend(m.all_rust_links().iter());
82        });
83        rust_links
84    }
85
86    pub fn insert_all_types(&self, in_path: Path, out: &mut Env) {
87        let mut mod_symbols = ModuleEnv::new(self.attrs.clone());
88
89        self.imports.iter().for_each(|(path, name)| {
90            mod_symbols.insert(name.clone(), ModSymbol::Alias(path.clone()));
91        });
92
93        self.declared_types.iter().for_each(|(k, v)| {
94            if mod_symbols
95                .insert(k.clone(), ModSymbol::CustomType(v.clone()))
96                .is_some()
97            {
98                panic!("Two types were declared with the same name, this needs to be implemented");
99            }
100        });
101
102        let path_to_self = in_path.sub_path(self.name.clone());
103        self.sub_modules.iter().for_each(|m| {
104            m.insert_all_types(path_to_self.clone(), out);
105            mod_symbols.insert(m.name.clone(), ModSymbol::SubModule(m.name.clone()));
106        });
107
108        out.insert(path_to_self, mod_symbols);
109    }
110
111    pub fn from_syn(input: &ItemMod, force_analyze: bool) -> Module {
112        let mut custom_types_by_name = BTreeMap::new();
113        let mut sub_modules = Vec::new();
114        let mut imports = Vec::new();
115
116        let analyze_types = force_analyze
117            || input
118                .attrs
119                .iter()
120                .any(|a| a.path().to_token_stream().to_string() == "diplomat :: bridge");
121
122        let mod_attrs: Attrs = (&*input.attrs).into();
123
124        let impl_parent_attrs: Attrs =
125            mod_attrs.attrs_for_inheritance(AttrInheritContext::MethodOrImplFromModule);
126        let type_parent_attrs: Attrs = mod_attrs.attrs_for_inheritance(AttrInheritContext::Type);
127
128        input
129            .content
130            .as_ref()
131            .map(|t| &t.1[..])
132            .unwrap_or_default()
133            .iter()
134            .for_each(|a| match a {
135                Item::Use(u) => {
136                    if analyze_types {
137                        extract_imports(&Path::empty(), &u.tree, &mut imports);
138                    }
139                }
140                Item::Struct(strct) => {
141                    if analyze_types {
142                        let custom_type = match DiplomatStructAttribute::parse(&strct.attrs[..]) {
143                            Ok(None) => CustomType::Struct(Struct::new(strct, false, &type_parent_attrs)),
144                            Ok(Some(DiplomatStructAttribute::Out)) => {
145                                CustomType::Struct(Struct::new(strct, true, &type_parent_attrs))
146                            }
147                            Ok(Some(DiplomatStructAttribute::Opaque)) => {
148                                CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Immutable, &type_parent_attrs))
149                            }
150                            Ok(Some(DiplomatStructAttribute::OpaqueMut)) => {
151                                CustomType::Opaque(OpaqueStruct::new(strct, Mutability::Mutable, &type_parent_attrs))
152                            }
153                            Err(errors) => {
154                                panic!("Multiple conflicting Diplomat struct attributes, there can be at most one: {errors:?}");
155                            }
156                        };
157
158                        custom_types_by_name.insert(Ident::from(&strct.ident), custom_type);
159                    }
160                }
161
162                Item::Enum(enm) => {
163                    if analyze_types {
164                        let ident = (&enm.ident).into();
165                        let enm = Enum::new(enm, &type_parent_attrs);
166                        custom_types_by_name
167                            .insert(ident, CustomType::Enum(enm));
168                    }
169                }
170
171                Item::Impl(imp) => {
172                    if analyze_types {
173                        assert!(imp.trait_.is_none());
174
175                        let self_path = match imp.self_ty.as_ref() {
176                            syn::Type::Path(s) => PathType::from(s),
177                            _ => panic!("Self type not found"),
178                        };
179                        let mut impl_attrs = impl_parent_attrs.clone();
180                        impl_attrs.add_attrs(&imp.attrs);
181                        let method_parent_attrs = impl_attrs.attrs_for_inheritance(AttrInheritContext::MethodFromImpl);
182                        let mut new_methods = imp
183                            .items
184                            .iter()
185                            .filter_map(|i| match i {
186                                ImplItem::Fn(m) => Some(m),
187                                _ => None,
188                            })
189                            .filter(|m| matches!(m.vis, Visibility::Public(_)))
190                            .map(|m| Method::from_syn(m, self_path.clone(), Some(&imp.generics), &method_parent_attrs))
191                            .collect();
192
193                        let self_ident = self_path.path.elements.last().unwrap();
194
195                        match custom_types_by_name.get_mut(self_ident).unwrap() {
196                            CustomType::Struct(strct) => {
197                                strct.methods.append(&mut new_methods);
198                            }
199                            CustomType::Opaque(strct) => {
200                                strct.methods.append(&mut new_methods);
201                            }
202                            CustomType::Enum(enm) => {
203                                enm.methods.append(&mut new_methods);
204                            }
205                        }
206                    }
207                }
208                Item::Mod(item_mod) => {
209                    sub_modules.push(Module::from_syn(item_mod, false));
210                }
211                _ => {}
212            });
213
214        Module {
215            name: (&input.ident).into(),
216            imports,
217            declared_types: custom_types_by_name,
218            sub_modules,
219            attrs: mod_attrs,
220        }
221    }
222}
223
224fn extract_imports(base_path: &Path, use_tree: &UseTree, out: &mut Vec<(Path, Ident)>) {
225    match use_tree {
226        UseTree::Name(name) => out.push((
227            base_path.sub_path((&name.ident).into()),
228            (&name.ident).into(),
229        )),
230        UseTree::Path(path) => {
231            extract_imports(&base_path.sub_path((&path.ident).into()), &path.tree, out)
232        }
233        UseTree::Glob(_) => todo!("Glob imports are not yet supported"),
234        UseTree::Group(group) => {
235            group
236                .items
237                .iter()
238                .for_each(|i| extract_imports(base_path, i, out));
239        }
240        UseTree::Rename(rename) => out.push((
241            base_path.sub_path((&rename.ident).into()),
242            (&rename.rename).into(),
243        )),
244    }
245}
246
247#[derive(Serialize, Clone, Debug)]
248#[non_exhaustive]
249pub struct File {
250    pub modules: BTreeMap<String, Module>,
251}
252
253impl File {
254    /// Fuses all declared types into a single environment `HashMap`.
255    pub fn all_types(&self) -> Env {
256        let mut out = Env::default();
257        let mut top_symbols = ModuleEnv::new(Default::default());
258
259        self.modules.values().for_each(|m| {
260            m.insert_all_types(Path::empty(), &mut out);
261            top_symbols.insert(m.name.clone(), ModSymbol::SubModule(m.name.clone()));
262        });
263
264        out.insert(Path::empty(), top_symbols);
265
266        out
267    }
268
269    pub fn all_rust_links(&self) -> HashSet<&RustLink> {
270        self.modules
271            .values()
272            .flat_map(|m| m.all_rust_links().into_iter())
273            .collect()
274    }
275}
276
277impl From<&syn::File> for File {
278    /// Get all custom types across all modules defined in a given file.
279    fn from(file: &syn::File) -> File {
280        let mut out = BTreeMap::new();
281        file.items.iter().for_each(|i| {
282            if let Item::Mod(item_mod) = i {
283                out.insert(
284                    item_mod.ident.to_string(),
285                    Module::from_syn(item_mod, false),
286                );
287            }
288        });
289
290        File { modules: out }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use insta::{self, Settings};
297
298    use syn;
299
300    use crate::ast::{File, Module};
301
302    #[test]
303    fn simple_mod() {
304        let mut settings = Settings::new();
305        settings.set_sort_maps(true);
306
307        settings.bind(|| {
308            insta::assert_yaml_snapshot!(Module::from_syn(
309                &syn::parse_quote! {
310                    mod ffi {
311                        struct NonOpaqueStruct {
312                            a: i32,
313                            b: Box<NonOpaqueStruct>
314                        }
315
316                        impl NonOpaqueStruct {
317                            pub fn new(x: i32) -> NonOpaqueStruct {
318                                unimplemented!();
319                            }
320
321                            pub fn set_a(&mut self, new_a: i32) {
322                                self.a = new_a;
323                            }
324                        }
325
326                        #[diplomat::opaque]
327                        struct OpaqueStruct {
328                            a: SomeExternalType
329                        }
330
331                        impl OpaqueStruct {
332                            pub fn new() -> Box<OpaqueStruct> {
333                                unimplemented!();
334                            }
335
336                            pub fn get_string(&self) -> String {
337                                unimplemented!()
338                            }
339                        }
340                    }
341                },
342                true
343            ));
344        });
345    }
346
347    #[test]
348    fn method_visibility() {
349        let mut settings = Settings::new();
350        settings.set_sort_maps(true);
351
352        settings.bind(|| {
353            insta::assert_yaml_snapshot!(Module::from_syn(
354                &syn::parse_quote! {
355                    #[diplomat::bridge]
356                    mod ffi {
357                        struct Foo {}
358
359                        impl Foo {
360                            pub fn pub_fn() {
361                                unimplemented!()
362                            }
363                            pub(crate) fn pub_crate_fn() {
364                                unimplemented!()
365                            }
366                            pub(super) fn pub_super_fn() {
367                                unimplemented!()
368                            }
369                            fn priv_fn() {
370                                unimplemented!()
371                            }
372                        }
373                    }
374                },
375                true
376            ));
377        });
378    }
379
380    #[test]
381    fn import_in_non_diplomat_not_analyzed() {
382        let mut settings = Settings::new();
383        settings.set_sort_maps(true);
384
385        settings.bind(|| {
386            insta::assert_yaml_snapshot!(File::from(&syn::parse_quote! {
387                #[diplomat::bridge]
388                mod ffi {
389                    struct Foo {}
390                }
391
392                mod other {
393                    use something::*;
394                }
395            }));
396        });
397    }
398}