Skip to main content

miniextendr_macros/
miniextendr_impl_trait.rs

1//! # `#[miniextendr]` on trait impls - Trait Implementation Registration
2//!
3//! This module handles `#[miniextendr]` applied to trait implementations,
4//! generating the vtable static for cross-package trait dispatch, plus optional
5//! R-callable wrappers for direct method access.
6//!
7//! ## Overview
8//!
9//! When `#[miniextendr]` is applied to an `impl Trait for Type` block, it:
10//!
11//! 1. **Detects the trait** from the impl syntax (no attribute args needed)
12//! 2. **Generates vtable static** using the trait's `__<trait>_build_vtable` function
13//! 3. **Generates C wrappers** for each trait method (for R `.Call` access)
14//! 4. **Generates R wrapper code** for the trait methods
15//! 5. **Passes through** the original impl block unchanged
16//!
17//! ## Usage
18//!
19//! ```ignore
20//! use miniextendr_api::miniextendr;
21//!
22//! // The trait must have been defined with #[miniextendr]
23//! // which generates __counter_build_vtable::<T>()
24//!
25//! struct MyCounter { value: i32 }
26//!
27//! #[miniextendr]
28//! impl Counter for MyCounter {
29//!     fn value(&self) -> i32 {
30//!         self.value
31//!     }
32//!     fn increment(&mut self) {
33//!         self.value += 1;
34//!     }
35//!     fn add(&mut self, n: i32) {
36//!         self.value += n;
37//!     }
38//! }
39//! ```
40//!
41//! Generates (conceptually):
42//!
43//! ```ignore
44//! // Original impl block (passed through)
45//! impl Counter for MyCounter {
46//!     fn value(&self) -> i32 { self.value }
47//!     fn increment(&mut self) { self.value += 1; }
48//!     fn add(&mut self, n: i32) { self.value += n; }
49//! }
50//!
51//! // Generated vtable static
52//! pub static __VTABLE_COUNTER_FOR_MYCOUNTER: CounterVTable =
53//!     __counter_build_vtable::<MyCounter>();
54//! ```
55//!
56//! ## How It Works
57//!
58//! 1. Parse `impl Trait for Type` to extract trait path and concrete type
59//! 2. Generate vtable static name: `__VTABLE_{TRAIT}_FOR_{TYPE}`
60//! 3. Generate vtable builder call: `__{trait}_build_vtable::<Type>()`
61//! 4. The vtable builder was generated by `#[miniextendr]` on the trait
62//!
63//! ## Trait Detection
64//!
65//! The macro reads the trait directly from the impl syntax:
66//!
67//! ```ignore
68//! #[miniextendr]
69//! impl path::to::Counter for MyType { ... }
70//! //   ^^^^^^^^^^^^^^^^^ detected automatically
71//! ```
72//!
73//! No extra arguments are needed; the trait path is explicit in the impl syntax.
74//!
75//! ## Name Generation
76//!
77//! - **Vtable static**: `__VTABLE_{TRAIT}_FOR_{TYPE}` (uppercase, underscores)
78//! - **Vtable builder**: `__{trait}_build_vtable` (lowercase)
79//!
80//! For `impl foo::Counter for my_mod::MyType`:
81//! - Static: `__VTABLE_COUNTER_FOR_MYTYPE`
82//! - Builder call: `foo::__counter_build_vtable::<my_mod::MyType>()`
83//!
84//! ## Integration with ExternalPtr / TypedExternal
85//!
86//! The generated vtable static is automatically registered via linkme
87//! distributed slices.
88//!
89//! ```ignore
90//! #[derive(ExternalPtr)]
91//! struct MyCounter { value: i32 }
92//!
93//! #[miniextendr]
94//! impl Counter for MyCounter { /* ... */ }
95//! ```
96//!
97//! `ExternalPtr<T>` provides the type identity for the external pointer.
98//! Trait dispatch is wired automatically.
99//!
100//! ## Thread Safety
101//!
102//! The generated vtable is a static constant, safe to access from any thread.
103//! Trait shims now mirror inherent impls: instance methods stay on the main
104//! thread, while static trait methods run on the worker thread unless
105//! `main_thread` is explicitly requested.
106
107use proc_macro2::TokenStream;
108use quote::{ToTokens, format_ident};
109use syn::ItemImpl;
110
111use crate::miniextendr_impl::{ClassSystem, ImplAttrs};
112
113/// Parsed method from a trait impl block.
114///
115/// Stores everything needed to generate C wrappers, R wrappers, and vtable
116/// shims for a single method in an `impl Trait for Type` block.
117#[derive(Debug, Clone)]
118struct TraitMethod {
119    /// Rust method identifier (e.g., `value`, `increment`).
120    ident: syn::Ident,
121    /// Full method signature including self receiver and all parameters.
122    sig: syn::Signature,
123    /// Whether the method has a receiver (`&self`, `&mut self`).
124    /// False for static/associated methods.
125    has_self: bool,
126    /// Whether receiver is `&mut self` (vs `&self`). Only meaningful if `has_self` is true.
127    is_mut: bool,
128    /// When true, dispatches to the worker thread via `run_on_worker`.
129    /// Set by explicit `#[miniextendr(worker)]` or the `worker-default` feature flag.
130    worker: bool,
131    /// When true, forces execution on R's main thread via `unsafe(main_thread)`.
132    unsafe_main_thread: bool,
133    /// Enable automatic type coercion for all parameters via `Rf_coerceVector`.
134    coerce: bool,
135    /// Check for R user interrupts (`R_CheckUserInterrupt`) before calling the method.
136    check_interrupt: bool,
137    /// Enable RNG state management (`GetRNGstate`/`PutRNGstate`) around the call.
138    rng: bool,
139    /// Return `Result<T, E>` to R without unwrapping -- R wrapper receives the result variant.
140    unwrap_in_r: bool,
141    /// Parameter default values from `#[miniextendr(defaults(param = "value", ...))]`.
142    /// Keys are parameter names, values are R expressions used as default values.
143    param_defaults: std::collections::HashMap<String, String>,
144    /// Roxygen `@param` tags extracted from method doc comments.
145    param_tags: Vec<String>,
146    /// When true, this method is excluded from C wrappers, R wrappers, and vtable shims.
147    /// The method is still kept in the emitted impl block (it's a real trait method).
148    skip: bool,
149    /// Override the R-facing method name. When set, the R wrapper uses this name
150    /// instead of the Rust method name (e.g., `next` -> `next_item` to avoid R reserved words).
151    r_name: Option<String>,
152    /// Strict output conversion: panic instead of lossy widening for i64/u64/isize/usize.
153    strict: bool,
154    /// Lifecycle specification for deprecation/experimental status.
155    lifecycle: Option<crate::lifecycle::LifecycleSpec>,
156    /// R code to inject at the very top of the wrapper body.
157    r_entry: Option<String>,
158    /// R code to inject after all checks, immediately before `.Call()`.
159    r_post_checks: Option<String>,
160    /// Register `on.exit()` cleanup code in the R wrapper.
161    r_on_exit: Option<crate::miniextendr_fn::ROnExit>,
162    /// Opt out of the per-class `<ClassName>_<method>` S7 fast-dispatch shortcut
163    /// (set via `#[miniextendr(s7(no_shortcut))]`). Only meaningful for S7 trait
164    /// impls; ignored by the other class systems.
165    no_shortcut: bool,
166    /// Per-parameter `#[miniextendr(match_arg(...))]` / `choices(...)` /
167    /// `several_ok` attributes, keyed by Rust parameter name. Mirrors
168    /// `MethodAttrs::per_param` on the inherent-impl path (`miniextendr_impl.rs`)
169    /// so trait methods get the same `match.arg()` validation prelude — see
170    /// `TraitMethodContext::match_arg_prelude`.
171    per_param: std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs>,
172}
173
174impl TraitMethod {
175    /// Returns the R-facing method name.
176    ///
177    /// Uses `r_name` override if set, otherwise falls back to the Rust identifier string.
178    fn r_method_name(&self) -> String {
179        self.r_name
180            .clone()
181            .unwrap_or_else(|| self.ident.to_string())
182    }
183
184    /// Generates the C wrapper function identifier: `C_{Type}__{Trait}__{method}`.
185    ///
186    /// This is the symbol name registered with R via `R_CallMethodDef` for `.Call()` access.
187    fn c_wrapper_ident(&self, type_ident: &syn::Ident, trait_name: &syn::Ident) -> syn::Ident {
188        format_ident!("C_{}__{}__{}", type_ident, trait_name, self.ident)
189    }
190
191    /// Generates the C wrapper identifier as a `String`, for use in R-side `.Call()` generation.
192    ///
193    /// Prefer `c_wrapper_ident()` for Rust token generation; use this variant
194    /// when building R code strings (e.g., `".Call(C_Type__Trait__method, ...)"`).
195    fn c_wrapper_ident_string(&self, type_ident: &syn::Ident, trait_name: &syn::Ident) -> String {
196        format!("C_{}__{}__{}", type_ident, trait_name, self.ident)
197    }
198
199    /// Returns true if this method has no return type (returns unit `()`).
200    ///
201    /// Used to decide whether the R wrapper should emit `invisible(x)` for
202    /// void instance methods (pipe-friendly chaining).
203    fn returns_unit(&self) -> bool {
204        match &self.sig.output {
205            syn::ReturnType::Default => true,
206            syn::ReturnType::Type(_, ty) => {
207                matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty())
208            }
209        }
210    }
211
212    /// Generates the `R_CallMethodDef` static identifier: `call_method_def_{Type}__{Trait}_{method}`.
213    ///
214    /// This constant holds the C function pointer and arity used by R's `.Call()` registration.
215    fn call_method_def_ident(
216        &self,
217        type_ident: &syn::Ident,
218        trait_name: &syn::Ident,
219    ) -> syn::Ident {
220        format_ident!(
221            "call_method_def_{}__{}_{}",
222            type_ident,
223            trait_name,
224            self.ident
225        )
226    }
227}
228
229/// Parsed associated constant from a trait impl block.
230///
231/// Trait constants are exposed to R as zero-argument `.Call()` wrappers
232/// that simply return the constant value.
233#[derive(Debug)]
234struct TraitConst {
235    /// Constant identifier (e.g., `MAX_SIZE`).
236    ident: syn::Ident,
237    /// Constant type (e.g., `i32`, `&str`). Used to determine SEXP conversion.
238    ty: syn::Type,
239}
240
241impl TraitConst {
242    /// Generates the C wrapper function identifier: `C_{Type}__{Trait}__{CONST}`.
243    ///
244    /// This is the symbol name registered with R for `.Call()` access to the constant.
245    fn c_wrapper_ident(&self, type_ident: &syn::Ident, trait_name: &syn::Ident) -> syn::Ident {
246        format_ident!("C_{}__{}__{}", type_ident, trait_name, self.ident)
247    }
248
249    /// Generates the C wrapper identifier as a `String`, for use in R-side `.Call()` generation.
250    fn c_wrapper_ident_string(&self, type_ident: &syn::Ident, trait_name: &syn::Ident) -> String {
251        format!("C_{}__{}__{}", type_ident, trait_name, self.ident)
252    }
253
254    /// Generates the `R_CallMethodDef` static identifier: `call_method_def_{Type}__{Trait}_{CONST}`.
255    fn call_method_def_ident(
256        &self,
257        type_ident: &syn::Ident,
258        trait_name: &syn::Ident,
259    ) -> syn::Ident {
260        format_ident!(
261            "call_method_def_{}__{}_{}",
262            type_ident,
263            trait_name,
264            self.ident
265        )
266    }
267}
268
269/// Expand `#[miniextendr]` applied to a trait implementation.
270///
271/// # Arguments
272///
273/// * `attr` - Attribute arguments (currently unused)
274/// * `item` - The impl block token stream
275///
276/// # Returns
277///
278/// Expanded token stream containing:
279/// - Original impl block
280/// - Vtable static constant
281///
282/// # Errors
283///
284/// Returns a compile error if:
285/// - Not applied to a trait impl (`impl Trait for Type`)
286/// - Applied to an inherent impl (`impl Type`)
287pub fn expand_miniextendr_impl_trait(
288    attr: proc_macro::TokenStream,
289    item: proc_macro::TokenStream,
290) -> proc_macro::TokenStream {
291    // Parse class system from attribute (defaults to Env if empty)
292    let impl_attrs: ImplAttrs = syn::parse_macro_input!(attr as ImplAttrs);
293    let impl_item = syn::parse_macro_input!(item as ItemImpl);
294
295    // Validate: must be a trait impl, not inherent impl
296    let (trait_path, concrete_type) = match extract_trait_and_type(&impl_item) {
297        Ok(result) => result,
298        Err(e) => return e.into_compile_error().into(),
299    };
300
301    // TPIE: empty impl body → expand via macro_rules! helper from the trait definition
302    if impl_item.items.is_empty() && !impl_attrs.blanket {
303        let raw_tags = crate::roxygen::roxygen_tags_from_attrs(&impl_item.attrs);
304        let (doc_tags, param_warnings) = crate::roxygen::strip_method_tags(
305            &raw_tags,
306            &concrete_type.to_token_stream().to_string(),
307            impl_item.impl_token.span,
308        );
309        let no_rd = crate::roxygen::has_roxygen_tag(&doc_tags, "noRd");
310        let mut output = generate_tpie_invocation(
311            &trait_path,
312            &concrete_type,
313            impl_attrs.class_system,
314            no_rd,
315            impl_attrs.internal,
316            impl_attrs.noexport,
317        );
318        output.extend(param_warnings);
319        return output.into();
320    }
321
322    // Generate the vtable static and R wrappers
323    let expanded = generate_vtable_static(
324        &impl_item,
325        &trait_path,
326        &concrete_type,
327        impl_attrs.class_system,
328        impl_attrs.blanket,
329        impl_attrs.internal,
330        impl_attrs.noexport,
331    );
332
333    expanded.into()
334}
335
336/// Extract the trait path and concrete type from an impl block.
337///
338/// For `impl path::Trait for concrete::Type`, returns:
339/// - trait_path: `path::Trait`
340/// - concrete_type: `concrete::Type`
341fn extract_trait_and_type(impl_item: &ItemImpl) -> syn::Result<(syn::Path, syn::Type)> {
342    // Check for trait impl
343    let (_, trait_path, _) = impl_item.trait_.as_ref().ok_or_else(|| {
344        syn::Error::new_spanned(
345            impl_item,
346            "#[miniextendr] must be applied to a trait implementation (impl Trait for Type), \
347             not an inherent impl (impl Type)",
348        )
349    })?;
350
351    let concrete_type = (*impl_item.self_ty).clone();
352
353    Ok((trait_path.clone(), concrete_type))
354}
355
356// region: Sub-modules
357
358mod method_context;
359mod r_wrappers;
360mod vtable;
361
362use r_wrappers::TraitWrapperOpts;
363use r_wrappers::generate_trait_r_wrapper;
364use vtable::generate_trait_method_c_wrapper;
365use vtable::generate_vtable_static;
366use vtable::is_self_ref_type;
367
368/// Generate R function body lines that capture the `.Call()` result in `.val`,
369/// check for a tagged `rust_condition_value`, and return `.val`.
370fn trait_method_body_lines(call_expr: &str, indent: &str) -> Vec<String> {
371    let mut lines = vec![format!("{}.val <- {}", indent, call_expr)];
372    lines.extend(crate::method_return_builder::condition_check_lines(indent));
373    lines.push(format!("{}.val", indent));
374    lines
375}
376
377/// Convert a type to an uppercase identifier-safe name.
378///
379/// For non-generic types, the name is simply the last path segment uppercased:
380/// - `MyType` → `MYTYPE`
381/// - `path::to::MyType` → `MYTYPE`
382///
383/// For generic types, a 16-character lowercase hex suffix derived from a stable
384/// FNV-1a-64 hash of the full canonical token stream is appended:
385/// - `MyType<u32>` → `MYTYPE_a1b2c3d4e5f60718`
386/// - `MyType<f64>` → `MYTYPE_0102030405060708` (different hash → no collision)
387///
388/// This prevents vtable static name collisions when the same base type is
389/// monomorphised with different generic arguments in the same crate.
390///
391/// **Hash stability:** FNV-1a-64 with a fixed seed (offset basis = 0xcbf29ce484222325)
392/// is deterministic across builds, rustc versions, and platforms.  We deliberately
393/// avoid `std::collections::hash_map::DefaultHasher` and `RandomState` because both
394/// are explicitly unspecified and can change across releases.
395fn type_to_uppercase_name(ty: &syn::Type) -> String {
396    /// FNV-1a 64-bit hash of a byte slice.
397    ///
398    /// Chosen for simplicity (≈10 lines, no new deps) and cross-build stability.
399    /// The FNV offset basis and prime are fixed constants defined in the FNV spec.
400    fn fnv1a_64(data: &[u8]) -> u64 {
401        const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
402        const PRIME: u64 = 0x00000100000001b3;
403        let mut h = OFFSET_BASIS;
404        for &b in data {
405            h ^= b as u64;
406            h = h.wrapping_mul(PRIME);
407        }
408        h
409    }
410
411    match ty {
412        syn::Type::Path(type_path) => {
413            let last = type_path.path.segments.last();
414            let base = last
415                .map(|s| s.ident.to_string().to_uppercase())
416                .unwrap_or_else(|| "UNKNOWN".to_string());
417
418            // Only append a hash suffix when the type actually carries generic arguments
419            // (e.g. `MyType<u32>`). Plain `MyType` keeps the clean `MYTYPE` form.
420            let has_generics = last
421                .map(|s| !matches!(s.arguments, syn::PathArguments::None))
422                .unwrap_or(false);
423
424            if has_generics {
425                // Canonical token string of the *full* type (including all generic args)
426                // so that `MyType<u32>` and `MyType<f64>` produce distinct hashes.
427                let token_str = quote::quote!(#ty).to_string();
428                let hash = fnv1a_64(token_str.as_bytes());
429                format!("{}_{:016x}", base, hash)
430            } else {
431                base
432            }
433        }
434        _ => "UNKNOWN".to_string(),
435    }
436}
437// endregion
438
439// region: TPIE: Trait-Provided Impl Expansion
440
441/// Input to the `__mx_trait_impl_expand!` proc macro.
442///
443/// TPIE (Trait-Provided Impl Expansion) allows empty `#[miniextendr] impl Trait for Type {}`
444/// blocks to auto-expand C/R wrappers using metadata embedded in a `macro_rules!` helper
445/// generated at the trait definition site.
446///
447/// Parsed from tokens like:
448/// ```text
449/// concrete_type = Point;
450/// trait_path = miniextendr_api::adapter_traits::RDebug;
451/// class_system = env;
452/// method { r_name = debug_str; fn debug_str(&self) -> String; }
453/// method { r_name = debug_str_pretty; fn debug_str_pretty(&self) -> String; }
454/// ```
455struct TpieInput {
456    /// The concrete type implementing the trait (e.g., `Point`).
457    concrete_type: syn::Type,
458    /// Fully qualified path to the trait (e.g., `miniextendr_api::adapter_traits::RDebug`).
459    trait_path: syn::Path,
460    /// Which R class system to generate wrappers for (env, r6, s3, s4, s7).
461    class_system: ClassSystem,
462    /// Whether the impl block has `@noRd`, suppressing roxygen documentation.
463    no_rd: bool,
464    /// Whether the impl block has `#[miniextendr(internal)]`, adding `@keywords internal`.
465    internal: bool,
466    /// Whether the impl block has `#[miniextendr(noexport)]`, suppressing `@export`.
467    noexport: bool,
468    /// Method signatures and R-facing names from the trait definition.
469    methods: Vec<TpieMethod>,
470}
471
472/// A single method entry in TPIE metadata.
473///
474/// Contains the R-facing name and the method signature as declared in the trait.
475struct TpieMethod {
476    /// The R-facing method name (may differ from the Rust ident via `r_name`).
477    r_name: String,
478    /// The method signature (parameters and return type) from the trait definition.
479    sig: syn::Signature,
480}
481
482impl syn::parse::Parse for TpieInput {
483    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
484        // concrete_type = Type;
485        let kw: syn::Ident = input.parse()?;
486        if kw != "concrete_type" {
487            return Err(syn::Error::new_spanned(
488                &kw,
489                format!("expected 'concrete_type', got '{}'", kw),
490            ));
491        }
492        input.parse::<syn::Token![=]>()?;
493        let concrete_type: syn::Type = input.parse()?;
494        input.parse::<syn::Token![;]>()?;
495
496        // trait_path = some::Path;
497        let kw: syn::Ident = input.parse()?;
498        if kw != "trait_path" {
499            return Err(syn::Error::new_spanned(
500                &kw,
501                format!("expected 'trait_path', got '{}'", kw),
502            ));
503        }
504        input.parse::<syn::Token![=]>()?;
505        let trait_path: syn::Path = input.parse()?;
506        input.parse::<syn::Token![;]>()?;
507
508        // class_system = env;
509        let kw: syn::Ident = input.parse()?;
510        if kw != "class_system" {
511            return Err(syn::Error::new_spanned(
512                &kw,
513                format!("expected 'class_system', got '{}'", kw),
514            ));
515        }
516        input.parse::<syn::Token![=]>()?;
517        let cs_ident: syn::Ident = input.parse()?;
518        input.parse::<syn::Token![;]>()?;
519
520        let class_system = ClassSystem::from_ident(&cs_ident).ok_or_else(|| {
521            syn::Error::new_spanned(&cs_ident, format!("unknown class system: {}", cs_ident))
522        })?;
523
524        // no_rd = true/false;
525        let kw: syn::Ident = input.parse()?;
526        if kw != "no_rd" {
527            return Err(syn::Error::new_spanned(
528                &kw,
529                format!("expected 'no_rd', got '{}'", kw),
530            ));
531        }
532        input.parse::<syn::Token![=]>()?;
533        let no_rd_lit: syn::LitBool = input.parse()?;
534        input.parse::<syn::Token![;]>()?;
535        let no_rd = no_rd_lit.value;
536
537        // internal = true/false;
538        let kw: syn::Ident = input.parse()?;
539        if kw != "internal" {
540            return Err(syn::Error::new_spanned(
541                &kw,
542                format!("expected 'internal', got '{}'", kw),
543            ));
544        }
545        input.parse::<syn::Token![=]>()?;
546        let internal_lit: syn::LitBool = input.parse()?;
547        input.parse::<syn::Token![;]>()?;
548        let internal = internal_lit.value;
549
550        // noexport = true/false;
551        let kw: syn::Ident = input.parse()?;
552        if kw != "noexport" {
553            return Err(syn::Error::new_spanned(
554                &kw,
555                format!("expected 'noexport', got '{}'", kw),
556            ));
557        }
558        input.parse::<syn::Token![=]>()?;
559        let noexport_lit: syn::LitBool = input.parse()?;
560        input.parse::<syn::Token![;]>()?;
561        let noexport = noexport_lit.value;
562
563        // method { ... } repeated
564        let mut methods = Vec::new();
565        while !input.is_empty() {
566            let kw: syn::Ident = input.parse()?;
567            if kw != "method" {
568                return Err(syn::Error::new_spanned(
569                    &kw,
570                    format!("expected 'method', got '{}'", kw),
571                ));
572            }
573            let content;
574            syn::braced!(content in input);
575            methods.push(content.parse::<TpieMethod>()?);
576        }
577
578        Ok(TpieInput {
579            concrete_type,
580            trait_path,
581            class_system,
582            no_rd,
583            internal,
584            noexport,
585            methods,
586        })
587    }
588}
589
590impl syn::parse::Parse for TpieMethod {
591    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
592        // r_name = some_name;
593        let kw: syn::Ident = input.parse()?;
594        if kw != "r_name" {
595            return Err(syn::Error::new_spanned(
596                &kw,
597                format!("expected 'r_name', got '{}'", kw),
598            ));
599        }
600        input.parse::<syn::Token![=]>()?;
601        let r_name_ident: syn::Ident = input.parse()?;
602        input.parse::<syn::Token![;]>()?;
603
604        // fn method_name(...) -> ReturnType;
605        let sig: syn::Signature = input.parse()?;
606        input.parse::<syn::Token![;]>()?;
607
608        Ok(TpieMethod {
609            r_name: r_name_ident.to_string(),
610            sig,
611        })
612    }
613}
614
615/// Rewrite `Self` → concrete type in a method signature.
616///
617/// `&Self` params are left as-is because `generate_trait_method_c_wrapper`
618/// detects them via `is_self_ref_type` and generates `ExternalPtr<T>` extraction.
619fn rewrite_self_in_sig(sig: &mut syn::Signature, concrete_type: &syn::Type) {
620    for input in &mut sig.inputs {
621        if let syn::FnArg::Typed(pt) = input {
622            // Don't rewrite &Self — C wrapper generator handles it specially
623            if is_self_ref_type(&pt.ty) {
624                continue;
625            }
626            let rewritten = rewrite_self_type(&pt.ty, concrete_type);
627            *pt.ty = rewritten;
628        }
629    }
630    if let syn::ReturnType::Type(_, ty) = &mut sig.output {
631        let rewritten = rewrite_self_type(ty, concrete_type);
632        **ty = rewritten;
633    }
634}
635
636/// Recursively replace `Self` with the concrete type in a type tree.
637fn rewrite_self_type(ty: &syn::Type, concrete_type: &syn::Type) -> syn::Type {
638    match ty {
639        syn::Type::Path(tp) => {
640            if tp.path.is_ident("Self") {
641                return concrete_type.clone();
642            }
643            let mut new_tp = tp.clone();
644            for seg in &mut new_tp.path.segments {
645                if let syn::PathArguments::AngleBracketed(args) = &mut seg.arguments {
646                    for arg in &mut args.args {
647                        if let syn::GenericArgument::Type(inner) = arg {
648                            *inner = rewrite_self_type(inner, concrete_type);
649                        }
650                    }
651                }
652            }
653            syn::Type::Path(new_tp)
654        }
655        syn::Type::Reference(r) => {
656            let mut new_r = r.clone();
657            new_r.elem = Box::new(rewrite_self_type(&r.elem, concrete_type));
658            syn::Type::Reference(new_r)
659        }
660        syn::Type::Tuple(t) => {
661            let mut new_t = t.clone();
662            for elem in &mut new_t.elems {
663                *elem = rewrite_self_type(elem, concrete_type);
664            }
665            syn::Type::Tuple(new_t)
666        }
667        _ => ty.clone(),
668    }
669}
670
671/// Unwrap invisible Group tokens from `macro_rules!` `$t:ty` captures.
672///
673/// When a type passes through a `macro_rules!` pattern like `$concrete_type:ty`,
674/// the compiler wraps it in a `Group` with invisible delimiters. This function
675/// recursively unwraps those groups to get the underlying type.
676fn unwrap_group_type(ty: &syn::Type) -> syn::Type {
677    match ty {
678        syn::Type::Group(g) => unwrap_group_type(&g.elem),
679        _ => ty.clone(),
680    }
681}
682
683/// Entry point for the `__mx_trait_impl_expand!` proc macro.
684///
685/// Parses TPIE metadata tokens and generates C wrappers, R wrappers,
686/// and call defs — the same outputs as a manual trait impl with method bodies.
687pub fn expand_tpie(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
688    let tpie_input = syn::parse_macro_input!(input as TpieInput);
689
690    // Unwrap Group tokens (macro_rules! wraps $concrete_type:ty and $trait_path:path
691    // in invisible groups)
692    let concrete_type = unwrap_group_type(&tpie_input.concrete_type);
693    let trait_path = &tpie_input.trait_path;
694    let class_system = tpie_input.class_system;
695
696    let Some(trait_name) = trait_path.segments.last().map(|s| &s.ident) else {
697        return syn::Error::new(
698            proc_macro2::Span::call_site(),
699            "trait path must have at least one segment",
700        )
701        .into_compile_error()
702        .into();
703    };
704
705    let type_ident = match &concrete_type {
706        syn::Type::Path(tp) => tp
707            .path
708            .segments
709            .last()
710            .map(|s| s.ident.clone())
711            .unwrap_or_else(|| format_ident!("Unknown")),
712        _ => format_ident!("Unknown"),
713    };
714
715    // Convert TpieMethod → TraitMethod, rewriting Self → ConcreteType
716    let methods: Vec<TraitMethod> = tpie_input
717        .methods
718        .iter()
719        .map(|tm| {
720            let mut sig = tm.sig.clone();
721            rewrite_self_in_sig(&mut sig, &concrete_type);
722
723            let (has_self, is_mut) = sig.inputs.first().map_or((false, false), |arg| {
724                if let syn::FnArg::Receiver(r) = arg {
725                    (true, r.mutability.is_some())
726                } else {
727                    (false, false)
728                }
729            });
730
731            TraitMethod {
732                ident: sig.ident.clone(),
733                sig,
734                has_self,
735                is_mut,
736                worker: cfg!(feature = "worker-default"),
737                unsafe_main_thread: false,
738                coerce: false,
739                check_interrupt: false,
740                rng: false,
741                unwrap_in_r: false,
742                param_defaults: Default::default(),
743                param_tags: vec![],
744                skip: false,
745                strict: false,
746                lifecycle: None,
747                r_entry: None,
748                r_post_checks: None,
749                r_on_exit: None,
750                no_shortcut: false,
751                per_param: Default::default(),
752                r_name: if tm.sig.ident == tm.r_name {
753                    None // r_name matches ident → no override
754                } else {
755                    Some(tm.r_name.clone())
756                },
757            }
758        })
759        .collect();
760
761    // Generate C wrappers
762    let c_wrappers: Vec<TokenStream> = methods
763        .iter()
764        .map(|m| generate_trait_method_c_wrapper(m, &type_ident, trait_name, trait_path))
765        .collect();
766
767    // Generate R wrappers
768    let r_wrapper_string = match generate_trait_r_wrapper(
769        &type_ident,
770        trait_name,
771        &methods,
772        &[], // no consts in TPIE
773        TraitWrapperOpts {
774            class_system,
775            class_has_no_rd: tpie_input.no_rd,
776            internal: tpie_input.internal,
777            noexport: tpie_input.noexport,
778        },
779    ) {
780        Ok(s) => s,
781        Err(e) => return e.into_compile_error().into(),
782    };
783
784    // Generate const names (same pattern as generate_vtable_static)
785    let trait_name_upper = trait_name.to_string().to_uppercase();
786    let type_name_str = type_to_uppercase_name(&concrete_type);
787    let r_wrappers_const = format_ident!(
788        "R_WRAPPERS_{}_{}_IMPL",
789        type_ident.to_string().to_uppercase(),
790        trait_name_upper
791    );
792
793    // Generate trait dispatch entry name
794    let dispatch_entry_name = format_ident!(
795        "__MX_DISPATCH_{}_{}_FOR_{}",
796        trait_name_upper,
797        type_ident.to_string().to_uppercase(),
798        type_name_str
799    );
800
801    // Build TAG path for the trait
802    let mut trait_tag_path = trait_path.clone();
803    if let Some(last) = trait_tag_path.segments.last_mut() {
804        last.ident = format_ident!("TAG_{}", trait_name_upper);
805        last.arguments = syn::PathArguments::None;
806    }
807
808    // Build vtable static name (same as generate_vtable_static)
809    let vtable_static_name = format_ident!("__VTABLE_{}_FOR_{}", trait_name_upper, type_name_str);
810
811    // Format R wrapper as raw string literal
812    let r_wrapper_str = crate::r_wrapper_raw_literal(&r_wrapper_string);
813    let source_start = type_ident.span().start();
814    let source_line_lit = syn::LitInt::new(&source_start.line.to_string(), type_ident.span());
815    let source_col_lit =
816        syn::LitInt::new(&(source_start.column + 1).to_string(), type_ident.span());
817
818    let expanded = quote::quote! {
819        // C wrappers and call method defs for trait methods
820        #(#c_wrappers)*
821
822        // R wrapper registration via distributed slice
823        #[doc(hidden)]
824        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_R_WRAPPERS), linkme(crate = ::miniextendr_api::linkme))]
825        static #r_wrappers_const: ::miniextendr_api::registry::RWrapperEntry =
826            ::miniextendr_api::registry::RWrapperEntry {
827                priority: ::miniextendr_api::registry::RWrapperPriority::TraitImpl,
828                source_file: file!(),
829                content: concat!(
830                    "# Generated from Rust impl `",
831                    stringify!(#trait_name),
832                    "` for `",
833                    stringify!(#type_ident),
834                    "` (",
835                    file!(),
836                    ":",
837                    #source_line_lit,
838                    ":",
839                    #source_col_lit,
840                    ")",
841                    #r_wrapper_str
842                ),
843            };
844
845        // Trait dispatch entry for universal_query
846        #[doc(hidden)]
847        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_TRAIT_DISPATCH), linkme(crate = ::miniextendr_api::linkme))]
848        static #dispatch_entry_name: ::miniextendr_api::registry::TraitDispatchEntry =
849            ::miniextendr_api::registry::TraitDispatchEntry {
850                concrete_tag: ::miniextendr_api::abi::mx_tag_from_path(
851                    concat!(module_path!(), "::", stringify!(#type_ident))
852                ),
853                trait_tag: #trait_tag_path,
854                vtable: unsafe {
855                    ::std::ptr::from_ref(&#vtable_static_name).cast::<::std::os::raw::c_void>()
856                },
857                vtable_symbol: stringify!(#vtable_static_name),
858            };
859    };
860
861    expanded.into()
862}
863
864/// Generate vtable static + TPIE macro invocation for an empty trait impl.
865///
866/// When `#[miniextendr] impl Trait for Type {}` has no method bodies, this
867/// generates the vtable static and delegates to the `__mx_impl_{Trait}!` macro
868/// (generated at the trait definition site) to expand C/R wrappers.
869///
870/// Requires a fully qualified trait path (at least 2 segments) so the TPIE macro
871/// can be resolved from the trait's crate root.
872fn generate_tpie_invocation(
873    trait_path: &syn::Path,
874    concrete_type: &syn::Type,
875    class_system: ClassSystem,
876    class_has_no_rd: bool,
877    internal: bool,
878    noexport: bool,
879) -> TokenStream {
880    let Some(trait_name) = trait_path.segments.last().map(|s| &s.ident) else {
881        return syn::Error::new_spanned(trait_path, "trait path must have at least one segment")
882            .into_compile_error();
883    };
884
885    let type_name_str = type_to_uppercase_name(concrete_type);
886    let trait_name_upper = trait_name.to_string().to_uppercase();
887    let trait_name_lower = trait_name.to_string().to_lowercase();
888
889    // Vtable static
890    let vtable_static_name = format_ident!("__VTABLE_{}_FOR_{}", trait_name_upper, type_name_str);
891    let vtable_type_name = format_ident!("{}VTable", trait_name);
892
893    // Build vtable type path (same module as trait, strip type args)
894    let mut vtable_type_path = trait_path.clone();
895    if let Some(last) = vtable_type_path.segments.last_mut() {
896        last.ident = vtable_type_name;
897        last.arguments = syn::PathArguments::None;
898    }
899
900    // Build builder function path
901    let mut builder_path = trait_path.clone();
902    if let Some(last) = builder_path.segments.last_mut() {
903        last.ident = format_ident!("__{}_build_vtable", trait_name_lower);
904        last.arguments = syn::PathArguments::None;
905    }
906
907    // Build TPIE macro path: crate_root::__mx_impl_TraitName
908    if trait_path.segments.len() < 2 {
909        return syn::Error::new_spanned(
910            trait_path,
911            "empty trait impl requires a fully qualified trait path \
912             (e.g., miniextendr_api::adapter_traits::RDebug) so the TPIE \
913             macro can be resolved",
914        )
915        .into_compile_error();
916    }
917
918    let crate_ident = &trait_path.segments[0].ident;
919    let macro_name = format_ident!("__mx_impl_{}", trait_name);
920    let class_system_ident = class_system.to_ident();
921    let no_rd_ident = if class_has_no_rd {
922        format_ident!("true")
923    } else {
924        format_ident!("false")
925    };
926    let internal_ident = if internal {
927        format_ident!("true")
928    } else {
929        format_ident!("false")
930    };
931    let noexport_ident = if noexport {
932        format_ident!("true")
933    } else {
934        format_ident!("false")
935    };
936
937    let source_loc_doc = crate::source_location_doc(trait_name.span());
938
939    quote::quote! {
940        #[doc(hidden)]
941        #[doc = #source_loc_doc]
942        #[unsafe(no_mangle)]
943        pub static #vtable_static_name: #vtable_type_path =
944            #builder_path::<#concrete_type>();
945
946        #crate_ident :: #macro_name !(#concrete_type, #trait_path, #class_system_ident, #no_rd_ident, #internal_ident, #noexport_ident);
947    }
948}
949
950#[cfg(test)]
951mod tests;
952// endregion