diff --git a/CHANGELOG.md b/CHANGELOG.md index 3744876..539b277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 6.2.1 (api=1.1.0, abi=1.0.0) +- Add support for `#[stabby::stabby(version=10, module="my::module")]` to let you change the values in those fields without having to implement the whole trait yourself. +- Add support for `serde` through the `serde` feature flag. +- Add conversions between `std` and `stabby` `String`s. +- Fix an issue where optimized layout checks would prevent compilation due to missing trait bounds. +- Fix estimation of `IStable::CType` for arrays. + # 6.1.1 (api=1.0.0, abi=1.0.0) - Add support for multi-fields variants in `repr(C, u*)` enums. - Deprecate support for `repr(C)` by deprecating any enum that uses it without also specifying a determinant size. diff --git a/Cargo.toml b/Cargo.toml index f64fa8b..80aeef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,12 +33,12 @@ license = " EPL-2.0 OR Apache-2.0" categories = ["development-tools::ffi", "no-std::no-alloc"] repository = "https://github.com/ZettaScaleLabs/stabby" readme = "stabby/README.md" -version = "6.1.1" # Track +version = "6.2.1" # Track [workspace.dependencies] -stabby-macros = { path = "./stabby-macros/", version = "6.1.1", default-features = false } # Track -stabby-abi = { path = "./stabby-abi/", version = "6.1.1", default-features = false } # Track -stabby = { path = "./stabby/", version = "6.1.1", default-features = false } # Track +stabby-macros = { path = "./stabby-macros/", version = "6.2.1", default-features = false } # Track +stabby-abi = { path = "./stabby-abi/", version = "6.2.1", default-features = false } # Track +stabby = { path = "./stabby/", version = "6.2.1", default-features = false } # Track abi_stable = "0.11.2" criterion = "0.5.1" @@ -51,5 +51,6 @@ quote = "1.0" rand = "0.8.5" rustversion = "1.0" sha2-const-stable = "0.1.0" +serde = "1.0.203" smol = "2.0.0" syn = "1.0" diff --git a/stabby-abi/Cargo.toml b/stabby-abi/Cargo.toml index e07f300..0c74148 100644 --- a/stabby-abi/Cargo.toml +++ b/stabby-abi/Cargo.toml @@ -28,6 +28,7 @@ default = ["std"] std = ["libc"] libc = ["dep:libc"] test = [] +serde = ["dep:serde"] abi_stable = ["dep:abi_stable"] abi_stable-channels = ["abi_stable", "abi_stable/channels"] @@ -44,6 +45,7 @@ stabby-macros.workspace = true abi_stable = { workspace = true, optional = true } libc = { workspace = true, optional = true } rustversion = { workspace = true } +serde = { workspace = true, optional = true, features = ["derive"] } sha2-const-stable = { workspace = true } [dev-dependencies] diff --git a/stabby-abi/src/alloc/boxed.rs b/stabby-abi/src/alloc/boxed.rs index b67d551..d2f914a 100644 --- a/stabby-abi/src/alloc/boxed.rs +++ b/stabby-abi/src/alloc/boxed.rs @@ -284,6 +284,18 @@ impl BoxedSlice { (slice, capacity, alloc) } } +impl core::ops::Deref for BoxedSlice { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl core::ops::DerefMut for BoxedSlice { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_slice_mut() + } +} impl Eq for BoxedSlice {} impl PartialEq for BoxedSlice { fn eq(&self, other: &Self) -> bool { @@ -421,3 +433,44 @@ impl IntoIterator for BoxedSlice { } } pub use super::string::BoxedStr; + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use crate::alloc::IAlloc; + use serde::{Deserialize, Serialize}; + impl Serialize for BoxedSlice { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &[T] = self; + slice.serialize(serializer) + } + } + impl<'a, T: Deserialize<'a>, Alloc: IAlloc + Default> Deserialize<'a> for BoxedSlice { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + crate::alloc::vec::Vec::deserialize(deserializer).map(Into::into) + } + } + impl Serialize for BoxedStr { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &str = self; + slice.serialize(serializer) + } + } + impl<'a, Alloc: IAlloc + Default> Deserialize<'a> for BoxedStr { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + crate::alloc::string::String::deserialize(deserializer).map(Into::into) + } + } +} diff --git a/stabby-abi/src/alloc/string.rs b/stabby-abi/src/alloc/string.rs index 47cdfd6..a0597b5 100644 --- a/stabby-abi/src/alloc/string.rs +++ b/stabby-abi/src/alloc/string.rs @@ -135,6 +135,12 @@ impl From<&str> for String { } } +impl From> for String { + fn from(value: crate::str::Str<'_>) -> Self { + Self::default() + value.as_ref() + } +} + /// A reference counted boxed string. #[crate::stabby] pub struct ArcStr { @@ -339,3 +345,42 @@ impl core::fmt::Write for String { self.try_concat(s).map_err(|_| core::fmt::Error) } } + +#[cfg(feature = "std")] +mod std_impl { + use crate::alloc::IAlloc; + impl From for crate::alloc::string::String { + fn from(value: std::string::String) -> Self { + Self::from(value.as_ref()) + } + } + impl From> for std::string::String { + fn from(value: crate::alloc::string::String) -> Self { + Self::from(value.as_ref()) + } + } +} + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use crate::alloc::IAlloc; + use serde::{Deserialize, Serialize}; + impl Serialize for String { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &str = self; + slice.serialize(serializer) + } + } + impl<'a, Alloc: IAlloc + Default> Deserialize<'a> for String { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + crate::str::Str::deserialize(deserializer).map(Into::into) + } + } +} diff --git a/stabby-abi/src/alloc/sync.rs b/stabby-abi/src/alloc/sync.rs index 7140a15..21b3620 100644 --- a/stabby-abi/src/alloc/sync.rs +++ b/stabby-abi/src/alloc/sync.rs @@ -855,3 +855,44 @@ impl AtomicArc { } } } + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use crate::alloc::IAlloc; + use serde::{Deserialize, Serialize}; + impl Serialize for ArcSlice { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &[T] = self; + slice.serialize(serializer) + } + } + impl<'a, T: Deserialize<'a>, Alloc: IAlloc + Default> Deserialize<'a> for ArcSlice { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + crate::alloc::vec::Vec::deserialize(deserializer).map(Into::into) + } + } + impl Serialize for ArcStr { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &str = self; + slice.serialize(serializer) + } + } + impl<'a, Alloc: IAlloc + Default> Deserialize<'a> for ArcStr { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + crate::alloc::string::String::deserialize(deserializer).map(Into::into) + } + } +} diff --git a/stabby-abi/src/alloc/vec.rs b/stabby-abi/src/alloc/vec.rs index 8e925bf..fc01145 100644 --- a/stabby-abi/src/alloc/vec.rs +++ b/stabby-abi/src/alloc/vec.rs @@ -818,3 +818,44 @@ fn test() { } pub use super::single_or_vec::SingleOrVec; + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use crate::alloc::IAlloc; + use serde::{de::Visitor, Deserialize, Serialize}; + impl Serialize for Vec { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &[T] = self; + slice.serialize(serializer) + } + } + impl<'a, T: Deserialize<'a>, Alloc: IAlloc + Default> Deserialize<'a> for Vec { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_seq(VecVisitor(core::marker::PhantomData)) + } + } + pub struct VecVisitor(core::marker::PhantomData<(T, Alloc)>); + impl<'a, T: Deserialize<'a>, Alloc: IAlloc + Default> Visitor<'a> for VecVisitor { + type Value = Vec; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("A sequence") + } + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'a>, + { + let mut this = Vec::with_capacity_in(seq.size_hint().unwrap_or(0), Alloc::default()); + while let Some(v) = seq.next_element()? { + this.push(v); + } + Ok(this) + } + } +} diff --git a/stabby-abi/src/istable.rs b/stabby-abi/src/istable.rs index ab1c59f..a3c4b77 100644 --- a/stabby-abi/src/istable.rs +++ b/stabby-abi/src/istable.rs @@ -513,7 +513,7 @@ impl IncludesComputer<(O1, T1, unsafe impl IStable for Union { type ForbiddenValues = End; type UnusedBits = End; - type Size = ::Max; + type Size = <::Max as Unsigned>::NextMultipleOf; type Align = ::Max; type HasExactlyOneNiche = B0; type ContainsIndirections = ::Or; diff --git a/stabby-abi/src/num.rs b/stabby-abi/src/num.rs index bf365cf..d8d9ed3 100644 --- a/stabby-abi/src/num.rs +++ b/stabby-abi/src/num.rs @@ -1,10 +1,25 @@ +/// Returned when using [`core::convert::TryInto`] on the illegal value of a restricted integer. +#[crate::stabby] +#[derive(Clone, Copy, Default, Debug, Ord, PartialEq, PartialOrd, Eq, Hash)] +pub struct IllegalValue; +impl core::fmt::Display for IllegalValue { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self, f) + } +} +#[cfg(feature = "std")] +impl std::error::Error for IllegalValue {} + macro_rules! define_non_max { - ($NonMaxU8:ident: $u8: ty = $NonZeroU8: ty ) => { + ($NonMaxU8:ident: $u8: ty = $NonZeroU8: ty; $u8s: literal ) => { /// A number whose bit pattern is guaranteed not to be only 1s. /// /// `x` is stored as `NonZero(!x)`, so transmuting results in wrong values. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(into = $u8s))] + #[cfg_attr(feature = "serde", serde(try_from = $u8s))] pub struct $NonMaxU8 { inner: $NonZeroU8, } @@ -36,9 +51,9 @@ macro_rules! define_non_max { } } impl TryFrom<$u8> for $NonMaxU8 { - type Error = (); + type Error = IllegalValue; fn try_from(value: $u8) -> Result { - Self::new(value).ok_or(()) + Self::new(value).ok_or(IllegalValue) } } impl PartialOrd for $NonMaxU8 { @@ -76,12 +91,15 @@ macro_rules! define_non_max { }; } macro_rules! define_non_x { - ($NonMaxU8:ident: $u8: ty = $NonZeroU8: ty ) => { + ($NonMaxU8:ident: $u8: ty = $NonZeroU8: ty; $u8s: literal ) => { /// A number whose value is guaranteed not to be `FORBIDDEN`. /// /// `x` is stored as `NonZero(x.wrapping_sub(FORBIDDEN))`, so transmuting results in wrong values. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(into = $u8s))] + #[cfg_attr(feature = "serde", serde(try_from = $u8s))] pub struct $NonMaxU8 { inner: $NonZeroU8, } @@ -113,9 +131,9 @@ macro_rules! define_non_x { } } impl TryFrom<$u8> for $NonMaxU8<{ FORBIDDEN }> { - type Error = (); + type Error = IllegalValue; fn try_from(value: $u8) -> Result { - Self::new(value).ok_or(()) + Self::new(value).ok_or(IllegalValue) } } impl PartialOrd for $NonMaxU8<{ FORBIDDEN }> { @@ -153,25 +171,25 @@ macro_rules! define_non_x { }; } -define_non_max!(NonMaxU8: u8 = core::num::NonZeroU8); -define_non_max!(NonMaxU16: u16 = core::num::NonZeroU16); -define_non_max!(NonMaxU32: u32 = core::num::NonZeroU32); -define_non_max!(NonMaxU64: u64 = core::num::NonZeroU64); -define_non_max!(NonMaxU128: u128 = core::num::NonZeroU128); -define_non_max!(NonMaxUsize: usize = core::num::NonZeroUsize); +define_non_max!(NonMaxU8: u8 = core::num::NonZeroU8; "u8"); +define_non_max!(NonMaxU16: u16 = core::num::NonZeroU16; "u16"); +define_non_max!(NonMaxU32: u32 = core::num::NonZeroU32; "u32"); +define_non_max!(NonMaxU64: u64 = core::num::NonZeroU64; "u64"); +define_non_max!(NonMaxU128: u128 = core::num::NonZeroU128; "u128"); +define_non_max!(NonMaxUsize: usize = core::num::NonZeroUsize; "usize"); -define_non_x!(NonXU8: u8 = core::num::NonZeroU8); -define_non_x!(NonXU16: u16 = core::num::NonZeroU16); -define_non_x!(NonXU32: u32 = core::num::NonZeroU32); -define_non_x!(NonXU64: u64 = core::num::NonZeroU64); -define_non_x!(NonXU128: u128 = core::num::NonZeroU128); -define_non_x!(NonXUsize: usize = core::num::NonZeroUsize); -define_non_x!(NonXI8: i8 = core::num::NonZeroI8); -define_non_x!(NonXI16: i16 = core::num::NonZeroI16); -define_non_x!(NonXI32: i32 = core::num::NonZeroI32); -define_non_x!(NonXI64: i64 = core::num::NonZeroI64); -define_non_x!(NonXI128: i128 = core::num::NonZeroI128); -define_non_x!(NonXIsize: isize = core::num::NonZeroIsize); +define_non_x!(NonXU8: u8 = core::num::NonZeroU8; "u8"); +define_non_x!(NonXU16: u16 = core::num::NonZeroU16; "u16"); +define_non_x!(NonXU32: u32 = core::num::NonZeroU32; "u32"); +define_non_x!(NonXU64: u64 = core::num::NonZeroU64; "u64"); +define_non_x!(NonXU128: u128 = core::num::NonZeroU128; "u128"); +define_non_x!(NonXUsize: usize = core::num::NonZeroUsize; "usize"); +define_non_x!(NonXI8: i8 = core::num::NonZeroI8; "i8"); +define_non_x!(NonXI16: i16 = core::num::NonZeroI16; "i16"); +define_non_x!(NonXI32: i32 = core::num::NonZeroI32; "i32"); +define_non_x!(NonXI64: i64 = core::num::NonZeroI64; "i64"); +define_non_x!(NonXI128: i128 = core::num::NonZeroI128; "i128"); +define_non_x!(NonXIsize: isize = core::num::NonZeroIsize; "isize"); macro_rules! makeutest { ($u8: ident, $NonMaxU8: ident, $NonXU8: ident) => { diff --git a/stabby-abi/src/option.rs b/stabby-abi/src/option.rs index 9ee87a5..408561a 100644 --- a/stabby-abi/src/option.rs +++ b/stabby-abi/src/option.rs @@ -179,3 +179,32 @@ where self.unwrap_or_else(|| panic!("Option::unwrap called on None")) } } + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use serde::{Deserialize, Serialize}; + impl Serialize for Option + where + Ok: IDeterminantProvider<()>, + { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: serde::Serializer, + { + let this = self.as_ref(); + this.serialize(serializer) + } + } + impl<'a, Ok: IDeterminantProvider<()>> Deserialize<'a> for Option + where + core::option::Option: Deserialize<'a>, + { + fn deserialize(deserializer: D) -> core::result::Result + where + D: serde::Deserializer<'a>, + { + Ok(core::option::Option::::deserialize(deserializer)?.into()) + } + } +} diff --git a/stabby-abi/src/result.rs b/stabby-abi/src/result.rs index 3b6340f..f11e7a5 100644 --- a/stabby-abi/src/result.rs +++ b/stabby-abi/src/result.rs @@ -549,3 +549,33 @@ where } } } + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use serde::{Deserialize, Serialize}; + impl Serialize for Result + where + Ok: IDeterminantProvider, + Err: IStable, + { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: serde::Serializer, + { + let this: core::result::Result<_, _> = self.as_ref(); + this.serialize(serializer) + } + } + impl<'a, Ok: IDeterminantProvider, Err: IStable> Deserialize<'a> for Result + where + core::result::Result: Deserialize<'a>, + { + fn deserialize(deserializer: D) -> core::result::Result + where + D: serde::Deserializer<'a>, + { + Ok(core::result::Result::::deserialize(deserializer)?.into()) + } + } +} diff --git a/stabby-abi/src/slice.rs b/stabby-abi/src/slice.rs index 6e1f53e..1b0bb2b 100644 --- a/stabby-abi/src/slice.rs +++ b/stabby-abi/src/slice.rs @@ -195,3 +195,48 @@ impl<'a, T> From> for &'a [T] { unsafe { core::slice::from_raw_parts(value.start.as_mut(), value.len) } } } + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use serde::{de::Visitor, Deserialize, Serialize}; + impl<'a, T: Serialize> Serialize for Slice<'a, T> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &[T] = self; + slice.serialize(serializer) + } + } + impl<'a, T: Serialize> Serialize for SliceMut<'a, T> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let slice: &[T] = self; + slice.serialize(serializer) + } + } + impl<'a> Deserialize<'a> for Slice<'a, u8> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_bytes(BytesVisitor(core::marker::PhantomData)) + } + } + struct BytesVisitor<'a>(core::marker::PhantomData>); + impl<'a> Visitor<'a> for BytesVisitor<'a> { + type Value = Slice<'a, u8>; + fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result + where + E: serde::de::Error, + { + Ok(v.into()) + } + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "A borrowed_str") + } + } +} diff --git a/stabby-abi/src/stable_impls/mod.rs b/stabby-abi/src/stable_impls/mod.rs index e8f15e4..a3e7ee1 100644 --- a/stabby-abi/src/stable_impls/mod.rs +++ b/stabby-abi/src/stable_impls/mod.rs @@ -731,7 +731,7 @@ macro_rules! sliceimpl { <<$size as Unsigned>::Equal as Bit>::SaddTernary, >; type ContainsIndirections = T::ContainsIndirections; - type CType = T::CType; + type CType = [T::CType; <$size as Unsigned>::USIZE]; primitive_report!(ARRAY_NAME[<$size as Unsigned>::USIZE], T); } }; diff --git a/stabby-abi/src/str.rs b/stabby-abi/src/str.rs index 4705e41..47f9b77 100644 --- a/stabby-abi/src/str.rs +++ b/stabby-abi/src/str.rs @@ -51,6 +51,11 @@ impl<'a> From> for &'a str { unsafe { core::str::from_utf8_unchecked(value.inner.into()) } } } +impl AsRef for Str<'_> { + fn as_ref(&self) -> &str { + self + } +} impl<'a> Deref for Str<'a> { type Target = str; fn deref(&self) -> &Self::Target { @@ -78,6 +83,11 @@ impl core::cmp::PartialOrd for Str<'_> { pub struct StrMut<'a> { pub(crate) inner: crate::slice::SliceMut<'a, u8>, } +impl AsRef for StrMut<'_> { + fn as_ref(&self) -> &str { + self + } +} impl<'a> Deref for StrMut<'a> { type Target = str; fn deref(&self) -> &Self::Target { @@ -114,3 +124,46 @@ impl<'a> From> for &'a str { unsafe { core::str::from_utf8_unchecked(value.inner.into()) } } } + +#[cfg(feature = "serde")] +mod serde_impl { + use super::*; + use serde::{de::Visitor, Deserialize, Serialize}; + impl<'a> Serialize for Str<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self) + } + } + impl<'a> Serialize for StrMut<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self) + } + } + impl<'a> Deserialize<'a> for Str<'a> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + deserializer.deserialize_str(StrVisitor(core::marker::PhantomData)) + } + } + struct StrVisitor<'a>(core::marker::PhantomData>); + impl<'a> Visitor<'a> for StrVisitor<'a> { + type Value = Str<'a>; + fn visit_borrowed_str(self, v: &'a str) -> Result + where + E: serde::de::Error, + { + Ok(v.into()) + } + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "A borrowed_str") + } + } +} diff --git a/stabby-macros/src/enums.rs b/stabby-macros/src/enums.rs index 399404c..afb8afb 100644 --- a/stabby-macros/src/enums.rs +++ b/stabby-macros/src/enums.rs @@ -100,14 +100,50 @@ impl syn::parse::Parse for FullRepr { } } +struct Args { + version: u32, + module: proc_macro2::TokenStream, +} +impl syn::parse::Parse for Args { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut this = Args { + version: 0, + module: quote!(), + }; + while !input.is_empty() { + let ident: Ident = input.parse()?; + match ident.to_string().as_str() { + "version" => { + input.parse::()?; + this.version = input.parse::()?.to_string().parse().unwrap(); + } + "module" => { + input.parse::()?; + while !input.is_empty() { + if input.peek(syn::Token!(,)) { + break; + } + let token: proc_macro2::TokenTree = input.parse()?; + this.module.extend(Some(token)) + } + } + _ => return Err(input.error("Unknown stabby attribute {ident}")), + } + _ = input.parse::(); + } + Ok(this) + } +} pub fn stabby( attrs: Vec, vis: Visibility, ident: Ident, generics: Generics, data: DataEnum, + stabby_attrs: &proc_macro::TokenStream, ) -> TokenStream { let st = crate::tl_mod(); + let Args { version, module } = syn::parse(stabby_attrs.clone()).unwrap(); let unbound_generics = &generics.params; let mut repr: Option = None; let repr_ident = quote::format_ident!("repr"); @@ -139,12 +175,13 @@ pub fn stabby( let DataEnum { variants, .. } = &data; let mut has_non_empty_fields = false; let unit = syn::parse2(quote!(())).unwrap(); - let mut report = crate::Report::r#enum(ident.to_string(), 0); + let mut report = crate::Report::r#enum(ident.to_string(), version, module.clone()); for variant in variants { match &variant.fields { syn::Fields::Named(f) if matches!(repr, Some(FullRepr { is_c: true, .. })) => { has_non_empty_fields = true; - let mut variant_report = crate::Report::r#struct(variant.ident.to_string(), 0); + let mut variant_report = + crate::Report::r#struct(variant.ident.to_string(), version, module.clone()); let mut variant_layout = quote!(()); for f in &f.named { let ty = &f.ty; @@ -161,7 +198,8 @@ pub fn stabby( syn::Fields::Unnamed(f) => { if f.unnamed.len() != 1 && matches!(repr, Some(FullRepr { is_c: true, .. })) { has_non_empty_fields = true; - let mut variant_report = crate::Report::r#struct(variant.ident.to_string(), 0); + let mut variant_report = + crate::Report::r#struct(variant.ident.to_string(), version, module.clone()); let mut variant_layout = quote!(()); for (n, f) in f.unnamed.iter().enumerate() { let ty = &f.ty; @@ -267,8 +305,7 @@ pub fn stabby( type CType = #ctype; const REPORT: &'static #st::report::TypeReport = & #report; const ID: u64 ={ - if (<::Size as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Size as #st::Unsigned>::USIZE) - || (<::Align as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Align as #st::Unsigned>::USIZE) { + if core::mem::size_of::() != core::mem::size_of::<::CType>() || core::mem::align_of::() != core::mem::align_of::<::CType>() { panic!(#reprc_bug) } if core::mem::size_of::() != <::Size as #st::Unsigned>::USIZE { diff --git a/stabby-macros/src/lib.rs b/stabby-macros/src/lib.rs index 560468a..993e5fc 100644 --- a/stabby-macros/src/lib.rs +++ b/stabby-macros/src/lib.rs @@ -86,8 +86,12 @@ pub fn stabby(stabby_attrs: TokenStream, tokens: TokenStream) -> TokenStream { syn::Data::Struct(data) => { structs::stabby(attrs, vis, ident, generics, data, &stabby_attrs) } - syn::Data::Enum(data) => enums::stabby(attrs, vis, ident, generics, data), - syn::Data::Union(data) => unions::stabby(attrs, vis, ident, generics, data), + syn::Data::Enum(data) => { + enums::stabby(attrs, vis, ident, generics, data, &stabby_attrs) + } + syn::Data::Union(data) => { + unions::stabby(attrs, vis, ident, generics, data, &stabby_attrs) + } } } else if let Ok(fn_spec) = syn::parse(tokens.clone()) { functions::stabby(syn::parse(stabby_attrs).unwrap(), fn_spec) @@ -294,25 +298,57 @@ pub(crate) struct Report<'a> { name: String, fields: Vec<(String, Type<'a>)>, version: u32, + module: proc_macro2::TokenStream, pub tyty: Tyty, } impl<'a> Report<'a> { - pub fn r#struct(name: impl Into, version: u32) -> Self { + pub fn r#struct( + name: impl Into, + version: u32, + module: proc_macro2::TokenStream, + ) -> Self { Self { name: name.into(), fields: Vec::new(), version, + module: if module.is_empty() { + quote!(::core::module_path!()) + } else { + module + }, tyty: Tyty::Struct, } } - pub fn r#enum(name: impl Into, version: u32) -> Self { + pub fn r#enum(name: impl Into, version: u32, module: proc_macro2::TokenStream) -> Self { Self { name: name.into(), fields: Vec::new(), version, + module: if module.is_empty() { + quote!(::core::module_path!()) + } else { + module + }, tyty: Tyty::Enum(enums::Repr::Stabby), } } + pub fn r#union( + name: impl Into, + version: u32, + module: proc_macro2::TokenStream, + ) -> Self { + Self { + name: name.into(), + fields: Vec::new(), + version, + module: if module.is_empty() { + quote!(::core::module_path!()) + } else { + module + }, + tyty: Tyty::Union, + } + } pub fn add_field(&mut self, name: String, ty: impl Into>) { self.fields.push((name, ty.into())); } @@ -390,6 +426,7 @@ impl ToTokens for Report<'_> { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let st = crate::tl_mod(); let mut fields = quote!(None); + for (name, ty) in &self.fields { fields = match ty { Type::Syn(ty) => quote! { @@ -412,11 +449,12 @@ impl ToTokens for Report<'_> { name, version, tyty, + module, .. } = self; tokens.extend(quote!(#st::report::TypeReport { name: #st::str::Str::new(#name), - module: #st::str::Str::new(core::module_path!()), + module: #st::str::Str::new(#module), fields: unsafe{#st::StableLike::new(#fields)}, version: #version, tyty: #tyty, @@ -424,28 +462,6 @@ impl ToTokens for Report<'_> { } } -pub(crate) fn report( - fields: &[(String, &syn::Type)], -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let st = crate::tl_mod(); - let mut report_bounds = quote!(); - let mut report = quote!(None); - let mut bounded_types = HashSet::new(); - for (name, ty) in fields.iter().rev() { - if bounded_types.insert(*ty) { - report_bounds = quote!(#ty: #st::IStable, #report_bounds); - } - report = quote! { - Some(& #st::report::FieldReport { - name: #st::str::Str::new(#name), - ty: <#ty as #st::IStable>::REPORT, - next_field: #st::StableLike::new(#report) - }) - }; - } - (report, report_bounds) -} - #[proc_macro_attribute] pub fn export(attrs: TokenStream, fn_spec: TokenStream) -> TokenStream { crate::functions::export(attrs, syn::parse(fn_spec).unwrap()).into() diff --git a/stabby-macros/src/structs.rs b/stabby-macros/src/structs.rs index 6c6fb5e..54c5aee 100644 --- a/stabby-macros/src/structs.rs +++ b/stabby-macros/src/structs.rs @@ -16,6 +16,44 @@ use proc_macro2::Ident; use quote::quote; use syn::{Attribute, DataStruct, Generics, Visibility}; +struct Args { + optimize: bool, + version: u32, + module: proc_macro2::TokenStream, +} +impl syn::parse::Parse for Args { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut this = Args { + optimize: true, + version: 0, + module: quote!(), + }; + while !input.is_empty() { + let ident: Ident = input.parse()?; + match ident.to_string().as_str() { + "no_opt" => this.optimize = false, + "version" => { + input.parse::()?; + this.version = input.parse::()?.to_string().parse().unwrap(); + } + "module" => { + input.parse::()?; + while !input.is_empty() { + if input.peek(syn::Token!(,)) { + break; + } + let token: proc_macro2::TokenTree = input.parse()?; + this.module.extend(Some(token)) + } + } + _ => return Err(input.error("Unknown stabby attribute {ident}")), + } + _ = input.parse::(); + } + Ok(this) + } +} + pub fn stabby( attrs: Vec, vis: Visibility, @@ -26,19 +64,19 @@ pub fn stabby( }: DataStruct, stabby_attrs: &proc_macro::TokenStream, ) -> proc_macro2::TokenStream { - let mut opt = match stabby_attrs.to_string().as_str() { - "no_opt" => false, - "" => true, - _ => panic!("Unkown stabby attributes {stabby_attrs}"), - }; - opt &= generics.params.is_empty(); + let Args { + mut optimize, + version, + module, + } = syn::parse(stabby_attrs.clone()).unwrap(); + optimize &= generics.params.is_empty(); let st = crate::tl_mod(); let unbound_generics = crate::utils::unbound_generics(&generics.params); let generics_without_defaults = crate::utils::generics_without_defaults(&generics.params); let where_clause = &generics.where_clause; let clauses = where_clause.as_ref().map(|w| &w.predicates); let mut layout = None; - let mut report = crate::Report::r#struct(ident.to_string(), 0); + let mut report = crate::Report::r#struct(ident.to_string(), version, module); let struct_code = match &fields { syn::Fields::Named(fields) => { let fields = &fields.named; @@ -90,10 +128,10 @@ pub fn stabby( let align_bug = format!( "{ident}'s align was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" ); - // let reprc_bug = format!( - // "{ident}'s CType was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" - // ); - let assertion = opt.then(|| { + let reprc_bug = format!( + "{ident}'s CType was mis-evaluated by stabby, this is definitely a bug and may cause UB, please file an issue" + ); + let assertion = optimize.then(|| { let sub_optimal_message = format!( "{ident}'s layout is sub-optimal, reorder fields or use `#[stabby::stabby(no_opt)]`" ); @@ -122,10 +160,9 @@ pub fn stabby( type CType = #ctype; const REPORT: &'static #st::report::TypeReport = &#report; const ID: u64 = { - // if (<::Size as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Size as #st::Unsigned>::USIZE) - // || (<::Align as #st::Unsigned>::USIZE != <<::CType as #st::IStable>::Align as #st::Unsigned>::USIZE) { - // panic!(#reprc_bug) - // } + if core::mem::size_of::() != core::mem::size_of::<::CType>() || core::mem::align_of::() != core::mem::align_of::<::CType>() { + panic!(#reprc_bug) + } if core::mem::size_of::() != <::Size as #st::Unsigned>::USIZE { panic!(#size_bug) } @@ -138,7 +175,7 @@ pub fn stabby( #[allow(dead_code, missing_docs)] struct #opt_id #generics #where_clause #fields #semi_token #assertion - impl < #generics_without_defaults > #ident <#unbound_generics> #where_clause { + impl < #generics_without_defaults > #ident <#unbound_generics> where #layout: #st::IStable, #report_bounds #clauses { #[doc = #optdoc] pub const fn has_optimal_layout() -> bool { core::mem::size_of::() <= core::mem::size_of::<#opt_id<#unbound_generics>>() diff --git a/stabby-macros/src/unions.rs b/stabby-macros/src/unions.rs index 90bc2aa..9588d6a 100644 --- a/stabby-macros/src/unions.rs +++ b/stabby-macros/src/unions.rs @@ -15,28 +15,64 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{Attribute, DataUnion, Generics, Ident, Visibility}; + +struct Args { + version: u32, + module: proc_macro2::TokenStream, +} +impl syn::parse::Parse for Args { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut this = Args { + version: 0, + module: quote!(), + }; + while !input.is_empty() { + let ident: Ident = input.parse()?; + match ident.to_string().as_str() { + "version" => { + input.parse::()?; + this.version = input.parse::()?.to_string().parse().unwrap(); + } + "module" => { + input.parse::()?; + while !input.is_empty() { + if input.peek(syn::Token!(,)) { + break; + } + let token: proc_macro2::TokenTree = input.parse()?; + this.module.extend(Some(token)) + } + } + _ => return Err(input.error("Unknown stabby attribute {ident}")), + } + _ = input.parse::(); + } + Ok(this) + } +} pub fn stabby( attrs: Vec, vis: Visibility, ident: Ident, generics: Generics, data: DataUnion, + stabby_attrs: &proc_macro::TokenStream, ) -> TokenStream { let st = crate::tl_mod(); let DataUnion { union_token: _, fields, } = &data; + let Args { version, module } = syn::parse(stabby_attrs.clone()).unwrap(); let unbound_generics = &generics.params; let mut layout = quote!(()); - let mut report = Vec::new(); + let mut report = crate::Report::r#union(ident.to_string(), version, module); for field in &fields.named { let ty = &field.ty; layout = quote!(#st::Union<#layout, #ty>); - report.push((field.ident.as_ref().unwrap().to_string(), ty)); + report.add_field(field.ident.as_ref().unwrap().to_string(), ty); } - let sident = format!("{ident}"); - let (report, report_bounds) = crate::report(&report); + let report_bounds = report.bounds(); quote! { #(#attrs)* #[repr(C)] @@ -52,13 +88,7 @@ pub fn stabby( type HasExactlyOneNiche = #st::B0; type ContainsIndirections = <#layout as #st::IStable>::ContainsIndirections; type CType = <#layout as #st::IStable>::CType; - const REPORT: &'static #st::report::TypeReport = & #st::report::TypeReport { - name: #st::str::Str::new(#sident), - module: #st::str::Str::new(core::module_path!()), - fields: unsafe{#st::StableLike::new(#report)}, - version: 0, - tyty: #st::report::TyTy::Struct, - }; + const REPORT: &'static #st::report::TypeReport = & #report; const ID: u64 = #st::report::gen_id(Self::REPORT); } } diff --git a/stabby/Cargo.toml b/stabby/Cargo.toml index d25469b..2f8e4fa 100644 --- a/stabby/Cargo.toml +++ b/stabby/Cargo.toml @@ -24,13 +24,14 @@ readme = { workspace = true } description = "A Stable ABI for Rust with compact sum-types." [features] -default = ["std", "libc"] +default = ["std", "libc", "serde"] std = ["libc", "stabby-abi/std"] libloading = ["dep:libloading", "std"] libc = ["stabby-abi/libc"] +serde = ["stabby-abi/serde"] [dependencies] -stabby-abi = { workspace = true } +stabby-abi = { workspace = true, default-features = false } lazy_static = { workspace = true } libloading = { workspace = true, optional = true }