-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
2 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,375 @@ | ||
// Copyright 2019 The Fuchsia Authors | ||
// | ||
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0 | ||
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT | ||
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, 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::{ | ||
punctuated::Punctuated, spanned::Spanned as _, token::Comma, Attribute, DeriveInput, Error, | ||
LitInt, Meta, | ||
}; | ||
|
||
/// 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<Prim, Packed> { | ||
/// `#[repr(transparent)]` | ||
Transparent(Span), | ||
/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)` | ||
/// optionally combined with `repr(packed(...))` or `repr(align(...))` | ||
Compound(Spanned<CompoundRepr<Prim>>, Option<Spanned<AlignRepr<Packed>>>), | ||
} | ||
|
||
/// A compound representation: `repr(C)`, `repr(Rust)`, or `repr(Int)`. | ||
enum CompoundRepr<Prim> { | ||
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(Packed), | ||
Align(NonZeroU64), | ||
} | ||
|
||
/// The representations which can legally appear on a struct type. | ||
type StructRepr = Repr<Infallible, NonZeroU64>; | ||
|
||
/// The representations which can legally appear on a union type. | ||
type UnionRepr = Repr<Infallible, NonZeroU64>; | ||
|
||
/// The representations which can legally appear on an enum type. | ||
type EnumRepr = Repr<PrimitiveRepr, Infallible>; | ||
|
||
/// 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: T, | ||
span: Span, | ||
} | ||
|
||
impl<T> Spanned<T> { | ||
fn new(t: T, span: Span) -> Spanned<T> { | ||
Spanned { t, span } | ||
} | ||
|
||
/// Delegates to `T: TryFrom`, preserving span information in both the | ||
/// success and error cases. | ||
fn try_from<E, U>(u: Spanned<U>) -> Result<Spanned<T>, FromRawReprError<Spanned<E>>> | ||
where | ||
T: TryFrom<U, Error = FromRawReprError<E>>, | ||
{ | ||
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<E> { | ||
/// 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<RawRepr> for PrimitiveRepr { | ||
type Error = (); | ||
fn try_from(raw: RawRepr) -> Result<PrimitiveRepr, ()> { | ||
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<RawRepr> for Infallible { | ||
type Error = (); | ||
fn try_from(raw: RawRepr) -> Result<Infallible, ()> { | ||
Err(()) | ||
} | ||
} | ||
|
||
impl<Prim: TryFrom<RawRepr, Error = ()>> TryFrom<RawRepr> for CompoundRepr<Prim> { | ||
type Error = FromRawReprError<()>; | ||
fn try_from(raw: RawRepr) -> Result<CompoundRepr<Prim>, 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<RawRepr> for NonZeroU64 { | ||
type Error = (); | ||
fn try_from(raw: RawRepr) -> Result<NonZeroU64, ()> { | ||
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<Pcked: TryFrom<RawRepr, Error = ()>> TryFrom<RawRepr> for AlignRepr<Pcked> { | ||
type Error = FromRawReprError<()>; | ||
fn try_from(raw: RawRepr) -> Result<AlignRepr<Pcked>, 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<E> { | ||
/// 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<E> From<Spanned<FromRawReprsError<E>>> for Error { | ||
fn from(err: Spanned<FromRawReprsError<E>>) -> Error { | ||
todo!() | ||
} | ||
} | ||
|
||
/// Tries to extract a high-level repr from a list of `RawRepr`s. | ||
fn try_from_raw_reprs<'a, E, R: TryFrom<RawRepr, Error = FromRawReprError<E>>>( | ||
r: impl IntoIterator<Item = &'a Spanned<RawRepr>>, | ||
) -> Result<Option<Spanned<R>>, Spanned<FromRawReprsError<E>>> { | ||
// 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::<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(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)) | ||
} | ||
}) | ||
} | ||
|
||
impl<Prim, Packed> Repr<Prim, Packed> { | ||
pub(crate) fn parse(input: &DeriveInput) -> Result<Repr<Prim, Packed>, Error> | ||
where | ||
Prim: TryFrom<RawRepr, Error = ()>, | ||
Packed: TryFrom<RawRepr, Error = ()>, | ||
{ | ||
let raw_reprs = RawRepr::from_attrs(&input.attrs)?; | ||
|
||
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<Spanned<CompoundRepr<Prim>>> = try_from_raw_reprs(raw_reprs.iter())?; | ||
let align: Option<Spanned<AlignRepr<Packed>>> = 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, | ||
)) | ||
} | ||
} | ||
} | ||
|
||
impl RawRepr { | ||
fn from_attrs(attrs: &[Attribute]) -> Result<Vec<Spanned<RawRepr>>, Error> { | ||
let mut reprs = Vec::new(); | ||
for attr in attrs { | ||
// Ignore documentation attributes. | ||
if attr.path().is_ident("doc") { | ||
continue; | ||
} | ||
if let Meta::List(ref meta_list) = attr.meta { | ||
if meta_list.path.is_ident("repr") { | ||
let parsed: Punctuated<Meta, Comma> = | ||
match meta_list.parse_args_with(Punctuated::parse_terminated) { | ||
Ok(parsed) => parsed, | ||
Err(_) => { | ||
return Err(Error::new_spanned( | ||
&meta_list.tokens, | ||
"unrecognized representation hint", | ||
)) | ||
} | ||
}; | ||
for meta in parsed { | ||
reprs.push(RawRepr::from_meta(&meta)?); | ||
// match R::parse(&meta) { | ||
// Ok(repr) => reprs.push((meta, repr)), | ||
// Err(err) => errors.push(err), | ||
// } | ||
} | ||
} | ||
} | ||
} | ||
|
||
Ok(reprs) | ||
} | ||
|
||
fn from_meta(meta: &Meta) -> Result<Spanned<RawRepr>, Error> { | ||
let (path, list) = match meta { | ||
Meta::Path(path) => (path, None), | ||
Meta::List(list) => (&list.path, Some(list)), | ||
_ => return Err(Error::new_spanned(meta, "unrecognized representation hint")), | ||
}; | ||
|
||
let ident = path | ||
.get_ident() | ||
.ok_or_else(|| Error::new_spanned(meta, "unrecognized representation hint"))?; | ||
|
||
use RawRepr::*; | ||
let repr = match (ident.to_string().as_str(), list) { | ||
("u8", None) => U8, | ||
("u16", None) => U16, | ||
("u32", None) => U32, | ||
("u64", None) => U64, | ||
("usize", None) => Usize, | ||
("i8", None) => I8, | ||
("i16", None) => I16, | ||
("i32", None) => I32, | ||
("i64", None) => I64, | ||
("isize", None) => Isize, | ||
("C", None) => C, | ||
("transparent", None) => Transparent, | ||
("packed", None) => Packed, | ||
("packed", Some(list)) => { | ||
PackedN(list.parse_args::<LitInt>()?.base10_parse::<NonZeroU64>()?) | ||
} | ||
("align", Some(list)) => { | ||
Align(list.parse_args::<LitInt>()?.base10_parse::<NonZeroU64>()?) | ||
} | ||
_ => return Err(Error::new_spanned(meta, "unrecognized representation hint")), | ||
}; | ||
|
||
Ok(Spanned::new(repr, meta.span())) | ||
} | ||
} | ||
|
||
#[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). | ||
} |