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