miniextendr_macros/c_wrapper_builder.rs
1//! Unified C wrapper generation for standalone functions and impl methods.
2//!
3//! This module provides shared infrastructure for generating C wrappers that:
4//! - Handle worker thread vs main thread execution strategies
5//! - Perform parameter conversion from SEXP to Rust types
6//! - Convert Rust return values back to SEXP
7//! - Properly handle panics and R errors
8//!
9//! The same infrastructure is used by both `#[miniextendr]` on standalone functions
10//! and `#[miniextendr(env|r6|s3|s4|s7)]` on impl blocks.
11
12use proc_macro2::TokenStream;
13use quote::{format_ident, quote};
14
15/// Thread execution strategy for C wrappers.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ThreadStrategy {
18 /// Execute on main R thread with `with_r_unwind_protect`. **Default.**
19 ///
20 /// All code runs on R's main thread. Errors are returned as tagged SEXP values
21 /// (via `make_rust_condition_value`) and the R wrapper raises structured
22 /// condition objects. Simpler execution model with better R integration.
23 ///
24 /// Also required when:
25 /// - Function takes SEXP inputs (not Send)
26 /// - Function returns raw SEXP
27 /// - Instance method (self_ptr isn't Send)
28 /// - Function uses variadic dots (Dots type isn't Send)
29 /// - `#[miniextendr(check_interrupt)]` used
30 MainThread,
31
32 /// Execute on worker thread with panic catching. **Opt-in via `#[miniextendr(worker)]`.**
33 ///
34 /// Structure:
35 /// 1. Argument conversion on main thread
36 /// 2. Function execution on worker thread via `run_on_worker`
37 /// 3. SEXP conversion on main thread with `with_r_unwind_protect`
38 WorkerThread,
39}
40
41/// Strategy for converting a Rust return value into an R `SEXP`.
42///
43/// Determined automatically by [`detect_return_handling`] from the function's return type,
44/// or set explicitly via [`CWrapperContextBuilder::return_handling`]. Each variant
45/// handles a different return type pattern, controlling how the C wrapper converts
46/// the Rust value back to R and how errors/None values are surfaced.
47#[derive(Debug, Clone)]
48pub enum ReturnHandling {
49 /// Returns unit type `()` -- emits `R_NilValue`.
50 Unit,
51 /// Returns raw `SEXP` -- passes the value through unchanged (no conversion).
52 RawSexp,
53 /// Returns `Self` -- wraps the value in an `ExternalPtr` via `ExternalPtr::new`.
54 ExternalPtr,
55 /// Returns an arbitrary type `T: IntoR` -- converts via `IntoR::into_sexp`.
56 IntoR,
57 /// Returns `Option<()>` -- raises an error on `None`, otherwise emits `R_NilValue`.
58 OptionUnit,
59 /// Returns `Option<SEXP>` -- raises an error on `None`, otherwise passes through.
60 OptionSexp,
61 /// Returns `Option<T>` where `Option<T>: IntoR` -- calls `IntoR::into_sexp` on the whole
62 /// `Option` value. Suitable when the type has a direct `impl IntoR for Option<T>` (e.g.,
63 /// `Option<&T>`, `Option<Vec<T>>`, `Option<i32>`). `None` maps to whatever the `IntoR`
64 /// impl returns (typically NULL or NA).
65 ///
66 /// Use this variant explicitly via [`CWrapperContextBuilder::return_handling`] when
67 /// the type has a direct `IntoR` impl for the whole `Option`. The auto-detector
68 /// [`detect_return_handling`] conservatively returns [`OptionIntoRUnwrap`] instead
69 /// since it cannot resolve trait impls at macro expansion time.
70 #[allow(dead_code)]
71 // Used via explicit return_handling() call; auto-detect uses OptionIntoRUnwrap
72 OptionIntoR,
73 /// Returns `Option<T>` where `T: IntoR` -- unwraps the option first, then converts the
74 /// inner value via `IntoR::into_sexp`. Raises an error on `None`. Suitable when `T: IntoR`
75 /// but `Option<T>` doesn't have a direct `IntoR` impl (e.g., `Option<SomeExternalPtr>`).
76 OptionIntoRUnwrap,
77 /// Returns `Result<(), E>` -- raises an error on `Err`, otherwise emits `R_NilValue`.
78 ResultUnit,
79 /// Returns `Result<SEXP, E>` -- raises an error on `Err`, otherwise passes through.
80 ResultSexp,
81 /// Returns `Result<T, E>` -- raises an error on `Err`, otherwise converts via `IntoR::into_sexp`.
82 ResultIntoR,
83 /// Returns `Result<T, ()>` -- maps `Err(())` to `Err(NullOnErr)` then converts via `IntoR`.
84 /// `None`/`Err` maps to R `NULL` (unit error is a deliberate sentinel, not a Rust failure).
85 ResultNullOnErr,
86 /// Returns `T` where `T: IntoList` -- wraps in `AsList(result)` then calls `IntoR::into_sexp`.
87 ///
88 /// Produced when `#[miniextendr(prefer = "list")]` is used on a function returning `T: IntoList`.
89 /// Distinct from returning `AsList<T>` explicitly only in that the wrapping is generated
90 /// by the macro rather than written by the user.
91 AsListOf,
92 /// Returns `T` where `T: IntoExternalPtr` -- wraps in `AsExternalPtr(result)` then calls `IntoR::into_sexp`.
93 ///
94 /// Produced when `#[miniextendr(prefer = "externalptr")]` is used on a function returning
95 /// `T: IntoExternalPtr`. Distinct from the existing `ExternalPtr` variant (which boxes `Self`
96 /// via `ExternalPtr::new`): this variant calls `IntoExternalPtr::into_external_ptr()` on `T`.
97 AsExternalPtrOf,
98 /// Returns `T` where `T: RNativeType` -- wraps in `AsRNative(result)` then calls `IntoR::into_sexp`.
99 ///
100 /// Produced when `#[miniextendr(prefer = "native")]` is used on a function returning `T: RNativeType`.
101 AsNativeOf,
102}
103
104/// All information needed to generate a C wrapper function for an R-exported Rust item.
105///
106/// This struct abstracts over the differences between standalone `#[miniextendr]` functions
107/// and `impl` block methods (R6, S3, S4, S7, Env). It is constructed via
108/// [`CWrapperContextBuilder`] and consumed by [`CWrapperContext::generate`], which emits
109/// both the `extern "C-unwind"` wrapper and the corresponding `R_CallMethodDef` constant.
110pub struct CWrapperContext {
111 /// Identifier of the original Rust function or method being wrapped.
112 pub fn_ident: syn::Ident,
113 /// Identifier for the generated C wrapper (e.g., `C_foo` or `C_Type__method`).
114 pub c_ident: syn::Ident,
115 /// Identifier of the `R_WRAPPER_*` or `R_WRAPPERS_IMPL_*` const that holds the
116 /// generated R wrapper code string. Used for rustdoc cross-references.
117 pub r_wrapper_const: syn::Ident,
118 /// Function parameters (excluding the `self` receiver for methods).
119 /// Each parameter becomes a `SEXP` argument in the C wrapper signature.
120 pub inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
121 /// The original Rust return type. Used by strict-mode to inspect whether the inner
122 /// type is lossy (e.g., `i64`, `u64`) and needs checked conversion.
123 pub output: syn::ReturnType,
124 /// Statements emitted before the call expression. For instance methods, this
125 /// includes extracting `self` from the `ExternalPtr` SEXP.
126 pub pre_call: Vec<TokenStream>,
127 /// The actual Rust call expression (e.g., `my_func(arg0, arg1)` or
128 /// `self_ref.method(arg0)`). Inserted into the wrapper body after conversions.
129 pub call_expr: TokenStream,
130 /// Whether to run on the main R thread or dispatch to the worker thread.
131 pub thread_strategy: ThreadStrategy,
132 /// How to convert the Rust return value into a `SEXP` for R.
133 pub return_handling: ReturnHandling,
134 /// When `true`, all parameters use coercing conversion (`Rf_coerceVector`) instead
135 /// of strict type-matching. Set by `#[miniextendr(coerce)]`.
136 pub coerce_all: bool,
137 /// Names of individual parameters that use coercing conversion.
138 /// Set by `#[miniextendr(coerce = "param_name")]`.
139 pub coerce_params: Vec<String>,
140 /// When `true`, emits `R_CheckUserInterrupt()` before the call expression.
141 /// Set by `#[miniextendr(check_interrupt)]`.
142 pub check_interrupt: bool,
143 /// When `true`, wraps the call in `GetRNGstate()`/`PutRNGstate()` for R's
144 /// random number generator state management. Set by `#[miniextendr(rng)]`.
145 pub rng: bool,
146 /// `#[cfg(...)]` attributes from the original item, propagated to the C wrapper
147 /// and `call_method_def` constant so they are conditionally compiled.
148 pub cfg_attrs: Vec<syn::Attribute>,
149 /// For methods: the type identifier (e.g., `MyStruct`). Used in doc comments
150 /// and default `call_method_def` naming. `None` for standalone functions.
151 pub type_context: Option<syn::Ident>,
152 /// Whether the original method has a `self` receiver. When `true`, the C wrapper
153 /// includes a `self_sexp` parameter before the regular arguments.
154 pub has_self: bool,
155 /// Override for the `call_method_def` constant name. If `None`, defaults to
156 /// `call_method_def_{type}_{method}` (methods) or `call_method_def_{fn}` (standalone).
157 pub call_method_def_ident: Option<syn::Ident>,
158 /// When `true`, uses `checked_into_sexp_*` for lossy return types (`i64`, `u64`,
159 /// `isize`, `usize` and their `Vec` variants) instead of regular `IntoR::into_sexp`.
160 /// Set by `#[miniextendr(strict)]`.
161 pub strict: bool,
162 /// Parameter names with `#[miniextendr(match_arg, several_ok)]` — use
163 /// `match_arg_vec_from_sexp` (instead of `TryFromSexp`) for the Vec<T> conversion
164 /// so each element is validated against the enum's `MatchArg::CHOICES`.
165 ///
166 /// Scalar `match_arg` doesn't need entries here because R-side `match.arg()`
167 /// already narrowed the SEXP to a valid choice; the default `TryFromSexp for Enum`
168 /// (generated by `#[derive(MatchArg)]`) decodes it.
169 pub match_arg_several_ok_params: Vec<String>,
170 /// When `true`, preserve original parameter names from `inputs` in the C wrapper
171 /// signature instead of renaming to `arg_0`, `arg_1`, ... The fn path preserves
172 /// user identifiers for rustdoc visibility; impl method path uses `arg_N` for safety.
173 pub preserve_param_names: bool,
174 /// Visibility of the generated `extern "C-unwind"` wrapper function.
175 /// Default: [`syn::Visibility::Inherited`] (no visibility keyword).
176 /// Standalone `#[miniextendr]` fns forward the user's visibility (`pub`, `pub(crate)`, etc.).
177 pub vis: syn::Visibility,
178 /// Generic parameters of the wrapped function, emitted on the C wrapper signature
179 /// as `fn #c_ident #generics(...)`. Default: empty (no generics).
180 pub generics: syn::Generics,
181 /// When `true`, the original Rust fn is already an `extern "C-unwind"` symbol (user-written).
182 /// Skip generating the wrapper body but still emit the `R_CallMethodDef` for registration.
183 /// The `numArgs` count excludes the synthetic `__miniextendr_call` SEXP parameter since
184 /// the user-written fn doesn't have it.
185 pub skip_wrapper: bool,
186}
187
188impl CWrapperContext {
189 /// Creates a new [`CWrapperContextBuilder`] with the given function and C wrapper identifiers.
190 ///
191 /// All other fields start at their defaults (empty/false/None). Use the builder methods
192 /// to configure the context, then call [`CWrapperContextBuilder::build`] to finalize.
193 pub fn builder(fn_ident: syn::Ident, c_ident: syn::Ident) -> CWrapperContextBuilder {
194 CWrapperContextBuilder {
195 fn_ident,
196 c_ident,
197 r_wrapper_const: None,
198 inputs: syn::punctuated::Punctuated::new(),
199 output: syn::ReturnType::Default,
200 pre_call: Vec::new(),
201 call_expr: None,
202 thread_strategy: None,
203 return_handling: None,
204 coerce_all: false,
205 coerce_params: Vec::new(),
206 check_interrupt: false,
207 rng: false,
208 cfg_attrs: Vec::new(),
209 type_context: None,
210 has_self: false,
211 call_method_def_ident: None,
212 strict: false,
213 match_arg_several_ok_params: Vec::new(),
214 preserve_param_names: false,
215 vis: syn::Visibility::Inherited,
216 generics: syn::Generics::default(),
217 skip_wrapper: false,
218 }
219 }
220
221 /// Generates the complete output for this wrapper: an `extern "C-unwind"` function
222 /// and an `R_CallMethodDef` constant, both decorated with `#[cfg(...)]` attributes
223 /// if present.
224 ///
225 /// When `skip_wrapper` is set (for user-written `extern "C-unwind"` fns), only the
226 /// `R_CallMethodDef` is emitted — the fn body itself is already the C symbol.
227 ///
228 /// Dispatches to [`generate_main_thread_wrapper`](Self::generate_main_thread_wrapper) or
229 /// [`generate_worker_thread_wrapper`](Self::generate_worker_thread_wrapper) based on
230 /// [`thread_strategy`](Self::thread_strategy).
231 pub fn generate(&self) -> TokenStream {
232 let call_method_def = self.generate_call_method_def();
233
234 let cfg_attrs = &self.cfg_attrs;
235
236 if self.skip_wrapper {
237 // User-written extern "C-unwind" fn — only emit the registration entry
238 quote! {
239 #(#cfg_attrs)*
240 #call_method_def
241 }
242 } else {
243 let c_wrapper = match self.thread_strategy {
244 ThreadStrategy::MainThread => self.generate_main_thread_wrapper(),
245 ThreadStrategy::WorkerThread => self.generate_worker_thread_wrapper(),
246 };
247
248 quote! {
249 #(#cfg_attrs)*
250 #c_wrapper
251
252 #(#cfg_attrs)*
253 #call_method_def
254 }
255 }
256 }
257
258 /// Builds the C wrapper's parameter list from the Rust function signature.
259 ///
260 /// Returns a tuple of:
261 /// - `c_params`: `SEXP` parameter declarations for the C wrapper signature. Always
262 /// starts with `__miniextendr_call` (the R call object for error context), followed
263 /// by `self_sexp` for instance methods, then `arg_0`, `arg_1`, ... for each input.
264 /// - `rust_args`: The original Rust parameter identifiers (used in the call expression).
265 /// - `sexp_idents`: The generated `arg_N` identifiers (used in SEXP-to-Rust conversions).
266 fn build_c_params(&self) -> (Vec<TokenStream>, Vec<syn::Ident>, Vec<syn::Ident>) {
267 let mut c_params: Vec<TokenStream> = Vec::new();
268 let mut rust_args: Vec<syn::Ident> = Vec::new();
269 let mut sexp_idents: Vec<syn::Ident> = Vec::new();
270
271 // First param is always __miniextendr_call for error context
272 c_params.push(quote!(__miniextendr_call: ::miniextendr_api::ffi::SEXP));
273
274 // For instance methods, add self_sexp parameter
275 if self.has_self {
276 c_params.push(quote!(self_sexp: ::miniextendr_api::ffi::SEXP));
277 }
278
279 // Add regular parameters
280 for (idx, arg) in self.inputs.iter().enumerate() {
281 if let syn::FnArg::Typed(pt) = arg
282 && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
283 {
284 let ident = &pat_ident.ident;
285 // When preserve_param_names is set, use the original parameter name
286 // (visible in rustdoc). Otherwise use arg_N for predictable mangling.
287 let param_ident = if self.preserve_param_names {
288 ident.clone()
289 } else {
290 format_ident!("arg_{}", idx)
291 };
292
293 c_params.push(quote!(#param_ident: ::miniextendr_api::ffi::SEXP));
294 rust_args.push(ident.clone());
295 sexp_idents.push(param_ident);
296 }
297 }
298
299 (c_params, rust_args, sexp_idents)
300 }
301
302 /// Generates `TryFromSexp` conversion statements for each parameter.
303 ///
304 /// Each statement converts an `arg_N: SEXP` into the corresponding Rust type
305 /// declared in the original function signature. Respects `strict` and `coerce` settings.
306 ///
307 /// Used by the main-thread wrapper where all conversions happen inline.
308 fn build_conversion_stmts(&self, sexp_idents: &[syn::Ident]) -> Vec<TokenStream> {
309 let mut builder = crate::RustConversionBuilder::new();
310 if self.strict {
311 builder = builder.with_strict();
312 }
313 if self.coerce_all {
314 builder = builder.with_coerce_all();
315 }
316 for param in &self.coerce_params {
317 builder = builder.with_coerce_param(param.clone());
318 }
319 for param in &self.match_arg_several_ok_params {
320 builder = builder.with_match_arg_several_ok(param.clone());
321 }
322 builder.build_conversions(&self.inputs, sexp_idents)
323 }
324
325 /// Build conversion statements split for worker thread execution.
326 ///
327 /// Returns (pre_closure, in_closure) statements:
328 /// - pre_closure: Run on main thread, produce owned values to move
329 /// - in_closure: Run inside worker closure, create borrows
330 fn build_conversion_stmts_split(
331 &self,
332 sexp_idents: &[syn::Ident],
333 ) -> (Vec<TokenStream>, Vec<TokenStream>) {
334 let mut builder = crate::RustConversionBuilder::new();
335 if self.strict {
336 builder = builder.with_strict();
337 }
338 if self.coerce_all {
339 builder = builder.with_coerce_all();
340 }
341 for param in &self.coerce_params {
342 builder = builder.with_coerce_param(param.clone());
343 }
344 for param in &self.match_arg_several_ok_params {
345 builder = builder.with_match_arg_several_ok(param.clone());
346 }
347
348 let mut all_pre = Vec::new();
349 let mut all_in = Vec::new();
350
351 for (arg, sexp_ident) in self.inputs.iter().zip(sexp_idents.iter()) {
352 if let syn::FnArg::Typed(pat_type) = arg {
353 let (owned, borrowed) = builder.build_conversion_split(pat_type, sexp_ident);
354 all_pre.extend(owned);
355 all_in.extend(borrowed);
356 }
357 }
358
359 (all_pre, all_in)
360 }
361
362 /// Generates an `extern "C-unwind"` wrapper that runs entirely on the R main thread.
363 ///
364 /// The wrapper body is enclosed in `with_r_unwind_protect`, which catches both Rust
365 /// panics and R longjmps and returns a tagged-condition SEXP on failure (the R-side
366 /// wrapper raises a structured condition). When `rng` is enabled, the call is
367 /// additionally wrapped in `catch_unwind` so that `PutRNGstate()` runs even on panic.
368 fn generate_main_thread_wrapper(&self) -> TokenStream {
369 let c_ident = &self.c_ident;
370 let vis = &self.vis;
371 let generics = &self.generics;
372 let (c_params, _, sexp_idents) = self.build_c_params();
373 let conversion_stmts = self.build_conversion_stmts(&sexp_idents);
374 let pre_call = &self.pre_call;
375 let call_expr = &self.call_expr;
376
377 let pre_call_checks = if self.check_interrupt {
378 quote! {
379 unsafe { ::miniextendr_api::ffi::R_CheckUserInterrupt(); }
380 }
381 } else {
382 TokenStream::new()
383 };
384
385 let return_handling = self.generate_return_handling(call_expr);
386
387 let doc = self.generate_doc_comment("main thread");
388 let source_loc_doc = crate::source_location_doc(self.fn_ident.span());
389
390 // Unwind protection returns tagged condition SEXP on panic; the R-side wrapper raises.
391 let unwind_protect_fn = quote! { ::miniextendr_api::unwind_protect::with_r_unwind_protect };
392
393 if self.rng {
394 // RNG variant: wrap in catch_unwind so we can call PutRNGstate before error handling.
395 // The wrapper always returns a tagged condition SEXP on panic; the R-side wrapper raises.
396 let rng_panic_handler = quote! {
397 ::miniextendr_api::error_value::make_rust_condition_value(
398 &::miniextendr_api::unwind_protect::panic_payload_to_string(&*payload),
399 ::miniextendr_api::error_value::kind::PANIC,
400 ::core::option::Option::None,
401 Some(__miniextendr_call),
402 )
403 };
404 quote! {
405 #[doc = #doc]
406 #[doc = #source_loc_doc]
407 #[doc = concat!("Generated from source file `", file!(), "`.")]
408 #[unsafe(no_mangle)]
409 #vis extern "C-unwind" fn #c_ident #generics(#(#c_params),*) -> ::miniextendr_api::ffi::SEXP {
410 unsafe { ::miniextendr_api::ffi::GetRNGstate(); }
411 let __result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
412 #unwind_protect_fn(
413 || {
414 #pre_call_checks
415 #(#pre_call)*
416 #(#conversion_stmts)*
417 #return_handling
418 },
419 Some(__miniextendr_call),
420 )
421 }));
422 // PutRNGstate runs after catch_unwind, before error handling
423 unsafe { ::miniextendr_api::ffi::PutRNGstate(); }
424 match __result {
425 Ok(sexp) => sexp,
426 Err(payload) => { #rng_panic_handler },
427 }
428 }
429 }
430 } else {
431 // Non-RNG variant: direct call to with_r_unwind_protect
432 quote! {
433 #[doc = #doc]
434 #[doc = #source_loc_doc]
435 #[doc = concat!("Generated from source file `", file!(), "`.")]
436 #[unsafe(no_mangle)]
437 #vis extern "C-unwind" fn #c_ident #generics(#(#c_params),*) -> ::miniextendr_api::ffi::SEXP {
438 #unwind_protect_fn(
439 || {
440 #pre_call_checks
441 #(#pre_call)*
442 #(#conversion_stmts)*
443 #return_handling
444 },
445 Some(__miniextendr_call),
446 )
447 }
448 }
449 }
450 }
451
452 /// Generates an `extern "C-unwind"` wrapper that dispatches to the worker thread.
453 ///
454 /// Structure:
455 /// 1. `GetRNGstate()` (if `rng` enabled)
456 /// 2. `catch_unwind` around the entire body
457 /// 3. Pre-closure conversions on the main thread (produces owned values)
458 /// 4. `run_on_worker` (returns `Result<T, String>`) with a
459 /// `move` closure containing in-closure conversions and the call expression
460 /// 5. Return conversion back on the main thread via `with_r_unwind_protect`
461 /// 6. `PutRNGstate()` (if `rng` enabled)
462 /// 7. Panic handling: either tagged error value or `Rf_errorcall`
463 fn generate_worker_thread_wrapper(&self) -> TokenStream {
464 let c_ident = &self.c_ident;
465 let vis = &self.vis;
466 let generics = &self.generics;
467 let (c_params, _, sexp_idents) = self.build_c_params();
468 let (pre_closure_stmts, in_closure_stmts) = self.build_conversion_stmts_split(&sexp_idents);
469 let pre_call = &self.pre_call;
470 let call_expr = &self.call_expr;
471
472 // Compile-time check: worker dispatch requires the `worker-thread` feature.
473 // Check both `worker-thread` (direct) and `default-worker` (implies worker-thread
474 // via miniextendr-api, but the user crate may only have the latter in its features).
475 let fn_name = self.fn_ident.to_string();
476 let feature_msg = format!(
477 "`#[miniextendr(worker)]` on `{fn_name}` requires the `worker-thread` cargo feature. \
478 Add `worker-thread = [\"miniextendr-api/worker-thread\"]` to your [features] in Cargo.toml."
479 );
480 let worker_feature_check = quote! {
481 #[cfg(not(any(feature = "worker-thread", feature = "default-worker")))]
482 compile_error!(#feature_msg);
483 };
484
485 let pre_call_checks = if self.check_interrupt {
486 quote! {
487 unsafe { ::miniextendr_api::ffi::R_CheckUserInterrupt(); }
488 }
489 } else {
490 TokenStream::new()
491 };
492
493 let (worker_body, return_conversion) = self.generate_worker_return_handling(call_expr);
494
495 let doc = self.generate_doc_comment("worker thread");
496 let source_loc_doc = crate::source_location_doc(self.fn_ident.span());
497
498 // RNG state management: GetRNGstate at start, PutRNGstate before returning/error handling
499 let (rng_get, rng_put) = if self.rng {
500 (
501 quote! { unsafe { ::miniextendr_api::ffi::GetRNGstate(); } },
502 quote! { unsafe { ::miniextendr_api::ffi::PutRNGstate(); } },
503 )
504 } else {
505 (TokenStream::new(), TokenStream::new())
506 };
507
508 // Panic error handling: return tagged error value (the only mode).
509 let panic_error_handling = quote! {
510 ::miniextendr_api::error_value::make_rust_condition_value(
511 &::miniextendr_api::unwind_protect::panic_payload_to_string(&*payload),
512 ::miniextendr_api::error_value::kind::PANIC,
513 ::core::option::Option::None,
514 Some(__miniextendr_call),
515 )
516 };
517
518 // run_on_worker returns Result; Err → tagged error value.
519 quote! {
520 #worker_feature_check
521
522 #[doc = #doc]
523 #[doc = #source_loc_doc]
524 #[doc = concat!("Generated from source file `", file!(), "`.")]
525 #[unsafe(no_mangle)]
526 #vis extern "C-unwind" fn #c_ident #generics(#(#c_params),*) -> ::miniextendr_api::ffi::SEXP {
527 #rng_get
528 let __miniextendr_panic_result = ::std::panic::catch_unwind(::std::panic::AssertUnwindSafe(move || {
529 #pre_call_checks
530 #(#pre_call)*
531 #(#pre_closure_stmts)*
532
533 match ::miniextendr_api::worker::run_on_worker(move || {
534 #(#in_closure_stmts)*
535 #worker_body
536 }) {
537 Ok(__miniextendr_result) => {
538 #return_conversion
539 }
540 Err(__panic_msg) => {
541 ::miniextendr_api::error_value::make_rust_condition_value(
542 &__panic_msg, ::miniextendr_api::error_value::kind::PANIC, ::core::option::Option::None, Some(__miniextendr_call),
543 )
544 }
545 }
546 }));
547 #rng_put
548 match __miniextendr_panic_result {
549 Ok(sexp) => sexp,
550 Err(payload) => {
551 #panic_error_handling
552 },
553 }
554 }
555 }
556 }
557
558 /// Generates the inline return-handling code for the main-thread wrapper.
559 ///
560 /// Emits the call expression followed by conversion logic based on [`ReturnHandling`].
561 /// For `Option`/`Result` variants, also emits error-path code that returns a
562 /// tagged condition SEXP (which the R-side wrapper raises).
563 fn generate_return_handling(&self, call_expr: &TokenStream) -> TokenStream {
564 let fn_ident = &self.fn_ident;
565
566 match &self.return_handling {
567 ReturnHandling::Unit => {
568 quote! {
569 #call_expr;
570 ::miniextendr_api::ffi::SEXP::nil()
571 }
572 }
573 ReturnHandling::RawSexp => {
574 quote! {
575 #call_expr
576 }
577 }
578 ReturnHandling::ExternalPtr => {
579 quote! {
580 let __result = #call_expr;
581 ::miniextendr_api::into_r::IntoR::into_sexp(
582 ::miniextendr_api::externalptr::ExternalPtr::new(__result)
583 )
584 }
585 }
586 ReturnHandling::IntoR => {
587 let result_ident = format_ident!("__result");
588 let conversion = self.sexp_conversion_expr(&result_ident);
589 quote! {
590 let #result_ident = #call_expr;
591 #conversion
592 }
593 }
594 ReturnHandling::OptionUnit => {
595 let error_msg = format!("miniextendr function `{}` returned None", fn_ident);
596 quote! {
597 let __result = #call_expr;
598 if __result.is_none() {
599 return ::miniextendr_api::error_value::make_rust_condition_value(
600 #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
601 );
602 }
603 ::miniextendr_api::ffi::SEXP::nil()
604 }
605 }
606 ReturnHandling::OptionSexp => {
607 let error_msg = format!("miniextendr function `{}` returned None", fn_ident);
608 quote! {
609 let __result = #call_expr;
610 match __result {
611 Some(v) => v,
612 None => return ::miniextendr_api::error_value::make_rust_condition_value(
613 #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
614 ),
615 }
616 }
617 }
618 ReturnHandling::OptionIntoR => {
619 // For Option<T> where Option<T>: IntoR (e.g. Option<&T>, Option<Vec<T>>).
620 // Call into_sexp on the whole Option — IntoR impl handles None → NULL/NA.
621 let result_ident = format_ident!("__result");
622 let conversion = self.sexp_conversion_expr(&result_ident);
623 quote! {
624 let #result_ident = #call_expr;
625 #conversion
626 }
627 }
628 ReturnHandling::OptionIntoRUnwrap => {
629 // For Option<T> where T: IntoR but Option<T>: IntoR is not available.
630 // Unwraps first (raises error on None), then converts T via IntoR.
631 let error_msg = format!("miniextendr function `{}` returned None", fn_ident);
632 let result_ident = format_ident!("__result");
633 let conversion = self.sexp_conversion_expr(&result_ident);
634 quote! {
635 let __result = #call_expr;
636 let #result_ident = match __result {
637 Some(v) => v,
638 None => return ::miniextendr_api::error_value::make_rust_condition_value(
639 #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
640 ),
641 };
642 #conversion
643 }
644 }
645 ReturnHandling::ResultUnit => {
646 quote! {
647 let __result = #call_expr;
648 if let Err(e) = __result {
649 return ::miniextendr_api::error_value::make_rust_condition_value(
650 &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
651 );
652 }
653 ::miniextendr_api::ffi::SEXP::nil()
654 }
655 }
656 ReturnHandling::ResultSexp => {
657 quote! {
658 let __result = #call_expr;
659 match __result {
660 Ok(v) => v,
661 Err(e) => return ::miniextendr_api::error_value::make_rust_condition_value(
662 &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
663 ),
664 }
665 }
666 }
667 ReturnHandling::ResultIntoR => {
668 let result_ident = format_ident!("__result");
669 let conversion = self.sexp_conversion_expr(&result_ident);
670 quote! {
671 let __result = #call_expr;
672 let #result_ident = match __result {
673 Ok(v) => v,
674 Err(e) => return ::miniextendr_api::error_value::make_rust_condition_value(
675 &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
676 ),
677 };
678 #conversion
679 }
680 }
681 // Result<T, ()>: unit error is a deliberate sentinel — always return NULL on Err.
682 ReturnHandling::ResultNullOnErr => {
683 let result_ident = format_ident!("__result");
684 let conversion = self.sexp_conversion_expr(&result_ident);
685 quote! {
686 let __result = #call_expr;
687 match __result {
688 Ok(#result_ident) => #conversion,
689 Err(()) => ::miniextendr_api::ffi::SEXP::nil(),
690 }
691 }
692 }
693 ReturnHandling::AsListOf => {
694 quote! {
695 let __result = #call_expr;
696 ::miniextendr_api::into_r::IntoR::into_sexp(
697 ::miniextendr_api::convert::AsList(__result)
698 )
699 }
700 }
701 ReturnHandling::AsExternalPtrOf => {
702 quote! {
703 let __result = #call_expr;
704 ::miniextendr_api::into_r::IntoR::into_sexp(
705 ::miniextendr_api::convert::AsExternalPtr(__result)
706 )
707 }
708 }
709 ReturnHandling::AsNativeOf => {
710 quote! {
711 let __result = #call_expr;
712 ::miniextendr_api::into_r::IntoR::into_sexp(
713 ::miniextendr_api::convert::AsRNative(__result)
714 )
715 }
716 }
717 }
718 }
719
720 /// Generates return-handling code split between worker and main threads.
721 ///
722 /// Returns `(worker_body, return_conversion)`:
723 /// - `worker_body`: Runs inside the `run_on_worker` closure. Contains just the call
724 /// expression (the worker returns the raw `Option`/`Result` for the main thread
725 /// to inspect).
726 /// - `return_conversion`: Runs back on the main thread after the worker returns.
727 /// Converts the Rust value to SEXP (via `with_r_unwind_protect`). For `Option`
728 /// and `Result` variants, error checking happens here and produces a tagged
729 /// condition SEXP that the R-side wrapper raises.
730 fn generate_worker_return_handling(
731 &self,
732 call_expr: &TokenStream,
733 ) -> (TokenStream, TokenStream) {
734 let fn_ident = &self.fn_ident;
735
736 match &self.return_handling {
737 ReturnHandling::Unit => {
738 let worker = quote! {
739 #call_expr;
740 };
741 let convert = quote! {
742 ::miniextendr_api::ffi::SEXP::nil()
743 };
744 (worker, convert)
745 }
746 ReturnHandling::RawSexp => {
747 // Raw SEXP can't use worker thread - this shouldn't happen
748 // but handle it gracefully
749 let worker = quote! {
750 #call_expr
751 };
752 let convert = quote! {
753 __miniextendr_result
754 };
755 (worker, convert)
756 }
757 ReturnHandling::ExternalPtr => {
758 let worker = quote! {
759 #call_expr
760 };
761 let unwind_fn = self.worker_conversion_unwind_fn();
762 let convert = quote! {
763 #unwind_fn(
764 || ::miniextendr_api::into_r::IntoR::into_sexp(
765 ::miniextendr_api::externalptr::ExternalPtr::new(__miniextendr_result)
766 ),
767 None,
768 )
769 };
770 (worker, convert)
771 }
772 ReturnHandling::IntoR => {
773 let worker = quote! {
774 #call_expr
775 };
776 let result_ident = format_ident!("__miniextendr_result");
777 let conversion = self.sexp_conversion_expr(&result_ident);
778 let unwind_fn = self.worker_conversion_unwind_fn();
779 let convert = quote! {
780 #unwind_fn(
781 || #conversion,
782 None,
783 )
784 };
785 (worker, convert)
786 }
787 ReturnHandling::OptionUnit => {
788 let error_msg = format!("miniextendr function `{}` returned None", fn_ident);
789 // Return the Option from worker, check on main thread.
790 let worker = quote! { #call_expr };
791 let convert = quote! {
792 if __miniextendr_result.is_none() {
793 ::miniextendr_api::error_value::make_rust_condition_value(
794 #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
795 )
796 } else {
797 ::miniextendr_api::ffi::SEXP::nil()
798 }
799 };
800 (worker, convert)
801 }
802 ReturnHandling::OptionSexp => {
803 let error_msg = format!("miniextendr function `{}` returned None", fn_ident);
804 let worker = quote! { #call_expr };
805 let convert = quote! {
806 match __miniextendr_result {
807 Some(v) => v,
808 None => ::miniextendr_api::error_value::make_rust_condition_value(
809 #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
810 ),
811 }
812 };
813 (worker, convert)
814 }
815 ReturnHandling::OptionIntoR => {
816 // For Option<T> where Option<T>: IntoR, call into_sexp on the whole Option.
817 // The worker returns the raw Option<T>; the main thread converts via IntoR.
818 // None maps to whatever IntoR for Option<T> returns (NULL/NA) — not an error.
819 let worker = quote! { #call_expr };
820 let result_ident = format_ident!("__miniextendr_result");
821 let conversion = self.sexp_conversion_expr(&result_ident);
822 let unwind_fn = self.worker_conversion_unwind_fn();
823 let convert = quote! {
824 {
825 let #result_ident = __miniextendr_result;
826 #unwind_fn(|| #conversion, None)
827 }
828 };
829 (worker, convert)
830 }
831 ReturnHandling::OptionIntoRUnwrap => {
832 // For Option<T> where T: IntoR but Option<T>: IntoR is not available.
833 // Unwraps first (raises error on None), then converts T via IntoR.
834 let error_msg = format!("miniextendr function `{}` returned None", fn_ident);
835 // Return the Option from worker, check on main thread.
836 let worker = quote! { #call_expr };
837 let result_ident = format_ident!("__miniextendr_result");
838 let conversion = self.sexp_conversion_expr(&result_ident);
839 let unwind_fn = self.worker_conversion_unwind_fn();
840 let convert = quote! {
841 match __miniextendr_result {
842 Some(#result_ident) => #unwind_fn(|| #conversion, None),
843 None => ::miniextendr_api::error_value::make_rust_condition_value(
844 #error_msg, ::miniextendr_api::error_value::kind::NONE_ERR, ::core::option::Option::None, Some(__miniextendr_call),
845 ),
846 }
847 };
848 (worker, convert)
849 }
850 ReturnHandling::ResultUnit => {
851 let worker = quote! { #call_expr };
852 let convert = quote! {
853 match __miniextendr_result {
854 Ok(()) => ::miniextendr_api::ffi::SEXP::nil(),
855 Err(e) => ::miniextendr_api::error_value::make_rust_condition_value(
856 &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
857 ),
858 }
859 };
860 (worker, convert)
861 }
862 ReturnHandling::ResultSexp => {
863 let worker = quote! { #call_expr };
864 let convert = quote! {
865 match __miniextendr_result {
866 Ok(v) => v,
867 Err(e) => ::miniextendr_api::error_value::make_rust_condition_value(
868 &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
869 ),
870 }
871 };
872 (worker, convert)
873 }
874 ReturnHandling::ResultIntoR => {
875 let worker = quote! { #call_expr };
876 let result_ident = format_ident!("__miniextendr_result");
877 let conversion = self.sexp_conversion_expr(&result_ident);
878 let unwind_fn = self.worker_conversion_unwind_fn();
879 let convert = quote! {
880 match __miniextendr_result {
881 Ok(#result_ident) => #unwind_fn(
882 || #conversion,
883 None,
884 ),
885 Err(e) => ::miniextendr_api::error_value::make_rust_condition_value(
886 &format!("{:?}", e), ::miniextendr_api::error_value::kind::RESULT_ERR, ::core::option::Option::None, Some(__miniextendr_call),
887 ),
888 }
889 };
890 (worker, convert)
891 }
892 // Result<T, ()>: unit error is a deliberate sentinel — always map to NULL.
893 // Convert via NullOnErr so IntoR returns R NULL on Err.
894 ReturnHandling::ResultNullOnErr => {
895 let result_ident = format_ident!("__miniextendr_result");
896 let unwind_fn = self.worker_conversion_unwind_fn();
897 let worker = quote! { #call_expr };
898 let conversion = self.sexp_conversion_expr(&result_ident);
899 let convert = quote! {
900 match __miniextendr_result {
901 Ok(#result_ident) => #unwind_fn(|| #conversion, None),
902 Err(()) => ::miniextendr_api::ffi::SEXP::nil(),
903 }
904 };
905 (worker, convert)
906 }
907 ReturnHandling::AsListOf => {
908 let worker = quote! { #call_expr };
909 let unwind_fn = self.worker_conversion_unwind_fn();
910 let convert = quote! {
911 #unwind_fn(
912 || ::miniextendr_api::into_r::IntoR::into_sexp(
913 ::miniextendr_api::convert::AsList(__miniextendr_result)
914 ),
915 None,
916 )
917 };
918 (worker, convert)
919 }
920 ReturnHandling::AsExternalPtrOf => {
921 let worker = quote! { #call_expr };
922 let unwind_fn = self.worker_conversion_unwind_fn();
923 let convert = quote! {
924 #unwind_fn(
925 || ::miniextendr_api::into_r::IntoR::into_sexp(
926 ::miniextendr_api::convert::AsExternalPtr(__miniextendr_result)
927 ),
928 None,
929 )
930 };
931 (worker, convert)
932 }
933 ReturnHandling::AsNativeOf => {
934 let worker = quote! { #call_expr };
935 let unwind_fn = self.worker_conversion_unwind_fn();
936 let convert = quote! {
937 #unwind_fn(
938 || ::miniextendr_api::into_r::IntoR::into_sexp(
939 ::miniextendr_api::convert::AsRNative(__miniextendr_result)
940 ),
941 None,
942 )
943 };
944 (worker, convert)
945 }
946 }
947 }
948
949 /// Returns the unwind protection function for worker-thread conversion steps.
950 /// Always returns tagged condition SEXP on conversion panics; the R-side wrapper raises.
951 fn worker_conversion_unwind_fn(&self) -> TokenStream {
952 quote! { ::miniextendr_api::unwind_protect::with_r_unwind_protect }
953 }
954
955 /// Returns the SEXP conversion expression for `result_ident`, using strict
956 /// checked conversion if strict mode is on and the inner return type is lossy,
957 /// otherwise falling back to `IntoR::into_sexp()`.
958 fn sexp_conversion_expr(&self, result_ident: &syn::Ident) -> TokenStream {
959 if self.strict {
960 // Extract effective inner type from output
961 let inner_ty = match &self.output {
962 syn::ReturnType::Type(_, ty) => {
963 let ty = ty.as_ref();
964 // Check for Option<T> or Result<T, E> wrappers
965 if let syn::Type::Path(p) = ty
966 && let Some(seg) = p.path.segments.last()
967 {
968 let name = seg.ident.to_string();
969 if (name == "Option" || name == "Result")
970 && let Some(inner) = first_type_argument(seg)
971 {
972 Some(inner)
973 } else {
974 Some(ty)
975 }
976 } else {
977 Some(ty)
978 }
979 }
980 syn::ReturnType::Default => None,
981 };
982
983 if let Some(inner_ty) = inner_ty.and_then(|ty| {
984 crate::return_type_analysis::strict_conversion_for_type(ty, result_ident)
985 }) {
986 return inner_ty;
987 }
988 }
989
990 quote! { ::miniextendr_api::into_r::IntoR::into_sexp(#result_ident) }
991 }
992
993 /// Generates the `R_CallMethodDef` constant for R's `.Call` interface registration.
994 ///
995 /// The constant contains the C symbol name, a `DL_FUNC` pointer to the wrapper
996 /// (obtained via `transmute`), and the argument count. R uses this at package load
997 /// time (via `R_registerRoutines`) to register the native routine.
998 fn generate_call_method_def(&self) -> TokenStream {
999 let fn_ident = &self.fn_ident;
1000 let c_ident = &self.c_ident;
1001 // When skip_wrapper is set, the user-written fn doesn't have the synthetic
1002 // __miniextendr_call SEXP param — use the actual input count. Otherwise
1003 // use build_c_params() which includes __miniextendr_call + self_sexp.
1004 let num_args = if self.skip_wrapper {
1005 self.inputs
1006 .iter()
1007 .filter(|arg| matches!(arg, syn::FnArg::Typed(_)))
1008 .count()
1009 } else {
1010 let (c_params, _, _) = self.build_c_params();
1011 c_params.len()
1012 };
1013 let num_args_lit = syn::LitInt::new(&num_args.to_string(), proc_macro2::Span::call_site());
1014
1015 let c_ident_name = syn::LitCStr::new(
1016 std::ffi::CString::new(c_ident.to_string())
1017 .expect("valid C string")
1018 .as_c_str(),
1019 c_ident.span(),
1020 );
1021
1022 // Use custom call_method_def_ident if set, otherwise use default naming
1023 let call_method_def_ident = self.call_method_def_ident.clone().unwrap_or_else(|| {
1024 if let Some(ref type_ident) = self.type_context {
1025 format_ident!("call_method_def_{}_{}", type_ident, fn_ident)
1026 } else {
1027 format_ident!("call_method_def_{}", fn_ident)
1028 }
1029 });
1030
1031 // Build func_ptr_def for transmute
1032 let func_ptr_def: Vec<syn::Type> = (0..num_args)
1033 .map(|_| syn::parse_quote!(::miniextendr_api::ffi::SEXP))
1034 .collect();
1035
1036 let item_label = if let Some(ref type_ident) = self.type_context {
1037 format!("`{}::{}`", type_ident, fn_ident)
1038 } else {
1039 format!("`{}`", fn_ident)
1040 };
1041 let doc = format!(
1042 "R call method definition for {} (C wrapper: [`{}`]).",
1043 item_label, c_ident
1044 );
1045 let doc_example = format!(
1046 "Value: `R_CallMethodDef {{ name: \"{}\", numArgs: {}, fun: <DL_FUNC> }}`",
1047 c_ident, num_args
1048 );
1049 let source_loc_doc = crate::source_location_doc(self.fn_ident.span());
1050
1051 quote! {
1052 #[doc = #doc]
1053 #[doc = #doc_example]
1054 #[doc = #source_loc_doc]
1055 #[doc = concat!("Generated from source file `", file!(), "`.")]
1056 #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_CALL_DEFS), linkme(crate = ::miniextendr_api::linkme))]
1057 #[allow(non_upper_case_globals)]
1058 #[allow(non_snake_case)]
1059 static #call_method_def_ident: ::miniextendr_api::ffi::R_CallMethodDef = unsafe {
1060 ::miniextendr_api::ffi::R_CallMethodDef {
1061 name: #c_ident_name.as_ptr(),
1062 fun: Some(std::mem::transmute::<
1063 unsafe extern "C-unwind" fn(#(#func_ptr_def),*) -> ::miniextendr_api::ffi::SEXP,
1064 unsafe extern "C-unwind" fn() -> *mut ::std::os::raw::c_void
1065 >(#c_ident)),
1066 numArgs: #num_args_lit,
1067 }
1068 };
1069 }
1070 }
1071
1072 /// Generates a rustdoc comment string for the C wrapper function.
1073 ///
1074 /// Includes the original function/method name, thread strategy label, and a
1075 /// cross-reference to the R wrapper constant.
1076 fn generate_doc_comment(&self, thread_info: &str) -> String {
1077 if let Some(ref type_ident) = self.type_context {
1078 format!(
1079 "C wrapper for [`{}::{}`] ({}). See [`{}`] for R wrapper.",
1080 type_ident, self.fn_ident, thread_info, self.r_wrapper_const
1081 )
1082 } else {
1083 format!(
1084 "C wrapper for [`{}`] ({}). See [`{}`] for R wrapper.",
1085 self.fn_ident, thread_info, self.r_wrapper_const
1086 )
1087 }
1088 }
1089}
1090
1091/// Builder for [`CWrapperContext`].
1092///
1093/// Created via [`CWrapperContext::builder`]. All fields except `fn_ident` and `c_ident`
1094/// (provided at construction) default to empty/false/None. Required fields (`call_expr`,
1095/// `r_wrapper_const`) must be set before calling [`build`](Self::build) or it will panic.
1096///
1097/// Optional fields like `thread_strategy` and `return_handling` are auto-detected from
1098/// the function signature if not explicitly set.
1099pub struct CWrapperContextBuilder {
1100 /// Rust function/method identifier (set at construction).
1101 fn_ident: syn::Ident,
1102 /// C wrapper function identifier (set at construction).
1103 c_ident: syn::Ident,
1104 /// R wrapper constant identifier for doc cross-references. **Required.**
1105 r_wrapper_const: Option<syn::Ident>,
1106 /// Function parameters (excluding `self`). Defaults to empty.
1107 inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
1108 /// Rust return type. Defaults to `()` (no return type annotation).
1109 output: syn::ReturnType,
1110 /// Pre-call statements emitted before the call expression. Defaults to empty.
1111 pre_call: Vec<TokenStream>,
1112 /// The Rust call expression. **Required.**
1113 call_expr: Option<TokenStream>,
1114 /// Thread strategy override. If `None`, defaults to [`ThreadStrategy::MainThread`].
1115 thread_strategy: Option<ThreadStrategy>,
1116 /// Return handling override. If `None`, auto-detected from `output` via [`detect_return_handling`].
1117 return_handling: Option<ReturnHandling>,
1118 /// Enable coercing conversion for all parameters.
1119 coerce_all: bool,
1120 /// Names of individual parameters with coercing conversion enabled.
1121 coerce_params: Vec<String>,
1122 /// Emit `R_CheckUserInterrupt()` before the call.
1123 check_interrupt: bool,
1124 /// Wrap call in `GetRNGstate()`/`PutRNGstate()`.
1125 rng: bool,
1126 /// `#[cfg(...)]` attributes to propagate to generated items.
1127 cfg_attrs: Vec<syn::Attribute>,
1128 /// Type identifier for method context (e.g., `MyStruct`). `None` for standalone functions.
1129 type_context: Option<syn::Ident>,
1130 /// Whether the original method has a `self` receiver.
1131 has_self: bool,
1132 /// Custom `call_method_def` constant name override.
1133 call_method_def_ident: Option<syn::Ident>,
1134 /// Enable strict checked conversions for lossy return types.
1135 strict: bool,
1136 /// Parameter names with `match_arg + several_ok` — forwarded to
1137 /// `RustConversionBuilder::with_match_arg_several_ok` so each element of the
1138 /// Vec is decoded via `match_arg_vec_from_sexp` (enum's `MatchArg::CHOICES`).
1139 match_arg_several_ok_params: Vec<String>,
1140 /// When `true`, use original parameter names in C wrapper signature (for rustdoc).
1141 preserve_param_names: bool,
1142 /// Visibility of the generated `extern "C-unwind"` wrapper.
1143 vis: syn::Visibility,
1144 /// Generic parameters for the C wrapper signature.
1145 generics: syn::Generics,
1146 /// When `true`, skip wrapper body but still emit `R_CallMethodDef`.
1147 skip_wrapper: bool,
1148}
1149
1150impl CWrapperContextBuilder {
1151 /// Sets the R wrapper constant identifier (e.g., `R_WRAPPER_my_func`).
1152 /// **Required** -- [`build`](Self::build) panics if not set.
1153 pub fn r_wrapper_const(mut self, ident: syn::Ident) -> Self {
1154 self.r_wrapper_const = Some(ident);
1155 self
1156 }
1157
1158 /// Sets the function parameters (excluding `self` receiver).
1159 /// Each input becomes a `SEXP` argument in the C wrapper.
1160 pub fn inputs(
1161 mut self,
1162 inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
1163 ) -> Self {
1164 self.inputs = inputs;
1165 self
1166 }
1167
1168 /// Sets the Rust return type. Used for auto-detecting [`ReturnHandling`]
1169 /// and for strict-mode type inspection.
1170 pub fn output(mut self, output: syn::ReturnType) -> Self {
1171 self.output = output;
1172 self
1173 }
1174
1175 /// Sets pre-call statements emitted before the call expression.
1176 /// Typically used for self-extraction in instance methods.
1177 pub fn pre_call(mut self, stmts: Vec<TokenStream>) -> Self {
1178 self.pre_call = stmts;
1179 self
1180 }
1181
1182 /// Sets the Rust call expression (e.g., `my_func(arg0)` or `self_ref.method(arg0)`).
1183 /// **Required** -- [`build`](Self::build) panics if not set.
1184 pub fn call_expr(mut self, expr: TokenStream) -> Self {
1185 self.call_expr = Some(expr);
1186 self
1187 }
1188
1189 /// Overrides the thread strategy. If not called, defaults to [`ThreadStrategy::MainThread`].
1190 pub fn thread_strategy(mut self, strategy: ThreadStrategy) -> Self {
1191 self.thread_strategy = Some(strategy);
1192 self
1193 }
1194
1195 /// Overrides the return handling strategy. If not called, auto-detected from `output`
1196 /// via [`detect_return_handling`].
1197 pub fn return_handling(mut self, handling: ReturnHandling) -> Self {
1198 self.return_handling = Some(handling);
1199 self
1200 }
1201
1202 /// Enables coercing conversion for all parameters via `Rf_coerceVector`.
1203 pub fn coerce_all(mut self) -> Self {
1204 self.coerce_all = true;
1205 self
1206 }
1207
1208 /// Enables coercing conversion for a specific named parameter.
1209 pub fn with_coerce_param(mut self, param_name: String) -> Self {
1210 self.coerce_params.push(param_name);
1211 self
1212 }
1213
1214 /// Enables `R_CheckUserInterrupt()` before the call expression.
1215 pub fn check_interrupt(mut self) -> Self {
1216 self.check_interrupt = true;
1217 self
1218 }
1219
1220 /// Enable RNG state management (GetRNGstate/PutRNGstate).
1221 pub fn rng(mut self) -> Self {
1222 self.rng = true;
1223 self
1224 }
1225
1226 /// Sets `#[cfg(...)]` attributes to propagate to the C wrapper and `call_method_def`.
1227 pub fn cfg_attrs(mut self, attrs: Vec<syn::Attribute>) -> Self {
1228 self.cfg_attrs = attrs;
1229 self
1230 }
1231
1232 /// Sets the type context for methods (e.g., `MyStruct`). Used in doc comments
1233 /// and default `call_method_def` naming.
1234 pub fn type_context(mut self, type_ident: syn::Ident) -> Self {
1235 self.type_context = Some(type_ident);
1236 self
1237 }
1238
1239 /// Marks this as an instance method with a `self` receiver.
1240 /// Causes the C wrapper to include a `self_sexp` parameter.
1241 pub fn has_self(mut self) -> Self {
1242 self.has_self = true;
1243 self
1244 }
1245
1246 /// Enables strict checked conversions for lossy return types (`i64`, `u64`, `isize`,
1247 /// `usize` and their `Vec` variants).
1248 pub fn strict(mut self) -> Self {
1249 self.strict = true;
1250 self
1251 }
1252
1253 /// Record a parameter as `match_arg + several_ok`.
1254 ///
1255 /// Passed through to `RustConversionBuilder::with_match_arg_several_ok`, which
1256 /// switches that parameter's conversion from `TryFromSexp` to
1257 /// `match_arg_vec_from_sexp::<Inner>` so each STRSXP element is validated against
1258 /// the enum's `MatchArg::CHOICES`.
1259 pub fn match_arg_several_ok(mut self, param_name: String) -> Self {
1260 self.match_arg_several_ok_params.push(param_name);
1261 self
1262 }
1263
1264 /// Set a custom call_method_def identifier.
1265 ///
1266 /// If not set, the default naming is used:
1267 /// - With type_context: `call_method_def_{type}_{method}`
1268 /// - Without: `call_method_def_{method}`
1269 pub fn call_method_def_ident(mut self, ident: syn::Ident) -> Self {
1270 self.call_method_def_ident = Some(ident);
1271 self
1272 }
1273
1274 /// Preserve original parameter names in the C wrapper signature.
1275 ///
1276 /// When `true`, `build_c_params` uses the original identifier from `inputs` instead
1277 /// of renaming to `arg_N`. Enables rustdoc to show descriptive parameter names.
1278 /// Used by the standalone-fn path; impl methods use the default `arg_N` form.
1279 pub fn preserve_param_names(mut self) -> Self {
1280 self.preserve_param_names = true;
1281 self
1282 }
1283
1284 /// Set the visibility of the generated `extern "C-unwind"` wrapper.
1285 ///
1286 /// Defaults to [`syn::Visibility::Inherited`]. Standalone fns forward the user's
1287 /// declared visibility (`pub`, `pub(crate)`, etc.).
1288 pub fn vis(mut self, vis: syn::Visibility) -> Self {
1289 self.vis = vis;
1290 self
1291 }
1292
1293 /// Set the generic parameters for the C wrapper function signature.
1294 ///
1295 /// Defaults to empty generics. Standalone fns with generic parameters
1296 /// must forward them so the generated wrapper is also generic.
1297 pub fn generics(mut self, generics: syn::Generics) -> Self {
1298 self.generics = generics;
1299 self
1300 }
1301
1302 /// Skip generating the wrapper body and only emit the `R_CallMethodDef`.
1303 ///
1304 /// Use this when the Rust fn is already `extern "C-unwind"` with `#[no_mangle]` or
1305 /// `#[unsafe(no_mangle)]` (the user wrote the C symbol directly). The function still
1306 /// needs to be registered with R via `R_CallMethodDef`.
1307 ///
1308 /// When set, `numArgs` is computed from `inputs` directly (no synthetic
1309 /// `__miniextendr_call` param).
1310 pub fn skip_wrapper(mut self) -> Self {
1311 self.skip_wrapper = true;
1312 self
1313 }
1314
1315 /// Consumes the builder and returns a fully configured [`CWrapperContext`].
1316 ///
1317 /// If `thread_strategy` was not set, defaults to [`ThreadStrategy::MainThread`].
1318 /// If `return_handling` was not set, auto-detects from the `output` type via
1319 /// [`detect_return_handling`].
1320 ///
1321 /// # Panics
1322 ///
1323 /// Panics if `call_expr` or `r_wrapper_const` was not set.
1324 pub fn build(self) -> CWrapperContext {
1325 let call_expr = self
1326 .call_expr
1327 .expect("call_expr is required for CWrapperContext");
1328 let r_wrapper_const = self
1329 .r_wrapper_const
1330 .expect("r_wrapper_const is required for CWrapperContext");
1331
1332 // Detect thread strategy if not explicitly set.
1333 // Main thread is the default for all methods (safer, simpler execution model).
1334 let thread_strategy = self.thread_strategy.unwrap_or(ThreadStrategy::MainThread);
1335
1336 // Detect return handling if not explicitly set
1337 let return_handling = self
1338 .return_handling
1339 .unwrap_or_else(|| detect_return_handling(&self.output));
1340
1341 CWrapperContext {
1342 fn_ident: self.fn_ident,
1343 c_ident: self.c_ident,
1344 r_wrapper_const,
1345 inputs: self.inputs,
1346 output: self.output,
1347 pre_call: self.pre_call,
1348 call_expr,
1349 thread_strategy,
1350 return_handling,
1351 coerce_all: self.coerce_all,
1352 coerce_params: self.coerce_params,
1353 check_interrupt: self.check_interrupt,
1354 rng: self.rng,
1355 cfg_attrs: self.cfg_attrs,
1356 type_context: self.type_context,
1357 has_self: self.has_self,
1358 call_method_def_ident: self.call_method_def_ident,
1359 strict: self.strict,
1360 match_arg_several_ok_params: self.match_arg_several_ok_params,
1361 preserve_param_names: self.preserve_param_names,
1362 vis: self.vis,
1363 generics: self.generics,
1364 skip_wrapper: self.skip_wrapper,
1365 }
1366 }
1367}
1368
1369/// Detects the appropriate [`ReturnHandling`] strategy from a function's return type.
1370///
1371/// Inspects the `syn::ReturnType`:
1372/// - No return type annotation (`Default`) maps to [`ReturnHandling::Unit`].
1373/// - An explicit type is analyzed by [`detect_return_handling_from_type`].
1374pub fn detect_return_handling(output: &syn::ReturnType) -> ReturnHandling {
1375 match output {
1376 syn::ReturnType::Default => ReturnHandling::Unit,
1377 syn::ReturnType::Type(_, ty) => detect_return_handling_from_type(ty),
1378 }
1379}
1380
1381/// Detects [`ReturnHandling`] for the standalone-`#[miniextendr]`-fn path.
1382///
1383/// Identical to [`detect_return_handling`] except that general `Option<T>` maps to
1384/// [`ReturnHandling::OptionIntoR`] (call `into_sexp` on the whole Option, matching the
1385/// historical `analyze_return_type` behavior) rather than [`ReturnHandling::OptionIntoRUnwrap`]
1386/// (the default that preserves impl-method behavior). Use this when building a
1387/// [`CWrapperContext`] for a standalone function.
1388pub fn detect_return_handling_standalone_fn(output: &syn::ReturnType) -> ReturnHandling {
1389 let handling = detect_return_handling(output);
1390 // Standalone fns' old path called into_sexp(whole_option), which is OptionIntoR semantics.
1391 match handling {
1392 ReturnHandling::OptionIntoRUnwrap => ReturnHandling::OptionIntoR,
1393 other => other,
1394 }
1395}
1396
1397/// Determines the [`ReturnHandling`] variant for a concrete `syn::Type`.
1398///
1399/// Recognition rules:
1400/// - `()` -> [`Unit`](ReturnHandling::Unit)
1401/// - `Self` -> [`ExternalPtr`](ReturnHandling::ExternalPtr)
1402/// - `SEXP` -> [`RawSexp`](ReturnHandling::RawSexp)
1403/// - `Option<T>` -> recurses into `T` for `OptionUnit`, `OptionSexp`, or `OptionIntoRUnwrap`
1404/// - `Result<T, E>` -> recurses into `T` for `ResultUnit`, `ResultSexp`, or `ResultIntoR`
1405/// - Anything else -> [`IntoR`](ReturnHandling::IntoR)
1406///
1407/// Note: The default for `Option<T>` (non-unit, non-SEXP) is [`OptionIntoRUnwrap`] (unwrap
1408/// first, error on `None`), which preserves the historical behavior for impl methods.
1409/// Use [`ReturnHandling::OptionIntoR`] explicitly when the type has a direct
1410/// `impl IntoR for Option<T>` (e.g., `Option<&T>`, `Option<Vec<T>>`, `Option<i32>`).
1411fn detect_return_handling_from_type(ty: &syn::Type) -> ReturnHandling {
1412 match ty {
1413 // Unit tuple ()
1414 syn::Type::Tuple(t) if t.elems.is_empty() => ReturnHandling::Unit,
1415
1416 // Self - wrap in ExternalPtr
1417 syn::Type::Path(p)
1418 if p.path
1419 .segments
1420 .last()
1421 .map(|s| s.ident == "Self")
1422 .unwrap_or(false) =>
1423 {
1424 ReturnHandling::ExternalPtr
1425 }
1426
1427 // SEXP - pass through
1428 syn::Type::Path(p)
1429 if p.path
1430 .segments
1431 .last()
1432 .map(|s| s.ident == "SEXP")
1433 .unwrap_or(false) =>
1434 {
1435 ReturnHandling::RawSexp
1436 }
1437
1438 // Option<T>
1439 syn::Type::Path(p)
1440 if p.path
1441 .segments
1442 .last()
1443 .map(|s| s.ident == "Option")
1444 .unwrap_or(false) =>
1445 {
1446 if let Some(inner_ty) = first_type_argument(p.path.segments.last().unwrap()) {
1447 match inner_ty {
1448 syn::Type::Tuple(t) if t.elems.is_empty() => ReturnHandling::OptionUnit,
1449 syn::Type::Path(ip)
1450 if ip
1451 .path
1452 .segments
1453 .last()
1454 .map(|s| s.ident == "SEXP")
1455 .unwrap_or(false) =>
1456 {
1457 ReturnHandling::OptionSexp
1458 }
1459 _ => ReturnHandling::OptionIntoRUnwrap,
1460 }
1461 } else {
1462 ReturnHandling::OptionIntoRUnwrap
1463 }
1464 }
1465
1466 // Result<T, E>
1467 syn::Type::Path(p)
1468 if p.path
1469 .segments
1470 .last()
1471 .map(|s| s.ident == "Result")
1472 .unwrap_or(false) =>
1473 {
1474 let seg = p.path.segments.last().unwrap();
1475 // Special case: Result<T, ()> — unit error is a deliberate sentinel that maps to
1476 // R NULL, not a failure.
1477 let err_is_unit = crate::second_type_argument(seg)
1478 .is_some_and(|ty| matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty()));
1479 if err_is_unit {
1480 return ReturnHandling::ResultNullOnErr;
1481 }
1482 if let Some(ok_ty) = first_type_argument(seg) {
1483 match ok_ty {
1484 syn::Type::Tuple(t) if t.elems.is_empty() => ReturnHandling::ResultUnit,
1485 syn::Type::Path(ip)
1486 if ip
1487 .path
1488 .segments
1489 .last()
1490 .map(|s| s.ident == "SEXP")
1491 .unwrap_or(false) =>
1492 {
1493 ReturnHandling::ResultSexp
1494 }
1495 _ => ReturnHandling::ResultIntoR,
1496 }
1497 } else {
1498 ReturnHandling::ResultIntoR
1499 }
1500 }
1501
1502 // Everything else - use IntoR
1503 _ => ReturnHandling::IntoR,
1504 }
1505}
1506
1507/// Extracts the first generic type argument from a path segment's angle-bracketed arguments.
1508///
1509/// For example, given `Option<String>`, returns `Some(&String)`.
1510/// Returns `None` if the segment has no angle-bracketed arguments or no type arguments.
1511fn first_type_argument(seg: &syn::PathSegment) -> Option<&syn::Type> {
1512 if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
1513 for arg in ab.args.iter() {
1514 if let syn::GenericArgument::Type(ty) = arg {
1515 return Some(ty);
1516 }
1517 }
1518 }
1519 None
1520}
1521
1522#[cfg(test)]
1523mod tests;