intercom_common/model/
comclass.rs

1use super::macros::*;
2use super::*;
3use crate::prelude::*;
4
5use crate::guid::GUID;
6use syn::{Generics, Path, Visibility};
7
8intercom_attribute!(
9    ComClassAttr<ComClassAttrParam, Path> {
10        clsid : StrOption,
11    }
12);
13
14/// Details of a struct marked with `#[com_class]` attribute.
15#[derive(Debug, PartialEq, Eq)]
16pub struct ComClass
17{
18    pub name: Ident,
19    pub clsid: Option<GUID>,
20    pub visibility: Visibility,
21    pub interfaces: Vec<Path>,
22    pub generics: Generics,
23}
24
25impl ComClass
26{
27    /// Creates ComClass from AST elements.
28    pub fn parse(
29        crate_name: &str,
30        attr_params: TokenStream,
31        item: TokenStream,
32    ) -> ParseResult<ComClass>
33    {
34        // Parse the inputs.
35        let item: ::syn::ItemStruct = ::syn::parse2(item)
36            .map_err(|_| ParseError::ComClass("<Unknown>".into(), "Item syntax error".into()))?;
37
38        let attr: ComClassAttr = ::syn::parse2(attr_params).map_err(|e| {
39            ParseError::ComClass(
40                item.ident.to_string(),
41                format!("Attribute syntax error: {}", e),
42            )
43        })?;
44
45        // First attribute parameter is the CLSID. Parse it.
46        let clsid_attr = attr
47            .clsid()
48            .map_err(|msg| ParseError::ComClass(item.ident.to_string(), msg))?;
49        let clsid = match clsid_attr {
50            None => Some(crate::utils::generate_clsid(
51                crate_name,
52                &item.ident.to_string(),
53            )),
54            Some(StrOption::Str(clsid)) => Some(GUID::parse(&clsid.value()).map_err(|_| {
55                ParseError::ComClass(item.ident.to_string(), "Bad CLSID format".into())
56            })?),
57            Some(StrOption::None) => None,
58        };
59
60        // Remaining parameters are coclasses.
61        let name = item.ident.clone();
62        let interfaces = attr
63            .args()
64            .into_iter()
65            .map(|itf| match itf.get_ident() {
66                Some(ident) if ident == "Self" => parse_quote!(#name),
67                _ => itf.clone(),
68            })
69            .collect();
70
71        Ok(ComClass {
72            visibility: item.vis.clone(),
73            generics: item.generics,
74            name,
75            clsid,
76            interfaces,
77        })
78    }
79
80    /// Figure out whether the path refers to the current struct.
81    pub fn is_self_path(&self, path: &Path) -> bool
82    {
83        // The self type must be specified as ident. We can't resolve
84        // the current path of the com_class so we can't figure out whether
85        // `foo::bar::ThisStruct` _really_ is `ThisStruct`.
86        if let Some(ident) = path.get_ident() {
87            // The self type can be specified either with the type name or
88            // by using 'Self' as type name.
89            if ident == &self.name || ident == "Self" {
90                return true;
91            }
92        }
93
94        false
95    }
96}
97
98#[cfg(test)]
99mod test
100{
101    use super::*;
102
103    #[test]
104    fn parse_com_class()
105    {
106        let cls = ComClass::parse(
107            "not used",
108            quote!(clsid = "12345678-1234-1234-1234-567890ABCDEF", Foo, Bar),
109            quote!(
110                struct S;
111            ),
112        )
113        .expect("com_class attribute parsing failed");
114
115        assert_eq!(cls.name, "S");
116        assert_eq!(
117            cls.clsid,
118            Some(GUID::parse("12345678-1234-1234-1234-567890ABCDEF").unwrap())
119        );
120        assert_eq!(cls.interfaces.len(), 2);
121        assert_eq!(cls.interfaces[0], parse_quote!(Foo));
122        assert_eq!(cls.interfaces[1], parse_quote!(Bar));
123    }
124
125    #[test]
126    fn parse_com_class_with_auto_guid()
127    {
128        // This test derives the GUID from the library name.
129        //
130        // What the final GUID is isn't important, what _is_ important however
131        // is that the final GUID will not change ever as long as the library
132        // name stays the same.
133        let cls = ComClass::parse(
134            "not used",
135            quote!(MyStruct, IThings, IStuff),
136            quote!(
137                struct MyStruct
138                {
139                    a: u32,
140                }
141            ),
142        )
143        .expect("com_class attribute parsing failed");
144
145        assert_eq!(cls.name, "MyStruct");
146        assert_eq!(
147            cls.clsid,
148            Some(GUID::parse("28F57CBA-6AF4-3D3F-7C55-1CF1394D5C7A").unwrap())
149        );
150        assert_eq!(cls.interfaces.len(), 3);
151        assert_eq!(cls.interfaces[0], parse_quote!(MyStruct));
152        assert_eq!(cls.interfaces[1], parse_quote!(IThings));
153        assert_eq!(cls.interfaces[2], parse_quote!(IStuff));
154    }
155
156    #[test]
157    fn parse_com_class_with_no_data()
158    {
159        let cls = ComClass::parse(
160            "not used",
161            quote!(clsid = None),
162            quote!(
163                struct EmptyType;
164            ),
165        )
166        .expect("com_class attribute parsing failed");
167
168        assert_eq!(cls.name, "EmptyType");
169        assert_eq!(cls.clsid, None);
170        assert_eq!(cls.interfaces.len(), 0);
171    }
172
173    #[test]
174    fn parse_com_class_with_no_guid_with_interface()
175    {
176        let cls = ComClass::parse(
177            "not used",
178            quote!(clsid = None, ITestInterface),
179            quote!(
180                struct EmptyType;
181            ),
182        )
183        .expect("com_class attribute parsing failed");
184
185        assert_eq!(cls.name, "EmptyType");
186        assert_eq!(cls.clsid, None);
187        assert_eq!(cls.interfaces.len(), 1);
188    }
189}