diff --git a/Cargo.lock b/Cargo.lock index 7e754fd2d922a4..021f0bd9d2adf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9108,6 +9108,7 @@ dependencies = [ "serde", "serde_json", "smallvec", + "unty", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b37a233c2f65e3..9e9a888b80d261 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -474,6 +474,7 @@ tracing = "0.1.37" tracing-subscriber = "0.3.16" triomphe = { git = "https://github.com/sokra/triomphe", branch = "sokra/unstable" } unsize = "1.1.0" +unty = "0.0.4" url = "2.2.2" urlencoding = "2.1.2" uuid = "1.18.1" diff --git a/turbopack/crates/turbo-bincode/Cargo.toml b/turbopack/crates/turbo-bincode/Cargo.toml index abac7b2d57c910..30ebc25e0a6101 100644 --- a/turbopack/crates/turbo-bincode/Cargo.toml +++ b/turbopack/crates/turbo-bincode/Cargo.toml @@ -19,3 +19,4 @@ ringmap = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } smallvec = { workspace = true } +unty = { workspace = true } diff --git a/turbopack/crates/turbo-bincode/src/lib.rs b/turbopack/crates/turbo-bincode/src/lib.rs index 171d6e0e6eb94a..e9e9a2544db454 100644 --- a/turbopack/crates/turbo-bincode/src/lib.rs +++ b/turbopack/crates/turbo-bincode/src/lib.rs @@ -1,3 +1,6 @@ +#[doc(hidden)] +pub mod macro_helpers; + use std::ptr::copy_nonoverlapping; use ::smallvec::SmallVec; @@ -115,6 +118,57 @@ impl Reader for TurboBincodeReader<'_> { } } +/// Represents a type that can only be encoded with a [`TurboBincodeEncoder`]. +/// +/// All traits implementing this must also implement the more generic [`Encode`] trait, but they +/// should panic if any other encoder is used. +/// +/// Use [`impl_encode_for_turbo_bincode_encode`] to automatically implement the [`Encode`] trait +/// from this one. +pub trait TurboBincodeEncode: Encode { + fn encode(&self, encoder: &mut TurboBincodeEncoder) -> Result<(), EncodeError>; +} + +/// Represents a type that can only be decoded with a [`TurboBincodeDecoder`] and an empty `()` +/// context. +/// +/// All traits implementing this must also implement the more generic [`Decode`] trait, but they +/// should panic if any other encoder is used. +/// +/// Use [`impl_decode_for_turbo_bincode_decode`] to automatically implement the [`Decode`] trait +/// from this one. +pub trait TurboBincodeDecode: Decode { + fn decode(decoder: &mut TurboBincodeDecoder) -> Result; +} + +#[macro_export] +macro_rules! impl_encode_for_turbo_bincode_encode { + ($ty:ty) => { + impl $crate::macro_helpers::bincode::Encode for $ty { + fn encode<'a, E: $crate::macro_helpers::bincode::enc::Encoder>( + &self, + encoder: &'a mut E, + ) -> ::std::result::Result<(), $crate::macro_helpers::bincode::error::EncodeError> { + $crate::macro_helpers::encode_for_turbo_bincode_encode_impl(self, encoder) + } + } + }; +} + +#[macro_export] +macro_rules! impl_decode_for_turbo_bincode_decode { + ($ty:ty) => { + impl $crate::macro_helpers::bincode::Decode for $ty { + fn decode>( + decoder: &mut D, + ) -> ::std::result::Result + { + $crate::macro_helpers::decode_for_turbo_bincode_decode_impl(decoder) + } + } + }; +} + pub mod indexmap { use std::hash::{BuildHasher, Hash}; diff --git a/turbopack/crates/turbo-bincode/src/macro_helpers.rs b/turbopack/crates/turbo-bincode/src/macro_helpers.rs new file mode 100644 index 00000000000000..ad0c624f7e66ec --- /dev/null +++ b/turbopack/crates/turbo-bincode/src/macro_helpers.rs @@ -0,0 +1,62 @@ +use std::{any::type_name, mem::transmute}; + +pub use bincode; +use bincode::{ + de::Decoder, + enc::Encoder, + error::{DecodeError, EncodeError}, +}; + +use crate::{TurboBincodeDecode, TurboBincodeDecoder, TurboBincodeEncode, TurboBincodeEncoder}; + +#[track_caller] +pub fn encode_for_turbo_bincode_encode_impl<'a, T: TurboBincodeEncode, E: Encoder>( + value: &T, + encoder: &'a mut E, +) -> Result<(), EncodeError> { + let encoder = if unty::type_equal::() { + // SAFETY: Transmute is safe because `&mut E` is `&mut TurboBincodeEncoder`: + // - `unty::type_equal::()` does not check lifetimes, but does check + // the type and layout, so we know those are correct. + // - The transmuted encoder cannot escape this function, and we know that the lifetime of + // `'a` is at least as long as the function. + // - Lifetimes don't change layout. This is not strictly guaranteed, but if this assumption + // is broken, we'd have a different type id (type ids are derived from layout + // information), `type_equal` would return `false`, and we'd panic instead of violating + // memory safety. + // - Two mutable references have the same layout and alignment when they reference exactly + // the same type. + // - The explicit lifetime ('a) avoids creating an implitly unbounded lifetime. + unsafe { transmute::<&'a mut E, &'a mut TurboBincodeEncoder>(encoder) } + } else { + unreachable!( + "{} implements TurboBincodeEncode, but was called with a {} encoder implementation", + type_name::(), + type_name::(), + ) + }; + TurboBincodeEncode::encode(value, encoder) +} + +#[track_caller] +pub fn decode_for_turbo_bincode_decode_impl< + 'a, + Context, + T: TurboBincodeDecode, + D: Decoder, +>( + decoder: &'a mut D, +) -> Result { + let decoder = if unty::type_equal::() { + // SAFETY: See notes on the `Encode::encode` implementation on + // `encode_for_turbo_bincode_encode_impl`. + unsafe { transmute::<&'a mut D, &'a mut TurboBincodeDecoder<'a>>(decoder) } + } else { + unreachable!( + "{} implements TurboBincodeDecode, but was called with a {} decoder implementation", + type_name::(), + type_name::(), + ) + }; + TurboBincodeDecode::decode(decoder) +}