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 = crate::naming::vtable_static_ident(&trait_name_upper, &type_name_str);
99 let vtable_type_name = format_ident!("{}VTable", trait_name);
100
101 let mut builder_path = trait_path.clone();
105 if let Some(last) = builder_path.segments.last_mut() {
106 last.ident = format_ident!("__{}_build_vtable", trait_name_lower);
107 last.arguments = syn::PathArguments::None;
108 }
109
110 let mut vtable_type_path = trait_path.clone();
113 if let Some(last) = vtable_type_path.segments.last_mut() {
114 last.ident = vtable_type_name.clone();
115 last.arguments = syn::PathArguments::None;
116 }
117
118 let all_methods = match extract_methods(impl_item) {
120 Ok(m) => m,
121 Err(e) => return e.into_compile_error(),
122 };
123 let consts = extract_consts(impl_item);
124
125 let methods: Vec<&TraitMethod> = all_methods.iter().filter(|m| !m.skip).collect();
128
129 let method_c_wrappers: Vec<TokenStream> = methods
131 .iter()
132 .map(|m| generate_trait_method_c_wrapper(m, &type_ident, trait_name, trait_path))
133 .collect();
134
135 let const_c_wrappers: Vec<TokenStream> = consts
137 .iter()
138 .map(|c| generate_trait_const_c_wrapper(c, &type_ident, trait_name, trait_path))
139 .collect();
140
141 let c_wrappers: Vec<TokenStream> = method_c_wrappers
143 .into_iter()
144 .chain(const_c_wrappers)
145 .collect();
146
147 let raw_impl_tags = crate::roxygen::roxygen_tags_from_attrs(&impl_item.attrs);
149 let (impl_doc_tags, param_warnings) = crate::roxygen::strip_method_tags(
150 &raw_impl_tags,
151 &type_ident.to_string(),
152 crate::roxygen::next_impl_tag_block_id(),
153 impl_item.impl_token.span,
154 );
155 let class_has_no_rd = crate::roxygen::has_roxygen_tag(&impl_doc_tags, "noRd");
156
157 let methods_owned: Vec<TraitMethod> = methods.iter().map(|m| (*m).clone()).collect();
159 let r_wrapper_string = match generate_trait_r_wrapper(
160 &type_ident,
161 trait_name,
162 &methods_owned,
163 &consts,
164 TraitWrapperOpts {
165 class_system,
166 class_has_no_rd,
167 internal,
168 noexport,
169 },
170 ) {
171 Ok(s) => s,
172 Err(e) => return e.into_compile_error(),
173 };
174
175 let r_wrappers_const = format_ident!(
177 "R_WRAPPERS_{}_{}_IMPL",
178 type_ident.to_string().to_uppercase(),
179 trait_name_upper
180 );
181
182 let dispatch_entry_name = format_ident!(
184 "__MX_DISPATCH_{}_{}_FOR_{}",
185 trait_name_upper,
186 type_ident.to_string().to_uppercase(),
187 type_name_str
188 );
189
190 let mut trait_tag_path = trait_path.clone();
192 if let Some(last) = trait_tag_path.segments.last_mut() {
193 last.ident = format_ident!("TAG_{}", trait_name_upper);
194 last.arguments = syn::PathArguments::None;
195 }
196
197 let r_wrapper_str = crate::r_wrapper_raw_literal(&r_wrapper_string);
199 let source_loc_doc = crate::source_location_doc(type_ident.span());
200 let source_start = type_ident.span().start();
201 let source_line_lit = syn::LitInt::new(&source_start.line.to_string(), type_ident.span());
202 let source_col_lit =
203 syn::LitInt::new(&(source_start.column + 1).to_string(), type_ident.span());
204
205 let has_items = !all_methods.is_empty() || !consts.is_empty();
213 let clean_impl_tokens = if has_items && !blanket {
214 let mut clean_impl = impl_item.clone();
215 for item in &mut clean_impl.items {
216 if let syn::ImplItem::Fn(method) = item {
217 method
218 .attrs
219 .retain(|attr| !attr.path().is_ident("miniextendr"));
220 }
221 }
222 quote::quote! { #clean_impl }
223 } else {
224 quote::quote! {}
225 };
226
227 let vtable_static_tokens = if trait_type_args.is_empty() {
233 quote::quote! {
235 #[unsafe(no_mangle)]
236 pub static #vtable_static_name: #vtable_type_path =
237 #builder_path::<#concrete_type>();
238 }
239 } else {
240 let methods_for_vtable: Vec<TraitMethod> = methods.iter().map(|m| (*m).clone()).collect();
243 let concrete_shims = generate_concrete_vtable_shims(
244 &methods_for_vtable,
245 &type_ident,
246 trait_name,
247 trait_path,
248 concrete_type,
249 );
250 let vtable_inits: Vec<TokenStream> = methods
251 .iter()
252 .filter(|m| m.has_self)
253 .map(|m| {
254 let name = &m.ident;
255 let shim_name = crate::naming::vtshim_ident(&type_ident, trait_name, &m.ident);
256 quote::quote! { #name: #shim_name }
257 })
258 .collect();
259 quote::quote! {
260 #concrete_shims
261 #[unsafe(no_mangle)]
262 pub static #vtable_static_name: #vtable_type_path = #vtable_type_path {
263 #(#vtable_inits),*
264 };
265 }
266 };
267
268 quote::quote! {
269 #clean_impl_tokens
272
273 #param_warnings
275
276 #[doc = concat!(
277 "Vtable for `",
278 stringify!(#concrete_type),
279 "` implementing `",
280 stringify!(#trait_path),
281 "`."
282 )]
283 #[doc = "Generated by `#[miniextendr]` on the trait impl block."]
284 #[doc = #source_loc_doc]
285 #[doc = concat!("Generated from source file `", file!(), "`.")]
286 #[doc(hidden)]
287 #vtable_static_tokens
288
289 #(#c_wrappers)*
291
292 #[doc = concat!(
294 "R wrapper code for `",
295 stringify!(#type_ident),
296 "` implementing `",
297 stringify!(#trait_name),
298 "`."
299 )]
300 #[doc = #source_loc_doc]
301 #[doc = concat!("Generated from source file `", file!(), "`.")]
302 #[doc(hidden)]
303 #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_R_WRAPPERS), linkme(crate = ::miniextendr_api::linkme))]
304 static #r_wrappers_const: ::miniextendr_api::registry::RWrapperEntry =
305 ::miniextendr_api::registry::RWrapperEntry {
306 priority: ::miniextendr_api::registry::RWrapperPriority::TraitImpl,
307 source_file: file!(),
308 content: concat!(
309 "# Generated from Rust impl `",
310 stringify!(#trait_name),
311 "` for `",
312 stringify!(#type_ident),
313 "` (",
314 file!(),
315 ":",
316 #source_line_lit,
317 ":",
318 #source_col_lit,
319 ")",
320 #r_wrapper_str
321 ),
322 };
323
324 #[doc = concat!(
326 "Trait dispatch entry: `",
327 stringify!(#trait_name),
328 "` for `",
329 stringify!(#type_ident),
330 "`."
331 )]
332 #[doc = #source_loc_doc]
333 #[doc = concat!("Generated from source file `", file!(), "`.")]
334 #[doc(hidden)]
335 #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_TRAIT_DISPATCH), linkme(crate = ::miniextendr_api::linkme))]
336 static #dispatch_entry_name: ::miniextendr_api::registry::TraitDispatchEntry =
337 ::miniextendr_api::registry::TraitDispatchEntry {
338 concrete_tag: ::miniextendr_api::abi::mx_tag_from_path(
339 concat!(module_path!(), "::", stringify!(#type_ident))
340 ),
341 trait_tag: #trait_tag_path,
342 vtable: unsafe {
343 ::std::ptr::from_ref(&#vtable_static_name).cast::<::std::os::raw::c_void>()
345 },
346 vtable_symbol: stringify!(#vtable_static_name),
347 };
348 }
349}
350
351fn generate_concrete_vtable_shims(
369 methods: &[TraitMethod],
370 type_ident: &syn::Ident,
371 trait_name: &syn::Ident,
372 trait_path: &syn::Path,
373 concrete_type: &syn::Type,
374) -> TokenStream {
375 let mut shims = Vec::new();
376
377 for method in methods {
378 if !method.has_self {
379 continue; }
381
382 let method_ident = &method.ident;
383 let shim_name = crate::naming::vtshim_ident(type_ident, trait_name, method_ident);
384
385 let param_count = method
387 .sig
388 .inputs
389 .iter()
390 .filter(|a| !matches!(a, syn::FnArg::Receiver(_)))
391 .count();
392 let expected_argc = param_count as i32;
393
394 let arg_extractions: Vec<TokenStream> = method
396 .sig
397 .inputs
398 .iter()
399 .filter(|a| !matches!(a, syn::FnArg::Receiver(_)))
400 .enumerate()
401 .map(|(i, arg)| {
402 if let syn::FnArg::Typed(pt) = arg {
403 let name = if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
404 pat_ident.ident.clone()
405 } else {
406 format_ident!("arg{}", i)
407 };
408 let name_str = name.to_string();
409
410 if is_self_ref_type(&pt.ty) {
412 let extptr_name = format_ident!("__extptr_{}", name);
413 quote::quote! {
414 let #extptr_name: ::miniextendr_api::ExternalPtr<#concrete_type> = unsafe {
415 ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
416 };
417 let #name = &*#extptr_name;
418 }
419 } else {
420 let ty = &pt.ty;
421 quote::quote! {
422 let #name: #ty = unsafe {
423 ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
424 };
425 }
426 }
427 } else {
428 quote::quote! {}
429 }
430 })
431 .collect();
432
433 let param_names: Vec<syn::Ident> = method
435 .sig
436 .inputs
437 .iter()
438 .filter(|a| !matches!(a, syn::FnArg::Receiver(_)))
439 .enumerate()
440 .map(|(i, arg)| {
441 if let syn::FnArg::Typed(pt) = arg
442 && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
443 {
444 return pat_ident.ident.clone();
445 }
446 format_ident!("arg{}", i)
447 })
448 .collect();
449
450 let method_call = if method.is_mut {
454 quote::quote! {
455 let self_ref = unsafe { &mut *data.cast::<#concrete_type>() };
456 <#concrete_type as #trait_path>::#method_ident(self_ref, #(#param_names),*)
457 }
458 } else {
459 quote::quote! {
460 let self_ref = unsafe { &*data.cast::<#concrete_type>().cast_const() };
461 <#concrete_type as #trait_path>::#method_ident(self_ref, #(#param_names),*)
462 }
463 };
464
465 let has_return = match &method.sig.output {
467 syn::ReturnType::Default => false,
468 syn::ReturnType::Type(_, ty) => {
469 !matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty())
470 }
471 };
472 let result_conversion = if has_return {
473 quote::quote! {
474 unsafe { ::miniextendr_api::trait_abi::to_sexp(result) }
475 }
476 } else {
477 quote::quote! {
478 let _ = result;
479 unsafe { ::miniextendr_api::trait_abi::nil() }
480 }
481 };
482
483 let method_name_str = format!("{}::{}", trait_name, method_ident);
484
485 shims.push(quote::quote! {
486 #[doc(hidden)]
487 #[allow(non_snake_case)]
488 unsafe extern "C" fn #shim_name(
489 data: *mut ::std::os::raw::c_void,
490 argc: i32,
491 argv: *const ::miniextendr_api::SEXP,
492 ) -> ::miniextendr_api::SEXP {
493 unsafe {
494 ::miniextendr_api::trait_abi::check_arity(argc, #expected_argc, #method_name_str);
495 }
496 ::miniextendr_api::unwind_protect::with_r_unwind_protect_shim(|| {
500 #(#arg_extractions)*
501 let result = { #method_call };
502 #result_conversion
503 })
504 }
505 });
506 }
507
508 quote::quote! { #(#shims)* }
509}
510
511fn extract_methods(impl_item: &ItemImpl) -> syn::Result<Vec<TraitMethod>> {
517 let mut methods = Vec::new();
518 for item in &impl_item.items {
519 if let syn::ImplItem::Fn(method) = item {
520 let (has_self, is_mut) = method.sig.inputs.first().map_or((false, false), |arg| {
522 if let syn::FnArg::Receiver(r) = arg {
523 (true, r.mutability.is_some())
524 } else {
525 (false, false)
526 }
527 });
528 let attrs = parse_trait_method_attrs(&method.attrs)?;
529
530 let all_tags = crate::roxygen::roxygen_tags_from_attrs(&method.attrs);
532 let param_tags: Vec<String> = all_tags
533 .into_iter()
534 .filter(|tag| tag.starts_with("@param"))
535 .collect();
536
537 methods.push(TraitMethod {
538 ident: method.sig.ident.clone(),
539 sig: method.sig.clone(),
540 has_self,
541 is_mut,
542 worker: attrs.worker,
543 unsafe_main_thread: attrs.unsafe_main_thread,
544 coerce: attrs.coerce,
545 check_interrupt: attrs.check_interrupt,
546 rng: attrs.rng,
547 unwrap_in_r: attrs.unwrap_in_r,
548 param_defaults: attrs.defaults,
549 param_tags,
550 skip: attrs.skip,
551 r_name: attrs.r_name,
552 strict: attrs.strict,
553 lifecycle: attrs.lifecycle,
554 r_entry: attrs.r_entry,
555 r_post_checks: attrs.r_post_checks,
556 r_on_exit: attrs.r_on_exit,
557 no_shortcut: attrs.no_shortcut,
558 per_param: attrs.per_param,
559 });
560 }
561 }
562 Ok(methods)
563}
564
565struct TraitMethodAttrs {
570 worker: bool,
572 unsafe_main_thread: bool,
574 coerce: bool,
576 check_interrupt: bool,
578 rng: bool,
580 unwrap_in_r: bool,
582 skip: bool,
584 defaults: std::collections::HashMap<String, String>,
586 r_name: Option<String>,
588 strict: bool,
590 lifecycle: Option<crate::lifecycle::LifecycleSpec>,
592 r_entry: Option<String>,
594 r_post_checks: Option<String>,
596 r_on_exit: Option<crate::miniextendr_fn::ROnExit>,
598 no_shortcut: bool,
600 per_param: std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs>,
603}
604
605fn parse_trait_method_attrs(attrs: &[syn::Attribute]) -> syn::Result<TraitMethodAttrs> {
614 let mut worker = false;
615 let mut unsafe_main_thread = false;
616 let mut coerce = false;
617 let mut check_interrupt = false;
618 let mut rng = false;
619 let mut unwrap_in_r = false;
620 let mut skip = false;
621 let mut strict = false;
622 let mut defaults = std::collections::HashMap::new();
623 let mut r_name: Option<String> = None;
624 let mut lifecycle: Option<crate::lifecycle::LifecycleSpec> = None;
625 let mut r_entry: Option<String> = None;
626 let mut r_post_checks: Option<String> = None;
627 let mut r_on_exit: Option<crate::miniextendr_fn::ROnExit> = None;
628 let mut no_shortcut = false;
629 let mut per_param: std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs> =
630 std::collections::HashMap::new();
631
632 for attr in attrs {
633 if !attr.path().is_ident("miniextendr") {
634 continue;
635 }
636
637 attr.parse_nested_meta(|meta| {
638 let is_class_meta = meta.path.is_ident("env")
639 || meta.path.is_ident("r6")
640 || meta.path.is_ident("s7")
641 || meta.path.is_ident("s3")
642 || meta.path.is_ident("s4");
643
644 if is_class_meta {
645 meta.parse_nested_meta(|inner| {
646 if inner.path.is_ident("worker") {
647 worker = true;
648 } else if inner.path.is_ident("main_thread") {
649 unsafe_main_thread = true;
650 } else if inner.path.is_ident("coerce") {
651 coerce = true;
652 } else if inner.path.is_ident("check_interrupt") {
653 check_interrupt = true;
654 } else if inner.path.is_ident("unwrap_in_r") {
655 unwrap_in_r = true;
656 } else if inner.path.is_ident("no_shortcut") {
657 no_shortcut = true;
658 } else {
659 return Err(inner.error(
660 "unknown nested option; expected `worker`, `main_thread`, `coerce`, \
661 `check_interrupt`, `unwrap_in_r`, or `no_shortcut`",
662 ));
663 }
664 Ok(())
665 })?;
666 } else if meta.path.is_ident("worker") {
667 worker = true;
668 } else if meta.path.is_ident("main_thread") {
669 unsafe_main_thread = true;
670 } else if meta.path.is_ident("coerce") {
671 coerce = true;
672 } else if meta.path.is_ident("check_interrupt") {
673 check_interrupt = true;
674 } else if meta.path.is_ident("rng") {
675 rng = true;
676 } else if meta.path.is_ident("unwrap_in_r") {
677 unwrap_in_r = true;
678 } else if meta.path.is_ident("skip") {
679 skip = true;
680 } else if meta.path.is_ident("no_shortcut") {
681 no_shortcut = true;
682 } else if meta.path.is_ident("r_name") {
683 let value: syn::LitStr = meta.value()?.parse()?;
684 r_name = Some(value.value());
685 } else if meta.path.is_ident("strict") {
686 strict = true;
687 } else if meta.path.is_ident("defaults") {
688 meta.parse_nested_meta(|inner| {
690 let param_name = inner
691 .path
692 .get_ident()
693 .map(|i| i.to_string())
694 .unwrap_or_default();
695 let value: syn::LitStr = inner.value()?.parse()?;
696 defaults.insert(param_name, value.value());
697 Ok(())
698 })?;
699 } else if meta.path.is_ident("lifecycle") {
700 if meta.input.peek(syn::Token![=]) {
701 let _: syn::Token![=] = meta.input.parse()?;
703 let value: syn::LitStr = meta.input.parse()?;
704 let stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
705 .ok_or_else(|| {
706 syn::Error::new(
707 value.span(),
708 "invalid lifecycle stage; expected one of: experimental, stable, superseded, soft-deprecated, deprecated, defunct",
709 )
710 })?;
711 lifecycle = Some(crate::lifecycle::LifecycleSpec::new(stage));
712 } else {
713 let mut spec = crate::lifecycle::LifecycleSpec::default();
715 meta.parse_nested_meta(|inner| {
716 let key = inner.path.get_ident()
717 .ok_or_else(|| inner.error("expected identifier"))?
718 .to_string();
719 let _: syn::Token![=] = inner.input.parse()?;
720 let value: syn::LitStr = inner.input.parse()?;
721 match key.as_str() {
722 "stage" => {
723 spec.stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
724 .ok_or_else(|| syn::Error::new(value.span(), "invalid lifecycle stage"))?;
725 }
726 "when" => spec.when = Some(value.value()),
727 "what" => spec.what = Some(value.value()),
728 "with" => spec.with = Some(value.value()),
729 "details" => spec.details = Some(value.value()),
730 "id" => spec.id = Some(value.value()),
731 _ => return Err(inner.error(
732 "unknown lifecycle option; expected: stage, when, what, with, details, id"
733 )),
734 }
735 Ok(())
736 })?;
737 lifecycle = Some(spec);
738 }
739 } else if meta.path.is_ident("r_entry") {
740 let _: syn::Token![=] = meta.input.parse()?;
741 let value: syn::LitStr = meta.input.parse()?;
742 r_entry = Some(value.value());
743 } else if meta.path.is_ident("r_post_checks") {
744 let _: syn::Token![=] = meta.input.parse()?;
745 let value: syn::LitStr = meta.input.parse()?;
746 r_post_checks = Some(value.value());
747 } else if meta.path.is_ident("r_on_exit") {
748 if meta.input.peek(syn::Token![=]) {
749 let _: syn::Token![=] = meta.input.parse()?;
751 let value: syn::LitStr = meta.input.parse()?;
752 r_on_exit = Some(crate::miniextendr_fn::ROnExit {
753 expr: value.value(),
754 add: true,
755 after: true,
756 });
757 } else {
758 let mut expr = None;
760 let mut add = true;
761 let mut after = true;
762 meta.parse_nested_meta(|inner| {
763 if inner.path.is_ident("expr") {
764 let _: syn::Token![=] = inner.input.parse()?;
765 let value: syn::LitStr = inner.input.parse()?;
766 expr = Some(value.value());
767 } else if inner.path.is_ident("add") {
768 let _: syn::Token![=] = inner.input.parse()?;
769 let value: syn::LitBool = inner.input.parse()?;
770 add = value.value;
771 } else if inner.path.is_ident("after") {
772 let _: syn::Token![=] = inner.input.parse()?;
773 let value: syn::LitBool = inner.input.parse()?;
774 after = value.value;
775 } else {
776 return Err(inner.error(
777 "unknown r_on_exit option; expected `expr`, `add`, or `after`",
778 ));
779 }
780 Ok(())
781 })?;
782 let expr = expr.ok_or_else(|| {
783 meta.error("r_on_exit(...) requires `expr = \"...\"` specifying the R expression")
784 })?;
785 r_on_exit = Some(crate::miniextendr_fn::ROnExit { expr, add, after });
786 }
787 } else if meta.path.is_ident("choices") {
788 meta.parse_nested_meta(|inner| {
804 let name = inner
805 .path
806 .get_ident()
807 .ok_or_else(|| inner.error("expected parameter name"))?
808 .to_string();
809 let _: syn::Token![=] = inner.input.parse()?;
810 let value: syn::LitStr = inner.input.parse()?;
811 let choices = crate::r_wrapper_builder::split_choice_list(&value.value());
812 per_param.entry(name).or_default().choices = Some(choices);
813 Ok(())
814 })?;
815 } else if meta.path.is_ident("choices_several_ok") {
816 meta.parse_nested_meta(|inner| {
818 let name = inner
819 .path
820 .get_ident()
821 .ok_or_else(|| inner.error("expected parameter name"))?
822 .to_string();
823 let _: syn::Token![=] = inner.input.parse()?;
824 let value: syn::LitStr = inner.input.parse()?;
825 let choices = crate::r_wrapper_builder::split_choice_list(&value.value());
826 let entry = per_param.entry(name).or_default();
827 entry.choices = Some(choices);
828 entry.several_ok = true;
829 Ok(())
830 })?;
831 } else {
832 return Err(meta.error(
833 "unknown #[miniextendr] option on trait impl method; expected one of: \
834 `env`, `r6`, `s7`, `s3`, `s4`, `worker`, `main_thread`, `coerce`, \
835 `check_interrupt`, `rng`, `unwrap_in_r`, `skip`, `no_shortcut`, `r_name`, \
836 `defaults`, `strict`, `lifecycle`, `r_entry`, `r_post_checks`, `r_on_exit`, \
837 `choices`, `choices_several_ok`",
838 ));
839 }
840 Ok(())
841 })?;
842 }
843
844 Ok(TraitMethodAttrs {
845 worker: worker || cfg!(feature = "worker-default"),
846 unsafe_main_thread,
847 coerce,
848 check_interrupt,
849 rng,
850 unwrap_in_r,
851 skip,
852 strict,
853 defaults,
854 r_name,
855 lifecycle,
856 r_entry,
857 r_post_checks,
858 r_on_exit,
859 no_shortcut,
860 per_param,
861 })
862}
863
864fn extract_consts(impl_item: &ItemImpl) -> Vec<TraitConst> {
869 impl_item
870 .items
871 .iter()
872 .filter_map(|item| {
873 if let syn::ImplItem::Const(const_item) = item {
874 Some(TraitConst {
875 ident: const_item.ident.clone(),
876 ty: const_item.ty.clone(),
877 })
878 } else {
879 None
880 }
881 })
882 .collect()
883}
884
885pub(super) fn is_self_ref_type(ty: &syn::Type) -> bool {
891 if let syn::Type::Reference(r) = ty
892 && let syn::Type::Path(tp) = r.elem.as_ref()
893 && tp.path.is_ident("Self")
894 {
895 return true;
896 }
897 false
898}
899
900pub(super) fn generate_trait_method_c_wrapper(
913 method: &TraitMethod,
914 type_ident: &syn::Ident,
915 trait_name: &syn::Ident,
916 trait_path: &syn::Path,
917) -> TokenStream {
918 use crate::c_wrapper_builder::{CWrapperContext, ReturnHandling, ThreadStrategy};
919
920 let method_ident = &method.ident;
921 let c_ident = method.c_wrapper_ident(type_ident, trait_name);
922 let call_method_def_ident = method.call_method_def_ident(type_ident, trait_name);
923
924 let thread_strategy = if method.has_self || method.unsafe_main_thread {
927 ThreadStrategy::MainThread
928 } else if method.worker {
929 ThreadStrategy::WorkerThread
930 } else {
931 ThreadStrategy::MainThread
932 };
933
934 let rust_args: Vec<syn::Ident> = method
936 .sig
937 .inputs
938 .iter()
939 .filter_map(|arg| {
940 if let syn::FnArg::Typed(pt) = arg {
941 if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
942 Some(pat_ident.ident.clone())
943 } else {
944 None
945 }
946 } else {
947 None
948 }
949 })
950 .collect();
951
952 let mut self_ref_params = std::collections::HashSet::new();
956 let filtered_inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> = method
957 .sig
958 .inputs
959 .iter()
960 .filter(|arg| !matches!(arg, syn::FnArg::Receiver(_)))
961 .map(|arg| {
962 if let syn::FnArg::Typed(pt) = arg
963 && is_self_ref_type(&pt.ty)
964 {
965 if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
967 self_ref_params.insert(pat_ident.ident.to_string());
968 }
969 let pat = &pt.pat;
971 return syn::parse_quote!(#pat: ::miniextendr_api::ExternalPtr<#type_ident>);
972 }
973 arg.clone()
974 })
975 .collect();
976
977 let call_args: Vec<proc_macro2::TokenStream> = rust_args
979 .iter()
980 .map(|arg| {
981 if self_ref_params.contains(&arg.to_string()) {
982 quote::quote! { &*#arg }
983 } else {
984 quote::quote! { #arg }
985 }
986 })
987 .collect();
988
989 let return_handling = if method.unwrap_in_r && output_is_result(&method.sig.output) {
991 ReturnHandling::IntoR
992 } else {
993 crate::c_wrapper_builder::detect_return_handling(&method.sig.output)
994 };
995
996 let r_wrappers_const = format_ident!(
998 "R_WRAPPERS_{}_{}_IMPL",
999 type_ident.to_string().to_uppercase(),
1000 trait_name.to_string().to_uppercase()
1001 );
1002
1003 let mut builder = CWrapperContext::builder(method_ident.clone(), c_ident)
1006 .r_wrapper_const(r_wrappers_const)
1007 .inputs(filtered_inputs)
1008 .output(method.sig.output.clone())
1009 .thread_strategy(thread_strategy)
1010 .return_handling(return_handling)
1011 .type_context(type_ident.clone())
1012 .call_method_def_ident(call_method_def_ident);
1013
1014 if method.has_self {
1015 let trait_method_name = format!("{}::{}()", trait_name, method_ident);
1017 let self_extraction = if method.is_mut {
1018 quote::quote! {
1019 let mut self_ptr = unsafe {
1020 ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(self_sexp)
1021 };
1022 let self_ref = self_ptr.downcast_mut::<#type_ident>()
1023 .unwrap_or_else(|| panic!(
1024 "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
1025 This can happen if you pass an object of a different type to a trait method.",
1026 #trait_method_name,
1027 stringify!(#type_ident)
1028 ));
1029 }
1030 } else {
1031 quote::quote! {
1032 let self_ptr = unsafe {
1033 ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(self_sexp)
1034 };
1035 let self_ref = self_ptr.downcast_ref::<#type_ident>()
1036 .unwrap_or_else(|| panic!(
1037 "type mismatch in {}: expected ExternalPtr<{}>, got different type. \
1038 This can happen if you pass an object of a different type to a trait method.",
1039 #trait_method_name,
1040 stringify!(#type_ident)
1041 ));
1042 }
1043 };
1044
1045 let call_expr = quote::quote! {
1049 <#type_ident as #trait_path>::#method_ident(self_ref, #(#call_args),*)
1050 };
1051
1052 builder = builder
1053 .pre_call(vec![self_extraction])
1054 .call_expr(call_expr)
1055 .has_self();
1056 } else {
1057 let call_expr = quote::quote! {
1059 <#type_ident as #trait_path>::#method_ident(#(#call_args),*)
1060 };
1061
1062 builder = builder.call_expr(call_expr);
1063 }
1064
1065 if method.coerce {
1067 builder = builder.coerce_all();
1068 }
1069
1070 if method.check_interrupt {
1072 builder = builder.check_interrupt();
1073 }
1074
1075 if method.rng {
1077 builder = builder.rng();
1078 }
1079
1080 if method.strict {
1082 builder = builder.strict();
1083 }
1084
1085 builder.build().generate()
1087}
1088
1089fn output_is_result(output: &syn::ReturnType) -> bool {
1094 match output {
1095 syn::ReturnType::Type(_, ty) => matches!(
1096 ty.as_ref(),
1097 syn::Type::Path(p)
1098 if p.path
1099 .segments
1100 .last()
1101 .map(|s| s.ident == "Result")
1102 .unwrap_or(false)
1103 ),
1104 syn::ReturnType::Default => false,
1105 }
1106}
1107
1108pub(super) fn generate_trait_const_c_wrapper(
1114 trait_const: &TraitConst,
1115 type_ident: &syn::Ident,
1116 trait_name: &syn::Ident,
1117 trait_path: &syn::Path,
1118) -> TokenStream {
1119 use crate::c_wrapper_builder::{CWrapperContext, ThreadStrategy};
1120
1121 let const_ident = &trait_const.ident;
1122 let c_ident = trait_const.c_wrapper_ident(type_ident, trait_name);
1123 let call_method_def_ident = trait_const.call_method_def_ident(type_ident, trait_name);
1124 let const_ty = &trait_const.ty;
1125
1126 let r_wrappers_const = format_ident!(
1128 "R_WRAPPERS_{}_{}_IMPL",
1129 type_ident.to_string().to_uppercase(),
1130 trait_name.to_string().to_uppercase()
1131 );
1132
1133 let call_expr = quote::quote! {
1135 <#type_ident as #trait_path>::#const_ident
1136 };
1137
1138 let return_type: syn::ReturnType = syn::parse_quote!(-> #const_ty);
1141 let return_handling = crate::c_wrapper_builder::detect_return_handling(&return_type);
1142
1143 let builder = CWrapperContext::builder(const_ident.clone(), c_ident)
1145 .r_wrapper_const(r_wrappers_const)
1146 .inputs(Default::default()) .output(return_type)
1148 .call_expr(call_expr)
1149 .thread_strategy(ThreadStrategy::MainThread)
1150 .return_handling(return_handling)
1151 .type_context(type_ident.clone())
1152 .call_method_def_ident(call_method_def_ident);
1153
1154 builder.build().generate()
1155}