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