Skip to main content

miniextendr_api/from_r/
coerced_scalars.rs

1//! Coerced scalar conversions (multi-source numeric) and large integer scalars.
2//!
3//! These types accept multiple R source types (INTSXP, REALSXP, RAWSXP, LGLSXP)
4//! and coerce to the target Rust type via [`TryCoerce`](crate::coerce::TryCoerce).
5//!
6//! Covers: `i8`, `i16`, `u16`, `u32`, `f32` (sub-native scalars) and
7//! `i64`, `u64`, `isize`, `usize` (large integers via f64 intermediary).
8
9use crate::altrep_traits::NA_INTEGER;
10use crate::coerce::TryCoerce;
11use crate::ffi::{RLogical, SEXP, SEXPTYPE, SexpExt};
12use crate::from_r::{SexpError, TryFromSexp, is_na_real};
13
14#[inline]
15pub(crate) fn coerce_value<R, T>(value: R) -> Result<T, SexpError>
16where
17    R: TryCoerce<T>,
18    <R as TryCoerce<T>>::Error: std::fmt::Debug,
19{
20    value
21        .try_coerce()
22        .map_err(|e| SexpError::InvalidValue(format!("{e:?}")))
23}
24
25#[inline]
26fn try_from_sexp_numeric_scalar<T>(sexp: SEXP) -> Result<T, SexpError>
27where
28    i32: TryCoerce<T>,
29    f64: TryCoerce<T>,
30    u8: TryCoerce<T>,
31    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
32    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
33    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
34{
35    let actual = sexp.type_of();
36    match actual {
37        SEXPTYPE::INTSXP => {
38            let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
39            coerce_value(value)
40        }
41        SEXPTYPE::REALSXP => {
42            let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
43            coerce_value(value)
44        }
45        SEXPTYPE::RAWSXP => {
46            let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
47            coerce_value(value)
48        }
49        SEXPTYPE::LGLSXP => {
50            let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
51            coerce_value(value.to_i32())
52        }
53        _ => Err(SexpError::InvalidValue(format!(
54            "expected integer, numeric, logical, or raw; got {:?}",
55            actual
56        ))),
57    }
58}
59
60#[inline]
61unsafe fn try_from_sexp_numeric_scalar_unchecked<T>(sexp: SEXP) -> Result<T, SexpError>
62where
63    i32: TryCoerce<T>,
64    f64: TryCoerce<T>,
65    u8: TryCoerce<T>,
66    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
67    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
68    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
69{
70    let actual = sexp.type_of();
71    match actual {
72        SEXPTYPE::INTSXP => {
73            let value: i32 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
74            coerce_value(value)
75        }
76        SEXPTYPE::REALSXP => {
77            let value: f64 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
78            coerce_value(value)
79        }
80        SEXPTYPE::RAWSXP => {
81            let value: u8 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
82            coerce_value(value)
83        }
84        SEXPTYPE::LGLSXP => {
85            let value: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
86            coerce_value(value.to_i32())
87        }
88        _ => Err(SexpError::InvalidValue(format!(
89            "expected integer, numeric, logical, or raw; got {:?}",
90            actual
91        ))),
92    }
93}
94
95#[inline]
96fn try_from_sexp_numeric_option<T>(sexp: SEXP) -> Result<Option<T>, SexpError>
97where
98    i32: TryCoerce<T>,
99    f64: TryCoerce<T>,
100    u8: TryCoerce<T>,
101    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
102    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
103    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
104{
105    if sexp.type_of() == SEXPTYPE::NILSXP {
106        return Ok(None);
107    }
108
109    let actual = sexp.type_of();
110    match actual {
111        SEXPTYPE::INTSXP => {
112            // Read raw i32 without the NA guard — we handle NA here by returning None.
113            let len = sexp.len();
114            if len != 1 {
115                return Err(crate::from_r::SexpLengthError {
116                    expected: 1,
117                    actual: len,
118                }
119                .into());
120            }
121            let value = unsafe { sexp.as_slice::<i32>() }
122                .first()
123                .cloned()
124                .ok_or_else(|| {
125                    SexpError::from(crate::from_r::SexpLengthError {
126                        expected: 1,
127                        actual: 0,
128                    })
129                })?;
130            if value == NA_INTEGER {
131                Ok(None)
132            } else {
133                coerce_value(value).map(Some)
134            }
135        }
136        SEXPTYPE::REALSXP => {
137            let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
138            if is_na_real(value) {
139                Ok(None)
140            } else {
141                coerce_value(value).map(Some)
142            }
143        }
144        SEXPTYPE::RAWSXP => {
145            let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
146            coerce_value(value).map(Some)
147        }
148        SEXPTYPE::LGLSXP => {
149            let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
150            if value.is_na() {
151                Ok(None)
152            } else {
153                coerce_value(value.to_i32()).map(Some)
154            }
155        }
156        _ => Err(SexpError::InvalidValue(format!(
157            "expected integer, numeric, logical, or raw; got {:?}",
158            actual
159        ))),
160    }
161}
162
163#[inline]
164unsafe fn try_from_sexp_numeric_option_unchecked<T>(sexp: SEXP) -> Result<Option<T>, SexpError>
165where
166    i32: TryCoerce<T>,
167    f64: TryCoerce<T>,
168    u8: TryCoerce<T>,
169    <i32 as TryCoerce<T>>::Error: std::fmt::Debug,
170    <f64 as TryCoerce<T>>::Error: std::fmt::Debug,
171    <u8 as TryCoerce<T>>::Error: std::fmt::Debug,
172{
173    if sexp.type_of() == SEXPTYPE::NILSXP {
174        return Ok(None);
175    }
176
177    let actual = sexp.type_of();
178    match actual {
179        SEXPTYPE::INTSXP => {
180            // Read raw i32 without the NA guard — we handle NA here by returning None.
181            let len = unsafe { sexp.len_unchecked() };
182            if len != 1 {
183                return Err(crate::from_r::SexpLengthError {
184                    expected: 1,
185                    actual: len,
186                }
187                .into());
188            }
189            let value = unsafe { sexp.as_slice_unchecked::<i32>() }
190                .first()
191                .cloned()
192                .ok_or_else(|| {
193                    SexpError::from(crate::from_r::SexpLengthError {
194                        expected: 1,
195                        actual: 0,
196                    })
197                })?;
198            if value == NA_INTEGER {
199                Ok(None)
200            } else {
201                coerce_value(value).map(Some)
202            }
203        }
204        SEXPTYPE::REALSXP => {
205            let value: f64 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
206            if is_na_real(value) {
207                Ok(None)
208            } else {
209                coerce_value(value).map(Some)
210            }
211        }
212        SEXPTYPE::RAWSXP => {
213            let value: u8 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
214            coerce_value(value).map(Some)
215        }
216        SEXPTYPE::LGLSXP => {
217            let value: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
218            if value.is_na() {
219                Ok(None)
220            } else {
221                coerce_value(value.to_i32()).map(Some)
222            }
223        }
224        _ => Err(SexpError::InvalidValue(format!(
225            "expected integer, numeric, logical, or raw; got {:?}",
226            actual
227        ))),
228    }
229}
230
231impl TryFromSexp for i8 {
232    type Error = SexpError;
233
234    #[inline]
235    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
236        try_from_sexp_numeric_scalar(sexp)
237    }
238
239    #[inline]
240    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
241        unsafe { try_from_sexp_numeric_scalar_unchecked(sexp) }
242    }
243}
244
245impl TryFromSexp for i16 {
246    type Error = SexpError;
247
248    #[inline]
249    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
250        try_from_sexp_numeric_scalar(sexp)
251    }
252
253    #[inline]
254    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
255        unsafe { try_from_sexp_numeric_scalar_unchecked(sexp) }
256    }
257}
258
259impl TryFromSexp for u16 {
260    type Error = SexpError;
261
262    #[inline]
263    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
264        try_from_sexp_numeric_scalar(sexp)
265    }
266
267    #[inline]
268    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
269        unsafe { try_from_sexp_numeric_scalar_unchecked(sexp) }
270    }
271}
272
273impl TryFromSexp for u32 {
274    type Error = SexpError;
275
276    #[inline]
277    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
278        try_from_sexp_numeric_scalar(sexp)
279    }
280
281    #[inline]
282    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
283        unsafe { try_from_sexp_numeric_scalar_unchecked(sexp) }
284    }
285}
286
287impl TryFromSexp for f32 {
288    type Error = SexpError;
289
290    #[inline]
291    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
292        let actual = sexp.type_of();
293        match actual {
294            SEXPTYPE::INTSXP => {
295                let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
296                Ok(value as f32)
297            }
298            SEXPTYPE::REALSXP => {
299                let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
300                Ok(value as f32)
301            }
302            SEXPTYPE::RAWSXP => {
303                let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
304                Ok(value as f32)
305            }
306            SEXPTYPE::LGLSXP => {
307                let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
308                Ok(value.to_i32() as f32)
309            }
310            _ => Err(SexpError::InvalidValue(format!(
311                "expected integer, numeric, logical, or raw; got {:?}",
312                actual
313            ))),
314        }
315    }
316
317    #[inline]
318    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
319        let actual = sexp.type_of();
320        match actual {
321            SEXPTYPE::INTSXP => {
322                let value: i32 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
323                Ok(value as f32)
324            }
325            SEXPTYPE::REALSXP => {
326                let value: f64 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
327                Ok(value as f32)
328            }
329            SEXPTYPE::RAWSXP => {
330                let value: u8 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
331                Ok(value as f32)
332            }
333            SEXPTYPE::LGLSXP => {
334                let value: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
335                Ok(value.to_i32() as f32)
336            }
337            _ => Err(SexpError::InvalidValue(format!(
338                "expected integer, numeric, logical, or raw; got {:?}",
339                actual
340            ))),
341        }
342    }
343}
344
345impl TryFromSexp for Option<i8> {
346    type Error = SexpError;
347
348    #[inline]
349    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
350        try_from_sexp_numeric_option(sexp)
351    }
352
353    #[inline]
354    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
355        unsafe { try_from_sexp_numeric_option_unchecked(sexp) }
356    }
357}
358
359impl TryFromSexp for Option<i16> {
360    type Error = SexpError;
361
362    #[inline]
363    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
364        try_from_sexp_numeric_option(sexp)
365    }
366
367    #[inline]
368    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
369        unsafe { try_from_sexp_numeric_option_unchecked(sexp) }
370    }
371}
372
373impl TryFromSexp for Option<u16> {
374    type Error = SexpError;
375
376    #[inline]
377    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
378        try_from_sexp_numeric_option(sexp)
379    }
380
381    #[inline]
382    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
383        unsafe { try_from_sexp_numeric_option_unchecked(sexp) }
384    }
385}
386
387impl TryFromSexp for Option<u32> {
388    type Error = SexpError;
389
390    #[inline]
391    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
392        try_from_sexp_numeric_option(sexp)
393    }
394
395    #[inline]
396    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
397        unsafe { try_from_sexp_numeric_option_unchecked(sexp) }
398    }
399}
400
401impl TryFromSexp for Option<f32> {
402    type Error = SexpError;
403
404    #[inline]
405    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
406        if sexp.type_of() == SEXPTYPE::NILSXP {
407            return Ok(None);
408        }
409        let actual = sexp.type_of();
410        match actual {
411            SEXPTYPE::INTSXP => {
412                let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
413                if value == crate::altrep_traits::NA_INTEGER {
414                    Ok(None)
415                } else {
416                    Ok(Some(value as f32))
417                }
418            }
419            SEXPTYPE::REALSXP => {
420                let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
421                if is_na_real(value) {
422                    Ok(None)
423                } else {
424                    Ok(Some(value as f32))
425                }
426            }
427            SEXPTYPE::RAWSXP => {
428                let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
429                Ok(Some(value as f32))
430            }
431            SEXPTYPE::LGLSXP => {
432                let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
433                if value.is_na() {
434                    Ok(None)
435                } else {
436                    Ok(Some(value.to_i32() as f32))
437                }
438            }
439            _ => Err(SexpError::InvalidValue(format!(
440                "expected integer, numeric, logical, or raw; got {:?}",
441                actual
442            ))),
443        }
444    }
445
446    #[inline]
447    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
448        if sexp.type_of() == SEXPTYPE::NILSXP {
449            return Ok(None);
450        }
451        let actual = sexp.type_of();
452        match actual {
453            SEXPTYPE::INTSXP => {
454                let value: i32 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
455                if value == crate::altrep_traits::NA_INTEGER {
456                    Ok(None)
457                } else {
458                    Ok(Some(value as f32))
459                }
460            }
461            SEXPTYPE::REALSXP => {
462                let value: f64 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
463                if is_na_real(value) {
464                    Ok(None)
465                } else {
466                    Ok(Some(value as f32))
467                }
468            }
469            SEXPTYPE::RAWSXP => {
470                let value: u8 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
471                Ok(Some(value as f32))
472            }
473            SEXPTYPE::LGLSXP => {
474                let value: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
475                if value.is_na() {
476                    Ok(None)
477                } else {
478                    Ok(Some(value.to_i32() as f32))
479                }
480            }
481            _ => Err(SexpError::InvalidValue(format!(
482                "expected integer, numeric, logical, or raw; got {:?}",
483                actual
484            ))),
485        }
486    }
487}
488// endregion
489
490// region: Large integer scalar conversions (via f64)
491//
492// R doesn't have native 64-bit integers, so these read from REALSXP (f64)
493// and convert with range/precision checking.
494//
495// **Round-trip precision:**
496// - Values in [-2^53, 2^53] round-trip exactly: i64 → R → i64
497// - Values outside this range may not round-trip due to f64 precision loss
498//
499// **Conversion behavior:**
500// - Reads from REALSXP (f64) or INTSXP (i32)
501// - Validates the value is a whole number (no fractional part)
502// - Validates the value fits in the target type's range
503// - Returns error for NA values (use Option<i64> for nullable)
504
505/// Convert R numeric scalar to `i64`.
506///
507/// # Behavior
508///
509/// - Reads from REALSXP (f64) or INTSXP (i32)
510/// - Validates value is a whole number (no fractional part)
511/// - Validates value fits in i64 range
512/// - Returns `Err` for NA values
513///
514/// # Example
515///
516/// ```ignore
517/// // From R integer
518/// let val: i64 = TryFromSexp::try_from_sexp(int_sexp)?;
519///
520/// // From R numeric (must be whole number)
521/// let val: i64 = TryFromSexp::try_from_sexp(real_sexp)?;
522///
523/// // Error: 3.14 is not a whole number
524/// let val: Result<i64, _> = TryFromSexp::try_from_sexp(pi_sexp);
525/// ```
526impl TryFromSexp for i64 {
527    type Error = SexpError;
528
529    #[inline]
530    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
531        try_from_sexp_numeric_scalar(sexp)
532    }
533
534    #[inline]
535    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
536        unsafe { try_from_sexp_numeric_scalar_unchecked(sexp) }
537    }
538}
539
540/// Convert R numeric scalar to `u64`.
541///
542/// Same behavior as [`i64`](impl TryFromSexp for i64), but also validates
543/// the value is non-negative.
544impl TryFromSexp for u64 {
545    type Error = SexpError;
546
547    #[inline]
548    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
549        try_from_sexp_numeric_scalar(sexp)
550    }
551
552    #[inline]
553    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
554        unsafe { try_from_sexp_numeric_scalar_unchecked(sexp) }
555    }
556}
557
558/// Convert R numeric scalar to `Option<i64>`, with NA → `None`.
559impl TryFromSexp for Option<i64> {
560    type Error = SexpError;
561
562    #[inline]
563    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
564        try_from_sexp_numeric_option(sexp)
565    }
566
567    #[inline]
568    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
569        unsafe { try_from_sexp_numeric_option_unchecked(sexp) }
570    }
571}
572
573impl TryFromSexp for Option<u64> {
574    type Error = SexpError;
575
576    #[inline]
577    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
578        try_from_sexp_numeric_option(sexp)
579    }
580
581    #[inline]
582    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
583        unsafe { try_from_sexp_numeric_option_unchecked(sexp) }
584    }
585}
586
587impl TryFromSexp for usize {
588    type Error = SexpError;
589
590    #[inline]
591    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
592        // Try i32 first (more common), fall back to f64 for large values
593        let actual = sexp.type_of();
594        match actual {
595            SEXPTYPE::INTSXP => {
596                use crate::coerce::TryCoerce;
597                let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
598                value
599                    .try_coerce()
600                    .map_err(|e| SexpError::InvalidValue(format!("{e}")))
601            }
602            SEXPTYPE::REALSXP => {
603                use crate::coerce::TryCoerce;
604                let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
605                let u: u64 = value
606                    .try_coerce()
607                    .map_err(|e| SexpError::InvalidValue(format!("{e}")))?;
608                u.try_into()
609                    .map_err(|_| SexpError::InvalidValue("value out of usize range".into()))
610            }
611            SEXPTYPE::RAWSXP => {
612                use crate::coerce::Coerce;
613                let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
614                Ok(value.coerce())
615            }
616            SEXPTYPE::LGLSXP => {
617                use crate::coerce::TryCoerce;
618                let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
619                value
620                    .to_i32()
621                    .try_coerce()
622                    .map_err(|e| SexpError::InvalidValue(format!("{e}")))
623            }
624            _ => Err(SexpError::InvalidValue(format!(
625                "expected integer, numeric, logical, or raw; got {:?}",
626                actual
627            ))),
628        }
629    }
630}
631
632impl TryFromSexp for Option<usize> {
633    type Error = SexpError;
634
635    #[inline]
636    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
637        if sexp.type_of() == SEXPTYPE::NILSXP {
638            return Ok(None);
639        }
640        let actual = sexp.type_of();
641        match actual {
642            SEXPTYPE::INTSXP => {
643                use crate::coerce::TryCoerce;
644                let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
645                if value == crate::altrep_traits::NA_INTEGER {
646                    Ok(None)
647                } else {
648                    value
649                        .try_coerce()
650                        .map(Some)
651                        .map_err(|e| SexpError::InvalidValue(format!("{e}")))
652                }
653            }
654            SEXPTYPE::REALSXP => {
655                use crate::coerce::TryCoerce;
656                let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
657                if is_na_real(value) {
658                    return Ok(None);
659                }
660                let u: u64 = value
661                    .try_coerce()
662                    .map_err(|e| SexpError::InvalidValue(format!("{e}")))?;
663                u.try_into()
664                    .map(Some)
665                    .map_err(|_| SexpError::InvalidValue("value out of usize range".into()))
666            }
667            SEXPTYPE::RAWSXP => {
668                use crate::coerce::Coerce;
669                let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
670                Ok(Some(value.coerce()))
671            }
672            SEXPTYPE::LGLSXP => {
673                use crate::coerce::TryCoerce;
674                let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
675                if value.is_na() {
676                    Ok(None)
677                } else {
678                    value
679                        .to_i32()
680                        .try_coerce()
681                        .map(Some)
682                        .map_err(|e| SexpError::InvalidValue(format!("{e}")))
683                }
684            }
685            _ => Err(SexpError::InvalidValue(format!(
686                "expected integer, numeric, logical, or raw; got {:?}",
687                actual
688            ))),
689        }
690    }
691
692    #[inline]
693    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
694        Self::try_from_sexp(sexp)
695    }
696}
697
698impl TryFromSexp for isize {
699    type Error = SexpError;
700
701    #[inline]
702    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
703        // Try i32 first (more common), fall back to f64 for large values
704        let actual = sexp.type_of();
705        match actual {
706            SEXPTYPE::INTSXP => {
707                use crate::coerce::Coerce;
708                let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
709                Ok(value.coerce())
710            }
711            SEXPTYPE::REALSXP => {
712                use crate::coerce::TryCoerce;
713                let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
714                let i: i64 = value
715                    .try_coerce()
716                    .map_err(|e| SexpError::InvalidValue(format!("{e}")))?;
717                i.try_into()
718                    .map_err(|_| SexpError::InvalidValue("value out of isize range".into()))
719            }
720            SEXPTYPE::RAWSXP => {
721                use crate::coerce::Coerce;
722                let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
723                Ok(value.coerce())
724            }
725            SEXPTYPE::LGLSXP => {
726                use crate::coerce::Coerce;
727                let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
728                Ok(value.to_i32().coerce())
729            }
730            _ => Err(SexpError::InvalidValue(format!(
731                "expected integer, numeric, logical, or raw; got {:?}",
732                actual
733            ))),
734        }
735    }
736}
737
738impl TryFromSexp for Option<isize> {
739    type Error = SexpError;
740
741    #[inline]
742    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
743        if sexp.type_of() == SEXPTYPE::NILSXP {
744            return Ok(None);
745        }
746        let actual = sexp.type_of();
747        match actual {
748            SEXPTYPE::INTSXP => {
749                use crate::coerce::Coerce;
750                let value: i32 = TryFromSexp::try_from_sexp(sexp)?;
751                if value == crate::altrep_traits::NA_INTEGER {
752                    Ok(None)
753                } else {
754                    Ok(Some(value.coerce()))
755                }
756            }
757            SEXPTYPE::REALSXP => {
758                use crate::coerce::TryCoerce;
759                let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
760                if is_na_real(value) {
761                    return Ok(None);
762                }
763                let i: i64 = value
764                    .try_coerce()
765                    .map_err(|e| SexpError::InvalidValue(format!("{e}")))?;
766                i.try_into()
767                    .map(Some)
768                    .map_err(|_| SexpError::InvalidValue("value out of isize range".into()))
769            }
770            SEXPTYPE::RAWSXP => {
771                use crate::coerce::Coerce;
772                let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
773                Ok(Some(value.coerce()))
774            }
775            SEXPTYPE::LGLSXP => {
776                use crate::coerce::Coerce;
777                let value: RLogical = TryFromSexp::try_from_sexp(sexp)?;
778                if value.is_na() {
779                    Ok(None)
780                } else {
781                    Ok(Some(value.to_i32().coerce()))
782                }
783            }
784            _ => Err(SexpError::InvalidValue(format!(
785                "expected integer, numeric, logical, or raw; got {:?}",
786                actual
787            ))),
788        }
789    }
790
791    #[inline]
792    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
793        Self::try_from_sexp(sexp)
794    }
795}
796// endregion