mod codegen;
use crate::meta_types::IdentTypeMaybeDefault;
use codegen::{Builder, BuilderField, BuilderFieldKind};
use proc_macro2::{Span, TokenStream};
use quote::format_ident;
use syn::{
parse::{Parse, ParseStream},
spanned::Spanned,
DeriveInput, Expr, Ident, Token, Visibility,
};
pub(crate) fn arg_type_name(ty_name: &Ident, is_write: bool) -> Ident {
if is_write {
format_ident!("{}BinWriteArgs", ty_name, span = Span::mixed_site())
} else {
format_ident!("{}BinReadArgs", ty_name, span = Span::mixed_site())
}
}
pub(crate) fn derive_from_imports(
ty_name: &Ident,
is_write: bool,
result_name: &Ident,
vis: &Visibility,
lifetime: Option<syn::Lifetime>,
args: impl Iterator<Item = IdentTypeMaybeDefault>,
) -> TokenStream {
let builder_name = &if is_write {
format_ident!("{}BinWriteArgBuilder", ty_name, span = Span::mixed_site())
} else {
format_ident!("{}BinReadArgBuilder", ty_name, span = Span::mixed_site())
};
Builder {
owner_name: Some(ty_name),
is_write,
builder_name,
result_name,
fields: &args.map(Into::into).collect::<Vec<_>>(),
generics: lifetime
.map(|lifetime| {
[syn::GenericParam::Lifetime(syn::LifetimeParam::new(
lifetime,
))]
})
.as_ref()
.map_or(&[], |generics| generics.as_slice()),
vis,
}
.generate(true)
}
#[cfg_attr(coverage_nightly, coverage(off))]
pub(crate) fn derive_from_input(input: DeriveInput) -> TokenStream {
from_input(input).unwrap_or_else(syn::Error::into_compile_error)
}
fn from_input(input: DeriveInput) -> syn::Result<TokenStream> {
if let syn::Data::Struct(s) = input.data {
let mut has_try_optional = false;
let fields = s
.fields
.into_iter()
.map(|field| {
let attrs = field.attrs.iter().filter_map(|attr| {
attr.path()
.get_ident()
.filter(|ident| *ident == "named_args")
.map(|_| attr.parse_args::<NamedArgAttr>())
});
let mut kind = BuilderFieldKind::Required;
for attr in attrs {
match attr? {
NamedArgAttr::Default(default) => {
kind = BuilderFieldKind::Optional { default }
}
NamedArgAttr::TryOptional(span) => {
if has_try_optional {
return Err(syn::Error::new(
span,
"cannot have more than one `try_optional` per struct",
));
}
has_try_optional = true;
kind = BuilderFieldKind::TryOptional;
break;
}
}
}
Ok(BuilderField {
kind,
name: match field.ident {
Some(ident) => ident,
None => {
return Err(syn::Error::new(
field.span(),
"tuple structs are not supported",
))
}
},
ty: field.ty.clone(),
})
})
.collect::<Result<Vec<_>, syn::Error>>()?;
Ok(Builder {
owner_name: None,
is_write: false,
result_name: &input.ident,
builder_name: "e::format_ident!("{}Builder", input.ident),
fields: &fields,
generics: &input.generics.params.iter().cloned().collect::<Vec<_>>(),
vis: &input.vis,
}
.generate(false))
} else {
Err(syn::Error::new(input.span(), "only structs are supported"))
}
}
enum NamedArgAttr {
Default(Box<Expr>),
TryOptional(Span),
}
impl Parse for NamedArgAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::try_optional) {
Ok(NamedArgAttr::TryOptional(
input.parse::<kw::try_optional>()?.span(),
))
} else if lookahead.peek(kw::default) {
input.parse::<kw::default>()?;
input.parse::<Token![=]>()?;
Ok(NamedArgAttr::Default(Box::new(input.parse()?)))
} else {
Err(lookahead.error())
}
}
}
mod kw {
syn::custom_keyword!(default);
syn::custom_keyword!(try_optional);
}
#[cfg(coverage)]
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn derive_named_args_code_coverage_for_tool() {
use runtime_macros::emulate_derive_macro_expansion;
let file = std::fs::File::open("../binrw/tests/named_args.rs").unwrap();
emulate_derive_macro_expansion(
file,
&[("NamedArgs", |input| {
derive_from_input(syn::parse2::<syn::DeriveInput>(input).unwrap())
})],
)
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg_attr(coverage_nightly, coverage(off))]
fn try_input(input: TokenStream) {
from_input(syn::parse2::<DeriveInput>(input).unwrap()).unwrap();
}
macro_rules! try_error (
($name:ident: $message:literal $tt:tt) => {
#[test]
#[cfg_attr(coverage_nightly, coverage(off))]
#[should_panic(expected = $message)]
fn $name() {
try_input(quote::quote! $tt);
}
};
);
try_error!(invalid_attr_name: "expected `try_optional` or `default`" {
struct Foo<A> {
#[named_args(invalid)]
a: A,
}
});
try_error!(invalid_attr_syntax: "unexpected token" {
struct Foo<A> {
#[named_args(try_optional, invalid)]
a: A,
}
});
try_error!(invalid_enum: "only structs" {
enum Foo {}
});
try_error!(invalid_tuple: "tuple structs are not supported" {
struct Foo<A>(A);
});
try_error!(invalid_union: "only structs" {
union Foo {}
});
try_error!(missing_default_eq_value: "expected `=`" {
struct Foo<A> {
#[named_args(default)]
a: A,
}
});
try_error!(missing_default_value: "unexpected end of input, expected an expression" {
struct Foo<A> {
#[named_args(default = )]
a: A,
}
});
try_error!(multiple_try_optional: "more than one `try_optional`" {
struct Foo<A, B> {
#[named_args(try_optional)]
a: A,
#[named_args(try_optional)]
b: B,
}
});
}