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