Skip to main content

miniextendr_api/
sexp_types.rs

1//! Core type vocabulary for R values: `SEXPTYPE`, `R_xlen_t`, `Rbyte`,
2//! `Rcomplex`, `RLogical`, `Rboolean`, `cetype_t`, `R_CFinalizer_t`, and
3//! the `RNativeType` and `IsNA` traits + impls.
4//!
5//! These types are the bridge between raw R FFI (in `crate::sys`) and the
6//! safe Rust API on `SEXP` (in `crate::sexp` and `crate::sexp_ext`).
7//! Most user code reaches them via [`crate::prelude`].
8
9use crate::SEXP;
10use crate::altrep_traits::NA_REAL;
11use crate::sys::{
12    COMPLEX, COMPLEX_ELT, INTEGER, INTEGER_ELT, LOGICAL, LOGICAL_ELT, RAW, RAW_ELT, REAL, REAL_ELT,
13    Rf_type2char, Rf_xlength,
14};
15
16#[allow(non_camel_case_types)]
17/// R's extended vector length type (`R_xlen_t`).
18pub type R_xlen_t = isize;
19/// R byte element type used by `RAWSXP`.
20pub type Rbyte = ::std::os::raw::c_uchar;
21
22/// R's complex scalar layout (`Rcomplex`).
23#[repr(C)]
24#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Rcomplex {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "Rcomplex", "r",
            &self.r, "i", &&self.i)
    }
}Debug, #[automatically_derived]
impl ::core::marker::Copy for Rcomplex { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Rcomplex { }
#[automatically_derived]
impl ::core::clone::Clone for Rcomplex {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<f64>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Rcomplex { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Rcomplex {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.r == other.r && self.i == other.i
    }
}PartialEq)]
25pub struct Rcomplex {
26    /// Real part.
27    pub r: f64,
28    /// Imaginary part.
29    pub i: f64,
30}
31
32/// R S-expression tag values (`SEXPTYPE`).
33#[repr(u32)]
34#[non_exhaustive]
35#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SEXPTYPE {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                SEXPTYPE::NILSXP => "NILSXP",
                SEXPTYPE::SYMSXP => "SYMSXP",
                SEXPTYPE::LISTSXP => "LISTSXP",
                SEXPTYPE::CLOSXP => "CLOSXP",
                SEXPTYPE::ENVSXP => "ENVSXP",
                SEXPTYPE::PROMSXP => "PROMSXP",
                SEXPTYPE::LANGSXP => "LANGSXP",
                SEXPTYPE::SPECIALSXP => "SPECIALSXP",
                SEXPTYPE::BUILTINSXP => "BUILTINSXP",
                SEXPTYPE::CHARSXP => "CHARSXP",
                SEXPTYPE::LGLSXP => "LGLSXP",
                SEXPTYPE::INTSXP => "INTSXP",
                SEXPTYPE::REALSXP => "REALSXP",
                SEXPTYPE::CPLXSXP => "CPLXSXP",
                SEXPTYPE::STRSXP => "STRSXP",
                SEXPTYPE::DOTSXP => "DOTSXP",
                SEXPTYPE::ANYSXP => "ANYSXP",
                SEXPTYPE::VECSXP => "VECSXP",
                SEXPTYPE::EXPRSXP => "EXPRSXP",
                SEXPTYPE::BCODESXP => "BCODESXP",
                SEXPTYPE::EXTPTRSXP => "EXTPTRSXP",
                SEXPTYPE::WEAKREFSXP => "WEAKREFSXP",
                SEXPTYPE::RAWSXP => "RAWSXP",
                SEXPTYPE::S4SXP => "S4SXP",
                SEXPTYPE::NEWSXP => "NEWSXP",
                SEXPTYPE::FREESXP => "FREESXP",
                SEXPTYPE::FUNSXP => "FUNSXP",
            })
    }
}Debug, #[automatically_derived]
impl ::core::marker::Copy for SEXPTYPE { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SEXPTYPE { }
#[automatically_derived]
impl ::core::clone::Clone for SEXPTYPE {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::hash::Hash for SEXPTYPE {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&::core::intrinsics::discriminant_value(self),
            state)
    }
}Hash, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for SEXPTYPE { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SEXPTYPE {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SEXPTYPE { }Eq)]
36pub enum SEXPTYPE {
37    #[doc = " nil = NULL"]
38    NILSXP = 0,
39    #[doc = " symbols"]
40    SYMSXP = 1,
41    #[doc = " lists of dotted pairs"]
42    LISTSXP = 2,
43    #[doc = " closures"]
44    CLOSXP = 3,
45    #[doc = " environments"]
46    ENVSXP = 4,
47    #[doc = r" promises: \[un\]evaluated closure arguments"]
48    PROMSXP = 5,
49    #[doc = " language constructs (special lists)"]
50    LANGSXP = 6,
51    #[doc = " special forms"]
52    SPECIALSXP = 7,
53    #[doc = " builtin non-special forms"]
54    BUILTINSXP = 8,
55    #[doc = " \"scalar\" string type (internal only)"]
56    CHARSXP = 9,
57    #[doc = " logical vectors"]
58    LGLSXP = 10,
59    #[doc = " integer vectors"]
60    INTSXP = 13,
61    #[doc = " real variables"]
62    REALSXP = 14,
63    #[doc = " complex variables"]
64    CPLXSXP = 15,
65    #[doc = " string vectors"]
66    STRSXP = 16,
67    #[doc = " dot-dot-dot object"]
68    DOTSXP = 17,
69    #[doc = " make \"any\" args work"]
70    ANYSXP = 18,
71    #[doc = " generic vectors"]
72    VECSXP = 19,
73    #[doc = " expressions vectors"]
74    EXPRSXP = 20,
75    #[doc = " byte code"]
76    BCODESXP = 21,
77    #[doc = " external pointer"]
78    EXTPTRSXP = 22,
79    #[doc = " weak reference"]
80    WEAKREFSXP = 23,
81    #[doc = " raw bytes"]
82    RAWSXP = 24,
83    #[doc = " S4 non-vector"]
84    S4SXP = 25,
85    #[doc = " fresh node created in new page"]
86    NEWSXP = 30,
87    #[doc = " node released by GC"]
88    FREESXP = 31,
89    #[doc = " Closure or Builtin"]
90    FUNSXP = 99,
91}
92
93impl SEXPTYPE {
94    /// Alias for `S4SXP` (value 25).
95    ///
96    /// R defines both `OBJSXP` and `S4SXP` as value 25. `S4SXP` is retained
97    /// for backwards compatibility; `OBJSXP` is the preferred name.
98    pub const OBJSXP: SEXPTYPE = SEXPTYPE::S4SXP;
99
100    /// Get R's name for this SEXPTYPE (e.g. `"double"`, `"integer"`, `"list"`).
101    ///
102    /// Returns the same string as R's `typeof()` function.
103    #[inline]
104    pub fn type_name(self) -> &'static str {
105        let cstr = unsafe { Rf_type2char(self) };
106        // SAFETY: R's type names are static ASCII strings
107        unsafe { std::ffi::CStr::from_ptr(cstr) }
108            .to_str()
109            .unwrap_or("unknown")
110    }
111}
112
113/// Marker trait for types that correspond to R's native vector element types.
114///
115/// This enables blanket implementations for `TryFromSexp` and safe conversions.
116pub trait RNativeType: Sized + Copy + 'static {
117    /// The SEXPTYPE for vectors containing this element type.
118    const SEXP_TYPE: SEXPTYPE;
119
120    /// The per-type `NA` (missing-value) sentinel used when filling vector slots
121    /// that have no source value (e.g. sparse scatter into a longer column).
122    ///
123    /// - `f64`   → `NA_REAL` (R's canonical NA double bit pattern, *not* a plain `NaN`)
124    /// - `i32`   → `i32::MIN` (`NA_INTEGER`)
125    /// - `RLogical` → `RLogical::NA` (`NA_LOGICAL`)
126    /// - `Rcomplex` → both parts `NA_REAL`
127    /// - `u8` (RAWSXP) → `0` — **R's raw type has no NA**, so absent positions
128    ///   become `0x00` rather than a missing marker.
129    const R_NA: Self;
130
131    /// Get mutable pointer to vector data.
132    ///
133    /// For empty vectors (length 0), returns an aligned dangling pointer rather than
134    /// R's internal 0x1 sentinel, which isn't properly aligned for most types.
135    /// This allows safe creation of zero-length slices with `std::slice::from_raw_parts_mut`.
136    ///
137    /// # Safety
138    ///
139    /// - `sexp` must be a valid, non-null SEXP of the corresponding vector type.
140    /// - For ALTREP vectors, this may trigger materialization.
141    unsafe fn dataptr_mut(sexp: SEXP) -> *mut Self;
142
143    /// Read the i-th element via the appropriate `*_ELT` accessor.
144    ///
145    /// Goes through R's ALTREP dispatch for ALTREP vectors.
146    fn elt(sexp: SEXP, i: isize) -> Self;
147}
148
149/// Native R elements with an actual NA representation.
150///
151/// This is the missing-value capability bound for native buffers. It excludes
152/// `u8`: R raw vectors have no NA, even though `RNativeType::R_NA` uses zero
153/// to fill absent raw positions. The existing `R_NA` constant constructs the
154/// sentinel; `is_na` recognizes it, including computed real NAs.
155///
156/// For floating point this follows R's `R_IsNA`, not R's `is.na()` or
157/// `anyNA()`: ordinary NaNs are not NA sentinels. In particular, ALTREP
158/// `no_na` hints must still reject ordinary NaNs too.
159///
160/// Rust values without native NA storage use `Option<T>` at conversion
161/// boundaries; no additional `NA<T>` wrapper is needed. Native newtypes
162/// can use `#[derive(IsNA)]` to forward this check to their single field.
163pub trait IsNA: RNativeType {
164    /// Whether this element is R's NA sentinel.
165    fn is_na(&self) -> bool;
166}
167
168impl IsNA for i32 {
169    #[inline]
170    fn is_na(&self) -> bool {
171        *self == Self::R_NA
172    }
173}
174
175impl IsNA for f64 {
176    #[inline]
177    fn is_na(&self) -> bool {
178        // R_IsNA compares only the low word of a NaN (1954). Arithmetic
179        // quiets NA_REAL from 0x7FF0_0000_0000_07A2 to 0x7FF8_0000_0000_07A2,
180        // which must still be NA; ordinary NaNs remain valid values.
181        self.is_nan() && (self.to_bits() & 0xFFFF_FFFF) == (Self::R_NA.to_bits() & 0xFFFF_FFFF)
182    }
183}
184
185impl IsNA for RLogical {
186    #[inline]
187    fn is_na(&self) -> bool {
188        RLogical::is_na(*self)
189    }
190}
191
192impl IsNA for Rcomplex {
193    #[inline]
194    fn is_na(&self) -> bool {
195        self.r.is_na() || self.i.is_na()
196    }
197}
198
199/// R's logical element type (the contents of a `LGLSXP` vector).
200///
201/// In R, logical vectors are stored as `int` with possible values:
202/// - `0` for FALSE
203/// - `1` for TRUE
204/// - `NA_LOGICAL` (typically `INT_MIN`) for NA
205///
206/// **Important:** R may also contain other non-zero values in logical vectors
207/// (e.g., from low-level code). Those should be interpreted as TRUE.
208///
209/// This type is `repr(transparent)` over `i32` so *any* raw value is valid,
210/// avoiding UB when viewing `LGLSXP` data as a slice.
211#[repr(transparent)]
212#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RLogical {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "RLogical",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::marker::Copy for RLogical { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for RLogical { }
#[automatically_derived]
impl ::core::clone::Clone for RLogical {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<i32>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::hash::Hash for RLogical {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}Hash, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for RLogical { }
#[automatically_derived]
impl ::core::cmp::PartialEq for RLogical {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for RLogical {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<i32>;
    }
}Eq)]
213pub struct RLogical(i32);
214
215impl RLogical {
216    /// FALSE logical scalar.
217    pub const FALSE: Self = Self(0);
218    /// TRUE logical scalar.
219    pub const TRUE: Self = Self(1);
220    /// Missing logical scalar (`NA_LOGICAL`).
221    pub const NA: Self = Self(i32::MIN);
222
223    /// Construct directly from raw R logical storage.
224    #[inline]
225    pub const fn from_i32(raw: i32) -> Self {
226        Self(raw)
227    }
228
229    /// Get raw R logical storage value.
230    #[inline]
231    pub const fn to_i32(self) -> i32 {
232        self.0
233    }
234
235    /// Returns whether the value is `NA_LOGICAL`.
236    #[inline]
237    pub const fn is_na(self) -> bool {
238        self.0 == i32::MIN
239    }
240
241    /// Convert to Rust `Option<bool>` (`None` for `NA`).
242    #[inline]
243    pub const fn to_option_bool(self) -> Option<bool> {
244        if self.is_na() {
245            None
246        } else {
247            Some(self.0 != 0)
248        }
249    }
250}
251
252impl From<bool> for RLogical {
253    #[inline]
254    fn from(value: bool) -> Self {
255        if value { Self::TRUE } else { Self::FALSE }
256    }
257}
258
259impl RNativeType for i32 {
260    const SEXP_TYPE: SEXPTYPE = SEXPTYPE::INTSXP;
261    const R_NA: Self = i32::MIN; // NA_INTEGER
262
263    #[inline]
264    unsafe fn dataptr_mut(sexp: SEXP) -> *mut Self {
265        unsafe {
266            if Rf_xlength(sexp) == 0 {
267                std::ptr::NonNull::<Self>::dangling().as_ptr()
268            } else {
269                INTEGER(sexp)
270            }
271        }
272    }
273
274    #[inline]
275    fn elt(sexp: SEXP, i: isize) -> Self {
276        unsafe { INTEGER_ELT(sexp, i) }
277    }
278}
279
280impl RNativeType for f64 {
281    const SEXP_TYPE: SEXPTYPE = SEXPTYPE::REALSXP;
282    const R_NA: Self = NA_REAL; // R's canonical NA double bit pattern, not f64::NAN
283
284    #[inline]
285    unsafe fn dataptr_mut(sexp: SEXP) -> *mut Self {
286        unsafe {
287            if Rf_xlength(sexp) == 0 {
288                std::ptr::NonNull::<Self>::dangling().as_ptr()
289            } else {
290                REAL(sexp)
291            }
292        }
293    }
294
295    #[inline]
296    fn elt(sexp: SEXP, i: isize) -> Self {
297        unsafe { REAL_ELT(sexp, i) }
298    }
299}
300
301impl RNativeType for u8 {
302    const SEXP_TYPE: SEXPTYPE = SEXPTYPE::RAWSXP;
303    // R's raw vectors have no NA; absent scatter positions become 0x00.
304    const R_NA: Self = 0;
305
306    #[inline]
307    unsafe fn dataptr_mut(sexp: SEXP) -> *mut Self {
308        unsafe {
309            if Rf_xlength(sexp) == 0 {
310                std::ptr::NonNull::<Self>::dangling().as_ptr()
311            } else {
312                RAW(sexp)
313            }
314        }
315    }
316
317    #[inline]
318    fn elt(sexp: SEXP, i: isize) -> Self {
319        unsafe { RAW_ELT(sexp, i) }
320    }
321}
322
323impl RNativeType for RLogical {
324    const SEXP_TYPE: SEXPTYPE = SEXPTYPE::LGLSXP;
325    const R_NA: Self = RLogical::NA; // NA_LOGICAL
326
327    #[inline]
328    unsafe fn dataptr_mut(sexp: SEXP) -> *mut Self {
329        // LOGICAL returns *mut c_int, RLogical is repr(transparent) over i32
330        unsafe {
331            if Rf_xlength(sexp) == 0 {
332                std::ptr::NonNull::<Self>::dangling().as_ptr()
333            } else {
334                LOGICAL(sexp).cast()
335            }
336        }
337    }
338
339    #[inline]
340    fn elt(sexp: SEXP, i: isize) -> Self {
341        RLogical(unsafe { LOGICAL_ELT(sexp, i) })
342    }
343}
344
345impl RNativeType for Rcomplex {
346    const SEXP_TYPE: SEXPTYPE = SEXPTYPE::CPLXSXP;
347    const R_NA: Self = Rcomplex {
348        r: NA_REAL,
349        i: NA_REAL,
350    };
351
352    #[inline]
353    unsafe fn dataptr_mut(sexp: SEXP) -> *mut Self {
354        unsafe {
355            if Rf_xlength(sexp) == 0 {
356                std::ptr::NonNull::<Self>::dangling().as_ptr()
357            } else {
358                COMPLEX(sexp)
359            }
360        }
361    }
362
363    #[inline]
364    fn elt(sexp: SEXP, i: isize) -> Self {
365        unsafe { COMPLEX_ELT(sexp, i) }
366    }
367}
368
369#[repr(i32)]
370#[non_exhaustive]
371#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Rboolean {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                Rboolean::FALSE => "FALSE",
                Rboolean::TRUE => "TRUE",
            })
    }
}Debug, #[automatically_derived]
impl ::core::marker::Copy for Rboolean { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Rboolean { }
#[automatically_derived]
impl ::core::clone::Clone for Rboolean {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::hash::Hash for Rboolean {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&::core::intrinsics::discriminant_value(self),
            state)
    }
}Hash, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Rboolean { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Rboolean {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Rboolean { }Eq)]
372/// Binary boolean used by many R C APIs.
373pub enum Rboolean {
374    /// False.
375    FALSE = 0,
376    /// True.
377    TRUE = 1,
378}
379
380impl From<bool> for Rboolean {
381    fn from(value: bool) -> Self {
382        match value {
383            true => Rboolean::TRUE,
384            false => Rboolean::FALSE,
385        }
386    }
387}
388
389impl From<Rboolean> for bool {
390    fn from(value: Rboolean) -> Self {
391        match value {
392            Rboolean::FALSE => false,
393            Rboolean::TRUE => true,
394        }
395    }
396}
397
398#[allow(non_camel_case_types)]
399/// C finalizer callback signature used by external pointers.
400pub type R_CFinalizer_t = ::std::option::Option<unsafe extern "C-unwind" fn(s: SEXP)>;
401
402#[repr(C)]
403#[derive(#[automatically_derived]
#[allow(non_camel_case_types)]
impl ::core::marker::Copy for cetype_t { }Copy, #[automatically_derived]
#[doc(hidden)]
#[allow(non_camel_case_types)]
unsafe impl ::core::clone::TrivialClone for cetype_t { }
#[automatically_derived]
#[allow(non_camel_case_types)]
impl ::core::clone::Clone for cetype_t {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone)]
404#[allow(non_camel_case_types)]
405/// Character encoding tag used by CHARSXP constructors.
406pub enum cetype_t {
407    /// Native locale encoding.
408    CE_NATIVE = 0,
409    /// UTF-8 encoding.
410    CE_UTF8 = 1,
411    /// Latin-1 encoding.
412    CE_LATIN1 = 2,
413    /// Raw bytes encoding.
414    CE_BYTES = 3,
415    /// Symbol encoding marker.
416    CE_SYMBOL = 5,
417    /// Any encoding accepted.
418    CE_ANY = 99,
419}
420pub use cetype_t::CE_UTF8;