Skip to main content

miniextendr_macros/
miniextendr_trait.rs

1//! # Trait Support for `#[miniextendr]`
2//!
3//! This module handles `#[miniextendr]` applied to trait definitions,
4//! generating the ABI infrastructure for cross-package trait dispatch.
5//!
6//! ## Overview
7//!
8//! When `#[miniextendr]` is applied to a trait, it generates:
9//!
10//! 1. **Type tag constant** (`TAG_<TraitName>`) - 128-bit identifier for runtime type checking
11//! 2. **Vtable struct** (`<TraitName>VTable`) - Function pointer table for method dispatch
12//! 3. **View struct** (`<TraitName>View`) - Runtime wrapper combining data pointer and vtable
13//! 4. **Method shims** - `extern "C"` functions that convert SEXP arguments and call methods
14//! 5. **Vtable builder** - `__<trait>_build_vtable::<T>()` for impl blocks
15//!
16//! ## Usage
17//!
18//! ```ignore
19//! #[miniextendr]
20//! pub trait Counter {
21//!     fn value(&self) -> i32;
22//!     fn increment(&mut self);
23//!     fn add(&mut self, n: i32);
24//! }
25//! ```
26//!
27//! Generates (conceptually):
28//!
29//! ```text
30//! // Original trait (passed through)
31//! pub trait Counter {
32//!     fn value(&self) -> i32;
33//!     fn increment(&mut self);
34//!     fn add(&mut self, n: i32);
35//! }
36//!
37//! // Type tag for runtime identification
38//! pub const TAG_COUNTER: mx_tag = mx_tag::new(0x..., 0x...);
39//!
40//! // Vtable with one entry per method
41//! #[repr(C)]
42//! pub struct CounterVTable {
43//!     pub value: mx_meth,
44//!     pub increment: mx_meth,
45//!     pub add: mx_meth,
46//! }
47//!
48//! // View combining data pointer and vtable
49//! #[repr(C)]
50//! pub struct CounterView {
51//!     pub data: *mut std::ffi::c_void,
52//!     pub vtable: *const CounterVTable,
53//! }
54//!
55//! // Shim for each method
56//! unsafe extern "C" fn __counter_value_shim<T: Counter>(
57//!     data: *mut c_void, argc: i32, argv: *const SEXP
58//! ) -> SEXP {
59//!     // 1. Check arity
60//!     // 2. Cast data to &T
61//!     // 3. Call method
62//!     // 4. Convert result to SEXP
63//!     // 5. Catch panics
64//! }
65//!
66//! // Builder to create vtable for a concrete type
67//! pub const fn __counter_build_vtable<T: Counter>() -> CounterVTable {
68//!     CounterVTable {
69//!         value: __counter_value_shim::<T>,
70//!         increment: __counter_increment_shim::<T>,
71//!         add: __counter_add_shim::<T>,
72//!     }
73//! }
74//! ```
75//!
76//! ## Supported Method Signatures
77//!
78//! Methods must follow these constraints:
79//!
80//! - **Receiver**: `&self` or `&mut self` for instance methods, or none for static methods
81//! - **Arguments**: Types that implement `TryFromSexp`
82//! - **Return**: Types that implement `IntoR`, or `()`
83//! - **No generics**: Methods cannot have generic type parameters
84//! - **No async**: Async methods are not supported
85//! - **Static methods**: Methods without a receiver are allowed and resolved at compile time
86//!   (they don't go through the vtable)
87//!
88//! ## Default Methods
89//!
90//! Default method implementations are supported. The vtable builder will
91//! use the default implementation if the concrete type doesn't override it.
92//!
93//! ```ignore
94//! #[miniextendr]
95//! pub trait Counter {
96//!     fn value(&self) -> i32;
97//!
98//!     // Default implementation - included in vtable
99//!     fn is_zero(&self) -> bool {
100//!         self.value() == 0
101//!     }
102//! }
103//! ```
104//!
105//! ## Error Handling
106//!
107//! Method shims handle errors as follows:
108//!
109//! - **Arity mismatch**: Raises R error ("expected N arguments, got M")
110//! - **Type conversion failure**: Raises R error with the error message
111//! - **Panic**: Caught via `with_r_unwind_protect`, converted to R error
112//!
113//! ## Thread Safety
114//!
115//! All generated shims are **main-thread only**. They do not route through
116//! `with_r_thread` because R invokes `.Call` on the main thread.
117
118use proc_macro2::TokenStream;
119use syn::ItemTrait;
120
121/// Expand `#[miniextendr]` applied to a trait definition.
122///
123/// # Arguments
124///
125/// * `attr` - Attribute arguments (currently unused, reserved for future options)
126/// * `item` - The trait definition token stream
127///
128/// # Returns
129///
130/// Expanded token stream containing:
131/// - Original trait definition
132/// - Type tag constant
133/// - Vtable struct
134/// - View struct
135/// - Method shims
136/// - Vtable builder function
137///
138/// # Errors
139///
140/// Returns a compile error if:
141/// - Methods have unsupported signatures
142/// - Methods are async
143pub fn expand_trait(
144    _attr: proc_macro::TokenStream,
145    item: proc_macro::TokenStream,
146) -> proc_macro::TokenStream {
147    let trait_item = syn::parse_macro_input!(item as ItemTrait);
148
149    // Validate trait constraints
150    if let Err(e) = validate_trait(&trait_item) {
151        return e.into_compile_error().into();
152    }
153
154    // Generate the expanded code
155    let expanded = generate_trait_abi(&trait_item);
156
157    expanded.into()
158}
159
160/// Validate that the trait meets requirements for ABI generation.
161///
162/// # Constraints
163///
164/// - All methods must have `&self` or `&mut self` receiver
165/// - Methods cannot be async
166/// - Methods cannot have generic parameters
167/// - Generic type parameters on the trait itself are allowed
168fn validate_trait(trait_item: &ItemTrait) -> syn::Result<()> {
169    let trait_name = &trait_item.ident;
170
171    // Validate each method
172    for item in &trait_item.items {
173        if let syn::TraitItem::Fn(method) = item {
174            validate_method(method, trait_name)?;
175        }
176    }
177
178    Ok(())
179}
180
181/// Validate a single trait method for ABI compatibility.
182///
183/// Rejects async methods, methods with generic type parameters, and methods
184/// that take `self` by value (only `&self` and `&mut self` are allowed).
185/// Static methods (no receiver) are permitted.
186fn validate_method(method: &syn::TraitItemFn, trait_name: &syn::Ident) -> syn::Result<()> {
187    let method_name = &method.sig.ident;
188
189    // Check for async
190    if method.sig.asyncness.is_some() {
191        return Err(syn::Error::new_spanned(
192            method.sig.asyncness,
193            format!(
194                "#[miniextendr] trait `{}::{}` cannot be async",
195                trait_name, method_name
196            ),
197        ));
198    }
199
200    // Check for type/const generics on method.
201    // Lifetime params are allowed (erased at codegen); type/const params would require
202    // monomorphization and are incompatible with the vtable-shim codegen path.
203    {
204        let has_type_or_const = method
205            .sig
206            .generics
207            .params
208            .iter()
209            .any(|p| matches!(p, syn::GenericParam::Type(_) | syn::GenericParam::Const(_)));
210        if has_type_or_const {
211            return Err(syn::Error::new_spanned(
212                &method.sig.generics,
213                format!(
214                    "#[miniextendr] trait method `{}::{}` cannot have generic type or const parameters",
215                    trait_name, method_name
216                ),
217            ));
218        }
219    }
220
221    // Check receiver - must be &self, &mut self, self: &Self, self: &mut Self, or no receiver
222    // Static methods are allowed but won't be included in the vtable
223    // (they're resolved at compile time via <Type as Trait>::method())
224    let receiver = method.sig.inputs.first();
225    if let Some(syn::FnArg::Receiver(r)) = receiver {
226        // Accept either:
227        // - `&self` / `&mut self` (r.reference is Some)
228        // - `self: &Self` / `self: &mut Self` (r.colon_token is Some with reference type)
229        let is_ref = if r.reference.is_some() {
230            true
231        } else if r.colon_token.is_some() {
232            // Check if the type is a reference type (&Self or &mut Self)
233            matches!(r.ty.as_ref(), syn::Type::Reference(_))
234        } else {
235            false
236        };
237
238        if !is_ref {
239            return Err(syn::Error::new_spanned(
240                r,
241                format!(
242                    "#[miniextendr] trait method `{}::{}` receiver must be `&self` or `&mut self`, not `self` by value",
243                    trait_name, method_name
244                ),
245            ));
246        }
247    }
248    // If receiver is None or FnArg::Typed (no self), it's a static method - allowed
249
250    Ok(())
251}
252
253/// Generate the ABI infrastructure for a trait.
254///
255/// This is the main code generation function that produces:
256/// - Type tag constant
257/// - Vtable struct
258/// - View struct (skipped for generic traits)
259/// - Method shims (with trait type params threaded through)
260/// - Vtable builder (with trait type params threaded through)
261fn generate_trait_abi(trait_item: &ItemTrait) -> TokenStream {
262    let trait_name = &trait_item.ident;
263    let vis = &trait_item.vis;
264
265    // Generate names for generated items
266    let tag_name = quote::format_ident!("TAG_{}", trait_name.to_string().to_uppercase());
267    let vtable_name = quote::format_ident!("{}VTable", trait_name);
268    let view_name = quote::format_ident!("{}View", trait_name);
269    let build_vtable_fn =
270        quote::format_ident!("__{}_build_vtable", trait_name.to_string().to_lowercase());
271
272    // Collect trait-level generic type parameters
273    let trait_type_params: Vec<&syn::GenericParam> = trait_item.generics.params.iter().collect();
274    let trait_param_idents: Vec<&syn::Ident> = trait_type_params
275        .iter()
276        .filter_map(|p| {
277            if let syn::GenericParam::Type(tp) = p {
278                Some(&tp.ident)
279            } else {
280                None
281            }
282        })
283        .collect();
284    let has_generics = !trait_param_idents.is_empty();
285
286    // Collect associated types
287    let assoc_types: Vec<&syn::Ident> = trait_item
288        .items
289        .iter()
290        .filter_map(|item| {
291            if let syn::TraitItem::Type(t) = item {
292                Some(&t.ident)
293            } else {
294                None
295            }
296        })
297        .collect();
298
299    // Collect trait where clause
300    let trait_where_clause = &trait_item.generics.where_clause;
301
302    // Collect method information
303    // Filter to only include instance methods (with &self or &mut self) that aren't skipped
304    let methods: Vec<_> = {
305        let mut collected = Vec::new();
306        for item in &trait_item.items {
307            if let syn::TraitItem::Fn(method) = item {
308                let info = match extract_method_info(method) {
309                    Ok(info) => info,
310                    Err(e) => return e.into_compile_error(),
311                };
312                if info.has_self && !info.skip {
313                    collected.push(info);
314                }
315            }
316        }
317        collected
318    }
319    .into_iter()
320    .collect();
321
322    // Generate tag path string for hashing
323    // IMPORTANT: For cross-package trait dispatch, the tag must NOT include module_path!()
324    // Different packages defining the same trait signature should get the same tag.
325    // We use just the trait name - in practice, trait names + methods should be unique enough.
326    let tag_path = trait_name.to_string();
327
328    // Generate vtable fields
329    let vtable_fields: Vec<_> = methods
330        .iter()
331        .map(|m| {
332            let name = &m.name;
333            quote::quote! {
334                pub #name: ::miniextendr_api::abi::mx_meth
335            }
336        })
337        .collect();
338
339    // Compute extra bounds needed for shims
340    let extra_bounds =
341        compute_extra_bounds(&methods, trait_name, &assoc_types, &trait_param_idents);
342
343    // Build trait bound for __ImplT
344    let impl_t = quote::format_ident!("__ImplT");
345    let trait_bound = if trait_param_idents.is_empty() {
346        quote::quote! { #trait_name }
347    } else {
348        quote::quote! { #trait_name<#(#trait_param_idents),*> }
349    };
350
351    // Build combined where clause
352    let all_where_predicates = build_where_predicates(trait_where_clause, &extra_bounds);
353    let where_clause = if all_where_predicates.is_empty() {
354        quote::quote! {}
355    } else {
356        quote::quote! { where #(#all_where_predicates),* }
357    };
358
359    // Generate shim functions
360    let shim_fns: Vec<_> = methods
361        .iter()
362        .map(|m| {
363            generate_method_shim(
364                trait_name,
365                m,
366                &extra_bounds,
367                &trait_param_idents,
368                &trait_type_params,
369                trait_where_clause,
370            )
371        })
372        .collect();
373
374    // Generate vtable field initializers (turbofish includes trait type params)
375    let vtable_inits: Vec<_> = methods
376        .iter()
377        .map(|m| {
378            let name = &m.name;
379            let shim_name =
380                quote::format_ident!("__{}_{}_shim", trait_name.to_string().to_lowercase(), name);
381            quote::quote! {
382                #name: #shim_name::<#(#trait_param_idents,)* #impl_t>
383            }
384        })
385        .collect();
386
387    // Generate method wrappers for the View struct
388    // Skip entirely for generic traits (type-erased view can't know type params)
389    let view_methods: Vec<_> = if has_generics {
390        vec![]
391    } else {
392        methods.iter().filter_map(generate_view_method).collect()
393    };
394
395    // Strip #[miniextendr(...)] attrs from trait items before emitting
396    let mut clean_trait = trait_item.clone();
397    for item in &mut clean_trait.items {
398        if let syn::TraitItem::Fn(method) = item {
399            method
400                .attrs
401                .retain(|attr| !attr.path().is_ident("miniextendr"));
402        }
403    }
404
405    let trait_name_str = trait_name.to_string();
406    let source_loc_doc = crate::source_location_doc(trait_name.span());
407
408    let impl_bounds = &extra_bounds.impl_bounds;
409
410    // View struct and its impls (skipped for generic traits)
411    let view_tokens = if has_generics {
412        quote::quote! {}
413    } else {
414        quote::quote! {
415            #[doc = concat!(
416                "Runtime view for objects implementing `",
417                stringify!(#trait_name),
418                "`."
419            )]
420            #[doc = #source_loc_doc]
421            #[doc = concat!("Generated from source file `", file!(), "`.")]
422            ///
423            /// Combines a data pointer with a vtable pointer for method dispatch.
424            /// Use `try_from_sexp` to create a view from an R external pointer.
425            #[repr(C)]
426            #vis struct #view_name {
427                /// Pointer to the concrete object data.
428                pub data: *mut ::std::os::raw::c_void,
429                /// Pointer to the vtable for this trait.
430                pub vtable: *const #vtable_name,
431            }
432
433            // TraitView implementation
434            impl ::miniextendr_api::TraitView for #view_name {
435                const TAG: ::miniextendr_api::abi::mx_tag = #tag_name;
436
437                #[inline]
438                unsafe fn from_raw_parts(
439                    data: *mut ::std::os::raw::c_void,
440                    vtable: *const ::std::os::raw::c_void,
441                ) -> Self {
442                    Self {
443                        data,
444                        vtable: vtable.cast::<#vtable_name>(),
445                    }
446                }
447            }
448
449            // Method wrappers on View
450            impl #view_name {
451                /// Try to create a view from an R SEXP.
452                ///
453                /// Returns `Some(Self)` if the object implements this trait,
454                /// `None` otherwise.
455                ///
456                /// # Safety
457                ///
458                /// - `sexp` must be a valid R external pointer (EXTPTRSXP)
459                /// - Must be called on R's main thread
460                #[inline]
461                pub unsafe fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> Option<Self> {
462                    <Self as ::miniextendr_api::TraitView>::try_from_sexp(sexp)
463                }
464
465                /// Try to create a view, panicking with error message on failure.
466                ///
467                /// # Safety
468                ///
469                /// Same as `try_from_sexp`.
470                #[inline]
471                pub unsafe fn from_sexp(sexp: ::miniextendr_api::SEXP) -> Self {
472                    Self::try_from_sexp(sexp)
473                        .expect(concat!("Object does not implement ", #trait_name_str, " trait"))
474                }
475
476                #(#view_methods)*
477            }
478        }
479    };
480
481    // For generic traits (with type params like <T>), skip shim and builder generation.
482    // These are generated at the impl site with concrete types to avoid recursive trait
483    // resolution overflow (e.g., `Vec<T>: TryFromSexp` triggers infinite recursion through
484    // `impl<T> TryFromSexp for Vec<Vec<T>>`).
485    let shim_and_builder = if has_generics {
486        quote::quote! {}
487    } else {
488        quote::quote! {
489            // Method shims
490            #(#shim_fns)*
491
492            #[doc = concat!(
493                "Build a vtable for a concrete type implementing `",
494                stringify!(#trait_name),
495                "`."
496            )]
497            #[doc = #source_loc_doc]
498            #[doc = concat!("Generated from source file `", file!(), "`.")]
499            #vis const fn #build_vtable_fn<#(#trait_type_params,)* #impl_t: #trait_bound #(+ #impl_bounds)*>() -> #vtable_name
500            #where_clause
501            {
502                #vtable_name {
503                    #(#vtable_inits),*
504                }
505            }
506        }
507    };
508
509    // TPIE: Generate macro_rules! for non-generic traits without associated types.
510    // This enables `#[miniextendr] impl Trait for Type {}` (empty body) to auto-expand wrappers.
511    let tpie_macro = if !has_generics && assoc_types.is_empty() {
512        // Collect ALL non-skipped methods (including static) for TPIE metadata
513        let tpie_method_metadata: Vec<TokenStream> = {
514            let mut collected = Vec::new();
515            for item in &trait_item.items {
516                if let syn::TraitItem::Fn(method) = item {
517                    let info = match extract_method_info(method) {
518                        Ok(info) => info,
519                        Err(e) => return e.into_compile_error(),
520                    };
521                    if !info.skip {
522                        let r_name_ident = if let Some(ref rn) = info.r_name {
523                            quote::format_ident!("{}", rn)
524                        } else {
525                            method.sig.ident.clone()
526                        };
527                        let sig = &method.sig;
528                        collected.push(quote::quote! {
529                            method { r_name = #r_name_ident; #sig; }
530                        });
531                    }
532                }
533            }
534            collected
535        };
536
537        let tpie_macro_name = quote::format_ident!("__mx_impl_{}", trait_name);
538        quote::quote! {
539            #[macro_export]
540            #[doc(hidden)]
541            macro_rules! #tpie_macro_name {
542                ($concrete_type:ty, $trait_path:path, $class_system:ident, $no_rd:tt, $internal:tt, $noexport:tt) => {
543                    $crate::__mx_trait_impl_expand! {
544                        concrete_type = $concrete_type;
545                        trait_path = $trait_path;
546                        class_system = $class_system;
547                        no_rd = $no_rd;
548                        internal = $internal;
549                        noexport = $noexport;
550                        #(#tpie_method_metadata)*
551                    }
552                };
553            }
554        }
555    } else {
556        quote::quote! {}
557    };
558
559    quote::quote! {
560        // Pass through the original trait (with #[miniextendr] attrs stripped from items)
561        #clean_trait
562
563        #[doc = concat!(
564            "Type tag for runtime identification of the `",
565            stringify!(#trait_name),
566            "` trait."
567        )]
568        #[doc = #source_loc_doc]
569        #[doc = concat!("Generated from source file `", file!(), "`.")]
570        #vis const #tag_name: ::miniextendr_api::abi::mx_tag =
571            ::miniextendr_api::abi::mx_tag_from_path(#tag_path);
572
573        #[doc = concat!("Vtable for the `", stringify!(#trait_name), "` trait.")]
574        #[doc = #source_loc_doc]
575        #[doc = concat!("Generated from source file `", file!(), "`.")]
576        ///
577        /// Contains one `mx_meth` function pointer per trait method.
578        #[repr(C)]
579        #[doc(hidden)]
580        #vis struct #vtable_name {
581            #(#vtable_fields),*
582        }
583
584        #view_tokens
585
586        #shim_and_builder
587
588        #tpie_macro
589    }
590}
591
592/// Generate a method wrapper for the View struct.
593///
594/// This creates a method on the View that calls through the vtable.
595/// Returns None for methods with `Self` in return types or `&Self` in parameters,
596/// since these can't be meaningfully expressed on the type-erased View.
597fn generate_view_method(method: &MethodInfo) -> Option<TokenStream> {
598    // Skip methods where Self appears in return type or parameters.
599    // In the View context, Self refers to the View struct, not the concrete type,
600    // so these methods can't work through the type-erased vtable dispatch.
601    if method.return_type.as_ref().is_some_and(type_contains_self) {
602        return None;
603    }
604    if method.param_types.iter().any(type_contains_self) {
605        return None;
606    }
607
608    let method_name = &method.name;
609    let param_names = &method.param_names;
610    let param_types = &method.param_types;
611
612    // Generate function parameters
613    let params: Vec<_> = param_names
614        .iter()
615        .zip(param_types.iter())
616        .map(|(name, ty)| {
617            quote::quote! { #name: #ty }
618        })
619        .collect();
620
621    // Generate self receiver
622    let self_param = if method.is_mut {
623        quote::quote! { &mut self }
624    } else {
625        quote::quote! { &self }
626    };
627
628    // Generate argument array for vtable call
629    let argc = param_types.len() as i32;
630    let arg_conversions: Vec<_> = param_names
631        .iter()
632        .map(|name| {
633            quote::quote! {
634                ::miniextendr_api::trait_abi::to_sexp(#name)
635            }
636        })
637        .collect();
638
639    // Generate vtable call
640    let vtable_call = if argc > 0 {
641        quote::quote! {
642            let args: [::miniextendr_api::SEXP; #argc as usize] = [#(#arg_conversions),*];
643            ((*self.vtable).#method_name)(self.data, #argc, args.as_ptr())
644        }
645    } else {
646        quote::quote! {
647            ((*self.vtable).#method_name)(self.data, 0, ::std::ptr::null())
648        }
649    };
650
651    // Generate return type handling
652    let return_type = &method.return_type;
653    let (return_sig, result_conversion) = if let Some(ret_ty) = return_type {
654        (
655            quote::quote! { -> #ret_ty },
656            quote::quote! {
657                ::miniextendr_api::trait_abi::from_sexp::<#ret_ty>(result)
658            },
659        )
660    } else {
661        (
662            quote::quote! {},
663            quote::quote! {
664                let _ = result;
665            },
666        )
667    };
668
669    Some(quote::quote! {
670        #[doc = concat!("Call `", stringify!(#method_name), "` through the vtable.")]
671        #[inline]
672        pub fn #method_name(#self_param #(, #params)*) #return_sig {
673            unsafe {
674                let result = { #vtable_call };
675                // Approach 1 (issue #345): if the shim returned a tagged error SEXP,
676                // re-panic with the reconstructed RCondition so the consumer's outer
677                // `with_r_unwind_protect` guard can apply rust_* class layering.
678                ::miniextendr_api::trait_abi::repanic_if_rust_error(result);
679                #result_conversion
680            }
681        }
682    })
683}
684
685/// Generate a method shim function for a trait method.
686///
687/// The shim is an `extern "C"` function that:
688/// 1. Checks argument arity
689/// 2. Wraps everything in `with_r_unwind_protect` to prevent unwinding across FFI
690/// 3. Converts SEXP arguments to Rust types
691/// 4. Calls the actual method on the concrete type
692/// 5. Converts the result back to SEXP
693/// 6. On panic, converts to R error via `with_r_unwind_protect`
694///
695/// For generic traits, the shim carries the trait's type parameters plus `__ImplT`.
696fn generate_method_shim(
697    trait_name: &syn::Ident,
698    method: &MethodInfo,
699    extra_bounds: &ExtraBounds,
700    trait_param_idents: &[&syn::Ident],
701    trait_type_params: &[&syn::GenericParam],
702    trait_where_clause: &Option<syn::WhereClause>,
703) -> TokenStream {
704    let method_name = &method.name;
705    let shim_name = quote::format_ident!(
706        "__{}_{}_shim",
707        trait_name.to_string().to_lowercase(),
708        method_name
709    );
710    let impl_t = quote::format_ident!("__ImplT");
711
712    let param_count = method.param_types.len();
713    let expected_argc = param_count as i32;
714
715    // Generate argument extraction
716    // For &Self params, extract ExternalPtr<__ImplT> and borrow from it
717    let arg_extractions: Vec<_> = method
718        .param_names
719        .iter()
720        .zip(method.param_types.iter())
721        .enumerate()
722        .map(|(i, (name, ty))| {
723            let name_str = name.to_string();
724            let (is_self_ref, is_mut) = param_is_self_ref(ty);
725            if is_self_ref {
726                let extptr_name = quote::format_ident!("__extptr_{}", name);
727                if is_mut {
728                    quote::quote! {
729                        let mut #extptr_name: ::miniextendr_api::ExternalPtr<#impl_t> = unsafe {
730                            ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
731                        };
732                        let #name: &mut #impl_t = &mut *#extptr_name;
733                    }
734                } else {
735                    quote::quote! {
736                        let #extptr_name: ::miniextendr_api::ExternalPtr<#impl_t> = unsafe {
737                            ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
738                        };
739                        let #name: &#impl_t = &*#extptr_name;
740                    }
741                }
742            } else {
743                quote::quote! {
744                    let #name: #ty = unsafe {
745                        ::miniextendr_api::trait_abi::extract_arg(argc, argv, #i, #name_str)
746                    };
747                }
748            }
749        })
750        .collect();
751
752    // Generate method call (uses __ImplT)
753    let param_names = &method.param_names;
754    let method_call = if method.is_mut {
755        quote::quote! {
756            let self_ref = unsafe { &mut *data.cast::<#impl_t>() };
757            self_ref.#method_name(#(#param_names),*)
758        }
759    } else {
760        quote::quote! {
761            let self_ref = unsafe { &*data.cast::<#impl_t>().cast_const() };
762            self_ref.#method_name(#(#param_names),*)
763        }
764    };
765
766    // Generate result conversion
767    let result_conversion = if method.return_type.is_some() {
768        quote::quote! {
769            unsafe { ::miniextendr_api::trait_abi::to_sexp(result) }
770        }
771    } else {
772        quote::quote! {
773            let _ = result;
774            unsafe { ::miniextendr_api::trait_abi::nil() }
775        }
776    };
777
778    // Build trait bound for __ImplT
779    let trait_bound = if trait_param_idents.is_empty() {
780        quote::quote! { #trait_name }
781    } else {
782        quote::quote! { #trait_name<#(#trait_param_idents),*> }
783    };
784
785    let impl_bounds = &extra_bounds.impl_bounds;
786    let all_where_predicates = build_where_predicates(trait_where_clause, extra_bounds);
787    let where_clause = if all_where_predicates.is_empty() {
788        quote::quote! {}
789    } else {
790        quote::quote! { where #(#all_where_predicates),* }
791    };
792
793    let method_name_str = format!("{}::{}", trait_name, method_name);
794
795    quote::quote! {
796        #[doc = concat!(
797            "Method shim for `",
798            stringify!(#trait_name),
799            "::",
800            stringify!(#method_name),
801            "`."
802        )]
803        ///
804        /// Converts SEXP arguments, calls the method, and returns SEXP result.
805        /// Both Rust panics and R longjmps are caught via `with_r_unwind_protect`.
806        #[doc(hidden)]
807        unsafe extern "C" fn #shim_name<#(#trait_type_params,)* #impl_t: #trait_bound #(+ #impl_bounds)*>(
808            data: *mut ::std::os::raw::c_void,
809            argc: i32,
810            argv: *const ::miniextendr_api::SEXP,
811        ) -> ::miniextendr_api::SEXP
812        #where_clause
813        {
814            // Check arity (before unwind protect - uses r_stop which doesn't return)
815            unsafe {
816                ::miniextendr_api::trait_abi::check_arity(argc, #expected_argc, #method_name_str);
817            }
818
819            // Wrap in with_r_unwind_protect_shim: catches Rust panics and returns
820            // a tagged error SEXP instead of calling Rf_errorcall directly. The
821            // tagged SEXP is returned to the View method wrapper which re-panics
822            // via repanic_if_rust_error, allowing the consumer's outer
823            // `with_r_unwind_protect` guard to produce rust_* class layering
824            // (issue #345). R-origin longjmps still propagate via R_ContinueUnwind.
825            ::miniextendr_api::unwind_protect::with_r_unwind_protect_shim(|| {
826                // Extract arguments
827                #(#arg_extractions)*
828
829                // Call method
830                let result = { #method_call };
831
832                // Convert result
833                #result_conversion
834            })
835        }
836    }
837}
838
839/// Information extracted from a trait method for code generation.
840///
841/// Collects everything needed to generate vtable shims, view methods,
842/// and extra trait bounds for a single method in a `#[miniextendr]` trait.
843#[derive(Debug)]
844struct MethodInfo {
845    /// Method name (Rust identifier).
846    name: syn::Ident,
847    /// Whether the method has a self receiver (instance method).
848    /// False for static/associated methods.
849    has_self: bool,
850    /// Whether receiver is `&mut self` (vs `&self`). Only meaningful if `has_self` is true.
851    is_mut: bool,
852    /// Parameter types (excluding the self receiver).
853    param_types: Vec<syn::Type>,
854    /// Parameter names (excluding the self receiver). Uses `arg{i}` for unnamed patterns.
855    param_names: Vec<syn::Ident>,
856    /// Return type. `None` when the method returns `()` (unit type or no return annotation).
857    return_type: Option<syn::Type>,
858    /// Whether method is marked `#[miniextendr(skip)]`, excluding it from codegen.
859    skip: bool,
860    /// Override the R-facing method name (from `#[miniextendr(r_name = "...")]`).
861    /// When set, R wrappers and TPIE metadata use this name instead of the Rust ident.
862    r_name: Option<String>,
863}
864
865// region: Self-type detection helpers
866
867/// Check if a type syntactically contains `Self`.
868///
869/// Used to detect when a method returns `Self` (or `Option<Self>`, `Vec<Self>`, etc.)
870/// so the generated shim can add `IntoR` bounds.
871fn type_contains_self(ty: &syn::Type) -> bool {
872    match ty {
873        syn::Type::Path(tp) => {
874            for seg in &tp.path.segments {
875                if seg.ident == "Self" {
876                    return true;
877                }
878                if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
879                    for arg in &args.args {
880                        if let syn::GenericArgument::Type(inner) = arg
881                            && type_contains_self(inner)
882                        {
883                            return true;
884                        }
885                    }
886                }
887            }
888            false
889        }
890        syn::Type::Reference(r) => type_contains_self(&r.elem),
891        syn::Type::Tuple(t) => t.elems.iter().any(type_contains_self),
892        syn::Type::Slice(s) => type_contains_self(&s.elem),
893        syn::Type::Array(a) => type_contains_self(&a.elem),
894        syn::Type::Paren(p) => type_contains_self(&p.elem),
895        _ => false,
896    }
897}
898
899/// Check if a parameter type is `&Self` or `&mut Self`.
900///
901/// Returns `(is_self_ref, is_mut)`. When true, the generated shim extracts
902/// an `ExternalPtr<T>` from the SEXP and borrows from it instead of trying
903/// to extract `&T` directly (which doesn't implement `TryFromSexp`).
904fn param_is_self_ref(ty: &syn::Type) -> (bool, bool) {
905    if let syn::Type::Reference(r) = ty
906        && let syn::Type::Path(tp) = r.elem.as_ref()
907        && tp.path.is_ident("Self")
908    {
909        return (true, r.mutability.is_some());
910    }
911    (false, false)
912}
913
914/// Check if a type syntactically contains `Self::AssocType` for a given associated type name.
915///
916/// Recursively walks the type tree looking for a 2-segment path where the first
917/// segment is `Self` and the second matches `assoc_name` (e.g., `Self::Item`).
918/// Used to determine whether extra `where` bounds are needed for associated types.
919fn type_contains_self_assoc(ty: &syn::Type, assoc_name: &syn::Ident) -> bool {
920    match ty {
921        syn::Type::Path(tp) => {
922            if tp.path.segments.len() == 2
923                && tp.path.segments[0].ident == "Self"
924                && tp.path.segments[1].ident == *assoc_name
925            {
926                return true;
927            }
928            for seg in &tp.path.segments {
929                if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
930                    for arg in &args.args {
931                        if let syn::GenericArgument::Type(inner) = arg
932                            && type_contains_self_assoc(inner, assoc_name)
933                        {
934                            return true;
935                        }
936                    }
937                }
938            }
939            false
940        }
941        syn::Type::Reference(r) => type_contains_self_assoc(&r.elem, assoc_name),
942        syn::Type::Tuple(t) => t
943            .elems
944            .iter()
945            .any(|e| type_contains_self_assoc(e, assoc_name)),
946        syn::Type::Slice(s) => type_contains_self_assoc(&s.elem, assoc_name),
947        syn::Type::Array(a) => type_contains_self_assoc(&a.elem, assoc_name),
948        syn::Type::Paren(p) => type_contains_self_assoc(&p.elem, assoc_name),
949        _ => false,
950    }
951}
952
953/// Rewrite `Self` and `Self::AssocType` in a type tree to use `__ImplT`.
954///
955/// Transforms:
956/// - `Self` → `__ImplT`
957/// - `Self::Item` → `<__ImplT as TraitName>::Item`
958/// - Recursively processes generic arguments (e.g., `Option<Self::Item>` →
959///   `Option<<__ImplT as TraitName>::Item>`)
960fn rewrite_self_in_type(
961    ty: &syn::Type,
962    trait_name: &syn::Ident,
963    assoc_types: &[&syn::Ident],
964) -> syn::Type {
965    match ty {
966        syn::Type::Path(tp) => {
967            // Check for Self::AssocType (2-segment path: Self::Item)
968            if tp.path.segments.len() == 2
969                && tp.path.segments[0].ident == "Self"
970                && assoc_types.iter().any(|a| *a == &tp.path.segments[1].ident)
971            {
972                let assoc = &tp.path.segments[1].ident;
973                let impl_t = quote::format_ident!("__ImplT");
974                return syn::parse_quote!(<#impl_t as #trait_name>::#assoc);
975            }
976            // Check for bare Self
977            if tp.path.is_ident("Self") {
978                let impl_t = quote::format_ident!("__ImplT");
979                return syn::parse_quote!(#impl_t);
980            }
981            // Recursively process generic args
982            let mut new_tp = tp.clone();
983            for seg in &mut new_tp.path.segments {
984                if let syn::PathArguments::AngleBracketed(args) = &mut seg.arguments {
985                    for arg in &mut args.args {
986                        if let syn::GenericArgument::Type(inner) = arg {
987                            *inner = rewrite_self_in_type(inner, trait_name, assoc_types);
988                        }
989                    }
990                }
991            }
992            syn::Type::Path(new_tp)
993        }
994        syn::Type::Reference(r) => {
995            let mut new_r = r.clone();
996            new_r.elem = Box::new(rewrite_self_in_type(&r.elem, trait_name, assoc_types));
997            syn::Type::Reference(new_r)
998        }
999        syn::Type::Tuple(t) => {
1000            let mut new_t = t.clone();
1001            for elem in &mut new_t.elems {
1002                *elem = rewrite_self_in_type(elem, trait_name, assoc_types);
1003            }
1004            syn::Type::Tuple(new_t)
1005        }
1006        syn::Type::Slice(s) => {
1007            let mut new_s = s.clone();
1008            new_s.elem = Box::new(rewrite_self_in_type(&s.elem, trait_name, assoc_types));
1009            syn::Type::Slice(new_s)
1010        }
1011        syn::Type::Array(a) => {
1012            let mut new_a = a.clone();
1013            new_a.elem = Box::new(rewrite_self_in_type(&a.elem, trait_name, assoc_types));
1014            syn::Type::Array(new_a)
1015        }
1016        syn::Type::Paren(p) => {
1017            let mut new_p = p.clone();
1018            new_p.elem = Box::new(rewrite_self_in_type(&p.elem, trait_name, assoc_types));
1019            syn::Type::Paren(new_p)
1020        }
1021        _ => ty.clone(),
1022    }
1023}
1024
1025/// Check if a type syntactically contains a specific identifier.
1026///
1027/// Used to detect trait type parameters (like `T`) in method signatures so that
1028/// appropriate `TryFromSexp` or `IntoR` bounds can be added. Recursively walks
1029/// through path segments, generic arguments, references, tuples, slices, and arrays.
1030fn type_contains_ident(ty: &syn::Type, ident: &syn::Ident) -> bool {
1031    match ty {
1032        syn::Type::Path(tp) => {
1033            if tp.path.segments.len() == 1 && tp.path.segments[0].ident == *ident {
1034                return true;
1035            }
1036            for seg in &tp.path.segments {
1037                if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
1038                    for arg in &args.args {
1039                        if let syn::GenericArgument::Type(inner) = arg
1040                            && type_contains_ident(inner, ident)
1041                        {
1042                            return true;
1043                        }
1044                    }
1045                }
1046            }
1047            false
1048        }
1049        syn::Type::Reference(r) => type_contains_ident(&r.elem, ident),
1050        syn::Type::Tuple(t) => t.elems.iter().any(|e| type_contains_ident(e, ident)),
1051        syn::Type::Slice(s) => type_contains_ident(&s.elem, ident),
1052        syn::Type::Array(a) => type_contains_ident(&a.elem, ident),
1053        syn::Type::Paren(p) => type_contains_ident(&p.elem, ident),
1054        _ => false,
1055    }
1056}
1057
1058/// Extra trait bounds inferred from method signatures.
1059///
1060/// For generic traits and methods that reference `Self` or associated types,
1061/// the generated shim and vtable builder functions need additional bounds
1062/// beyond `__ImplT: TraitName`. This struct collects those bounds.
1063struct ExtraBounds {
1064    /// Bounds added directly to `__ImplT` (e.g., `IntoR` when methods return `Self`,
1065    /// or `TypedExternal + Send + 'static` when methods take `&Self` parameters).
1066    impl_bounds: Vec<TokenStream>,
1067    /// Where clause predicates for complex types (e.g.,
1068    /// `<__ImplT as Trait>::Item: IntoR` or `Vec<T>: TryFromSexp`).
1069    where_predicates: Vec<TokenStream>,
1070}
1071
1072/// Compute extra bounds needed for the shim and build_vtable functions.
1073///
1074/// - Methods returning `Self` → `__ImplT: IntoR`
1075/// - Methods with `&Self` params → `__ImplT: TypedExternal + Send + 'static`
1076/// - Methods returning types with `Self::AssocType` or trait type params →
1077///   full rewritten return type `: IntoR` (e.g., `Option<<__ImplT as RIterator>::Item>: IntoR`)
1078/// - Methods with trait type params in params →
1079///   full param type `: TryFromSexp` (e.g., `Vec<T>: TryFromSexp`)
1080fn compute_extra_bounds(
1081    methods: &[MethodInfo],
1082    trait_name: &syn::Ident,
1083    assoc_types: &[&syn::Ident],
1084    trait_param_idents: &[&syn::Ident],
1085) -> ExtraBounds {
1086    let mut impl_bounds = Vec::new();
1087    let mut where_predicates = Vec::new();
1088
1089    let mut needs_into_r = false;
1090    let mut needs_typed_external = false;
1091
1092    // Track full types needing bounds (deduplicated by token string)
1093    let mut return_type_bound_keys: std::collections::BTreeMap<String, syn::Type> =
1094        Default::default();
1095    let mut param_type_bound_keys: std::collections::BTreeMap<String, syn::Type> =
1096        Default::default();
1097
1098    for method in methods {
1099        // Bare Self in returns → __ImplT: IntoR (as impl bound)
1100        if method.return_type.as_ref().is_some_and(type_contains_self) {
1101            needs_into_r = true;
1102        }
1103        // &Self in params → __ImplT: TypedExternal + 'static
1104        if method.param_types.iter().any(|ty| param_is_self_ref(ty).0) {
1105            needs_typed_external = true;
1106        }
1107
1108        // Full return type bounds for Self::AssocType and/or trait type params.
1109        // Instead of bare `<__ImplT as Trait>::Item: IntoR`, we add the FULL rewritten
1110        // return type: `Option<<__ImplT as Trait>::Item>: IntoR`, `Vec<T>: IntoR`, etc.
1111        // This is required because IntoR impls are concrete (no blanket `Option<T: IntoR>: IntoR`).
1112        if let Some(ref ret_ty) = method.return_type {
1113            let has_assoc = assoc_types
1114                .iter()
1115                .any(|a| type_contains_self_assoc(ret_ty, a));
1116            let has_param = trait_param_idents
1117                .iter()
1118                .any(|p| type_contains_ident(ret_ty, p));
1119            if has_assoc || has_param {
1120                let rewritten = rewrite_self_in_type(ret_ty, trait_name, assoc_types);
1121                let key = quote::quote!(#rewritten).to_string();
1122                return_type_bound_keys.entry(key).or_insert(rewritten);
1123            }
1124        }
1125
1126        // Full param type bounds for trait type params.
1127        // Instead of bare `T: TryFromSexp`, we add `Vec<T>: TryFromSexp` etc.
1128        for param_ty in &method.param_types {
1129            if !param_is_self_ref(param_ty).0 {
1130                let has_param = trait_param_idents
1131                    .iter()
1132                    .any(|p| type_contains_ident(param_ty, p));
1133                if has_param {
1134                    let key = quote::quote!(#param_ty).to_string();
1135                    param_type_bound_keys.entry(key).or_insert(param_ty.clone());
1136                }
1137            }
1138        }
1139    }
1140
1141    if needs_into_r {
1142        impl_bounds.push(quote::quote! { ::miniextendr_api::IntoR });
1143    }
1144    if needs_typed_external {
1145        impl_bounds.push(quote::quote! { ::miniextendr_api::TypedExternal + Send + 'static });
1146    }
1147
1148    // Add full return type bounds: RewrittenType: IntoR
1149    for ty in return_type_bound_keys.values() {
1150        where_predicates.push(quote::quote! {
1151            #ty: ::miniextendr_api::IntoR
1152        });
1153    }
1154
1155    // Add full param type bounds: ParamType: TryFromSexp, Error: Display
1156    for ty in param_type_bound_keys.values() {
1157        where_predicates.push(quote::quote! {
1158            #ty: ::miniextendr_api::TryFromSexp
1159        });
1160        where_predicates.push(quote::quote! {
1161            <#ty as ::miniextendr_api::TryFromSexp>::Error: ::std::fmt::Display
1162        });
1163    }
1164
1165    ExtraBounds {
1166        impl_bounds,
1167        where_predicates,
1168    }
1169}
1170
1171/// Build combined where predicates from the trait's own where clause and computed extra bounds.
1172///
1173/// Merges the original trait-level where clause predicates with the extra
1174/// bounds computed from method signatures (e.g., `IntoR` for return types,
1175/// `TryFromSexp` for parameters containing trait type params).
1176///
1177/// Returns a flat list of predicates suitable for use in a `where` clause.
1178fn build_where_predicates(
1179    trait_where_clause: &Option<syn::WhereClause>,
1180    extra_bounds: &ExtraBounds,
1181) -> Vec<TokenStream> {
1182    let mut all = Vec::new();
1183    if let Some(wc) = trait_where_clause {
1184        for pred in &wc.predicates {
1185            all.push(quote::quote! { #pred });
1186        }
1187    }
1188    all.extend(extra_bounds.where_predicates.iter().cloned());
1189    all
1190}
1191
1192/// Extract method information from a trait method definition.
1193///
1194/// Parses the method signature to determine receiver type, parameter names/types,
1195/// return type, and any `#[miniextendr(...)]` attributes like `skip` and `r_name`.
1196/// Parameters with non-ident patterns are assigned synthetic names (`arg0`, `arg1`, etc.).
1197fn extract_method_info(method: &syn::TraitItemFn) -> syn::Result<MethodInfo> {
1198    let name = method.sig.ident.clone();
1199
1200    // Check for #[miniextendr(skip)] and #[miniextendr(r_name = "...")]
1201    let mut skip = false;
1202    let mut r_name: Option<String> = None;
1203    for attr in &method.attrs {
1204        if !attr.path().is_ident("miniextendr") {
1205            continue;
1206        }
1207        attr.parse_nested_meta(|meta| {
1208            if meta.path.is_ident("skip") {
1209                skip = true;
1210            } else if meta.path.is_ident("r_name") {
1211                let value: syn::LitStr = meta.value()?.parse()?;
1212                r_name = Some(value.value());
1213            } else {
1214                return Err(meta.error(
1215                    "unknown #[miniextendr] option on trait method; expected `skip` or `r_name`",
1216                ));
1217            }
1218            Ok(())
1219        })?;
1220    }
1221
1222    // Check for receiver
1223    let (has_self, is_mut) = method.sig.inputs.first().map_or((false, false), |arg| {
1224        if let syn::FnArg::Receiver(r) = arg {
1225            (true, r.mutability.is_some())
1226        } else {
1227            (false, false)
1228        }
1229    });
1230
1231    // Extract parameters (skip self if present)
1232    let skip_count = if has_self { 1 } else { 0 };
1233    let mut param_types = Vec::new();
1234    let mut param_names = Vec::new();
1235    for (i, arg) in method.sig.inputs.iter().skip(skip_count).enumerate() {
1236        if let syn::FnArg::Typed(pat_type) = arg {
1237            param_types.push((*pat_type.ty).clone());
1238            if let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() {
1239                param_names.push(pat_ident.ident.clone());
1240            } else {
1241                param_names.push(quote::format_ident!("arg{}", i));
1242            }
1243        }
1244    }
1245
1246    // Extract return type
1247    let return_type = match &method.sig.output {
1248        syn::ReturnType::Default => None,
1249        syn::ReturnType::Type(_, ty) => {
1250            // Check if it's unit type ()
1251            if matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty()) {
1252                None
1253            } else {
1254                Some((**ty).clone())
1255            }
1256        }
1257    };
1258
1259    Ok(MethodInfo {
1260        name,
1261        has_self,
1262        is_mut,
1263        param_types,
1264        param_names,
1265        return_type,
1266        skip,
1267        r_name,
1268    })
1269}
1270
1271#[cfg(test)]
1272mod tests;
1273// endregion