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