#[cfg(feature = "alloc")]
mod alloc;
mod core;
use ::core::cmp::Ordering;
#[allow(dead_code)]
#[inline]
pub(crate) fn lexicographical_partial_ord<T, U>(a: &[T], b: &[U]) -> Option<Ordering>
where
T: PartialOrd<U>,
{
for (a, b) in a.iter().zip(b.iter()) {
match (*a).partial_cmp(b) {
Some(Ordering::Equal) => {}
ord => return ord,
}
}
a.len().partial_cmp(&b.len())
}
#[cfg(test)]
mod core_tests {
use core::fmt::{Debug, Formatter};
use core::mem::MaybeUninit;
use core::ptr::Alignment;
use crate::test::{roundtrip, to_archived};
use crate::{Serialize, SerializeError, VerifyError};
#[test]
fn roundtrip_unit_struct() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate)]
struct Test;
roundtrip(&Test);
roundtrip(&[Test, Test]);
}
#[test]
fn roundtrip_tuple_struct() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate, compare(PartialEq), derive(Debug))]
struct Test((), i32, Option<i32>);
roundtrip(&Test((), 42, Some(42)));
roundtrip(&[Test((), 42, Some(42)), Test((), 42, Some(42))]);
}
#[test]
fn roundtrip_struct() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate, compare(PartialEq), derive(Debug))]
struct Test {
a: (),
b: i32,
c: Option<i32>,
}
roundtrip(&Test {
a: (),
b: 42,
c: Some(42),
});
roundtrip(&[
Test {
a: (),
b: 42,
c: Some(42),
},
Test {
a: (),
b: 42,
c: Some(42),
},
]);
}
#[test]
fn roundtrip_generic_struct() {
use core::fmt;
pub trait TestTrait {
type Associated: PartialEq;
}
impl TestTrait for () {
type Associated = i32;
}
#[derive(Serialize, PartialEq)]
#[nibblecode(crate, compare(PartialEq))]
struct Test<T: TestTrait> {
a: (),
b: <T as TestTrait>::Associated,
c: Option<i32>,
}
impl<T: TestTrait> Debug for Test<T>
where
T::Associated: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Test")
.field("a", &self.a)
.field("b", &self.b)
.field("c", &self.c)
.finish()
}
}
impl<T: TestTrait> Debug for ArchivedTest<T>
where
T::Associated: Serialize,
<T::Associated as Serialize>::Archived: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Test")
.field("a", &self.a)
.field("b", &self.b)
.field("c", &self.c)
.finish()
}
}
roundtrip(&Test::<()> {
a: (),
b: 42,
c: Some(42),
});
roundtrip(&[
Test::<()> {
a: (),
b: 42,
c: Some(42),
},
Test::<()> {
a: (),
b: 42,
c: Some(42),
},
]);
}
#[test]
fn roundtrip_enum() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate, compare(PartialEq), derive(Debug))]
enum Test {
A,
B(i32),
C { inner: i32 },
}
roundtrip(&Test::A);
roundtrip(&Test::B(42));
roundtrip(&Test::C { inner: 42 });
roundtrip(&[Test::A, Test::B(42), Test::C { inner: 42 }]);
}
#[test]
fn roundtrip_generic_enum() {
use core::fmt;
pub trait TestTrait {
type Associated: PartialEq;
}
impl TestTrait for () {
type Associated = i32;
}
#[derive(Serialize, PartialEq)]
#[nibblecode(crate, compare(PartialEq))]
enum Test<T: TestTrait> {
A,
B(<T as TestTrait>::Associated),
C { inner: <T as TestTrait>::Associated },
}
impl<T: TestTrait> Debug for Test<T>
where
T::Associated: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Test::A => f.debug_tuple("Test::A").finish(),
Test::B(value) => f.debug_tuple("Test::B").field(value).finish(),
Test::C { inner } => f.debug_struct("Test::C").field("inner", inner).finish(),
}
}
}
impl<T: TestTrait> Debug for ArchivedTest<T>
where
T::Associated: Serialize,
<T::Associated as Serialize>::Archived: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ArchivedTest::A => f.debug_tuple("ArchivedTest::A").finish(),
ArchivedTest::B(value) => {
f.debug_tuple("ArchivedTest::B").field(value).finish()
}
ArchivedTest::C { inner } => f
.debug_struct("ArchivedTest::C")
.field("inner", inner)
.finish(),
}
}
}
roundtrip(&Test::<()>::A);
roundtrip(&Test::<()>::B(42));
roundtrip(&Test::<()>::C { inner: 42 });
roundtrip(&[
Test::<()>::A,
Test::<()>::B(42),
Test::<()>::C { inner: 42 },
]);
}
#[test]
fn basic_mutable_refs() {
to_archived(&42i32, |archived| {
assert_eq!(*archived, 42);
*archived = 11.into();
assert_eq!(*archived, 11);
});
}
#[test]
fn struct_mutable_refs() {
#[derive(Serialize)]
#[nibblecode(crate)]
struct Opaque(i32);
impl ArchivedOpaque {
fn get(&self) -> i32 {
self.0.into()
}
fn set(&mut self, value: i32) {
self.0 = value.into();
}
}
#[derive(Serialize)]
#[nibblecode(crate)]
struct Test {
a: Opaque,
}
let value = Test { a: Opaque(10) };
to_archived(&value, |archived| {
assert_eq!(archived.a.get(), 10);
ArchivedOpaque::set(&mut archived.a, 50);
assert_eq!(archived.a.get(), 50);
})
}
#[test]
fn enum_mutable_ref() {
#[allow(dead_code)]
#[derive(Serialize)]
#[nibblecode(crate)]
enum Test {
A,
B(char),
C(i32),
}
let value = Test::A;
to_archived(&value, |archived| {
if let ArchivedTest::A = *archived {
()
} else {
panic!("incorrect enum after archiving");
}
*archived = ArchivedTest::C(42.into());
if let ArchivedTest::C(i) = *archived {
assert_eq!(i, 42);
} else {
panic!("incorrect enum after mutation");
}
});
}
#[test]
fn complex_bounds() {
use core::marker::PhantomData;
trait MyTrait {}
impl MyTrait for i32 {}
#[repr(transparent)]
struct MyStruct<T> {
_phantom: PhantomData<T>,
}
impl<T: Serialize + MyTrait> Serialize for MyStruct<T> {
type Archived = MyStruct<T::Archived>;
const ALIGN: Alignment = Alignment::MIN;
unsafe fn serialize(
&self,
_: *mut MaybeUninit<MyStruct<T::Archived>>,
_: *mut MaybeUninit<u8>,
) -> usize {
0
}
fn serialized_size(&self, _: usize) -> Result<usize, SerializeError> {
Ok(0)
}
unsafe fn verify(_: *const Self::Archived, _: *const u8) -> Result<(), VerifyError> {
Ok(())
}
}
#[allow(dead_code)]
#[derive(Serialize)]
#[nibblecode(
crate,
archive_bounds(T: MyTrait)
)]
enum Node<T> {
Nil,
Cons {
value: T,
#[nibblecode(recursive)]
next: MyStruct<Self>,
},
}
impl<T: MyTrait> MyTrait for Node<T> {}
}
#[test]
fn derive_attributes() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate, compare(PartialEq), derive(Debug))]
struct Test {
a: i32,
b: Option<u32>,
}
let value = Test { a: 42, b: Some(12) };
roundtrip(&value);
}
#[test]
fn compare() {
#[derive(Serialize)]
#[nibblecode(crate, compare(PartialEq, PartialOrd))]
pub struct TupleFoo(i32);
#[derive(Serialize)]
#[nibblecode(crate, compare(PartialEq, PartialOrd))]
pub struct StructFoo {
t: i32,
}
#[derive(Serialize)]
#[nibblecode(crate, compare(PartialEq, PartialOrd))]
pub enum EnumFoo {
#[allow(dead_code)]
Foo(i32),
}
}
#[test]
fn default_type_parameters() {
#[derive(Serialize)]
#[nibblecode(crate)]
pub struct TupleFoo<T = i32>(T);
#[derive(Serialize)]
#[nibblecode(crate)]
pub struct StructFoo<T = i32> {
t: T,
}
#[derive(Serialize)]
#[nibblecode(crate)]
pub enum EnumFoo<T = i32> {
#[allow(dead_code)]
T(T),
}
}
#[test]
fn const_generics() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate)]
pub struct Const<const N: usize>;
roundtrip(&Const::<1>);
roundtrip(&Const::<2>);
roundtrip(&Const::<3>);
#[derive(Serialize)]
#[nibblecode(crate)]
pub struct Array<T, const N: usize>([T; N]);
}
#[test]
fn repr_c_packed() {
#[derive(Serialize)]
#[nibblecode(crate, attr(repr(C, packed)))]
#[allow(dead_code)]
struct CPackedRepr {
a: u8,
b: u32,
c: u8,
}
assert_eq!(core::mem::size_of::<ArchivedCPackedRepr>(), 6);
#[derive(Serialize)]
#[nibblecode(crate, attr(repr(C), repr(packed)))]
#[allow(dead_code)]
struct CPackedRepr2 {
a: u8,
b: u32,
c: u8,
}
assert_eq!(core::mem::size_of::<ArchivedCPackedRepr2>(), 6);
}
#[test]
fn repr_c_align() {
#[derive(Serialize)]
#[nibblecode(crate, attr(repr(C, align(8))))]
#[allow(dead_code)]
struct CAlignRepr {
a: u8,
}
assert_eq!(core::mem::align_of::<ArchivedCAlignRepr>(), 8);
#[derive(Serialize)]
#[nibblecode(crate, attr(repr(C), repr(align(8))))]
#[allow(dead_code)]
struct CAlignRepr2 {
a: u8,
}
assert_eq!(core::mem::align_of::<ArchivedCAlignRepr>(), 8);
}
#[test]
fn archive_as_unit_struct() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate, as = ExampleUnitStruct)]
#[repr(C)]
struct ExampleUnitStruct;
roundtrip(&ExampleUnitStruct);
}
#[test]
fn archive_as_tuple_struct() {
#[derive(Serialize, Debug)]
#[nibblecode(crate, as = ExampleTupleStruct<T::Archived>)]
#[repr(transparent)]
struct ExampleTupleStruct<T>(T);
impl<T: PartialEq<U>, U> PartialEq<ExampleTupleStruct<U>> for ExampleTupleStruct<T> {
fn eq(&self, other: &ExampleTupleStruct<U>) -> bool {
self.0.eq(&other.0)
}
}
roundtrip(&ExampleTupleStruct(42i32));
}
#[test]
fn archive_as_struct() {
#[derive(Serialize, Debug)]
#[nibblecode(crate, as = ExampleStruct<T::Archived>)]
#[repr(transparent)]
struct ExampleStruct<T> {
value: T,
}
impl<T, U> PartialEq<ExampleStruct<U>> for ExampleStruct<T>
where
T: PartialEq<U>,
{
fn eq(&self, other: &ExampleStruct<U>) -> bool {
self.value.eq(&other.value)
}
}
roundtrip(&ExampleStruct { value: 42i32 });
}
#[test]
fn archive_as_enum() {
#[derive(Serialize, Debug)]
#[nibblecode(crate, as = ExampleEnum<T::Archived>)]
#[repr(u8)]
enum ExampleEnum<T> {
A(T),
#[allow(dead_code)]
B,
}
impl<T: PartialEq<U>, U> PartialEq<ExampleEnum<U>> for ExampleEnum<T> {
fn eq(&self, other: &ExampleEnum<U>) -> bool {
match self {
ExampleEnum::A(value) => {
if let ExampleEnum::A(other) = other {
value.eq(other)
} else {
false
}
}
ExampleEnum::B => {
if let ExampleEnum::B = other {
true
} else {
false
}
}
}
}
}
roundtrip(&ExampleEnum::A(42i32));
}
#[test]
fn archive_as_self() {
#[derive(Clone, Debug, Default, Serialize)]
#[nibblecode(crate, as = Self)]
#[repr(C)]
struct Example {
inner: bool,
}
}
#[test]
fn archive_as_generic() {
#[repr(C)]
struct Wrapper<T> {
inner: T,
}
#[derive(Clone, Debug, Default, Serialize)]
#[nibblecode(crate, as = Wrapper<bool>)]
#[repr(C)]
struct Example {
inner: bool,
}
}
#[test]
fn archive_crate_path() {
use crate as alt_path;
#[derive(Serialize)]
#[nibblecode(crate = alt_path)]
struct Test<'a> {
#[nibblecode(with = alt_path::with::InlineAsBox)]
value: &'a str,
other: i32,
}
}
#[test]
fn pass_thru_derive_with_option() {
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
#[nibblecode(crate, compare(PartialEq), derive(Clone, Copy, Debug))]
enum ExampleEnum {
#[allow(dead_code)]
Foo,
Bar(u64),
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
#[nibblecode(crate, compare(PartialEq), derive(Clone, Copy, Debug))]
struct Example {
x: i32,
y: Option<ExampleEnum>,
}
let _ = Example {
x: 0,
y: Some(ExampleEnum::Bar(0)),
};
}
}
#[cfg(all(test, feature = "alloc"))]
mod alloc_tests {
use core::fmt::Debug;
use crate::Serialize;
use crate::test::{roundtrip, to_archived};
#[test]
fn struct_container_mutable_refs() {
#[derive(Serialize)]
#[nibblecode(crate)]
struct Test {
a: Box<i32>,
b: Vec<String>,
}
let value = Test {
a: Box::new(10),
b: vec!["hello".to_string(), "world".to_string()],
};
to_archived(&value, |archived| {
assert_eq!(*archived.a, 10);
assert_eq!(archived.b.len(), 2);
assert_eq!(archived.b[0], "hello");
assert_eq!(archived.b[1], "world");
*archived.a = 50.into();
assert_eq!(*archived.a, 50);
let slice = &mut archived.b;
slice[0].make_ascii_uppercase();
slice[1].make_ascii_uppercase();
assert_eq!(archived.b[0], "HELLO");
assert_eq!(archived.b[1], "WORLD");
});
}
#[test]
fn recursive_structures() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(crate, compare(PartialEq), derive(Debug))]
enum Node {
Nil,
Cons(#[nibblecode(recursive)] Box<Node>),
}
roundtrip(&Node::Cons(Box::new(Node::Cons(Box::new(Node::Nil)))));
}
#[test]
fn recursive_self_types() {
#[derive(Serialize, Debug, PartialEq)]
#[nibblecode(
crate,
archive_bounds(T::Archived: core::fmt::Debug),
compare(PartialEq),
derive(Debug),
)]
pub enum LinkedList<T: Serialize> {
Empty,
Node {
val: T,
#[nibblecode(recursive)]
next: Box<Self>,
},
}
roundtrip(&LinkedList::Node {
val: 42i32,
next: Box::new(LinkedList::Node {
val: 100i32,
next: Box::new(LinkedList::Empty),
}),
});
}
}