intercom_common/
tyhandlers.rs

1use crate::prelude::*;
2use proc_macro2::Span;
3use std::rc::Rc;
4use syn::*;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Direction
8{
9    In,
10    Out,
11    Retval,
12}
13
14#[derive(PartialEq, Eq, Debug, Hash)]
15pub struct ModelTypeSystemConfig
16{
17    pub effective_system: ModelTypeSystem,
18    pub is_default: bool,
19}
20
21impl ModelTypeSystemConfig
22{
23    pub fn get_unique_name(&self, base: &str) -> String
24    {
25        match self.is_default {
26            true => base.to_string(),
27            false => format!("{}_{:?}", base, self.effective_system),
28        }
29    }
30}
31
32#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
33pub enum ModelTypeSystem
34{
35    /// COM Automation compatible type system.
36    Automation,
37
38    /// Raw type system.
39    Raw,
40}
41
42impl ModelTypeSystem
43{
44    /// Converts the model type system into public type system tokens.
45    pub fn as_tokens(self) -> TokenStream
46    {
47        match self {
48            ModelTypeSystem::Automation => quote!(Automation),
49            ModelTypeSystem::Raw => quote!(Raw),
50        }
51    }
52
53    /// Converts the model type system into public type system tokens.
54    pub fn as_typesystem_tokens(self, span: Span) -> TokenStream
55    {
56        match self {
57            ModelTypeSystem::Automation => {
58                quote_spanned!(span=> intercom::type_system::TypeSystemName::Automation)
59            }
60            ModelTypeSystem::Raw => {
61                quote_spanned!(span=> intercom::type_system::TypeSystemName::Raw)
62            }
63        }
64    }
65
66    /// Returns the intercom type that represents the type system.
67    pub fn as_typesystem_type(self, span: Span) -> Type
68    {
69        syn::parse2(match self {
70            ModelTypeSystem::Automation => {
71                quote_spanned!(span=> intercom::type_system::AutomationTypeSystem)
72            }
73            ModelTypeSystem::Raw => quote_spanned!(span=> intercom::type_system::RawTypeSystem),
74        })
75        .unwrap()
76    }
77}
78
79impl quote::IdentFragment for ModelTypeSystem
80{
81    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result
82    {
83        write!(f, "{:?}", self)
84    }
85}
86
87/// Type usage context.
88pub struct TypeContext
89{
90    type_system: ModelTypeSystem,
91}
92
93impl TypeContext
94{
95    pub fn new(type_system: ModelTypeSystem) -> TypeContext
96    {
97        TypeContext { type_system }
98    }
99}
100
101/// Defines Type-specific logic for handling the various parameter types in the
102/// Rust/COM interface.
103pub struct TypeHandler
104{
105    ty: Type,
106    context: TypeContext,
107}
108
109impl TypeHandler
110{
111    /// The Rust type.
112    pub fn rust_ty(&self) -> Type
113    {
114        self.ty.clone()
115    }
116
117    /// The COM type.
118    pub fn com_ty(&self, span: Span) -> Type
119    {
120        // Construct bits for the quote.
121        let ty = &self.ty;
122        let ts = self.context.type_system.as_typesystem_type(span);
123        syn::parse2(
124            quote_spanned!(span => <#ty as intercom::type_system::ExternType<#ts>>::ForeignType),
125        )
126        .unwrap()
127    }
128
129    /// Converts a COM parameter named by the ident into a Rust type.
130    pub fn com_to_rust(
131        &self,
132        ident: &Ident,
133        span: Span,
134        dir: Direction,
135        infallible: bool,
136    ) -> TokenStream
137    {
138        // Construct bits for the quote.
139        let ty = &self.ty;
140        let ts = self.context.type_system.as_typesystem_type(span);
141        let (tr, unwrap) = resolve_type_handling(dir, infallible, span);
142        let (maybe_ref, maybe_as_ref) = resolve_ref(ty);
143        match dir {
144            Direction::In => quote_spanned!(span=>
145                    #maybe_ref <#ty as #tr<#ts>>
146                        ::from_foreign_parameter(#ident)#unwrap#maybe_as_ref),
147
148            // Output variables do not use #unwrap to avoid jumping out before all parameters have
149            // been converted from foreign output. This ensures all parameters are brought under
150            // Rust's memory management.
151            Direction::Out | Direction::Retval => quote_spanned!(span=>
152                    <#ty as #tr<#ts>>::from_foreign_output(#ident)),
153        }
154    }
155
156    /// Converts a Rust parameter named by the ident into a COM type.
157    pub fn rust_to_com(
158        &self,
159        ident: &Ident,
160        span: Span,
161        dir: Direction,
162        infallible: bool,
163    ) -> TokenStream
164    {
165        // Construct bits for the quote.
166        let ty = &self.ty;
167        let ts = self.context.type_system.as_typesystem_type(span);
168        let (tr, unwrap) = resolve_type_handling(dir, infallible, span);
169        match dir {
170            Direction::In => quote_spanned!(span=>
171                    <#ty as #tr<#ts>>
172                        ::into_foreign_parameter(#ident)#unwrap.0),
173            Direction::Out | Direction::Retval => quote_spanned!(span=>
174                    <#ty as #tr<#ts>>
175                        ::into_foreign_output(#ident)#unwrap),
176        }
177    }
178
179    /// Gets the default value for the type.
180    pub fn default_value(&self) -> TokenStream
181    {
182        quote!(intercom::type_system::ExternDefault::extern_default())
183    }
184}
185
186fn resolve_ref(ty: &Type) -> (TokenStream, TokenStream)
187{
188    if let syn::Type::Reference(..) = ty {
189        return (quote!(&), quote!());
190    }
191
192    if has_ref(ty) {
193        (quote!(), quote!(.as_ref()))
194    } else {
195        (quote!(), quote!())
196    }
197}
198
199fn has_ref(ty: &Type) -> bool
200{
201    match ty {
202        syn::Type::Reference(..) => true,
203        syn::Type::Path(p) => {
204            let last_segment = p.path.segments.last().expect("Path was empth");
205            match &last_segment.arguments {
206                syn::PathArguments::None | syn::PathArguments::Parenthesized(..) => false,
207                syn::PathArguments::AngleBracketed(generics) => {
208                    generics.args.iter().any(|g| match g {
209                        syn::GenericArgument::Type(t) => has_ref(t),
210                        _ => false,
211                    })
212                }
213            }
214        }
215        _ => false,
216    }
217}
218
219fn resolve_type_handling(dir: Direction, infallible: bool, span: Span)
220    -> (TokenStream, TokenStream)
221{
222    let dir_part = match dir {
223        Direction::In => "Input",
224        Direction::Out | Direction::Retval => "Output",
225    };
226    let (fallibility, unwrap) = match infallible {
227        true => ("Infallible", quote!()),
228        false => ("", quote!(?)),
229    };
230
231    let ident = Ident::new(&format!("{}Extern{}", fallibility, dir_part), span);
232    (
233        quote_spanned!(span => intercom::type_system::#ident),
234        unwrap,
235    )
236}
237
238/// Resolves the `TypeHandler` to use.
239pub fn get_ty_handler(arg_ty: &Type, context: TypeContext) -> Rc<TypeHandler>
240{
241    Rc::new(TypeHandler {
242        ty: arg_ty.clone(),
243        context,
244    })
245}