Skip to main content

miniextendr_macros/miniextendr_impl_trait/
vtable.rs

1//! Vtable static generation, C wrapper generation, and method attribute parsing for trait impls.
2//!
3//! This module contains the core codegen for `#[miniextendr]` on `impl Trait for Type` blocks.
4//! It produces the vtable static constant, C-callable wrapper functions for each method and
5//! associated constant, and delegates to [`super::r_wrappers`] for R wrapper code generation.
6
7use 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
15/// Generate the vtable static, C wrappers, R wrappers, and call defs for a trait implementation.
16///
17/// This is the main entry point for trait impl codegen. For a given
18/// `impl Trait for Type { ... }` block, it produces:
19///
20/// - The cleaned original impl block (with `#[miniextendr]` attrs stripped from methods)
21/// - A `static __VTABLE_{TRAIT}_FOR_{TYPE}: {Trait}VTable` constant
22/// - C wrapper functions and `R_CallMethodDef` entries for each method and const
23/// - R wrapper code string (class-system specific)
24/// - Two `const` items: `{TYPE}_{TRAIT}_CALL_DEFS` and `R_WRAPPERS_{TYPE}_{TRAIT}_IMPL`
25///
26/// # Arguments
27///
28/// - `impl_item`: The parsed `impl Trait for Type` block
29/// - `trait_path`: Full path to the trait (e.g., `crate::Counter`)
30/// - `concrete_type`: The implementing type (e.g., `MyCounter`)
31/// - `class_system`: Which R class system (env, r6, s3, s4, s7) to generate wrappers for
32/// - `blanket`: If true, skip emitting the impl block (a blanket impl already provides it)
33/// - `internal`: If true, add `@keywords internal` to R documentation
34/// - `noexport`: If true, suppress `@export` in R documentation
35pub(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    // Extract trait name for naming
45    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    // Extract type args from trait path's last segment
51    // e.g., for `RExtend<i32>`, extract `[i32]`; for `RMakeIter<i32, IterableVecIter>`, extract `[i32, IterableVecIter]`
52    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    // Extract type identifier (for simple types)
76    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    // Extract type name for naming (simplified - handles Path types)
91    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    // Generate names
96    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    // Build path to vtable builder function
100    // If trait is `foo::Counter`, builder is `foo::__counter_build_vtable`
101    // Strip type args from the path (builder uses turbofish instead)
102    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    // Build the vtable type path (same module as trait)
109    // Strip type args (vtable type is not generic)
110    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    // Parse methods and consts from the impl block
117    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    // Separate skipped methods: skipped methods are kept in the emitted impl block
124    // but excluded from C wrappers, R wrappers, call defs, and vtable shims.
125    let methods: Vec<&TraitMethod> = all_methods.iter().filter(|m| !m.skip).collect();
126
127    // Generate C wrappers and call defs for each non-skipped method
128    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    // Generate C wrappers for consts
134    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    // Combine C wrappers
140    let c_wrappers: Vec<TokenStream> = method_c_wrappers
141        .into_iter()
142        .chain(const_c_wrappers)
143        .collect();
144
145    // Check if impl block has @noRd doc comment (strip @param from class-level tags)
146    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    // Generate R wrapper code string based on class system (only non-skipped methods)
155    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    // Generate constant name for R wrapper registration
173    let r_wrappers_const = format_ident!(
174        "R_WRAPPERS_{}_{}_IMPL",
175        type_ident.to_string().to_uppercase(),
176        trait_name_upper
177    );
178
179    // Generate trait dispatch entry name
180    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    // Build TAG path for the trait (same module as trait, e.g. counter::TAG_COUNTER)
188    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    // Format R wrapper as raw string literal
195    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    // Strip #[miniextendr(...)] attrs from methods before emitting,
203    // so they don't trigger another macro expansion.
204    //
205    // Skip emitting the impl block when:
206    // - Body is empty (no methods, no consts) — blanket impl provides it
207    // - `blanket` flag is set — a blanket impl exists, methods are only for
208    //   C wrapper signature extraction, not for actual trait implementation
209    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    // For generic traits (with type args like <i32>), generate concrete vtable shims
225    // and inline vtable construction. For non-generic traits, use the builder function.
226    //
227    // Both paths emit the static as `pub` with `#[unsafe(no_mangle)]` so that a separate
228    // compilation unit (e.g. a WASM codegen path) can reference it by name.
229    let vtable_static_tokens = if trait_type_args.is_empty() {
230        // Non-generic: use the builder function generated at the trait definition site
231        quote::quote! {
232            #[unsafe(no_mangle)]
233            pub static #vtable_static_name: #vtable_type_path =
234                #builder_path::<#concrete_type>();
235        }
236    } else {
237        // Generic: generate concrete vtable shims and inline construction
238        // Only non-skipped methods go into vtable shims
239        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        // Pass through the original impl block (with method attrs stripped)
268        // — omitted when the body is empty (blanket impl covers it)
269        #clean_impl_tokens
270
271        // Warnings for @param tags on trait impl blocks
272        #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 and call method defs for trait methods
288        #(#c_wrappers)*
289
290        // R wrapper registration via distributed slice
291        #[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        // Trait dispatch entry for universal_query
323        #[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                    // SAFETY: vtable is a static reference valid for program lifetime
342                    ::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
349/// Generate concrete vtable shims for a generic trait impl.
350///
351/// For generic traits (e.g., `RExtend<T>`), shims and the vtable builder function
352/// cannot be generated at the trait definition site because where clauses like
353/// `Vec<T>: TryFromSexp` cause recursive trait resolution overflow. Instead, we
354/// generate fully monomorphized shims at the impl site where `T` is known (e.g., `T = i32`).
355///
356/// Each instance method gets a concrete `unsafe extern "C"` shim named
357/// `__vtshim_{Type}__{Trait}__{method}` that:
358/// 1. Checks argument arity
359/// 2. Wraps everything in `with_r_unwind_protect`
360/// 3. Extracts SEXP arguments to concrete Rust types
361/// 4. Calls the method via fully-qualified syntax `<Type as Trait>::method()`
362/// 5. Converts the result back to SEXP
363///
364/// Static methods are skipped (not part of the vtable).
365fn 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; // Static methods not in vtable
377        }
378
379        let method_ident = &method.ident;
380        let shim_name = format_ident!("__vtshim_{}__{}__{}", type_ident, trait_name, method_ident);
381
382        // Count non-self parameters
383        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        // Generate argument extraction (concrete types, no generics)
392        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                    // Handle &Self params: extract ExternalPtr<ConcreteType>
408                    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        // Collect param names for the method call
431        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        // Generate method call using fully-qualified syntax to avoid ambiguity
448        // with generic trait paths like `RExtend<i32>::method()` where `<` would
449        // be parsed as a comparison operator in expression position.
450        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        // Generate result conversion
463        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                // with_r_unwind_protect_shim: returns tagged error SEXP on panic
494                // so the View method wrapper can re-panic via repanic_if_rust_error,
495                // allowing rust_* class layering (issue #345).
496                ::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
508/// Extract all methods from a trait impl block as [`TraitMethod`] structs.
509///
510/// Parses each `ImplItem::Fn` to determine receiver type, mutability,
511/// `#[miniextendr(...)]` attributes (coerce, skip, r_name, defaults, etc.),
512/// and roxygen `@param` tags from doc comments.
513fn 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            // Check receiver type
518            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            // Extract @param tags from method doc comments
528            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
560/// Parsed `#[miniextendr(...)]` attributes for a single trait method.
561///
562/// Extracted from method-level attributes to control C wrapper behavior,
563/// threading, and R wrapper generation.
564struct TraitMethodAttrs {
565    /// Dispatch to worker thread. Set by explicit `#[miniextendr(worker)]` or `default-worker` feature.
566    worker: bool,
567    /// Force execution on R's main thread (overrides default worker thread for static methods).
568    unsafe_main_thread: bool,
569    /// Enable `Rf_coerceVector` for all parameters.
570    coerce: bool,
571    /// Call `R_CheckUserInterrupt` before the method body.
572    check_interrupt: bool,
573    /// Wrap the call in `GetRNGstate`/`PutRNGstate` for reproducible random number generation.
574    rng: bool,
575    /// Return `Result<T, E>` to R without unwrapping (R wrapper receives the result variant).
576    unwrap_in_r: bool,
577    /// Exclude this method from all generated wrappers (C, R, vtable shims).
578    skip: bool,
579    /// Parameter default values: keys are parameter names, values are R expressions.
580    defaults: std::collections::HashMap<String, String>,
581    /// Override the R-facing method name.
582    r_name: Option<String>,
583    /// Strict output conversion: panic instead of lossy widening for i64/u64/isize/usize.
584    strict: bool,
585    /// Lifecycle specification for deprecation/experimental status.
586    lifecycle: Option<crate::lifecycle::LifecycleSpec>,
587    /// R code to inject at the very top of the wrapper body.
588    r_entry: Option<String>,
589    /// R code to inject after all checks, immediately before `.Call()`.
590    r_post_checks: Option<String>,
591    /// Register `on.exit()` cleanup code in the R wrapper.
592    r_on_exit: Option<crate::miniextendr_fn::ROnExit>,
593}
594
595/// Parse `#[miniextendr(...)]` attributes from a trait method.
596///
597/// Supports two syntax styles:
598/// - **Flat**: `#[miniextendr(worker, coerce, rng)]`
599/// - **Nested class-system**: `#[miniextendr(env(worker, coerce))]`
600///
601/// Both styles can coexist. The `worker` flag controls whether static methods
602/// dispatch to the worker thread (defaults to `cfg!(feature = "default-worker")`).
603fn 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                // Parse defaults(param = "value", param2 = "value2", ...)
672                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                    // lifecycle = "stage"
685                    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                    // lifecycle(stage = "deprecated", when = "0.4.0", ...)
697                    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                    // Short form: r_on_exit = "expr"
733                    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                    // Long form: r_on_exit(expr = "...", add = false, after = false)
742                    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
800/// Extract associated constant items from a trait impl block.
801///
802/// Each `const NAME: Type = value;` in the impl block becomes a [`TraitConst`]
803/// that will get its own zero-argument C wrapper for R access.
804fn 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
821/// Check if a type is `&Self` or `&mut Self`.
822///
823/// Used to detect trait method parameters that take another instance of the same type
824/// (e.g., `ROrd::cmp(&self, other: &Self)`) so we can generate `ExternalPtr<T>` extraction
825/// and dereference in the C wrapper.
826pub(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
836/// Generate a C wrapper function and `R_CallMethodDef` for a single trait method.
837///
838/// Uses [`CWrapperContext`] builder to produce:
839/// - An `extern "C"` function callable from R via `.Call()`
840/// - A `R_CallMethodDef` constant for symbol registration
841///
842/// Instance methods (`has_self`) extract the object via `ErasedExternalPtr::from_sexp`
843/// and call with fully-qualified trait syntax `<Type as Trait>::method(self_ref, ...)`.
844/// Static methods run on the worker thread when the `worker` flag is set.
845///
846/// `&Self` parameters are rewritten to `ExternalPtr<Type>` for the C wrapper,
847/// then dereferenced to `&Type` when calling the actual trait method.
848pub(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    // Thread strategy: instance methods stay on main thread (self_ref can't cross threads);
861    // static methods use worker thread only when worker=true (explicit or default-worker feature)
862    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    // Build rust argument names from the signature (excluding self receiver)
871    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    // Filter inputs to exclude the receiver (builder handles self separately with has_self())
889    // Also handle &Self params: replace with ExternalPtr<ConcreteType> so the builder
890    // can auto-extract them, and track which params need dereferencing in the call.
891    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                // Track this param for dereferencing in the call expression
902                if let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() {
903                    self_ref_params.insert(pat_ident.ident.to_string());
904                }
905                // Replace &Self with ExternalPtr<ConcreteType>
906                let pat = &pt.pat;
907                return syn::parse_quote!(#pat: ::miniextendr_api::ExternalPtr<#type_ident>);
908            }
909            arg.clone()
910        })
911        .collect();
912
913    // Build call args: &Self params use `&*param` (deref ExternalPtr), others use `param`
914    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    // Determine return handling
926    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    // Generate R wrapper const name (not actually used but needed by builder)
933    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    // Build the wrapper using the builder infrastructure
940    // Use custom call_method_def_ident to avoid collisions with inherent impl methods
941    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        // Instance method: generate self extraction and call with self_ref
952        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        // Call expression with self_ref and dereferenced &Self params
982        // Use fully-qualified syntax to avoid ambiguity with generic traits
983        // (e.g., `RExtend<i32>::method()` is ambiguous — `<` parsed as comparison)
984        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        // Static method: call directly without self
994        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    // Apply coerce_all if the method has #[miniextendr(coerce)]
1002    if method.coerce {
1003        builder = builder.coerce_all();
1004    }
1005
1006    // Apply check_interrupt if the method has #[miniextendr(check_interrupt)]
1007    if method.check_interrupt {
1008        builder = builder.check_interrupt();
1009    }
1010
1011    // Apply rng if the method has #[miniextendr(rng)]
1012    if method.rng {
1013        builder = builder.rng();
1014    }
1015
1016    // Apply strict mode for lossy type conversions
1017    if method.strict {
1018        builder = builder.strict();
1019    }
1020
1021    // The builder generates both the C wrapper and the R_CallMethodDef
1022    builder.build().generate()
1023}
1024
1025/// Returns true when the return type is syntactically `Result<_, _>`.
1026///
1027/// Used to determine whether `unwrap_in_r` mode should use `ReturnHandling::IntoR`
1028/// (passing the Result through to R) instead of the default return handling.
1029fn 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
1044/// Generate a C wrapper function and `R_CallMethodDef` for a trait associated constant.
1045///
1046/// The generated wrapper takes no arguments and returns the constant value
1047/// converted to SEXP. Uses fully-qualified syntax `<Type as Trait>::CONST`
1048/// to access the value. Always runs on the main thread.
1049pub(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    // Generate R wrapper const name
1063    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    // Build the call expression to access the const
1070    let call_expr = quote::quote! {
1071        <#type_ident as #trait_path>::#const_ident
1072    };
1073
1074    // Determine return type handling - we need to convert the const to SEXP
1075    // The return type is `-> Type` not just `Type`
1076    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    // Build wrapper - no inputs, just returns the const value
1080    let builder = CWrapperContext::builder(const_ident.clone(), c_ident)
1081        .r_wrapper_const(r_wrappers_const)
1082        .inputs(Default::default()) // no inputs
1083        .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}