1use proc_macro2::TokenStream;
8use quote::format_ident;
9use syn::ItemImpl;
10
11use super::r_wrappers::{TraitWrapperOpts, generate_trait_r_wrapper};
12use super::{TraitConst, TraitMethod, type_to_uppercase_name};
13use crate::miniextendr_impl::ClassSystem;
14
15pub(super) fn generate_vtable_static(
36 impl_item: &ItemImpl,
37 trait_path: &syn::Path,
38 concrete_type: &syn::Type,
39 class_system: ClassSystem,
40 blanket: bool,
41 internal: bool,
42 noexport: bool,
43) -> TokenStream {
44 let Some(trait_name) = trait_path.segments.last().map(|s| &s.ident) else {
46 return syn::Error::new_spanned(trait_path, "trait path must have at least one segment")
47 .into_compile_error();
48 };
49
50 let trait_type_args: Vec<syn::Type> = trait_path
53 .segments
54 .last()
55 .and_then(|seg| {
56 if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
57 Some(
58 args.args
59 .iter()
60 .filter_map(|arg| {
61 if let syn::GenericArgument::Type(ty) = arg {
62 Some(ty.clone())
63 } else {
64 None
65 }
66 })
67 .collect(),
68 )
69 } else {
70 None
71 }
72 })
73 .unwrap_or_default();
74
75 let type_ident = match concrete_type {
77 syn::Type::Path(type_path) => {
78 let Some(last_seg) = type_path.path.segments.last() else {
79 return syn::Error::new_spanned(
80 concrete_type,
81 "type path must have at least one segment",
82 )
83 .into_compile_error();
84 };
85 last_seg.ident.clone()
86 }
87 _ => format_ident!("Unknown"),
88 };
89
90 let type_name_str = type_to_uppercase_name(concrete_type);
92 let trait_name_upper = trait_name.to_string().to_uppercase();
93 let trait_name_lower = trait_name.to_string().to_lowercase();
94
95 let vtable_static_name = format_ident!("__VTABLE_{}_FOR_{}", trait_name_upper, type_name_str);
97 let vtable_type_name = format_ident!("{}VTable", trait_name);
98
99 let mut builder_path = trait_path.clone();
103 if let Some(last) = builder_path.segments.last_mut() {
104 last.ident = format_ident!("__{}_build_vtable", trait_name_lower);
105 last.arguments = syn::PathArguments::None;
106 }
107
108 let mut vtable_type_path = trait_path.clone();
111 if let Some(last) = vtable_type_path.segments.last_mut() {
112 last.ident = vtable_type_name.clone();
113 last.arguments = syn::PathArguments::None;
114 }
115
116 let all_methods = match extract_methods(impl_item) {
118 Ok(m) => m,
119 Err(e) => return e.into_compile_error(),
120 };
121 let consts = extract_consts(impl_item);
122
123 let methods: Vec<&TraitMethod> = all_methods.iter().filter(|m| !m.skip).collect();
126
127 let method_c_wrappers: Vec<TokenStream> = methods
129 .iter()
130 .map(|m| generate_trait_method_c_wrapper(m, &type_ident, trait_name, trait_path))
131 .collect();
132
133 let const_c_wrappers: Vec<TokenStream> = consts
135 .iter()
136 .map(|c| generate_trait_const_c_wrapper(c, &type_ident, trait_name, trait_path))
137 .collect();
138
139 let c_wrappers: Vec<TokenStream> = method_c_wrappers
141 .into_iter()
142 .chain(const_c_wrappers)
143 .collect();
144
145 let raw_impl_tags = crate::roxygen::roxygen_tags_from_attrs(&impl_item.attrs);
147 let (impl_doc_tags, param_warnings) = crate::roxygen::strip_method_tags(
148 &raw_impl_tags,
149 &type_ident.to_string(),
150 impl_item.impl_token.span,
151 );
152 let class_has_no_rd = crate::roxygen::has_roxygen_tag(&impl_doc_tags, "noRd");
153
154 let methods_owned: Vec<TraitMethod> = methods.iter().map(|m| (*m).clone()).collect();
156 let r_wrapper_string = match generate_trait_r_wrapper(
157 &type_ident,
158 trait_name,
159 &methods_owned,
160 &consts,
161 TraitWrapperOpts {
162 class_system,
163 class_has_no_rd,
164 internal,
165 noexport,
166 },
167 ) {
168 Ok(s) => s,
169 Err(e) => return e.into_compile_error(),
170 };
171
172 let r_wrappers_const = format_ident!(
174 "R_WRAPPERS_{}_{}_IMPL",
175 type_ident.to_string().to_uppercase(),
176 trait_name_upper
177 );
178
179 let dispatch_entry_name = format_ident!(
181 "__MX_DISPATCH_{}_{}_FOR_{}",
182 trait_name_upper,
183 type_ident.to_string().to_uppercase(),
184 type_name_str
185 );
186
187 let mut trait_tag_path = trait_path.clone();
189 if let Some(last) = trait_tag_path.segments.last_mut() {
190 last.ident = format_ident!("TAG_{}", trait_name_upper);
191 last.arguments = syn::PathArguments::None;
192 }
193
194 let r_wrapper_str = crate::r_wrapper_raw_literal(&r_wrapper_string);
196 let source_loc_doc = crate::source_location_doc(type_ident.span());
197 let source_start = type_ident.span().start();
198 let source_line_lit = syn::LitInt::new(&source_start.line.to_string(), type_ident.span());
199 let source_col_lit =
200 syn::LitInt::new(&(source_start.column + 1).to_string(), type_ident.span());
201
202 let has_items = !all_methods.is_empty() || !consts.is_empty();
210 let clean_impl_tokens = if has_items && !blanket {
211 let mut clean_impl = impl_item.clone();
212 for item in &mut clean_impl.items {
213 if let syn::ImplItem::Fn(method) = item {
214 method
215 .attrs
216 .retain(|attr| !attr.path().is_ident("miniextendr"));
217 }
218 }
219 quote::quote! { #clean_impl }
220 } else {
221 quote::quote! {}
222 };
223
224 let vtable_static_tokens = if trait_type_args.is_empty() {
230 quote::quote! {
232 #[unsafe(no_mangle)]
233 pub static #vtable_static_name: #vtable_type_path =
234 #builder_path::<#concrete_type>();
235 }
236 } else {
237 let methods_for_vtable: Vec<TraitMethod> = methods.iter().map(|m| (*m).clone()).collect();
240 let concrete_shims = generate_concrete_vtable_shims(
241 &methods_for_vtable,
242 &type_ident,
243 trait_name,
244 trait_path,
245 concrete_type,
246 );
247 let vtable_inits: Vec<TokenStream> = methods
248 .iter()
249 .filter(|m| m.has_self)
250 .map(|m| {
251 let name = &m.ident;
252 let shim_name =
253 format_ident!("__vtshim_{}__{}__{}", type_ident, trait_name, m.ident);
254 quote::quote! { #name: #shim_name }
255 })
256 .collect();
257 quote::quote! {
258 #concrete_shims
259 #[unsafe(no_mangle)]
260 pub static #vtable_static_name: #vtable_type_path = #vtable_type_path {
261 #(#vtable_inits),*
262 };
263 }
264 };
265
266 quote::quote! {
267 #clean_impl_tokens
270
271 #param_warnings
273
274 #[doc = concat!(
275 "Vtable for `",
276 stringify!(#concrete_type),
277 "` implementing `",
278 stringify!(#trait_path),
279 "`."
280 )]
281 #[doc = "Generated by `#[miniextendr]` on the trait impl block."]
282 #[doc = #source_loc_doc]
283 #[doc = concat!("Generated from source file `", file!(), "`.")]
284 #[doc(hidden)]
285 #vtable_static_tokens
286
287 #(#c_wrappers)*
289
290 #[doc = concat!(
292 "R wrapper code for `",
293 stringify!(#type_ident),
294 "` implementing `",
295 stringify!(#trait_name),
296 "`."
297 )]
298 #[doc = #source_loc_doc]
299 #[doc = concat!("Generated from source file `", file!(), "`.")]
300 #[doc(hidden)]
301 #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_R_WRAPPERS), linkme(crate = ::miniextendr_api::linkme))]
302 static #r_wrappers_const: ::miniextendr_api::registry::RWrapperEntry =
303 ::miniextendr_api::registry::RWrapperEntry {
304 priority: ::miniextendr_api::registry::RWrapperPriority::TraitImpl,
305 source_file: file!(),
306 content: concat!(
307 "# Generated from Rust impl `",
308 stringify!(#trait_name),
309 "` for `",
310 stringify!(#type_ident),
311 "` (",
312 file!(),
313 ":",
314 #source_line_lit,
315 ":",
316 #source_col_lit,
317 ")",
318 #r_wrapper_str
319 ),
320 };
321
322 #[doc = concat!(
324 "Trait dispatch entry: `",
325 stringify!(#trait_name),
326 "` for `",
327 stringify!(#type_ident),
328 "`."
329 )]
330 #[doc = #source_loc_doc]
331 #[doc = concat!("Generated from source file `", file!(), "`.")]
332 #[doc(hidden)]
333 #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_TRAIT_DISPATCH), linkme(crate = ::miniextendr_api::linkme))]
334 static #dispatch_entry_name: ::miniextendr_api::registry::TraitDispatchEntry =
335 ::miniextendr_api::registry::TraitDispatchEntry {
336 concrete_tag: ::miniextendr_api::abi::mx_tag_from_path(
337 concat!(module_path!(), "::", stringify!(#type_ident))
338 ),
339 trait_tag: #trait_tag_path,
340 vtable: unsafe {
341 ::std::ptr::from_ref(&#vtable_static_name).cast::<::std::os::raw::c_void>()
343 },
344 vtable_symbol: stringify!(#vtable_static_name),
345 };
346 }
347}
348
349fn generate_concrete_vtable_shims(
366 methods: &[TraitMethod],
367 type_ident: &syn::Ident,
368 trait_name: &syn::Ident,
369 trait_path: &syn::Path,
370 concrete_type: &syn::Type,
371) -> TokenStream {
372 let mut shims = Vec::new();
373
374 for method in methods {
375 if !method.has_self {
376 continue; }
378
379 let method_ident = &method.ident;
380 let shim_name = format_ident!("__vtshim_{}__{}__{}", type_ident, trait_name, method_ident);
381
382 let param_count = method
384 .sig
385 .inputs
386 .iter()
387 .filter(|a| !matches!(a, syn::FnArg::Receiver(_)))
388 .count();
389 let expected_argc = param_count as i32;
390
391 let arg_extractions: Vec<TokenStream> = method
393 .sig
394 .inputs
395 .iter()
396 .filter(|a| !matches!(a, syn::FnArg::Receiver(_)))
397 .enumerate()
398 .map(|(i, arg)| {
399 if let syn::FnArg::Typed(pt) = arg {
400 let name = if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
401 pat_ident.ident.clone()
402 } else {
403 format_ident!("arg{}", i)
404 };
405 let name_str = name.to_string();
406
407 if is_self_ref_type(&pt.ty) {
409 let extptr_name = format_ident!("__extptr_{}", name);
410 quote::quote! {
411 let #extptr_name: ::miniextendr_api::ExternalPtr<#concrete_type> = unsafe {
412 ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
413 };
414 let #name = &*#extptr_name;
415 }
416 } else {
417 let ty = &pt.ty;
418 quote::quote! {
419 let #name: #ty = unsafe {
420 ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
421 };
422 }
423 }
424 } else {
425 quote::quote! {}
426 }
427 })
428 .collect();
429
430 let param_names: Vec<syn::Ident> = method
432 .sig
433 .inputs
434 .iter()
435 .filter(|a| !matches!(a, syn::FnArg::Receiver(_)))
436 .enumerate()
437 .map(|(i, arg)| {
438 if let syn::FnArg::Typed(pt) = arg
439 && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
440 {
441 return pat_ident.ident.clone();
442 }
443 format_ident!("arg{}", i)
444 })
445 .collect();
446
447 let method_call = if method.is_mut {
451 quote::quote! {
452 let self_ref = unsafe { &mut *data.cast::<#concrete_type>() };
453 <#concrete_type as #trait_path>::#method_ident(self_ref, #(#param_names),*)
454 }
455 } else {
456 quote::quote! {
457 let self_ref = unsafe { &*data.cast::<#concrete_type>().cast_const() };
458 <#concrete_type as #trait_path>::#method_ident(self_ref, #(#param_names),*)
459 }
460 };
461
462 let has_return = match &method.sig.output {
464 syn::ReturnType::Default => false,
465 syn::ReturnType::Type(_, ty) => {
466 !matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty())
467 }
468 };
469 let result_conversion = if has_return {
470 quote::quote! {
471 unsafe { ::miniextendr_api::trait_abi::to_sexp(result) }
472 }
473 } else {
474 quote::quote! {
475 let _ = result;
476 unsafe { ::miniextendr_api::trait_abi::nil() }
477 }
478 };
479
480 let method_name_str = format!("{}::{}", trait_name, method_ident);
481
482 shims.push(quote::quote! {
483 #[doc(hidden)]
484 #[allow(non_snake_case)]
485 unsafe extern "C" fn #shim_name(
486 data: *mut ::std::os::raw::c_void,
487 argc: i32,
488 argv: *const ::miniextendr_api::ffi::SEXP,
489 ) -> ::miniextendr_api::ffi::SEXP {
490 unsafe {
491 ::miniextendr_api::trait_abi::check_arity(argc, #expected_argc, #method_name_str);
492 }
493 ::miniextendr_api::unwind_protect::with_r_unwind_protect_shim(|| {
497 #(#arg_extractions)*
498 let result = { #method_call };
499 #result_conversion
500 })
501 }
502 });
503 }
504
505 quote::quote! { #(#shims)* }
506}
507
508fn extract_methods(impl_item: &ItemImpl) -> syn::Result<Vec<TraitMethod>> {
514 let mut methods = Vec::new();
515 for item in &impl_item.items {
516 if let syn::ImplItem::Fn(method) = item {
517 let (has_self, is_mut) = method.sig.inputs.first().map_or((false, false), |arg| {
519 if let syn::FnArg::Receiver(r) = arg {
520 (true, r.mutability.is_some())
521 } else {
522 (false, false)
523 }
524 });
525 let attrs = parse_trait_method_attrs(&method.attrs)?;
526
527 let all_tags = crate::roxygen::roxygen_tags_from_attrs(&method.attrs);
529 let param_tags: Vec<String> = all_tags
530 .into_iter()
531 .filter(|tag| tag.starts_with("@param"))
532 .collect();
533
534 methods.push(TraitMethod {
535 ident: method.sig.ident.clone(),
536 sig: method.sig.clone(),
537 has_self,
538 is_mut,
539 worker: attrs.worker,
540 unsafe_main_thread: attrs.unsafe_main_thread,
541 coerce: attrs.coerce,
542 check_interrupt: attrs.check_interrupt,
543 rng: attrs.rng,
544 unwrap_in_r: attrs.unwrap_in_r,
545 param_defaults: attrs.defaults,
546 param_tags,
547 skip: attrs.skip,
548 r_name: attrs.r_name,
549 strict: attrs.strict,
550 lifecycle: attrs.lifecycle,
551 r_entry: attrs.r_entry,
552 r_post_checks: attrs.r_post_checks,
553 r_on_exit: attrs.r_on_exit,
554 });
555 }
556 }
557 Ok(methods)
558}
559
560struct TraitMethodAttrs {
565 worker: bool,
567 unsafe_main_thread: bool,
569 coerce: bool,
571 check_interrupt: bool,
573 rng: bool,
575 unwrap_in_r: bool,
577 skip: bool,
579 defaults: std::collections::HashMap<String, String>,
581 r_name: Option<String>,
583 strict: bool,
585 lifecycle: Option<crate::lifecycle::LifecycleSpec>,
587 r_entry: Option<String>,
589 r_post_checks: Option<String>,
591 r_on_exit: Option<crate::miniextendr_fn::ROnExit>,
593}
594
595fn parse_trait_method_attrs(attrs: &[syn::Attribute]) -> syn::Result<TraitMethodAttrs> {
604 let mut worker = false;
605 let mut unsafe_main_thread = false;
606 let mut coerce = false;
607 let mut check_interrupt = false;
608 let mut rng = false;
609 let mut unwrap_in_r = false;
610 let mut skip = false;
611 let mut strict = false;
612 let mut defaults = std::collections::HashMap::new();
613 let mut r_name: Option<String> = None;
614 let mut lifecycle: Option<crate::lifecycle::LifecycleSpec> = None;
615 let mut r_entry: Option<String> = None;
616 let mut r_post_checks: Option<String> = None;
617 let mut r_on_exit: Option<crate::miniextendr_fn::ROnExit> = None;
618
619 for attr in attrs {
620 if !attr.path().is_ident("miniextendr") {
621 continue;
622 }
623
624 attr.parse_nested_meta(|meta| {
625 let is_class_meta = meta.path.is_ident("env")
626 || meta.path.is_ident("r6")
627 || meta.path.is_ident("s7")
628 || meta.path.is_ident("s3")
629 || meta.path.is_ident("s4");
630
631 if is_class_meta {
632 meta.parse_nested_meta(|inner| {
633 if inner.path.is_ident("worker") {
634 worker = true;
635 } else if inner.path.is_ident("main_thread") {
636 unsafe_main_thread = true;
637 } else if inner.path.is_ident("coerce") {
638 coerce = true;
639 } else if inner.path.is_ident("check_interrupt") {
640 check_interrupt = true;
641 } else if inner.path.is_ident("unwrap_in_r") {
642 unwrap_in_r = true;
643 } else {
644 return Err(inner.error(
645 "unknown nested option; expected `worker`, `main_thread`, `coerce`, \
646 `check_interrupt`, or `unwrap_in_r`",
647 ));
648 }
649 Ok(())
650 })?;
651 } else if meta.path.is_ident("worker") {
652 worker = true;
653 } else if meta.path.is_ident("main_thread") {
654 unsafe_main_thread = true;
655 } else if meta.path.is_ident("coerce") {
656 coerce = true;
657 } else if meta.path.is_ident("check_interrupt") {
658 check_interrupt = true;
659 } else if meta.path.is_ident("rng") {
660 rng = true;
661 } else if meta.path.is_ident("unwrap_in_r") {
662 unwrap_in_r = true;
663 } else if meta.path.is_ident("skip") {
664 skip = true;
665 } else if meta.path.is_ident("r_name") {
666 let value: syn::LitStr = meta.value()?.parse()?;
667 r_name = Some(value.value());
668 } else if meta.path.is_ident("strict") {
669 strict = true;
670 } else if meta.path.is_ident("defaults") {
671 meta.parse_nested_meta(|inner| {
673 let param_name = inner
674 .path
675 .get_ident()
676 .map(|i| i.to_string())
677 .unwrap_or_default();
678 let value: syn::LitStr = inner.value()?.parse()?;
679 defaults.insert(param_name, value.value());
680 Ok(())
681 })?;
682 } else if meta.path.is_ident("lifecycle") {
683 if meta.input.peek(syn::Token![=]) {
684 let _: syn::Token![=] = meta.input.parse()?;
686 let value: syn::LitStr = meta.input.parse()?;
687 let stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
688 .ok_or_else(|| {
689 syn::Error::new(
690 value.span(),
691 "invalid lifecycle stage; expected one of: experimental, stable, superseded, soft-deprecated, deprecated, defunct",
692 )
693 })?;
694 lifecycle = Some(crate::lifecycle::LifecycleSpec::new(stage));
695 } else {
696 let mut spec = crate::lifecycle::LifecycleSpec::default();
698 meta.parse_nested_meta(|inner| {
699 let key = inner.path.get_ident()
700 .ok_or_else(|| inner.error("expected identifier"))?
701 .to_string();
702 let _: syn::Token![=] = inner.input.parse()?;
703 let value: syn::LitStr = inner.input.parse()?;
704 match key.as_str() {
705 "stage" => {
706 spec.stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
707 .ok_or_else(|| syn::Error::new(value.span(), "invalid lifecycle stage"))?;
708 }
709 "when" => spec.when = Some(value.value()),
710 "what" => spec.what = Some(value.value()),
711 "with" => spec.with = Some(value.value()),
712 "details" => spec.details = Some(value.value()),
713 "id" => spec.id = Some(value.value()),
714 _ => return Err(inner.error(
715 "unknown lifecycle option; expected: stage, when, what, with, details, id"
716 )),
717 }
718 Ok(())
719 })?;
720 lifecycle = Some(spec);
721 }
722 } else if meta.path.is_ident("r_entry") {
723 let _: syn::Token![=] = meta.input.parse()?;
724 let value: syn::LitStr = meta.input.parse()?;
725 r_entry = Some(value.value());
726 } else if meta.path.is_ident("r_post_checks") {
727 let _: syn::Token![=] = meta.input.parse()?;
728 let value: syn::LitStr = meta.input.parse()?;
729 r_post_checks = Some(value.value());
730 } else if meta.path.is_ident("r_on_exit") {
731 if meta.input.peek(syn::Token![=]) {
732 let _: syn::Token![=] = meta.input.parse()?;
734 let value: syn::LitStr = meta.input.parse()?;
735 r_on_exit = Some(crate::miniextendr_fn::ROnExit {
736 expr: value.value(),
737 add: true,
738 after: true,
739 });
740 } else {
741 let mut expr = None;
743 let mut add = true;
744 let mut after = true;
745 meta.parse_nested_meta(|inner| {
746 if inner.path.is_ident("expr") {
747 let _: syn::Token![=] = inner.input.parse()?;
748 let value: syn::LitStr = inner.input.parse()?;
749 expr = Some(value.value());
750 } else if inner.path.is_ident("add") {
751 let _: syn::Token![=] = inner.input.parse()?;
752 let value: syn::LitBool = inner.input.parse()?;
753 add = value.value;
754 } else if inner.path.is_ident("after") {
755 let _: syn::Token![=] = inner.input.parse()?;
756 let value: syn::LitBool = inner.input.parse()?;
757 after = value.value;
758 } else {
759 return Err(inner.error(
760 "unknown r_on_exit option; expected `expr`, `add`, or `after`",
761 ));
762 }
763 Ok(())
764 })?;
765 let expr = expr.ok_or_else(|| {
766 meta.error("r_on_exit(...) requires `expr = \"...\"` specifying the R expression")
767 })?;
768 r_on_exit = Some(crate::miniextendr_fn::ROnExit { expr, add, after });
769 }
770 } else {
771 return Err(meta.error(
772 "unknown #[miniextendr] option on trait impl method; expected one of: \
773 `env`, `r6`, `s7`, `s3`, `s4`, `worker`, `main_thread`, `coerce`, \
774 `check_interrupt`, `rng`, `unwrap_in_r`, `skip`, `r_name`, \
775 `defaults`, `strict`, `lifecycle`, `r_entry`, `r_post_checks`, `r_on_exit`",
776 ));
777 }
778 Ok(())
779 })?;
780 }
781
782 Ok(TraitMethodAttrs {
783 worker: worker || cfg!(feature = "default-worker"),
784 unsafe_main_thread,
785 coerce,
786 check_interrupt,
787 rng,
788 unwrap_in_r,
789 skip,
790 strict,
791 defaults,
792 r_name,
793 lifecycle,
794 r_entry,
795 r_post_checks,
796 r_on_exit,
797 })
798}
799
800fn extract_consts(impl_item: &ItemImpl) -> Vec<TraitConst> {
805 impl_item
806 .items
807 .iter()
808 .filter_map(|item| {
809 if let syn::ImplItem::Const(const_item) = item {
810 Some(TraitConst {
811 ident: const_item.ident.clone(),
812 ty: const_item.ty.clone(),
813 })
814 } else {
815 None
816 }
817 })
818 .collect()
819}
820
821pub(super) fn is_self_ref_type(ty: &syn::Type) -> bool {
827 if let syn::Type::Reference(r) = ty
828 && let syn::Type::Path(tp) = r.elem.as_ref()
829 && tp.path.is_ident("Self")
830 {
831 return true;
832 }
833 false
834}
835
836pub(super) fn generate_trait_method_c_wrapper(
849 method: &TraitMethod,
850 type_ident: &syn::Ident,
851 trait_name: &syn::Ident,
852 trait_path: &syn::Path,
853) -> TokenStream {
854 use crate::c_wrapper_builder::{CWrapperContext, ReturnHandling, ThreadStrategy};
855
856 let method_ident = &method.ident;
857 let c_ident = method.c_wrapper_ident(type_ident, trait_name);
858 let call_method_def_ident = method.call_method_def_ident(type_ident, trait_name);
859
860 let thread_strategy = if method.has_self || method.unsafe_main_thread {
863 ThreadStrategy::MainThread
864 } else if method.worker {
865 ThreadStrategy::WorkerThread
866 } else {
867 ThreadStrategy::MainThread
868 };
869
870 let rust_args: Vec<syn::Ident> = method
872 .sig
873 .inputs
874 .iter()
875 .filter_map(|arg| {
876 if let syn::FnArg::Typed(pt) = arg {
877 if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
878 Some(pat_ident.ident.clone())
879 } else {
880 None
881 }
882 } else {
883 None
884 }
885 })
886 .collect();
887
888 let mut self_ref_params = std::collections::HashSet::new();
892 let filtered_inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> = method
893 .sig
894 .inputs
895 .iter()
896 .filter(|arg| !matches!(arg, syn::FnArg::Receiver(_)))
897 .map(|arg| {
898 if let syn::FnArg::Typed(pt) = arg
899 && is_self_ref_type(&pt.ty)
900 {
901 if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
903 self_ref_params.insert(pat_ident.ident.to_string());
904 }
905 let pat = &pt.pat;
907 return syn::parse_quote!(#pat: ::miniextendr_api::ExternalPtr<#type_ident>);
908 }
909 arg.clone()
910 })
911 .collect();
912
913 let call_args: Vec<proc_macro2::TokenStream> = rust_args
915 .iter()
916 .map(|arg| {
917 if self_ref_params.contains(&arg.to_string()) {
918 quote::quote! { &*#arg }
919 } else {
920 quote::quote! { #arg }
921 }
922 })
923 .collect();
924
925 let return_handling = if method.unwrap_in_r && output_is_result(&method.sig.output) {
927 ReturnHandling::IntoR
928 } else {
929 crate::c_wrapper_builder::detect_return_handling(&method.sig.output)
930 };
931
932 let r_wrappers_const = format_ident!(
934 "R_WRAPPERS_{}_{}_IMPL",
935 type_ident.to_string().to_uppercase(),
936 trait_name.to_string().to_uppercase()
937 );
938
939 let mut builder = CWrapperContext::builder(method_ident.clone(), c_ident)
942 .r_wrapper_const(r_wrappers_const)
943 .inputs(filtered_inputs)
944 .output(method.sig.output.clone())
945 .thread_strategy(thread_strategy)
946 .return_handling(return_handling)
947 .type_context(type_ident.clone())
948 .call_method_def_ident(call_method_def_ident);
949
950 if method.has_self {
951 let trait_method_name = format!("{}::{}()", trait_name, method_ident);
953 let self_extraction = if method.is_mut {
954 quote::quote! {
955 let mut self_ptr = unsafe {
956 ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(self_sexp)
957 };
958 let self_ref = self_ptr.downcast_mut::<#type_ident>()
959 .unwrap_or_else(|| panic!(
960 "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
961 This can happen if you pass an object of a different type to a trait method.",
962 #trait_method_name,
963 stringify!(#type_ident)
964 ));
965 }
966 } else {
967 quote::quote! {
968 let self_ptr = unsafe {
969 ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(self_sexp)
970 };
971 let self_ref = self_ptr.downcast_ref::<#type_ident>()
972 .unwrap_or_else(|| panic!(
973 "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
974 This can happen if you pass an object of a different type to a trait method.",
975 #trait_method_name,
976 stringify!(#type_ident)
977 ));
978 }
979 };
980
981 let call_expr = quote::quote! {
985 <#type_ident as #trait_path>::#method_ident(self_ref, #(#call_args),*)
986 };
987
988 builder = builder
989 .pre_call(vec![self_extraction])
990 .call_expr(call_expr)
991 .has_self();
992 } else {
993 let call_expr = quote::quote! {
995 <#type_ident as #trait_path>::#method_ident(#(#call_args),*)
996 };
997
998 builder = builder.call_expr(call_expr);
999 }
1000
1001 if method.coerce {
1003 builder = builder.coerce_all();
1004 }
1005
1006 if method.check_interrupt {
1008 builder = builder.check_interrupt();
1009 }
1010
1011 if method.rng {
1013 builder = builder.rng();
1014 }
1015
1016 if method.strict {
1018 builder = builder.strict();
1019 }
1020
1021 builder.build().generate()
1023}
1024
1025fn output_is_result(output: &syn::ReturnType) -> bool {
1030 match output {
1031 syn::ReturnType::Type(_, ty) => matches!(
1032 ty.as_ref(),
1033 syn::Type::Path(p)
1034 if p.path
1035 .segments
1036 .last()
1037 .map(|s| s.ident == "Result")
1038 .unwrap_or(false)
1039 ),
1040 syn::ReturnType::Default => false,
1041 }
1042}
1043
1044pub(super) fn generate_trait_const_c_wrapper(
1050 trait_const: &TraitConst,
1051 type_ident: &syn::Ident,
1052 trait_name: &syn::Ident,
1053 trait_path: &syn::Path,
1054) -> TokenStream {
1055 use crate::c_wrapper_builder::{CWrapperContext, ThreadStrategy};
1056
1057 let const_ident = &trait_const.ident;
1058 let c_ident = trait_const.c_wrapper_ident(type_ident, trait_name);
1059 let call_method_def_ident = trait_const.call_method_def_ident(type_ident, trait_name);
1060 let const_ty = &trait_const.ty;
1061
1062 let r_wrappers_const = format_ident!(
1064 "R_WRAPPERS_{}_{}_IMPL",
1065 type_ident.to_string().to_uppercase(),
1066 trait_name.to_string().to_uppercase()
1067 );
1068
1069 let call_expr = quote::quote! {
1071 <#type_ident as #trait_path>::#const_ident
1072 };
1073
1074 let return_type: syn::ReturnType = syn::parse_quote!(-> #const_ty);
1077 let return_handling = crate::c_wrapper_builder::detect_return_handling(&return_type);
1078
1079 let builder = CWrapperContext::builder(const_ident.clone(), c_ident)
1081 .r_wrapper_const(r_wrappers_const)
1082 .inputs(Default::default()) .output(return_type)
1084 .call_expr(call_expr)
1085 .thread_strategy(ThreadStrategy::MainThread)
1086 .return_handling(return_handling)
1087 .type_context(type_ident.clone())
1088 .call_method_def_ident(call_method_def_ident);
1089
1090 builder.build().generate()
1091}