From 781b62e041d39594662af6bafca17f3cad6eb1e4 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Sun, 18 Sep 2022 17:03:59 -0500 Subject: [PATCH 1/2] Implement `with` directive Closes jam1garner/binrw#98. --- binrw/doc/attribute.md | 22 +- binrw/doc/index.md | 13 +- binrw/src/binread/mod.rs | 265 +++++++++++++++ binrw/src/binwrite/mod.rs | 295 ++++++++++++++++ binrw/src/file_ptr.rs | 47 ++- binrw/src/lib.rs | 4 +- binrw/src/meta.rs | 2 +- binrw/src/strings.rs | 314 ++++++++---------- binrw/src/with.rs | 133 ++++++++ binrw/tests/derive/struct.rs | 18 +- binrw/tests/strings.rs | 310 +++++++++++------ .../ui/invalid_keyword_struct_field.stderr | 2 +- binrw/tests/ui/non_blocking_errors.stderr | 4 +- binrw/tests/with.rs | 19 ++ .../src/binrw/codegen/read_options/struct.rs | 21 +- .../src/binrw/codegen/sanitization.rs | 2 + .../codegen/write_options/struct_field.rs | 33 +- binrw_derive/src/binrw/parser/attrs.rs | 1 + .../src/binrw/parser/field_level_attrs.rs | 2 +- binrw_derive/src/binrw/parser/keywords.rs | 1 + .../src/binrw/parser/types/field_mode.rs | 7 + 21 files changed, 1183 insertions(+), 332 deletions(-) create mode 100644 binrw/src/with.rs create mode 100644 binrw/tests/with.rs diff --git a/binrw/doc/attribute.md b/binrw/doc/attribute.md index 46b77c4e..98ad19c0 100644 --- a/binrw/doc/attribute.md +++ b/binrw/doc/attribute.md @@ -1205,11 +1205,11 @@ assert_eq!(output.into_inner(), b"\0\0\0\x01\0\x02\0\x03"); ### Using `FilePtr::parse` to read a `NullString` without storing a `FilePtr` ``` -# use binrw::{prelude::*, io::Cursor, FilePtr32, NullString}; +# use binrw::{prelude::*, io::Cursor, FilePtr32, NullString, ReadFrom}; #[derive(BinRead)] struct MyType { - #[br(parse_with = FilePtr32::parse)] - some_string: NullString, + #[br(parse_with = FilePtr32::parse_with(<_ as ReadFrom>::read_from))] + some_string: String, } # let val: MyType = Cursor::new(b"\0\0\0\x04Test\0").read_be().unwrap(); @@ -1933,8 +1933,8 @@ referenced by the expressions in any of these directives. # use binrw::{prelude::*, NullString, io::SeekFrom}; #[derive(BinRead)] struct MyType { - #[br(align_before = 4, pad_after = 1, align_after = 4)] - str: NullString, + #[br(align_before = 4, pad_after = 1, align_after = 4, with(NullString))] + str: String, #[br(pad_size_to = 0x10)] test: u64, @@ -1950,8 +1950,8 @@ struct MyType { # use binrw::{prelude::*, NullString, io::SeekFrom}; #[derive(BinWrite)] struct MyType { - #[bw(align_before = 4, pad_after = 1, align_after = 4)] - str: NullString, + #[bw(align_before = 4, pad_after = 1, align_after = 4, with(NullString))] + str: String, #[bw(pad_size_to = 0x10)] test: u64, @@ -2000,12 +2000,12 @@ this to happen. ## Examples ``` -# use binrw::{prelude::*, FilePtr32, NullString, io::Cursor}; +# use binrw::{prelude::*, FilePtr32, FilePtrWith, NullString, io::Cursor}; #[derive(BinRead, Debug)] #[br(big, magic = b"TEST")] struct TestFile { - #[br(deref_now)] - ptr: FilePtr32, + #[br(deref_now, with(FilePtrWith))] + ptr: FilePtr32, value: i32, @@ -2018,7 +2018,7 @@ struct TestFile { # let test = Cursor::new(test_contents).read_be::().unwrap(); # assert_eq!(test.ptr_len, 11); # assert_eq!(test.value, -1); -# assert_eq!(test.ptr.to_string(), "Test string"); +# assert_eq!(*test.ptr, "Test string"); ``` diff --git a/binrw/doc/index.md b/binrw/doc/index.md index 5b00a379..469a289f 100644 --- a/binrw/doc/index.md +++ b/binrw/doc/index.md @@ -159,14 +159,17 @@ struct Dog { #[br(count = bone_pile_count)] bone_piles: Vec, - #[br(align_before = 0xA)] - name: NullString + #[brw(align_before = 0xA, with(NullString))] + name: String } -let mut data = Cursor::new(b"DOG\x02\x00\x01\x00\x12\0\0Rudy\0"); -let dog = Dog::read(&mut data).unwrap(); +let data = b"DOG\x02\x00\x01\x00\x12\0\0Rudy\0"; +let dog = Dog::read(&mut Cursor::new(data)).unwrap(); assert_eq!(dog.bone_piles, &[0x1, 0x12]); -assert_eq!(dog.name.to_string(), "Rudy") +assert_eq!(dog.name.to_string(), "Rudy"); +let mut out = Cursor::new(Vec::new()); +dog.write(&mut out).unwrap(); +assert_eq!(out.into_inner(), data); ``` Directives can also reference earlier fields by name. For tuple types, diff --git a/binrw/src/binread/mod.rs b/binrw/src/binread/mod.rs index 704ff10d..2f774c2c 100644 --- a/binrw/src/binread/mod.rs +++ b/binrw/src/binread/mod.rs @@ -178,6 +178,155 @@ pub trait BinRead: Sized + 'static { } } +/// Extension methods for reading [`BinRead`] objects using a converter. +pub trait ReadWith { + /// Read `Self` from the reader using the given converter. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + T: ReadEndian, + >::Args: Required, + { + Self::read_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + >::Args: Required, + { + Self::read_be_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + >::Args: Required, + { + Self::read_le_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_with(reader: &mut R) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + >::Args: Required, + { + Self::read_ne_args_with::(reader, >::Args::args()) + } + + /// Read `Self` from the reader, using the given converter and arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + T: ReadEndian, + { + Self::read_from(reader, Endian::Little, args) + } + + /// Read `Self` from the reader, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + { + Self::read_from(reader, Endian::Big, args) + } + + /// Read `Self` from the reader, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + { + Self::read_from(reader, Endian::Little, args) + } + + /// Read `Self` from the reader, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_args_with(reader: &mut R, args: >::Args) -> BinResult + where + Self: ReadFrom + Sized, + R: Read + Seek, + { + Self::read_from(reader, Endian::NATIVE, args) + } +} + +impl ReadWith for T {} + +/// The `ReadFrom` trait enables transparent deserialisation into a +/// non-[`BinRead`] type. +pub trait ReadFrom: Sized { + /// The type used for the `args` parameter of [`read_from()`]. + /// + /// [`read_from()`]: Self::read_from + type Args: Clone; + + /// Read `T` from the reader using the given arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + fn read_from( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult; +} + /// Extension methods for reading [`BinRead`] objects directly from a reader. /// /// # Examples @@ -289,6 +438,122 @@ pub trait BinReaderExt: Read + Seek + Sized { fn read_ne_args(&mut self, args: T::Args) -> BinResult { self.read_type_args(Endian::NATIVE, args) } + + /// Read `T` from the reader using the given converter. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_with(&mut self) -> BinResult + where + T: ReadFrom, + C: ReadEndian, + >::Args: Required, + { + self.read_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_with(&mut self) -> BinResult + where + T: ReadFrom, + >::Args: Required, + { + self.read_be_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_with(&mut self) -> BinResult + where + T: ReadFrom, + >::Args: Required, + { + self.read_le_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_with(&mut self) -> BinResult + where + T: ReadFrom, + >::Args: Required, + { + self.read_ne_args_with::(>::Args::args()) + } + + /// Read `T` from the reader, using the given converter and arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + C: ReadEndian, + { + T::read_from(self, Endian::Little, args) + } + + /// Read `T` from the reader, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_be_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + { + T::read_from(self, Endian::Big, args) + } + + /// Read `T` from the reader, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_le_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + { + T::read_from(self, Endian::Little, args) + } + + /// Read `T` from the reader, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn read_ne_args_with(&mut self, args: >::Args) -> BinResult + where + T: ReadFrom, + { + T::read_from(self, Endian::NATIVE, args) + } } impl BinReaderExt for R {} diff --git a/binrw/src/binwrite/mod.rs b/binrw/src/binwrite/mod.rs index 61e06767..30645b65 100644 --- a/binrw/src/binwrite/mod.rs +++ b/binrw/src/binwrite/mod.rs @@ -4,6 +4,7 @@ use crate::{ io::{Seek, Write}, BinResult, Endian, __private::Required, + meta::WriteEndian, }; /// The `BinWrite` trait serialises objects and writes them to streams. @@ -135,6 +136,172 @@ pub trait BinWrite { ) -> BinResult<()>; } +/// Extension methods for writing [`BinWrite`] objects using a converter. +pub trait WriteWith { + /// Write `Self` to the writer using the given converter. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + T: WriteEndian, + >::Args: Required, + { + self.write_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + >::Args: Required, + { + self.write_be_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + >::Args: Required, + { + self.write_le_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_with(&self, writer: &mut W) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + >::Args: Required, + { + self.write_ne_args_with::(writer, >::Args::args()) + } + + /// Write `Self` to the writer, using the given converter and arguments. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + T: WriteEndian, + { + self.write_into(writer, Endian::Little, args) + } + + /// Write `Self` to the writer, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + { + self.write_into(writer, Endian::Big, args) + } + + /// Write `Self` to the writer, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + { + self.write_into(writer, Endian::Little, args) + } + + /// Write `Self` to the writer, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_args_with( + &self, + writer: &mut W, + args: >::Args, + ) -> BinResult<()> + where + Self: WriteInto, + W: Write + Seek, + { + self.write_into(writer, Endian::NATIVE, args) + } +} + +impl WriteWith for T {} + +/// The `WriteInto` trait enables transparent conversion from a non-[`BinWrite`] +/// type. +pub trait WriteInto { + /// The type used for the `args` parameter of [`write_into()`]. + /// + /// [`write_into()`]: Self::write_into + type Args: Clone; + + /// Write `Self` into the writer using the given arguments. + /// + /// # Errors + /// + /// If reading fails, an [`Error`](crate::Error) variant will be returned. + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()>; +} + /// Extension methods for writing [`BinWrite`] objects directly to a writer. /// /// # Examples @@ -245,6 +412,134 @@ pub trait BinWriterExt: Write + Seek + Sized { fn write_ne_args(&mut self, value: &T, args: T::Args) -> BinResult<()> { self.write_type_args(value, Endian::NATIVE, args) } + + /// Write `T` to the writer using the given converter. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_with(&mut self, value: &T) -> BinResult<()> + where + C: WriteEndian, + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter, assuming + /// big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_with(&mut self, value: &T) -> BinResult<()> + where + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_be_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter, assuming + /// little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_with(&mut self, value: &T) -> BinResult<()> + where + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_le_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter, assuming + /// native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_with(&mut self, value: &T) -> BinResult<()> + where + T: WriteInto + ?Sized, + >::Args: Required, + { + self.write_ne_args_with::(value, >::Args::args()) + } + + /// Write `T` to the writer, using the given converter and arguments. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_args_with(&mut self, value: &T, args: >::Args) -> BinResult<()> + where + C: WriteEndian, + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::Little, args) + } + + /// Write `T` to the writer, using the given converter and arguments, + /// assuming big-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_be_args_with( + &mut self, + value: &T, + args: >::Args, + ) -> BinResult<()> + where + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::Big, args) + } + + /// Write `T` to the writer, using the given converter and arguments, + /// assuming little-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_le_args_with( + &mut self, + value: &T, + args: >::Args, + ) -> BinResult<()> + where + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::Little, args) + } + + /// Write `T` to the writer, using the given converter and arguments, + /// assuming native-endian byte order. + /// + /// # Errors + /// + /// If writing fails, an [`Error`](crate::Error) variant will be returned. + #[inline] + fn write_ne_args_with( + &mut self, + value: &T, + args: >::Args, + ) -> BinResult<()> + where + T: WriteInto + ?Sized, + { + value.write_into(self, Endian::NATIVE, args) + } } impl BinWriterExt for W {} diff --git a/binrw/src/file_ptr.rs b/binrw/src/file_ptr.rs index 40e2af17..171585ea 100644 --- a/binrw/src/file_ptr.rs +++ b/binrw/src/file_ptr.rs @@ -1,17 +1,18 @@ //! Type definitions for wrappers which represent a layer of indirection within //! a file. -use crate::NamedArgs; use crate::{ io::{Read, Seek, SeekFrom}, - BinRead, BinResult, Endian, + BinRead, BinResult, Endian, NamedArgs, ReadFrom, }; -use core::fmt; -use core::num::{ - NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, - NonZeroU32, NonZeroU64, NonZeroU8, +use core::{ + fmt, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16, + NonZeroU32, NonZeroU64, NonZeroU8, + }, + ops::{Deref, DerefMut}, }; -use core::ops::{Deref, DerefMut}; /// A type alias for [`FilePtr`] with 8-bit offsets. pub type FilePtr8 = FilePtr; @@ -35,6 +36,34 @@ pub type NonZeroFilePtr64 = FilePtr; /// A type alias for [`FilePtr`] with non-zero 128-bit offsets. pub type NonZeroFilePtr128 = FilePtr; +/// A converter for non-[`BinRead`] [`FilePtr`] values. +pub enum FilePtrWith { + #[doc(hidden)] + _Phantom(Private), +} +#[doc(hidden)] +pub struct Private(core::marker::PhantomData); + +impl + IntoSeekFrom, C, T: ReadFrom> ReadFrom> + for FilePtr +{ + type Args = FilePtrArgs<>::Args>; + + fn read_from( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult { + Self::read_with_parser( + |reader, endian, args| >::read_from(reader, endian, args), + |_, _, _, _| Ok(()), + reader, + endian, + args, + ) + } +} + /// A wrapper type which represents a layer of indirection within a file. /// /// `FilePtr` is composed of two types. The pointer type `P` is the @@ -250,7 +279,7 @@ impl + IntoSeekFrom, Value> FilePtr { /// /// Will panic if `FilePtr` hasn’t been finalized by calling /// [`after_parse()`](Self::after_parse). -impl Deref for FilePtr { +impl Deref for FilePtr { type Target = Value; fn deref(&self) -> &Self::Target { @@ -266,7 +295,7 @@ impl Deref for FilePtr { /// # Panics /// Will panic if the `FilePtr` has not been read yet using /// [`BinRead::after_parse`](BinRead::after_parse) -impl DerefMut for FilePtr { +impl DerefMut for FilePtr { fn deref_mut(&mut self) -> &mut Value { match self.value.as_mut() { Some(x) => x, diff --git a/binrw/src/lib.rs b/binrw/src/lib.rs index 5b76b736..aaf1c108 100644 --- a/binrw/src/lib.rs +++ b/binrw/src/lib.rs @@ -35,6 +35,7 @@ pub mod pos_value; pub mod punctuated; #[doc(hidden)] pub mod strings; +mod with; #[cfg(all(doc, not(feature = "std")))] use alloc::vec::Vec; @@ -44,11 +45,12 @@ pub use { binwrite::*, endian::Endian, error::Error, - file_ptr::{FilePtr, FilePtr128, FilePtr16, FilePtr32, FilePtr64, FilePtr8}, + file_ptr::{FilePtr, FilePtr128, FilePtr16, FilePtr32, FilePtr64, FilePtr8, FilePtrWith}, helpers::{count, until, until_eof, until_exclusive}, named_args::*, pos_value::PosValue, strings::{NullString, NullWideString}, + with::With, }; /// Derive macro generating an impl of the trait [`BinRead`]. diff --git a/binrw/src/meta.rs b/binrw/src/meta.rs index ba4a001a..9b8fcc75 100644 --- a/binrw/src/meta.rs +++ b/binrw/src/meta.rs @@ -83,7 +83,7 @@ macro_rules! endian_impl { )+)+} } -endian_impl!(() i8 u8 core::num::NonZeroU8 core::num::NonZeroI8 crate::strings::NullString => EndianKind::None); +endian_impl!(() i8 u8 core::num::NonZeroU8 core::num::NonZeroI8 => EndianKind::None); impl ReadEndian for Box { const ENDIAN: EndianKind = ::ENDIAN; diff --git a/binrw/src/strings.rs b/binrw/src/strings.rs index 02e6232a..929e2b9c 100644 --- a/binrw/src/strings.rs +++ b/binrw/src/strings.rs @@ -1,14 +1,14 @@ //! Type definitions for string readers. use crate::{ - alloc::string::{FromUtf16Error, FromUtf8Error}, + helpers::until_exclusive, io::{Read, Seek, Write}, - BinRead, BinResult, BinWrite, Endian, + meta::{EndianKind, ReadEndian, WriteEndian}, + BinResult, BinWrite, Endian, ReadFrom, WriteInto, }; -use alloc::{string::String, vec, vec::Vec}; -use core::fmt::{self, Write as _}; +use alloc::{boxed::Box, string::String, vec::Vec}; -/// A null-terminated 8-bit string. +/// A converter for null-terminated 8-bit strings. /// /// The null terminator is consumed and not included in the value. /// @@ -18,112 +18,122 @@ use core::fmt::{self, Write as _}; /// let mut null_separated_strings = Cursor::new(b"null terminated strings? in my system's language?\0no thanks\0"); /// /// assert_eq!( -/// null_separated_strings.read_be::().unwrap().to_string(), +/// null_separated_strings.read_with::().unwrap(), /// "null terminated strings? in my system's language?" /// ); /// /// assert_eq!( -/// null_separated_strings.read_be::().unwrap().to_string(), -/// "no thanks" +/// null_separated_strings.read_with::>().unwrap(), +/// b"no thanks" /// ); /// ``` -#[derive(Clone, Eq, PartialEq, Default)] -pub struct NullString( - /// The raw byte string. - pub Vec, -); +pub enum NullString {} -impl BinRead for NullString { +impl ReadEndian for NullString { + const ENDIAN: EndianKind = EndianKind::None; +} + +impl ReadFrom for String { type Args = (); - fn read_options( + fn read_from( reader: &mut R, endian: Endian, - _: Self::Args, + args: Self::Args, ) -> BinResult { - let mut values = vec![]; - - loop { - let val = ::read_options(reader, endian, ())?; - if val == 0 { - return Ok(Self(values)); - } - values.push(val); - } + let pos = reader.stream_position()?; + as ReadFrom>::read_from(reader, endian, args).and_then(|vec| { + Self::from_utf8(vec).map_err(|err| binrw::Error::Custom { + pos, + err: Box::new(err) as _, + }) + }) } } -impl BinWrite for NullString { +impl ReadFrom for Vec { type Args = (); - fn write_options( - &self, - writer: &mut W, + fn read_from( + reader: &mut R, endian: Endian, args: Self::Args, - ) -> BinResult<()> { - self.0.write_options(writer, endian, args)?; - 0u8.write_options(writer, endian, args)?; - - Ok(()) + ) -> BinResult { + until_exclusive(|b| *b == 0)(reader, endian, args) } } -impl From<&str> for NullString { - fn from(s: &str) -> Self { - Self(s.as_bytes().to_vec()) - } +impl WriteEndian for NullString { + const ENDIAN: EndianKind = EndianKind::None; } -impl From for NullString { - fn from(s: String) -> Self { - Self(s.into_bytes()) - } -} +impl WriteInto for String { + type Args = (); -impl From for Vec { - fn from(s: NullString) -> Self { - s.0 + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_bytes(), writer, endian, args) } } -impl TryFrom for String { - type Error = FromUtf8Error; +impl WriteInto for str { + type Args = (); - fn try_from(value: NullString) -> Result { - String::from_utf8(value.0) + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_bytes(), writer, endian, args) } } -impl core::ops::Deref for NullString { - type Target = Vec; +impl WriteInto for Vec { + type Args = (); - fn deref(&self) -> &Self::Target { - &self.0 + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_slice(), writer, endian, args) } } -impl core::ops::DerefMut for NullString { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} +impl WriteInto for [u8] { + type Args = (); -impl fmt::Debug for NullString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "NullString(\"")?; - display_utf8(&self.0, f, str::escape_debug)?; - write!(f, "\")") + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + self.write_options(writer, endian, args)?; + 0_u8.write_options(writer, endian, args) } } -impl fmt::Display for NullString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - display_utf8(&self.0, f, str::chars) +impl WriteInto for [u8; N] { + type Args = (); + + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_slice(), writer, endian, args) } } -/// A null-terminated 16-bit string. +/// A converter for null-terminated 16-bit strings. /// /// The null terminator must also be 16-bits, and is consumed and not included /// in the value. @@ -139,147 +149,113 @@ impl fmt::Display for NullString { /// /// assert_eq!( /// // notice: read_le -/// wide_strings.read_le::().unwrap().to_string(), +/// wide_strings.read_le_with::().unwrap(), /// "wide strings" /// ); /// /// assert_eq!( /// // notice: read_be -/// are_endian_dependent.read_be::().unwrap().to_string(), +/// are_endian_dependent.read_be_with::().unwrap(), /// "are endian dependent" /// ); /// ``` -#[derive(Clone, Eq, PartialEq, Default)] -pub struct NullWideString( - /// The raw wide byte string. - pub Vec, -); +pub enum NullWideString {} -impl BinRead for NullWideString { +impl ReadFrom for Vec { type Args = (); - fn read_options( + fn read_from( reader: &mut R, endian: Endian, - _: Self::Args, + args: Self::Args, ) -> BinResult { - let mut values = vec![]; - - loop { - let val = ::read_options(reader, endian, ())?; - if val == 0 { - return Ok(Self(values)); - } - values.push(val); - } + until_exclusive(|b| *b == 0)(reader, endian, args) } } -impl BinWrite for NullWideString { +impl ReadFrom for String { type Args = (); - fn write_options( - &self, - writer: &mut W, + fn read_from( + reader: &mut R, endian: Endian, args: Self::Args, - ) -> BinResult<()> { - self.0.write_options(writer, endian, args)?; - 0u16.write_options(writer, endian, args)?; - - Ok(()) - } -} - -impl From for Vec { - fn from(s: NullWideString) -> Self { - s.0 + ) -> BinResult { + let pos = reader.stream_position()?; + as ReadFrom>::read_from(reader, endian, args).and_then(|vec| { + String::from_utf16(&vec).map_err(|err| binrw::Error::Custom { + pos, + err: Box::new(err) as _, + }) + }) } } -impl From<&str> for NullWideString { - fn from(s: &str) -> Self { - Self(s.encode_utf16().collect()) - } -} +impl WriteInto for String { + type Args = (); -impl From for NullWideString { - fn from(s: String) -> Self { - Self(s.encode_utf16().collect()) + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_str(), writer, endian, args) } } -impl TryFrom for String { - type Error = FromUtf16Error; +impl WriteInto for str { + type Args = (); - fn try_from(value: NullWideString) -> Result { - String::from_utf16(&value.0) + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + for c in self.encode_utf16() { + c.write_options(writer, endian, ())?; + } + 0_u16.write_options(writer, endian, args) } } -impl core::ops::Deref for NullWideString { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} +impl WriteInto for Vec { + type Args = (); -impl core::ops::DerefMut for NullWideString { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_slice(), writer, endian, args) } } -impl fmt::Display for NullWideString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - display_utf16(&self.0, f, core::iter::once) - } -} +impl WriteInto for [u16] { + type Args = (); -impl fmt::Debug for NullWideString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "NullWideString(\"")?; - display_utf16(&self.0, f, char::escape_debug)?; - write!(f, "\")") + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + self.write_options(writer, endian, args)?; + 0_u16.write_options(writer, endian, args) } } -fn display_utf16 O, O: Iterator>( - input: &[u16], - f: &mut fmt::Formatter<'_>, - t: Transformer, -) -> fmt::Result { - char::decode_utf16(input.iter().copied()) - .flat_map(|r| t(r.unwrap_or(char::REPLACEMENT_CHARACTER))) - .try_for_each(|c| f.write_char(c)) -} +impl WriteInto for [u16; N] { + type Args = (); -fn display_utf8<'a, Transformer: Fn(&'a str) -> O, O: Iterator + 'a>( - mut input: &'a [u8], - f: &mut fmt::Formatter<'_>, - t: Transformer, -) -> fmt::Result { - // Adapted from - loop { - match core::str::from_utf8(input) { - Ok(valid) => { - t(valid).try_for_each(|c| f.write_char(c))?; - break; - } - Err(error) => { - let (valid, after_valid) = input.split_at(error.valid_up_to()); - - t(core::str::from_utf8(valid).unwrap()).try_for_each(|c| f.write_char(c))?; - f.write_char(char::REPLACEMENT_CHARACTER)?; - - if let Some(invalid_sequence_length) = error.error_len() { - input = &after_valid[invalid_sequence_length..]; - } else { - break; - } - } - } + fn write_into( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + <_ as WriteInto>::write_into(self.as_slice(), writer, endian, args) } - Ok(()) } diff --git a/binrw/src/with.rs b/binrw/src/with.rs new file mode 100644 index 00000000..619f7e87 --- /dev/null +++ b/binrw/src/with.rs @@ -0,0 +1,133 @@ +//! Wrapper type for conversions. + +use super::{io, BinRead, BinResult, BinWrite, Endian, ReadFrom, WriteInto}; +use crate::meta::{EndianKind, ReadEndian, WriteEndian}; +use core::{cmp::Ordering, marker::PhantomData}; + +/// A wrapper for reading or writing types through a converter. +/// +/// The converter must implement [`ReadFrom`] for reads and [`WriteInto`] +/// for writes. +pub struct With(PhantomData, T); + +impl With { + /// Consumes this wrapper, returning the wrapped value. + pub fn into_inner(self) -> T { + self.1 + } +} + +impl From for With { + fn from(value: T) -> Self { + Self(PhantomData, value) + } +} + +impl Clone for With +where + T: Clone, +{ + fn clone(&self) -> Self { + Self(PhantomData, self.1.clone()) + } +} + +impl Copy for With where T: Copy {} + +impl Eq for With where T: Eq {} + +impl Ord for With +where + T: Ord, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.1.cmp(&other.1) + } +} + +impl PartialEq for With +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.1 == other.1 + } +} + +impl PartialOrd for With +where + T: PartialOrd, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.1.partial_cmp(&other.1) + } +} + +impl core::fmt::Debug for With +where + T: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("With").field(&self.1).finish() + } +} + +impl core::ops::Deref for With { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.1 + } +} + +impl core::ops::DerefMut for With { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.1 + } +} + +impl BinRead for With +where + C: 'static, + T: ReadFrom + 'static, +{ + type Args = >::Args; + + fn read_options( + reader: &mut R, + endian: Endian, + args: Self::Args, + ) -> BinResult { + >::read_from(reader, endian, args).map(Self::from) + } +} + +impl ReadEndian for With +where + C: ReadEndian, +{ + const ENDIAN: EndianKind = C::ENDIAN; +} + +impl BinWrite for With +where + T: WriteInto, +{ + type Args = >::Args; + + fn write_options( + &self, + writer: &mut W, + endian: Endian, + args: Self::Args, + ) -> BinResult<()> { + >::write_into(self, writer, endian, args) + } +} + +impl WriteEndian for With +where + C: WriteEndian, +{ + const ENDIAN: EndianKind = C::ENDIAN; +} diff --git a/binrw/tests/derive/struct.rs b/binrw/tests/derive/struct.rs index 11b0498e..ea9a48f8 100644 --- a/binrw/tests/derive/struct.rs +++ b/binrw/tests/derive/struct.rs @@ -1,7 +1,7 @@ use binrw::{ args, binread, io::{Cursor, Seek, SeekFrom}, - BinRead, BinResult, FilePtr, NullString, + BinRead, BinResult, FilePtr, FilePtrWith, NullString, }; #[test] @@ -49,7 +49,7 @@ fn all_the_things() { offsets: (u16, u16), #[br(if(offsets.0 == 0x20))] - name: Option>, + name: Option>>, #[br(calc(extra_val))] extra_val: u8, @@ -153,8 +153,8 @@ fn deref_now() { struct Test { // deref_now on the first field tests that the reader position is correctly // restored before reading the second field - #[br(deref_now)] - a: FilePtr, + #[br(deref_now, with(FilePtrWith))] + a: FilePtr, b: i32, } @@ -164,7 +164,7 @@ fn deref_now() { Test { a: FilePtr { ptr: 0x10, - value: Some(NullString(b"Test string".to_vec())) + value: Some("Test string".to_string()) }, b: -1, } @@ -177,17 +177,17 @@ fn move_temp_field() { #[binread] #[derive(Debug, Eq, PartialEq)] struct Foo { - #[br(temp, postprocess_now)] - foo: binrw::NullString, + #[br(temp, postprocess_now, with(binrw::NullString))] + foo: String, #[br(calc = foo)] - bar: binrw::NullString, + bar: String, } assert_eq!( Foo::read_le(&mut Cursor::new(b"hello\0goodbyte\0")).unwrap(), Foo { - bar: binrw::NullString::from("hello"), + bar: String::from("hello"), } ); } diff --git a/binrw/tests/strings.rs b/binrw/tests/strings.rs index 1835a085..fdd254ae 100644 --- a/binrw/tests/strings.rs +++ b/binrw/tests/strings.rs @@ -1,154 +1,254 @@ +use binrw::{io::Cursor, BinReaderExt, NullString, NullWideString, ReadWith}; + #[test] -fn null_wide_strings() { - use binrw::{io::Cursor, BinReaderExt, NullWideString}; +fn null_strings() { + use binrw::WriteWith; - assert_eq!( - Cursor::new(b"w\0i\0d\0e\0 \0s\0t\0r\0i\0n\0g\0s\0\0\0") - .read_le::() - .unwrap() - .to_string(), - "wide strings" - ); + let mut null_separated_strings = + Cursor::new(b"null terminated strings? in my system's language?\0no thanks\0"); assert_eq!( - Cursor::new(b"\0a\0r\0e\0 \0e\0n\0d\0i\0a\0n\0 \0d\0e\0p\0e\0n\0d\0e\0n\0t\0\0") - .read_be::() - .unwrap() - .to_string(), - "are endian dependent" + null_separated_strings + .read_with::() + .unwrap(), + "null terminated strings? in my system's language?" ); assert_eq!( - format!( - "{:?}", - Cursor::new(b"d\0e\0b\0u\0g\0\x3a\x26\n\0\0\0") - .read_le::() - .unwrap() - ), - "NullWideString(\"debug☺\\n\")" + null_separated_strings + .read_with::() + .unwrap(), + "no thanks" ); - assert_eq!( - format!( - "{:?}", - Cursor::new(b"b\0a\0d\0 \0\0\xdc\0\xdc \0s\0u\0r\0r\0o\0g\0a\0t\0e\0\0\0") - .read_le::() - .unwrap() - ), - "NullWideString(\"bad \u{FFFD}\u{FFFD} surrogate\")" - ); + Cursor::new(b"no terminator") + .read_with::>() + .unwrap_err(); - // Default/Deref/DerefMut - let mut s = NullWideString::default(); - s.extend_from_slice(&[b'h'.into(), b'e'.into(), b'y'.into()]); - assert_eq!(&s[0..2], &[b'h'.into(), b'e'.into()]); + Cursor::new(b"bad utf8\xc3\x28\0") + .read_with::() + .unwrap_err(); - // Clone/TryFrom - let t = String::try_from(s.clone()).unwrap(); - assert_eq!(t, "hey"); - s.push(0xdc00); - String::try_from(s).expect_err("accepted bad data"); + assert_eq!( + String::read_with::(&mut Cursor::new(b"test\0")).unwrap(), + "test" + ); - // From - let s = NullWideString::from(t.clone()); - assert_eq!(Vec::from(s), t.encode_utf16().collect::>()); + let s = String::from("test"); + let mut out = Cursor::new(Vec::new()); + s.write_with::(&mut out).unwrap(); + assert_eq!(out.into_inner(), b"test\0"); } #[test] -fn null_strings() { - use binrw::{io::Cursor, BinReaderExt, NullString}; - - let mut null_separated_strings = - Cursor::new(b"null terminated strings? in my system's language?\0no thanks\0"); +fn null_wide_strings() { + use binrw::WriteWith; assert_eq!( - null_separated_strings - .read_be::() - .unwrap() - .to_string(), - "null terminated strings? in my system's language?" + Cursor::new(b"w\0i\0d\0e\0 \0s\0t\0r\0i\0n\0g\0s\0\0\0") + .read_le_with::() + .unwrap(), + "wide strings" ); assert_eq!( - null_separated_strings - .read_be::() - .unwrap() - .to_string(), - "no thanks" + Cursor::new(b"\0a\0r\0e\0 \0e\0n\0d\0i\0a\0n\0 \0d\0e\0p\0e\0n\0d\0e\0n\0t\0\0") + .read_be_with::() + .unwrap(), + "are endian dependent" ); + Cursor::new(b"bad utf16\0\xd8\x3d\0\x27\0\0") + .read_be_with::() + .unwrap_err(); + + Cursor::new(b"\0n\0o\0t\0e\0r\0m") + .read_be_with::>() + .unwrap_err(); + assert_eq!( - format!( - "{:?}", - Cursor::new(b"debug\xe2\x98\xba\n\0") - .read_be::() - .unwrap() - ), - "NullString(\"debug☺\\n\")" + String::read_be_with::(&mut Cursor::new(b"\0t\0e\0s\0t\0\0")).unwrap(), + "test" ); - assert_eq!( - format!( - "{:?}", - Cursor::new(b"bad \xfe utf8 \xfe\0") - .read_be::() - .unwrap() - ), - "NullString(\"bad \u{FFFD} utf8 \u{FFFD}\")" + String::read_le_with::(&mut Cursor::new(b"t\0e\0s\0t\0\0\0")).unwrap(), + "test" ); - assert_eq!( - format!( - "{:?}", - Cursor::new(b"truncated\xe2\0") - .read_be::() - .unwrap() - ), - "NullString(\"truncated\u{FFFD}\")" + String::read_ne_with::(&mut Cursor::new( + if cfg!(target_endian = "big") { + b"\0t\0e\0s\0t\0\0" + } else { + b"t\0e\0s\0t\0\0\0" + } + )) + .unwrap(), + "test" + ); + assert_eq!( + Cursor::new(if cfg!(target_endian = "big") { + b"\0t\0e\0s\0t\0\0" + } else { + b"t\0e\0s\0t\0\0\0" + }) + .read_ne_with::() + .unwrap(), + "test" ); - // Default/Deref/DerefMut - let mut s = NullString::default(); - s.extend_from_slice(b"hey"); - assert_eq!(&s[0..2], b"he"); - - // Clone/TryFrom - let t = String::try_from(s.clone()).unwrap(); - assert_eq!(t, "hey"); - s.extend_from_slice(b"\xe2"); - String::try_from(s).expect_err("accepted bad data"); + let s = String::from("test"); + let mut out = Cursor::new(Vec::new()); + s.write_be_with::(&mut out).unwrap(); + assert_eq!(out.into_inner(), b"\0t\0e\0s\0t\0\0"); + let mut out = Cursor::new(Vec::new()); + s.write_le_with::(&mut out).unwrap(); + assert_eq!(out.into_inner(), b"t\0e\0s\0t\0\0\0"); + let mut out = Cursor::new(Vec::new()); + s.write_ne_with::(&mut out).unwrap(); + assert_eq!( + out.into_inner(), + if cfg!(target_endian = "big") { + b"\0t\0e\0s\0t\0\0" + } else { + b"t\0e\0s\0t\0\0\0" + } + ); +} - // From - let s = NullString::from(t.clone()); - assert_eq!(Vec::from(s), t.as_bytes()); +#[test] +fn bin_writer_ext() { + use binrw::BinWriterExt; + + let s = String::from("test"); + let mut out = Cursor::new(Vec::new()); + out.write_be_with::(&s).unwrap(); + assert_eq!(out.into_inner(), b"\0t\0e\0s\0t\0\0"); + let mut out = Cursor::new(Vec::new()); + out.write_le_with::(&s).unwrap(); + assert_eq!(out.into_inner(), b"t\0e\0s\0t\0\0\0"); + let mut out = Cursor::new(Vec::new()); + out.write_ne_with::(&s).unwrap(); + assert_eq!( + out.into_inner(), + if cfg!(target_endian = "big") { + b"\0t\0e\0s\0t\0\0" + } else { + b"t\0e\0s\0t\0\0\0" + } + ); } #[test] fn null_string_round_trip() { - use binrw::{io::Cursor, BinReaderExt, BinWriterExt, NullString}; + use binrw::BinWriterExt; - let data = "test test test"; - let s = NullString::from(data); + // str + let s = "test test test"; + let mut x = Cursor::new(Vec::new()); + x.write_with::(s).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_with::() + .unwrap(), + s + ); + // String let mut x = Cursor::new(Vec::new()); - x.write_be(&s).unwrap(); + x.write_with::(&s.to_string()).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_with::() + .unwrap(), + s + ); - let s2: NullString = Cursor::new(x.into_inner()).read_be().unwrap(); + // [u8; N] + let s = b"test test test"; + let mut x = Cursor::new(Vec::new()); + x.write_with::(s).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_with::>() + .unwrap(), + s + ); + + // [u8] + let mut x = Cursor::new(Vec::new()); + x.write_with::(s.as_slice()).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_with::>() + .unwrap(), + s + ); - assert_eq!(&s2.to_string(), data); + // Vec + let mut x = Cursor::new(Vec::new()); + x.write_with::(&s.to_vec()).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_with::>() + .unwrap(), + s + ); } #[test] fn null_wide_string_round_trip() { - use binrw::{io::Cursor, BinReaderExt, BinWriterExt, NullWideString}; + use binrw::BinWriterExt; - let data = "test test test"; - let s = NullWideString::from(data); + // str + let s = "test test test"; + let mut x = Cursor::new(Vec::new()); + x.write_be_with::(s).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_be_with::() + .unwrap(), + s + ); + + // String + let mut x = Cursor::new(Vec::new()); + x.write_be_with::(&s.to_string()) + .unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_be_with::() + .unwrap(), + s + ); + // [u16; N] + let s = b"test test test".map(u16::from); let mut x = Cursor::new(Vec::new()); - x.write_be(&s).unwrap(); + x.write_be_with::(&s).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_be_with::>() + .unwrap(), + s + ); - let s2: NullWideString = Cursor::new(x.into_inner()).read_be().unwrap(); + // [u16] + let mut x = Cursor::new(Vec::new()); + x.write_be_with::(s.as_slice()).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_be_with::>() + .unwrap(), + s + ); - assert_eq!(&s2.to_string(), data); + // Vec + let mut x = Cursor::new(Vec::new()); + x.write_be_with::(&s.to_vec()).unwrap(); + assert_eq!( + Cursor::new(x.into_inner()) + .read_be_with::>() + .unwrap(), + s + ); } diff --git a/binrw/tests/ui/invalid_keyword_struct_field.stderr b/binrw/tests/ui/invalid_keyword_struct_field.stderr index c97aa444..2a7af25f 100644 --- a/binrw/tests/ui/invalid_keyword_struct_field.stderr +++ b/binrw/tests/ui/invalid_keyword_struct_field.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` --> tests/ui/invalid_keyword_struct_field.rs:5:10 | 5 | #[br(invalid_struct_field_keyword)] diff --git a/binrw/tests/ui/non_blocking_errors.stderr b/binrw/tests/ui/non_blocking_errors.stderr index 293402c5..1bb27f9c 100644 --- a/binrw/tests/ui/non_blocking_errors.stderr +++ b/binrw/tests/ui/non_blocking_errors.stderr @@ -4,13 +4,13 @@ error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map` 6 | #[br(invalid_keyword_struct)] | ^^^^^^^^^^^^^^^^^^^^^^ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` --> tests/ui/non_blocking_errors.rs:8:10 | 8 | #[br(invalid_keyword_struct_field_a)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` --> tests/ui/non_blocking_errors.rs:10:10 | 10 | #[br(invalid_keyword_struct_field_b)] diff --git a/binrw/tests/with.rs b/binrw/tests/with.rs new file mode 100644 index 00000000..1bd7a0ef --- /dev/null +++ b/binrw/tests/with.rs @@ -0,0 +1,19 @@ +use binrw::{io::Cursor, BinRead, BinWrite, NullString, With}; + +#[test] +fn with() { + let mut s = With::::read(&mut Cursor::new(b"test\0")).unwrap(); + assert_eq!(*s, "test"); + *s = "mutable".to_string(); + assert_eq!(format!("{:?}", s), "With(\"mutable\")"); + let s2 = s.clone(); + assert_eq!(s, s2); + assert_eq!(s2.into_inner(), "mutable"); + let mut out = Cursor::new(Vec::new()); + s.write(&mut out).unwrap(); + assert_eq!(out.into_inner(), b"mutable\0"); + let a = With::::from("a"); + let b = With::::from("b"); + assert_eq!(a.partial_cmp(&b), Some(core::cmp::Ordering::Less)); + assert_eq!(a.cmp(&b), core::cmp::Ordering::Less); +} diff --git a/binrw_derive/src/binrw/codegen/read_options/struct.rs b/binrw_derive/src/binrw/codegen/read_options/struct.rs index daa7ffa3..10bdf732 100644 --- a/binrw_derive/src/binrw/codegen/read_options/struct.rs +++ b/binrw_derive/src/binrw/codegen/read_options/struct.rs @@ -8,8 +8,8 @@ use crate::{ sanitization::{ make_ident, AFTER_PARSE, ARGS_MACRO, ARGS_TYPE_HINT, BACKTRACE_FRAME, BINREAD_TRAIT, COERCE_FN, DBG_EPRINTLN, MAP_ARGS_TYPE_HINT, OPT, - PARSE_FN_TYPE_HINT, POS, READER, READ_FUNCTION, READ_METHOD, REQUIRED_ARG_TRAIT, - SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, TEMP, WITH_CONTEXT, + PARSE_FN_TYPE_HINT, POS, READER, READ_FROM_TRAIT, READ_FUNCTION, READ_METHOD, + REQUIRED_ARG_TRAIT, SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, TEMP, WITH_CONTEXT, }, }, parser::{ErrContext, FieldMode, Input, Map, Struct, StructField}, @@ -375,6 +375,12 @@ impl<'field> FieldGenerator<'field> { let #READ_FUNCTION = #PARSE_FN_TYPE_HINT(#parser); } } + FieldMode::Converter(converter) => { + let ty = &self.field.ty; + quote_spanned_any! { converter.span()=> + let #READ_FUNCTION = <#ty as #READ_FROM_TRAIT<#converter>>::read_from; + } + } FieldMode::Normal => quote! { let #READ_FUNCTION = #READ_METHOD; }, @@ -396,8 +402,11 @@ impl<'field> FieldGenerator<'field> { let args = get_passed_args(self.field); let ty = &self.field.ty; - if let FieldMode::Function(_) = &self.field.field_mode { - quote_spanned! {ty.span()=> + if matches!( + self.field.field_mode, + FieldMode::Function(_) | FieldMode::Converter(_) + ) { + quote_spanned! { ty.span()=> let #args_var = #ARGS_TYPE_HINT::(#READ_FUNCTION, #args); } } else { @@ -450,11 +459,11 @@ impl<'field> FieldGenerator<'field> { FieldMode::Default => quote! { <_>::default() }, FieldMode::Calc(calc) => quote! { #calc }, FieldMode::TryCalc(calc) => get_try_calc(POS, &self.field.ty, calc), - read_mode @ (FieldMode::Normal | FieldMode::Function(_)) => { + read_mode @ (FieldMode::Normal | FieldMode::Function(_) | FieldMode::Converter(_)) => { let args_arg = get_args_argument(self.field, &self.args_var); let endian_var = &self.endian_var; - if let FieldMode::Function(f) = read_mode { + if let FieldMode::Function(f) | FieldMode::Converter(f) = read_mode { let ty = &self.field.ty; // Adding a closure suppresses mentions of the generated // READ_FUNCTION variable in errors; mapping the value with diff --git a/binrw_derive/src/binrw/codegen/sanitization.rs b/binrw_derive/src/binrw/codegen/sanitization.rs index 97fcd167..df3456c3 100644 --- a/binrw_derive/src/binrw/codegen/sanitization.rs +++ b/binrw_derive/src/binrw/codegen/sanitization.rs @@ -33,6 +33,8 @@ ident_str! { pub(crate) READ_METHOD = from_read_trait!(read_options); pub(crate) AFTER_PARSE = from_read_trait!(after_parse); pub(crate) WRITE_METHOD = from_write_trait!(write_options); + pub(crate) READ_FROM_TRAIT = from_crate!(ReadFrom); + pub(crate) WRITE_INTO_TRAIT = from_crate!(WriteInto); pub(crate) READER = "__binrw_generated_var_reader"; pub(crate) WRITER = "__binrw_generated_var_writer"; pub(crate) OPT = "__binrw_generated_var_endian"; diff --git a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs index ff839058..4c831bb5 100644 --- a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs +++ b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs @@ -1,20 +1,23 @@ -use crate::binrw::{ - codegen::{ - get_assertions, get_endian, get_map_err, get_passed_args, get_try_calc, - sanitization::{ - make_ident, BEFORE_POS, BINWRITE_TRAIT, POS, SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, - WRITER, WRITE_ARGS_TYPE_HINT, WRITE_FN_MAP_OUTPUT_TYPE_HINT, - WRITE_FN_TRY_MAP_OUTPUT_TYPE_HINT, WRITE_FN_TYPE_HINT, WRITE_FUNCTION, - WRITE_MAP_ARGS_TYPE_HINT, WRITE_MAP_INPUT_TYPE_HINT, WRITE_METHOD, - WRITE_TRY_MAP_ARGS_TYPE_HINT, WRITE_ZEROES, +use crate::{ + binrw::{ + codegen::{ + get_assertions, get_endian, get_map_err, get_passed_args, get_try_calc, + sanitization::{ + make_ident, BEFORE_POS, BINWRITE_TRAIT, POS, SAVED_POSITION, SEEK_FROM, SEEK_TRAIT, + WRITER, WRITE_ARGS_TYPE_HINT, WRITE_FN_MAP_OUTPUT_TYPE_HINT, + WRITE_FN_TRY_MAP_OUTPUT_TYPE_HINT, WRITE_FN_TYPE_HINT, WRITE_FUNCTION, + WRITE_INTO_TRAIT, WRITE_MAP_ARGS_TYPE_HINT, WRITE_MAP_INPUT_TYPE_HINT, + WRITE_METHOD, WRITE_TRY_MAP_ARGS_TYPE_HINT, WRITE_ZEROES, + }, }, + parser::{FieldMode, Map, StructField}, }, - parser::{FieldMode, Map, StructField}, + util::quote_spanned_any, }; use core::ops::Not; use proc_macro2::TokenStream; use quote::quote; -use syn::Ident; +use syn::{spanned::Spanned, Ident}; pub(crate) fn write_field(field: &StructField) -> TokenStream { StructFieldGenerator::new(field) @@ -64,6 +67,12 @@ impl<'a> StructFieldGenerator<'a> { quote! { #WRITE_METHOD } } FieldMode::Function(write_fn) => write_fn.clone(), + FieldMode::Converter(converter) => { + let ty = &self.field.ty; + quote_spanned_any! {converter.span()=> + <#ty as #WRITE_INTO_TRAIT<#converter>>::write_into + } + } FieldMode::Default => unreachable!("Ignored fields are not written"), }; @@ -240,7 +249,7 @@ impl<'a> StructFieldGenerator<'a> { let #args = (); #out }, - FieldMode::Function(_) => { + FieldMode::Function(_) | FieldMode::Converter(_) => { let ty = &self.field.ty; quote! { let #args = #WRITE_ARGS_TYPE_HINT::<#ty, W, _, _>( diff --git a/binrw_derive/src/binrw/parser/attrs.rs b/binrw_derive/src/binrw/parser/attrs.rs index afc29715..7af8c4b9 100644 --- a/binrw_derive/src/binrw/parser/attrs.rs +++ b/binrw_derive/src/binrw/parser/attrs.rs @@ -44,4 +44,5 @@ pub(super) type Temp = MetaVoid; pub(super) type Try = MetaVoid; pub(super) type TryCalc = MetaExpr; pub(super) type TryMap = MetaExpr; +pub(super) type With = MetaType; pub(super) type WriteWith = MetaExpr; diff --git a/binrw_derive/src/binrw/parser/field_level_attrs.rs b/binrw_derive/src/binrw/parser/field_level_attrs.rs index dc11a6ab..41fe79b4 100644 --- a/binrw_derive/src/binrw/parser/field_level_attrs.rs +++ b/binrw_derive/src/binrw/parser/field_level_attrs.rs @@ -24,7 +24,7 @@ attr_struct! { pub(crate) magic: Magic, #[from(RW:Args, RW:ArgsRaw)] pub(crate) args: PassedArgs, - #[from(RW:Calc, RW:TryCalc, RO:Default, RW:Ignore, RO:ParseWith, WO:WriteWith)] + #[from(RW:Calc, RW:TryCalc, RO:Default, RW:Ignore, RW:With, RO:ParseWith, WO:WriteWith)] pub(crate) field_mode: FieldMode, #[from(RO:Count)] pub(crate) count: Option, diff --git a/binrw_derive/src/binrw/parser/keywords.rs b/binrw_derive/src/binrw/parser/keywords.rs index c45e90ee..ff178301 100644 --- a/binrw_derive/src/binrw/parser/keywords.rs +++ b/binrw_derive/src/binrw/parser/keywords.rs @@ -48,5 +48,6 @@ define_keywords! { temp, try_calc, try_map, + with, write_with, } diff --git a/binrw_derive/src/binrw/parser/types/field_mode.rs b/binrw_derive/src/binrw/parser/types/field_mode.rs index 8ad71cd0..b59030b0 100644 --- a/binrw_derive/src/binrw/parser/types/field_mode.rs +++ b/binrw_derive/src/binrw/parser/types/field_mode.rs @@ -12,6 +12,7 @@ pub(crate) enum FieldMode { Calc(TokenStream), TryCalc(TokenStream), Function(TokenStream), + Converter(TokenStream), } impl Default for FieldMode { @@ -56,6 +57,12 @@ impl From for FieldMode { } } +impl From for FieldMode { + fn from(with: attrs::With) -> Self { + Self::Converter(with.into_token_stream()) + } +} + impl + KeywordToken> TrySet for T { fn try_set(self, to: &mut FieldMode) -> syn::Result<()> { if matches!(*to, FieldMode::Normal) { From 99696d4d27350764d46140f8e7fb06855272bc32 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Mon, 19 Sep 2022 00:55:42 -0500 Subject: [PATCH 2/2] Revert `repr` on structs, enums, and fields This feature does not work and fundamentally cannot work because the write-side always receives references to values, but (1) there are no `From<&T>` conversions for any std type that anyone would use as a repr, and (2) this would cause a complete copy of the converted object to be retained in memory just to write the object. The `with` feature offers a more appropriate approach to custom serialisations that does not suffer from these limitations. This reverts: "Implement top-level `repr` attribute for structs and enums." commit 6f320039db1732014851e20285ecb6e4cbcb9d30 "Fix nightly tests" commit 38b48d4f1fc2b996205ab2e0055f7401ac827db3 "Implement field-level `repr` for conversion." commit 8353699f9640c1401802353dd97833608630585c ...with the exception of portions of those commits which fixed a bug where arbitrary errors from `try_map` in BinWrite were not allowed due to overly-restrictive type hints. --- binrw/tests/derive/struct_map.rs | 80 ------------------- binrw/tests/derive/write/map.rs | 72 ----------------- binrw/tests/ui/invalid_keyword_enum.stderr | 2 +- .../ui/invalid_keyword_enum_variant.stderr | 2 +- binrw/tests/ui/invalid_keyword_struct.stderr | 2 +- .../ui/invalid_keyword_struct_field.stderr | 2 +- .../tests/ui/invalid_keyword_unit_enum.stderr | 2 +- .../ui/invalid_keyword_with_imports.stderr | 2 +- binrw/tests/ui/non_blocking_errors.stderr | 6 +- binrw/tests/ui/repr_magic_conflict.stderr | 4 +- binrw_derive/src/binrw/codegen/meta.rs | 18 ++--- .../src/binrw/codegen/read_options.rs | 8 -- .../src/binrw/codegen/read_options/enum.rs | 2 +- .../src/binrw/codegen/read_options/struct.rs | 14 +--- .../src/binrw/codegen/write_options.rs | 9 --- .../src/binrw/codegen/write_options/enum.rs | 2 +- .../codegen/write_options/struct_field.rs | 3 +- .../src/binrw/parser/field_level_attrs.rs | 2 +- .../src/binrw/parser/top_level_attrs.rs | 31 ++++--- binrw_derive/src/binrw/parser/types/map.rs | 16 +--- 20 files changed, 43 insertions(+), 236 deletions(-) diff --git a/binrw/tests/derive/struct_map.rs b/binrw/tests/derive/struct_map.rs index 3e586622..65357400 100644 --- a/binrw/tests/derive/struct_map.rs +++ b/binrw/tests/derive/struct_map.rs @@ -33,86 +33,6 @@ fn map_expr() { assert_eq!(result.a, 2); } -#[test] -fn map_repr_enum() { - #[derive(BinRead, Debug, PartialEq)] - #[br(repr = u8)] - enum Test { - SubTest(u8), - } - - impl From for Test { - fn from(u: u8) -> Self { - Self::SubTest(u) - } - } - - let result = Test::read(&mut Cursor::new("\x01")).unwrap(); - assert_eq!(result, Test::SubTest(1)); -} - -#[test] -fn map_repr_enum_variant() { - #[derive(BinRead, Debug, PartialEq)] - enum Test { - SubTest(#[br(repr = u8)] SubTest), - } - - #[derive(Debug, PartialEq)] - struct SubTest(u8); - - impl From for SubTest { - fn from(u: u8) -> Self { - Self(u) - } - } - - let result = Test::read_le(&mut Cursor::new("\x01")).unwrap(); - assert_eq!(result, Test::SubTest(SubTest(1))); -} - -#[test] -fn map_repr_struct() { - #[derive(BinRead, Debug)] - #[br(repr = u8)] - struct Test { - a: u8, - } - - impl From for Test { - fn from(a: u8) -> Self { - Self { a } - } - } - - let result = Test::read(&mut Cursor::new("\x01")).unwrap(); - assert_eq!(result.a, 1); -} - -#[test] -fn map_repr_struct_field() { - #[derive(BinRead, Debug)] - #[br(big)] - struct Test { - #[br(repr = u8)] - a: SubTest, - } - - #[derive(Debug)] - struct SubTest { - a: u8, - } - - impl From for SubTest { - fn from(a: u8) -> Self { - Self { a } - } - } - - let result = Test::read(&mut Cursor::new("\x01")).unwrap(); - assert_eq!(result.a.a, 1); -} - #[test] fn map_struct() { #[derive(BinRead, Debug)] diff --git a/binrw/tests/derive/write/map.rs b/binrw/tests/derive/write/map.rs index a2f00e24..9ab72437 100644 --- a/binrw/tests/derive/write/map.rs +++ b/binrw/tests/derive/write/map.rs @@ -39,78 +39,6 @@ fn map_field_code_coverage() { } } -#[test] -fn map_repr_enum() { - #[allow(dead_code)] - #[derive(BinWrite, Debug)] - #[bw(repr = u8)] - enum Test { - SubTest(u8), - } - - impl From<&Test> for u8 { - fn from(t: &Test) -> Self { - match t { - Test::SubTest(u) => *u, - } - } - } -} - -#[test] -fn map_repr_enum_variant() { - #[allow(dead_code)] - #[derive(BinWrite, Debug)] - enum Test { - SubTest(#[bw(repr = u8)] SubTest), - } - - #[derive(Debug)] - struct SubTest(u8); - - impl From<&SubTest> for u8 { - fn from(s: &SubTest) -> Self { - s.0 - } - } -} - -#[test] -fn map_repr_struct() { - #[derive(BinWrite, Debug)] - #[bw(repr = u8)] - struct Test { - a: u8, - } - - impl From<&Test> for u8 { - fn from(t: &Test) -> Self { - t.a - } - } -} - -#[test] -fn map_repr_struct_field() { - #[derive(BinWrite, Debug)] - #[bw(big)] - struct Test { - #[bw(repr = u8)] - a: SubTest, - } - - #[derive(Debug)] - struct SubTest { - a: u8, - } - - impl From<&SubTest> for u8 { - fn from(s: &SubTest) -> Self { - s.a - } - } -} - #[test] fn try_map() { use binrw::prelude::*; diff --git a/binrw/tests/ui/invalid_keyword_enum.stderr b/binrw/tests/ui/invalid_keyword_enum.stderr index 447f66e9..1f67d421 100644 --- a/binrw/tests/ui/invalid_keyword_enum.stderr +++ b/binrw/tests/ui/invalid_keyword_enum.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert`, `return_all_errors`, `return_unexpected_error` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `import`, `import_raw`, `assert`, `pre_assert`, `return_all_errors`, `return_unexpected_error` --> $DIR/invalid_keyword_enum.rs:4:6 | 4 | #[br(invalid_enum_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_enum_variant.stderr b/binrw/tests/ui/invalid_keyword_enum_variant.stderr index 7c5fdf3c..e8cb9215 100644 --- a/binrw/tests/ui/invalid_keyword_enum_variant.stderr +++ b/binrw/tests/ui/invalid_keyword_enum_variant.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` --> $DIR/invalid_keyword_enum_variant.rs:5:10 | 5 | #[br(invalid_enum_variant_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_struct.stderr b/binrw/tests/ui/invalid_keyword_struct.stderr index 52f4d0ac..bd892ebb 100644 --- a/binrw/tests/ui/invalid_keyword_struct.stderr +++ b/binrw/tests/ui/invalid_keyword_struct.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` --> $DIR/invalid_keyword_struct.rs:4:6 | 4 | #[br(invalid_struct_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_struct_field.stderr b/binrw/tests/ui/invalid_keyword_struct_field.stderr index 2a7af25f..396870b3 100644 --- a/binrw/tests/ui/invalid_keyword_struct_field.stderr +++ b/binrw/tests/ui/invalid_keyword_struct_field.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` --> tests/ui/invalid_keyword_struct_field.rs:5:10 | 5 | #[br(invalid_struct_field_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_unit_enum.stderr b/binrw/tests/ui/invalid_keyword_unit_enum.stderr index 22b0d2bc..8f9f0d71 100644 --- a/binrw/tests/ui/invalid_keyword_unit_enum.stderr +++ b/binrw/tests/ui/invalid_keyword_unit_enum.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `import`, `import_raw`, `repr` --> tests/ui/invalid_keyword_unit_enum.rs:4:6 | 4 | #[br(invalid_unit_enum_keyword)] diff --git a/binrw/tests/ui/invalid_keyword_with_imports.stderr b/binrw/tests/ui/invalid_keyword_with_imports.stderr index d2594dc1..6c214c54 100644 --- a/binrw/tests/ui/invalid_keyword_with_imports.stderr +++ b/binrw/tests/ui/invalid_keyword_with_imports.stderr @@ -1,4 +1,4 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` --> $DIR/invalid_keyword_with_imports.rs:5:6 | 5 | #[br(invalid_struct_keyword)] diff --git a/binrw/tests/ui/non_blocking_errors.stderr b/binrw/tests/ui/non_blocking_errors.stderr index 1bb27f9c..f386af18 100644 --- a/binrw/tests/ui/non_blocking_errors.stderr +++ b/binrw/tests/ui/non_blocking_errors.stderr @@ -1,16 +1,16 @@ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `import`, `import_raw`, `assert`, `pre_assert` --> tests/ui/non_blocking_errors.rs:6:6 | 6 | #[br(invalid_keyword_struct)] | ^^^^^^^^^^^^^^^^^^^^^^ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` --> tests/ui/non_blocking_errors.rs:8:10 | 8 | #[br(invalid_keyword_struct_field_a)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `repr`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` +error: expected one of: `big`, `little`, `is_big`, `is_little`, `map`, `try_map`, `magic`, `args`, `args_raw`, `calc`, `try_calc`, `default`, `ignore`, `with`, `parse_with`, `count`, `offset`, `offset_after`, `if`, `deref_now`, `postprocess_now`, `restore_position`, `try`, `temp`, `assert`, `err_context`, `pad_before`, `pad_after`, `align_before`, `align_after`, `seek_before`, `pad_size_to`, `dbg` --> tests/ui/non_blocking_errors.rs:10:10 | 10 | #[br(invalid_keyword_struct_field_b)] diff --git a/binrw/tests/ui/repr_magic_conflict.stderr b/binrw/tests/ui/repr_magic_conflict.stderr index 53f16036..cc2ff40d 100644 --- a/binrw/tests/ui/repr_magic_conflict.stderr +++ b/binrw/tests/ui/repr_magic_conflict.stderr @@ -1,8 +1,8 @@ error: `repr` and `magic` are mutually exclusive - --> tests/ui/repr_magic_conflict.rs:4:13 + --> tests/ui/repr_magic_conflict.rs:4:6 | 4 | #[br(repr = u8)] - | _____________^ + | ______^ 5 | | enum Foo { 6 | | #[br(magic = 0u8)] A, | |______________^ diff --git a/binrw_derive/src/binrw/codegen/meta.rs b/binrw_derive/src/binrw/codegen/meta.rs index f85e3c3c..a26da27b 100644 --- a/binrw_derive/src/binrw/codegen/meta.rs +++ b/binrw_derive/src/binrw/codegen/meta.rs @@ -1,5 +1,5 @@ use super::sanitization::{META_ENDIAN_KIND, READ_ENDIAN, READ_MAGIC, WRITE_ENDIAN, WRITE_MAGIC}; -use crate::binrw::parser::{CondEndian, Input, Map}; +use crate::binrw::parser::{CondEndian, Input}; use proc_macro2::TokenStream; use quote::quote; @@ -25,19 +25,11 @@ pub(crate) fn generate( let endian_meta = if WRITE { WRITE_ENDIAN } else { READ_ENDIAN }; let endian = match input.endian() { - CondEndian::Inherited => match input.map() { - Map::None => input.is_empty().then(|| { - quote! { - #META_ENDIAN_KIND::None - } - }), - Map::Map(_) | Map::Try(_) => Some(quote! { + CondEndian::Inherited => input.is_endian_agnostic().then(|| { + quote! { #META_ENDIAN_KIND::None - }), - Map::Repr(repr) => ["i8", "u8"].contains(&repr.to_string().as_str()).then(|| { - quote! { <(#repr) as #endian_meta>::ENDIAN } - }), - }, + } + }), CondEndian::Fixed(endian) => Some(quote! { #META_ENDIAN_KIND::Endian(#endian) }), diff --git a/binrw_derive/src/binrw/codegen/read_options.rs b/binrw_derive/src/binrw/codegen/read_options.rs index 239f8153..1e8be6d8 100644 --- a/binrw_derive/src/binrw/codegen/read_options.rs +++ b/binrw_derive/src/binrw/codegen/read_options.rs @@ -27,14 +27,6 @@ pub(crate) fn generate(input: &Input, derive_input: &syn::DeriveInput) -> TokenS }, Map::Try(map) => map::generate_try_map(input, name, map), Map::Map(map) => map::generate_map(input, name, map), - Map::Repr(ty) => match input { - Input::UnitOnlyEnum(e) => generate_unit_enum(input, name, e), - _ => map::generate_try_map( - input, - name, - "e! { <#ty as core::convert::TryInto<_>>::try_into }, - ), - }, }; quote! { diff --git a/binrw_derive/src/binrw/codegen/read_options/enum.rs b/binrw_derive/src/binrw/codegen/read_options/enum.rs index 77f6d54e..319229e0 100644 --- a/binrw_derive/src/binrw/codegen/read_options/enum.rs +++ b/binrw_derive/src/binrw/codegen/read_options/enum.rs @@ -27,7 +27,7 @@ pub(super) fn generate_unit_enum( .add_magic_pre_assertion() .finish(); - let read = match en.map.as_repr() { + let read = match &en.repr { Some(repr) => generate_unit_enum_repr(repr, &en.fields), None => generate_unit_enum_magic(&en.fields), }; diff --git a/binrw_derive/src/binrw/codegen/read_options/struct.rs b/binrw_derive/src/binrw/codegen/read_options/struct.rs index 10bdf732..40d76509 100644 --- a/binrw_derive/src/binrw/codegen/read_options/struct.rs +++ b/binrw_derive/src/binrw/codegen/read_options/struct.rs @@ -317,7 +317,7 @@ impl<'field> FieldGenerator<'field> { let value = self.out; quote! { #map_func(#value) } } - Map::Try(t) | Map::Repr(t) => { + Map::Try(t) => { // TODO: Position should always just be saved once for a field if used let value = self.out; let map_err = get_map_err(SAVED_POSITION, t.span()); @@ -343,15 +343,7 @@ impl<'field> FieldGenerator<'field> { let #map_func = (#COERCE_FN::<#ty, _, _>(#map)); } } - Map::Try(try_map) | Map::Repr(try_map) => { - let try_map = if matches!(self.field.map, Map::Repr(_)) { - quote! { - <#try_map as core::convert::TryInto<_>>::try_into - } - } else { - try_map.clone() - }; - + Map::Try(try_map) => { // TODO: Position should always just be saved once for a field if used quote! { let #map_func = (#COERCE_FN::<::core::result::Result<#ty, _>, _, _>(#try_map)); @@ -411,7 +403,7 @@ impl<'field> FieldGenerator<'field> { } } else { match &self.field.map { - Map::Map(_) | Map::Try(_) | Map::Repr(_) => { + Map::Map(_) | Map::Try(_) => { quote_spanned! {ty.span()=> let #args_var = #MAP_ARGS_TYPE_HINT(&#map_func, #args); } diff --git a/binrw_derive/src/binrw/codegen/write_options.rs b/binrw_derive/src/binrw/codegen/write_options.rs index b6e97563..8a2d94b6 100644 --- a/binrw_derive/src/binrw/codegen/write_options.rs +++ b/binrw_derive/src/binrw/codegen/write_options.rs @@ -23,10 +23,6 @@ pub(crate) fn generate(input: &Input, derive_input: &syn::DeriveInput) -> TokenS Input::UnitOnlyEnum(e) => generate_unit_enum(input, name, e), }, Map::Try(map) | Map::Map(map) => generate_map(input, name, map), - Map::Repr(map) => match input { - Input::UnitOnlyEnum(e) => generate_unit_enum(input, name, e), - _ => generate_map(input, name, map), - }, }; quote! { @@ -42,11 +38,6 @@ fn generate_map(input: &Input, name: Option<&Ident>, map: &TokenStream) -> Token let map_err = get_map_err(POS, map.span()); quote! { #map_err? } }); - let map = if matches!(input.map(), Map::Repr(_)) { - quote! { <#map as core::convert::TryFrom<_>>::try_from } - } else { - map.clone() - }; let write_data = quote! { #WRITE_METHOD( &((#map)(self) #map_try), diff --git a/binrw_derive/src/binrw/codegen/write_options/enum.rs b/binrw_derive/src/binrw/codegen/write_options/enum.rs index bd5c8ecb..fc797017 100644 --- a/binrw_derive/src/binrw/codegen/write_options/enum.rs +++ b/binrw_derive/src/binrw/codegen/write_options/enum.rs @@ -11,7 +11,7 @@ pub(crate) fn generate_unit_enum( name: Option<&Ident>, en: &UnitOnlyEnum, ) -> TokenStream { - let write = match en.map.as_repr() { + let write = match &en.repr { Some(repr) => generate_unit_enum_repr(repr, &en.fields), None => generate_unit_enum_magic(&en.fields), }; diff --git a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs index 4c831bb5..bba517f7 100644 --- a/binrw_derive/src/binrw/codegen/write_options/struct_field.rs +++ b/binrw_derive/src/binrw/codegen/write_options/struct_field.rs @@ -233,7 +233,7 @@ impl<'a> StructFieldGenerator<'a> { let #args = #WRITE_MAP_ARGS_TYPE_HINT(&#map_fn, #args_val); #out }, - Map::Try(_) | Map::Repr(_) => quote! { + Map::Try(_) => quote! { let #args = #WRITE_TRY_MAP_ARGS_TYPE_HINT(&#map_fn, #args_val); #out }, @@ -296,7 +296,6 @@ fn args_ident(ident: &Ident) -> Ident { fn field_mapping(map: &Map) -> Option { match map { Map::Try(map_fn) | Map::Map(map_fn) => Some(quote! { (#map_fn) }), - Map::Repr(ty) => Some(quote! { (<#ty as core::convert::TryFrom<_>>::try_from) }), Map::None => None, } } diff --git a/binrw_derive/src/binrw/parser/field_level_attrs.rs b/binrw_derive/src/binrw/parser/field_level_attrs.rs index 41fe79b4..3b82336a 100644 --- a/binrw_derive/src/binrw/parser/field_level_attrs.rs +++ b/binrw_derive/src/binrw/parser/field_level_attrs.rs @@ -18,7 +18,7 @@ attr_struct! { pub(crate) field: syn::Field, #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, - #[from(RW:Map, RW:TryMap, RW:Repr)] + #[from(RW:Map, RW:TryMap)] pub(crate) map: Map, #[from(RW:Magic)] pub(crate) magic: Magic, diff --git a/binrw_derive/src/binrw/parser/top_level_attrs.rs b/binrw_derive/src/binrw/parser/top_level_attrs.rs index 5ca86fe2..5f53bb88 100644 --- a/binrw_derive/src/binrw/parser/top_level_attrs.rs +++ b/binrw_derive/src/binrw/parser/top_level_attrs.rs @@ -1,7 +1,7 @@ use super::{ attr_struct, types::{Assert, CondEndian, EnumErrorMode, Imports, Magic, Map}, - EnumVariant, FromInput, ParseResult, StructField, TrySet, UnitEnumField, + EnumVariant, FromInput, ParseResult, SpannedValue, StructField, TrySet, UnitEnumField, }; use crate::binrw::Options; use proc_macro2::TokenStream; @@ -113,12 +113,17 @@ impl Input { } } - pub(crate) fn is_empty(&self) -> bool { - match self { - Input::Struct(s) => s.fields.is_empty() && s.magic.is_none(), - Input::UnitStruct(_) => true, - Input::Enum(e) => e.variants.is_empty() && e.magic.is_none(), - Input::UnitOnlyEnum(_) => false, + pub(crate) fn is_endian_agnostic(&self) -> bool { + match self.map() { + Map::None => match self { + Input::Struct(s) => s.fields.is_empty() && s.magic.is_none(), + Input::UnitStruct(_) => true, + Input::Enum(en) => en.variants.is_empty() && en.magic.is_none(), + Input::UnitOnlyEnum(en) => { + matches!(&en.repr, Some(repr) if ["i8", "u8"].contains(&repr.to_string().as_str())) + } + }, + Map::Map(_) | Map::Try(_) => true, } } @@ -181,7 +186,7 @@ attr_struct! { pub(crate) struct Struct { #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, - #[from(RW:Map, RW:TryMap, RW:Repr)] + #[from(RW:Map, RW:TryMap)] pub(crate) map: Map, #[from(RW:Magic)] pub(crate) magic: Magic, @@ -284,7 +289,7 @@ attr_struct! { pub(crate) ident: Option, #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, - #[from(RW:Map, RW:TryMap, RW:Repr)] + #[from(RW:Map, RW:TryMap)] pub(crate) map: Map, #[from(RW:Magic)] pub(crate) magic: Magic, @@ -332,12 +337,14 @@ attr_struct! { pub(crate) struct UnitOnlyEnum { #[from(RW:Big, RW:Little, RW:IsBig, RW:IsLittle)] pub(crate) endian: CondEndian, - #[from(RW:Map, RW:TryMap, RW:Repr)] + #[from(RW:Map, RW:TryMap)] pub(crate) map: Map, #[from(RW:Magic)] pub(crate) magic: Magic, #[from(RW:Import, RW:ImportRaw)] pub(crate) imports: Imports, + #[from(RW:Repr)] + pub(crate) repr: Option>, pub(crate) fields: Vec, pub(crate) is_magic_enum: bool, } @@ -353,7 +360,7 @@ impl FromInput> for UnitOnlyEnum { type Field = UnitEnumField; fn push_field(&mut self, field: Self::Field) -> syn::Result<()> { - if let (Some(repr), Some(magic)) = (self.map.as_repr(), field.magic.as_ref()) { + if let (Some(repr), Some(magic)) = (self.repr.as_ref(), field.magic.as_ref()) { let magic_span = magic.span(); let span = magic_span.join(repr.span()).unwrap_or(magic_span); Err(syn::Error::new( @@ -368,7 +375,7 @@ impl FromInput> for UnitOnlyEnum { } fn validate(&self, options: Options) -> syn::Result<()> { - if self.map.as_repr().is_some() || self.is_magic_enum() { + if self.repr.is_some() || self.is_magic_enum() { Ok(()) } else if options.write { Err(syn::Error::new(proc_macro2::Span::call_site(), "BinWrite on unit-like enums requires either `#[bw(repr = ...)]` on the enum or `#[bw(magic = ...)]` on at least one variant")) diff --git a/binrw_derive/src/binrw/parser/types/map.rs b/binrw_derive/src/binrw/parser/types/map.rs index 89398560..23dc16c2 100644 --- a/binrw_derive/src/binrw/parser/types/map.rs +++ b/binrw_derive/src/binrw/parser/types/map.rs @@ -10,17 +10,9 @@ pub(crate) enum Map { None, Map(TokenStream), Try(TokenStream), - Repr(TokenStream), } impl Map { - pub(crate) fn as_repr(&self) -> Option<&TokenStream> { - match self { - Map::Repr(r) => Some(r), - _ => None, - } - } - pub(crate) fn is_some(&self) -> bool { !matches!(self, Self::None) } @@ -30,7 +22,7 @@ impl Map { } pub(crate) fn is_try(&self) -> bool { - matches!(self, Self::Try(_) | Self::Repr(_)) + matches!(self, Self::Try(_)) } } @@ -52,12 +44,6 @@ impl From for Map { } } -impl From for Map { - fn from(repr: attrs::Repr) -> Self { - Self::Repr(repr.value.to_token_stream()) - } -} - impl + KeywordToken> TrySet for T { fn try_set(self, to: &mut Map) -> syn::Result<()> { if to.is_some() {