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