use crate::ParseResult;
#[cfg(test)]
mod tests;
mod kw {
syn::custom_keyword!(error);
syn::custom_keyword!(output);
syn::custom_keyword!(with);
}
const NEWTYPE_NAME: &str = "newtype";
pub(crate) fn parse_input(input: syn::DeriveInput) -> syn::Result<ParseResult> {
let inner_ty = parse_derive_input_data(input.data)?;
let mut res = ParseResult::new(input.ident, inner_ty, input.generics);
for attr in input.attrs {
if !attr.path().is_ident(NEWTYPE_NAME) {
continue;
}
parse_top_level_meta(attr.meta, &mut res)?;
}
Ok(res)
}
fn parse_derive_input_data(data: syn::Data) -> syn::Result<syn::Type> {
let msg = "expected `struct Newtype(inner_type)`";
let field = match &data {
syn::Data::Struct(s) => match &s.fields {
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
fields.unnamed.first().unwrap()
}
syn::Fields::Unnamed(fields) => Err(syn::Error::new_spanned(fields, msg))?,
syn::Fields::Named(fields) => Err(syn::Error::new_spanned(fields, msg))?,
syn::Fields::Unit => Err(syn::Error::new_spanned(s.struct_token, msg))?,
},
syn::Data::Enum(e) => Err(syn::Error::new_spanned(e.enum_token, msg))?,
syn::Data::Union(u) => Err(syn::Error::new_spanned(u.union_token, msg))?,
};
let inner_ty = field.ty.clone();
Ok(inner_ty)
}
fn parse_top_level_meta(meta: syn::Meta, res: &mut ParseResult) -> syn::Result<()> {
match meta {
syn::Meta::Path(path) => parse_top_level_path(path, res)?,
syn::Meta::List(list) => parse_top_level_list(list, res)?,
syn::Meta::NameValue(name_value) => parse_top_level_name_value(name_value, res)?,
}
Ok(())
}
fn parse_top_level_path(path: syn::Path, _res: &mut ParseResult) -> syn::Result<()> {
Err(syn::Error::new_spanned(
path,
"expected `#[newtype(attr1, attr2)]`",
))
}
fn parse_top_level_list(list: syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
let args = list.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
)?;
for meta in &args {
parse_nested_meta(meta, res)?;
}
Ok(())
}
fn parse_top_level_name_value(
name_value: syn::MetaNameValue,
_res: &mut ParseResult,
) -> syn::Result<()> {
Err(syn::Error::new_spanned(
name_value,
"expected `#[newtype(attr1, attr2)]`",
))
}
fn parse_nested_meta(meta: &syn::Meta, res: &mut ParseResult) -> syn::Result<()> {
let attr_type = AttrType::try_from(meta.path().get_ident())?;
match meta {
syn::Meta::Path(path) => parse_nested_path(attr_type, path, res)?,
syn::Meta::List(list) => parse_nested_list(attr_type, list, res)?,
syn::Meta::NameValue(name_value) => parse_nested_name_value(attr_type, name_value, res)?,
}
Ok(())
}
fn parse_nested_path(
attr_type: AttrType,
path: &syn::Path,
_res: &mut ParseResult,
) -> syn::Result<()> {
match attr_type {
AttrType::From
| AttrType::TryFrom
| AttrType::Into
| AttrType::TryInto
| AttrType::Add
| AttrType::AddAssign
| AttrType::PartialEq
| AttrType::Sub
| AttrType::SubAssign => Err(syn::Error::new_spanned(
path,
format!("expected `#[newtype({attr_type}(...))]`"),
)),
}
}
fn parse_nested_list(
attr_type: AttrType,
list: &syn::MetaList,
res: &mut ParseResult,
) -> syn::Result<()> {
match attr_type {
AttrType::From => parse_from(list, res),
AttrType::TryFrom => parse_try_from(list, res),
AttrType::Into => parse_into(list, res),
AttrType::TryInto => parse_try_into(list, res),
AttrType::Add => parse_add(list, res),
AttrType::AddAssign => parse_add_assign(list, res),
AttrType::PartialEq => parse_partial_eq(list, res),
AttrType::Sub => parse_sub(list, res),
AttrType::SubAssign => parse_sub_assign(list, res),
}
}
fn parse_nested_name_value(
attr_type: AttrType,
name_value: &syn::MetaNameValue,
_res: &mut ParseResult,
) -> syn::Result<()> {
Err(syn::Error::new_spanned(
name_value,
format!("expected `#[newtype({attr_type}(...))]`"),
))
}
fn parse_from(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
let (from_ty, with_expr) = list.parse_args_with(|input: syn::parse::ParseStream| {
let from_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((from_ty, with_expr))
})?;
res.from.push((from_ty, with_expr));
Ok(())
}
fn parse_try_from(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
let (try_from_ty, error_ty, with_expr) =
list.parse_args_with(|input: syn::parse::ParseStream| {
let try_from_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::error>()?;
input.parse::<syn::Token![=]>()?;
let error_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((try_from_ty, error_ty, with_expr))
})?;
res.try_from.push((try_from_ty, error_ty, with_expr));
Ok(())
}
fn parse_into(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
let (output_ty, with_expr) = list.parse_args_with(|input: syn::parse::ParseStream| {
let output_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((output_ty, with_expr))
})?;
res.into.push((output_ty, with_expr));
Ok(())
}
fn parse_try_into(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
let (output_ty, error_ty, with_expr) =
list.parse_args_with(|input: syn::parse::ParseStream| {
let output_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::error>()?;
input.parse::<syn::Token![=]>()?;
let error_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((output_ty, error_ty, with_expr))
})?;
res.try_into.push((output_ty, error_ty, with_expr));
Ok(())
}
fn parse_add(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
res.add.push(parse_type_output_with(list)?);
Ok(())
}
fn parse_add_assign(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
res.add_assign.push(parse_type_with(list)?);
Ok(())
}
fn parse_partial_eq(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
let (other_ty, with_expr) = list.parse_args_with(|input: syn::parse::ParseStream| {
let other_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((other_ty, with_expr))
})?;
res.partial_eq.push((other_ty, with_expr));
Ok(())
}
fn parse_sub(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
res.sub.push(parse_type_output_with(list)?);
Ok(())
}
fn parse_sub_assign(list: &syn::MetaList, res: &mut ParseResult) -> syn::Result<()> {
res.sub_assign.push(parse_type_with(list)?);
Ok(())
}
fn parse_type_output_with(list: &syn::MetaList) -> syn::Result<(syn::Type, syn::Type, syn::Expr)> {
list.parse_args_with(|input: syn::parse::ParseStream| {
let rhs_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::output>()?;
input.parse::<syn::Token![=]>()?;
let output_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((rhs_ty, output_ty, with_expr))
})
}
fn parse_type_with(list: &syn::MetaList) -> syn::Result<(syn::Type, syn::Expr)> {
list.parse_args_with(|input: syn::parse::ParseStream| {
let rhs_ty = parse_lit_or::<syn::Type>(&input)?;
input.parse::<syn::Token![,]>()?;
input.parse::<kw::with>()?;
input.parse::<syn::Token![=]>()?;
let with_expr = parse_lit_or::<syn::Expr>(&input)?;
Ok((rhs_ty, with_expr))
})
}
#[derive(Debug, PartialEq)]
enum AttrType {
From,
TryFrom,
Into,
TryInto,
Add,
AddAssign,
PartialEq,
Sub,
SubAssign,
}
impl std::fmt::Display for AttrType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::From => f.write_str("from"),
Self::TryFrom => f.write_str("try_from"),
Self::Into => f.write_str("into"),
Self::TryInto => f.write_str("try_into"),
Self::Add => f.write_str("add"),
Self::AddAssign => f.write_str("add_assign"),
Self::PartialEq => f.write_str("partial_eq"),
Self::Sub => f.write_str("sub"),
Self::SubAssign => f.write_str("sub_assign"),
}
}
}
impl TryFrom<Option<&syn::Ident>> for AttrType {
type Error = syn::Error;
fn try_from(value: Option<&syn::Ident>) -> Result<Self, Self::Error> {
match value {
Some(i) if i == "from" => Ok(Self::From),
Some(i) if i == "try_from" => Ok(Self::TryFrom),
Some(i) if i == "into" => Ok(Self::Into),
Some(i) if i == "try_into" => Ok(Self::TryInto),
Some(i) if i == "add" => Ok(Self::Add),
Some(i) if i == "add_assign" => Ok(Self::AddAssign),
Some(i) if i == "partial_eq" => Ok(Self::PartialEq),
Some(i) if i == "sub" => Ok(Self::Sub),
Some(i) if i == "sub_assign" => Ok(Self::SubAssign),
_ => Err(syn::Error::new_spanned(
value,
"expected `(try_)from`, `(try_)into`, `add(_assign)`, `partial_eq`, \
`sub(_assign)`, `iter`",
)),
}
}
}
fn parse_lit_or<T>(input: &syn::parse::ParseStream) -> syn::Result<T>
where
T: syn::parse::Parse,
{
let fork = input.fork();
if let Ok(lit_str) = fork.parse::<syn::LitStr>() {
use syn::parse::discouraged::Speculative;
input.advance_to(&fork);
return lit_str.parse::<T>();
}
input.parse::<T>()
}