1use super::common::*;
2use crate::prelude::*;
3
4use std::collections::BTreeMap;
5use std::iter;
6
7use crate::idents;
8use crate::methodinfo::ComMethodInfo;
9use crate::model;
10use crate::tyhandlers::{Direction, ModelTypeSystem};
11use crate::utils;
12
13use syn::spanned::Spanned;
14
15extern crate proc_macro;
16
17#[derive(Default)]
19struct InterfaceOutput
20{
21 iid_arms: Vec<TokenStream>,
22 method_impls: BTreeMap<String, MethodImpl>,
23}
24
25struct MethodImpl
26{
27 info: ComMethodInfo,
31
32 impls: BTreeMap<ModelTypeSystem, TokenStream>,
34}
35
36impl MethodImpl
37{
38 pub fn new(mi: ComMethodInfo) -> Self
39 {
40 MethodImpl {
41 info: mi,
42 impls: Default::default(),
43 }
44 }
45}
46
47pub fn expand_com_interface(
56 attr_tokens: TokenStreamNightly,
57 item_tokens: TokenStreamNightly,
58) -> Result<TokenStreamNightly, model::ParseError>
59{
60 let mut output = vec![];
62 let itf =
63 model::ComInterface::from_ast(&lib_name(), attr_tokens.into(), item_tokens.clone().into())?;
64 let itf_path = &itf.path;
65 let itf_name = itf.ident.to_string();
66 let itf_ref = &itf.itf_ref;
67
68 let mut itf_output = InterfaceOutput::default();
69 for (ts, itf_variant) in &itf.variants {
70 process_itf_variant(&itf, *ts, itf_variant, &mut output, &mut itf_output);
71 }
72
73 if itf.item_type == utils::InterfaceType::Trait {
74 let mut impls = vec![];
75 for (_, method) in itf_output.method_impls.iter() {
76 let method_rust_ident = &method.info.name;
77 let method_name = method_rust_ident.to_string();
78 let mut impl_branches = vec![];
79 for (ts, method_ts_impl) in method.impls.iter() {
80 let ts_tokens = ts.as_typesystem_type(method.info.signature_span);
81 let ts_name = format!("{:?}", ts);
82 impl_branches.push(quote_spanned!(method.info.signature_span =>
83 if let Some( comptr ) = intercom::ComItf::ptr::<#ts_tokens>( self ) {
84 intercom::logging::trace(|l| l(module_path!(), format_args!(
85 "[{:p}, with {:p}] Calling {}::{}, type system: {}",
86 self, comptr.ptr, #itf_name, #method_name, #ts_name)));
87
88 #method_ts_impl
89 }
90 ));
91 }
92
93 let impl_args = method.info.args.iter().map(|ca| {
95 let name = &ca.name;
96 let ty = &ca.ty;
97 quote_spanned!(ca.span => #name : #ty )
98 });
99
100 let unsafety = if method.info.is_unsafe {
101 quote_spanned!(method.info.signature_span => unsafe)
102 } else {
103 quote!()
104 };
105 let self_arg = &method.info.rust_self_arg;
106 let return_ty = &method.info.rust_return_ty;
107
108 impls.push(quote_spanned!(method.info.signature_span =>
110 #unsafety fn #method_rust_ident(
111 #self_arg, #( #impl_args ),*
112 ) -> #return_ty {
113
114 intercom::logging::trace(|l| l(module_path!(), format_args!(
115 "[{:p}] Calling {}::{}", self, #itf_name, #method_name)));
116
117 #[allow(unused_imports)]
118 use intercom::ErrorValue;
119
120 #( #impl_branches )*
122
123 unreachable!();
129 }
130 ));
131 }
132
133 let unsafety = if itf.is_unsafe {
134 quote_spanned!(itf.span => unsafe)
135 } else {
136 quote!()
137 };
138 output.push(quote_spanned!(itf.span =>
139 #[allow(clippy::all)]
140 #[allow(unused_braces)]
141 #unsafety impl<I: intercom::attributes::ComInterface + #itf_path + ?Sized> #itf_path for intercom::ComItf<I> {
142 #( #impls )*
143 }
144 ));
145 }
146
147 let iid_arms = itf_output.iid_arms;
149 let (deref_impl, deref_ret) = if itf.item_type == utils::InterfaceType::Trait {
150 (
151 quote_spanned!(itf.span => com_itf),
152 quote_spanned!(itf.span => &( dyn #itf_path + 'static ) ),
153 )
154 } else {
155 (
165 quote_spanned!(itf.span =>
166 let some_iunk : &intercom::ComItf<dyn intercom::interfaces::RawIUnknown> = com_itf.as_raw_iunknown();
167 let iunknown_iid = <dyn intercom::IUnknown>::iid(
168 intercom::type_system::TypeSystemName::Automation )
169 .expect( "IUnknown must have Automation IID" );
170 let primary_iunk = some_iunk.query_interface( iunknown_iid )
171 .expect( "All types must implement IUnknown" );
172
173 let combox : *mut intercom::ComBoxData< #itf_path > =
174 primary_iunk as *mut intercom::ComBoxData< #itf_path >;
175 unsafe {
176
177 intercom::ComBoxData::release( combox );
181
182 use std::ops::Deref;
184 (*combox).deref()
185 }
186 ),
187 quote_spanned!(itf.span => & #itf_path ),
188 )
189 };
190
191 output.push(quote_spanned!(itf.span =>
192 impl intercom::attributes::ComInterface for #itf_ref {
193
194 type TSelf = #itf_ref;
195
196 #[doc = "Returns the IID of the requested interface."]
197 fn iid_ts<TS: intercom::type_system::TypeSystem>() -> &'static intercom::IID
198 where Self: intercom::attributes::ComInterfaceVariant<TS>
199 {
200 <Self as intercom::attributes::ComInterfaceVariant<TS>>::iid()
201 }
202
203 fn iid(
204 ts : intercom::type_system::TypeSystemName
205 ) -> Option< &'static intercom::IID >
206 {
207 match ts {
208 #( #iid_arms ),*
209 }
210 }
211
212 fn deref(
213 com_itf : &intercom::ComItf<#itf_ref>
214 ) -> #deref_ret {
215 #deref_impl
216 }
217 }
218 ));
219
220 output.push(quote_spanned!(itf.span =>
222
223 impl intercom::type_system::ForeignType for #itf_ref {
224
225 fn type_name() -> &'static str { stringify!( #itf_path ) }
227 }
228
229 ));
230
231 output.push(create_get_typeinfo_function(&itf));
233
234 Ok(tokens_to_tokenstream(item_tokens, output))
235}
236
237fn process_itf_variant(
247 itf: &model::ComInterface,
248 ts: ModelTypeSystem,
249 itf_variant: &model::ComInterfaceVariant,
250 output: &mut Vec<TokenStream>,
251 itf_output: &mut InterfaceOutput,
252)
253{
254 let itf_path = &itf.path;
255 let itf_ident = &itf.ident;
256 let visibility = &itf.visibility;
257 let ts_value_tokens = ts.as_typesystem_tokens(itf.span);
258 let ts_type_tokens = ts.as_typesystem_type(itf.span);
259 let itf_ref = &itf.itf_ref;
260 let vtable_path = itf.vtable(ts);
261 let attr_cominterfacevariant = quote_spanned!(itf_ident.span() =>
262 intercom::attributes::ComInterfaceVariant<#ts_type_tokens>);
263 let attr_cominterfacevtablefor = quote_spanned!(itf_ident.span() =>
264 intercom::attributes::ComInterfaceVTableFor<I, S, #ts_type_tokens>);
265
266 itf_output
268 .iid_arms
269 .push(quote_spanned!(itf.span => #ts_value_tokens => Some( <Self as #attr_cominterfacevariant>::iid() ) ));
270
271 let mut vtbl_fields = vec![];
274 let mut vtbl_values = vec![];
275 if let Some(ref base) = itf.base_interface {
276 vtbl_values.push(quote_spanned!(itf.span =>
277 __base : <dyn #base as #attr_cominterfacevtablefor>::VTABLE));
278 vtbl_fields.push(quote_spanned!(itf.span =>
279 pub __base : <dyn #base as #attr_cominterfacevariant>::VTable));
280 }
281
282 for method_info in &itf_variant.methods {
284 let (vtbl_field, vtbl_value) = format_method_vtable_entries(itf, method_info, ts);
286 vtbl_fields.push(vtbl_field);
287 vtbl_values.push(vtbl_value);
288
289 let method_name = method_info.name.to_string();
293 if !itf_output.method_impls.contains_key(&method_name) {
294 itf_output
295 .method_impls
296 .insert(method_name.clone(), MethodImpl::new(method_info.clone()));
297 }
298
299 let method_impl = &mut itf_output
300 .method_impls
301 .get_mut(&method_name)
302 .expect("We just ensured this exists three lines up... ;_;");
303 method_impl.impls.insert(
304 itf_variant.type_system,
305 rust_to_com_delegate(itf, itf_variant, method_info),
306 );
307
308 output.push(create_virtual_method(itf, method_info, ts));
309 }
310
311 let itf_bound = match itf.item_type {
314 utils::InterfaceType::Struct => quote!(),
315 utils::InterfaceType::Trait if itf.implemented_by.is_some() => quote!(),
316 utils::InterfaceType::Trait => quote!(+ #itf_ident),
317 };
318 if itf.vtable_of.is_none() {
319 output.push(quote_spanned!(itf.span =>
320 #[allow(non_camel_case_types)]
321 #[allow(non_snake_case)]
322 #[allow(clippy::all)]
323 #[repr(C)]
324 #[doc(hidden)]
325 #[derive(Clone, Copy)]
326 #visibility struct #vtable_path { #( #vtbl_fields, )* }
327
328 #[allow(unused)]
329 impl<I, S> intercom::attributes::ComInterfaceVTableFor<I, S, #ts_type_tokens> for #itf_ref
330 where I: ?Sized,
331 S: intercom::attributes::ComClassInterface<I, #ts_type_tokens> + intercom::attributes::ComClass #itf_bound,
332 {
333 const VTABLE: #vtable_path = #vtable_path {
334 #( #vtbl_values, )*
335 };
336 }
337 ));
338 }
339
340 let iid_tokens = utils::get_guid_tokens(&itf_variant.iid, itf.span);
341 output.push(quote_spanned!(itf_path.span() =>
342 #[allow(non_camel_case_types)]
343 #[allow(non_snake_case)]
344 #[allow(clippy::all)]
345 #[doc(hidden)]
346 impl #attr_cominterfacevariant for #itf_ref {
347 type VTable = #vtable_path;
348 fn iid() -> &'static intercom::IID {
349 & #iid_tokens
350 }
351 }
352 ));
353}
354
355fn rust_to_com_delegate(
364 itf: &model::ComInterface,
365 itf_variant: &model::ComInterfaceVariant,
366 method_info: &ComMethodInfo,
367) -> TokenStream
368{
369 let infallible = method_info.returnhandler.is_infallible();
373 let out_arg_declarations = method_info
374 .returnhandler
375 .com_out_args()
376 .iter()
377 .map(|ca| {
378 let ident = &ca.name;
379 let ty = &ca.handler.com_ty(ca.span);
380 let default = ca.handler.default_value();
381 quote_spanned!(ca.span => let mut #ident : #ty = #default; )
382 })
383 .collect::<Vec<_>>();
384
385 let params: Vec<_> = method_info
387 .raw_com_args()
388 .into_iter()
389 .map(|com_arg| {
390 let name = com_arg.name;
391 match com_arg.dir {
392 Direction::In => {
393 com_arg
394 .handler
395 .rust_to_com(&name, com_arg.span, Direction::In, infallible)
396 }
397 Direction::Out | Direction::Retval => quote_spanned!(com_arg.span => &mut #name ),
398 }
399 })
400 .collect();
401
402 let params =
406 iter::once(quote_spanned!(method_info.rust_self_arg.span() => comptr.ptr.as_ptr()))
407 .chain(params);
408
409 let return_ident = Ident::new("__result", Span::call_site());
411 let return_statement = method_info.returnhandler.com_to_rust_return(&return_ident);
412
413 let method_ident = &method_info.name;
415 let return_ty = &method_info.rust_return_ty;
416 let iid_tokens = utils::get_guid_tokens(&itf_variant.iid, method_info.signature_span);
417 let itf_ref = &itf.itf_ref;
418 let ts_type = itf_variant.type_system.as_typesystem_type(itf.span);
419
420 if infallible {
422 quote_spanned!(method_info.signature_span =>
423 #[allow(unused_imports)]
424 let vtbl = comptr.ptr.as_ptr() as *const *const <#itf_ref as
425 intercom::attributes::ComInterfaceVariant<#ts_type>>::VTable;
426
427 #[allow(unused_unsafe)] unsafe {
429 #( #out_arg_declarations )*
430 let #return_ident = ((**vtbl).#method_ident)( #( #params ),* );
431
432 let __intercom_iid = #iid_tokens;
433 #[allow(unused_braces)]
434 return { #return_statement };
435 }
436 )
437 } else {
438 quote_spanned!(method_info.signature_span =>
439 #[allow(unused_imports)]
440 let vtbl = comptr.ptr.as_ptr() as *const *const <#itf_ref as
441 intercom::attributes::ComInterfaceVariant<#ts_type>>::VTable;
442
443 #[allow(unused_unsafe)] let __intercom_result : Result< #return_ty, intercom::ComError > = ( || unsafe {
448 #( #out_arg_declarations )*
449 let #return_ident = ((**vtbl).#method_ident)( #( #params ),* );
450
451 let __intercom_iid = #iid_tokens;
452 #[allow(unused_braces)]
453 Ok( { #return_statement } )
454 } )();
455
456 return match __intercom_result {
457 Ok( v ) => v,
458 Err( err ) => < #return_ty as intercom::ErrorValue >::from_error( err ),
459 };
460 )
461 }
462}
463
464fn format_method_vtable_entries(
465 itf: &model::ComInterface,
466 method_info: &ComMethodInfo,
467 ts: ModelTypeSystem,
468) -> (TokenStream, TokenStream)
469{
470 let itf_ident = &itf.ident;
471 let method_ident = &method_info.name;
472 let method_impl_ident = idents::com_to_rust_method_impl(itf_ident, method_ident, ts);
473 let ret_ty = method_info.returnhandler.com_ty();
474 let generics = match itf.item_type {
475 utils::InterfaceType::Struct => quote!(),
476 utils::InterfaceType::Trait => quote!(::<I, S>),
477 };
478 let params = method_info.get_parameters_tokenstream();
479
480 (
481 quote_spanned!(method_info.signature_span =>
482 pub #method_ident : unsafe extern "system" fn(#params) -> #ret_ty),
483 quote_spanned!(method_info.signature_span =>
484 #method_ident: #method_impl_ident #generics),
485 )
486}
487
488fn create_virtual_method(
489 itf: &model::ComInterface,
490 method_info: &ComMethodInfo,
491 ts: ModelTypeSystem,
492) -> TokenStream
493{
494 let itf_ident = &itf.ident;
495 let method_ident = &method_info.name;
496 let method_name = method_ident.to_string();
497 let method_impl_ident = idents::com_to_rust_method_impl(itf_ident, method_ident, ts);
498 let infallible = method_info.returnhandler.is_infallible();
499 let itf_ident = &itf.ident;
500 let itf_path = &itf.path;
501 let ts_type_tokens = ts.as_typesystem_type(itf.span);
502 let params = method_info.get_parameters_tokenstream();
503 let attr_comclassinterface = quote!(intercom::attributes::ComClassInterface);
504
505 let in_args: Vec<_> = method_info
507 .args
508 .iter()
509 .map(|ca| {
510 ca.handler
511 .com_to_rust(&ca.name, ca.span, Direction::In, infallible)
512 })
513 .collect();
514
515 let return_ident = Ident::new("__result", Span::call_site());
516 let return_statement = method_info.returnhandler.rust_to_com_return(&return_ident);
517 let ret_ty = method_info.returnhandler.com_ty();
518
519 let self_struct_expr = if itf.implemented_by.is_some() {
521 quote!(&*self_combox)
522 } else if method_info.is_const {
523 quote!(&**self_combox)
524 } else {
525 quote!(&mut **self_combox)
526 };
527
528 let (required_itf, call) = match &itf.implemented_by {
532 Some(path) => (
533 quote!(),
534 quote!(#path::#method_ident(self_struct, #( #in_args ),*)),
535 ),
536 None => (
537 quote!(+ #itf_ident),
538 quote!(self_struct.#method_ident( #( #in_args ),* )),
539 ),
540 };
541
542 let (generics, bounds, s_ref, i_ref) = match itf.item_type {
546 utils::InterfaceType::Struct => (quote!(), quote!(), quote!(#itf_path), quote!(#itf_path)),
547 utils::InterfaceType::Trait => (
548 quote!(<I, S>),
549 quote!(
550 where I: ?Sized,
551 S: #attr_comclassinterface<I, #ts_type_tokens> + intercom::attributes::ComClass #required_itf
552 ),
553 quote!(S),
554 quote!(I),
555 ),
556 };
557
558 let payload = if infallible {
560 quote!(
561 let self_struct = #self_struct_expr;
562 let #return_ident = #call;
563
564 intercom::logging::trace(|l| l(module_path!(), format_args!(
565 "[{:p}, through {:p}] Serving {}::{}, OK",
566 self_combox, self_vtable, std::any::type_name::<#s_ref>(), #method_name)));
567
568 #return_statement
569 )
570 } else {
571 quote!(
573 let result : Result< #ret_ty, intercom::ComError > = ( || {
574 let self_struct = #self_struct_expr;
575 let #return_ident = #call;
576 Ok( { #return_statement } )
577 } )();
578
579 match result {
580 Ok( v ) => {
581 intercom::logging::trace(|l| l(module_path!(), format_args!(
582 "[{:p}, through {:p}] Serving {}::{}, OK",
583 self_combox, self_vtable,
584 std::any::type_name::<#s_ref>(), #method_name)));
585 v
586 },
587 Err( err ) => {
588 intercom::logging::trace(|l| l(module_path!(), format_args!(
589 "[{:p}, through {:p}] Serving {}::{}, ERROR",
590 self_combox, self_vtable,
591 std::any::type_name::<#s_ref>(), #method_name)));
592 <#ret_ty as intercom::ErrorValue>::from_error(
593 intercom::store_error(err))
594 },
595 }
596 )
597 };
598
599 quote!(
600 #[allow(non_snake_case)]
601 #[allow(dead_code)]
602 #[doc(hidden)]
603 unsafe extern "system" fn #method_impl_ident #generics(
604 #params
605 ) -> #ret_ty
606 #bounds
607 {
608 let offset = <#s_ref as #attr_comclassinterface<#i_ref, #ts_type_tokens>>::offset();
611 let self_combox = ( self_vtable as usize - offset )
612 as *mut intercom::ComBoxData<#s_ref>;
613
614 intercom::logging::trace(|l| l(module_path!(), format_args!(
615 "[{:p}, through {:p}] Serving {}::{}",
616 self_combox, self_vtable,
617 std::any::type_name::<#s_ref>(), #method_name)));
618
619 #payload
620 }
621 )
622}
623
624fn create_get_typeinfo_function(itf: &model::ComInterface) -> TokenStream
625{
626 let itf_name = itf.ident.to_string();
627 let itf_ref = &itf.itf_ref;
628 let mut variant_tokens = vec![];
629 for (ts, variant) in &itf.variants {
630 variant_tokens.push(create_typeinfo_for_variant(itf, *ts, variant));
631 }
632 let is_impl_interface = itf.item_type == utils::InterfaceType::Struct;
633
634 quote_spanned!(itf.span =>
635 #[allow(non_snake_case)]
636 #[allow(dead_code)]
637 impl intercom::attributes::ComInterfaceTypeInfo for #itf_ref
638 {
639 fn gather_type_info() -> Vec<intercom::typelib::TypeInfo>
640 {
641 let variants = vec![ #( #variant_tokens ),* ];
642
643 vec![ intercom::typelib::TypeInfo::Interface(
644 intercom::ComBox::new( intercom::typelib::Interface {
645 name: #itf_name.into(),
646 variants,
647 options: intercom::typelib::InterfaceOptions {
648 class_impl_interface: #is_impl_interface,
649 ..Default::default()
650 }
651 })
652 ) ]
653 }
654 }
655 )
656}
657
658fn create_typeinfo_for_variant(
659 itf: &model::ComInterface,
660 ts: ModelTypeSystem,
661 itf_variant: &model::ComInterfaceVariant,
662) -> TokenStream
663{
664 let ts_tokens = ts.as_typesystem_tokens(itf.span);
665 let ts_type = ts.as_typesystem_type(itf.span);
666 let iid_tokens = utils::get_guid_tokens(&itf_variant.iid, itf.span);
667 let methods = itf_variant.methods.iter().map( |m| {
668 let method_name = m.name.to_string();
669 let return_type = match &m.return_type {
670 Some(rt) => quote_spanned!(m.signature_span =>
671 intercom::typelib::Arg {
672 name: "".into(),
673 ty: <
674 <#rt as intercom::type_system::ExternType<#ts_type>>::ForeignType
675 as intercom::type_system::ForeignType>::type_name().into(),
676 indirection_level: <
677 <#rt as intercom::type_system::ExternType<#ts_type>>::ForeignType
678 as intercom::type_system::ForeignType>::indirection_level(),
679 direction: intercom::typelib::Direction::Return,
680 }),
681 None => quote_spanned!(m.signature_span => intercom::typelib::Arg {
682 name: "".into(),
683 ty: "void".into(),
684 indirection_level: 0,
685 direction: intercom::typelib::Direction::Return,
686 } ),
687 };
688
689 let params = m.raw_com_args().into_iter().map(|arg| {
690 let com_ty = arg.handler.com_ty(arg.span);
691 let arg_name = arg.name.to_string();
692 let dir_ident = Ident::new(match arg.dir {
693 Direction::In => "In",
694 Direction::Out => "Out",
695 Direction::Retval => "Retval"
696 }, arg.span);
697
698 quote_spanned!(arg.span => intercom::typelib::Arg {
699 name: #arg_name.into(),
700 ty: <#com_ty as intercom::type_system::ForeignType>::type_name().into(),
701 indirection_level: <#com_ty as intercom::type_system::ForeignType>::indirection_level(),
702 direction: intercom::typelib::Direction::#dir_ident,
703 })
704 }).collect::<Vec<_>>();
705
706 quote_spanned!(m.signature_span =>
707 intercom::ComBox::new(intercom::typelib::Method {
708 name: #method_name.into(),
709 return_type: #return_type,
710 parameters: vec![ #( #params ),* ],
711 })
712 )
713 }).collect::<Vec<_>>();
714
715 quote_spanned!(itf.span =>
716 intercom::ComBox::new( intercom::typelib::InterfaceVariant {
717 ts: #ts_tokens,
718 iid: #iid_tokens,
719 methods: vec![ #( #methods ),* ],
720 })
721 )
722}