1use crate::coerce::TryCoerce;
34use crate::from_r::{BatchedErrors, SexpError, TryFromSexp};
35use crate::into_r::IntoR;
36use crate::{SEXP, SEXPTYPE, SexpExt};
37
38fn panic_strict_vec_batched(container: &str, errors: BatchedErrors) -> ! {
48 let SexpError::InvalidValue(msg) = errors.into_error(container) else {
49 unreachable!("BatchedErrors::into_error always returns SexpError::InvalidValue")
50 };
51 panic!(
52 "strict conversion failed: {msg}; use a non-strict function to allow lossy f64 widening"
53 );
54}
55
56#[inline]
61pub fn checked_into_sexp_i64(val: i64) -> SEXP {
62 if val > i32::MIN as i64 && val <= i32::MAX as i64 {
63 (val as i32).into_sexp()
64 } else {
65 panic!(
66 "strict conversion failed: i64 value {} is outside R integer range \
67 ({}..={}); use a non-strict function to allow lossy f64 widening",
68 val,
69 i32::MIN as i64 + 1,
70 i32::MAX
71 );
72 }
73}
74
75#[inline]
77pub fn checked_into_sexp_u64(val: u64) -> SEXP {
78 if val <= i32::MAX as u64 {
79 (val as i32).into_sexp()
80 } else {
81 panic!(
82 "strict conversion failed: u64 value {} exceeds R integer max ({}); \
83 use a non-strict function to allow lossy f64 widening",
84 val,
85 i32::MAX
86 );
87 }
88}
89
90#[inline]
92pub fn checked_into_sexp_isize(val: isize) -> SEXP {
93 checked_into_sexp_i64(val as i64)
94}
95
96#[inline]
98pub fn checked_into_sexp_usize(val: usize) -> SEXP {
99 checked_into_sexp_u64(val as u64)
100}
101
102pub fn checked_vec_i64_into_sexp(val: Vec<i64>) -> SEXP {
107 let mut coerced: Vec<i32> = Vec::with_capacity(val.len());
108 let mut errors = BatchedErrors::default();
109 for (i, x) in val.into_iter().enumerate() {
110 if x > i32::MIN as i64 && x <= i32::MAX as i64 {
111 coerced.push(x as i32);
112 } else {
113 errors.push(|| {
114 format!(
115 "invalid value at index {i}: i64 value {x} is outside R integer range \
116 ({}..={})",
117 i32::MIN as i64 + 1,
118 i32::MAX
119 )
120 });
121 }
122 }
123 if !errors.is_empty() {
124 panic_strict_vec_batched("Vec<i64>", errors);
125 }
126 coerced.into_sexp()
127}
128
129pub fn checked_vec_u64_into_sexp(val: Vec<u64>) -> SEXP {
134 let mut coerced: Vec<i32> = Vec::with_capacity(val.len());
135 let mut errors = BatchedErrors::default();
136 for (i, x) in val.into_iter().enumerate() {
137 if x <= i32::MAX as u64 {
138 coerced.push(x as i32);
139 } else {
140 errors.push(|| {
141 format!(
142 "invalid value at index {i}: u64 value {x} exceeds R integer max ({})",
143 i32::MAX
144 )
145 });
146 }
147 }
148 if !errors.is_empty() {
149 panic_strict_vec_batched("Vec<u64>", errors);
150 }
151 coerced.into_sexp()
152}
153
154pub fn checked_vec_isize_into_sexp(val: Vec<isize>) -> SEXP {
156 checked_vec_i64_into_sexp(val.into_iter().map(|x| x as i64).collect())
157}
158
159pub fn checked_vec_usize_into_sexp(val: Vec<usize>) -> SEXP {
161 checked_vec_u64_into_sexp(val.into_iter().map(|x| x as u64).collect())
162}
163
164pub fn checked_vec_option_i64_into_sexp(val: Vec<Option<i64>>) -> SEXP {
171 let mut coerced: Vec<Option<i32>> = Vec::with_capacity(val.len());
172 let mut errors = BatchedErrors::default();
173 for (i, opt) in val.into_iter().enumerate() {
174 match opt {
175 Some(x) => {
176 if x > i32::MIN as i64 && x <= i32::MAX as i64 {
177 coerced.push(Some(x as i32));
178 } else {
179 errors.push(|| {
180 format!(
181 "invalid value at index {i}: i64 value {x} is outside R integer range \
182 ({}..={})",
183 i32::MIN as i64 + 1,
184 i32::MAX
185 )
186 });
187 }
188 }
189 None => coerced.push(None),
190 }
191 }
192 if !errors.is_empty() {
193 panic_strict_vec_batched("Vec<Option<i64>>", errors);
194 }
195 coerced.into_sexp()
196}
197
198pub fn checked_vec_option_u64_into_sexp(val: Vec<Option<u64>>) -> SEXP {
203 let mut coerced: Vec<Option<i32>> = Vec::with_capacity(val.len());
204 let mut errors = BatchedErrors::default();
205 for (i, opt) in val.into_iter().enumerate() {
206 match opt {
207 Some(x) => {
208 if x <= i32::MAX as u64 {
209 coerced.push(Some(x as i32));
210 } else {
211 errors.push(|| {
212 format!(
213 "invalid value at index {i}: u64 value {x} exceeds R integer max ({})",
214 i32::MAX
215 )
216 });
217 }
218 }
219 None => coerced.push(None),
220 }
221 }
222 if !errors.is_empty() {
223 panic_strict_vec_batched("Vec<Option<u64>>", errors);
224 }
225 coerced.into_sexp()
226}
227
228pub fn checked_vec_option_isize_into_sexp(val: Vec<Option<isize>>) -> SEXP {
230 checked_vec_option_i64_into_sexp(val.into_iter().map(|opt| opt.map(|x| x as i64)).collect())
231}
232
233pub fn checked_vec_option_usize_into_sexp(val: Vec<Option<usize>>) -> SEXP {
235 checked_vec_option_u64_into_sexp(val.into_iter().map(|opt| opt.map(|x| x as u64)).collect())
236}
237
238#[inline]
241pub fn checked_option_i64_into_sexp(val: Option<i64>) -> SEXP {
242 match val {
243 Some(x) => checked_into_sexp_i64(x),
244 None => Option::<i32>::None.into_sexp(),
245 }
246}
247
248#[inline]
251pub fn checked_option_u64_into_sexp(val: Option<u64>) -> SEXP {
252 match val {
253 Some(x) => checked_into_sexp_u64(x),
254 None => Option::<i32>::None.into_sexp(),
255 }
256}
257
258#[inline]
260pub fn checked_option_isize_into_sexp(val: Option<isize>) -> SEXP {
261 checked_option_i64_into_sexp(val.map(|x| x as i64))
262}
263
264#[inline]
266pub fn checked_option_usize_into_sexp(val: Option<usize>) -> SEXP {
267 checked_option_u64_into_sexp(val.map(|x| x as u64))
268}
269
270#[inline]
277pub fn checked_try_from_sexp_i64(sexp: SEXP, param: &str) -> i64 {
278 checked_try_from_sexp_numeric_scalar::<i64>(sexp, param)
279}
280
281#[inline]
283pub fn checked_try_from_sexp_u64(sexp: SEXP, param: &str) -> u64 {
284 checked_try_from_sexp_numeric_scalar::<u64>(sexp, param)
285}
286
287#[inline]
289pub fn checked_try_from_sexp_isize(sexp: SEXP, param: &str) -> isize {
290 let val = checked_try_from_sexp_i64(sexp, param);
291 isize::try_from(val).unwrap_or_else(|_| {
292 panic!(
293 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
294 param, val
295 )
296 })
297}
298
299#[inline]
301pub fn checked_try_from_sexp_usize(sexp: SEXP, param: &str) -> usize {
302 let val = checked_try_from_sexp_u64(sexp, param);
303 usize::try_from(val).unwrap_or_else(|_| {
304 panic!(
305 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
306 param, val
307 )
308 })
309}
310
311pub fn checked_vec_try_from_sexp_i64(sexp: SEXP, param: &str) -> Vec<i64> {
313 checked_vec_try_from_sexp_numeric::<i64>(sexp, param)
314}
315
316pub fn checked_vec_try_from_sexp_u64(sexp: SEXP, param: &str) -> Vec<u64> {
318 checked_vec_try_from_sexp_numeric::<u64>(sexp, param)
319}
320
321pub fn checked_vec_try_from_sexp_isize(sexp: SEXP, param: &str) -> Vec<isize> {
323 checked_vec_try_from_sexp_i64(sexp, param)
324 .into_iter()
325 .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 .collect()
334}
335
336pub fn checked_vec_try_from_sexp_usize(sexp: SEXP, param: &str) -> Vec<usize> {
338 checked_vec_try_from_sexp_u64(sexp, param)
339 .into_iter()
340 .map(|x| {
341 usize::try_from(x).unwrap_or_else(|_| {
342 panic!(
343 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
344 param, x
345 )
346 })
347 })
348 .collect()
349}
350
351pub fn checked_vec_option_try_from_sexp_i64(sexp: SEXP, param: &str) -> Vec<Option<i64>> {
357 checked_vec_option_try_from_sexp_numeric::<i64>(sexp, param)
358}
359
360pub fn checked_vec_option_try_from_sexp_u64(sexp: SEXP, param: &str) -> Vec<Option<u64>> {
362 checked_vec_option_try_from_sexp_numeric::<u64>(sexp, param)
363}
364
365pub fn checked_vec_option_try_from_sexp_isize(sexp: SEXP, param: &str) -> Vec<Option<isize>> {
367 checked_vec_option_try_from_sexp_i64(sexp, param)
368 .into_iter()
369 .map(|opt| {
370 opt.map(|x| {
371 isize::try_from(x).unwrap_or_else(|_| {
372 panic!(
373 "strict conversion failed for parameter '{}': i64 value {} does not fit in isize",
374 param, x
375 )
376 })
377 })
378 })
379 .collect()
380}
381
382pub fn checked_vec_option_try_from_sexp_usize(sexp: SEXP, param: &str) -> Vec<Option<usize>> {
384 checked_vec_option_try_from_sexp_u64(sexp, param)
385 .into_iter()
386 .map(|opt| {
387 opt.map(|x| {
388 usize::try_from(x).unwrap_or_else(|_| {
389 panic!(
390 "strict conversion failed for parameter '{}': u64 value {} does not fit in usize",
391 param, x
392 )
393 })
394 })
395 })
396 .collect()
397}
398
399#[inline]
401fn checked_try_from_sexp_numeric_scalar<T>(sexp: SEXP, param: &str) -> 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 value: i32 = TryFromSexp::try_from_sexp(sexp).unwrap_or_else(|e| {
412 panic!(
413 "strict conversion failed for parameter '{}': {:?}",
414 param, e
415 )
416 });
417 TryCoerce::<T>::try_coerce(value).unwrap_or_else(|e| {
418 panic!(
419 "strict conversion failed for parameter '{}': {:?}",
420 param, e
421 )
422 })
423 }
424 SEXPTYPE::REALSXP => {
425 let value: f64 = TryFromSexp::try_from_sexp(sexp).unwrap_or_else(|e| {
426 panic!(
427 "strict conversion failed for parameter '{}': {:?}",
428 param, e
429 )
430 });
431 TryCoerce::<T>::try_coerce(value).unwrap_or_else(|e| {
432 panic!(
433 "strict conversion failed for parameter '{}': {:?}",
434 param, e
435 )
436 })
437 }
438 _ => panic!(
439 "strict conversion failed for parameter '{}': expected integer or double, got {:?}",
440 param, actual
441 ),
442 }
443}
444
445fn checked_vec_try_from_sexp_numeric<T>(sexp: SEXP, param: &str) -> Vec<T>
447where
448 i32: TryCoerce<T>,
449 f64: TryCoerce<T>,
450 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
451 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
452{
453 let actual = sexp.type_of();
454 match actual {
455 SEXPTYPE::INTSXP => {
456 let slice: &[i32] = unsafe { sexp.as_slice() };
457 slice
458 .iter()
459 .copied()
460 .map(|v| {
461 TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
462 panic!(
463 "strict conversion failed for parameter '{}': {:?}",
464 param, e
465 )
466 })
467 })
468 .collect()
469 }
470 SEXPTYPE::REALSXP => {
471 let slice: &[f64] = unsafe { sexp.as_slice() };
472 slice
473 .iter()
474 .copied()
475 .map(|v| {
476 TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
477 panic!(
478 "strict conversion failed for parameter '{}': {:?}",
479 param, e
480 )
481 })
482 })
483 .collect()
484 }
485 _ => panic!(
486 "strict conversion failed for parameter '{}': expected integer or double vector, got {:?}",
487 param, actual
488 ),
489 }
490}
491
492fn checked_vec_option_try_from_sexp_numeric<T>(sexp: SEXP, param: &str) -> Vec<Option<T>>
498where
499 i32: TryCoerce<T>,
500 f64: TryCoerce<T>,
501 <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
502 <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
503{
504 let actual = sexp.type_of();
505 match actual {
506 SEXPTYPE::INTSXP => {
507 let slice: &[i32] = unsafe { sexp.as_slice() };
508 slice
509 .iter()
510 .copied()
511 .map(|v| {
512 if v == crate::altrep_traits::NA_INTEGER {
513 None
514 } else {
515 Some(TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
516 panic!(
517 "strict conversion failed for parameter '{}': {:?}",
518 param, e
519 )
520 }))
521 }
522 })
523 .collect()
524 }
525 SEXPTYPE::REALSXP => {
526 let slice: &[f64] = unsafe { sexp.as_slice() };
527 slice
528 .iter()
529 .copied()
530 .map(|v| {
531 if crate::from_r::is_na_real(v) {
532 None
533 } else {
534 Some(TryCoerce::<T>::try_coerce(v).unwrap_or_else(|e| {
535 panic!(
536 "strict conversion failed for parameter '{}': {:?}",
537 param, e
538 )
539 }))
540 }
541 })
542 .collect()
543 }
544 _ => panic!(
545 "strict conversion failed for parameter '{}': expected integer or double vector, got {:?}",
546 param, actual
547 ),
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554
555 #[test]
556 fn i64_in_range_succeeds() {
557 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(0));
560 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(42));
561 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(-1));
562 let _ = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MAX as i64));
563 }
564
565 #[test]
566 fn i64_out_of_range_panics() {
567 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i64::MAX));
568 assert!(result.is_err(), "should panic for i64::MAX");
569
570 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MIN as i64));
571 assert!(result.is_err(), "should panic for i32::MIN (NA_integer_)");
572
573 let result = std::panic::catch_unwind(|| checked_into_sexp_i64(i32::MAX as i64 + 1));
574 assert!(result.is_err(), "should panic for i32::MAX + 1");
575 }
576
577 #[test]
578 fn u64_in_range_succeeds() {
579 let _ = std::panic::catch_unwind(|| checked_into_sexp_u64(0));
580 let _ = std::panic::catch_unwind(|| checked_into_sexp_u64(i32::MAX as u64));
581 }
582
583 #[test]
584 fn u64_out_of_range_panics() {
585 let result = std::panic::catch_unwind(|| checked_into_sexp_u64(i32::MAX as u64 + 1));
586 assert!(result.is_err());
587 }
588
589 fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
592 *payload
593 .downcast::<String>()
594 .expect("panic payload should be a String")
595 }
596
597 #[test]
598 fn vec_i64_batches_multiple_out_of_range_indices() {
599 let result =
600 std::panic::catch_unwind(|| checked_vec_i64_into_sexp(vec![1, i64::MAX, 2, i64::MIN]));
601 let msg = panic_message(result.expect_err("should panic for out-of-range elements"));
602 assert!(
603 msg.starts_with("strict conversion failed: Vec<i64> conversion failed:"),
604 "{msg}"
605 );
606 assert!(msg.contains("invalid value at index 1: i64 value"), "{msg}");
607 assert!(msg.contains("invalid value at index 3: i64 value"), "{msg}");
608 assert!(
609 !msg.contains("and "),
610 "should not summarize under the cap: {msg}"
611 );
612 assert!(
613 msg.ends_with("use a non-strict function to allow lossy f64 widening"),
614 "{msg}"
615 );
616 }
617
618 #[test]
619 fn vec_i64_batches_caps_and_summarizes_remainder() {
620 let vals: Vec<i64> = std::iter::repeat_n(i64::MAX, 15).collect();
621 let result = std::panic::catch_unwind(|| checked_vec_i64_into_sexp(vals));
622 let msg = panic_message(result.expect_err("should panic for out-of-range elements"));
623 assert!(msg.contains("and 5 more"), "{msg}");
624 }
625
626 #[test]
627 fn vec_u64_batches_multiple_out_of_range_indices() {
628 let bad = i32::MAX as u64 + 1;
629 let result = std::panic::catch_unwind(|| checked_vec_u64_into_sexp(vec![0, bad, 1, bad]));
630 let msg = panic_message(result.expect_err("should panic for out-of-range elements"));
631 assert!(
632 msg.starts_with("strict conversion failed: Vec<u64> conversion failed:"),
633 "{msg}"
634 );
635 assert!(msg.contains("invalid value at index 1: u64 value"), "{msg}");
636 assert!(msg.contains("invalid value at index 3: u64 value"), "{msg}");
637 }
638
639 #[test]
640 fn vec_option_i64_batches_multiple_out_of_range_indices() {
641 let result = std::panic::catch_unwind(|| {
642 checked_vec_option_i64_into_sexp(vec![Some(1), Some(i64::MAX), None, Some(i64::MIN)])
643 });
644 let msg = panic_message(result.expect_err("should panic for out-of-range elements"));
645 assert!(
646 msg.starts_with("strict conversion failed: Vec<Option<i64>> conversion failed:"),
647 "{msg}"
648 );
649 assert!(msg.contains("invalid value at index 1: i64 value"), "{msg}");
650 assert!(msg.contains("invalid value at index 3: i64 value"), "{msg}");
651 }
652
653 #[test]
654 fn vec_option_u64_batches_multiple_out_of_range_indices() {
655 let bad = i32::MAX as u64 + 1;
656 let result = std::panic::catch_unwind(|| {
657 checked_vec_option_u64_into_sexp(vec![Some(0), Some(bad), None, Some(bad)])
658 });
659 let msg = panic_message(result.expect_err("should panic for out-of-range elements"));
660 assert!(
661 msg.starts_with("strict conversion failed: Vec<Option<u64>> conversion failed:"),
662 "{msg}"
663 );
664 assert!(msg.contains("invalid value at index 1: u64 value"), "{msg}");
665 assert!(msg.contains("invalid value at index 3: u64 value"), "{msg}");
666 }
667}
668