1use proc_macro2::TokenStream;
48use quote::quote;
49use syn::{Data, DeriveInput, Fields};
50
51use crate::naming::apply_rename_all;
52
53#[derive(#[automatically_derived]
impl ::core::default::Default for MatchArgAttrs {
#[inline]
fn default() -> Self {
MatchArgAttrs {
rename: ::core::default::Default::default(),
rename_all: ::core::default::Default::default(),
aliases: ::core::default::Default::default(),
}
}
}Default)]
55struct MatchArgAttrs {
56 rename: Option<String>,
58 rename_all: Option<String>,
61 aliases: Vec<(syn::LitStr, bool)>,
66}
67
68fn parse_match_arg_attrs(attrs: &[syn::Attribute]) -> syn::Result<MatchArgAttrs> {
77 let mut result = MatchArgAttrs::default();
78
79 for attr in attrs {
80 if attr.path().is_ident("match_arg") {
81 let mut aliases: Vec<syn::LitStr> = Vec::new();
82 let mut listed: Option<(bool, syn::Path)> = None;
85 attr.parse_nested_meta(|meta| {
86 if meta.path.is_ident("rename") {
87 let value: syn::LitStr = meta.value()?.parse()?;
88 result.rename = Some(value.value());
89 } else if meta.path.is_ident("alias") {
90 aliases.push(meta.value()?.parse()?);
91 } else if meta.path.is_ident("listed") {
92 if listed.is_some() {
93 return Err(meta
94 .error("`listed` is given more than once in this #[match_arg(...)]"));
95 }
96 let value: syn::Expr = meta.value()?.parse()?;
97 let syn::Expr::Lit(syn::ExprLit {
98 lit: syn::Lit::Bool(value),
99 ..
100 }) = value
101 else {
102 return Err(syn::Error::new_spanned(
103 value,
104 "expected `listed = true` or `listed = false`",
105 ));
106 };
107 listed = Some((value.value, meta.path.clone()));
108 } else if meta.path.is_ident("rename_all") {
109 let value: syn::LitStr = meta.value()?.parse()?;
110 let val = value.value();
111 match val.as_str() {
112 "snake_case" | "kebab-case" | "lower" | "upper" => {}
113 _ => {
114 return Err(meta.error(
115 "unsupported rename_all value; expected one of: \
116 snake_case, kebab-case, lower, upper",
117 ));
118 }
119 }
120 result.rename_all = Some(val);
121 } else {
122 return Err(meta.error(
123 "unknown match_arg attribute; expected `rename`, `rename_all`, \
124 `alias` or `listed`",
125 ));
126 }
127 Ok(())
128 })?;
129 if let Some((_, key)) = listed.as_ref().filter(|_| aliases.is_empty()) {
130 return Err(syn::Error::new_spanned(
131 key,
132 "`listed` applies to the aliases of its own #[match_arg(...)], and this \
133 one has none: write it next to `alias = \"...\"`",
134 ));
135 }
136 let listed = listed.is_none_or(|(listed, _)| listed);
137 result
138 .aliases
139 .extend(aliases.into_iter().map(|alias| (alias, listed)));
140 }
141 }
142
143 Ok(result)
144}
145
146pub fn derive_match_arg(input: DeriveInput) -> syn::Result<TokenStream> {
171 let name = &input.ident;
172 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
173
174 if !input.generics.params.is_empty() {
176 return Err(syn::Error::new_spanned(
177 &input.generics,
178 "#[derive(MatchArg)] does not support generic enums",
179 ));
180 }
181
182 let attrs = parse_match_arg_attrs(&input.attrs)?;
184 if let Some((alias, _)) = attrs.aliases.first() {
185 return Err(syn::Error::new_spanned(
186 alias,
187 "`alias` names another spelling of one choice: put \
188 #[match_arg(alias = \"...\")] on that choice's variant",
189 ));
190 }
191
192 let variants = match &input.data {
194 Data::Enum(data) => &data.variants,
195 Data::Struct(_) => {
196 return Err(syn::Error::new_spanned(
197 &input,
198 "#[derive(MatchArg)] can only be applied to enums",
199 ));
200 }
201 Data::Union(_) => {
202 return Err(syn::Error::new_spanned(
203 &input,
204 "#[derive(MatchArg)] can only be applied to enums",
205 ));
206 }
207 };
208
209 if variants.is_empty() {
210 return Err(syn::Error::new_spanned(
211 &input,
212 "#[derive(MatchArg)] requires at least one variant",
213 ));
214 }
215
216 let mut choice_names = Vec::new();
217 let mut variant_idents = Vec::new();
218 let mut variant_aliases: Vec<(syn::LitStr, usize, bool)> = Vec::new();
221
222 for variant in variants {
223 if !#[allow(non_exhaustive_omitted_patterns)] match variant.fields {
Fields::Unit => true,
_ => false,
}matches!(variant.fields, Fields::Unit) {
225 return Err(syn::Error::new_spanned(
226 variant,
227 "#[derive(MatchArg)] only supports fieldless (C-style) enum variants",
228 ));
229 }
230
231 let var_attrs = parse_match_arg_attrs(&variant.attrs)?;
233
234 let choice_name = if let Some(r) = var_attrs.rename {
236 r
237 } else {
238 apply_rename_all(
239 &crate::naming::ident_name(&variant.ident),
240 attrs.rename_all.as_deref(),
241 )
242 };
243
244 let at = choice_names.len();
245 variant_aliases.extend(
246 var_attrs
247 .aliases
248 .into_iter()
249 .map(|(alias, listed)| (alias, at, listed)),
250 );
251 choice_names.push(choice_name);
252 variant_idents.push(&variant.ident);
253 }
254
255 {
257 let mut seen = std::collections::HashSet::new();
258 for (i, name) in choice_names.iter().enumerate() {
259 if !seen.insert(name.as_str()) {
260 return Err(syn::Error::new_spanned(
261 &variants.iter().nth(i).unwrap().ident,
262 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("duplicate choice name {0:?} in #[derive(MatchArg)]",
name))
})format!("duplicate choice name {:?} in #[derive(MatchArg)]", name),
263 ));
264 }
265 }
266 }
267
268 check_aliases(&choice_names, &variant_aliases)?;
269
270 let choice_strs: Vec<&str> = choice_names.iter().map(|s| s.as_str()).collect();
271 let alias_entries = variant_aliases.iter().map(|(alias, at, listed)| {
274 let alias = alias.value();
275 let target = choice_names[*at].as_str();
276 if *listed {
277 {
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "match_arg");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "MatchArgAlias");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "new");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&alias, &mut _s);
::quote::__private::push_comma(&mut _s);
::quote::ToTokens::to_tokens(&target, &mut _s);
_s
});
_s
}quote! { ::miniextendr_api::match_arg::MatchArgAlias::new(#alias, #target) }
278 } else {
279 {
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "match_arg");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "MatchArgAlias");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "unlisted");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&alias, &mut _s);
::quote::__private::push_comma(&mut _s);
::quote::ToTokens::to_tokens(&target, &mut _s);
_s
});
_s
}quote! { ::miniextendr_api::match_arg::MatchArgAlias::unlisted(#alias, #target) }
280 }
281 });
282
283 Ok({
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "impl");
::quote::ToTokens::to_tokens(&impl_generics, &mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "match_arg");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "MatchArg");
::quote::__private::push_ident(&mut _s, "for");
::quote::ToTokens::to_tokens(&name, &mut _s);
::quote::ToTokens::to_tokens(&ty_generics, &mut _s);
::quote::ToTokens::to_tokens(&where_clause, &mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "const");
::quote::__private::push_ident(&mut _s, "CHOICES");
::quote::__private::push_colon(&mut _s);
::quote::__private::push_and(&mut _s);
::quote::__private::push_lifetime(&mut _s, "\'static");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Bracket,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_and(&mut _s);
::quote::__private::push_lifetime(&mut _s, "\'static");
::quote::__private::push_ident(&mut _s, "str");
_s
});
::quote::__private::push_eq(&mut _s);
::quote::__private::push_and(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Bracket,
{
let mut _s = ::quote::__private::TokenStream::new();
{
use ::quote::__private::ext::*;
let mut _first = true;
let has_iter = ::quote::__private::HasIterator::<false>;
#[allow(unused_mut)]
let (mut choice_strs, i) = choice_strs.quote_into_iter();
let has_iter = has_iter | i;
<_ as
::quote::__private::CheckHasIterator<true>>::check(has_iter);
while true {
let choice_strs =
match choice_strs.next() {
Some(_x) => ::quote::__private::RepInterp(_x),
None => break,
};
if !_first { ::quote::__private::push_comma(&mut _s); }
_first = false;
::quote::ToTokens::to_tokens(&choice_strs, &mut _s);
}
}
_s
});
::quote::__private::push_semi(&mut _s);
::quote::__private::push_ident(&mut _s, "const");
::quote::__private::push_ident(&mut _s, "ALIASES");
::quote::__private::push_colon(&mut _s);
::quote::__private::push_and(&mut _s);
::quote::__private::push_lifetime(&mut _s, "\'static");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Bracket,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "match_arg");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "MatchArgAlias");
_s
});
::quote::__private::push_eq(&mut _s);
::quote::__private::push_and(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Bracket,
{
let mut _s = ::quote::__private::TokenStream::new();
{
use ::quote::__private::ext::*;
let mut _first = true;
let has_iter = ::quote::__private::HasIterator::<false>;
#[allow(unused_mut)]
let (mut alias_entries, i) =
alias_entries.quote_into_iter();
let has_iter = has_iter | i;
<_ as
::quote::__private::CheckHasIterator<true>>::check(has_iter);
while true {
let alias_entries =
match alias_entries.next() {
Some(_x) => ::quote::__private::RepInterp(_x),
None => break,
};
if !_first { ::quote::__private::push_comma(&mut _s); }
_first = false;
::quote::ToTokens::to_tokens(&alias_entries, &mut _s);
}
}
_s
});
::quote::__private::push_semi(&mut _s);
::quote::__private::push_ident(&mut _s, "fn");
::quote::__private::push_ident(&mut _s, "from_choice");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "choice");
::quote::__private::push_colon(&mut _s);
::quote::__private::push_and(&mut _s);
::quote::__private::push_ident(&mut _s, "str");
_s
});
::quote::__private::push_rarrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "option");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Option");
::quote::__private::push_lt(&mut _s);
::quote::__private::push_ident(&mut _s, "Self");
::quote::__private::push_gt(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "match");
::quote::__private::push_ident(&mut _s, "choice");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
{
use ::quote::__private::ext::*;
let has_iter = ::quote::__private::HasIterator::<false>;
#[allow(unused_mut)]
let (mut choice_strs, i) = choice_strs.quote_into_iter();
let has_iter = has_iter | i;
#[allow(unused_mut)]
let (mut variant_idents, i) =
variant_idents.quote_into_iter();
let has_iter = has_iter | i;
<_ as
::quote::__private::CheckHasIterator<true>>::check(has_iter);
while true {
let choice_strs =
match choice_strs.next() {
Some(_x) => ::quote::__private::RepInterp(_x),
None => break,
};
let variant_idents =
match variant_idents.next() {
Some(_x) => ::quote::__private::RepInterp(_x),
None => break,
};
::quote::ToTokens::to_tokens(&choice_strs, &mut _s);
::quote::__private::push_fat_arrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "option");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Option");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Some");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "Self");
::quote::__private::push_colon2(&mut _s);
::quote::ToTokens::to_tokens(&variant_idents, &mut _s);
_s
});
::quote::__private::push_comma(&mut _s);
}
}
::quote::__private::push_underscore(&mut _s);
::quote::__private::push_fat_arrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "option");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Option");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "None");
::quote::__private::push_comma(&mut _s);
_s
});
_s
});
::quote::__private::push_ident(&mut _s, "fn");
::quote::__private::push_ident(&mut _s, "to_choice");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "self");
_s
});
::quote::__private::push_rarrow(&mut _s);
::quote::__private::push_and(&mut _s);
::quote::__private::push_lifetime(&mut _s, "\'static");
::quote::__private::push_ident(&mut _s, "str");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "match");
::quote::__private::push_ident(&mut _s, "self");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
{
use ::quote::__private::ext::*;
let has_iter = ::quote::__private::HasIterator::<false>;
#[allow(unused_mut)]
let (mut variant_idents, i) =
variant_idents.quote_into_iter();
let has_iter = has_iter | i;
#[allow(unused_mut)]
let (mut choice_strs, i) = choice_strs.quote_into_iter();
let has_iter = has_iter | i;
<_ as
::quote::__private::CheckHasIterator<true>>::check(has_iter);
while true {
let variant_idents =
match variant_idents.next() {
Some(_x) => ::quote::__private::RepInterp(_x),
None => break,
};
let choice_strs =
match choice_strs.next() {
Some(_x) => ::quote::__private::RepInterp(_x),
None => break,
};
::quote::__private::push_ident(&mut _s, "Self");
::quote::__private::push_colon2(&mut _s);
::quote::ToTokens::to_tokens(&variant_idents, &mut _s);
::quote::__private::push_fat_arrow(&mut _s);
::quote::ToTokens::to_tokens(&choice_strs, &mut _s);
::quote::__private::push_comma(&mut _s);
}
}
_s
});
_s
});
_s
});
::quote::__private::push_ident(&mut _s, "impl");
::quote::ToTokens::to_tokens(&impl_generics, &mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "TryFromSexp");
::quote::__private::push_ident(&mut _s, "for");
::quote::ToTokens::to_tokens(&name, &mut _s);
::quote::ToTokens::to_tokens(&ty_generics, &mut _s);
::quote::ToTokens::to_tokens(&where_clause, &mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "type");
::quote::__private::push_ident(&mut _s, "Error");
::quote::__private::push_eq(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "SexpError");
::quote::__private::push_semi(&mut _s);
::quote::__private::push_ident(&mut _s, "const");
::quote::__private::push_ident(&mut _s, "CHARACTER_ONLY");
::quote::__private::push_colon(&mut _s);
::quote::__private::push_ident(&mut _s, "bool");
::quote::__private::push_eq(&mut _s);
::quote::__private::push_ident(&mut _s, "true");
::quote::__private::push_semi(&mut _s);
::quote::__private::push_ident(&mut _s, "fn");
::quote::__private::push_ident(&mut _s, "try_from_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "sexp");
::quote::__private::push_colon(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "SEXP");
_s
});
::quote::__private::push_rarrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "result");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Result");
::quote::__private::push_lt(&mut _s);
::quote::__private::push_ident(&mut _s, "Self");
::quote::__private::push_comma(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "SexpError");
::quote::__private::push_gt(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s,
"match_arg_from_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "sexp");
_s
});
::quote::__private::push_dot(&mut _s);
::quote::__private::push_ident(&mut _s, "map_err");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "convert");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Into");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "into");
_s
});
_s
});
_s
});
::quote::__private::push_ident(&mut _s, "impl");
::quote::ToTokens::to_tokens(&impl_generics, &mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "IntoR");
::quote::__private::push_ident(&mut _s, "for");
::quote::ToTokens::to_tokens(&name, &mut _s);
::quote::ToTokens::to_tokens(&ty_generics, &mut _s);
::quote::ToTokens::to_tokens(&where_clause, &mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "type");
::quote::__private::push_ident(&mut _s, "Error");
::quote::__private::push_eq(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "convert");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Infallible");
::quote::__private::push_semi(&mut _s);
::quote::__private::push_ident(&mut _s, "fn");
::quote::__private::push_ident(&mut _s, "try_into_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "self");
_s
});
::quote::__private::push_rarrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "result");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Result");
::quote::__private::push_lt(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "SEXP");
::quote::__private::push_comma(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "convert");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Infallible");
::quote::__private::push_gt(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "result");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Result");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Ok");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "self");
::quote::__private::push_dot(&mut _s);
::quote::__private::push_ident(&mut _s, "into_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
::quote::__private::TokenStream::new());
_s
});
_s
});
::quote::__private::push_ident(&mut _s, "unsafe");
::quote::__private::push_ident(&mut _s, "fn");
::quote::__private::push_ident(&mut _s,
"try_into_sexp_unchecked");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "self");
_s
});
::quote::__private::push_rarrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "result");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Result");
::quote::__private::push_lt(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "SEXP");
::quote::__private::push_comma(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "core");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "convert");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "Infallible");
::quote::__private::push_gt(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "self");
::quote::__private::push_dot(&mut _s);
::quote::__private::push_ident(&mut _s, "try_into_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
::quote::__private::TokenStream::new());
_s
});
::quote::__private::push_ident(&mut _s, "fn");
::quote::__private::push_ident(&mut _s, "into_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "self");
_s
});
::quote::__private::push_rarrow(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "SEXP");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "use");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "match_arg");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "MatchArg");
::quote::__private::push_semi(&mut _s);
::quote::__private::push_ident(&mut _s, "self");
::quote::__private::push_dot(&mut _s);
::quote::__private::push_ident(&mut _s, "to_choice");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
::quote::__private::TokenStream::new());
::quote::__private::push_dot(&mut _s);
::quote::__private::push_ident(&mut _s, "into_sexp");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
::quote::__private::TokenStream::new());
_s
});
_s
});
_s
}quote! {
284 impl #impl_generics ::miniextendr_api::match_arg::MatchArg for #name #ty_generics #where_clause {
285 const CHOICES: &'static [&'static str] = &[#(#choice_strs),*];
286 const ALIASES: &'static [::miniextendr_api::match_arg::MatchArgAlias] =
287 &[#(#alias_entries),*];
288
289 fn from_choice(choice: &str) -> ::core::option::Option<Self> {
290 match choice {
291 #(#choice_strs => ::core::option::Option::Some(Self::#variant_idents),)*
292 _ => ::core::option::Option::None,
293 }
294 }
295
296 fn to_choice(self) -> &'static str {
297 match self {
298 #(Self::#variant_idents => #choice_strs,)*
299 }
300 }
301 }
302
303 impl #impl_generics ::miniextendr_api::TryFromSexp for #name #ty_generics #where_clause {
304 type Error = ::miniextendr_api::SexpError;
305 const CHARACTER_ONLY: bool = true;
306
307 fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, ::miniextendr_api::SexpError> {
308 ::miniextendr_api::match_arg_from_sexp(sexp).map_err(::core::convert::Into::into)
309 }
310 }
311
312 impl #impl_generics ::miniextendr_api::IntoR for #name #ty_generics #where_clause {
313 type Error = ::core::convert::Infallible;
314
315 fn try_into_sexp(self) -> ::core::result::Result<::miniextendr_api::SEXP, ::core::convert::Infallible> {
316 ::core::result::Result::Ok(self.into_sexp())
317 }
318
319 unsafe fn try_into_sexp_unchecked(self) -> ::core::result::Result<::miniextendr_api::SEXP, ::core::convert::Infallible> {
320 self.try_into_sexp()
321 }
322
323 fn into_sexp(self) -> ::miniextendr_api::SEXP {
324 use ::miniextendr_api::match_arg::MatchArg;
325 self.to_choice().into_sexp()
326 }
327 }
328
329
330 })
331}
332
333fn check_aliases(choices: &[String], aliases: &[(syn::LitStr, usize, bool)]) -> syn::Result<()> {
343 let mut errors: Option<syn::Error> = None;
344 let mut seen = std::collections::HashSet::new();
345 for (lit, at, _) in aliases {
346 let alias = lit.value();
347 let target = &choices[*at];
348 let problem = if alias.is_empty() {
349 Some("an alias can't be empty: the empty string matches no choice".to_string())
350 } else if alias == "NA" {
351 Some("alias \"NA\" would read as a missing value".to_string())
352 } else if choices.contains(&alias) {
353 Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("alias {0:?} is already a choice",
alias))
})format!("alias {alias:?} is already a choice"))
354 } else if !seen.insert(alias.clone()) {
355 Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("alias {0:?} is given more than once",
alias))
})format!("alias {alias:?} is given more than once"))
356 } else {
357 choices
358 .iter()
359 .find(|choice| *choice != target && choice.starts_with(alias.as_str()))
360 .map(|other| {
361 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("alias {0:?} for {1:?} is a prefix of the choice {2:?}, so it would change what typing {0:?} selects",
alias, target, other))
})format!(
362 "alias {alias:?} for {target:?} is a prefix of the choice {other:?}, \
363 so it would change what typing {alias:?} selects"
364 )
365 })
366 };
367 if let Some(problem) = problem {
368 let error = syn::Error::new_spanned(lit, problem);
369 match errors.as_mut() {
370 Some(errors) => errors.combine(error),
371 None => errors = Some(error),
372 }
373 }
374 }
375 match errors {
376 Some(errors) => Err(errors),
377 None => Ok(()),
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_simple_derive() {
387 let input: DeriveInput = syn::parse_quote! {
388 enum Mode {
389 Fast,
390 Safe,
391 Debug,
392 }
393 };
394
395 let result = derive_match_arg(input).unwrap();
396 let code = result.to_string();
397 assert!(code.contains("Fast"));
398 assert!(code.contains("Safe"));
399 assert!(code.contains("Debug"));
400 assert!(code.contains("CHOICES"));
401 assert!(code.contains("from_choice"));
402 assert!(code.contains("to_choice"));
403 }
404
405 #[test]
406 fn test_rename_all() {
407 let input: DeriveInput = syn::parse_quote! {
408 #[match_arg(rename_all = "snake_case")]
409 enum Mode {
410 FastMode,
411 SafeMode,
412 }
413 };
414
415 let result = derive_match_arg(input).unwrap();
416 let code = result.to_string();
417 assert!(code.contains("fast_mode"));
418 assert!(code.contains("safe_mode"));
419 }
420
421 #[test]
422 fn test_rename_variant() {
423 let input: DeriveInput = syn::parse_quote! {
424 enum Priority {
425 #[match_arg(rename = "lo")]
426 Low,
427 #[match_arg(rename = "hi")]
428 High,
429 }
430 };
431
432 let result = derive_match_arg(input).unwrap();
433 let code = result.to_string();
434 assert!(code.contains("\"lo\""));
435 assert!(code.contains("\"hi\""));
436 }
437
438 #[test]
439 fn test_reject_fields() {
440 let input: DeriveInput = syn::parse_quote! {
441 enum Bad {
442 A(i32),
443 }
444 };
445
446 let result = derive_match_arg(input);
447 assert!(result.is_err());
448 }
449
450 #[test]
451 fn test_reject_struct() {
452 let input: DeriveInput = syn::parse_quote! {
453 struct Bad;
454 };
455
456 let result = derive_match_arg(input);
457 assert!(result.is_err());
458 }
459
460 #[test]
461 fn test_reject_empty() {
462 let input: DeriveInput = syn::parse_quote! {
463 enum Empty {}
464 };
465
466 let result = derive_match_arg(input);
467 assert!(result.is_err());
468 }
469
470 #[test]
471 fn test_into_r_impl_present() {
472 let input: DeriveInput = syn::parse_quote! {
476 enum Mode {
477 Fast,
478 Safe,
479 Debug,
480 }
481 };
482
483 let result = derive_match_arg(input).unwrap();
484 let code = result.to_string();
485 assert!(code.contains("IntoR for Mode"));
487 assert!(!code.contains("IntoR for :: std :: vec :: Vec < Mode >"));
489 assert!(!code.contains("match_arg_vec_into_sexp"));
490 }
491
492 #[test]
497 fn test_aliases() {
498 let input: DeriveInput = syn::parse_quote! {
499 #[match_arg(rename_all = "lower")]
500 enum Shade {
501 Red,
502 #[match_arg(alias = "grey", alias = "Grau")]
503 #[match_arg(alias = "gris", listed = false)]
504 Gray,
505 #[match_arg(rename = "navy", alias = "Blue", listed = true)]
506 Blue,
507 }
508 };
509 let code = derive_match_arg(input).unwrap().to_string();
510 assert!(
511 code.contains(
512 r#"const CHOICES : & 'static [& 'static str] = & ["red" , "gray" , "navy"] ;"#
513 ),
514 "{code}"
515 );
516 let alias = |kind: &str, alias: &str, choice: &str| {
517 format!(
518 ":: miniextendr_api :: match_arg :: MatchArgAlias :: {kind} ({alias:?} , {choice:?})"
519 )
520 };
521 let entries = [
522 alias("new", "grey", "gray"),
523 alias("new", "Grau", "gray"),
524 alias("unlisted", "gris", "gray"),
525 alias("new", "Blue", "navy"),
526 ];
527 assert!(
528 code.contains(&format!(
529 "const ALIASES : & 'static [:: miniextendr_api :: match_arg :: MatchArgAlias] = & [{}] ;",
530 entries.join(" , ")
531 )),
532 "{code}"
533 );
534 assert!(!code.contains(r#""grey" => Some"#), "{code}");
536 }
537
538 #[test]
540 fn test_no_aliases() {
541 let input: DeriveInput = syn::parse_quote! {
542 enum Mode { Fast, Safe }
543 };
544 let code = derive_match_arg(input).unwrap().to_string();
545 assert!(
546 code.contains(
547 r#"const ALIASES : & 'static [:: miniextendr_api :: match_arg :: MatchArgAlias] = & [] ;"#
548 ),
549 "{code}"
550 );
551 }
552
553 #[test]
556 fn test_reject_listed() {
557 let error = |input: DeriveInput| match derive_match_arg(input) {
558 Ok(_) => String::new(),
559 Err(e) => e.to_string(),
560 };
561 let no_alias = "`listed` applies to the aliases of its own #[match_arg(...)], and this \
562 one has none: write it next to `alias = \"...\"`";
563 assert_eq!(
564 error(syn::parse_quote! {
565 enum Shade {
566 #[match_arg(alias = "grey")]
567 #[match_arg(listed = false)]
568 Gray,
569 }
570 }),
571 no_alias
572 );
573 assert_eq!(
574 error(syn::parse_quote! {
575 #[match_arg(listed = false)]
576 enum Shade { Gray }
577 }),
578 no_alias
579 );
580 assert_eq!(
581 error(syn::parse_quote! {
582 enum Shade {
583 #[match_arg(alias = "grey", listed = false, listed = true)]
584 Gray,
585 }
586 }),
587 "`listed` is given more than once in this #[match_arg(...)]"
588 );
589 for value in [quote!("false"), quote!(no), quote!(0)] {
590 let input: DeriveInput = syn::parse_quote! {
591 enum Shade {
592 #[match_arg(alias = "grey", listed = #value)]
593 Gray,
594 }
595 };
596 assert_eq!(
597 error(input),
598 "expected `listed = true` or `listed = false`",
599 "{value}"
600 );
601 }
602 }
603
604 #[test]
607 fn test_reject_aliases() {
608 let errors = |input: DeriveInput| -> Vec<String> {
609 match derive_match_arg(input) {
610 Ok(_) => Vec::new(),
611 Err(e) => e.into_iter().map(|e| e.to_string()).collect(),
612 }
613 };
614 assert_eq!(
615 errors(syn::parse_quote! {
616 #[match_arg(rename_all = "lower")]
617 enum Shade {
618 #[match_arg(alias = "")]
619 Red,
620 #[match_arg(alias = "NA", alias = "blue", alias = "grey")]
621 Gray,
622 #[match_arg(alias = "grey", alias = "bl")]
623 Blue,
624 #[match_arg(alias = "gr")]
625 Green,
626 }
627 }),
628 [
629 "an alias can't be empty: the empty string matches no choice",
630 "alias \"NA\" would read as a missing value",
631 "alias \"blue\" is already a choice",
632 "alias \"grey\" is given more than once",
633 "alias \"gr\" for \"green\" is a prefix of the choice \"gray\", \
634 so it would change what typing \"gr\" selects",
635 ]
636 );
637 assert!(
639 errors(syn::parse_quote! {
640 enum Shade {
641 Red,
642 #[match_arg(alias = "Gr")]
643 Gray,
644 }
645 })
646 .is_empty()
647 );
648 assert_eq!(
650 errors(syn::parse_quote! {
651 #[match_arg(alias = "grey")]
652 enum Shade { Red, Gray }
653 }),
654 ["`alias` names another spelling of one choice: put \
655 #[match_arg(alias = \"...\")] on that choice's variant"]
656 );
657 }
658
659 #[test]
660 fn test_duplicate_choice_names() {
661 let input: DeriveInput = syn::parse_quote! {
662 enum Dup {
663 #[match_arg(rename = "same")]
664 A,
665 #[match_arg(rename = "same")]
666 B,
667 }
668 };
669
670 let result = derive_match_arg(input);
671 assert!(result.is_err());
672 }
673}