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, charsxp_to_str};
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    let actual = sexp.type_of();
74    if actual != SEXPTYPE::VECSXP {
75        return Err(SexpTypeError {
76            expected: SEXPTYPE::VECSXP,
77            actual,
78        }
79        .into());
80    }
81
82    let len = sexp.len();
83    let mut map = create_map(len);
84
85    // Get names attribute
86    let names = sexp.get_names();
87    let has_names = names.type_of() == SEXPTYPE::STRSXP && names.len() == len;
88
89    // Single-pass: check duplicates AND convert in one loop
90    let mut seen = HashSet::with_capacity(len);
91
92    for i in 0..len {
93        let key = if has_names {
94            let charsxp = names.string_elt(i as crate::ffi::R_xlen_t);
95            if charsxp == SEXP::na_string() {
96                String::new()
97            } else {
98                unsafe { charsxp_to_str(charsxp) }.to_owned()
99            }
100        } else {
101            // Use index as key if no names
102            i.to_string()
103        };
104
105        // Check duplicate for non-empty keys
106        if !key.is_empty() && !seen.insert(key.clone()) {
107            return Err(SexpError::DuplicateName(key));
108        }
109
110        let elem = sexp.vector_elt(i as crate::ffi::R_xlen_t);
111        let value = V::try_from_sexp(elem).map_err(|e| e.into())?;
112        map.extend(std::iter::once((key, value)));
113    }
114
115    Ok(map)
116}
117
118macro_rules! impl_vec_map_try_from_sexp {
119    ($(#[$meta:meta])* $map_ty:ident) => {
120        $(#[$meta])*
121        impl<V: TryFromSexp> TryFromSexp for Vec<$map_ty<String, V>>
122        where
123            V::Error: Into<SexpError>,
124        {
125            type Error = SexpError;
126
127            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
128                list_to_vec_of_maps::<$map_ty<String, V>>(sexp)
129            }
130        }
131    };
132}
133
134impl_vec_map_try_from_sexp!(
135    /// Convert R list of named lists to `Vec<HashMap<String, V>>`.
136    HashMap
137);
138impl_vec_map_try_from_sexp!(
139    /// Convert R list of named lists to `Vec<BTreeMap<String, V>>`.
140    BTreeMap
141);
142
143/// Helper to convert R list (VECSXP) to `Vec<M>` where each element is
144/// converted via `M: TryFromSexp`.
145fn list_to_vec_of_maps<M>(sexp: SEXP) -> Result<Vec<M>, SexpError>
146where
147    M: TryFromSexp,
148    M::Error: Into<SexpError>,
149{
150    let actual = sexp.type_of();
151    if actual != SEXPTYPE::VECSXP {
152        return Err(SexpTypeError {
153            expected: SEXPTYPE::VECSXP,
154            actual,
155        }
156        .into());
157    }
158
159    let len = sexp.len();
160    let mut result = Vec::with_capacity(len);
161
162    for i in 0..len {
163        let elem = sexp.vector_elt(i as crate::ffi::R_xlen_t);
164        let map = M::try_from_sexp(elem).map_err(Into::into)?;
165        result.push(map);
166    }
167
168    Ok(result)
169}
170
171macro_rules! impl_set_try_from_sexp_native {
172    ($set:ident<$t:ty>) => {
173        impl TryFromSexp for $set<$t> {
174            type Error = SexpTypeError;
175
176            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
177                let slice: &[$t] = TryFromSexp::try_from_sexp(sexp)?;
178                Ok(slice.iter().copied().collect())
179            }
180        }
181    };
182}
183
184impl_set_try_from_sexp_native!(HashSet<i32>);
185impl_set_try_from_sexp_native!(HashSet<u8>);
186impl_set_try_from_sexp_native!(HashSet<RLogical>);
187impl_set_try_from_sexp_native!(BTreeSet<i32>);
188impl_set_try_from_sexp_native!(BTreeSet<u8>);
189
190macro_rules! impl_vec_try_from_sexp_native {
191    ($t:ty) => {
192        impl TryFromSexp for Vec<$t> {
193            type Error = SexpTypeError;
194
195            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
196                let slice: &[$t] = TryFromSexp::try_from_sexp(sexp)?;
197                Ok(slice.to_vec())
198            }
199        }
200    };
201}
202
203impl_vec_try_from_sexp_native!(i32);
204impl_vec_try_from_sexp_native!(f64);
205impl_vec_try_from_sexp_native!(u8);
206impl_vec_try_from_sexp_native!(RLogical);
207impl_vec_try_from_sexp_native!(crate::ffi::Rcomplex);
208
209macro_rules! impl_boxed_slice_try_from_sexp_native {
210    ($t:ty) => {
211        impl TryFromSexp for Box<[$t]> {
212            type Error = SexpTypeError;
213
214            fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
215                let slice: &[$t] = TryFromSexp::try_from_sexp(sexp)?;
216                Ok(slice.into())
217            }
218        }
219    };
220}
221
222impl_boxed_slice_try_from_sexp_native!(i32);
223impl_boxed_slice_try_from_sexp_native!(f64);
224impl_boxed_slice_try_from_sexp_native!(u8);
225impl_boxed_slice_try_from_sexp_native!(RLogical);
226impl_boxed_slice_try_from_sexp_native!(crate::ffi::Rcomplex);
227// endregion