Skip to main content

miniextendr_api/
newtype.rs

1//! Container conversions for forwarding newtypes.
2//!
3//! `#[derive(TryFromSexp)]` / `#[derive(IntoR)]` on a single-field newtype
4//! (`struct UserId(Uuid)`) emit the scalar forwarding impls *and* a small marker
5//! impl from this module. The container blankets here then light up
6//! `Vec<UserId>`, `Option<UserId>`, and `Vec<Option<UserId>>` automatically — the
7//! newtype inherits the inner type's exact SEXPTYPE checks, NA policy, and error
8//! text in every shape.
9//!
10//! # Why the markers live here and not in the derive
11//!
12//! A downstream crate cannot write `impl TryFromSexp for Vec<MyNewtype>`: `Vec` /
13//! `Option` are not `#[fundamental]`, so the local newtype is "covered" and the
14//! orphan rule (E0117) forbids it. The container impls must live in
15//! `miniextendr-api`, keyed on a marker trait the derive *can* legally implement
16//! downstream (foreign trait + local `Self`). The blankets below are those
17//! container impls. See `analysis/rconvert-containers-coherence-2026-06-04.md`
18//! (issue #844) for the full coherence analysis.
19//!
20//! [`FromRNewtype`] / [`IntoRNewtype`] / [`IntoRVecElement`] are **plumbing**:
21//! they are emitted by the derives, not implemented by hand. Implementing them
22//! manually is supported but unusual.
23//!
24//! # The asymmetries
25//!
26//! Five of the six container shapes are granted. Two are not, for two different
27//! coherence reasons:
28//!
29//! - **`IntoR for Vec<T>` (granted, but shared).** This slot has exactly one
30//!   blanket and `MatchArg` already needs it (`Vec<MyEnum>` → STRSXP). Rather
31//!   than a second, conflicting `Vec<T>` blanket (E0119), both paths funnel
32//!   through [`IntoRVecElement`]: `MatchArg` types reach it via a bridge blanket
33//!   in `match_arg.rs`, newtypes via a concrete impl emitted by
34//!   `#[derive(IntoR)]`. A type that is *both* a `MatchArg` enum and an `IntoR`
35//!   newtype is a coherence error — don't derive both on one type.
36//! - **`IntoR for Option<T>` (not granted).** A bare `Option<T>` blanket
37//!   collides with the pre-existing `impl<T: Copy + IntoR> IntoR for Option<&T>`:
38//!   `&T` is `#[fundamental]`, so a downstream crate could impl `IntoRNewtype`
39//!   for `&LocalType` and coherence cannot prove the two disjoint. Return
40//!   `Option<Inner>` (`opt.map(|x| x.0)`) instead. See the note on the missing
41//!   blanket below.
42//!
43//! `TryFromSexp for Vec<T>` / `Option<T>` / `Vec<Option<T>>` and `IntoR for
44//! Vec<Option<T>>` are coherence-free: no other blanket occupies those slots.
45
46use crate::SEXP;
47use crate::from_r::TryFromSexp;
48use crate::into_r::IntoR;
49
50// region: marker traits (emitted by the derives, not hand-written)
51
52/// Construct a forwarding newtype from its inner value (R → Rust side).
53///
54/// Emitted by `#[derive(TryFromSexp)]`. Powers the `TryFromSexp` container
55/// blankets for `Vec<T>` / `Option<T>` / `Vec<Option<T>>` in this module.
56pub trait FromRNewtype: Sized {
57    /// The wrapped inner type, whose conversions are forwarded to.
58    type Inner;
59
60    /// Wrap an inner value into the newtype.
61    fn from_inner(inner: Self::Inner) -> Self;
62}
63
64/// Unwrap a forwarding newtype into its inner value (Rust → R side).
65///
66/// Emitted by `#[derive(IntoR)]`. Powers the `IntoR` container blankets for
67/// `Option<T>` / `Vec<Option<T>>` in this module.
68pub trait IntoRNewtype {
69    /// The wrapped inner type, whose conversions are forwarded to.
70    type Inner;
71
72    /// Unwrap the newtype into its inner value.
73    fn into_inner(self) -> Self::Inner;
74}
75
76/// How a `Vec<Self>` becomes a single R vector SEXP.
77///
78/// This is the shared element-marker behind the **one** `impl<T: …> IntoR for
79/// Vec<T>` blanket slot. Implemented concretely per type — by `#[derive(IntoR)]`
80/// for newtypes (forwarding to `Vec<Inner>`), and by the `MatchArg` bridge in
81/// `match_arg.rs` for `match.arg` enums (STRSXP by variant name). See the module
82/// docs for why this cannot be two competing blankets.
83pub trait IntoRVecElement: Sized {
84    /// Convert all elements into one R vector SEXP.
85    fn elements_into_sexp(values: Vec<Self>) -> SEXP;
86}
87
88// endregion
89
90// region: IntoR for Vec<T> — the unified element-marker blanket
91
92/// The single `IntoR for Vec<T>` blanket, shared by `MatchArg` enums and
93/// `#[derive(IntoR)]` newtypes via [`IntoRVecElement`]. Coexists with the
94/// concrete `impl IntoR for Vec<i32>` (etc.) impls: `IntoRVecElement` is
95/// crate-local, so coherence proves the foreign R-native types do not implement
96/// it.
97impl<T: IntoRVecElement> IntoR for Vec<T> {
98    type Error = std::convert::Infallible;
99
100    #[inline]
101    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
102        Ok(self.into_sexp())
103    }
104
105    #[inline]
106    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
107        self.try_into_sexp()
108    }
109
110    #[inline]
111    fn into_sexp(self) -> SEXP {
112        T::elements_into_sexp(self)
113    }
114}
115
116// endregion
117
118// region: TryFromSexp container blankets (R → Rust)
119
120impl<T: FromRNewtype> TryFromSexp for Vec<T>
121where
122    Vec<T::Inner>: TryFromSexp,
123{
124    type Error = <Vec<T::Inner> as TryFromSexp>::Error;
125
126    #[inline]
127    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
128        Ok(<Vec<T::Inner> as TryFromSexp>::try_from_sexp(sexp)?
129            .into_iter()
130            .map(T::from_inner)
131            .collect())
132    }
133
134    #[inline]
135    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
136        Ok(
137            unsafe { <Vec<T::Inner> as TryFromSexp>::try_from_sexp_unchecked(sexp) }?
138                .into_iter()
139                .map(T::from_inner)
140                .collect(),
141        )
142    }
143}
144
145impl<T: FromRNewtype> TryFromSexp for Option<T>
146where
147    Option<T::Inner>: TryFromSexp,
148{
149    type Error = <Option<T::Inner> as TryFromSexp>::Error;
150
151    #[inline]
152    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
153        Ok(<Option<T::Inner> as TryFromSexp>::try_from_sexp(sexp)?.map(T::from_inner))
154    }
155
156    #[inline]
157    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
158        Ok(
159            unsafe { <Option<T::Inner> as TryFromSexp>::try_from_sexp_unchecked(sexp) }?
160                .map(T::from_inner),
161        )
162    }
163}
164
165impl<T: FromRNewtype> TryFromSexp for Vec<Option<T>>
166where
167    Vec<Option<T::Inner>>: TryFromSexp,
168{
169    type Error = <Vec<Option<T::Inner>> as TryFromSexp>::Error;
170
171    #[inline]
172    fn try_from_sexp(sexp: SEXP) -> Result<Self, Self::Error> {
173        Ok(<Vec<Option<T::Inner>> as TryFromSexp>::try_from_sexp(sexp)?
174            .into_iter()
175            .map(|opt| opt.map(T::from_inner))
176            .collect())
177    }
178
179    #[inline]
180    unsafe fn try_from_sexp_unchecked(sexp: SEXP) -> Result<Self, Self::Error> {
181        Ok(
182            unsafe { <Vec<Option<T::Inner>> as TryFromSexp>::try_from_sexp_unchecked(sexp) }?
183                .into_iter()
184                .map(|opt| opt.map(T::from_inner))
185                .collect(),
186        )
187    }
188}
189
190// endregion
191
192// region: IntoR container blankets (Rust → R) for Vec<Option>
193
194// NOTE: there is deliberately no `impl<T: IntoRNewtype> IntoR for Option<T>`.
195// That bare `Option<T>` blanket collides (E0119) with the pre-existing
196// `impl<T: Copy + IntoR> IntoR for Option<&T>` (into_r/large_integers.rs): `&T`
197// is `#[fundamental]`, so a downstream crate *could* implement `IntoRNewtype`
198// for `&LocalType`, and coherence cannot prove the two disjoint. Returning a
199// `Option<MyNewtype>` to R is the one shape the derive does not grant — map to
200// the inner first (`opt.map(|x| x.0)` → `Option<Inner>`), which mirrors the
201// NULL-vs-NA guidance already on the `Option<&T>` impl. See issue #844.
202
203impl<T: IntoRNewtype> IntoR for Vec<Option<T>>
204where
205    Vec<Option<T::Inner>>: IntoR,
206{
207    type Error = <Vec<Option<T::Inner>> as IntoR>::Error;
208
209    #[inline]
210    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
211        self.into_iter()
212            .map(|opt| opt.map(T::into_inner))
213            .collect::<Vec<Option<T::Inner>>>()
214            .try_into_sexp()
215    }
216
217    #[inline]
218    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
219        unsafe {
220            self.into_iter()
221                .map(|opt| opt.map(T::into_inner))
222                .collect::<Vec<Option<T::Inner>>>()
223                .try_into_sexp_unchecked()
224        }
225    }
226
227    #[inline]
228    fn into_sexp(self) -> SEXP {
229        self.into_iter()
230            .map(|opt| opt.map(T::into_inner))
231            .collect::<Vec<Option<T::Inner>>>()
232            .into_sexp()
233    }
234
235    #[inline]
236    unsafe fn into_sexp_unchecked(self) -> SEXP {
237        unsafe {
238            self.into_iter()
239                .map(|opt| opt.map(T::into_inner))
240                .collect::<Vec<Option<T::Inner>>>()
241                .into_sexp_unchecked()
242        }
243    }
244}
245
246// endregion