From f4cdd97ed02d9a41a7bf5d699ae827c2659dbd62 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Tue, 24 Sep 2024 21:47:31 -0700 Subject: [PATCH] [derive] Overhaul repr parsing Represent the result of parsing all `#[repr(...)]` attributes on a type as a high-level type which is only capable of representing valid combinations of `#[repr(...)]` attributes and processes them into a concise representation that's easier for high-level code to work with. This prepares us to more easily fix #1748 --- zerocopy-derive/src/lib.rs | 1 + zerocopy-derive/src/repr2.rs | 258 +++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 zerocopy-derive/src/repr2.rs diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index 60e37696342..24e0f923dc4 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -33,6 +33,7 @@ mod ext; #[cfg(test)] mod output_tests; mod repr; +mod repr2; use proc_macro2::{TokenStream, TokenTree}; use quote::ToTokens; diff --git a/zerocopy-derive/src/repr2.rs b/zerocopy-derive/src/repr2.rs new file mode 100644 index 00000000000..aacb919ed4b --- /dev/null +++ b/zerocopy-derive/src/repr2.rs @@ -0,0 +1,258 @@ +// Copyright 2019 The Fuchsia Authors +// +// Licensed under a BSD-style license , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +use core::{ + convert::{Infallible, TryFrom}, + num::NonZeroU64, +}; + +use syn::{DeriveInput, Error}; + +/// The computed representation of a type. +/// +/// This is the result of processing all `#[repr(...)]` attributes on a type, if +/// any. A `Repr` is only capable of representing legal combinations of +/// `#[repr(...)]` attributes. +enum Repr { + /// `#[repr(transparent)]` + Transparent, + /// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)` + /// optionally combined with `repr(packed(...))` or `repr(align(...))` + Compound(CompoundRepr, Option>), +} + +/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`. +enum CompoundRepr { + C, + Rust, + Primitive(Prim), +} + +/// `repr(Int)` +enum PrimitiveRepr { + U8, + U16, + U32, + U64, + Usize, + I8, + I16, + I32, + I64, + Isize, +} + +/// `repr(packed(...))` or `repr(align(...))` +enum AlignRepr { + Packed(Packed), + Align(NonZeroU64), +} + +/// The representations which can legally appear on a struct type. +type StructRepr = Repr; + +/// The representations which can legally appear on a union type. +type UnionRepr = Repr; + +/// The representations which can legally appear on an enum type. +type EnumRepr = Repr; + +/// The result of parsing a single `#[repr(...)]` attribute or a single +/// directive inside a compound `#[repr(..., ...)]` attribute. +#[derive(Copy, Clone, PartialEq, Eq)] +enum RawRepr { + Transparent, + C, + Rust, + U8, + U16, + U32, + U64, + Usize, + I8, + I16, + I32, + I64, + Isize, + Align(NonZeroU64), + PackedN(NonZeroU64), + Packed, +} + +/// The error from converting from a `RawRepr`. +enum FromRawReprError { + /// The `RawRepr` doesn't affect the high-level repr we're parsing (e.g. + /// it's `align(...)` and we're parsing a `CompoundRepr`). + None, + /// The `RawRepr` is invalid for the high-level repr we're parsing (e.g. + /// it's `packed` repr and we're parsing an `AlignRepr` for an enum type). + Err(E), +} + +impl From> for Error { + fn from(err: FromRawReprError) -> Error { + todo!() + } +} + +impl TryFrom for PrimitiveRepr { + type Error = (); + fn try_from(raw: RawRepr) -> Result { + use RawRepr::*; + match raw { + U8 => Ok(PrimitiveRepr::U8), + U16 => Ok(PrimitiveRepr::U16), + U32 => Ok(PrimitiveRepr::U32), + U64 => Ok(PrimitiveRepr::U64), + Usize => Ok(PrimitiveRepr::Usize), + I8 => Ok(PrimitiveRepr::I8), + I16 => Ok(PrimitiveRepr::I16), + I32 => Ok(PrimitiveRepr::I32), + I64 => Ok(PrimitiveRepr::I64), + Isize => Ok(PrimitiveRepr::Isize), + Transparent | C | Rust | Align(_) | PackedN(_) | Packed => Err(()), + } + } +} + +impl TryFrom for Infallible { + type Error = (); + fn try_from(raw: RawRepr) -> Result { + Err(()) + } +} + +impl> TryFrom for CompoundRepr { + type Error = FromRawReprError<()>; + fn try_from(raw: RawRepr) -> Result, FromRawReprError<()>> { + use RawRepr::*; + match raw { + C => Ok(CompoundRepr::C), + Rust => Ok(CompoundRepr::Rust), + raw @ (U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize) => { + Prim::try_from(raw) + .map(CompoundRepr::Primitive) + .map_err(|()| FromRawReprError::Err(())) + } + Transparent | Align(_) | PackedN(_) | Packed => Err(FromRawReprError::None), + } + } +} + +impl TryFrom for NonZeroU64 { + type Error = (); + fn try_from(raw: RawRepr) -> Result { + use RawRepr::*; + match raw { + Packed => Ok(NonZeroU64::MIN), + PackedN(n) => Ok(n), + U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | Transparent | C + | Rust | Align(_) => Err(()), + } + } +} + +impl> TryFrom for AlignRepr { + type Error = FromRawReprError<()>; + fn try_from(raw: RawRepr) -> Result, FromRawReprError<()>> { + use RawRepr::*; + match raw { + Packed | PackedN(_) => { + Pcked::try_from(raw).map(AlignRepr::Packed).map_err(|()| FromRawReprError::Err(())) + } + Align(n) => Ok(AlignRepr::Align(n)), + U8 | U16 | U32 | U64 | Usize | I8 | I16 | I32 | I64 | Isize | Transparent | C + | Rust => Err(FromRawReprError::None), + } + } +} + +/// The error from extracting a high-level repr type from a list of `RawRepr`s. +enum FromRawReprsError { + /// None of the `RawRepr`s affect the high-level repr we're parsing (e.g. + /// it's a single `align(...)` and we're parsing a `CompoundRepr`). + None, + /// One of the `RawRepr`s is invalid for the high-level repr we're parsing + /// (e.g. there's a `packed` repr and we're parsing an `AlignRepr` for an + /// enum type). + Single(E), + /// Two `RawRepr`s appear which both affect the high-level repr we're + /// parsing (e.g., the list is `#[repr(align(2), packed)]`). Note that we + /// conservatively treat redundant reprs as conflicting (e.g. + /// `#[repr(packed, packed)]`). + Conflict, +} + +impl From> for Error { + fn from(err: FromRawReprsError) -> Error { + todo!() + } +} + +/// Tries to extract a high-level repr from a list of `RawRepr`s. +fn try_from_raw_reprs<'a, E, R: TryFrom>>( + r: impl IntoIterator, +) -> Result, FromRawReprsError> { + // Walk the list of `RawRepr`s and attempt to convert each to an `R`. Bail + // if we find any errors. If we find more than one which converts to an `R`, + // bail with a `Conflict` error. + r.into_iter().try_fold(None, |found, raw| { + let new = match R::try_from(*raw) { + Ok(r) => r, + // This `RawRepr` doesn't convert to an `R`, so keep the current + // found `R`, if any. + Err(FromRawReprError::None) => return Ok(found), + // This repr is unsupported for the decorated type (e.g. + // `repr(packed)` on an enum). + Err(FromRawReprError::Err(err)) => return Err(FromRawReprsError::Single(err)), + }; + + if found.is_some() { + // We already found an `R`, but this `RawRepr` also converts to an + // `R`, so that's a conflict. + Err(FromRawReprsError::Conflict) + } else { + Ok(Some(new)) + } + }) +} + +impl Repr { + pub(crate) fn parse(input: &DeriveInput) -> Result, Error> + where + Prim: TryFrom, + Packed: TryFrom, + { + // TODO: Report errors on the correct spans + + // TODO: Parse `RawRepr`s from `input` + let raw_reprs: Vec = todo!(); + + let transparent = raw_reprs.contains(&RawRepr::Transparent); + let compound: Option> = try_from_raw_reprs(raw_reprs.iter())?; + let align: Option> = try_from_raw_reprs(raw_reprs.iter())?; + + if transparent { + if compound.is_some() || align.is_some() { + // TODO: Error: conflicting representation hints + todo!() + } + + Ok(Repr::Transparent) + } else { + Ok(Repr::Compound(compound.unwrap_or(CompoundRepr::Rust), align)) + } + } +} + +#[cfg(test)] +mod tests { + // TODO: Test `try_from_raw_reprs` aggressively and all of the trait impls + // that it depends on. Do this for every type parameter (NonZeroU64, + // PrimitiveRepr, and Infallible). +}