Skip to main content

miniextendr_api/from_r/
collections.rs

1//! Collection conversions (HashMap, BTreeMap, HashSet, BTreeSet).
2//!
3//! Named R lists convert to `HashMap<String, V>` / `BTreeMap<String, V>`.
4//! Unnamed R vectors convert to `HashSet<T>` / `BTreeSet<T>` for native types.
5//! Nested lists convert to `Vec<HashMap<String, V>>` etc.
6
7use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
8
9use crate::ffi::{RLogical, SEXP, SEXPTYPE, SexpExt};
10use crate::from_r::{SexpError, SexpTypeError, TryFromSexp};
11
12macro_rules! impl_map_try_from_sexp {
13    ($(#[$meta:meta])* $map_ty:ident, $create:expr) => {
14        $(#[$meta])*
15        impl<V: TryFromSexp> TryFromSexp for $map_ty<String, V>
16        where
17            V::Error: Into<SexpError>,
18        {
19            type Error = SexpError;
20
21            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
22                named_list_to_map(sexp, $create)
23            }
24        }
25    };
26}
27
28impl_map_try_from_sexp!(
29    /// Convert R named list (VECSXP) to HashMap<String, V>.
30    ///
31    /// See `named_list_to_map` for NA/empty name handling (elements with NA/empty
32    /// names map to key `""` and may silently overwrite each other).
33    HashMap, HashMap::with_capacity
34);
35impl_map_try_from_sexp!(
36    /// Convert R named list (VECSXP) to BTreeMap<String, V>.
37    ///
38    /// See `named_list_to_map` for NA/empty name handling (elements with NA/empty
39    /// names map to key `""` and may silently overwrite each other).
40    BTreeMap, |_| BTreeMap::new()
41);
42
43/// Helper to convert R named list to a map type.
44///
45/// Returns an error if the list has duplicate non-empty, non-NA names.
46///
47/// # NA and Empty Name Handling
48///
49/// **Warning:** Elements with NA or empty names are converted with key `""`:
50/// - `NA` names become empty string key `""`
51/// - Empty string names `""` stay as `""`
52/// - If multiple elements have NA/empty names, later ones **silently overwrite** earlier ones
53///
54/// This means data loss can occur without error if your list has multiple
55/// unnamed or NA-named elements.
56///
57/// **Example of silent data loss:**
58/// ```r
59/// # In R:
60/// x <- list(a = 1, 2, 3)  # Elements 2 and 3 have empty names
61/// # After conversion, only one of them survives under key ""
62/// ```
63///
64/// If you need all elements regardless of names, use `Vec<(String, V)>` instead,
65/// or convert the list to a vector first.
66fn named_list_to_map<V, M, F>(sexp: SEXP, create_map: F) -> Result<M, SexpError>
67where
68    V: TryFromSexp,
69    V::Error: Into<SexpError>,
70    M: Extend<(String, V)>,
71    F: FnOnce(usize) -> M,
72{
73    use crate::ffi::Rf_translateCharUTF8;
74
75    let actual = sexp.type_of();
76    if actual != SEXPTYPE::VECSXP {
77        return Err(SexpTypeError {
78            expected: SEXPTYPE::VECSXP,
79            actual,
80        }
81        .into());
82    }
83
84    let len = sexp.len();
85    let mut map = create_map(len);
86
87    // Get names attribute
88    let names = sexp.get_names();
89    let has_names = names.type_of() == SEXPTYPE::STRSXP && names.len() == len;
90
91    // Single-pass: check duplicates AND convert in one loop
92    let mut seen = HashSet::with_capacity(len);
93
94    for i in 0..len {
95        let key = if has_names {
96            let charsxp = names.string_elt(i as crate::ffi::R_xlen_t);
97            if charsxp == SEXP::na_string() {
98                String::new()
99            } else {
100                let c_str = unsafe { Rf_translateCharUTF8(charsxp) };
101                if c_str.is_null() {
102                    String::new()
103                } else {
104                    unsafe { std::ffi::CStr::from_ptr(c_str) }
105                        .to_str()
106                        .unwrap_or("")
107                        .to_owned()
108                }
109            }
110        } else {
111            // Use index as key if no names
112            i.to_string()
113        };
114
115        // Check duplicate for non-empty keys
116        if !key.is_empty() && !seen.insert(key.clone()) {
117            return Err(SexpError::DuplicateName(key));
118        }
119
120        let elem = sexp.vector_elt(i as crate::ffi::R_xlen_t);
121        let value = V::try_from_sexp(elem).map_err(|e| e.into())?;
122        map.extend(std::iter::once((key, value)));
123    }
124
125    Ok(map)
126}
127
128macro_rules! impl_vec_map_try_from_sexp {
129    ($(#[$meta:meta])* $map_ty:ident) => {
130        $(#[$meta])*
131        impl<V: TryFromSexp> TryFromSexp for Vec<$map_ty<String, V>>
132        where
133            V::Error: Into<SexpError>,
134        {
135            type Error = SexpError;
136
137            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
138                list_to_vec_of_maps::<$map_ty<String, V>>(sexp)
139            }
140        }
141    };
142}
143
144impl_vec_map_try_from_sexp!(
145    /// Convert R list of named lists to `Vec<HashMap<String, V>>`.
146    HashMap
147);
148impl_vec_map_try_from_sexp!(
149    /// Convert R list of named lists to `Vec<BTreeMap<String, V>>`.
150    BTreeMap
151);
152
153/// Helper to convert R list (VECSXP) to `Vec<M>` where each element is
154/// converted via `M: TryFromSexp`.
155fn list_to_vec_of_maps<M>(sexp: SEXP) -> Result<Vec<M>, SexpError>
156where
157    M: TryFromSexp,
158    M::Error: Into<SexpError>,
159{
160    let actual = sexp.type_of();
161    if actual != SEXPTYPE::VECSXP {
162        return Err(SexpTypeError {
163            expected: SEXPTYPE::VECSXP,
164            actual,
165        }
166        .into());
167    }
168
169    let len = sexp.len();
170    let mut result = Vec::with_capacity(len);
171
172    for i in 0..len {
173        let elem = sexp.vector_elt(i as crate::ffi::R_xlen_t);
174        let map = M::try_from_sexp(elem).map_err(Into::into)?;
175        result.push(map);
176    }
177
178    Ok(result)
179}
180
181macro_rules! impl_set_try_from_sexp_native {
182    ($set:ident<$t:ty>) => {
183        impl TryFromSexp for $set<$t> {
184            type Error = SexpTypeError;
185
186            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
187                let slice: &[$t] = TryFromSexp::try_from_sexp(sexp)?;
188                Ok(slice.iter().copied().collect())
189            }
190        }
191    };
192}
193
194impl_set_try_from_sexp_native!(HashSet<i32>);
195impl_set_try_from_sexp_native!(HashSet<u8>);
196impl_set_try_from_sexp_native!(HashSet<RLogical>);
197impl_set_try_from_sexp_native!(BTreeSet<i32>);
198impl_set_try_from_sexp_native!(BTreeSet<u8>);
199
200macro_rules! impl_vec_try_from_sexp_native {
201    ($t:ty) => {
202        impl TryFromSexp for Vec<$t> {
203            type Error = SexpTypeError;
204
205            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
206                let slice: &[$t] = TryFromSexp::try_from_sexp(sexp)?;
207                Ok(slice.to_vec())
208            }
209        }
210    };
211}
212
213impl_vec_try_from_sexp_native!(i32);
214impl_vec_try_from_sexp_native!(f64);
215impl_vec_try_from_sexp_native!(u8);
216impl_vec_try_from_sexp_native!(RLogical);
217impl_vec_try_from_sexp_native!(crate::ffi::Rcomplex);
218
219macro_rules! impl_boxed_slice_try_from_sexp_native {
220    ($t:ty) => {
221        impl TryFromSexp for Box<[$t]> {
222            type Error = SexpTypeError;
223
224            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
225                let slice: &[$t] = TryFromSexp::try_from_sexp(sexp)?;
226                Ok(slice.into())
227            }
228        }
229    };
230}
231
232impl_boxed_slice_try_from_sexp_native!(i32);
233impl_boxed_slice_try_from_sexp_native!(f64);
234impl_boxed_slice_try_from_sexp_native!(u8);
235impl_boxed_slice_try_from_sexp_native!(RLogical);
236impl_boxed_slice_try_from_sexp_native!(crate::ffi::Rcomplex);
237// endregion