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. Handle conversion errors gracefully (via R errors)
13//!
14//! This module provides a uniform interface for these conversions.
15//!
16//! ## Error Handling
17//!
18//! Conversion errors raise R errors (via `Rf_error`), which unwinds
19//! back to R. This ensures that:
20//! - Invalid arguments produce helpful error messages
21//! - R's error handling mechanism is used consistently
22//! - No Rust panics escape to R
23//!
24//! ## Thread Safety
25//!
26//! All functions in this module must be called on R's main thread,
27//! as they interact with R's `SEXP` values and error handling.
28//!
29//! [`TryFromSexp`]: crate::TryFromSexp
30//! [`IntoR`]: crate::IntoR
31
32use crate::ffi::SEXP;
33
34/// Raise an R error with the given message.
35///
36/// This is a thin wrapper around `Rf_error` for use in method shims.
37/// It never returns (diverges to R's error handler).
38///
39/// # Arguments
40///
41/// * `msg` - Error message to display
42///
43/// # Safety
44///
45/// - Must be called on R's main thread
46/// - Never returns (uses R's `Rf_error` which longjmps)
47///
48/// # Example
49///
50/// ```ignore
51/// if argc != 2 {
52/// unsafe { rf_error("expected 2 arguments, got {argc}"); }
53/// }
54/// ```
55#[inline]
56pub unsafe fn rf_error(msg: &str) -> ! {
57 crate::error::r_stop(msg)
58}
59
60/// Return R's `NULL` value.
61///
62/// Convenience function for method shims returning nothing.
63///
64/// # Safety
65///
66/// - Must be called on R's main thread (accesses R_NilValue)
67///
68/// # Returns
69///
70/// R's `NULL` value (`R_NilValue`)
71#[inline]
72pub unsafe fn nil() -> SEXP {
73 crate::ffi::SEXP::nil()
74}
75
76/// Convert an R SEXP to a Rust type, aborting via `Rf_error` on failure.
77///
78/// Attempts [`TryFromSexp::try_from_sexp`](crate::TryFromSexp::try_from_sexp)
79/// and calls [`rf_error`] (R longjmp, never returns) if conversion fails.
80///
81/// # Type Parameters
82///
83/// * `T` - Target Rust type (must implement [`TryFromSexp`])
84///
85/// # Arguments
86///
87/// * `x` - R value to convert
88///
89/// # Returns
90///
91/// The converted Rust value.
92///
93/// # Safety
94///
95/// - `x` must be a valid SEXP
96/// - Must be called on R's main thread
97///
98/// # Errors
99///
100/// Calls [`rf_error`] (never returns) if conversion fails. Error messages
101/// are generated by [`TryFromSexp`]'s error type's `Display` impl.
102///
103/// # Example
104///
105/// ```ignore
106/// // In a method shim:
107/// let arg0: i32 = unsafe { from_sexp(argv[0]) };
108/// let arg1: String = unsafe { from_sexp(argv[1]) };
109/// ```
110///
111/// [`TryFromSexp`]: crate::TryFromSexp
112#[inline]
113pub unsafe fn from_sexp<T>(x: SEXP) -> T
114where
115 T: crate::TryFromSexp,
116 T::Error: std::fmt::Display,
117{
118 match T::try_from_sexp(x) {
119 Ok(val) => val,
120 Err(e) => unsafe { rf_error(&e.to_string()) },
121 }
122}
123
124/// Convert a Rust value to an R SEXP.
125///
126/// Calls [`IntoR::into_sexp`](crate::IntoR::into_sexp) to produce an R value.
127/// Used in trait ABI method shims.
128///
129/// # Type Parameters
130///
131/// * `T` - Source Rust type (must implement [`IntoR`])
132///
133/// # Arguments
134///
135/// * `x` - Rust value to convert
136///
137/// # Returns
138///
139/// R SEXP representation of the value.
140///
141/// # Safety
142///
143/// - Must be called on R's main thread
144/// - The returned SEXP must be protected if used across R allocations
145///
146/// # Example
147///
148/// ```ignore
149/// // In a method shim:
150/// let result: f64 = self_ref.area();
151/// unsafe { to_sexp(result) }
152/// ```
153///
154/// [`IntoR`]: crate::IntoR
155#[inline]
156pub unsafe fn to_sexp<T>(x: T) -> SEXP
157where
158 T: crate::IntoR,
159{
160 x.into_sexp()
161}
162
163/// Convert an R SEXP to a Rust type, returning a Result.
164///
165/// Unlike [`from_sexp`], this function returns a `Result` instead of
166/// calling `rf_error` on failure. Useful when you want custom error handling.
167///
168/// # Type Parameters
169///
170/// * `T` - Target Rust type (must implement [`TryFromSexp`])
171///
172/// # Arguments
173///
174/// * `x` - R value to convert
175///
176/// # Returns
177///
178/// `Ok(T)` on success, `Err` with the conversion error on failure.
179///
180/// # Safety
181///
182/// - `x` must be a valid SEXP
183/// - Must be called on R's main thread
184///
185/// # Example
186///
187/// ```ignore
188/// match unsafe { try_from_sexp::<i32>(argv[0]) } {
189/// Ok(val) => { /* use val */ }
190/// Err(e) => {
191/// // Custom error handling
192/// rf_error(&format!("argument 1: {}", e));
193/// }
194/// }
195/// ```
196///
197/// [`TryFromSexp`]: crate::TryFromSexp
198#[inline]
199pub unsafe fn try_from_sexp<T>(x: SEXP) -> Result<T, T::Error>
200where
201 T: crate::TryFromSexp,
202{
203 T::try_from_sexp(x)
204}
205
206// region: Argument extraction helpers
207
208/// Extract and convert an argument from argv with bounds checking.
209///
210/// Checks that `index < argc` before extracting, and provides a helpful
211/// error message if out of bounds.
212///
213/// # Type Parameters
214///
215/// * `T` - Target Rust type (must implement [`TryFromSexp`])
216///
217/// # Arguments
218///
219/// * `argc` - Number of arguments
220/// * `argv` - Pointer to argument array
221/// * `index` - Index of argument to extract
222/// * `name` - Name of argument (for error messages)
223///
224/// # Returns
225///
226/// The converted argument value.
227///
228/// # Safety
229///
230/// - `argv` must point to at least `argc` valid SEXPs
231/// - Must be called on R's main thread
232///
233/// # Errors
234///
235/// Calls [`rf_error`] if:
236/// - `index >= argc` (missing argument)
237/// - Conversion fails
238///
239/// # Example
240///
241/// ```ignore
242/// // In a method shim for fn foo(&self, x: i32, y: String)
243/// let x: i32 = unsafe { extract_arg(argc, argv, 0, "x") };
244/// let y: String = unsafe { extract_arg(argc, argv, 1, "y") };
245/// ```
246///
247/// [`TryFromSexp`]: crate::TryFromSexp
248#[inline]
249pub unsafe fn extract_arg<T>(argc: i32, argv: *const SEXP, index: usize, name: &str) -> T
250where
251 T: crate::TryFromSexp,
252 T::Error: std::fmt::Display,
253{
254 if index as i32 >= argc {
255 unsafe { rf_error(&format!("missing argument: {name}")) };
256 }
257 unsafe { from_sexp(*argv.add(index)) }
258}
259
260/// Check that the number of arguments matches expected arity.
261///
262/// # Arguments
263///
264/// * `argc` - Actual number of arguments
265/// * `expected` - Expected number of arguments
266/// * `method_name` - Name of method (for error messages)
267///
268/// # Safety
269///
270/// Must be called on R's main thread (may call `rf_error`).
271///
272/// # Errors
273///
274/// Calls [`rf_error`] if `argc != expected`.
275#[inline]
276pub unsafe fn check_arity(argc: i32, expected: i32, method_name: &str) {
277 if argc != expected {
278 unsafe {
279 rf_error(&format!(
280 "{method_name}: expected {expected} arguments, got {argc}"
281 ))
282 };
283 }
284}
285// endregion