Skip to main content

miniextendr_api/from_r/
logical.rs

1//! Logical type conversions (Rboolean, bool, Option variants).
2//!
3//! **`LGLSXP` literals here are the source of truth (#882).** `bool`/`Rboolean`
4//! are deliberately *not* `RNativeType` — R logical vectors are stored as `i32`
5//! (that's `RLogical`), and these impls translate the three-state TRUE/FALSE/NA
6//! `LGLSXP` payload into a two-state Rust `bool`. There is no `T::SEXP_TYPE` to
7//! derive the tag from for `bool`; the literal is the boundary, leave it.
8//!
9//! Handles the three R logical states (TRUE, FALSE, NA) and maps them to Rust:
10//!
11//! | Rust Type | NA Handling |
12//! |-----------|-------------|
13//! | `Rboolean` | Error on NA |
14//! | `bool` | Error on NA |
15//! | `Option<Rboolean>` | `None` on NA |
16//! | `Option<bool>` | `None` on NA |
17//!
18//! # Tradeoff
19//!
20//! Use `Option<bool>` / `Option<Rboolean>` if R might pass `NA` — the plain
21//! `bool` / `Rboolean` impls treat NA as a conversion failure. Failure mode
22//! of binding a plain `bool` when callers can pass `NA`: every NA argument
23//! surfaces as `SexpNaError` at the call site, which is usually not what you
24//! want for an interactive R API.
25//!
26//! Outbound counterpart: `bool` / `Option<bool>` impls in
27//! [`crate::into_r`].
28
29use crate::from_r::{SexpError, SexpNaError, TryFromSexp, is_na_real};
30use crate::{RLogical, Rboolean, SEXP, SEXPTYPE, SexpExt};
31
32impl TryFromSexp for Rboolean {
33    type Error = SexpError;
34
35    #[inline]
36    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
37        let raw: RLogical = TryFromSexp::try_from_sexp(sexp)?;
38        match raw.to_option_bool() {
39            Some(false) => Ok(Rboolean::FALSE),
40            Some(true) => Ok(Rboolean::TRUE),
41            None => Err(SexpNaError {
42                sexp_type: SEXPTYPE::LGLSXP,
43            }
44            .into()),
45        }
46    }
47
48    #[inline]
49    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
50        let raw: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
51        match raw.to_option_bool() {
52            Some(false) => Ok(Rboolean::FALSE),
53            Some(true) => Ok(Rboolean::TRUE),
54            None => Err(SexpNaError {
55                sexp_type: SEXPTYPE::LGLSXP,
56            }
57            .into()),
58        }
59    }
60}
61
62impl TryFromSexp for Option<Rboolean> {
63    type Error = SexpError;
64
65    #[inline]
66    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
67        if sexp.type_of() == SEXPTYPE::NILSXP {
68            return Ok(None);
69        }
70        let raw: RLogical = TryFromSexp::try_from_sexp(sexp)?;
71        match raw.to_option_bool() {
72            Some(false) => Ok(Some(Rboolean::FALSE)),
73            Some(true) => Ok(Some(Rboolean::TRUE)),
74            None => Ok(None),
75        }
76    }
77
78    #[inline]
79    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
80        if sexp.type_of() == SEXPTYPE::NILSXP {
81            return Ok(None);
82        }
83        let raw: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
84        match raw.to_option_bool() {
85            Some(false) => Ok(Some(Rboolean::FALSE)),
86            Some(true) => Ok(Some(Rboolean::TRUE)),
87            None => Ok(None),
88        }
89    }
90}
91
92impl TryFromSexp for bool {
93    type Error = SexpError;
94
95    #[inline]
96    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
97        let raw: RLogical = TryFromSexp::try_from_sexp(sexp)?;
98        raw.to_option_bool().ok_or_else(|| {
99            SexpNaError {
100                sexp_type: SEXPTYPE::LGLSXP,
101            }
102            .into()
103        })
104    }
105
106    #[inline]
107    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
108        let raw: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
109        raw.to_option_bool().ok_or_else(|| {
110            SexpNaError {
111                sexp_type: SEXPTYPE::LGLSXP,
112            }
113            .into()
114        })
115    }
116}
117
118impl TryFromSexp for Option<bool> {
119    type Error = SexpError;
120
121    #[inline]
122    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
123        // NULL -> None
124        if sexp.type_of() == SEXPTYPE::NILSXP {
125            return Ok(None);
126        }
127        let raw: RLogical = TryFromSexp::try_from_sexp(sexp)?;
128        Ok(raw.to_option_bool())
129    }
130
131    #[inline]
132    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
133        // NULL -> None
134        if sexp.type_of() == SEXPTYPE::NILSXP {
135            return Ok(None);
136        }
137        let raw: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
138        Ok(raw.to_option_bool())
139    }
140}
141
142impl TryFromSexp for Option<RLogical> {
143    type Error = SexpError;
144
145    #[inline]
146    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
147        if sexp.type_of() == SEXPTYPE::NILSXP {
148            return Ok(None);
149        }
150        let raw: RLogical = TryFromSexp::try_from_sexp(sexp)?;
151        if raw.is_na() { Ok(None) } else { Ok(Some(raw)) }
152    }
153
154    #[inline]
155    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
156        if sexp.type_of() == SEXPTYPE::NILSXP {
157            return Ok(None);
158        }
159        let raw: RLogical = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
160        if raw.is_na() { Ok(None) } else { Ok(Some(raw)) }
161    }
162}
163
164impl TryFromSexp for Option<i32> {
165    type Error = SexpError;
166
167    #[inline]
168    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
169        // NULL -> None
170        if sexp.type_of() == SEXPTYPE::NILSXP {
171            return Ok(None);
172        }
173        // The i32 TryFromSexp impl now returns SexpNaError for NA_integer_.
174        // Treat that as None here; propagate all other errors.
175        match TryFromSexp::try_from_sexp(sexp) {
176            Ok(value) => Ok(Some(value)),
177            Err(SexpError::Na(_)) => Ok(None),
178            Err(e) => Err(e),
179        }
180    }
181
182    #[inline]
183    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
184        // NULL -> None
185        if sexp.type_of() == SEXPTYPE::NILSXP {
186            return Ok(None);
187        }
188        match unsafe { TryFromSexp::try_from_sexp_unchecked(sexp) } {
189            Ok(value) => Ok(Some(value)),
190            Err(SexpError::Na(_)) => Ok(None),
191            Err(e) => Err(e),
192        }
193    }
194}
195
196impl TryFromSexp for Option<f64> {
197    type Error = SexpError;
198
199    #[inline]
200    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
201        // NULL -> None
202        if sexp.type_of() == SEXPTYPE::NILSXP {
203            return Ok(None);
204        }
205        let value: f64 = TryFromSexp::try_from_sexp(sexp)?;
206        if is_na_real(value) {
207            Ok(None)
208        } else {
209            Ok(Some(value))
210        }
211    }
212
213    #[inline]
214    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
215        // NULL -> None
216        if sexp.type_of() == SEXPTYPE::NILSXP {
217            return Ok(None);
218        }
219        let value: f64 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
220        if is_na_real(value) {
221            Ok(None)
222        } else {
223            Ok(Some(value))
224        }
225    }
226}
227
228impl TryFromSexp for Option<u8> {
229    type Error = SexpError;
230
231    #[inline]
232    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
233        if sexp.type_of() == SEXPTYPE::NILSXP {
234            return Ok(None);
235        }
236        let value: u8 = TryFromSexp::try_from_sexp(sexp)?;
237        Ok(Some(value))
238    }
239
240    #[inline]
241    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
242        if sexp.type_of() == SEXPTYPE::NILSXP {
243            return Ok(None);
244        }
245        let value: u8 = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
246        Ok(Some(value))
247    }
248}
249
250impl TryFromSexp for Option<crate::Rcomplex> {
251    type Error = SexpError;
252
253    #[inline]
254    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
255        use crate::altrep_traits::NA_REAL;
256
257        if sexp.type_of() == SEXPTYPE::NILSXP {
258            return Ok(None);
259        }
260        let value: crate::Rcomplex = TryFromSexp::try_from_sexp(sexp)?;
261        let na_bits = NA_REAL.to_bits();
262        if value.r.to_bits() == na_bits || value.i.to_bits() == na_bits {
263            Ok(None)
264        } else {
265            Ok(Some(value))
266        }
267    }
268
269    #[inline]
270    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
271        use crate::altrep_traits::NA_REAL;
272
273        if sexp.type_of() == SEXPTYPE::NILSXP {
274            return Ok(None);
275        }
276        let value: crate::Rcomplex = unsafe { TryFromSexp::try_from_sexp_unchecked(sexp)? };
277        let na_bits = NA_REAL.to_bits();
278        if value.r.to_bits() == na_bits || value.i.to_bits() == na_bits {
279            Ok(None)
280        } else {
281            Ok(Some(value))
282        }
283    }
284}
285// endregion