#[macro_use]
pub(crate) mod macros;
mod attrs;
mod field_level_attrs;
mod keywords;
pub(crate) mod meta_types;
mod top_level_attrs;
mod types;
pub(crate) use field_level_attrs::*;
use meta_types::MetaAttrList;
use proc_macro2::Span;
use syn::{spanned::Spanned, token::Token};
pub(crate) use top_level_attrs::*;
pub(crate) use types::*;
fn combine_error(all_errors: &mut Option<syn::Error>, new_error: syn::Error) {
if let Some(all_errors) = all_errors {
all_errors.combine(new_error);
} else {
*all_errors = Some(new_error);
}
}
pub(crate) fn is_binread_attr(attr: &syn::Attribute) -> bool {
attr.path.is_ident("br") || attr.path.is_ident("binread")
}
pub(crate) trait FromAttrs<Attr: syn::parse::Parse> {
fn try_from_attrs(attrs: &[syn::Attribute]) -> ParseResult<Self>
where
Self: Default + Sized,
{
Self::set_from_attrs(Self::default(), attrs)
}
fn set_from_attrs(mut self, attrs: &[syn::Attribute]) -> ParseResult<Self>
where
Self: Sized,
{
#[allow(clippy::filter_map)]
let attrs = attrs
.iter()
.filter(|attr| is_binread_attr(attr))
.flat_map(
|attr| match syn::parse2::<MetaAttrList<Attr>>(attr.tokens.clone()) {
Ok(list) => either::Either::Right(list.into_iter().map(Ok)),
Err(err) => either::Either::Left(core::iter::once(Err(err))),
},
);
let mut all_errors = None::<syn::Error>;
for attr in attrs {
let result = match attr {
Ok(attr) => self.try_set_attr(attr),
Err(e) => Err(e),
};
if let Err(parse_error) = result {
combine_error(&mut all_errors, parse_error);
}
}
#[allow(clippy::option_if_let_else)]
if let Some(error) = all_errors {
ParseResult::Partial(self, error)
} else {
ParseResult::Ok(self)
}
}
fn try_set_attr(&mut self, attr: Attr) -> syn::Result<()>;
}
pub(crate) trait FromField {
type In;
fn from_field(field: &Self::In, index: usize) -> ParseResult<Self>
where
Self: Sized;
}
pub(crate) trait FromInput<Attr: syn::parse::Parse>: FromAttrs<Attr> {
type Field: FromField + 'static;
fn from_input<'input>(
attrs: &'input [syn::Attribute],
fields: impl Iterator<Item = &'input <Self::Field as FromField>::In>,
) -> ParseResult<Self>
where
Self: Sized + Default,
{
let (mut this, mut all_errors) = Self::try_from_attrs(attrs).unwrap_tuple();
for (index, field) in fields.enumerate() {
let (field, mut field_error) = Self::Field::from_field(field, index).unwrap_tuple();
if field_error.is_none() {
field_error = this.push_field(field).err();
}
if let Some(field_error) = field_error {
combine_error(&mut all_errors, field_error);
}
}
if let Err(validation_error) = this.validate() {
combine_error(&mut all_errors, validation_error);
}
#[allow(clippy::option_if_let_else)]
if let Some(error) = all_errors {
ParseResult::Partial(this, error)
} else {
ParseResult::Ok(this)
}
}
fn push_field(&mut self, field: Self::Field) -> syn::Result<()>;
fn validate(&self) -> syn::Result<()> {
Ok(())
}
}
pub(crate) trait KeywordToken {
type Token: Token;
fn display() -> &'static str {
<Self::Token as Token>::display()
}
fn dyn_display(&self) -> &'static str {
Self::display()
}
fn keyword_span(&self) -> Span;
}
impl<T: Token + Spanned> KeywordToken for T {
type Token = T;
fn keyword_span(&self) -> Span {
self.span()
}
}
pub(crate) enum PartialResult<T, E> {
Ok(T),
Partial(T, E),
Err(E),
}
impl<T, E> PartialResult<T, E> {
#[cfg(test)]
pub(crate) fn err(self) -> Option<E> {
match self {
PartialResult::Ok(_) => None,
PartialResult::Partial(_, error) | PartialResult::Err(error) => Some(error),
}
}
pub(crate) fn map<F, U>(self, f: F) -> PartialResult<U, E>
where
F: FnOnce(T) -> U,
{
match self {
PartialResult::Ok(value) => PartialResult::Ok(f(value)),
PartialResult::Partial(value, error) => PartialResult::Partial(f(value), error),
PartialResult::Err(error) => PartialResult::Err(error),
}
}
pub(crate) fn ok(self) -> Option<T> {
match self {
PartialResult::Ok(value) | PartialResult::Partial(value, _) => Some(value),
PartialResult::Err(_) => None,
}
}
}
impl<T, E: core::fmt::Debug> PartialResult<T, E> {
#[cfg(test)]
pub(crate) fn unwrap(self) -> T {
match self {
PartialResult::Ok(value) => value,
PartialResult::Partial(_, error) => panic!(
"called `PartialResult::unwrap() on a `Partial` value: {:?}",
&error
),
PartialResult::Err(error) => panic!(
"called `PartialResult::unwrap() on an `Err` value: {:?}",
&error
),
}
}
pub(crate) fn unwrap_tuple(self) -> (T, Option<E>) {
match self {
PartialResult::Ok(value) => (value, None),
PartialResult::Partial(value, error) => (value, Some(error)),
PartialResult::Err(error) => panic!(
"called `PartialResult::unwrap_tuple() on an `Err` value: {:?}",
&error
),
}
}
}
pub(crate) type ParseResult<T> = PartialResult<T, syn::Error>;
pub(crate) trait TrySet<T> {
fn try_set(self, to: &mut T) -> syn::Result<()>;
}
impl<T: KeywordToken> TrySet<bool> for T {
fn try_set(self, to: &mut bool) -> syn::Result<()> {
if *to {
Err(syn::Error::new(
self.keyword_span(),
format!("conflicting {} keyword", self.dyn_display()),
))
} else {
*to = true;
Ok(())
}
}
}
pub(crate) enum TrySetError {
Infallible,
Syn(syn::Error),
}
impl From<core::convert::Infallible> for TrySetError {
fn from(_: core::convert::Infallible) -> Self {
Self::Infallible
}
}
impl From<syn::Error> for TrySetError {
fn from(error: syn::Error) -> Self {
Self::Syn(error)
}
}
impl<T: core::convert::TryInto<To, Error = E> + KeywordToken, E: Into<TrySetError>, To>
TrySet<Option<To>> for T
{
fn try_set(self, to: &mut Option<To>) -> syn::Result<()> {
if to.is_none() {
*to = Some(self.try_into().map_err(|error| match error.into() {
TrySetError::Infallible => unreachable!(),
TrySetError::Syn(error) => error,
})?);
Ok(())
} else {
Err(syn::Error::new(
self.keyword_span(),
format!("conflicting {} keyword", self.dyn_display()),
))
}
}
}
impl<T: core::convert::TryInto<To, Error = syn::Error> + KeywordToken, To> TrySet<Vec<To>> for T {
fn try_set(self, to: &mut Vec<To>) -> syn::Result<()> {
to.push(self.try_into()?);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::TokenStream;
use syn::DeriveInput;
fn try_input(input: TokenStream) -> ParseResult<Input> {
Input::from_input(&syn::parse2::<DeriveInput>(input).unwrap())
}
macro_rules! try_error (
($name:ident: $message:literal $tt:tt) => {
#[test]
#[should_panic(expected = $message)]
fn $name() {
try_input(quote::quote! $tt).unwrap();
}
};
($name:ident $tt:tt) => {
#[test]
#[should_panic]
fn $name() {
try_input(quote::quote! $tt).unwrap();
}
};
);
try_error!(conflicting_keyword_bool: "conflicting `restore_position` keyword" {
struct Foo {
#[br(restore_position, restore_position)]
a: i32,
}
});
try_error!(conflicting_keyword_cond_endian: "conflicting endianness keyword" {
struct Foo {
#[br(big, little, is_big = true, is_little = true)]
a: i32,
}
});
try_error!(conflicting_keyword_enum_error_mode: "conflicting error handling keyword" {
#[br(return_all_errors, return_unexpected_error)]
enum Foo {
A(i32),
}
});
try_error!(conflicting_keyword_imports: "conflicting import keyword" {
#[br(import(a: i32), import_tuple(args: (i32, )))]
struct Foo;
});
try_error!(conflicting_keyword_map: "conflicting map keyword" {
struct Foo {
#[br(map = |_| 0, try_map = |_| Ok(0))]
a: i32,
}
});
try_error!(conflicting_keyword_option: "conflicting `magic` keyword" {
#[br(magic = 0u8, magic = 0u8)]
struct Foo;
});
try_error!(conflicting_keyword_passed_args: "conflicting args keyword" {
struct Foo {
a: i32,
#[br(args(a), args_tuple = (a, ))]
b: i32,
}
});
try_error!(conflicting_keyword_read_mode: "conflicting read mode keyword" {
struct Foo {
#[br(calc(1), default, ignore, parse_with = u8)]
a: i32,
}
});
try_error!(enum_missing_magic_repr {
enum UnitEnum {
A,
}
});
try_error!(invalid_assert_args: "too many arguments" {
#[br(assert(false, String::from("message"), "too", "many", "arguments"))]
struct Foo;
});
try_error!(invalid_assert_empty: "requires a boolean expression" {
#[br(assert())]
struct Foo;
});
try_error!(invalid_if_args: "too many arguments" {
struct Foo {
#[br(if(false, 0, 1, 2, 3))]
a: u8,
}
});
try_error!(invalid_if_empty: "requires a boolean expression" {
struct Foo {
#[br(if())]
a: u8,
}
});
try_error!(invalid_keyword_enum_variant: "expected one of" {
enum Enum {
#[br(invalid_enum_variant_keyword)]
A(i32),
}
});
try_error!(invalid_keyword_enum: "expected one of" {
#[br(invalid_enum_keyword)]
enum Enum {
A(i32),
}
});
try_error!(invalid_keyword_struct_field: "expected one of" {
struct Struct {
#[br(invalid_struct_field_keyword)]
field: i32,
}
});
try_error!(invalid_keyword_struct: "expected one of" {
#[br(invalid_struct_keyword)]
struct Struct {
field: i32,
}
});
try_error!(invalid_keyword_unit_enum_field: "expected one of" {
#[br(repr = u8)]
enum UnitEnum {
#[br(invalid_unit_enum_field_keyword)]
A,
}
});
try_error!(invalid_keyword_unit_enum: "expected one of" {
#[br(invalid_unit_enum_keyword)]
enum UnitEnum {
#[br(magic = 0u8)]
A,
}
});
try_error!(invalid_magic_type: "expected byte string, byte, char, float, or int" {
#[br(magic = "invalid_type")]
struct Foo;
});
try_error!(magic_conflict: "conflicting magic types" {
enum Foo {
#[br(magic = 0u8)] A,
#[br(magic = 1i16)] B,
}
});
#[test]
fn non_blocking_errors() {
let error = try_input(quote::quote! {
#[br(invalid_keyword_struct)]
struct Foo {
#[br(invalid_keyword_struct_field_a)]
a: i32,
#[br(invalid_keyword_struct_field_b)]
b: i32,
}
})
.err()
.unwrap();
assert_eq!(error.into_iter().count(), 3);
}
try_error!(repr_magic_conflict: "mutually exclusive" {
#[br(repr = u8)]
enum Foo {
#[br(magic = 0u8)] A,
}
});
try_error!(deref_now_offset_after_conflict: "mutually exclusive" {
struct Foo {
#[br(deref_now, offset_after(1))]
a: u8,
}
});
try_error!(unsupported_type_enum: "null enums are not supported" {
enum Foo {}
});
try_error!(unsupported_type_union: "unions are not supported" {
union Bar {
a: i32,
}
});
}