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::SEXP,
489 ) -> ::miniextendr_api::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 no_shortcut: attrs.no_shortcut,
555 per_param: attrs.per_param,
556 });
557 }
558 }
559 Ok(methods)
560}
561
562struct TraitMethodAttrs {
567 worker: bool,
569 unsafe_main_thread: bool,
571 coerce: bool,
573 check_interrupt: bool,
575 rng: bool,
577 unwrap_in_r: bool,
579 skip: bool,
581 defaults: std::collections::HashMap<String, String>,
583 r_name: Option<String>,
585 strict: bool,
587 lifecycle: Option<crate::lifecycle::LifecycleSpec>,
589 r_entry: Option<String>,
591 r_post_checks: Option<String>,
593 r_on_exit: Option<crate::miniextendr_fn::ROnExit>,
595 no_shortcut: bool,
597 per_param: std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs>,
600}
601
602fn parse_trait_method_attrs(attrs: &[syn::Attribute]) -> syn::Result<TraitMethodAttrs> {
611 let mut worker = false;
612 let mut unsafe_main_thread = false;
613 let mut coerce = false;
614 let mut check_interrupt = false;
615 let mut rng = false;
616 let mut unwrap_in_r = false;
617 let mut skip = false;
618 let mut strict = false;
619 let mut defaults = std::collections::HashMap::new();
620 let mut r_name: Option<String> = None;
621 let mut lifecycle: Option<crate::lifecycle::LifecycleSpec> = None;
622 let mut r_entry: Option<String> = None;
623 let mut r_post_checks: Option<String> = None;
624 let mut r_on_exit: Option<crate::miniextendr_fn::ROnExit> = None;
625 let mut no_shortcut = false;
626 let mut per_param: std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs> =
627 std::collections::HashMap::new();
628
629 for attr in attrs {
630 if !attr.path().is_ident("miniextendr") {
631 continue;
632 }
633
634 attr.parse_nested_meta(|meta| {
635 let is_class_meta = meta.path.is_ident("env")
636 || meta.path.is_ident("r6")
637 || meta.path.is_ident("s7")
638 || meta.path.is_ident("s3")
639 || meta.path.is_ident("s4");
640
641 if is_class_meta {
642 meta.parse_nested_meta(|inner| {
643 if inner.path.is_ident("worker") {
644 worker = true;
645 } else if inner.path.is_ident("main_thread") {
646 unsafe_main_thread = true;
647 } else if inner.path.is_ident("coerce") {
648 coerce = true;
649 } else if inner.path.is_ident("check_interrupt") {
650 check_interrupt = true;
651 } else if inner.path.is_ident("unwrap_in_r") {
652 unwrap_in_r = true;
653 } else if inner.path.is_ident("no_shortcut") {
654 no_shortcut = true;
655 } else {
656 return Err(inner.error(
657 "unknown nested option; expected `worker`, `main_thread`, `coerce`, \
658 `check_interrupt`, `unwrap_in_r`, or `no_shortcut`",
659 ));
660 }
661 Ok(())
662 })?;
663 } else if meta.path.is_ident("worker") {
664 worker = true;
665 } else if meta.path.is_ident("main_thread") {
666 unsafe_main_thread = true;
667 } else if meta.path.is_ident("coerce") {
668 coerce = true;
669 } else if meta.path.is_ident("check_interrupt") {
670 check_interrupt = true;
671 } else if meta.path.is_ident("rng") {
672 rng = true;
673 } else if meta.path.is_ident("unwrap_in_r") {
674 unwrap_in_r = true;
675 } else if meta.path.is_ident("skip") {
676 skip = true;
677 } else if meta.path.is_ident("no_shortcut") {
678 no_shortcut = true;
679 } else if meta.path.is_ident("r_name") {
680 let value: syn::LitStr = meta.value()?.parse()?;
681 r_name = Some(value.value());
682 } else if meta.path.is_ident("strict") {
683 strict = true;
684 } else if meta.path.is_ident("defaults") {
685 meta.parse_nested_meta(|inner| {
687 let param_name = inner
688 .path
689 .get_ident()
690 .map(|i| i.to_string())
691 .unwrap_or_default();
692 let value: syn::LitStr = inner.value()?.parse()?;
693 defaults.insert(param_name, value.value());
694 Ok(())
695 })?;
696 } else if meta.path.is_ident("lifecycle") {
697 if meta.input.peek(syn::Token![=]) {
698 let _: syn::Token![=] = meta.input.parse()?;
700 let value: syn::LitStr = meta.input.parse()?;
701 let stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
702 .ok_or_else(|| {
703 syn::Error::new(
704 value.span(),
705 "invalid lifecycle stage; expected one of: experimental, stable, superseded, soft-deprecated, deprecated, defunct",
706 )
707 })?;
708 lifecycle = Some(crate::lifecycle::LifecycleSpec::new(stage));
709 } else {
710 let mut spec = crate::lifecycle::LifecycleSpec::default();
712 meta.parse_nested_meta(|inner| {
713 let key = inner.path.get_ident()
714 .ok_or_else(|| inner.error("expected identifier"))?
715 .to_string();
716 let _: syn::Token![=] = inner.input.parse()?;
717 let value: syn::LitStr = inner.input.parse()?;
718 match key.as_str() {
719 "stage" => {
720 spec.stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
721 .ok_or_else(|| syn::Error::new(value.span(), "invalid lifecycle stage"))?;
722 }
723 "when" => spec.when = Some(value.value()),
724 "what" => spec.what = Some(value.value()),
725 "with" => spec.with = Some(value.value()),
726 "details" => spec.details = Some(value.value()),
727 "id" => spec.id = Some(value.value()),
728 _ => return Err(inner.error(
729 "unknown lifecycle option; expected: stage, when, what, with, details, id"
730 )),
731 }
732 Ok(())
733 })?;
734 lifecycle = Some(spec);
735 }
736 } else if meta.path.is_ident("r_entry") {
737 let _: syn::Token![=] = meta.input.parse()?;
738 let value: syn::LitStr = meta.input.parse()?;
739 r_entry = Some(value.value());
740 } else if meta.path.is_ident("r_post_checks") {
741 let _: syn::Token![=] = meta.input.parse()?;
742 let value: syn::LitStr = meta.input.parse()?;
743 r_post_checks = Some(value.value());
744 } else if meta.path.is_ident("r_on_exit") {
745 if meta.input.peek(syn::Token![=]) {
746 let _: syn::Token![=] = meta.input.parse()?;
748 let value: syn::LitStr = meta.input.parse()?;
749 r_on_exit = Some(crate::miniextendr_fn::ROnExit {
750 expr: value.value(),
751 add: true,
752 after: true,
753 });
754 } else {
755 let mut expr = None;
757 let mut add = true;
758 let mut after = true;
759 meta.parse_nested_meta(|inner| {
760 if inner.path.is_ident("expr") {
761 let _: syn::Token![=] = inner.input.parse()?;
762 let value: syn::LitStr = inner.input.parse()?;
763 expr = Some(value.value());
764 } else if inner.path.is_ident("add") {
765 let _: syn::Token![=] = inner.input.parse()?;
766 let value: syn::LitBool = inner.input.parse()?;
767 add = value.value;
768 } else if inner.path.is_ident("after") {
769 let _: syn::Token![=] = inner.input.parse()?;
770 let value: syn::LitBool = inner.input.parse()?;
771 after = value.value;
772 } else {
773 return Err(inner.error(
774 "unknown r_on_exit option; expected `expr`, `add`, or `after`",
775 ));
776 }
777 Ok(())
778 })?;
779 let expr = expr.ok_or_else(|| {
780 meta.error("r_on_exit(...) requires `expr = \"...\"` specifying the R expression")
781 })?;
782 r_on_exit = Some(crate::miniextendr_fn::ROnExit { expr, add, after });
783 }
784 } else if meta.path.is_ident("choices") {
785 meta.parse_nested_meta(|inner| {
801 let name = inner
802 .path
803 .get_ident()
804 .ok_or_else(|| inner.error("expected parameter name"))?
805 .to_string();
806 let _: syn::Token![=] = inner.input.parse()?;
807 let value: syn::LitStr = inner.input.parse()?;
808 let choices = crate::r_wrapper_builder::split_choice_list(&value.value());
809 per_param.entry(name).or_default().choices = Some(choices);
810 Ok(())
811 })?;
812 } else if meta.path.is_ident("choices_several_ok") {
813 meta.parse_nested_meta(|inner| {
815 let name = inner
816 .path
817 .get_ident()
818 .ok_or_else(|| inner.error("expected parameter name"))?
819 .to_string();
820 let _: syn::Token![=] = inner.input.parse()?;
821 let value: syn::LitStr = inner.input.parse()?;
822 let choices = crate::r_wrapper_builder::split_choice_list(&value.value());
823 let entry = per_param.entry(name).or_default();
824 entry.choices = Some(choices);
825 entry.several_ok = true;
826 Ok(())
827 })?;
828 } else {
829 return Err(meta.error(
830 "unknown #[miniextendr] option on trait impl method; expected one of: \
831 `env`, `r6`, `s7`, `s3`, `s4`, `worker`, `main_thread`, `coerce`, \
832 `check_interrupt`, `rng`, `unwrap_in_r`, `skip`, `no_shortcut`, `r_name`, \
833 `defaults`, `strict`, `lifecycle`, `r_entry`, `r_post_checks`, `r_on_exit`, \
834 `choices`, `choices_several_ok`",
835 ));
836 }
837 Ok(())
838 })?;
839 }
840
841 Ok(TraitMethodAttrs {
842 worker: worker || cfg!(feature = "worker-default"),
843 unsafe_main_thread,
844 coerce,
845 check_interrupt,
846 rng,
847 unwrap_in_r,
848 skip,
849 strict,
850 defaults,
851 r_name,
852 lifecycle,
853 r_entry,
854 r_post_checks,
855 r_on_exit,
856 no_shortcut,
857 per_param,
858 })
859}
860
861fn extract_consts(impl_item: &ItemImpl) -> Vec<TraitConst> {
866 impl_item
867 .items
868 .iter()
869 .filter_map(|item| {
870 if let syn::ImplItem::Const(const_item) = item {
871 Some(TraitConst {
872 ident: const_item.ident.clone(),
873 ty: const_item.ty.clone(),
874 })
875 } else {
876 None
877 }
878 })
879 .collect()
880}
881
882pub(super) fn is_self_ref_type(ty: &syn::Type) -> bool {
888 if let syn::Type::Reference(r) = ty
889 && let syn::Type::Path(tp) = r.elem.as_ref()
890 && tp.path.is_ident("Self")
891 {
892 return true;
893 }
894 false
895}
896
897pub(super) fn generate_trait_method_c_wrapper(
910 method: &TraitMethod,
911 type_ident: &syn::Ident,
912 trait_name: &syn::Ident,
913 trait_path: &syn::Path,
914) -> TokenStream {
915 use crate::c_wrapper_builder::{CWrapperContext, ReturnHandling, ThreadStrategy};
916
917 let method_ident = &method.ident;
918 let c_ident = method.c_wrapper_ident(type_ident, trait_name);
919 let call_method_def_ident = method.call_method_def_ident(type_ident, trait_name);
920
921 let thread_strategy = if method.has_self || method.unsafe_main_thread {
924 ThreadStrategy::MainThread
925 } else if method.worker {
926 ThreadStrategy::WorkerThread
927 } else {
928 ThreadStrategy::MainThread
929 };
930
931 let rust_args: Vec<syn::Ident> = method
933 .sig
934 .inputs
935 .iter()
936 .filter_map(|arg| {
937 if let syn::FnArg::Typed(pt) = arg {
938 if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
939 Some(pat_ident.ident.clone())
940 } else {
941 None
942 }
943 } else {
944 None
945 }
946 })
947 .collect();
948
949 let mut self_ref_params = std::collections::HashSet::new();
953 let filtered_inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> = method
954 .sig
955 .inputs
956 .iter()
957 .filter(|arg| !matches!(arg, syn::FnArg::Receiver(_)))
958 .map(|arg| {
959 if let syn::FnArg::Typed(pt) = arg
960 && is_self_ref_type(&pt.ty)
961 {
962 if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
964 self_ref_params.insert(pat_ident.ident.to_string());
965 }
966 let pat = &pt.pat;
968 return syn::parse_quote!(#pat: ::miniextendr_api::ExternalPtr<#type_ident>);
969 }
970 arg.clone()
971 })
972 .collect();
973
974 let call_args: Vec<proc_macro2::TokenStream> = rust_args
976 .iter()
977 .map(|arg| {
978 if self_ref_params.contains(&arg.to_string()) {
979 quote::quote! { &*#arg }
980 } else {
981 quote::quote! { #arg }
982 }
983 })
984 .collect();
985
986 let return_handling = if method.unwrap_in_r && output_is_result(&method.sig.output) {
988 ReturnHandling::IntoR
989 } else {
990 crate::c_wrapper_builder::detect_return_handling(&method.sig.output)
991 };
992
993 let r_wrappers_const = format_ident!(
995 "R_WRAPPERS_{}_{}_IMPL",
996 type_ident.to_string().to_uppercase(),
997 trait_name.to_string().to_uppercase()
998 );
999
1000 let mut builder = CWrapperContext::builder(method_ident.clone(), c_ident)
1003 .r_wrapper_const(r_wrappers_const)
1004 .inputs(filtered_inputs)
1005 .output(method.sig.output.clone())
1006 .thread_strategy(thread_strategy)
1007 .return_handling(return_handling)
1008 .type_context(type_ident.clone())
1009 .call_method_def_ident(call_method_def_ident);
1010
1011 if method.has_self {
1012 let trait_method_name = format!("{}::{}()", trait_name, method_ident);
1014 let self_extraction = if method.is_mut {
1015 quote::quote! {
1016 let mut self_ptr = unsafe {
1017 ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(self_sexp)
1018 };
1019 let self_ref = self_ptr.downcast_mut::<#type_ident>()
1020 .unwrap_or_else(|| panic!(
1021 "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
1022 This can happen if you pass an object of a different type to a trait method.",
1023 #trait_method_name,
1024 stringify!(#type_ident)
1025 ));
1026 }
1027 } else {
1028 quote::quote! {
1029 let self_ptr = unsafe {
1030 ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(self_sexp)
1031 };
1032 let self_ref = self_ptr.downcast_ref::<#type_ident>()
1033 .unwrap_or_else(|| panic!(
1034 "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
1035 This can happen if you pass an object of a different type to a trait method.",
1036 #trait_method_name,
1037 stringify!(#type_ident)
1038 ));
1039 }
1040 };
1041
1042 let call_expr = quote::quote! {
1046 <#type_ident as #trait_path>::#method_ident(self_ref, #(#call_args),*)
1047 };
1048
1049 builder = builder
1050 .pre_call(vec![self_extraction])
1051 .call_expr(call_expr)
1052 .has_self();
1053 } else {
1054 let call_expr = quote::quote! {
1056 <#type_ident as #trait_path>::#method_ident(#(#call_args),*)
1057 };
1058
1059 builder = builder.call_expr(call_expr);
1060 }
1061
1062 if method.coerce {
1064 builder = builder.coerce_all();
1065 }
1066
1067 if method.check_interrupt {
1069 builder = builder.check_interrupt();
1070 }
1071
1072 if method.rng {
1074 builder = builder.rng();
1075 }
1076
1077 if method.strict {
1079 builder = builder.strict();
1080 }
1081
1082 builder.build().generate()
1084}
1085
1086fn output_is_result(output: &syn::ReturnType) -> bool {
1091 match output {
1092 syn::ReturnType::Type(_, ty) => matches!(
1093 ty.as_ref(),
1094 syn::Type::Path(p)
1095 if p.path
1096 .segments
1097 .last()
1098 .map(|s| s.ident == "Result")
1099 .unwrap_or(false)
1100 ),
1101 syn::ReturnType::Default => false,
1102 }
1103}
1104
1105pub(super) fn generate_trait_const_c_wrapper(
1111 trait_const: &TraitConst,
1112 type_ident: &syn::Ident,
1113 trait_name: &syn::Ident,
1114 trait_path: &syn::Path,
1115) -> TokenStream {
1116 use crate::c_wrapper_builder::{CWrapperContext, ThreadStrategy};
1117
1118 let const_ident = &trait_const.ident;
1119 let c_ident = trait_const.c_wrapper_ident(type_ident, trait_name);
1120 let call_method_def_ident = trait_const.call_method_def_ident(type_ident, trait_name);
1121 let const_ty = &trait_const.ty;
1122
1123 let r_wrappers_const = format_ident!(
1125 "R_WRAPPERS_{}_{}_IMPL",
1126 type_ident.to_string().to_uppercase(),
1127 trait_name.to_string().to_uppercase()
1128 );
1129
1130 let call_expr = quote::quote! {
1132 <#type_ident as #trait_path>::#const_ident
1133 };
1134
1135 let return_type: syn::ReturnType = syn::parse_quote!(-> #const_ty);
1138 let return_handling = crate::c_wrapper_builder::detect_return_handling(&return_type);
1139
1140 let builder = CWrapperContext::builder(const_ident.clone(), c_ident)
1142 .r_wrapper_const(r_wrappers_const)
1143 .inputs(Default::default()) .output(return_type)
1145 .call_expr(call_expr)
1146 .thread_strategy(ThreadStrategy::MainThread)
1147 .return_handling(return_handling)
1148 .type_context(type_ident.clone())
1149 .call_method_def_ident(call_method_def_ident);
1150
1151 builder.build().generate()
1152}