1use crate::coerce::TryCoerce;
34use crate::from_r::TryFromSexp;
35use crate::into_r::IntoR;
36use crate::{SEXP, SEXPTYPE, SexpExt};
37
38#[inline]
43pub fn checked_into_sexp_i64(val: i64) -> SEXP {
44 if val > i32::MIN as i64 && val <= i32::MAX as i64 {
45 (val as i32).into_sexp()
46 } else {
47 panic!(
48 "strict conversion failed: i64 value {} is outside R integer range \
49 ({}..={}); use a non-strict function to allow lossy f64 widening",
50 val,
51 i32::MIN as i64 + 1,
52 i32::MAX
53 );
54 }
55}
56
57#[inline]
59pub fn checked_into_sexp_u64(val: u64) -> SEXP {
60 if val <= i32::MAX as u64 {
61 (val as i32).into_sexp()
62 } else {
63 panic!(
64 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
65 use a non-strict function to allow lossy f64 widening",
66 val,
67 i32::MAX
68 );
69 }
70}
71
72#[inline]
74pub fn checked_into_sexp_isize(val: isize) -> SEXP {
75 checked_into_sexp_i64(val as i64)
76}
77
78#[inline]
80pub fn checked_into_sexp_usize(val: usize) -> SEXP {
81 checked_into_sexp_u64(val as u64)
82}
83
84pub fn checked_vec_i64_into_sexp(val: Vec<i64>) -> SEXP {
86 let coerced: Vec<i32> = val
87 .into_iter()
88 .map(|x| {
89 if x > i32::MIN as i64 && x <= i32::MAX as i64 {
90 x as i32
91 } else {
92 panic!(
93 "strict conversion failed: i64 value {} is outside R integer range \
94 ({}..={}); use a non-strict function to allow lossy f64 widening",
95 x,
96 i32::MIN as i64 + 1,
97 i32::MAX
98 );
99 }
100 })
101 .collect();
102 coerced.into_sexp()
103}
104
105pub fn checked_vec_u64_into_sexp(val: Vec<u64>) -> SEXP {
107 let coerced: Vec<i32> = val
108 .into_iter()
109 .map(|x| {
110 if x <= i32::MAX as u64 {
111 x as i32
112 } else {
113 panic!(
114 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
115 use a non-strict function to allow lossy f64 widening",
116 x,
117 i32::MAX
118 );
119 }
120 })
121 .collect();
122 coerced.into_sexp()
123}
124
125pub fn checked_vec_isize_into_sexp(val: Vec<isize>) -> SEXP {
127 checked_vec_i64_into_sexp(val.into_iter().map(|x| x as i64).collect())
128}
129
130pub fn checked_vec_usize_into_sexp(val: Vec<usize>) -> SEXP {
132 checked_vec_u64_into_sexp(val.into_iter().map(|x| x as u64).collect())
133}
134
135pub fn checked_vec_option_i64_into_sexp(val: Vec<Option<i64>>) -> SEXP {
138 let coerced: Vec<Option<i32>> = val
139 .into_iter()
140 .map(|opt| match opt {
141 Some(x) => {
142 if x > i32::MIN as i64 && x <= i32::MAX as i64 {
143 Some(x as i32)
144 } else {
145 panic!(
146 "strict conversion failed: i64 value {} is outside R integer range \
147 ({}..={}); use a non-strict function to allow lossy f64 widening",
148 x,
149 i32::MIN as i64 + 1,
150 i32::MAX
151 );
152 }
153 }
154 None => None,
155 })
156 .collect();
157 coerced.into_sexp()
158}
159
160pub fn checked_vec_option_u64_into_sexp(val: Vec<Option<u64>>) -> SEXP {
162 let coerced: Vec<Option<i32>> = val
163 .into_iter()
164 .map(|opt| match opt {
165 Some(x) => {
166 if x <= i32::MAX as u64 {
167 Some(x as i32)
168 } else {
169 panic!(
170 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
171 use a non-strict function to allow lossy f64 widening",
172 x,
173 i32::MAX
174 );
175 }
176 }
177 None => None,
178 })
179 .collect();
180 coerced.into_sexp()
181}
182
183pub fn checked_vec_option_isize_into_sexp(val: Vec<Option<isize>>) -> SEXP {
185 checked_vec_option_i64_into_sexp(val.into_iter().map(|opt| opt.map(|x| x as i64)).collect())
186}
187
188pub fn checked_vec_option_usize_into_sexp(val: Vec<Option<usize>>) -> SEXP {
190 checked_vec_option_u64_into_sexp(val.into_iter().map(|opt| opt.map(|x| x as u64)).collect())
191}
192
193#[inline]
196pub fn checked_option_i64_into_sexp(val: Option<i64>) -> SEXP {
197 match val {
198 Some(x) => checked_into_sexp_i64(x),
199 None => Option::<i32>::None.into_sexp(),
200 }
201}
202
203#[inline]
206pub fn checked_option_u64_into_sexp(val: Option<u64>) -> SEXP {
207 match val {
208 Some(x) => checked_into_sexp_u64(x),
209 None => Option::<i32>::None.into_sexp(),
210 }
211}
212
213#[inline]
215pub fn checked_option_isize_into_sexp(val: Option<isize>) -> SEXP {
216 checked_option_i64_into_sexp(val.map(|x| x as i64))
217}
218
219#[inline]
221pub fn checked_option_usize_into_sexp(val: Option<usize>) -> SEXP {
222 checked_option_u64_into_sexp(val.map(|x| x as u64))
223}
224
225#[inline]
232pub fn checked_try_from_sexp_i64(sexp: SEXP, param: &str) -> i64 {
233 checked_try_from_sexp_numeric_scalar::<i64>(sexp, param)
234}
235
236#[inline]
238pub fn checked_try_from_sexp_u64(sexp: SEXP, param: &str) -> u64 {
239 checked_try_from_sexp_numeric_scalar::<u64>(sexp, param)
240}
241
242#[inline]
244pub fn checked_try_from_sexp_isize(sexp: SEXP, param: &str) -> isize {
245 let val = checked_try_from_sexp_i64(sexp, param);
246 isize::try_from(val).unwrap_or_else(|_| {
247 panic!(
248 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
249 param, val
250 )
251 })
252}
253
254#[inline]
256pub fn checked_try_from_sexp_usize(sexp: SEXP, param: &str) -> usize {
257 let val = checked_try_from_sexp_u64(sexp, param);
258 usize::try_from(val).unwrap_or_else(|_| {
259 panic!(
260 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
261 param, val
262 )
263 })
264}
265
266pub fn checked_vec_try_from_sexp_i64(sexp: SEXP, param: &str) -> Vec<i64> {
268 checked_vec_try_from_sexp_numeric::<i64>(sexp, param)
269}
270
271pub fn checked_vec_try_from_sexp_u64(sexp: SEXP, param: &str) -> Vec<u64> {
273 checked_vec_try_from_sexp_numeric::<u64>(sexp, param)
274}
275
276pub fn checked_vec_try_from_sexp_isize(sexp: SEXP, param: &str) -> Vec<isize> {
278 checked_vec_try_from_sexp_i64(sexp, param)
279 .into_iter()
280 .map(|x| {
281 isize::try_from(x).unwrap_or_else(|_| {
282 panic!(
283 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
284 param, x
285 )
286 })
287 })
288 .collect()
289}
290
291pub fn checked_vec_try_from_sexp_usize(sexp: SEXP, param: &str) -> Vec<usize> {
293 checked_vec_try_from_sexp_u64(sexp, param)
294 .into_iter()
295 .map(|x| {
296 usize::try_from(x).unwrap_or_else(|_| {
297 panic!(
298 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
299 param, x
300 )
301 })
302 })
303 .collect()
304}
305
306pub fn checked_vec_option_try_from_sexp_i64(sexp: SEXP, param: &str) -> Vec<Option<i64>> {
312 checked_vec_option_try_from_sexp_numeric::<i64>(sexp, param)
313}
314
315pub fn checked_vec_option_try_from_sexp_u64(sexp: SEXP, param: &str) -> Vec<Option<u64>> {
317 checked_vec_option_try_from_sexp_numeric::<u64>(sexp, param)
318}
319
320pub fn checked_vec_option_try_from_sexp_isize(sexp: SEXP, param: &str) -> Vec<Option<isize>> {
322 checked_vec_option_try_from_sexp_i64(sexp, param)
323 .into_iter()
324 .map(|opt| {
325 opt.map(|x| {
326 isize::try_from(x).unwrap_or_else(|_| {
327 panic!(
328 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
329 param, x
330 )
331 })
332 })
333 })
334 .collect()
335}
336
337pub fn checked_vec_option_try_from_sexp_usize(sexp: SEXP, param: &str) -> Vec<Option<usize>> {
339 checked_vec_option_try_from_sexp_u64(sexp, param)
340 .into_iter()
341 .map(|opt| {
342 opt.map(|x| {
343 usize::try_from(x).unwrap_or_else(|_| {
344 panic!(
345 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
346 param, x
347 )
348 })
349 })
350 })
351 .collect()
352}
353
354#[inline]
356fn checked_try_from_sexp_numeric_scalar<T>(sexp: SEXP, param: &str) -> T
357where
358 i32: TryCoerce<T>,
359 f64: TryCoerce<T>,
360 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
361 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
362{
363 let actual = sexp.type_of();
364 match actual {
365 SEXPTYPE::INTSXP => {
366 let value: i32 = TryFromSexp::try_from_sexp(sexp).unwrap_or_else(|e| {
367 panic!(
368 "strict conversion failed for parameter '{}': {:?}",
369 param, e
370 )
371 });
372 TryCoerce::<T>::try_coerce(value).unwrap_or_else(|e| {
373 panic!(
374 "strict conversion failed for parameter '{}': {:?}",
375 param, e
376 )
377 })
378 }
379 SEXPTYPE::REALSXP => {
380 let value: f64 = TryFromSexp::try_from_sexp(sexp).unwrap_or_else(|e| {
381 panic!(
382 "strict conversion failed for parameter '{}': {:?}",
383 param, e
384 )
385 });
386 TryCoerce::<T>::try_coerce(value).unwrap_or_else(|e| {
387 panic!(
388 "strict conversion failed for parameter '{}': {:?}",
389 param, e
390 )
391 })
392 }
393 _ => panic!(
394 "strict conversion failed for parameter '{}': expected integer or double, got {:?}",
395 param, actual
396 ),
397 }
398}
399
400fn checked_vec_try_from_sexp_numeric<T>(sexp: SEXP, param: &str) -> Vec<T>
402where
403 i32: TryCoerce<T>,
404 f64: TryCoerce<T>,
405 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
406 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
407{
408 let actual = sexp.type_of();
409 match actual {
410 SEXPTYPE::INTSXP => {
411 let slice: &[i32] = unsafe { sexp.as_slice() };
412 slice
413 .iter()
414 .copied()
415 .map(|v| {
416 TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
417 panic!(
418 "strict conversion failed for parameter '{}': {:?}",
419 param, e
420 )
421 })
422 })
423 .collect()
424 }
425 SEXPTYPE::REALSXP => {
426 let slice: &[f64] = unsafe { sexp.as_slice() };
427 slice
428 .iter()
429 .copied()
430 .map(|v| {
431 TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
432 panic!(
433 "strict conversion failed for parameter '{}': {:?}",
434 param, e
435 )
436 })
437 })
438 .collect()
439 }
440 _ => panic!(
441 "strict conversion failed for parameter '{}': expected integer or double vector, got {:?}",
442 param, actual
443 ),
444 }
445}
446
447fn checked_vec_option_try_from_sexp_numeric<T>(sexp: SEXP, param: &str) -> Vec<Option<T>>
453where
454 i32: TryCoerce<T>,
455 f64: TryCoerce<T>,
456 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
457 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
458{
459 let actual = sexp.type_of();
460 match actual {
461 SEXPTYPE::INTSXP => {
462 let slice: &[i32] = unsafe { sexp.as_slice() };
463 slice
464 .iter()
465 .copied()
466 .map(|v| {
467 if v == crate::altrep_traits::NA_INTEGER {
468 None
469 } else {
470 Some(TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
471 panic!(
472 "strict conversion failed for parameter '{}': {:?}",
473 param, e
474 )
475 }))
476 }
477 })
478 .collect()
479 }
480 SEXPTYPE::REALSXP => {
481 let slice: &[f64] = unsafe { sexp.as_slice() };
482 slice
483 .iter()
484 .copied()
485 .map(|v| {
486 if crate::from_r::is_na_real(v) {
487 None
488 } else {
489 Some(TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
490 panic!(
491 "strict conversion failed for parameter '{}': {:?}",
492 param, e
493 )
494 }))
495 }
496 })
497 .collect()
498 }
499 _ => panic!(
500 "strict conversion failed for parameter '{}': expected integer or double vector, got {:?}",
501 param, actual
502 ),
503 }
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn i64_in_range_succeeds() {
512 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(0));
515 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(42));
516 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(-1));
517 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MAX as i64));
518 }
519
520 #[test]
521 fn i64_out_of_range_panics() {
522 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i64::MAX));
523 assert!(result.is_err(), "should panic for i64::MAX");
524
525 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MIN as i64));
526 assert!(result.is_err(), "should panic for i32::MIN (NA_integer_)");
527
528 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MAX as i64 + 1));
529 assert!(result.is_err(), "should panic for i32::MAX + 1");
530 }
531
532 #[test]
533 fn u64_in_range_succeeds() {
534 let _ = std::panic::catch_unwind(|| checked_into_sexp_u64(0));
535 let _ = std::panic::catch_unwind(|| checked_into_sexp_u64(i32::MAX as u64));
536 }
537
538 #[test]
539 fn u64_out_of_range_panics() {
540 let result = std::panic::catch_unwind(|| checked_into_sexp_u64(i32::MAX as u64 + 1));
541 assert!(result.is_err());
542 }
543}
544