Skip to main content

miniextendr_api/trait_abi/
conv.rs

1//! # Type Conversion Helpers for Method Shims
2//!
3//! This module provides type conversion functions used by the generated
4//! method shims from `#[miniextendr]` traits. These functions wrap the existing
5//! [`TryFromSexp`] and [`IntoR`] traits with appropriate error handling.
6//!
7//! ## Purpose
8//!
9//! Method shims generated by `#[miniextendr]` traits need to:
10//! 1. Convert `SEXP` arguments to Rust types
11//! 2. Convert Rust return values to SEXP
12//! 3. Return structured argument errors inside the shim's unwind boundary
13//!
14//! The shims convert their arguments themselves, with the C wrappers'
15//! structured `kind = "conversion"` condition (#2029); this module holds the
16//! other conversions: return values, the View's arguments and results, and
17//! the arity check.
18//!
19//! ## Error Handling
20//!
21//! These helpers panic with a diagnostic on conversion or argument-count
22//! failure. The shim's `with_r_unwind_protect_shim` catches it, so Rust-owned
23//! values are dropped before returning across the C ABI. A View re-panics with
24//! that condition for the consumer's outer boundary. Call these helpers only
25//! inside such a boundary: their panics must not cross a C ABI unguarded.
26//!
27//! ## Thread Safety
28//!
29//! All functions in this module must be called on R's main thread,
30//! as they interact with R's `SEXP` values and error handling.
31//!
32//! [`TryFromSexp`]: crate::TryFromSexp
33//! [`IntoR`]: crate::IntoR
34
35use crate::SEXP;
36
37/// Return R's `NULL` value.
38///
39/// Convenience function for method shims returning nothing.
40///
41/// # Safety
42///
43/// - Must be called on R's main thread (accesses R_NilValue)
44///
45/// # Returns
46///
47/// R's `NULL` value (`R_NilValue`)
48#[inline]
49pub unsafe fn nil() -> SEXP {
50    crate::SEXP::nil()
51}
52
53/// Convert an R SEXP to a Rust type, panicking on failure.
54///
55/// Attempts [`TryFromSexp::try_from_sexp`](crate::TryFromSexp::try_from_sexp).
56/// Generated shims catch failures with `with_r_unwind_protect_shim`; View
57/// result conversions unwind to the consumer's outer boundary.
58///
59/// # Type Parameters
60///
61/// * `T` - Target Rust type (must implement [`TryFromSexp`])
62///
63/// # Arguments
64///
65/// * `x` - R value to convert
66///
67/// # Returns
68///
69/// The converted Rust value.
70///
71/// # Safety
72///
73/// - `x` must be a valid SEXP
74/// - Must be called on R's main thread
75///
76/// # Panics
77///
78/// Panics if conversion fails, using [`TryFromSexp`]'s error type's `Display`
79/// impl. The error is dropped during Rust unwinding.
80///
81/// # Example
82///
83/// ```ignore
84/// // In a method shim:
85/// let arg0: i32 = unsafe { from_sexp(argv[0]) };
86/// let arg1: String = unsafe { from_sexp(argv[1]) };
87/// ```
88///
89/// [`TryFromSexp`]: crate::TryFromSexp
90#[inline]
91pub unsafe fn from_sexp<T>(x: SEXP) -> T
92where
93    T: crate::TryFromSexp,
94    T::Error: std::fmt::Display,
95{
96    match T::try_from_sexp(x) {
97        Ok(val) => val,
98        Err(e) => { ::core::panicking::panic_fmt(format_args!("{0}", e)); }panic!("{e}"),
99    }
100}
101
102/// Convert a Rust value to an R SEXP.
103///
104/// Calls [`IntoR::into_sexp`](crate::IntoR::into_sexp) to produce an R value.
105/// Used in trait ABI method shims for the result, and by a trait's View for
106/// each argument, which the shim reads back with `TryFromSexp` (or
107/// `TryFromRoot` for borrowed views). So a trait-method parameter type needs
108/// both directions:
109/// the reading markers cross as their inner value
110/// ([`AsNumeric`](crate::convert::AsNumeric) as `Option<f64>`, and so on) and
111/// [`AsFromStr`](crate::convert::AsFromStr) as its text; `Missing<T>` goes
112/// through [`missing_to_sexp`] instead. Parameter checks such as `no_na` run
113/// in the implementing method's R-facing wrappers, so they check R arguments,
114/// not the typed values a View caller passes.
115///
116/// # Type Parameters
117///
118/// * `T` - Source Rust type (must implement [`IntoR`])
119///
120/// # Arguments
121///
122/// * `x` - Rust value to convert
123///
124/// # Returns
125///
126/// R SEXP representation of the value.
127///
128/// # Safety
129///
130/// - Must be called on R's main thread
131/// - The returned SEXP must be protected if used across R allocations
132///
133/// # Example
134///
135/// ```ignore
136/// // In a method shim:
137/// let result: f64 = self_ref.area();
138/// unsafe { to_sexp(result) }
139/// ```
140///
141/// [`IntoR`]: crate::IntoR
142#[inline]
143pub unsafe fn to_sexp<T>(x: T) -> SEXP
144where
145    T: crate::IntoR,
146{
147    x.into_sexp()
148}
149
150/// Convert a `Missing<T>` trait-method argument to the SEXP the vtable shim
151/// receives: [`Missing::Absent`](crate::Missing::Absent) is R's
152/// missing-argument sentinel, which the shim's `TryFromSexp for Missing<T>`
153/// reads back as `Absent` (the same sentinel an R wrapper forwards for an
154/// omitted argument), and `Present(value)` converts `value` with
155/// [`to_sexp`], so `T` needs `IntoR` like any other argument type
156/// (`Missing<AsNumeric>` crosses as the sentinel or the number).
157///
158/// `Missing<T>` has no `IntoR` impl on purpose: the sentinel is an argument
159/// marker, not a value to hand back to R. Only the View's argument array
160/// needs this direction.
161///
162/// # Safety
163///
164/// Same as [`to_sexp`].
165#[inline]
166pub unsafe fn missing_to_sexp<T>(x: crate::Missing<T>) -> SEXP
167where
168    T: crate::IntoR,
169{
170    match x {
171        crate::Missing::Absent => SEXP::missing_arg(),
172        crate::Missing::Present(value) => unsafe { to_sexp(value) },
173    }
174}
175
176/// Convert an R SEXP to a Rust type, returning a Result.
177///
178/// Unlike [`from_sexp`], this function returns a `Result` instead of
179/// panicking on failure. Useful when you want custom error handling.
180///
181/// # Type Parameters
182///
183/// * `T` - Target Rust type (must implement [`TryFromSexp`])
184///
185/// # Arguments
186///
187/// * `x` - R value to convert
188///
189/// # Returns
190///
191/// `Ok(T)` on success, `Err` with the conversion error on failure.
192///
193/// # Safety
194///
195/// - `x` must be a valid SEXP
196/// - Must be called on R's main thread
197///
198/// # Example
199///
200/// ```ignore
201/// match unsafe { try_from_sexp::<i32>(argv[0]) } {
202///     Ok(val) => { /* use val */ }
203///     Err(e) => {
204///         // Custom error handling
205///         panic!("argument 1: {e}");
206///     }
207/// }
208/// ```
209///
210/// [`TryFromSexp`]: crate::TryFromSexp
211#[inline]
212pub unsafe fn try_from_sexp<T>(x: SEXP) -> Result<T, T::Error>
213where
214    T: crate::TryFromSexp,
215{
216    T::try_from_sexp(x)
217}
218
219// region: Arity check
220
221/// Check that the number of arguments matches expected arity.
222///
223/// # Arguments
224///
225/// * `argc` - Actual number of arguments
226/// * `expected` - Expected number of arguments
227/// * `method_name` - Name of method (for error messages)
228///
229/// # Panics
230///
231/// Panics if `argc != expected`. Generated vtable shims call this inside
232/// `with_r_unwind_protect_shim` so a mismatch uses the tagged-condition
233/// transport and unwinds Rust-owned resources before returning.
234#[inline]
235pub fn check_arity(argc: i32, expected: i32, method_name: &str) {
236    if !(argc == expected) {
    {
        ::core::panicking::panic_fmt(format_args!("{0}: expected {1} arguments, got {2}",
                method_name, expected, argc));
    }
};assert!(
237        argc == expected,
238        "{method_name}: expected {expected} arguments, got {argc}"
239    );
240}
241// endregion