Skip to main content

miniextendr_macros/
newtype_derive.rs

1//! `#[derive(TryFromSexp)]` / `#[derive(IntoR)]` — forward R↔Rust conversions
2//! from a single-field newtype to its inner type.
3//!
4//! For a newtype `struct UserId(Uuid)` (or `struct UserId { inner: Uuid }`):
5//!
6//! - `#[derive(TryFromSexp)]` generates the R → Rust direction: a scalar
7//!   `TryFromSexp` impl that forwards to the inner type, plus a
8//!   [`FromRNewtype`](miniextendr_api::FromRNewtype) marker impl. The marker lets
9//!   the container blankets in `miniextendr_api::newtype` light up
10//!   `Vec<UserId>` / `Option<UserId>` / `Vec<Option<UserId>>` — see issue #844.
11//! - `#[derive(IntoR)]` generates the Rust → R direction: a scalar `IntoR` impl
12//!   that forwards to the inner type, plus
13//!   [`IntoRNewtype`](miniextendr_api::IntoRNewtype) (for `Option` /
14//!   `Vec<Option>`) and a concrete [`IntoRVecElement`](miniextendr_api::IntoRVecElement)
15//!   impl (for `Vec`).
16//!
17//! Direction is chosen by *which* derive you list — there are no attributes.
18//! Some inner types are convertible in only one direction (a compiled
19//! `regex::Regex` reads from R but cannot be written back): derive only
20//! `TryFromSexp` for those.
21//!
22//! ```ignore
23//! #[derive(TryFromSexp)]            // R -> Rust only
24//! struct Pattern(regex::Regex);
25//!
26//! #[derive(TryFromSexp, IntoR)]     // round-trip
27//! struct UserId(uuid::Uuid);
28//! ```
29//!
30//! Do not derive both `IntoR` and `MatchArg` on the same type: both would feed
31//! the single `IntoR for Vec<T>` blanket slot and collide (E0119).
32
33use proc_macro2::TokenStream;
34use quote::quote;
35use syn::{Data, DeriveInput, Fields, Type};
36
37/// The single field of a newtype: its inner type plus how to wrap/unwrap it.
38struct Newtype {
39    inner: Type,
40    /// `Some(field_ident)` for named newtypes, `None` for tuple newtypes.
41    named_field: Option<syn::Ident>,
42}
43
44impl Newtype {
45    /// `Self(<val>)` or `Self { field: <val> }`.
46    fn wrap(&self, val: &TokenStream) -> TokenStream {
47        match &self.named_field {
48            Some(field) => quote! { Self { #field: #val } },
49            None => quote! { Self(#val) },
50        }
51    }
52
53    /// `<binding>.0` or `<binding>.field`.
54    fn unwrap(&self, binding: &TokenStream) -> TokenStream {
55        match &self.named_field {
56            Some(field) => quote! { #binding.#field },
57            None => quote! { #binding.0 },
58        }
59    }
60}
61
62fn parse_newtype(input: &DeriveInput) -> syn::Result<Newtype> {
63    let data = match &input.data {
64        Data::Struct(data) => data,
65        _ => {
66            return Err(syn::Error::new_spanned(
67                &input.ident,
68                "this derive only works on single-field newtype structs",
69            ));
70        }
71    };
72    match &data.fields {
73        Fields::Unnamed(fields) if fields.unnamed.len() == 1 => Ok(Newtype {
74            inner: fields.unnamed.first().unwrap().ty.clone(),
75            named_field: None,
76        }),
77        Fields::Named(fields) if fields.named.len() == 1 => {
78            let field = fields.named.first().unwrap();
79            Ok(Newtype {
80                inner: field.ty.clone(),
81                named_field: Some(field.ident.clone().unwrap()),
82            })
83        }
84        _ => Err(syn::Error::new_spanned(
85            &input.ident,
86            "this derive requires a struct with exactly one field",
87        )),
88    }
89}
90
91/// Build a `where` clause that merges the struct's existing predicates with one
92/// extra predicate (the `Inner: Trait` bound the forwarding impl requires).
93fn where_with(base: &Option<syn::WhereClause>, extra: TokenStream) -> TokenStream {
94    let mut preds: Vec<TokenStream> = base
95        .iter()
96        .flat_map(|w| w.predicates.iter().map(|p| quote! { #p }))
97        .collect();
98    preds.push(extra);
99    quote! { where #(#preds),* }
100}
101
102/// `#[derive(TryFromSexp)]`: scalar forwarding `TryFromSexp` + `FromRNewtype` marker.
103pub fn derive_try_from_sexp(input: DeriveInput) -> syn::Result<TokenStream> {
104    let nt = parse_newtype(&input)?;
105    let inner = &nt.inner;
106    let name = &input.ident;
107    let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
108    let base_where = &input.generics.where_clause;
109
110    let wrap_val = nt.wrap(&quote! { val });
111    let wrap_inner = nt.wrap(&quote! { inner });
112    let from_where = where_with(
113        base_where,
114        quote! { #inner: ::miniextendr_api::TryFromSexp },
115    );
116
117    Ok(quote! {
118        #[automatically_derived]
119        impl #impl_generics ::miniextendr_api::TryFromSexp for #name #ty_generics #from_where {
120            type Error = <#inner as ::miniextendr_api::TryFromSexp>::Error;
121            #[inline]
122            fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, Self::Error> {
123                <#inner as ::miniextendr_api::TryFromSexp>::try_from_sexp(sexp).map(|val| #wrap_val)
124            }
125            #[inline]
126            unsafe fn try_from_sexp_unchecked(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, Self::Error> {
127                unsafe { <#inner as ::miniextendr_api::TryFromSexp>::try_from_sexp_unchecked(sexp) }.map(|val| #wrap_val)
128            }
129        }
130
131        #[automatically_derived]
132        impl #impl_generics ::miniextendr_api::FromRNewtype for #name #ty_generics #base_where {
133            type Inner = #inner;
134            #[inline]
135            fn from_inner(inner: #inner) -> Self {
136                #wrap_inner
137            }
138        }
139    })
140}
141
142/// `#[derive(IntoR)]`: scalar forwarding `IntoR` + `IntoRNewtype` marker (for
143/// `Option`/`Vec<Option>` blankets) + concrete `IntoRVecElement` (for `Vec`).
144pub fn derive_into_r(input: DeriveInput) -> syn::Result<TokenStream> {
145    let nt = parse_newtype(&input)?;
146    let inner = &nt.inner;
147    let name = &input.ident;
148    let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
149    let base_where = &input.generics.where_clause;
150
151    let unwrap_self = nt.unwrap(&quote! { self });
152    let unwrap_v = nt.unwrap(&quote! { v });
153    let into_where = where_with(base_where, quote! { #inner: ::miniextendr_api::IntoR });
154    // The Vec element impl forwards to `Vec<Inner>: IntoR`; gate it on that so a
155    // newtype whose inner lacks a vector conversion still gets the scalar IntoR.
156    let vec_elem_where = where_with(
157        base_where,
158        quote! { ::std::vec::Vec<#inner>: ::miniextendr_api::IntoR },
159    );
160
161    Ok(quote! {
162        #[automatically_derived]
163        impl #impl_generics ::miniextendr_api::IntoR for #name #ty_generics #into_where {
164            type Error = <#inner as ::miniextendr_api::IntoR>::Error;
165            #[inline]
166            fn try_into_sexp(self) -> ::core::result::Result<::miniextendr_api::SEXP, Self::Error> {
167                <#inner as ::miniextendr_api::IntoR>::try_into_sexp(#unwrap_self)
168            }
169            #[inline]
170            unsafe fn try_into_sexp_unchecked(self) -> ::core::result::Result<::miniextendr_api::SEXP, Self::Error> {
171                unsafe { <#inner as ::miniextendr_api::IntoR>::try_into_sexp_unchecked(#unwrap_self) }
172            }
173            #[inline]
174            fn into_sexp(self) -> ::miniextendr_api::SEXP {
175                <#inner as ::miniextendr_api::IntoR>::into_sexp(#unwrap_self)
176            }
177            #[inline]
178            unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
179                unsafe { <#inner as ::miniextendr_api::IntoR>::into_sexp_unchecked(#unwrap_self) }
180            }
181        }
182
183        #[automatically_derived]
184        impl #impl_generics ::miniextendr_api::IntoRNewtype for #name #ty_generics #base_where {
185            type Inner = #inner;
186            #[inline]
187            fn into_inner(self) -> #inner {
188                #unwrap_self
189            }
190        }
191
192        #[automatically_derived]
193        impl #impl_generics ::miniextendr_api::IntoRVecElement for #name #ty_generics #vec_elem_where {
194            #[inline]
195            fn elements_into_sexp(values: ::std::vec::Vec<Self>) -> ::miniextendr_api::SEXP {
196                <::std::vec::Vec<#inner> as ::miniextendr_api::IntoR>::into_sexp(
197                    values.into_iter().map(|v| #unwrap_v).collect::<::std::vec::Vec<#inner>>()
198                )
199            }
200        }
201    })
202}