intercom_common/model/
cominterface.rs

1use super::macros::*;
2use super::*;
3use crate::prelude::*;
4
5use crate::ast_converters::*;
6use crate::guid::GUID;
7use crate::idents::{self, SomeIdent};
8use crate::methodinfo::ComMethodInfo;
9use crate::quote::ToTokens;
10use crate::tyhandlers::ModelTypeSystem;
11use indexmap::IndexMap;
12use proc_macro2::Span;
13use std::iter::FromIterator;
14use syn::{Ident, LitStr, Path, TypePath, Visibility};
15
16intercom_attribute!(
17    ComInterfaceAttr< ComInterfaceAttrParam, NoParams > {
18        com_iid : LitStr,
19        raw_iid : LitStr,
20        base : Path,
21        vtable_of: Path,
22        implemented_by: Path,
23    }
24);
25
26impl ComInterfaceAttr
27{
28    pub fn iid(&self, ts: ModelTypeSystem) -> Result<Option<&LitStr>, String>
29    {
30        match ts {
31            ModelTypeSystem::Raw => self.raw_iid(),
32            ModelTypeSystem::Automation => self.com_iid(),
33        }
34    }
35}
36
37#[derive(Debug)]
38pub struct ComInterface
39{
40    pub path: Path,
41    pub ident: Ident,
42    pub visibility: Visibility,
43    pub base_interface: Option<Path>,
44    pub variants: IndexMap<ModelTypeSystem, ComInterfaceVariant>,
45    pub item_type: crate::utils::InterfaceType,
46    pub span: Span,
47    pub is_unsafe: bool,
48    pub itf_ref: TokenStream,
49    pub vtable_of: Option<Path>,
50    pub implemented_by: Option<Path>,
51}
52
53#[derive(Debug, PartialEq)]
54pub struct ComInterfaceVariant
55{
56    pub type_system: ModelTypeSystem,
57    pub iid: GUID,
58    pub methods: Vec<ComMethodInfo>,
59}
60
61impl ComInterface
62{
63    /// Creates ComInterface from AST elements.
64    pub fn from_ast(
65        crate_name: &str,
66        attr: TokenStream,
67        item: TokenStream,
68    ) -> ParseResult<ComInterface>
69    {
70        let item: syn::Item = ::syn::parse2(item).map_err(|_| {
71            ParseError::ComInterface("<Unknown>".into(), "Item syntax error".into())
72        })?;
73
74        let attr: ComInterfaceAttr = ::syn::parse2(attr).map_err(|_| {
75            ParseError::ComInterface(
76                item.get_ident().unwrap().to_string(),
77                "Attribute syntax error".into(),
78            )
79        })?;
80
81        // Get the interface details. As [com_interface] can be applied to both
82        // impls and traits this handles both of those.
83        let (path, fns, itf_type, unsafety) =
84            crate::utils::get_ident_and_fns(&item).ok_or_else(|| {
85                ParseError::ComInterface(
86                    item.get_ident().unwrap().to_string(),
87                    "Unsupported associated item".into(),
88                )
89            })?;
90
91        let ident = path.get_some_ident().ok_or_else(|| {
92            ParseError::ComInterface(
93                path.to_token_stream().to_string(),
94                "Could not resolve ident for".to_string(),
95            )
96        })?;
97
98        // The second argument is the optional base class. If there's no base
99        // class defined, use IUnknown as the default. The value of NO_BASE will
100        // construct an interface that has no base class.
101        //
102        // In practice the NO_BASE should be used ONLY for the IUnknown itself.
103        let base = attr
104            .base()
105            .map_err(|msg| ParseError::ComInterface(item.get_ident().unwrap().to_string(), msg))?;
106        let base = match base {
107            Some(b) => {
108                if b.get_ident().map(|i| i == "NO_BASE") == Some(true) {
109                    None
110                } else {
111                    Some(b.to_owned())
112                }
113            }
114            None => Some(syn::parse2(quote!(intercom::IUnknown)).unwrap()),
115        };
116
117        // Visibility for trait interfaces is the visibility of the trait.
118        //
119        // For implicit interfaces (impl Struct) the visibility is always public.
120        // These interfaces should only exist for COM types that are meant to be
121        // called from external sources as they can't be impl'd for random ComItf.
122        //
123        // Note this may conflict with visibility of the actual [com_class], but
124        // nothing we can do for this really.
125        let visibility = if let ::syn::Item::Trait(ref t) = item {
126            t.vis.clone()
127        } else {
128            parse_quote!(pub)
129        };
130
131        let variants = IndexMap::from_iter(
132            [ModelTypeSystem::Automation, ModelTypeSystem::Raw]
133                .iter()
134                .map(|&ts| {
135                    let iid_attr = attr.iid(ts).map_err(|msg| {
136                        ParseError::ComInterface(item.get_ident().unwrap().to_string(), msg)
137                    })?;
138                    let iid = match iid_attr {
139                        Some(iid) => GUID::parse(&iid.value()).map_err(|_| {
140                            ParseError::ComInterface(
141                                item.get_ident().unwrap().to_string(),
142                                "Bad IID format".into(),
143                            )
144                        })?,
145                        None => crate::utils::generate_iid(crate_name, &ident.to_string(), ts),
146                    };
147
148                    // Read the method details.
149                    //
150                    // TODO: Currently we ignore invalid methods. We should probably do
151                    //       something smarter.
152                    let methods = fns
153                        .iter()
154                        .map(|sig| ComMethodInfo::new(sig, ts))
155                        .filter_map(Result::ok)
156                        .collect::<Vec<_>>();
157
158                    Ok((
159                        ts,
160                        ComInterfaceVariant {
161                            type_system: ts,
162                            iid,
163                            methods,
164                        },
165                    ))
166                })
167                .collect::<Result<Vec<_>, _>>()?,
168        );
169
170        let itf_ref = match itf_type {
171            crate::utils::InterfaceType::Trait => quote_spanned!(ident.span() => dyn #path),
172            crate::utils::InterfaceType::Struct => quote_spanned!(ident.span() => #path),
173        };
174
175        Ok(ComInterface {
176            base_interface: base,
177            item_type: itf_type,
178            is_unsafe: unsafety.is_some(),
179            span: ident.span(),
180            vtable_of: attr
181                .vtable_of()
182                .map_err(|e| ParseError::ComInterface(ident.to_string(), e))?
183                .cloned(),
184            implemented_by: attr
185                .implemented_by()
186                .map_err(|e| ParseError::ComInterface(ident.to_string(), e))?
187                .cloned(),
188            path,
189            ident,
190            visibility,
191            variants,
192            itf_ref,
193        })
194    }
195
196    pub fn vtable(&self, ts: ModelTypeSystem) -> TypePath
197    {
198        let ts_type_tokens = ts.as_typesystem_type(self.ident.span());
199        let tt = match &self.vtable_of {
200            Some(path) => {
201                quote_spanned!(self.ident.span() => <dyn #path as intercom::attributes::ComInterfaceVariant<#ts_type_tokens>>::VTable)
202            }
203            None => {
204                let ident = idents::vtable(&self.ident, ts);
205                quote!(#ident)
206            }
207        };
208        syn::parse2(tt).unwrap()
209    }
210}
211
212#[cfg(test)]
213mod test
214{
215    use super::*;
216    use crate::tyhandlers::ModelTypeSystem::*;
217
218    #[test]
219    fn parse_com_interface()
220    {
221        let itf = ComInterface::from_ast(
222            "not used",
223            quote!(
224                com_iid = "12345678-1234-1234-1234-567890ABCDEF",
225                raw_iid = "12345678-1234-1234-1234-567890FEDCBA",
226            ),
227            quote!(
228                trait ITrait
229                {
230                    fn foo(&self);
231                    fn bar(&self);
232                }
233            ),
234        )
235        .expect("com_interface attribute parsing failed");
236
237        assert_eq!(itf.path, parse_quote!(ITrait));
238        assert_eq!(itf.visibility, Visibility::Inherited);
239        assert_eq!(
240            itf.base_interface.as_ref().unwrap(),
241            &parse_quote!(intercom::IUnknown)
242        );
243
244        let variant = &itf.variants[&Automation];
245        assert_eq!(
246            variant.iid,
247            GUID::parse("12345678-1234-1234-1234-567890ABCDEF").unwrap()
248        );
249        assert_eq!(variant.methods.len(), 2);
250        assert_eq!(variant.methods[0].name, "foo");
251        assert_eq!(variant.methods[1].name, "bar");
252
253        let variant = &itf.variants[&Raw];
254        assert_eq!(
255            variant.iid,
256            GUID::parse("12345678-1234-1234-1234-567890FEDCBA").unwrap()
257        );
258        assert_eq!(variant.methods.len(), 2);
259        assert_eq!(variant.methods[0].name, "foo");
260        assert_eq!(variant.methods[1].name, "bar");
261    }
262
263    #[test]
264    fn parse_com_interface_with_auto_guid()
265    {
266        let itf = ComInterface::from_ast(
267            "not used",
268            quote!(),
269            quote!(
270                pub trait IAutoGuid
271                {
272                    fn one(&self);
273                    fn two(&self);
274                }
275            ),
276        )
277        .expect("com_interface attribute parsing failed");
278
279        assert_eq!(itf.path, parse_quote!(IAutoGuid));
280
281        let pub_visibility: Visibility = parse_quote!(pub);
282        assert_eq!(itf.visibility, pub_visibility);
283        assert_eq!(
284            itf.base_interface.as_ref().unwrap(),
285            &parse_quote!(intercom::IUnknown)
286        );
287
288        let variant = &itf.variants[&Automation];
289        assert_eq!(
290            variant.iid,
291            GUID::parse("82B905D9-D292-3531-452F-E04722F567DD").unwrap()
292        );
293        assert_eq!(variant.methods.len(), 2);
294        assert_eq!(variant.methods[0].name, "one");
295        assert_eq!(variant.methods[1].name, "two");
296
297        let variant = &itf.variants[&Raw];
298        assert_eq!(
299            variant.iid,
300            GUID::parse("E16EEA74-C0E0-34DE-6F51-1D949883DE06").unwrap()
301        );
302        assert_eq!(variant.methods.len(), 2);
303        assert_eq!(variant.methods[0].name, "one");
304        assert_eq!(variant.methods[1].name, "two");
305    }
306
307    #[test]
308    fn parse_com_interface_with_base_interface()
309    {
310        let itf = ComInterface::from_ast(
311            "not used",
312            quote!(base = IBase),
313            quote!(
314                pub trait IAutoGuid
315                {
316                    fn one(&self);
317                    fn two(&self);
318                }
319            ),
320        )
321        .expect("com_interface attribute parsing failed");
322
323        assert_eq!(itf.path, parse_quote!(IAutoGuid));
324
325        let pub_visibility: Visibility = parse_quote!(pub);
326        assert_eq!(itf.visibility, pub_visibility);
327        assert_eq!(itf.base_interface.as_ref().unwrap(), &parse_quote!(IBase));
328
329        let variant = &itf.variants[&ModelTypeSystem::Automation];
330        assert_eq!(
331            variant.iid,
332            GUID::parse("82B905D9-D292-3531-452F-E04722F567DD").unwrap()
333        );
334        assert_eq!(variant.methods.len(), 2);
335        assert_eq!(variant.methods[0].name, "one");
336        assert_eq!(variant.methods[1].name, "two");
337
338        let variant = &itf.variants[&ModelTypeSystem::Raw];
339        assert_eq!(
340            variant.iid,
341            GUID::parse("E16EEA74-C0E0-34DE-6F51-1D949883DE06").unwrap()
342        );
343        assert_eq!(variant.methods.len(), 2);
344        assert_eq!(variant.methods[0].name, "one");
345        assert_eq!(variant.methods[1].name, "two");
346    }
347
348    #[test]
349    fn parse_com_interface_with_no_base_interface()
350    {
351        let itf = ComInterface::from_ast(
352            "not used",
353            quote!(base = NO_BASE),
354            quote!(
355                pub trait IAutoGuid
356                {
357                    fn one(&self);
358                    fn two(&self);
359                }
360            ),
361        )
362        .expect("com_interface attribute parsing failed");
363
364        assert_eq!(itf.path, parse_quote!(IAutoGuid));
365
366        let pub_visibility: Visibility = parse_quote!(pub);
367        assert_eq!(itf.visibility, pub_visibility);
368        assert_eq!(itf.base_interface, None);
369
370        let variant = &itf.variants[&Automation];
371        assert_eq!(
372            variant.iid,
373            GUID::parse("82B905D9-D292-3531-452F-E04722F567DD").unwrap()
374        );
375        assert_eq!(variant.methods.len(), 2);
376        assert_eq!(variant.methods[0].name, "one");
377        assert_eq!(variant.methods[1].name, "two");
378
379        let variant = &itf.variants[&Raw];
380        assert_eq!(
381            variant.iid,
382            GUID::parse("E16EEA74-C0E0-34DE-6F51-1D949883DE06").unwrap()
383        );
384        assert_eq!(variant.methods.len(), 2);
385        assert_eq!(variant.methods[0].name, "one");
386        assert_eq!(variant.methods[1].name, "two");
387    }
388}