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