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..595260cbc97 --- /dev/null +++ b/zerocopy-derive/src/repr2.rs @@ -0,0 +1,332 @@ +// 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 proc_macro2::Span; +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(Span), + /// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)` + /// optionally combined with `repr(packed(...))` or `repr(align(...))` + Compound(Spanned>, 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, +} + +/// A value with an associated span. +#[derive(Copy, Clone)] +struct Spanned { + t: T, + span: Span, +} + +impl Spanned { + fn new(t: T, span: Span) -> Spanned { + Spanned { t, span } + } + + /// Delegates to `T: TryFrom`, preserving span information in both the + /// success and error cases. + fn try_from(u: Spanned) -> Result, FromRawReprError>> + where + T: TryFrom>, + { + let Spanned { t: u, span } = u; + T::try_from(u).map(|t| Spanned { t, span }).map_err(|err| match err { + FromRawReprError::None => FromRawReprError::None, + FromRawReprError::Err(e) => FromRawReprError::Err(Spanned::new(e, span)), + }) + } +} + +/// 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 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: Spanned>) -> 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>, Spanned>> { + // 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 Spanned::::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(Spanned { t: err, span })) => { + return Err(Spanned::new(FromRawReprsError::Single(err), span)) + } + }; + + if found.is_some() { + // We already found an `R`, but this `RawRepr` also converts to an + // `R`, so that's a conflict. + Err(Spanned::new(FromRawReprsError::Conflict, new.span)) + } else { + Ok(Some(new)) + } + }) +} + +// /// 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.iter().any(|Spanned { t, span: _ }| matches!(t, RawRepr::Transparent)); + + let transparent = { + let mut transparents = raw_reprs.iter().filter_map(|Spanned { t, span }| match t { + RawRepr::Transparent => Some(span), + _ => None, + }); + let first = transparents.next(); + let second = transparents.next(); + match (first, second) { + (None, None) => None, + (Some(span), None) => Some(*span), + (Some(_), Some(second)) => { + return Err(Spanned::new(FromRawReprsError::<()>::Conflict, *second).into()) + } + // An iterator can't produce a value only on the second call to + // `.next()`. + (None, Some(_)) => unreachable!(), + } + }; + let compound: Option>> = try_from_raw_reprs(raw_reprs.iter())?; + let align: Option>> = try_from_raw_reprs(raw_reprs.iter())?; + + if let Some(span) = transparent { + if compound.is_some() || align.is_some() { + // TODO: Error: conflicting representation hints + todo!() + } + + Ok(Repr::Transparent(span)) + } else { + Ok(Repr::Compound( + compound.unwrap_or(Spanned::new(CompoundRepr::Rust, Span::call_site())), + 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). +}