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