miniextendr_api/
strict.rs1use crate::coerce::TryCoerce;
17use crate::ffi::{SEXP, SEXPTYPE, SexpExt};
18use crate::from_r::TryFromSexp;
19use crate::into_r::IntoR;
20
21#[inline]
26pub fn checked_into_sexp_i64(val: i64) -> SEXP {
27 if val > i32::MIN as i64 && val <= i32::MAX as i64 {
28 (val as i32).into_sexp()
29 } else {
30 panic!(
31 "strict conversion failed: i64 value {} is outside R integer range \
32 ({}..={}); use a non-strict function to allow lossy f64 widening",
33 val,
34 i32::MIN as i64 + 1,
35 i32::MAX
36 );
37 }
38}
39
40#[inline]
42pub fn checked_into_sexp_u64(val: u64) -> SEXP {
43 if val <= i32::MAX as u64 {
44 (val as i32).into_sexp()
45 } else {
46 panic!(
47 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
48 use a non-strict function to allow lossy f64 widening",
49 val,
50 i32::MAX
51 );
52 }
53}
54
55#[inline]
57pub fn checked_into_sexp_isize(val: isize) -> SEXP {
58 checked_into_sexp_i64(val as i64)
59}
60
61#[inline]
63pub fn checked_into_sexp_usize(val: usize) -> SEXP {
64 checked_into_sexp_u64(val as u64)
65}
66
67pub fn checked_vec_i64_into_sexp(val: Vec<i64>) -> SEXP {
69 let coerced: Vec<i32> = val
70 .into_iter()
71 .map(|x| {
72 if x > i32::MIN as i64 && x <= i32::MAX as i64 {
73 x as i32
74 } else {
75 panic!(
76 "strict conversion failed: i64 value {} is outside R integer range \
77 ({}..={}); use a non-strict function to allow lossy f64 widening",
78 x,
79 i32::MIN as i64 + 1,
80 i32::MAX
81 );
82 }
83 })
84 .collect();
85 coerced.into_sexp()
86}
87
88pub fn checked_vec_u64_into_sexp(val: Vec<u64>) -> SEXP {
90 let coerced: Vec<i32> = val
91 .into_iter()
92 .map(|x| {
93 if x <= i32::MAX as u64 {
94 x as i32
95 } else {
96 panic!(
97 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
98 use a non-strict function to allow lossy f64 widening",
99 x,
100 i32::MAX
101 );
102 }
103 })
104 .collect();
105 coerced.into_sexp()
106}
107
108pub fn checked_vec_isize_into_sexp(val: Vec<isize>) -> SEXP {
110 checked_vec_i64_into_sexp(val.into_iter().map(|x| x as i64).collect())
111}
112
113pub fn checked_vec_usize_into_sexp(val: Vec<usize>) -> SEXP {
115 checked_vec_u64_into_sexp(val.into_iter().map(|x| x as u64).collect())
116}
117
118pub fn checked_vec_option_i64_into_sexp(val: Vec<Option<i64>>) -> SEXP {
121 let coerced: Vec<Option<i32>> = val
122 .into_iter()
123 .map(|opt| match opt {
124 Some(x) => {
125 if x > i32::MIN as i64 && x <= i32::MAX as i64 {
126 Some(x as i32)
127 } else {
128 panic!(
129 "strict conversion failed: i64 value {} is outside R integer range \
130 ({}..={}); use a non-strict function to allow lossy f64 widening",
131 x,
132 i32::MIN as i64 + 1,
133 i32::MAX
134 );
135 }
136 }
137 None => None,
138 })
139 .collect();
140 coerced.into_sexp()
141}
142
143pub fn checked_vec_option_u64_into_sexp(val: Vec<Option<u64>>) -> SEXP {
145 let coerced: Vec<Option<i32>> = val
146 .into_iter()
147 .map(|opt| match opt {
148 Some(x) => {
149 if x <= i32::MAX as u64 {
150 Some(x as i32)
151 } else {
152 panic!(
153 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
154 use a non-strict function to allow lossy f64 widening",
155 x,
156 i32::MAX
157 );
158 }
159 }
160 None => None,
161 })
162 .collect();
163 coerced.into_sexp()
164}
165
166pub fn checked_vec_option_isize_into_sexp(val: Vec<Option<isize>>) -> SEXP {
168 checked_vec_option_i64_into_sexp(val.into_iter().map(|opt| opt.map(|x| x as i64)).collect())
169}
170
171pub fn checked_vec_option_usize_into_sexp(val: Vec<Option<usize>>) -> SEXP {
173 checked_vec_option_u64_into_sexp(val.into_iter().map(|opt| opt.map(|x| x as u64)).collect())
174}
175
176#[inline]
179pub fn checked_option_i64_into_sexp(val: Option<i64>) -> SEXP {
180 match val {
181 Some(x) => checked_into_sexp_i64(x),
182 None => Option::<i32>::None.into_sexp(),
183 }
184}
185
186#[inline]
189pub fn checked_option_u64_into_sexp(val: Option<u64>) -> SEXP {
190 match val {
191 Some(x) => checked_into_sexp_u64(x),
192 None => Option::<i32>::None.into_sexp(),
193 }
194}
195
196#[inline]
198pub fn checked_option_isize_into_sexp(val: Option<isize>) -> SEXP {
199 checked_option_i64_into_sexp(val.map(|x| x as i64))
200}
201
202#[inline]
204pub fn checked_option_usize_into_sexp(val: Option<usize>) -> SEXP {
205 checked_option_u64_into_sexp(val.map(|x| x as u64))
206}
207
208#[inline]
215pub fn checked_try_from_sexp_i64(sexp: SEXP, param: &str) -> i64 {
216 checked_try_from_sexp_numeric_scalar::<i64>(sexp, param)
217}
218
219#[inline]
221pub fn checked_try_from_sexp_u64(sexp: SEXP, param: &str) -> u64 {
222 checked_try_from_sexp_numeric_scalar::<u64>(sexp, param)
223}
224
225#[inline]
227pub fn checked_try_from_sexp_isize(sexp: SEXP, param: &str) -> isize {
228 let val = checked_try_from_sexp_i64(sexp, param);
229 isize::try_from(val).unwrap_or_else(|_| {
230 panic!(
231 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
232 param, val
233 )
234 })
235}
236
237#[inline]
239pub fn checked_try_from_sexp_usize(sexp: SEXP, param: &str) -> usize {
240 let val = checked_try_from_sexp_u64(sexp, param);
241 usize::try_from(val).unwrap_or_else(|_| {
242 panic!(
243 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
244 param, val
245 )
246 })
247}
248
249pub fn checked_vec_try_from_sexp_i64(sexp: SEXP, param: &str) -> Vec<i64> {
251 checked_vec_try_from_sexp_numeric::<i64>(sexp, param)
252}
253
254pub fn checked_vec_try_from_sexp_u64(sexp: SEXP, param: &str) -> Vec<u64> {
256 checked_vec_try_from_sexp_numeric::<u64>(sexp, param)
257}
258
259pub fn checked_vec_try_from_sexp_isize(sexp: SEXP, param: &str) -> Vec<isize> {
261 checked_vec_try_from_sexp_i64(sexp, param)
262 .into_iter()
263 .map(|x| {
264 isize::try_from(x).unwrap_or_else(|_| {
265 panic!(
266 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
267 param, x
268 )
269 })
270 })
271 .collect()
272}
273
274pub fn checked_vec_try_from_sexp_usize(sexp: SEXP, param: &str) -> Vec<usize> {
276 checked_vec_try_from_sexp_u64(sexp, param)
277 .into_iter()
278 .map(|x| {
279 usize::try_from(x).unwrap_or_else(|_| {
280 panic!(
281 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
282 param, x
283 )
284 })
285 })
286 .collect()
287}
288
289#[inline]
291fn checked_try_from_sexp_numeric_scalar<T>(sexp: SEXP, param: &str) -> T
292where
293 i32: TryCoerce<T>,
294 f64: TryCoerce<T>,
295 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
296 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
297{
298 let actual = sexp.type_of();
299 match actual {
300 SEXPTYPE::INTSXP => {
301 let value: i32 = TryFromSexp::try_from_sexp(sexp).unwrap_or_else(|e| {
302 panic!(
303 "strict conversion failed for parameter '{}': {:?}",
304 param, e
305 )
306 });
307 TryCoerce::<T>::try_coerce(value).unwrap_or_else(|e| {
308 panic!(
309 "strict conversion failed for parameter '{}': {:?}",
310 param, e
311 )
312 })
313 }
314 SEXPTYPE::REALSXP => {
315 let value: f64 = TryFromSexp::try_from_sexp(sexp).unwrap_or_else(|e| {
316 panic!(
317 "strict conversion failed for parameter '{}': {:?}",
318 param, e
319 )
320 });
321 TryCoerce::<T>::try_coerce(value).unwrap_or_else(|e| {
322 panic!(
323 "strict conversion failed for parameter '{}': {:?}",
324 param, e
325 )
326 })
327 }
328 _ => panic!(
329 "strict conversion failed for parameter '{}': expected integer or double, got {:?}",
330 param, actual
331 ),
332 }
333}
334
335fn checked_vec_try_from_sexp_numeric<T>(sexp: SEXP, param: &str) -> Vec<T>
337where
338 i32: TryCoerce<T>,
339 f64: TryCoerce<T>,
340 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
341 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
342{
343 let actual = sexp.type_of();
344 match actual {
345 SEXPTYPE::INTSXP => {
346 let slice: &[i32] = unsafe { sexp.as_slice() };
347 slice
348 .iter()
349 .copied()
350 .map(|v| {
351 TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
352 panic!(
353 "strict conversion failed for parameter '{}': {:?}",
354 param, e
355 )
356 })
357 })
358 .collect()
359 }
360 SEXPTYPE::REALSXP => {
361 let slice: &[f64] = unsafe { sexp.as_slice() };
362 slice
363 .iter()
364 .copied()
365 .map(|v| {
366 TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
367 panic!(
368 "strict conversion failed for parameter '{}': {:?}",
369 param, e
370 )
371 })
372 })
373 .collect()
374 }
375 _ => panic!(
376 "strict conversion failed for parameter '{}': expected integer or double vector, got {:?}",
377 param, actual
378 ),
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn i64_in_range_succeeds() {
388 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(0));
391 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(42));
392 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(-1));
393 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MAX as i64));
394 }
395
396 #[test]
397 fn i64_out_of_range_panics() {
398 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i64::MAX));
399 assert!(result.is_err(), "should panic for i64::MAX");
400
401 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MIN as i64));
402 assert!(result.is_err(), "should panic for i32::MIN (NA_integer_)");
403
404 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MAX as i64 + 1));
405 assert!(result.is_err(), "should panic for i32::MAX + 1");
406 }
407
408 #[test]
409 fn u64_in_range_succeeds() {
410 let _ = std::panic::catch_unwind(|| checked_into_sexp_u64(0));
411 let _ = std::panic::catch_unwind(|| checked_into_sexp_u64(i32::MAX as u64));
412 }
413
414 #[test]
415 fn u64_out_of_range_panics() {
416 let result = std::panic::catch_unwind(|| checked_into_sexp_u64(i32::MAX as u64 + 1));
417 assert!(result.is_err());
418 }
419}
420