mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
rust: zerocopy-derive: import crate
This is a subset of the Rust `zerocopy-derive` crate, version v0.8.50
(released 2026-05-31), licensed under "BSD-2-Clause OR Apache-2.0 OR
MIT", from:
https://github.com/google/zerocopy/tree/v0.8.50/zerocopy-derive/src
The files are copied as-is, with no modifications whatsoever (not even
adding the SPDX identifiers).
For copyright details, please see:
https://github.com/google/zerocopy/blob/v0.8.50/README.md?plain=1
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-BSD
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-APACHE
https://github.com/google/zerocopy/blob/v0.8.50/LICENSE-MIT
The next two patches modify these files as needed for use within the
kernel. This patch split allows reviewers to double-check the import
and to clearly see the differences introduced.
The following script may be used to verify the contents:
for path in $(cd rust/zerocopy-derive/ && find . -type f); do
curl --silent --show-error --location \
https://github.com/google/zerocopy/raw/v0.8.50/zerocopy-derive/src/$path \
| diff --unified rust/zerocopy-derive/$path - && echo $path: OK
done
Cc: Joshua Liebow-Feeser <joshlf@google.com>
Cc: Jack Wrenn <jswrenn@google.com>
Link: https://patch.msgid.link/20260608141439.182634-14-ojeda@kernel.org
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
parent
567621523a
commit
b437b38328
190
rust/zerocopy-derive/derive/from_bytes.rs
Normal file
190
rust/zerocopy-derive/derive/from_bytes.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
use proc_macro2::{Span, TokenStream};
|
||||
use syn::{
|
||||
parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp,
|
||||
WherePredicate,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
derive::try_from_bytes::derive_try_from_bytes,
|
||||
repr::{CompoundRepr, EnumRepr, Repr, Spanned},
|
||||
util::{enum_size_from_repr, Ctx, FieldBounds, ImplBlockBuilder, Trait, TraitBound},
|
||||
};
|
||||
/// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
|
||||
/// zero. If `Err(bool)` is returned, the boolean is true if the enum has
|
||||
/// unknown discriminants (e.g. discriminants set to const expressions which we
|
||||
/// can't evaluate in a proc macro). If the enum has unknown discriminants, then
|
||||
/// it might have a zero variant that we just can't detect.
|
||||
pub(crate) fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
|
||||
// Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
|
||||
// the discriminant type may be signed or unsigned. Since we only care about
|
||||
// tracking the discriminant when it's less than or equal to zero, we can
|
||||
// avoid u128 -> i128 conversions and bounds checking by making the "next
|
||||
// discriminant" value implicitly negative.
|
||||
// Technically 64 bits is enough, but 128 is better for future compatibility
|
||||
// with https://github.com/rust-lang/rust/issues/56071
|
||||
let mut next_negative_discriminant = Some(0);
|
||||
|
||||
// Sometimes we encounter explicit discriminants that we can't know the
|
||||
// value of (e.g. a constant expression that requires evaluation). These
|
||||
// could evaluate to zero or a negative number, but we can't assume that
|
||||
// they do (no false positives allowed!). So we treat them like strictly-
|
||||
// positive values that can't result in any zero variants, and track whether
|
||||
// we've encountered any unknown discriminants.
|
||||
let mut has_unknown_discriminants = false;
|
||||
|
||||
for (i, v) in enm.variants.iter().enumerate() {
|
||||
match v.discriminant.as_ref() {
|
||||
// Implicit discriminant
|
||||
None => {
|
||||
match next_negative_discriminant.as_mut() {
|
||||
Some(0) => return Ok(i),
|
||||
// n is nonzero so subtraction is always safe
|
||||
Some(n) => *n -= 1,
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
// Explicit positive discriminant
|
||||
Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
|
||||
match int.base10_parse::<u128>().ok() {
|
||||
Some(0) => return Ok(i),
|
||||
Some(_) => next_negative_discriminant = None,
|
||||
None => {
|
||||
// Numbers should never fail to parse, but just in case:
|
||||
has_unknown_discriminants = true;
|
||||
next_negative_discriminant = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Explicit negative discriminant
|
||||
Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
|
||||
Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
|
||||
match int.base10_parse::<u128>().ok() {
|
||||
Some(0) => return Ok(i),
|
||||
// x is nonzero so subtraction is always safe
|
||||
Some(x) => next_negative_discriminant = Some(x - 1),
|
||||
None => {
|
||||
// Numbers should never fail to parse, but just in
|
||||
// case:
|
||||
has_unknown_discriminants = true;
|
||||
next_negative_discriminant = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Unknown negative discriminant (e.g. const repr)
|
||||
_ => {
|
||||
has_unknown_discriminants = true;
|
||||
next_negative_discriminant = None;
|
||||
}
|
||||
},
|
||||
// Unknown discriminant (e.g. const expr)
|
||||
_ => {
|
||||
has_unknown_discriminants = true;
|
||||
next_negative_discriminant = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(has_unknown_discriminants)
|
||||
}
|
||||
pub(crate) fn derive_from_zeros(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
|
||||
let try_from_bytes = derive_try_from_bytes(ctx, top_level)?;
|
||||
let from_zeros = match &ctx.ast.data {
|
||||
Data::Struct(strct) => derive_from_zeros_struct(ctx, strct),
|
||||
Data::Enum(enm) => derive_from_zeros_enum(ctx, enm)?,
|
||||
Data::Union(unn) => derive_from_zeros_union(ctx, unn),
|
||||
};
|
||||
Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
|
||||
}
|
||||
pub(crate) fn derive_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
|
||||
let from_zeros = derive_from_zeros(ctx, top_level)?;
|
||||
let from_bytes = match &ctx.ast.data {
|
||||
Data::Struct(strct) => derive_from_bytes_struct(ctx, strct),
|
||||
Data::Enum(enm) => derive_from_bytes_enum(ctx, enm)?,
|
||||
Data::Union(unn) => derive_from_bytes_union(ctx, unn),
|
||||
};
|
||||
|
||||
Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
|
||||
}
|
||||
fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
|
||||
ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build()
|
||||
}
|
||||
fn derive_from_zeros_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
|
||||
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
|
||||
// We don't actually care what the repr is; we just care that it's one of
|
||||
// the allowed ones.
|
||||
match repr {
|
||||
Repr::Compound(Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ }, _) => {
|
||||
}
|
||||
Repr::Transparent(_) | Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => {
|
||||
return ctx.error_or_skip(
|
||||
Error::new(
|
||||
Span::call_site(),
|
||||
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let zero_variant = match find_zero_variant(enm) {
|
||||
Ok(index) => enm.variants.iter().nth(index).unwrap(),
|
||||
// Has unknown variants
|
||||
Err(true) => {
|
||||
return ctx.error_or_skip(Error::new_spanned(
|
||||
&ctx.ast,
|
||||
"FromZeros only supported on enums with a variant that has a discriminant of `0`\n\
|
||||
help: This enum has discriminants which are not literal integers. One of those may \
|
||||
define or imply which variant has a discriminant of zero. Use a literal integer to \
|
||||
define or imply the variant with a discriminant of zero.",
|
||||
));
|
||||
}
|
||||
// Does not have unknown variants
|
||||
Err(false) => {
|
||||
return ctx.error_or_skip(Error::new_spanned(
|
||||
&ctx.ast,
|
||||
"FromZeros only supported on enums with a variant that has a discriminant of `0`",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let explicit_bounds = zero_variant
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let ty = &field.ty;
|
||||
parse_quote! { #ty: #zerocopy_crate::FromZeros }
|
||||
})
|
||||
.collect::<Vec<WherePredicate>>();
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds))
|
||||
.build())
|
||||
}
|
||||
fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
|
||||
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
|
||||
ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build()
|
||||
}
|
||||
fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
|
||||
ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build()
|
||||
}
|
||||
fn derive_from_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
|
||||
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
|
||||
let variants_required = 1usize << enum_size_from_repr(&repr)?;
|
||||
if enm.variants.len() != variants_required {
|
||||
return ctx.error_or_skip(Error::new_spanned(
|
||||
&ctx.ast,
|
||||
format!(
|
||||
"FromBytes only supported on {} enum with {} variants",
|
||||
repr.repr_type_name(),
|
||||
variants_required
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromBytes, FieldBounds::ALL_SELF).build())
|
||||
}
|
||||
fn derive_from_bytes_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
|
||||
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
|
||||
ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build()
|
||||
}
|
||||
162
rust/zerocopy-derive/derive/into_bytes.rs
Normal file
162
rust/zerocopy-derive/derive/into_bytes.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{Data, DataEnum, DataStruct, DataUnion, Error, Type};
|
||||
|
||||
use crate::{
|
||||
repr::{EnumRepr, StructUnionRepr},
|
||||
util::{
|
||||
generate_tag_enum, Ctx, DataExt, FieldBounds, ImplBlockBuilder, PaddingCheck, Trait,
|
||||
TraitBound,
|
||||
},
|
||||
};
|
||||
pub(crate) fn derive_into_bytes(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
|
||||
match &ctx.ast.data {
|
||||
Data::Struct(strct) => derive_into_bytes_struct(ctx, strct),
|
||||
Data::Enum(enm) => derive_into_bytes_enum(ctx, enm),
|
||||
Data::Union(unn) => derive_into_bytes_union(ctx, unn),
|
||||
}
|
||||
}
|
||||
fn derive_into_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
|
||||
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
|
||||
let is_transparent = repr.is_transparent();
|
||||
let is_c = repr.is_c();
|
||||
let is_packed_1 = repr.is_packed_1();
|
||||
let num_fields = strct.fields().len();
|
||||
|
||||
let (padding_check, require_unaligned_fields) = if is_transparent || is_packed_1 {
|
||||
// No padding check needed.
|
||||
// - repr(transparent): The layout and ABI of the whole struct is the
|
||||
// same as its only non-ZST field (meaning there's no padding outside
|
||||
// of that field) and we require that field to be `IntoBytes` (meaning
|
||||
// there's no padding in that field).
|
||||
// - repr(packed): Any inter-field padding bytes are removed, meaning
|
||||
// that any padding bytes would need to come from the fields, all of
|
||||
// which we require to be `IntoBytes` (meaning they don't have any
|
||||
// padding). Note that this holds regardless of other `repr`
|
||||
// attributes, including `repr(Rust)`. [1]
|
||||
//
|
||||
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-alignment-modifiers:
|
||||
//
|
||||
// An important consequence of these rules is that a type with
|
||||
// `#[repr(packed(1))]`` (or `#[repr(packed)]``) will have no
|
||||
// inter-field padding.
|
||||
(None, false)
|
||||
} else if is_c && !repr.is_align_gt_1() && num_fields <= 1 {
|
||||
// No padding check needed. A repr(C) struct with zero or one field has
|
||||
// no padding unless #[repr(align)] explicitly adds padding, which we
|
||||
// check for in this branch's condition.
|
||||
(None, false)
|
||||
} else if ctx.ast.generics.params.is_empty() {
|
||||
// Is the last field a syntactic slice, i.e., `[SomeType]`.
|
||||
let is_syntactic_dst =
|
||||
strct.fields().last().map(|(_, _, ty)| matches!(ty, Type::Slice(_))).unwrap_or(false);
|
||||
// Since there are no generics, we can emit a padding check. All reprs
|
||||
// guarantee that fields won't overlap [1], so the padding check is
|
||||
// sound. This is more permissive than the next case, which requires
|
||||
// that all field types implement `Unaligned`.
|
||||
//
|
||||
// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#the-rust-representation:
|
||||
//
|
||||
// The only data layout guarantees made by [`repr(Rust)`] are those
|
||||
// required for soundness. They are:
|
||||
// ...
|
||||
// 2. The fields do not overlap.
|
||||
// ...
|
||||
if is_c && is_syntactic_dst {
|
||||
(Some(PaddingCheck::ReprCStruct), false)
|
||||
} else {
|
||||
(Some(PaddingCheck::Struct), false)
|
||||
}
|
||||
} else if is_c && !repr.is_align_gt_1() {
|
||||
// We can't use a padding check since there are generic type arguments.
|
||||
// Instead, we require all field types to implement `Unaligned`. This
|
||||
// ensures that the `repr(C)` layout algorithm will not insert any
|
||||
// padding unless #[repr(align)] explicitly adds padding, which we check
|
||||
// for in this branch's condition.
|
||||
//
|
||||
// FIXME(#10): Support type parameters for non-transparent, non-packed
|
||||
// structs without requiring `Unaligned`.
|
||||
(None, true)
|
||||
} else {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"must have a non-align #[repr(...)] attribute in order to guarantee this type's memory layout",
|
||||
));
|
||||
};
|
||||
|
||||
let field_bounds = if require_unaligned_fields {
|
||||
FieldBounds::All(&[TraitBound::Slf, TraitBound::Other(Trait::Unaligned)])
|
||||
} else {
|
||||
FieldBounds::ALL_SELF
|
||||
};
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, strct, Trait::IntoBytes, field_bounds)
|
||||
.padding_check(padding_check)
|
||||
.build())
|
||||
}
|
||||
|
||||
fn derive_into_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
|
||||
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
if !repr.is_c() && !repr.is_primitive() {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
|
||||
));
|
||||
}
|
||||
|
||||
let tag_type_definition = generate_tag_enum(ctx, &repr, enm);
|
||||
Ok(ImplBlockBuilder::new(ctx, enm, Trait::IntoBytes, FieldBounds::ALL_SELF)
|
||||
.padding_check(PaddingCheck::Enum { tag_type_definition })
|
||||
.build())
|
||||
}
|
||||
|
||||
fn derive_into_bytes_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
|
||||
// See #1792 for more context.
|
||||
//
|
||||
// By checking for `zerocopy_derive_union_into_bytes` both here and in the
|
||||
// generated code, we ensure that `--cfg zerocopy_derive_union_into_bytes`
|
||||
// need only be passed *either* when compiling this crate *or* when
|
||||
// compiling the user's crate. The former is preferable, but in some
|
||||
// situations (such as when cross-compiling using `cargo build --target`),
|
||||
// it doesn't get propagated to this crate's build by default.
|
||||
let cfg_compile_error = if cfg!(zerocopy_derive_union_into_bytes) {
|
||||
quote!()
|
||||
} else {
|
||||
let core = ctx.core_path();
|
||||
let error_message = "requires --cfg zerocopy_derive_union_into_bytes;
|
||||
please let us know you use this feature: https://github.com/google/zerocopy/discussions/1802";
|
||||
quote!(
|
||||
#[allow(unused_attributes, unexpected_cfgs)]
|
||||
const _: () = {
|
||||
#[cfg(not(zerocopy_derive_union_into_bytes))]
|
||||
#core::compile_error!(#error_message);
|
||||
};
|
||||
)
|
||||
};
|
||||
|
||||
// FIXME(#10): Support type parameters.
|
||||
if !ctx.ast.generics.params.is_empty() {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"unsupported on types with type parameters",
|
||||
));
|
||||
}
|
||||
|
||||
// Because we don't support generics, we don't need to worry about
|
||||
// special-casing different reprs. So long as there is *some* repr which
|
||||
// guarantees the layout, our `PaddingCheck::Union` guarantees that there is
|
||||
// no padding.
|
||||
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
if !repr.is_c() && !repr.is_transparent() && !repr.is_packed_1() {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"must be #[repr(C)], #[repr(packed)], or #[repr(transparent)]",
|
||||
));
|
||||
}
|
||||
|
||||
let impl_block = ImplBlockBuilder::new(ctx, unn, Trait::IntoBytes, FieldBounds::ALL_SELF)
|
||||
.padding_check(PaddingCheck::Union)
|
||||
.build();
|
||||
Ok(quote!(#cfg_compile_error #impl_block))
|
||||
}
|
||||
348
rust/zerocopy-derive/derive/known_layout.rs
Normal file
348
rust/zerocopy-derive/derive/known_layout.rs
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_quote, Data, Error, Type};
|
||||
|
||||
use crate::{
|
||||
repr::StructUnionRepr,
|
||||
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, SelfBounds, Trait},
|
||||
};
|
||||
|
||||
fn derive_known_layout_for_repr_c_struct<'a>(
|
||||
ctx: &'a Ctx,
|
||||
repr: &StructUnionRepr,
|
||||
fields: &[(&'a syn::Visibility, TokenStream, &'a Type)],
|
||||
) -> Option<(SelfBounds<'a>, TokenStream, Option<TokenStream>)> {
|
||||
let (trailing_field, leading_fields) = fields.split_last()?;
|
||||
|
||||
let (_vis, trailing_field_name, trailing_field_ty) = trailing_field;
|
||||
let leading_fields_tys = leading_fields.iter().map(|(_vis, _name, ty)| ty);
|
||||
|
||||
let core = ctx.core_path();
|
||||
let repr_align = repr
|
||||
.get_align()
|
||||
.map(|align| {
|
||||
let align = align.t.get();
|
||||
quote!(#core::num::NonZeroUsize::new(#align as usize))
|
||||
})
|
||||
.unwrap_or_else(|| quote!(#core::option::Option::None));
|
||||
let repr_packed = repr
|
||||
.get_packed()
|
||||
.map(|packed| {
|
||||
let packed = packed.get();
|
||||
quote!(#core::num::NonZeroUsize::new(#packed as usize))
|
||||
})
|
||||
.unwrap_or_else(|| quote!(#core::option::Option::None));
|
||||
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let make_methods = |trailing_field_ty| {
|
||||
quote! {
|
||||
// SAFETY:
|
||||
// - The returned pointer has the same address and provenance as
|
||||
// `bytes`:
|
||||
// - The recursive call to `raw_from_ptr_len` preserves both
|
||||
// address and provenance.
|
||||
// - The `as` cast preserves both address and provenance.
|
||||
// - `NonNull::new_unchecked` preserves both address and
|
||||
// provenance.
|
||||
// - If `Self` is a slice DST, the returned pointer encodes
|
||||
// `elems` elements in the trailing slice:
|
||||
// - This is true of the recursive call to `raw_from_ptr_len`.
|
||||
// - `trailing.as_ptr() as *mut Self` preserves trailing slice
|
||||
// element count [1].
|
||||
// - `NonNull::new_unchecked` preserves trailing slice element
|
||||
// count.
|
||||
//
|
||||
// [1] Per https://doc.rust-lang.org/reference/expressions/operator-expr.html#pointer-to-pointer-cast:
|
||||
//
|
||||
// `*const T`` / `*mut T` can be cast to `*const U` / `*mut U`
|
||||
// with the following behavior:
|
||||
// ...
|
||||
// - If `T` and `U` are both unsized, the pointer is also
|
||||
// returned unchanged. In particular, the metadata is
|
||||
// preserved exactly.
|
||||
//
|
||||
// For instance, a cast from `*const [T]` to `*const [U]`
|
||||
// preserves the number of elements. ... The same holds
|
||||
// for str and any compound type whose unsized tail is a
|
||||
// slice type, such as struct `Foo(i32, [u8])` or
|
||||
// `(u64, Foo)`.
|
||||
#[inline(always)]
|
||||
fn raw_from_ptr_len(
|
||||
bytes: #core::ptr::NonNull<u8>,
|
||||
meta: <Self as #zerocopy_crate::KnownLayout>::PointerMetadata,
|
||||
) -> #core::ptr::NonNull<Self> {
|
||||
let trailing = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::raw_from_ptr_len(bytes, meta);
|
||||
let slf = trailing.as_ptr() as *mut Self;
|
||||
// SAFETY: Constructed from `trailing`, which is non-null.
|
||||
unsafe { #core::ptr::NonNull::new_unchecked(slf) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pointer_to_metadata(ptr: *mut Self) -> <Self as #zerocopy_crate::KnownLayout>::PointerMetadata {
|
||||
<#trailing_field_ty>::pointer_to_metadata(ptr as *mut _)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let inner_extras = {
|
||||
let leading_fields_tys = leading_fields_tys.clone();
|
||||
let methods = make_methods(*trailing_field_ty);
|
||||
let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
|
||||
|
||||
quote!(
|
||||
type PointerMetadata = <#trailing_field_ty as #zerocopy_crate::KnownLayout>::PointerMetadata;
|
||||
|
||||
type MaybeUninit = __ZerocopyKnownLayoutMaybeUninit #ty_generics;
|
||||
|
||||
// SAFETY: `LAYOUT` accurately describes the layout of `Self`.
|
||||
// The documentation of `DstLayout::for_repr_c_struct` vows that
|
||||
// invocations in this manner will accurately describe a type,
|
||||
// so long as:
|
||||
//
|
||||
// - that type is `repr(C)`,
|
||||
// - its fields are enumerated in the order they appear,
|
||||
// - the presence of `repr_align` and `repr_packed` are
|
||||
// correctly accounted for.
|
||||
//
|
||||
// We respect all three of these preconditions here. This
|
||||
// expansion is only used if `is_repr_c_struct`, we enumerate
|
||||
// the fields in order, and we extract the values of `align(N)`
|
||||
// and `packed(N)`.
|
||||
const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_repr_c_struct(
|
||||
#repr_align,
|
||||
#repr_packed,
|
||||
&[
|
||||
#(#zerocopy_crate::DstLayout::for_type::<#leading_fields_tys>(),)*
|
||||
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::LAYOUT
|
||||
],
|
||||
);
|
||||
|
||||
#methods
|
||||
)
|
||||
};
|
||||
|
||||
let outer_extras = {
|
||||
let ident = &ctx.ast.ident;
|
||||
let vis = &ctx.ast.vis;
|
||||
let params = &ctx.ast.generics.params;
|
||||
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
|
||||
|
||||
let predicates = if let Some(where_clause) = where_clause {
|
||||
where_clause.predicates.clone()
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// Generate a valid ident for a type-level handle to a field of a
|
||||
// given `name`.
|
||||
let field_index = |name: &TokenStream| ident!(("__Zerocopy_Field_{}", name), ident.span());
|
||||
|
||||
let field_indices: Vec<_> =
|
||||
fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();
|
||||
|
||||
// Define the collection of type-level field handles.
|
||||
let field_defs = field_indices.iter().zip(fields).map(|(idx, (vis, _, _))| {
|
||||
quote! {
|
||||
#vis struct #idx;
|
||||
}
|
||||
});
|
||||
|
||||
let field_impls = field_indices.iter().zip(fields).map(|(idx, (_, _, ty))| quote! {
|
||||
// SAFETY: `#ty` is the type of `#ident`'s field at `#idx`.
|
||||
//
|
||||
// We implement `Field` for each field of the struct to create a
|
||||
// projection from the field index to its type. This allows us
|
||||
// to refer to the field's type in a way that respects `Self`
|
||||
// hygiene. If we just copy-pasted the tokens of `#ty`, we
|
||||
// would not respect `Self` hygiene, as `Self` would refer to
|
||||
// the helper struct we are generating, not the derive target
|
||||
// type.
|
||||
unsafe impl #impl_generics #zerocopy_crate::util::macro_util::Field<#idx> for #ident #ty_generics
|
||||
where
|
||||
#predicates
|
||||
{
|
||||
type Type = #ty;
|
||||
}
|
||||
});
|
||||
|
||||
let trailing_field_index = field_index(trailing_field_name);
|
||||
let leading_field_indices =
|
||||
leading_fields.iter().map(|(_vis, name, _ty)| field_index(name));
|
||||
|
||||
// We use `Field` to project the type of the trailing field. This is
|
||||
// required to ensure that if the field type uses `Self`, it
|
||||
// resolves to the derive target type, not the helper struct we are
|
||||
// generating.
|
||||
let trailing_field_ty = quote! {
|
||||
<#ident #ty_generics as
|
||||
#zerocopy_crate::util::macro_util::Field<#trailing_field_index>
|
||||
>::Type
|
||||
};
|
||||
|
||||
let methods = make_methods(&parse_quote! {
|
||||
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
|
||||
});
|
||||
|
||||
let core = ctx.core_path();
|
||||
|
||||
quote! {
|
||||
#(#field_defs)*
|
||||
|
||||
#(#field_impls)*
|
||||
|
||||
// SAFETY: This has the same layout as the derive target type,
|
||||
// except that it admits uninit bytes. This is ensured by using
|
||||
// the same repr as the target type, and by using field types
|
||||
// which have the same layout as the target type's fields,
|
||||
// except that they admit uninit bytes. We indirect through
|
||||
// `Field` to ensure that occurrences of `Self` resolve to
|
||||
// `#ty`, not `__ZerocopyKnownLayoutMaybeUninit` (see #2116).
|
||||
#repr
|
||||
#[doc(hidden)]
|
||||
#vis struct __ZerocopyKnownLayoutMaybeUninit<#params> (
|
||||
#(#core::mem::MaybeUninit<
|
||||
<#ident #ty_generics as
|
||||
#zerocopy_crate::util::macro_util::Field<#leading_field_indices>
|
||||
>::Type
|
||||
>,)*
|
||||
// NOTE(#2302): We wrap in `ManuallyDrop` here in case the
|
||||
// type we're operating on is both generic and
|
||||
// `repr(packed)`. In that case, Rust needs to know that the
|
||||
// type is *either* `Sized` or has a trivial `Drop`.
|
||||
// `ManuallyDrop` has a trivial `Drop`, and so satisfies
|
||||
// this requirement.
|
||||
#core::mem::ManuallyDrop<
|
||||
<#trailing_field_ty as #zerocopy_crate::KnownLayout>::MaybeUninit
|
||||
>
|
||||
)
|
||||
where
|
||||
#trailing_field_ty: #zerocopy_crate::KnownLayout,
|
||||
#predicates;
|
||||
|
||||
// SAFETY: We largely defer to the `KnownLayout` implementation
|
||||
// on the derive target type (both by using the same tokens, and
|
||||
// by deferring to impl via type-level indirection). This is
|
||||
// sound, since `__ZerocopyKnownLayoutMaybeUninit` is guaranteed
|
||||
// to have the same layout as the derive target type, except
|
||||
// that `__ZerocopyKnownLayoutMaybeUninit` admits uninit bytes.
|
||||
unsafe impl #impl_generics #zerocopy_crate::KnownLayout for __ZerocopyKnownLayoutMaybeUninit #ty_generics
|
||||
where
|
||||
#trailing_field_ty: #zerocopy_crate::KnownLayout,
|
||||
#predicates
|
||||
{
|
||||
fn only_derive_is_allowed_to_implement_this_trait() {}
|
||||
|
||||
type PointerMetadata = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::PointerMetadata;
|
||||
|
||||
type MaybeUninit = Self;
|
||||
|
||||
const LAYOUT: #zerocopy_crate::DstLayout = <#ident #ty_generics as #zerocopy_crate::KnownLayout>::LAYOUT;
|
||||
|
||||
#methods
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some((SelfBounds::None, inner_extras, Some(outer_extras)))
|
||||
}
|
||||
|
||||
pub(crate) fn derive(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
|
||||
// If this is a `repr(C)` struct, then `c_struct_repr` contains the entire
|
||||
// `repr` attribute.
|
||||
let c_struct_repr = match &ctx.ast.data {
|
||||
Data::Struct(..) => {
|
||||
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
if repr.is_c() {
|
||||
Some(repr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Data::Enum(..) | Data::Union(..) => None,
|
||||
};
|
||||
|
||||
let fields = ctx.ast.data.fields();
|
||||
|
||||
let (self_bounds, inner_extras, outer_extras) = c_struct_repr
|
||||
.as_ref()
|
||||
.and_then(|repr| {
|
||||
derive_known_layout_for_repr_c_struct(ctx, repr, &fields)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let core = ctx.core_path();
|
||||
|
||||
// For enums, unions, and non-`repr(C)` structs, we require that
|
||||
// `Self` is sized, and as a result don't need to reason about the
|
||||
// internals of the type.
|
||||
(
|
||||
SelfBounds::SIZED,
|
||||
quote!(
|
||||
type PointerMetadata = ();
|
||||
type MaybeUninit =
|
||||
#core::mem::MaybeUninit<Self>;
|
||||
|
||||
// SAFETY: `LAYOUT` is guaranteed to accurately describe the
|
||||
// layout of `Self`, because that is the documented safety
|
||||
// contract of `DstLayout::for_type`.
|
||||
const LAYOUT: #zerocopy_crate::DstLayout = #zerocopy_crate::DstLayout::for_type::<Self>();
|
||||
|
||||
// SAFETY: `.cast` preserves address and provenance.
|
||||
//
|
||||
// FIXME(#429): Add documentation to `.cast` that promises that
|
||||
// it preserves provenance.
|
||||
#[inline(always)]
|
||||
fn raw_from_ptr_len(bytes: #core::ptr::NonNull<u8>, _meta: ()) -> #core::ptr::NonNull<Self> {
|
||||
bytes.cast::<Self>()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pointer_to_metadata(_ptr: *mut Self) -> () {}
|
||||
),
|
||||
None,
|
||||
)
|
||||
});
|
||||
Ok(match &ctx.ast.data {
|
||||
Data::Struct(strct) => {
|
||||
let require_trait_bound_on_field_types =
|
||||
if matches!(self_bounds, SelfBounds::All(&[Trait::Sized])) {
|
||||
FieldBounds::None
|
||||
} else {
|
||||
FieldBounds::TRAILING_SELF
|
||||
};
|
||||
|
||||
// A bound on the trailing field is required, since structs are
|
||||
// unsized if their trailing field is unsized. Reflecting the layout
|
||||
// of an usized trailing field requires that the field is
|
||||
// `KnownLayout`.
|
||||
ImplBlockBuilder::new(
|
||||
ctx,
|
||||
strct,
|
||||
Trait::KnownLayout,
|
||||
require_trait_bound_on_field_types,
|
||||
)
|
||||
.self_type_trait_bounds(self_bounds)
|
||||
.inner_extras(inner_extras)
|
||||
.outer_extras(outer_extras)
|
||||
.build()
|
||||
}
|
||||
Data::Enum(enm) => {
|
||||
// A bound on the trailing field is not required, since enums cannot
|
||||
// currently be unsized.
|
||||
ImplBlockBuilder::new(ctx, enm, Trait::KnownLayout, FieldBounds::None)
|
||||
.self_type_trait_bounds(SelfBounds::SIZED)
|
||||
.inner_extras(inner_extras)
|
||||
.outer_extras(outer_extras)
|
||||
.build()
|
||||
}
|
||||
Data::Union(unn) => {
|
||||
// A bound on the trailing field is not required, since unions
|
||||
// cannot currently be unsized.
|
||||
ImplBlockBuilder::new(ctx, unn, Trait::KnownLayout, FieldBounds::None)
|
||||
.self_type_trait_bounds(SelfBounds::SIZED)
|
||||
.inner_extras(inner_extras)
|
||||
.outer_extras(outer_extras)
|
||||
.build()
|
||||
}
|
||||
})
|
||||
}
|
||||
130
rust/zerocopy-derive/derive/mod.rs
Normal file
130
rust/zerocopy-derive/derive/mod.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
pub mod from_bytes;
|
||||
pub mod into_bytes;
|
||||
pub mod known_layout;
|
||||
pub mod try_from_bytes;
|
||||
pub mod unaligned;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{Data, Error};
|
||||
|
||||
use crate::{
|
||||
repr::StructUnionRepr,
|
||||
util::{Ctx, DataExt, FieldBounds, ImplBlockBuilder, Trait},
|
||||
};
|
||||
|
||||
pub(crate) fn derive_immutable(ctx: &Ctx, _top_level: Trait) -> TokenStream {
|
||||
match &ctx.ast.data {
|
||||
Data::Struct(strct) => {
|
||||
ImplBlockBuilder::new(ctx, strct, Trait::Immutable, FieldBounds::ALL_SELF).build()
|
||||
}
|
||||
Data::Enum(enm) => {
|
||||
ImplBlockBuilder::new(ctx, enm, Trait::Immutable, FieldBounds::ALL_SELF).build()
|
||||
}
|
||||
Data::Union(unn) => {
|
||||
ImplBlockBuilder::new(ctx, unn, Trait::Immutable, FieldBounds::ALL_SELF).build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn derive_hash(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
|
||||
// This doesn't delegate to `impl_block` because `impl_block` assumes it is
|
||||
// deriving a `zerocopy`-defined trait, and these trait impls share a common
|
||||
// shape that `Hash` does not. In particular, `zerocopy` traits contain a
|
||||
// method that only `zerocopy_derive` macros are supposed to implement, and
|
||||
// `impl_block` generating this trait method is incompatible with `Hash`.
|
||||
let type_ident = &ctx.ast.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
|
||||
let where_predicates = where_clause.map(|clause| &clause.predicates);
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let core = ctx.core_path();
|
||||
Ok(quote! {
|
||||
impl #impl_generics #core::hash::Hash for #type_ident #ty_generics
|
||||
where
|
||||
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
|
||||
#where_predicates
|
||||
{
|
||||
fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
|
||||
#core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(self))
|
||||
}
|
||||
|
||||
fn hash_slice<H: #core::hash::Hasher>(data: &[Self], state: &mut H) {
|
||||
#core::hash::Hasher::write(state, #zerocopy_crate::IntoBytes::as_bytes(data))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn derive_eq(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
|
||||
// This doesn't delegate to `impl_block` because `impl_block` assumes it is
|
||||
// deriving a `zerocopy`-defined trait, and these trait impls share a common
|
||||
// shape that `Eq` does not. In particular, `zerocopy` traits contain a
|
||||
// method that only `zerocopy_derive` macros are supposed to implement, and
|
||||
// `impl_block` generating this trait method is incompatible with `Eq`.
|
||||
let type_ident = &ctx.ast.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
|
||||
let where_predicates = where_clause.map(|clause| &clause.predicates);
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let core = ctx.core_path();
|
||||
Ok(quote! {
|
||||
impl #impl_generics #core::cmp::PartialEq for #type_ident #ty_generics
|
||||
where
|
||||
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
|
||||
#where_predicates
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
#core::cmp::PartialEq::eq(
|
||||
#zerocopy_crate::IntoBytes::as_bytes(self),
|
||||
#zerocopy_crate::IntoBytes::as_bytes(other),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #core::cmp::Eq for #type_ident #ty_generics
|
||||
where
|
||||
Self: #zerocopy_crate::IntoBytes + #zerocopy_crate::Immutable,
|
||||
#where_predicates
|
||||
{
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn derive_split_at(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
|
||||
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
|
||||
match &ctx.ast.data {
|
||||
Data::Struct(_) => {}
|
||||
Data::Enum(_) | Data::Union(_) => {
|
||||
return Err(Error::new(Span::call_site(), "can only be applied to structs"));
|
||||
}
|
||||
};
|
||||
|
||||
if repr.get_packed().is_some() {
|
||||
return Err(Error::new(Span::call_site(), "must not have #[repr(packed)] attribute"));
|
||||
}
|
||||
|
||||
if !(repr.is_c() || repr.is_transparent()) {
|
||||
return Err(Error::new(
|
||||
Span::call_site(),
|
||||
"must have #[repr(C)] or #[repr(transparent)] in order to guarantee this type's layout is splitable",
|
||||
));
|
||||
}
|
||||
|
||||
let fields = ctx.ast.data.fields();
|
||||
let trailing_field = if let Some(((_, _, trailing_field), _)) = fields.split_last() {
|
||||
trailing_field
|
||||
} else {
|
||||
return Err(Error::new(Span::call_site(), "must at least one field"));
|
||||
};
|
||||
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
// SAFETY: `#ty`, per the above checks, is `repr(C)` or `repr(transparent)`
|
||||
// and is not packed; its trailing field is guaranteed to be well-aligned
|
||||
// for its type. By invariant on `FieldBounds::TRAILING_SELF`, the trailing
|
||||
// slice of the trailing field is also well-aligned for its type.
|
||||
Ok(ImplBlockBuilder::new(ctx, &ctx.ast.data, Trait::SplitAt, FieldBounds::TRAILING_SELF)
|
||||
.inner_extras(quote! {
|
||||
type Elem = <#trailing_field as #zerocopy_crate::SplitAt>::Elem;
|
||||
})
|
||||
.build())
|
||||
}
|
||||
763
rust/zerocopy-derive/derive/try_from_bytes.rs
Normal file
763
rust/zerocopy-derive/derive/try_from_bytes.rs
Normal file
|
|
@ -0,0 +1,763 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
|
||||
Expr, Fields, Ident, Index, Type,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
repr::{EnumRepr, StructUnionRepr},
|
||||
util::{
|
||||
const_block, enum_size_from_repr, generate_tag_enum, Ctx, DataExt, FieldBounds,
|
||||
ImplBlockBuilder, Trait, TraitBound,
|
||||
},
|
||||
};
|
||||
fn tag_ident(variant_ident: &Ident) -> Ident {
|
||||
ident!(("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
|
||||
}
|
||||
|
||||
/// Generates a constant for the tag associated with each variant of the enum.
|
||||
/// When we match on the enum's tag, each arm matches one of these constants. We
|
||||
/// have to use constants here because:
|
||||
///
|
||||
/// - The type that we're matching on is not the type of the tag, it's an
|
||||
/// integer of the same size as the tag type and with the same bit patterns.
|
||||
/// - We can't read the enum tag as an enum because the bytes may not represent
|
||||
/// a valid variant.
|
||||
/// - Patterns do not currently support const expressions, so we have to assign
|
||||
/// these constants to names rather than use them inline in the `match`
|
||||
/// statement.
|
||||
fn generate_tag_consts(data: &DataEnum) -> TokenStream {
|
||||
let tags = data.variants.iter().map(|v| {
|
||||
let variant_ident = &v.ident;
|
||||
let tag_ident = tag_ident(variant_ident);
|
||||
|
||||
quote! {
|
||||
// This casts the enum variant to its discriminant, and then
|
||||
// converts the discriminant to the target integral type via a
|
||||
// numeric cast [1].
|
||||
//
|
||||
// Because these are the same size, this is defined to be a no-op
|
||||
// and therefore is a lossless conversion [2].
|
||||
//
|
||||
// [1] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#enum-cast:
|
||||
//
|
||||
// Casts an enum to its discriminant.
|
||||
//
|
||||
// [2] Per https://doc.rust-lang.org/1.81.0/reference/expressions/operator-expr.html#numeric-cast:
|
||||
//
|
||||
// Casting between two integers of the same size (e.g. i32 -> u32)
|
||||
// is a no-op.
|
||||
const #tag_ident: ___ZerocopyTagPrimitive =
|
||||
___ZerocopyTag::#variant_ident as ___ZerocopyTagPrimitive;
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#tags)*
|
||||
}
|
||||
}
|
||||
|
||||
fn variant_struct_ident(variant_ident: &Ident) -> Ident {
|
||||
ident!(("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
|
||||
}
|
||||
|
||||
/// Generates variant structs for the given enum variant.
|
||||
///
|
||||
/// These are structs associated with each variant of an enum. They are
|
||||
/// `repr(C)` tuple structs with the same fields as the variant after a
|
||||
/// `MaybeUninit<___ZerocopyInnerTag>`.
|
||||
///
|
||||
/// In order to unify the generated types for `repr(C)` and `repr(int)` enums,
|
||||
/// we use a "fused" representation with fields for both an inner tag and an
|
||||
/// outer tag. Depending on the repr, we will set one of these tags to the tag
|
||||
/// type and the other to `()`. This lets us generate the same code but put the
|
||||
/// tags in different locations.
|
||||
fn generate_variant_structs(ctx: &Ctx, data: &DataEnum) -> TokenStream {
|
||||
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
|
||||
|
||||
let enum_name = &ctx.ast.ident;
|
||||
|
||||
// All variant structs have a `PhantomData<MyEnum<...>>` field because we
|
||||
// don't know which generic parameters each variant will use, and unused
|
||||
// generic parameters are a compile error.
|
||||
let core = ctx.core_path();
|
||||
let phantom_ty = quote! {
|
||||
#core::marker::PhantomData<#enum_name #ty_generics>
|
||||
};
|
||||
|
||||
let variant_structs = data.variants.iter().filter_map(|variant| {
|
||||
// We don't generate variant structs for unit variants because we only
|
||||
// need to check the tag. This helps cut down our generated code a bit.
|
||||
if matches!(variant.fields, Fields::Unit) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let variant_struct_ident = variant_struct_ident(&variant.ident);
|
||||
let field_types = variant.fields.iter().map(|f| &f.ty);
|
||||
|
||||
let variant_struct = parse_quote! {
|
||||
#[repr(C)]
|
||||
struct #variant_struct_ident #impl_generics (
|
||||
#core::mem::MaybeUninit<___ZerocopyInnerTag>,
|
||||
#(#field_types,)*
|
||||
#phantom_ty,
|
||||
) #where_clause;
|
||||
};
|
||||
|
||||
// We do this rather than emitting `#[derive(::zerocopy::TryFromBytes)]`
|
||||
// because that is not hygienic, and this is also more performant.
|
||||
let try_from_bytes_impl =
|
||||
derive_try_from_bytes(&ctx.with_input(&variant_struct), Trait::TryFromBytes)
|
||||
.expect("derive_try_from_bytes should not fail on synthesized type");
|
||||
|
||||
Some(quote! {
|
||||
#variant_struct
|
||||
#try_from_bytes_impl
|
||||
})
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#variant_structs)*
|
||||
}
|
||||
}
|
||||
|
||||
fn variants_union_field_ident(ident: &Ident) -> Ident {
|
||||
// Field names are prefixed with `__field_` to prevent name collision
|
||||
// with the `__nonempty` field.
|
||||
ident!(("__field_{}", ident), ident.span())
|
||||
}
|
||||
|
||||
fn generate_variants_union(ctx: &Ctx, data: &DataEnum) -> TokenStream {
|
||||
let generics = &ctx.ast.generics;
|
||||
let (_, ty_generics, _) = generics.split_for_impl();
|
||||
|
||||
let fields = data.variants.iter().filter_map(|variant| {
|
||||
// We don't generate variant structs for unit variants because we only
|
||||
// need to check the tag. This helps cut down our generated code a bit.
|
||||
if matches!(variant.fields, Fields::Unit) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let field_name = variants_union_field_ident(&variant.ident);
|
||||
let variant_struct_ident = variant_struct_ident(&variant.ident);
|
||||
|
||||
let core = ctx.core_path();
|
||||
Some(quote! {
|
||||
#field_name: #core::mem::ManuallyDrop<#variant_struct_ident #ty_generics>,
|
||||
})
|
||||
});
|
||||
|
||||
let variants_union = parse_quote! {
|
||||
#[repr(C)]
|
||||
union ___ZerocopyVariants #generics {
|
||||
#(#fields)*
|
||||
// Enums can have variants with no fields, but unions must
|
||||
// have at least one field. So we just add a trailing unit
|
||||
// to ensure that this union always has at least one field.
|
||||
// Because this union is `repr(C)`, this unit type does not
|
||||
// affect the layout.
|
||||
__nonempty: (),
|
||||
}
|
||||
};
|
||||
|
||||
let has_field =
|
||||
derive_has_field_struct_union(&ctx.with_input(&variants_union), &variants_union.data);
|
||||
|
||||
quote! {
|
||||
#variants_union
|
||||
#has_field
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates an implementation of `is_bit_valid` for an arbitrary enum.
|
||||
///
|
||||
/// The general process is:
|
||||
///
|
||||
/// 1. Generate a tag enum. This is an enum with the same repr, variants, and
|
||||
/// corresponding discriminants as the original enum, but without any fields
|
||||
/// on the variants. This gives us access to an enum where the variants have
|
||||
/// the same discriminants as the one we're writing `is_bit_valid` for.
|
||||
/// 2. Make constants from the variants of the tag enum. We need these because
|
||||
/// we can't put const exprs in match arms.
|
||||
/// 3. Generate variant structs. These are structs which have the same fields as
|
||||
/// each variant of the enum, and are `#[repr(C)]` with an optional "inner
|
||||
/// tag".
|
||||
/// 4. Generate a variants union, with one field for each variant struct type.
|
||||
/// 5. And finally, our raw enum is a `#[repr(C)]` struct of an "outer tag" and
|
||||
/// the variants union.
|
||||
///
|
||||
/// See these reference links for fully-worked example decompositions.
|
||||
///
|
||||
/// - `repr(C)`: <https://doc.rust-lang.org/reference/type-layout.html#reprc-enums-with-fields>
|
||||
/// - `repr(int)`: <https://doc.rust-lang.org/reference/type-layout.html#primitive-representation-of-enums-with-fields>
|
||||
/// - `repr(C, int)`: <https://doc.rust-lang.org/reference/type-layout.html#combining-primitive-representations-of-enums-with-fields-and-reprc>
|
||||
pub(crate) fn derive_is_bit_valid(
|
||||
ctx: &Ctx,
|
||||
data: &DataEnum,
|
||||
repr: &EnumRepr,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let trait_path = Trait::TryFromBytes.crate_path(ctx);
|
||||
let tag_enum = generate_tag_enum(ctx, repr, data);
|
||||
let tag_consts = generate_tag_consts(data);
|
||||
|
||||
let (outer_tag_type, inner_tag_type) = if repr.is_c() {
|
||||
(quote! { ___ZerocopyTag }, quote! { () })
|
||||
} else if repr.is_primitive() {
|
||||
(quote! { () }, quote! { ___ZerocopyTag })
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
ctx.ast.span(),
|
||||
"must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
|
||||
));
|
||||
};
|
||||
|
||||
let variant_structs = generate_variant_structs(ctx, data);
|
||||
let variants_union = generate_variants_union(ctx, data);
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = ctx.ast.generics.split_for_impl();
|
||||
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
|
||||
.inner_extras(quote! {
|
||||
type Tag = ___ZerocopyTag;
|
||||
type ProjectToTag = #zerocopy_crate::pointer::cast::CastSized;
|
||||
})
|
||||
.build();
|
||||
let has_fields = data.variants().into_iter().flat_map(|(variant, fields)| {
|
||||
let variant_ident = &variant.unwrap().ident;
|
||||
let variants_union_field_ident = variants_union_field_ident(variant_ident);
|
||||
let field: Box<syn::Type> = parse_quote!(());
|
||||
fields.into_iter().enumerate().map(move |(idx, (vis, ident, ty))| {
|
||||
// Rust does not presently support explicit visibility modifiers on
|
||||
// enum fields, but we guard against the possibility to ensure this
|
||||
// derive remains sound.
|
||||
assert!(matches!(vis, syn::Visibility::Inherited));
|
||||
let variant_struct_field_index = Index::from(idx + 1);
|
||||
let (_, ty_generics, _) = ctx.ast.generics.split_for_impl();
|
||||
let has_field_trait = Trait::HasField {
|
||||
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
|
||||
// Since Rust does not presently support explicit visibility
|
||||
// modifiers on enum fields, any public type is suitable here;
|
||||
// we use `()`.
|
||||
field: field.clone(),
|
||||
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
|
||||
};
|
||||
let has_field_path = has_field_trait.crate_path(ctx);
|
||||
let has_field = ImplBlockBuilder::new(
|
||||
ctx,
|
||||
data,
|
||||
has_field_trait,
|
||||
FieldBounds::None,
|
||||
)
|
||||
.inner_extras(quote! {
|
||||
type Type = #ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
|
||||
use #zerocopy_crate::pointer::cast::{CastSized, Projection};
|
||||
|
||||
slf.project::<___ZerocopyRawEnum #ty_generics, CastSized>()
|
||||
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(variants) }>>()
|
||||
.project::<_, Projection<_, { #zerocopy_crate::REPR_C_UNION_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variants_union_field_ident) }>>()
|
||||
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(value) }>>()
|
||||
.project::<_, Projection<_, { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(#variant_struct_field_index) }>>()
|
||||
.as_ptr()
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
let project = ImplBlockBuilder::new(
|
||||
ctx,
|
||||
data,
|
||||
Trait::ProjectField {
|
||||
variant_id: parse_quote!({ #zerocopy_crate::ident_id!(#variant_ident) }),
|
||||
// Since Rust does not presently support explicit visibility
|
||||
// modifiers on enum fields, any public type is suitable
|
||||
// here; we use `()`.
|
||||
field: field.clone(),
|
||||
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
|
||||
invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
|
||||
},
|
||||
FieldBounds::None,
|
||||
)
|
||||
.param_extras(vec![
|
||||
parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
|
||||
parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
|
||||
])
|
||||
.inner_extras(quote! {
|
||||
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
|
||||
type Invariants = (Aliasing, Alignment, #zerocopy_crate::invariant::Initialized);
|
||||
})
|
||||
.build();
|
||||
|
||||
quote! {
|
||||
#has_field
|
||||
#project
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let core = ctx.core_path();
|
||||
let match_arms = data.variants.iter().map(|variant| {
|
||||
let tag_ident = tag_ident(&variant.ident);
|
||||
let variant_struct_ident = variant_struct_ident(&variant.ident);
|
||||
let variants_union_field_ident = variants_union_field_ident(&variant.ident);
|
||||
|
||||
if matches!(variant.fields, Fields::Unit) {
|
||||
// Unit variants don't need any further validation beyond checking
|
||||
// the tag.
|
||||
quote! {
|
||||
#tag_ident => true
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#tag_ident => {
|
||||
// SAFETY: Since we know that the tag is `#tag_ident`, we
|
||||
// know that no other `&`s exist which refer to this enum
|
||||
// as any other variant.
|
||||
let variant_md = variants.cast::<
|
||||
_,
|
||||
#zerocopy_crate::pointer::cast::Projection<
|
||||
// #zerocopy_crate::ReadOnly<_>,
|
||||
_,
|
||||
{ #zerocopy_crate::REPR_C_UNION_VARIANT_ID },
|
||||
{ #zerocopy_crate::ident_id!(#variants_union_field_ident) }
|
||||
>,
|
||||
_
|
||||
>();
|
||||
let variant = variant_md.cast::<
|
||||
#zerocopy_crate::ReadOnly<#variant_struct_ident #ty_generics>,
|
||||
#zerocopy_crate::pointer::cast::CastSized,
|
||||
(#zerocopy_crate::pointer::BecauseRead, _)
|
||||
>();
|
||||
<
|
||||
#variant_struct_ident #ty_generics as #trait_path
|
||||
>::is_bit_valid(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let generics = &ctx.ast.generics;
|
||||
let raw_enum: DeriveInput = parse_quote! {
|
||||
#[repr(C)]
|
||||
struct ___ZerocopyRawEnum #generics {
|
||||
tag: ___ZerocopyOuterTag,
|
||||
variants: ___ZerocopyVariants #ty_generics,
|
||||
}
|
||||
};
|
||||
|
||||
let self_ident = &ctx.ast.ident;
|
||||
let invariants_eq_impl = quote! {
|
||||
// SAFETY: `___ZerocopyRawEnum` is designed to have the same layout,
|
||||
// validity, and invariants as `Self`.
|
||||
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
|
||||
};
|
||||
|
||||
let raw_enum_projections =
|
||||
derive_has_field_struct_union(&ctx.with_input(&raw_enum), &raw_enum.data);
|
||||
|
||||
let raw_enum = quote! {
|
||||
#raw_enum
|
||||
#invariants_eq_impl
|
||||
#raw_enum_projections
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
// SAFETY: We use `is_bit_valid` to validate that the bit pattern of the
|
||||
// enum's tag corresponds to one of the enum's discriminants. Then, we
|
||||
// check the bit validity of each field of the corresponding variant.
|
||||
// Thus, this is a sound implementation of `is_bit_valid`.
|
||||
#[inline]
|
||||
fn is_bit_valid<___ZcAlignment>(
|
||||
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
|
||||
) -> #core::primitive::bool
|
||||
where
|
||||
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
|
||||
{
|
||||
#tag_enum
|
||||
|
||||
type ___ZerocopyTagPrimitive = #zerocopy_crate::util::macro_util::SizeToTag<
|
||||
{ #core::mem::size_of::<___ZerocopyTag>() },
|
||||
>;
|
||||
|
||||
#tag_consts
|
||||
|
||||
type ___ZerocopyOuterTag = #outer_tag_type;
|
||||
type ___ZerocopyInnerTag = #inner_tag_type;
|
||||
|
||||
#variant_structs
|
||||
|
||||
#variants_union
|
||||
|
||||
#raw_enum
|
||||
|
||||
#has_tag
|
||||
|
||||
#(#has_fields)*
|
||||
|
||||
let tag = {
|
||||
// SAFETY:
|
||||
// - The provided cast addresses a subset of the bytes addressed
|
||||
// by `candidate` because it addresses the starting tag of the
|
||||
// enum.
|
||||
// - Because the pointer is cast from `candidate`, it has the
|
||||
// same provenance as it.
|
||||
// - There are no `UnsafeCell`s in the tag because it is a
|
||||
// primitive integer.
|
||||
// - `tag_ptr` is casted from `candidate`, whose referent is
|
||||
// `Initialized`. Since we have not written uninitialized
|
||||
// bytes into the referent, `tag_ptr` is also `Initialized`.
|
||||
//
|
||||
// FIXME(#2874): Revise this to a `cast` once `candidate`
|
||||
// references a `ReadOnly<Self>`.
|
||||
let tag_ptr = unsafe {
|
||||
candidate.reborrow().project_transmute_unchecked::<
|
||||
_,
|
||||
#zerocopy_crate::invariant::Initialized,
|
||||
#zerocopy_crate::pointer::cast::CastSized
|
||||
>()
|
||||
};
|
||||
tag_ptr.recall_validity::<_, (_, (_, _))>().read::<#zerocopy_crate::BecauseImmutable>()
|
||||
};
|
||||
|
||||
let mut raw_enum = candidate.cast::<
|
||||
#zerocopy_crate::ReadOnly<___ZerocopyRawEnum #ty_generics>,
|
||||
#zerocopy_crate::pointer::cast::CastSized,
|
||||
(#zerocopy_crate::pointer::BecauseRead, _)
|
||||
>();
|
||||
|
||||
let variants = #zerocopy_crate::into_inner!(raw_enum.project::<
|
||||
_,
|
||||
{ #zerocopy_crate::STRUCT_VARIANT_ID },
|
||||
{ #zerocopy_crate::ident_id!(variants) }
|
||||
>());
|
||||
|
||||
match tag {
|
||||
#(#match_arms,)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
pub(crate) fn derive_try_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
|
||||
match &ctx.ast.data {
|
||||
Data::Struct(strct) => derive_try_from_bytes_struct(ctx, strct, top_level),
|
||||
Data::Enum(enm) => derive_try_from_bytes_enum(ctx, enm, top_level),
|
||||
Data::Union(unn) => Ok(derive_try_from_bytes_union(ctx, unn, top_level)),
|
||||
}
|
||||
}
|
||||
fn derive_has_field_struct_union(ctx: &Ctx, data: &dyn DataExt) -> TokenStream {
|
||||
let fields = ctx.ast.data.fields();
|
||||
if fields.is_empty() {
|
||||
return quote! {};
|
||||
}
|
||||
|
||||
let field_tokens = fields.iter().map(|(vis, ident, _)| {
|
||||
let ident = ident!(("ẕ{}", ident), ident.span());
|
||||
quote!(
|
||||
#vis enum #ident {}
|
||||
)
|
||||
});
|
||||
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let variant_id: Box<Expr> = match &ctx.ast.data {
|
||||
Data::Struct(_) => parse_quote!({ #zerocopy_crate::STRUCT_VARIANT_ID }),
|
||||
Data::Union(_) => {
|
||||
let is_repr_c = StructUnionRepr::from_attrs(&ctx.ast.attrs)
|
||||
.map(|repr| repr.is_c())
|
||||
.unwrap_or(false);
|
||||
if is_repr_c {
|
||||
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
|
||||
} else {
|
||||
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let core = ctx.core_path();
|
||||
let has_tag = ImplBlockBuilder::new(ctx, data, Trait::HasTag, FieldBounds::None)
|
||||
.inner_extras(quote! {
|
||||
type Tag = ();
|
||||
type ProjectToTag = #zerocopy_crate::pointer::cast::CastToUnit;
|
||||
})
|
||||
.build();
|
||||
let has_fields = fields.iter().map(move |(_, ident, ty)| {
|
||||
let field_token = ident!(("ẕ{}", ident), ident.span());
|
||||
let field: Box<Type> = parse_quote!(#field_token);
|
||||
let field_id: Box<Expr> = parse_quote!({ #zerocopy_crate::ident_id!(#ident) });
|
||||
let has_field_trait = Trait::HasField {
|
||||
variant_id: variant_id.clone(),
|
||||
field: field.clone(),
|
||||
field_id: field_id.clone(),
|
||||
};
|
||||
let has_field_path = has_field_trait.crate_path(ctx);
|
||||
ImplBlockBuilder::new(
|
||||
ctx,
|
||||
data,
|
||||
has_field_trait,
|
||||
FieldBounds::None,
|
||||
)
|
||||
.inner_extras(quote! {
|
||||
type Type = #ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn project(slf: #zerocopy_crate::pointer::PtrInner<'_, Self>) -> *mut <Self as #has_field_path>::Type {
|
||||
let slf = slf.as_ptr();
|
||||
// SAFETY: By invariant on `PtrInner`, `slf` is a non-null
|
||||
// pointer whose referent is zero-sized or lives in a valid
|
||||
// allocation. Since `#ident` is a struct or union field of
|
||||
// `Self`, this projection preserves or shrinks the referent
|
||||
// size, and so the resulting referent also fits in the same
|
||||
// allocation.
|
||||
unsafe { #core::ptr::addr_of_mut!((*slf).#ident) }
|
||||
}
|
||||
}).outer_extras(if matches!(&ctx.ast.data, Data::Struct(..)) {
|
||||
let fields_preserve_alignment = StructUnionRepr::from_attrs(&ctx.ast.attrs)
|
||||
.map(|repr| repr.get_packed().is_none())
|
||||
.unwrap();
|
||||
let alignment = if fields_preserve_alignment {
|
||||
quote! { Alignment }
|
||||
} else {
|
||||
quote! { #zerocopy_crate::invariant::Unaligned }
|
||||
};
|
||||
// SAFETY: See comments on items.
|
||||
ImplBlockBuilder::new(
|
||||
ctx,
|
||||
data,
|
||||
Trait::ProjectField {
|
||||
variant_id: variant_id.clone(),
|
||||
field: field.clone(),
|
||||
field_id: field_id.clone(),
|
||||
invariants: parse_quote!((Aliasing, Alignment, #zerocopy_crate::invariant::Initialized)),
|
||||
},
|
||||
FieldBounds::None,
|
||||
)
|
||||
.param_extras(vec![
|
||||
parse_quote!(Aliasing: #zerocopy_crate::invariant::Aliasing),
|
||||
parse_quote!(Alignment: #zerocopy_crate::invariant::Alignment),
|
||||
])
|
||||
.inner_extras(quote! {
|
||||
// SAFETY: Projection into structs is always infallible.
|
||||
type Error = #zerocopy_crate::util::macro_util::core_reexport::convert::Infallible;
|
||||
// SAFETY: The alignment of the projected `Ptr` is `Unaligned`
|
||||
// if the structure is packed; otherwise inherited from the
|
||||
// outer `Ptr`. If the validity of the outer pointer is
|
||||
// `Initialized`, so too is the validity of its fields.
|
||||
type Invariants = (Aliasing, #alignment, #zerocopy_crate::invariant::Initialized);
|
||||
})
|
||||
.build()
|
||||
} else {
|
||||
quote! {}
|
||||
})
|
||||
.build()
|
||||
});
|
||||
|
||||
const_block(field_tokens.into_iter().chain(Some(has_tag)).chain(has_fields).map(Some))
|
||||
}
|
||||
fn derive_try_from_bytes_struct(
|
||||
ctx: &Ctx,
|
||||
strct: &DataStruct,
|
||||
top_level: Trait,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let fields = strct.fields();
|
||||
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
|
||||
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
|
||||
let core = ctx.core_path();
|
||||
quote!(
|
||||
// SAFETY: We use `is_bit_valid` to validate that each field is
|
||||
// bit-valid, and only return `true` if all of them are. The bit
|
||||
// validity of a struct is just the composition of the bit
|
||||
// validities of its fields, so this is a sound implementation
|
||||
// of `is_bit_valid`.
|
||||
#[inline]
|
||||
fn is_bit_valid<___ZcAlignment>(
|
||||
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
|
||||
) -> #core::primitive::bool
|
||||
where
|
||||
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
|
||||
{
|
||||
true #(&& {
|
||||
let field_candidate = #zerocopy_crate::into_inner!(candidate.reborrow().project::<
|
||||
_,
|
||||
{ #zerocopy_crate::STRUCT_VARIANT_ID },
|
||||
{ #zerocopy_crate::ident_id!(#field_names) }
|
||||
>());
|
||||
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
|
||||
})*
|
||||
}
|
||||
)
|
||||
});
|
||||
Ok(ImplBlockBuilder::new(ctx, strct, Trait::TryFromBytes, FieldBounds::ALL_SELF)
|
||||
.inner_extras(extras)
|
||||
.outer_extras(derive_has_field_struct_union(ctx, strct))
|
||||
.build())
|
||||
}
|
||||
fn derive_try_from_bytes_union(ctx: &Ctx, unn: &DataUnion, top_level: Trait) -> TokenStream {
|
||||
let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
|
||||
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let variant_id: Box<Expr> = {
|
||||
let is_repr_c =
|
||||
StructUnionRepr::from_attrs(&ctx.ast.attrs).map(|repr| repr.is_c()).unwrap_or(false);
|
||||
if is_repr_c {
|
||||
parse_quote!({ #zerocopy_crate::REPR_C_UNION_VARIANT_ID })
|
||||
} else {
|
||||
parse_quote!({ #zerocopy_crate::UNION_VARIANT_ID })
|
||||
}
|
||||
};
|
||||
|
||||
let extras = try_gen_trivial_is_bit_valid(ctx, top_level).unwrap_or_else(|| {
|
||||
let fields = unn.fields();
|
||||
let field_names = fields.iter().map(|(_vis, name, _ty)| name);
|
||||
let field_tys = fields.iter().map(|(_vis, _name, ty)| ty);
|
||||
let core = ctx.core_path();
|
||||
quote!(
|
||||
// SAFETY: We use `is_bit_valid` to validate that any field is
|
||||
// bit-valid; we only return `true` if at least one of them is.
|
||||
// The bit validity of a union is not yet well defined in Rust,
|
||||
// but it is guaranteed to be no more strict than this
|
||||
// definition. See #696 for a more in-depth discussion.
|
||||
#[inline]
|
||||
fn is_bit_valid<___ZcAlignment>(
|
||||
mut candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
|
||||
) -> #core::primitive::bool
|
||||
where
|
||||
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
|
||||
{
|
||||
false #(|| {
|
||||
// SAFETY:
|
||||
// - Since `ReadOnly<Self>: Immutable` unconditionally,
|
||||
// neither `*slf` nor the returned pointer's referent
|
||||
// permit interior mutation.
|
||||
// - Both source and destination validity are
|
||||
// `Initialized`, which is always a sound
|
||||
// transmutation.
|
||||
let field_candidate = unsafe {
|
||||
candidate.reborrow().project_transmute_unchecked::<
|
||||
_,
|
||||
_,
|
||||
#zerocopy_crate::pointer::cast::Projection<
|
||||
_,
|
||||
#variant_id,
|
||||
{ #zerocopy_crate::ident_id!(#field_names) }
|
||||
>
|
||||
>()
|
||||
};
|
||||
|
||||
<#field_tys as #zerocopy_crate::TryFromBytes>::is_bit_valid(field_candidate)
|
||||
})*
|
||||
}
|
||||
)
|
||||
});
|
||||
ImplBlockBuilder::new(ctx, unn, Trait::TryFromBytes, field_type_trait_bounds)
|
||||
.inner_extras(extras)
|
||||
.outer_extras(derive_has_field_struct_union(ctx, unn))
|
||||
.build()
|
||||
}
|
||||
fn derive_try_from_bytes_enum(
|
||||
ctx: &Ctx,
|
||||
enm: &DataEnum,
|
||||
top_level: Trait,
|
||||
) -> Result<TokenStream, Error> {
|
||||
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
|
||||
// If an enum has no fields, it has a well-defined integer representation,
|
||||
// and every possible bit pattern corresponds to a valid discriminant tag,
|
||||
// then it *could* be `FromBytes` (even if the user hasn't derived
|
||||
// `FromBytes`). This holds if, for `repr(uN)` or `repr(iN)`, there are 2^N
|
||||
// variants.
|
||||
let could_be_from_bytes = enum_size_from_repr(&repr)
|
||||
.map(|size| enm.fields().is_empty() && enm.variants.len() == 1usize << size)
|
||||
.unwrap_or(false);
|
||||
|
||||
let trivial_is_bit_valid = try_gen_trivial_is_bit_valid(ctx, top_level);
|
||||
let extra = match (trivial_is_bit_valid, could_be_from_bytes) {
|
||||
(Some(is_bit_valid), _) => is_bit_valid,
|
||||
// SAFETY: It would be sound for the enum to implement `FromBytes`, as
|
||||
// required by `gen_trivial_is_bit_valid_unchecked`.
|
||||
(None, true) => unsafe { gen_trivial_is_bit_valid_unchecked(ctx) },
|
||||
(None, false) => match derive_is_bit_valid(ctx, enm, &repr) {
|
||||
Ok(extra) => extra,
|
||||
Err(_) if ctx.skip_on_error => return Ok(TokenStream::new()),
|
||||
Err(e) => return Err(e),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, enm, Trait::TryFromBytes, FieldBounds::ALL_SELF)
|
||||
.inner_extras(extra)
|
||||
.build())
|
||||
}
|
||||
fn try_gen_trivial_is_bit_valid(ctx: &Ctx, top_level: Trait) -> Option<proc_macro2::TokenStream> {
|
||||
// If the top-level trait is `FromBytes` and `Self` has no type parameters,
|
||||
// then the `FromBytes` derive will fail compilation if `Self` is not
|
||||
// actually soundly `FromBytes`, and so we can rely on that for our
|
||||
// `is_bit_valid` impl. It's plausible that we could make changes - or Rust
|
||||
// could make changes (such as the "trivial bounds" language feature) - that
|
||||
// make this no longer true. To hedge against these, we include an explicit
|
||||
// `Self: FromBytes` check in the generated `is_bit_valid`, which is
|
||||
// bulletproof.
|
||||
//
|
||||
// If `ctx.skip_on_error` is true, we can't rely on the `FromBytes` derive
|
||||
// to fail compilation if `Self` is not actually soundly `FromBytes`.
|
||||
if matches!(top_level, Trait::FromBytes)
|
||||
&& ctx.ast.generics.params.is_empty()
|
||||
&& !ctx.skip_on_error
|
||||
{
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let core = ctx.core_path();
|
||||
Some(quote!(
|
||||
// SAFETY: See inline.
|
||||
#[inline(always)]
|
||||
fn is_bit_valid<___ZcAlignment>(
|
||||
_candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
|
||||
) -> #core::primitive::bool
|
||||
where
|
||||
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
|
||||
{
|
||||
if false {
|
||||
fn assert_is_from_bytes<T>()
|
||||
where
|
||||
T: #zerocopy_crate::FromBytes,
|
||||
T: ?#core::marker::Sized,
|
||||
{
|
||||
}
|
||||
|
||||
assert_is_from_bytes::<Self>();
|
||||
}
|
||||
|
||||
// SAFETY: The preceding code only compiles if `Self:
|
||||
// FromBytes`. Thus, this code only compiles if all initialized
|
||||
// byte sequences represent valid instances of `Self`.
|
||||
true
|
||||
}
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// All initialized bit patterns must be valid for `Self`.
|
||||
unsafe fn gen_trivial_is_bit_valid_unchecked(ctx: &Ctx) -> proc_macro2::TokenStream {
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let core = ctx.core_path();
|
||||
quote!(
|
||||
// SAFETY: The caller of `gen_trivial_is_bit_valid_unchecked` has
|
||||
// promised that all initialized bit patterns are valid for `Self`.
|
||||
#[inline(always)]
|
||||
fn is_bit_valid<___ZcAlignment>(
|
||||
_candidate: #zerocopy_crate::Maybe<'_, Self, ___ZcAlignment>,
|
||||
) -> #core::primitive::bool
|
||||
where
|
||||
___ZcAlignment: #zerocopy_crate::invariant::Alignment,
|
||||
{
|
||||
true
|
||||
}
|
||||
)
|
||||
}
|
||||
78
rust/zerocopy-derive/derive/unaligned.rs
Normal file
78
rust/zerocopy-derive/derive/unaligned.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use proc_macro2::{Span, TokenStream};
|
||||
use syn::{Data, DataEnum, DataStruct, DataUnion, Error};
|
||||
|
||||
use crate::{
|
||||
repr::{EnumRepr, StructUnionRepr},
|
||||
util::{Ctx, FieldBounds, ImplBlockBuilder, Trait},
|
||||
};
|
||||
|
||||
pub(crate) fn derive_unaligned(ctx: &Ctx, _top_level: Trait) -> Result<TokenStream, Error> {
|
||||
match &ctx.ast.data {
|
||||
Data::Struct(strct) => derive_unaligned_struct(ctx, strct),
|
||||
Data::Enum(enm) => derive_unaligned_enum(ctx, enm),
|
||||
Data::Union(unn) => derive_unaligned_union(ctx, unn),
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct is `Unaligned` if:
|
||||
/// - `repr(align)` is no more than 1 and either
|
||||
/// - `repr(C)` or `repr(transparent)` and
|
||||
/// - all fields `Unaligned`
|
||||
/// - `repr(packed)`
|
||||
fn derive_unaligned_struct(ctx: &Ctx, strct: &DataStruct) -> Result<TokenStream, Error> {
|
||||
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
repr.unaligned_validate_no_align_gt_1()?;
|
||||
|
||||
let field_bounds = if repr.is_packed_1() {
|
||||
FieldBounds::None
|
||||
} else if repr.is_c() || repr.is_transparent() {
|
||||
FieldBounds::ALL_SELF
|
||||
} else {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, strct, Trait::Unaligned, field_bounds).build())
|
||||
}
|
||||
|
||||
/// An enum is `Unaligned` if:
|
||||
/// - No `repr(align(N > 1))`
|
||||
/// - `repr(u8)` or `repr(i8)`
|
||||
fn derive_unaligned_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
|
||||
let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
repr.unaligned_validate_no_align_gt_1()?;
|
||||
|
||||
if !repr.is_u8() && !repr.is_i8() {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"must have #[repr(u8)] or #[repr(i8)] attribute in order to guarantee this type's alignment",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, enm, Trait::Unaligned, FieldBounds::ALL_SELF).build())
|
||||
}
|
||||
|
||||
/// Like structs, a union is `Unaligned` if:
|
||||
/// - `repr(align)` is no more than 1 and either
|
||||
/// - `repr(C)` or `repr(transparent)` and
|
||||
/// - all fields `Unaligned`
|
||||
/// - `repr(packed)`
|
||||
fn derive_unaligned_union(ctx: &Ctx, unn: &DataUnion) -> Result<TokenStream, Error> {
|
||||
let repr = StructUnionRepr::from_attrs(&ctx.ast.attrs)?;
|
||||
repr.unaligned_validate_no_align_gt_1()?;
|
||||
|
||||
let field_type_trait_bounds = if repr.is_packed_1() {
|
||||
FieldBounds::None
|
||||
} else if repr.is_c() || repr.is_transparent() {
|
||||
FieldBounds::ALL_SELF
|
||||
} else {
|
||||
return ctx.error_or_skip(Error::new(
|
||||
Span::call_site(),
|
||||
"must have #[repr(C)], #[repr(transparent)], or #[repr(packed)] attribute in order to guarantee this type's alignment",
|
||||
));
|
||||
};
|
||||
|
||||
Ok(ImplBlockBuilder::new(ctx, unn, Trait::Unaligned, field_type_trait_bounds).build())
|
||||
}
|
||||
144
rust/zerocopy-derive/lib.rs
Normal file
144
rust/zerocopy-derive/lib.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// 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.
|
||||
|
||||
//! Derive macros for [zerocopy]'s traits.
|
||||
//!
|
||||
//! [zerocopy]: https://docs.rs/zerocopy
|
||||
|
||||
// Sometimes we want to use lints which were added after our MSRV.
|
||||
// `unknown_lints` is `warn` by default and we deny warnings in CI, so without
|
||||
// this attribute, any unknown lint would cause a CI failure when testing with
|
||||
// our MSRV.
|
||||
#![allow(unknown_lints)]
|
||||
#![deny(renamed_and_removed_lints)]
|
||||
#![deny(
|
||||
clippy::all,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::multiple_unsafe_ops_per_block,
|
||||
clippy::undocumented_unsafe_blocks
|
||||
)]
|
||||
// We defer to own discretion on type complexity.
|
||||
#![allow(clippy::type_complexity)]
|
||||
// Inlining format args isn't supported on our MSRV.
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
#![deny(
|
||||
rustdoc::bare_urls,
|
||||
rustdoc::broken_intra_doc_links,
|
||||
rustdoc::invalid_codeblock_attributes,
|
||||
rustdoc::invalid_html_tags,
|
||||
rustdoc::invalid_rust_codeblocks,
|
||||
rustdoc::missing_crate_level_docs,
|
||||
rustdoc::private_intra_doc_links
|
||||
)]
|
||||
#![recursion_limit = "128"]
|
||||
|
||||
macro_rules! ident {
|
||||
(($fmt:literal $(, $arg:expr)*), $span:expr) => {
|
||||
syn::Ident::new(&format!($fmt $(, crate::util::to_ident_str($arg))*), $span)
|
||||
};
|
||||
}
|
||||
|
||||
mod derive;
|
||||
#[cfg(test)]
|
||||
mod output_tests;
|
||||
mod repr;
|
||||
mod util;
|
||||
|
||||
use syn::{DeriveInput, Error};
|
||||
|
||||
use crate::util::*;
|
||||
|
||||
// FIXME(https://github.com/rust-lang/rust/issues/54140): Some errors could be
|
||||
// made better if we could add multiple lines of error output like this:
|
||||
//
|
||||
// error: unsupported representation
|
||||
// --> enum.rs:28:8
|
||||
// |
|
||||
// 28 | #[repr(transparent)]
|
||||
// |
|
||||
// help: required by the derive of FromBytes
|
||||
//
|
||||
// Instead, we have more verbose error messages like "unsupported representation
|
||||
// for deriving FromZeros, FromBytes, IntoBytes, or Unaligned on an enum"
|
||||
//
|
||||
// This will probably require Span::error
|
||||
// (https://doc.rust-lang.org/nightly/proc_macro/struct.Span.html#method.error),
|
||||
// which is currently unstable. Revisit this once it's stable.
|
||||
|
||||
/// Defines a derive function named `$outer` which parses its input
|
||||
/// `TokenStream` as a `DeriveInput` and then invokes the `$inner` function.
|
||||
///
|
||||
/// Note that the separate `$outer` parameter is required - proc macro functions
|
||||
/// are currently required to live at the crate root, and so the caller must
|
||||
/// specify the name in order to avoid name collisions.
|
||||
macro_rules! derive {
|
||||
($trait:ident => $outer:ident => $inner:path) => {
|
||||
#[proc_macro_derive($trait, attributes(zerocopy))]
|
||||
pub fn $outer(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse_macro_input!(ts as DeriveInput);
|
||||
let ctx = match Ctx::try_from_derive_input(ast) {
|
||||
Ok(ctx) => ctx,
|
||||
Err(e) => return e.into_compile_error().into(),
|
||||
};
|
||||
let ts = $inner(&ctx, Trait::$trait).into_ts();
|
||||
// We wrap in `const_block` as a backstop in case any derive fails
|
||||
// to wrap its output in `const_block` (and thus fails to annotate)
|
||||
// with the full set of `#[allow(...)]` attributes).
|
||||
let ts = const_block([Some(ts)]);
|
||||
#[cfg(test)]
|
||||
crate::util::testutil::check_hygiene(ts.clone());
|
||||
ts.into()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trait IntoTokenStream {
|
||||
fn into_ts(self) -> proc_macro2::TokenStream;
|
||||
}
|
||||
|
||||
impl IntoTokenStream for proc_macro2::TokenStream {
|
||||
fn into_ts(self) -> proc_macro2::TokenStream {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTokenStream for Result<proc_macro2::TokenStream, Error> {
|
||||
fn into_ts(self) -> proc_macro2::TokenStream {
|
||||
match self {
|
||||
Ok(ts) => ts,
|
||||
Err(err) => err.to_compile_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
derive!(KnownLayout => derive_known_layout => crate::derive::known_layout::derive);
|
||||
derive!(Immutable => derive_immutable => crate::derive::derive_immutable);
|
||||
derive!(TryFromBytes => derive_try_from_bytes => crate::derive::try_from_bytes::derive_try_from_bytes);
|
||||
derive!(FromZeros => derive_from_zeros => crate::derive::from_bytes::derive_from_zeros);
|
||||
derive!(FromBytes => derive_from_bytes => crate::derive::from_bytes::derive_from_bytes);
|
||||
derive!(IntoBytes => derive_into_bytes => crate::derive::into_bytes::derive_into_bytes);
|
||||
derive!(Unaligned => derive_unaligned => crate::derive::unaligned::derive_unaligned);
|
||||
derive!(ByteHash => derive_hash => crate::derive::derive_hash);
|
||||
derive!(ByteEq => derive_eq => crate::derive::derive_eq);
|
||||
derive!(SplitAt => derive_split_at => crate::derive::derive_split_at);
|
||||
|
||||
/// Deprecated: prefer [`FromZeros`] instead.
|
||||
#[deprecated(since = "0.8.0", note = "`FromZeroes` was renamed to `FromZeros`")]
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_derive(FromZeroes)]
|
||||
pub fn derive_from_zeroes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
derive_from_zeros(ts)
|
||||
}
|
||||
|
||||
/// Deprecated: prefer [`IntoBytes`] instead.
|
||||
#[deprecated(since = "0.8.0", note = "`AsBytes` was renamed to `IntoBytes`")]
|
||||
#[doc(hidden)]
|
||||
#[proc_macro_derive(AsBytes)]
|
||||
pub fn derive_as_bytes(ts: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
derive_into_bytes(ts)
|
||||
}
|
||||
849
rust/zerocopy-derive/repr.rs
Normal file
849
rust/zerocopy-derive/repr.rs
Normal file
|
|
@ -0,0 +1,849 @@
|
|||
// 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::NonZeroU32,
|
||||
};
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote_spanned, ToTokens, TokenStreamExt as _};
|
||||
use syn::{
|
||||
punctuated::Punctuated, spanned::Spanned as _, token::Comma, Attribute, Error, LitInt, Meta,
|
||||
MetaList,
|
||||
};
|
||||
|
||||
/// 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.
|
||||
#[cfg_attr(test, derive(Copy, Clone, Debug))]
|
||||
pub(crate) 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)`.
|
||||
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
|
||||
pub(crate) enum CompoundRepr<Prim> {
|
||||
C,
|
||||
Rust,
|
||||
Primitive(Prim),
|
||||
}
|
||||
|
||||
/// `repr(Int)`
|
||||
#[derive(Copy, Clone)]
|
||||
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
||||
pub(crate) enum PrimitiveRepr {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
Usize,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
Isize,
|
||||
}
|
||||
|
||||
/// `repr(packed(...))` or `repr(align(...))`
|
||||
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
|
||||
pub(crate) enum AlignRepr<Packed> {
|
||||
Packed(Packed),
|
||||
Align(NonZeroU32),
|
||||
}
|
||||
|
||||
/// The representations which can legally appear on a struct or union type.
|
||||
pub(crate) type StructUnionRepr = Repr<Infallible, NonZeroU32>;
|
||||
|
||||
/// The representations which can legally appear on an enum type.
|
||||
pub(crate) type EnumRepr = Repr<PrimitiveRepr, Infallible>;
|
||||
|
||||
impl<Prim, Packed> Repr<Prim, Packed> {
|
||||
/// Gets the name of this "repr type" - the non-align `repr(X)` that is used
|
||||
/// in prose to refer to this type.
|
||||
///
|
||||
/// For example, we would refer to `#[repr(C, align(4))] struct Foo { ... }`
|
||||
/// as a "`repr(C)` struct".
|
||||
pub(crate) fn repr_type_name(&self) -> &str
|
||||
where
|
||||
Prim: Copy + With<PrimitiveRepr>,
|
||||
{
|
||||
use CompoundRepr::*;
|
||||
use PrimitiveRepr::*;
|
||||
use Repr::*;
|
||||
match self {
|
||||
Transparent(_span) => "repr(transparent)",
|
||||
Compound(Spanned { t: repr, span: _ }, _align) => match repr {
|
||||
C => "repr(C)",
|
||||
Rust => "repr(Rust)",
|
||||
Primitive(prim) => prim.with(|prim| match prim {
|
||||
U8 => "repr(u8)",
|
||||
U16 => "repr(u16)",
|
||||
U32 => "repr(u32)",
|
||||
U64 => "repr(u64)",
|
||||
U128 => "repr(u128)",
|
||||
Usize => "repr(usize)",
|
||||
I8 => "repr(i8)",
|
||||
I16 => "repr(i16)",
|
||||
I32 => "repr(i32)",
|
||||
I64 => "repr(i64)",
|
||||
I128 => "repr(i128)",
|
||||
Isize => "repr(isize)",
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_transparent(&self) -> bool {
|
||||
matches!(self, Repr::Transparent(_))
|
||||
}
|
||||
|
||||
pub(crate) fn is_c(&self) -> bool {
|
||||
use CompoundRepr::*;
|
||||
matches!(self, Repr::Compound(Spanned { t: C, span: _ }, _align))
|
||||
}
|
||||
|
||||
pub(crate) fn is_primitive(&self) -> bool {
|
||||
use CompoundRepr::*;
|
||||
matches!(self, Repr::Compound(Spanned { t: Primitive(_), span: _ }, _align))
|
||||
}
|
||||
|
||||
pub(crate) fn get_packed(&self) -> Option<&Packed> {
|
||||
use AlignRepr::*;
|
||||
use Repr::*;
|
||||
if let Compound(_, Some(Spanned { t: Packed(p), span: _ })) = self {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_align(&self) -> Option<Spanned<NonZeroU32>> {
|
||||
use AlignRepr::*;
|
||||
use Repr::*;
|
||||
if let Compound(_, Some(Spanned { t: Align(n), span })) = self {
|
||||
Some(Spanned::new(*n, *span))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_align_gt_1(&self) -> bool {
|
||||
self.get_align().map(|n| n.t.get() > 1).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// When deriving `Unaligned`, validate that the decorated type has no
|
||||
/// `#[repr(align(N))]` attribute where `N > 1`. If no such attribute exists
|
||||
/// (including if `N == 1`), this returns `Ok(())`, and otherwise it returns
|
||||
/// a descriptive error.
|
||||
pub(crate) fn unaligned_validate_no_align_gt_1(&self) -> Result<(), Error> {
|
||||
if let Some(n) = self.get_align().filter(|n| n.t.get() > 1) {
|
||||
Err(Error::new(
|
||||
n.span,
|
||||
"cannot derive `Unaligned` on type with alignment greater than 1",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prim> Repr<Prim, NonZeroU32> {
|
||||
/// Does `self` describe a `#[repr(packed)]` or `#[repr(packed(1))]` type?
|
||||
pub(crate) fn is_packed_1(&self) -> bool {
|
||||
self.get_packed().map(|n| n.get() == 1).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Packed> Repr<PrimitiveRepr, Packed> {
|
||||
fn get_primitive(&self) -> Option<&PrimitiveRepr> {
|
||||
use CompoundRepr::*;
|
||||
use Repr::*;
|
||||
if let Compound(Spanned { t: Primitive(p), span: _ }, _align) = self {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Does `self` describe a `#[repr(u8)]` type?
|
||||
pub(crate) fn is_u8(&self) -> bool {
|
||||
matches!(self.get_primitive(), Some(PrimitiveRepr::U8))
|
||||
}
|
||||
|
||||
/// Does `self` describe a `#[repr(i8)]` type?
|
||||
pub(crate) fn is_i8(&self) -> bool {
|
||||
matches!(self.get_primitive(), Some(PrimitiveRepr::I8))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prim, Packed> ToTokens for Repr<Prim, Packed>
|
||||
where
|
||||
Prim: With<PrimitiveRepr> + Copy,
|
||||
Packed: With<NonZeroU32> + Copy,
|
||||
{
|
||||
fn to_tokens(&self, ts: &mut TokenStream) {
|
||||
use Repr::*;
|
||||
match self {
|
||||
Transparent(span) => ts.append_all(quote_spanned! { *span=> #[repr(transparent)] }),
|
||||
Compound(repr, align) => {
|
||||
repr.to_tokens(ts);
|
||||
if let Some(align) = align {
|
||||
align.to_tokens(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prim: With<PrimitiveRepr> + Copy> ToTokens for Spanned<CompoundRepr<Prim>> {
|
||||
fn to_tokens(&self, ts: &mut TokenStream) {
|
||||
use CompoundRepr::*;
|
||||
match &self.t {
|
||||
C => ts.append_all(quote_spanned! { self.span=> #[repr(C)] }),
|
||||
Rust => ts.append_all(quote_spanned! { self.span=> #[repr(Rust)] }),
|
||||
Primitive(prim) => prim.with(|prim| Spanned::new(prim, self.span).to_tokens(ts)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Spanned<PrimitiveRepr> {
|
||||
fn to_tokens(&self, ts: &mut TokenStream) {
|
||||
use PrimitiveRepr::*;
|
||||
match self.t {
|
||||
U8 => ts.append_all(quote_spanned! { self.span => #[repr(u8)] }),
|
||||
U16 => ts.append_all(quote_spanned! { self.span => #[repr(u16)] }),
|
||||
U32 => ts.append_all(quote_spanned! { self.span => #[repr(u32)] }),
|
||||
U64 => ts.append_all(quote_spanned! { self.span => #[repr(u64)] }),
|
||||
U128 => ts.append_all(quote_spanned! { self.span => #[repr(u128)] }),
|
||||
Usize => ts.append_all(quote_spanned! { self.span => #[repr(usize)] }),
|
||||
I8 => ts.append_all(quote_spanned! { self.span => #[repr(i8)] }),
|
||||
I16 => ts.append_all(quote_spanned! { self.span => #[repr(i16)] }),
|
||||
I32 => ts.append_all(quote_spanned! { self.span => #[repr(i32)] }),
|
||||
I64 => ts.append_all(quote_spanned! { self.span => #[repr(i64)] }),
|
||||
I128 => ts.append_all(quote_spanned! { self.span => #[repr(i128)] }),
|
||||
Isize => ts.append_all(quote_spanned! { self.span => #[repr(isize)] }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Packed: With<NonZeroU32> + Copy> ToTokens for Spanned<AlignRepr<Packed>> {
|
||||
fn to_tokens(&self, ts: &mut TokenStream) {
|
||||
use AlignRepr::*;
|
||||
// We use `syn::Index` instead of `u32` because `quote_spanned!`
|
||||
// serializes `u32` literals as `123u32`, not just `123`. Rust doesn't
|
||||
// recognize that as a valid argument to `#[repr(align(...))]` or
|
||||
// `#[repr(packed(...))]`.
|
||||
let to_index = |n: NonZeroU32| syn::Index { index: n.get(), span: self.span };
|
||||
match self.t {
|
||||
Packed(n) => n.with(|n| {
|
||||
let n = to_index(n);
|
||||
ts.append_all(quote_spanned! { self.span => #[repr(packed(#n))] })
|
||||
}),
|
||||
Align(n) => {
|
||||
let n = to_index(n);
|
||||
ts.append_all(quote_spanned! { self.span => #[repr(align(#n))] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of parsing a single `#[repr(...)]` attribute or a single
|
||||
/// directive inside a compound `#[repr(..., ...)]` attribute.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub(crate) enum RawRepr {
|
||||
Transparent,
|
||||
C,
|
||||
Rust,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
Usize,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
Isize,
|
||||
Align(NonZeroU32),
|
||||
PackedN(NonZeroU32),
|
||||
Packed,
|
||||
}
|
||||
|
||||
/// The error from converting from a `RawRepr`.
|
||||
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
|
||||
pub(crate) 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),
|
||||
}
|
||||
|
||||
/// The representation hint is not supported for the decorated type.
|
||||
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
|
||||
pub(crate) struct UnsupportedReprError;
|
||||
|
||||
impl<Prim: With<PrimitiveRepr>> TryFrom<RawRepr> for CompoundRepr<Prim> {
|
||||
type Error = FromRawReprError<UnsupportedReprError>;
|
||||
fn try_from(
|
||||
raw: RawRepr,
|
||||
) -> Result<CompoundRepr<Prim>, FromRawReprError<UnsupportedReprError>> {
|
||||
use RawRepr::*;
|
||||
match raw {
|
||||
C => Ok(CompoundRepr::C),
|
||||
Rust => Ok(CompoundRepr::Rust),
|
||||
raw @ (U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize) => {
|
||||
Prim::try_with_or(
|
||||
|| match raw {
|
||||
U8 => Ok(PrimitiveRepr::U8),
|
||||
U16 => Ok(PrimitiveRepr::U16),
|
||||
U32 => Ok(PrimitiveRepr::U32),
|
||||
U64 => Ok(PrimitiveRepr::U64),
|
||||
U128 => Ok(PrimitiveRepr::U128),
|
||||
Usize => Ok(PrimitiveRepr::Usize),
|
||||
I8 => Ok(PrimitiveRepr::I8),
|
||||
I16 => Ok(PrimitiveRepr::I16),
|
||||
I32 => Ok(PrimitiveRepr::I32),
|
||||
I64 => Ok(PrimitiveRepr::I64),
|
||||
I128 => Ok(PrimitiveRepr::I128),
|
||||
Isize => Ok(PrimitiveRepr::Isize),
|
||||
Transparent | C | Rust | Align(_) | PackedN(_) | Packed => {
|
||||
Err(UnsupportedReprError)
|
||||
}
|
||||
},
|
||||
UnsupportedReprError,
|
||||
)
|
||||
.map(CompoundRepr::Primitive)
|
||||
.map_err(FromRawReprError::Err)
|
||||
}
|
||||
Transparent | Align(_) | PackedN(_) | Packed => Err(FromRawReprError::None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Pcked: With<NonZeroU32>> TryFrom<RawRepr> for AlignRepr<Pcked> {
|
||||
type Error = FromRawReprError<UnsupportedReprError>;
|
||||
fn try_from(raw: RawRepr) -> Result<AlignRepr<Pcked>, FromRawReprError<UnsupportedReprError>> {
|
||||
use RawRepr::*;
|
||||
match raw {
|
||||
Packed | PackedN(_) => Pcked::try_with_or(
|
||||
|| match raw {
|
||||
Packed => Ok(NonZeroU32::new(1).unwrap()),
|
||||
PackedN(n) => Ok(n),
|
||||
U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
|
||||
| Transparent | C | Rust | Align(_) => Err(UnsupportedReprError),
|
||||
},
|
||||
UnsupportedReprError,
|
||||
)
|
||||
.map(AlignRepr::Packed)
|
||||
.map_err(FromRawReprError::Err),
|
||||
Align(n) => Ok(AlignRepr::Align(n)),
|
||||
U8 | U16 | U32 | U64 | U128 | Usize | I8 | I16 | I32 | I64 | I128 | Isize
|
||||
| Transparent | C | Rust => Err(FromRawReprError::None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error from extracting a high-level repr type from a list of `RawRepr`s.
|
||||
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
|
||||
enum FromRawReprsError<E> {
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// 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: Option<Spanned<R>>, 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 let Some(found) = found {
|
||||
// We already found an `R`, but this `RawRepr` also converts to an
|
||||
// `R`, so that's a conflict.
|
||||
//
|
||||
// `Span::join` returns `None` if the two spans are from different
|
||||
// files or if we're not on the nightly compiler. In that case, just
|
||||
// use `new`'s span.
|
||||
let span = found.span.join(new.span).unwrap_or(new.span);
|
||||
Err(Spanned::new(FromRawReprsError::Conflict, span))
|
||||
} else {
|
||||
Ok(Some(new))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// The error returned from [`Repr::from_attrs`].
|
||||
#[cfg_attr(test, derive(Copy, Clone, Debug, Eq, PartialEq))]
|
||||
enum FromAttrsError {
|
||||
FromRawReprs(FromRawReprsError<UnsupportedReprError>),
|
||||
Unrecognized,
|
||||
}
|
||||
|
||||
impl From<FromRawReprsError<UnsupportedReprError>> for FromAttrsError {
|
||||
fn from(err: FromRawReprsError<UnsupportedReprError>) -> FromAttrsError {
|
||||
FromAttrsError::FromRawReprs(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnrecognizedReprError> for FromAttrsError {
|
||||
fn from(_err: UnrecognizedReprError) -> FromAttrsError {
|
||||
FromAttrsError::Unrecognized
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Spanned<FromAttrsError>> for Error {
|
||||
fn from(err: Spanned<FromAttrsError>) -> Error {
|
||||
let Spanned { t: err, span } = err;
|
||||
match err {
|
||||
FromAttrsError::FromRawReprs(FromRawReprsError::Single(
|
||||
_err @ UnsupportedReprError,
|
||||
)) => Error::new(span, "unsupported representation hint for the decorated type"),
|
||||
FromAttrsError::FromRawReprs(FromRawReprsError::Conflict) => {
|
||||
// NOTE: This says "another" rather than "a preceding" because
|
||||
// when one of the reprs involved is `transparent`, we detect
|
||||
// that condition in `Repr::from_attrs`, and at that point we
|
||||
// can't tell which repr came first, so we might report this on
|
||||
// the first involved repr rather than the second, third, etc.
|
||||
Error::new(span, "this conflicts with another representation hint")
|
||||
}
|
||||
FromAttrsError::Unrecognized => Error::new(span, "unrecognized representation hint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prim, Packed> Repr<Prim, Packed> {
|
||||
fn from_attrs_inner(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Spanned<FromAttrsError>>
|
||||
where
|
||||
Prim: With<PrimitiveRepr>,
|
||||
Packed: With<NonZeroU32>,
|
||||
{
|
||||
let raw_reprs = RawRepr::from_attrs(attrs).map_err(Spanned::from)?;
|
||||
|
||||
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(
|
||||
FromAttrsError::FromRawReprs(FromRawReprsError::Conflict),
|
||||
*second,
|
||||
))
|
||||
}
|
||||
// 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()).map_err(Spanned::from)?;
|
||||
let align: Option<Spanned<AlignRepr<Packed>>> =
|
||||
try_from_raw_reprs(raw_reprs.iter()).map_err(Spanned::from)?;
|
||||
|
||||
if let Some(span) = transparent {
|
||||
if compound.is_some() || align.is_some() {
|
||||
// Arbitrarily report the problem on the `transparent` span. Any
|
||||
// span will do.
|
||||
return Err(Spanned::new(FromRawReprsError::Conflict.into(), span));
|
||||
}
|
||||
|
||||
Ok(Repr::Transparent(span))
|
||||
} else {
|
||||
Ok(Repr::Compound(
|
||||
compound.unwrap_or(Spanned::new(CompoundRepr::Rust, Span::call_site())),
|
||||
align,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Prim, Packed> Repr<Prim, Packed> {
|
||||
pub(crate) fn from_attrs(attrs: &[Attribute]) -> Result<Repr<Prim, Packed>, Error>
|
||||
where
|
||||
Prim: With<PrimitiveRepr>,
|
||||
Packed: With<NonZeroU32>,
|
||||
{
|
||||
Repr::from_attrs_inner(attrs).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation hint could not be parsed or was unrecognized.
|
||||
struct UnrecognizedReprError;
|
||||
|
||||
impl RawRepr {
|
||||
fn from_attrs(
|
||||
attrs: &[Attribute],
|
||||
) -> Result<Vec<Spanned<RawRepr>>, Spanned<UnrecognizedReprError>> {
|
||||
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(Spanned::new(
|
||||
UnrecognizedReprError,
|
||||
meta_list.tokens.span(),
|
||||
))
|
||||
}
|
||||
};
|
||||
for meta in parsed {
|
||||
let s = meta.span();
|
||||
reprs.push(
|
||||
RawRepr::from_meta(&meta)
|
||||
.map(|r| Spanned::new(r, s))
|
||||
.map_err(|e| Spanned::new(e, s))?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(reprs)
|
||||
}
|
||||
|
||||
fn from_meta(meta: &Meta) -> Result<RawRepr, UnrecognizedReprError> {
|
||||
let (path, list) = match meta {
|
||||
Meta::Path(path) => (path, None),
|
||||
Meta::List(list) => (&list.path, Some(list)),
|
||||
_ => return Err(UnrecognizedReprError),
|
||||
};
|
||||
|
||||
let ident = path.get_ident().ok_or(UnrecognizedReprError)?;
|
||||
|
||||
// Only returns `Ok` for non-zero power-of-two values.
|
||||
let parse_nzu64 = |list: &MetaList| {
|
||||
list.parse_args::<LitInt>()
|
||||
.and_then(|int| int.base10_parse::<NonZeroU32>())
|
||||
.map_err(|_| UnrecognizedReprError)
|
||||
.and_then(|nz| {
|
||||
if nz.get().is_power_of_two() {
|
||||
Ok(nz)
|
||||
} else {
|
||||
Err(UnrecognizedReprError)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
use RawRepr::*;
|
||||
Ok(match (ident.to_string().as_str(), list) {
|
||||
("u8", None) => U8,
|
||||
("u16", None) => U16,
|
||||
("u32", None) => U32,
|
||||
("u64", None) => U64,
|
||||
("u128", None) => U128,
|
||||
("usize", None) => Usize,
|
||||
("i8", None) => I8,
|
||||
("i16", None) => I16,
|
||||
("i32", None) => I32,
|
||||
("i64", None) => I64,
|
||||
("i128", None) => I128,
|
||||
("isize", None) => Isize,
|
||||
("C", None) => C,
|
||||
("transparent", None) => Transparent,
|
||||
("Rust", None) => Rust,
|
||||
("packed", None) => Packed,
|
||||
("packed", Some(list)) => PackedN(parse_nzu64(list)?),
|
||||
("align", Some(list)) => Align(parse_nzu64(list)?),
|
||||
_ => return Err(UnrecognizedReprError),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) use util::*;
|
||||
mod util {
|
||||
use super::*;
|
||||
/// A value with an associated span.
|
||||
#[derive(Copy, Clone)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub(crate) struct Spanned<T> {
|
||||
pub(crate) t: T,
|
||||
pub(crate) span: Span,
|
||||
}
|
||||
|
||||
impl<T> Spanned<T> {
|
||||
pub(super) fn new(t: T, span: Span) -> Spanned<T> {
|
||||
Spanned { t, span }
|
||||
}
|
||||
|
||||
pub(super) fn from<U>(s: Spanned<U>) -> Spanned<T>
|
||||
where
|
||||
T: From<U>,
|
||||
{
|
||||
let Spanned { t: u, span } = s;
|
||||
Spanned::new(u.into(), span)
|
||||
}
|
||||
|
||||
/// Delegates to `T: TryFrom`, preserving span information in both the
|
||||
/// success and error cases.
|
||||
pub(super) 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)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Used to permit implementing `With<T> for T: Inhabited` and for
|
||||
// `Infallible` without a blanket impl conflict.
|
||||
pub(crate) trait Inhabited {}
|
||||
impl Inhabited for PrimitiveRepr {}
|
||||
impl Inhabited for NonZeroU32 {}
|
||||
|
||||
pub(crate) trait With<T> {
|
||||
fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O;
|
||||
fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, err: E) -> Result<Self, E>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<T: Inhabited> With<T> for T {
|
||||
fn with<O, F: FnOnce(T) -> O>(self, f: F) -> O {
|
||||
f(self)
|
||||
}
|
||||
|
||||
fn try_with_or<E, F: FnOnce() -> Result<T, E>>(f: F, _err: E) -> Result<Self, E> {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> With<T> for Infallible {
|
||||
fn with<O, F: FnOnce(T) -> O>(self, _f: F) -> O {
|
||||
match self {}
|
||||
}
|
||||
|
||||
fn try_with_or<E, F: FnOnce() -> Result<T, E>>(_f: F, err: E) -> Result<Self, E> {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use syn::parse_quote;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T> From<T> for Spanned<T> {
|
||||
fn from(t: T) -> Spanned<T> {
|
||||
Spanned::new(t, Span::call_site())
|
||||
}
|
||||
}
|
||||
|
||||
// We ignore spans for equality in testing since real spans are hard to
|
||||
// synthesize and don't implement `PartialEq`.
|
||||
impl<T: PartialEq> PartialEq for Spanned<T> {
|
||||
fn eq(&self, other: &Spanned<T>) -> bool {
|
||||
self.t.eq(&other.t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq> Eq for Spanned<T> {}
|
||||
|
||||
impl<Prim: PartialEq, Packed: PartialEq> PartialEq for Repr<Prim, Packed> {
|
||||
fn eq(&self, other: &Repr<Prim, Packed>) -> bool {
|
||||
match (self, other) {
|
||||
(Repr::Transparent(_), Repr::Transparent(_)) => true,
|
||||
(Repr::Compound(sc, sa), Repr::Compound(oc, oa)) => (sc, sa) == (oc, oa),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn s() -> Span {
|
||||
Span::call_site()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
// Test that a given `#[repr(...)]` attribute parses and returns the
|
||||
// given `Repr` or error.
|
||||
macro_rules! test {
|
||||
($(#[$attr:meta])* => $repr:expr) => {
|
||||
test!(@inner $(#[$attr])* => Repr => Ok($repr));
|
||||
};
|
||||
// In the error case, the caller must explicitly provide the name of
|
||||
// the `Repr` type to assist in type inference.
|
||||
(@error $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
|
||||
test!(@inner $(#[$attr])* => $typ => Err($repr));
|
||||
};
|
||||
(@inner $(#[$attr:meta])* => $typ:ident => $repr:expr) => {
|
||||
let attr: Attribute = parse_quote!($(#[$attr])*);
|
||||
let mut got = $typ::from_attrs_inner(&[attr]);
|
||||
let expect: Result<Repr<_, _>, _> = $repr;
|
||||
if false {
|
||||
// Force Rust to infer `got` as having the same type as
|
||||
// `expect`.
|
||||
got = expect;
|
||||
}
|
||||
assert_eq!(got, expect, stringify!($(#[$attr])*));
|
||||
};
|
||||
}
|
||||
|
||||
use AlignRepr::*;
|
||||
use CompoundRepr::*;
|
||||
use PrimitiveRepr::*;
|
||||
let nz = |n: u32| NonZeroU32::new(n).unwrap();
|
||||
|
||||
test!(#[repr(transparent)] => StructUnionRepr::Transparent(s()));
|
||||
test!(#[repr()] => StructUnionRepr::Compound(Rust.into(), None));
|
||||
test!(#[repr(packed)] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(1)).into())));
|
||||
test!(#[repr(packed(2))] => StructUnionRepr::Compound(Rust.into(), Some(Packed(nz(2)).into())));
|
||||
test!(#[repr(align(1))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
|
||||
test!(#[repr(align(2))] => StructUnionRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
|
||||
test!(#[repr(C)] => StructUnionRepr::Compound(C.into(), None));
|
||||
test!(#[repr(C, packed)] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(1)).into())));
|
||||
test!(#[repr(C, packed(2))] => StructUnionRepr::Compound(C.into(), Some(Packed(nz(2)).into())));
|
||||
test!(#[repr(C, align(1))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(1)).into())));
|
||||
test!(#[repr(C, align(2))] => StructUnionRepr::Compound(C.into(), Some(Align(nz(2)).into())));
|
||||
|
||||
test!(#[repr(transparent)] => EnumRepr::Transparent(s()));
|
||||
test!(#[repr()] => EnumRepr::Compound(Rust.into(), None));
|
||||
test!(#[repr(align(1))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(1)).into())));
|
||||
test!(#[repr(align(2))] => EnumRepr::Compound(Rust.into(), Some(Align(nz(2)).into())));
|
||||
|
||||
macro_rules! for_each_compound_repr {
|
||||
($($r:tt => $var:expr),*) => {
|
||||
$(
|
||||
test!(#[repr($r)] => EnumRepr::Compound($var.into(), None));
|
||||
test!(#[repr($r, align(1))] => EnumRepr::Compound($var.into(), Some(Align(nz(1)).into())));
|
||||
test!(#[repr($r, align(2))] => EnumRepr::Compound($var.into(), Some(Align(nz(2)).into())));
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
for_each_compound_repr!(
|
||||
C => C,
|
||||
u8 => Primitive(U8),
|
||||
u16 => Primitive(U16),
|
||||
u32 => Primitive(U32),
|
||||
u64 => Primitive(U64),
|
||||
usize => Primitive(Usize),
|
||||
i8 => Primitive(I8),
|
||||
i16 => Primitive(I16),
|
||||
i32 => Primitive(I32),
|
||||
i64 => Primitive(I64),
|
||||
isize => Primitive(Isize)
|
||||
);
|
||||
|
||||
use FromAttrsError::*;
|
||||
use FromRawReprsError::*;
|
||||
|
||||
// Run failure tests which are valid for both `StructUnionRepr` and
|
||||
// `EnumRepr`.
|
||||
macro_rules! for_each_repr_type {
|
||||
($($repr:ident),*) => {
|
||||
$(
|
||||
// Invalid packed or align attributes
|
||||
test!(@error #[repr(packed(0))] => $repr => Unrecognized.into());
|
||||
test!(@error #[repr(packed(3))] => $repr => Unrecognized.into());
|
||||
test!(@error #[repr(align(0))] => $repr => Unrecognized.into());
|
||||
test!(@error #[repr(align(3))] => $repr => Unrecognized.into());
|
||||
|
||||
// Conflicts
|
||||
test!(@error #[repr(transparent, transparent)] => $repr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(transparent, C)] => $repr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(transparent, Rust)] => $repr => FromRawReprs(Conflict).into());
|
||||
|
||||
test!(@error #[repr(C, transparent)] => $repr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(C, C)] => $repr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(C, Rust)] => $repr => FromRawReprs(Conflict).into());
|
||||
|
||||
test!(@error #[repr(Rust, transparent)] => $repr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(Rust, C)] => $repr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(Rust, Rust)] => $repr => FromRawReprs(Conflict).into());
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
for_each_repr_type!(StructUnionRepr, EnumRepr);
|
||||
|
||||
// Enum-specific conflicts.
|
||||
//
|
||||
// We don't bother to test every combination since that would be a huge
|
||||
// number (enums can have primitive reprs u8, u16, u32, u64, usize, i8,
|
||||
// i16, i32, i64, and isize). Instead, since the conflict logic doesn't
|
||||
// care what specific value of `PrimitiveRepr` is present, we assume
|
||||
// that testing against u8 alone is fine.
|
||||
test!(@error #[repr(transparent, u8)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(u8, transparent)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(C, u8)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(u8, C)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(Rust, u8)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(u8, Rust)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
test!(@error #[repr(u8, u8)] => EnumRepr => FromRawReprs(Conflict).into());
|
||||
|
||||
// Illegal struct/union reprs
|
||||
test!(@error #[repr(u8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(u16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(u32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(u64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(usize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(i8)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(i16)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(i32)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(i64)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(isize)] => StructUnionRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
|
||||
// Illegal enum reprs
|
||||
test!(@error #[repr(packed)] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(packed(1))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
test!(@error #[repr(packed(2))] => EnumRepr => FromRawReprs(Single(UnsupportedReprError)).into());
|
||||
}
|
||||
}
|
||||
843
rust/zerocopy-derive/util.rs
Normal file
843
rust/zerocopy-derive/util.rs
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
// 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 std::num::NonZeroU32;
|
||||
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
|
||||
Expr, ExprLit, Field, GenericParam, Ident, Index, Lit, LitStr, Meta, Path, Type, Variant,
|
||||
Visibility, WherePredicate,
|
||||
};
|
||||
|
||||
use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned};
|
||||
|
||||
pub(crate) struct Ctx {
|
||||
pub(crate) ast: DeriveInput,
|
||||
pub(crate) zerocopy_crate: Path,
|
||||
|
||||
// The value of the last `#[zerocopy(on_error = ...)]` attribute, or `false`
|
||||
// if none is provided.
|
||||
pub(crate) skip_on_error: bool,
|
||||
|
||||
// The span of the last `#[zerocopy(on_error = ...)]` attribute, if any.
|
||||
pub(crate) on_error_span: Option<proc_macro2::Span>,
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
/// Attempt to extract a crate path from the provided attributes. Defaults to
|
||||
/// `::zerocopy` if not found.
|
||||
pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {
|
||||
let mut path = parse_quote!(::zerocopy);
|
||||
let mut skip_on_error = false;
|
||||
let mut on_error_span = None;
|
||||
|
||||
for attr in &ast.attrs {
|
||||
if let Meta::List(ref meta_list) = attr.meta {
|
||||
if meta_list.path.is_ident("zerocopy") {
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("crate") {
|
||||
let expr = meta.value().and_then(|value| value.parse());
|
||||
if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {
|
||||
if let Ok(path_lit) = lit.parse::<Ident>() {
|
||||
path = parse_quote!(::#path_lit);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
return Err(Error::new(
|
||||
Span::call_site(),
|
||||
"`crate` attribute requires a path as the value",
|
||||
));
|
||||
}
|
||||
|
||||
if meta.path.is_ident("on_error") {
|
||||
on_error_span = Some(meta.path.span());
|
||||
let value = meta.value()?;
|
||||
let s: LitStr = value.parse()?;
|
||||
match s.value().as_str() {
|
||||
"skip" => skip_on_error = true,
|
||||
"fail" => skip_on_error = false,
|
||||
_ => return Err(Error::new(
|
||||
s.span(),
|
||||
"unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",
|
||||
)),
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::new(
|
||||
Span::call_site(),
|
||||
format!(
|
||||
"unknown attribute encountered: {}",
|
||||
meta.path.into_token_stream()
|
||||
),
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span })
|
||||
}
|
||||
|
||||
pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {
|
||||
Self {
|
||||
ast: input.clone(),
|
||||
zerocopy_crate: self.zerocopy_crate.clone(),
|
||||
skip_on_error: self.skip_on_error,
|
||||
on_error_span: self.on_error_span,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn core_path(&self) -> TokenStream {
|
||||
let zerocopy_crate = &self.zerocopy_crate;
|
||||
quote!(#zerocopy_crate::util::macro_util::core_reexport)
|
||||
}
|
||||
|
||||
pub(crate) fn cfg_compile_error(&self) -> TokenStream {
|
||||
// By checking both during the compilation of the proc macro *and* in
|
||||
// the generated code, we ensure that `--cfg
|
||||
// zerocopy_unstable_derive_on_error` need only be passed *either* when
|
||||
// compiling this crate *or* when compiling the user's crate. The former
|
||||
// is preferable, but in some situations (such as when cross-compiling
|
||||
// using `cargo build --target`), it doesn't get propagated to this
|
||||
// crate's build by default.
|
||||
if cfg!(zerocopy_unstable_derive_on_error) {
|
||||
quote!()
|
||||
} else if let Some(span) = self.on_error_span {
|
||||
let core = self.core_path();
|
||||
let error_message = "`on_error` is experimental; pass '--cfg zerocopy_unstable_derive_on_error' to enable";
|
||||
quote::quote_spanned! {span=>
|
||||
#[allow(unused_attributes, unexpected_cfgs)]
|
||||
const _: () = {
|
||||
#[cfg(not(zerocopy_unstable_derive_on_error))]
|
||||
#core::compile_error!(#error_message);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
quote!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {
|
||||
if self.skip_on_error {
|
||||
Ok(self.cfg_compile_error())
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait DataExt {
|
||||
/// Extracts the names and types of all fields. For enums, extracts the
|
||||
/// names and types of fields from each variant. For tuple structs, the
|
||||
/// names are the indices used to index into the struct (ie, `0`, `1`, etc).
|
||||
///
|
||||
/// FIXME: Extracting field names for enums doesn't really make sense. Types
|
||||
/// makes sense because we don't care about where they live - we just care
|
||||
/// about transitive ownership. But for field names, we'd only use them when
|
||||
/// generating is_bit_valid, which cares about where they live.
|
||||
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
|
||||
|
||||
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;
|
||||
|
||||
fn tag(&self) -> Option<Ident>;
|
||||
}
|
||||
|
||||
impl DataExt for Data {
|
||||
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
||||
match self {
|
||||
Data::Struct(strc) => strc.fields(),
|
||||
Data::Enum(enm) => enm.fields(),
|
||||
Data::Union(un) => un.fields(),
|
||||
}
|
||||
}
|
||||
|
||||
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
||||
match self {
|
||||
Data::Struct(strc) => strc.variants(),
|
||||
Data::Enum(enm) => enm.variants(),
|
||||
Data::Union(un) => un.variants(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tag(&self) -> Option<Ident> {
|
||||
match self {
|
||||
Data::Struct(strc) => strc.tag(),
|
||||
Data::Enum(enm) => enm.tag(),
|
||||
Data::Union(un) => un.tag(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DataExt for DataStruct {
|
||||
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
||||
map_fields(&self.fields)
|
||||
}
|
||||
|
||||
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
||||
vec![(None, self.fields())]
|
||||
}
|
||||
|
||||
fn tag(&self) -> Option<Ident> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl DataExt for DataEnum {
|
||||
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
||||
map_fields(self.variants.iter().flat_map(|var| &var.fields))
|
||||
}
|
||||
|
||||
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
||||
self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
|
||||
}
|
||||
|
||||
fn tag(&self) -> Option<Ident> {
|
||||
Some(Ident::new("___ZerocopyTag", Span::call_site()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DataExt for DataUnion {
|
||||
fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
||||
map_fields(&self.fields.named)
|
||||
}
|
||||
|
||||
fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
||||
vec![(None, self.fields())]
|
||||
}
|
||||
|
||||
fn tag(&self) -> Option<Ident> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn map_fields<'a>(
|
||||
fields: impl 'a + IntoIterator<Item = &'a Field>,
|
||||
) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
|
||||
fields
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, f)| {
|
||||
(
|
||||
&f.vis,
|
||||
f.ident
|
||||
.as_ref()
|
||||
.map(ToTokens::to_token_stream)
|
||||
.unwrap_or_else(|| Index::from(idx).to_token_stream()),
|
||||
&f.ty,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn to_ident_str(t: &impl ToString) -> String {
|
||||
let s = t.to_string();
|
||||
if let Some(stripped) = s.strip_prefix("r#") {
|
||||
stripped.to_string()
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
/// This enum describes what kind of padding check needs to be generated for the
|
||||
/// associated impl.
|
||||
pub(crate) enum PaddingCheck {
|
||||
/// Check that the sum of the fields' sizes exactly equals the struct's
|
||||
/// size.
|
||||
Struct,
|
||||
/// Check that a `repr(C)` struct has no padding.
|
||||
ReprCStruct,
|
||||
/// Check that the size of each field exactly equals the union's size.
|
||||
Union,
|
||||
/// Check that every variant of the enum contains no padding.
|
||||
///
|
||||
/// Because doing so requires a tag enum, this padding check requires an
|
||||
/// additional `TokenStream` which defines the tag enum as `___ZerocopyTag`.
|
||||
Enum { tag_type_definition: TokenStream },
|
||||
}
|
||||
|
||||
impl PaddingCheck {
|
||||
/// Returns the idents of the trait to use and the macro to call in order to
|
||||
/// validate that a type passes the relevant padding check.
|
||||
pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {
|
||||
let (trt, mcro) = match self {
|
||||
PaddingCheck::Struct => ("PaddingFree", "struct_padding"),
|
||||
PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),
|
||||
PaddingCheck::Union => ("PaddingFree", "union_padding"),
|
||||
PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),
|
||||
};
|
||||
|
||||
let trt = Ident::new(trt, Span::call_site());
|
||||
let mcro = Ident::new(mcro, Span::call_site());
|
||||
(trt, mcro)
|
||||
}
|
||||
|
||||
/// Sometimes performing the padding check requires some additional
|
||||
/// "context" code. For enums, this is the definition of the tag enum.
|
||||
pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {
|
||||
match self {
|
||||
PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,
|
||||
PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum Trait {
|
||||
KnownLayout,
|
||||
HasTag,
|
||||
HasField {
|
||||
variant_id: Box<Expr>,
|
||||
field: Box<Type>,
|
||||
field_id: Box<Expr>,
|
||||
},
|
||||
ProjectField {
|
||||
variant_id: Box<Expr>,
|
||||
field: Box<Type>,
|
||||
field_id: Box<Expr>,
|
||||
invariants: Box<Type>,
|
||||
},
|
||||
Immutable,
|
||||
TryFromBytes,
|
||||
FromZeros,
|
||||
FromBytes,
|
||||
IntoBytes,
|
||||
Unaligned,
|
||||
Sized,
|
||||
ByteHash,
|
||||
ByteEq,
|
||||
SplitAt,
|
||||
}
|
||||
|
||||
impl ToTokens for Trait {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
// According to [1], the format of the derived `Debug`` output is not
|
||||
// stable and therefore not guaranteed to represent the variant names.
|
||||
// Indeed with the (unstable) `fmt-debug` compiler flag [2], it can
|
||||
// return only a minimalized output or empty string. To make sure this
|
||||
// code will work in the future and independent of the compiler flag, we
|
||||
// translate the variants to their names manually here.
|
||||
//
|
||||
// [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability
|
||||
// [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html
|
||||
let s = match self {
|
||||
Trait::HasField { .. } => "HasField",
|
||||
Trait::ProjectField { .. } => "ProjectField",
|
||||
Trait::KnownLayout => "KnownLayout",
|
||||
Trait::HasTag => "HasTag",
|
||||
Trait::Immutable => "Immutable",
|
||||
Trait::TryFromBytes => "TryFromBytes",
|
||||
Trait::FromZeros => "FromZeros",
|
||||
Trait::FromBytes => "FromBytes",
|
||||
Trait::IntoBytes => "IntoBytes",
|
||||
Trait::Unaligned => "Unaligned",
|
||||
Trait::Sized => "Sized",
|
||||
Trait::ByteHash => "ByteHash",
|
||||
Trait::ByteEq => "ByteEq",
|
||||
Trait::SplitAt => "SplitAt",
|
||||
};
|
||||
let ident = Ident::new(s, Span::call_site());
|
||||
let arguments: Option<syn::AngleBracketedGenericArguments> = match self {
|
||||
Trait::HasField { variant_id, field, field_id } => {
|
||||
Some(parse_quote!(<#field, #variant_id, #field_id>))
|
||||
}
|
||||
Trait::ProjectField { variant_id, field, field_id, invariants } => {
|
||||
Some(parse_quote!(<#field, #invariants, #variant_id, #field_id>))
|
||||
}
|
||||
Trait::KnownLayout
|
||||
| Trait::HasTag
|
||||
| Trait::Immutable
|
||||
| Trait::TryFromBytes
|
||||
| Trait::FromZeros
|
||||
| Trait::FromBytes
|
||||
| Trait::IntoBytes
|
||||
| Trait::Unaligned
|
||||
| Trait::Sized
|
||||
| Trait::ByteHash
|
||||
| Trait::ByteEq
|
||||
| Trait::SplitAt => None,
|
||||
};
|
||||
tokens.extend(quote!(#ident #arguments));
|
||||
}
|
||||
}
|
||||
|
||||
impl Trait {
|
||||
pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let core = ctx.core_path();
|
||||
match self {
|
||||
Self::Sized => parse_quote!(#core::marker::#self),
|
||||
_ => parse_quote!(#zerocopy_crate::#self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum TraitBound {
|
||||
Slf,
|
||||
Other(Trait),
|
||||
}
|
||||
|
||||
pub(crate) enum FieldBounds<'a> {
|
||||
None,
|
||||
All(&'a [TraitBound]),
|
||||
Trailing(&'a [TraitBound]),
|
||||
Explicit(Vec<WherePredicate>),
|
||||
}
|
||||
|
||||
impl<'a> FieldBounds<'a> {
|
||||
pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
|
||||
pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
|
||||
}
|
||||
|
||||
pub(crate) enum SelfBounds<'a> {
|
||||
None,
|
||||
All(&'a [Trait]),
|
||||
}
|
||||
|
||||
// FIXME(https://github.com/rust-lang/rust-clippy/issues/12908): This is a false
|
||||
// positive. Explicit lifetimes are actually necessary here.
|
||||
#[allow(clippy::needless_lifetimes)]
|
||||
impl<'a> SelfBounds<'a> {
|
||||
pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
|
||||
}
|
||||
|
||||
/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.
|
||||
pub(crate) fn normalize_bounds<'a>(
|
||||
slf: &'a Trait,
|
||||
bounds: &'a [TraitBound],
|
||||
) -> impl 'a + Iterator<Item = Trait> {
|
||||
bounds.iter().map(move |bound| match bound {
|
||||
TraitBound::Slf => slf.clone(),
|
||||
TraitBound::Other(trt) => trt.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) struct ImplBlockBuilder<'a> {
|
||||
ctx: &'a Ctx,
|
||||
data: &'a dyn DataExt,
|
||||
trt: Trait,
|
||||
field_type_trait_bounds: FieldBounds<'a>,
|
||||
self_type_trait_bounds: SelfBounds<'a>,
|
||||
padding_check: Option<PaddingCheck>,
|
||||
param_extras: Vec<GenericParam>,
|
||||
inner_extras: Option<TokenStream>,
|
||||
outer_extras: Option<TokenStream>,
|
||||
}
|
||||
|
||||
impl<'a> ImplBlockBuilder<'a> {
|
||||
pub(crate) fn new(
|
||||
ctx: &'a Ctx,
|
||||
data: &'a dyn DataExt,
|
||||
trt: Trait,
|
||||
field_type_trait_bounds: FieldBounds<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
data,
|
||||
trt,
|
||||
field_type_trait_bounds,
|
||||
self_type_trait_bounds: SelfBounds::None,
|
||||
padding_check: None,
|
||||
param_extras: Vec::new(),
|
||||
inner_extras: None,
|
||||
outer_extras: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
|
||||
self.self_type_trait_bounds = self_type_trait_bounds;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
|
||||
self.padding_check = padding_check.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {
|
||||
self.param_extras.extend(param_extras);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
|
||||
self.inner_extras = Some(inner_extras);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
|
||||
self.outer_extras = outer_extras.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> TokenStream {
|
||||
// In this documentation, we will refer to this hypothetical struct:
|
||||
//
|
||||
// #[derive(FromBytes)]
|
||||
// struct Foo<T, I: Iterator>
|
||||
// where
|
||||
// T: Copy,
|
||||
// I: Clone,
|
||||
// I::Item: Clone,
|
||||
// {
|
||||
// a: u8,
|
||||
// b: T,
|
||||
// c: I::Item,
|
||||
// }
|
||||
//
|
||||
// We extract the field types, which in this case are `u8`, `T`, and
|
||||
// `I::Item`. We re-use the existing parameters and where clauses. If
|
||||
// `require_trait_bound == true` (as it is for `FromBytes), we add where
|
||||
// bounds for each field's type:
|
||||
//
|
||||
// impl<T, I: Iterator> FromBytes for Foo<T, I>
|
||||
// where
|
||||
// T: Copy,
|
||||
// I: Clone,
|
||||
// I::Item: Clone,
|
||||
// T: FromBytes,
|
||||
// I::Item: FromBytes,
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// NOTE: It is standard practice to only emit bounds for the type
|
||||
// parameters themselves, not for field types based on those parameters
|
||||
// (e.g., `T` vs `T::Foo`). For a discussion of why this is standard
|
||||
// practice, see https://github.com/rust-lang/rust/issues/26925.
|
||||
//
|
||||
// The reason we diverge from this standard is that doing it that way
|
||||
// for us would be unsound. E.g., consider a type, `T` where `T:
|
||||
// FromBytes` but `T::Foo: !FromBytes`. It would not be sound for us to
|
||||
// accept a type with a `T::Foo` field as `FromBytes` simply because `T:
|
||||
// FromBytes`.
|
||||
//
|
||||
// While there's no getting around this requirement for us, it does have
|
||||
// the pretty serious downside that, when lifetimes are involved, the
|
||||
// trait solver ties itself in knots:
|
||||
//
|
||||
// #[derive(Unaligned)]
|
||||
// #[repr(C)]
|
||||
// struct Dup<'a, 'b> {
|
||||
// a: PhantomData<&'a u8>,
|
||||
// b: PhantomData<&'b u8>,
|
||||
// }
|
||||
//
|
||||
// error[E0283]: type annotations required: cannot resolve `core::marker::PhantomData<&'a u8>: zerocopy::Unaligned`
|
||||
// --> src/main.rs:6:10
|
||||
// |
|
||||
// 6 | #[derive(Unaligned)]
|
||||
// | ^^^^^^^^^
|
||||
// |
|
||||
// = note: required by `zerocopy::Unaligned`
|
||||
|
||||
let type_ident = &self.ctx.ast.ident;
|
||||
let trait_path = self.trt.crate_path(self.ctx);
|
||||
let fields = self.data.fields();
|
||||
let variants = self.data.variants();
|
||||
let tag = self.data.tag();
|
||||
let zerocopy_crate = &self.ctx.zerocopy_crate;
|
||||
|
||||
fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>, ctx: &Ctx) -> WherePredicate {
|
||||
let traits = traits.map(|t| t.crate_path(ctx));
|
||||
parse_quote!(#ty: #(#traits)+*)
|
||||
}
|
||||
let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) {
|
||||
(FieldBounds::All(traits), _) => fields
|
||||
.iter()
|
||||
.map(|(_vis, _name, ty)| {
|
||||
bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx)
|
||||
})
|
||||
.collect(),
|
||||
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
|
||||
(FieldBounds::Trailing(traits), [.., last]) => {
|
||||
vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)]
|
||||
}
|
||||
(FieldBounds::Explicit(bounds), _) => bounds,
|
||||
};
|
||||
|
||||
let padding_check_bound = self
|
||||
.padding_check
|
||||
.map(|check| {
|
||||
// Parse the repr for `align` and `packed` modifiers. Note that
|
||||
// `Repr::<PrimitiveRepr, NonZeroU32>` is more permissive than
|
||||
// what Rust supports for structs, enums, or unions, and thus
|
||||
// reliably extracts these modifiers for any kind of type.
|
||||
let repr =
|
||||
Repr::<PrimitiveRepr, NonZeroU32>::from_attrs(&self.ctx.ast.attrs).unwrap();
|
||||
let core = self.ctx.core_path();
|
||||
let option = quote! { #core::option::Option };
|
||||
let nonzero = quote! { #core::num::NonZeroUsize };
|
||||
let none = quote! { #option::None::<#nonzero> };
|
||||
let repr_align =
|
||||
repr.get_align().map(|spanned| {
|
||||
let n = spanned.t.get();
|
||||
quote_spanned! { spanned.span => (#nonzero::new(#n as usize)) }
|
||||
}).unwrap_or(quote! { (#none) });
|
||||
let repr_packed =
|
||||
repr.get_packed().map(|packed| {
|
||||
let n = packed.get();
|
||||
quote! { (#nonzero::new(#n as usize)) }
|
||||
}).unwrap_or(quote! { (#none) });
|
||||
let variant_types = variants.iter().map(|(_, fields)| {
|
||||
let types = fields.iter().map(|(_vis, _name, ty)| ty);
|
||||
quote!([#((#types)),*])
|
||||
});
|
||||
let validator_context = check.validator_macro_context();
|
||||
let (trt, validator_macro) = check.validator_trait_and_macro_idents();
|
||||
let t = tag.iter();
|
||||
parse_quote! {
|
||||
(): #zerocopy_crate::util::macro_util::#trt<
|
||||
Self,
|
||||
{
|
||||
#validator_context
|
||||
#zerocopy_crate::#validator_macro!(Self, #repr_align, #repr_packed, #(#t,)* #(#variant_types),*)
|
||||
}
|
||||
>
|
||||
}
|
||||
});
|
||||
|
||||
let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
|
||||
SelfBounds::None => None,
|
||||
SelfBounds::All(traits) => {
|
||||
Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx))
|
||||
}
|
||||
};
|
||||
|
||||
let bounds = self
|
||||
.ctx
|
||||
.ast
|
||||
.generics
|
||||
.where_clause
|
||||
.as_ref()
|
||||
.map(|where_clause| where_clause.predicates.iter())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(field_type_bounds.iter())
|
||||
.chain(padding_check_bound.iter())
|
||||
.chain(self_bounds.iter());
|
||||
|
||||
// The parameters with trait bounds, but without type defaults.
|
||||
let mut params: Vec<_> = self
|
||||
.ctx
|
||||
.ast
|
||||
.generics
|
||||
.params
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|mut param| {
|
||||
match &mut param {
|
||||
GenericParam::Type(ty) => ty.default = None,
|
||||
GenericParam::Const(cnst) => cnst.default = None,
|
||||
GenericParam::Lifetime(_) => {}
|
||||
}
|
||||
parse_quote!(#param)
|
||||
})
|
||||
.chain(self.param_extras)
|
||||
.collect();
|
||||
|
||||
// For MSRV purposes, ensure that lifetimes precede types precede const
|
||||
// generics.
|
||||
params.sort_by_cached_key(|param| match param {
|
||||
GenericParam::Lifetime(_) => 0,
|
||||
GenericParam::Type(_) => 1,
|
||||
GenericParam::Const(_) => 2,
|
||||
});
|
||||
|
||||
// The identifiers of the parameters without trait bounds or type
|
||||
// defaults.
|
||||
let param_idents = self.ctx.ast.generics.params.iter().map(|param| match param {
|
||||
GenericParam::Type(ty) => {
|
||||
let ident = &ty.ident;
|
||||
quote!(#ident)
|
||||
}
|
||||
GenericParam::Lifetime(l) => {
|
||||
let ident = &l.lifetime;
|
||||
quote!(#ident)
|
||||
}
|
||||
GenericParam::Const(cnst) => {
|
||||
let ident = &cnst.ident;
|
||||
quote!({#ident})
|
||||
}
|
||||
});
|
||||
|
||||
let inner_extras = self.inner_extras;
|
||||
let allow_trivial_bounds =
|
||||
if self.ctx.skip_on_error { quote!(#[allow(trivial_bounds)]) } else { quote!() };
|
||||
let impl_tokens = quote! {
|
||||
#allow_trivial_bounds
|
||||
unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
|
||||
where
|
||||
#(#bounds,)*
|
||||
{
|
||||
fn only_derive_is_allowed_to_implement_this_trait() {}
|
||||
|
||||
#inner_extras
|
||||
}
|
||||
};
|
||||
|
||||
let outer_extras = self.outer_extras.filter(|e| !e.is_empty());
|
||||
let cfg_compile_error = self.ctx.cfg_compile_error();
|
||||
const_block([Some(cfg_compile_error), Some(impl_tokens), outer_extras])
|
||||
}
|
||||
}
|
||||
|
||||
// A polyfill for `Option::then_some`, which was added after our MSRV.
|
||||
//
|
||||
// The `#[allow(unused)]` is necessary because, on sufficiently recent toolchain
|
||||
// versions, `b.then_some(...)` resolves to the inherent method rather than to
|
||||
// this trait, and so this trait is considered unused.
|
||||
//
|
||||
// FIXME(#67): Remove this once our MSRV is >= 1.62.
|
||||
#[allow(unused)]
|
||||
trait BoolExt {
|
||||
fn then_some<T>(self, t: T) -> Option<T>;
|
||||
}
|
||||
|
||||
impl BoolExt for bool {
|
||||
fn then_some<T>(self, t: T) -> Option<T> {
|
||||
if self {
|
||||
Some(t)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn const_block(items: impl IntoIterator<Item = Option<TokenStream>>) -> TokenStream {
|
||||
let items = items.into_iter().flatten();
|
||||
quote! {
|
||||
#[allow(
|
||||
// FIXME(#553): Add a test that generates a warning when
|
||||
// `#[allow(deprecated)]` isn't present.
|
||||
deprecated,
|
||||
// Required on some rustc versions due to a lint that is only
|
||||
// triggered when `derive(KnownLayout)` is applied to `repr(C)`
|
||||
// structs that are generated by macros. See #2177 for details.
|
||||
private_bounds,
|
||||
non_local_definitions,
|
||||
non_camel_case_types,
|
||||
non_upper_case_globals,
|
||||
non_snake_case,
|
||||
non_ascii_idents,
|
||||
clippy::missing_inline_in_public_items,
|
||||
)]
|
||||
#[deny(ambiguous_associated_items)]
|
||||
// While there are not currently any warnings that this suppresses
|
||||
// (that we're aware of), it's good future-proofing hygiene.
|
||||
#[automatically_derived]
|
||||
const _: () = {
|
||||
#(#items)*
|
||||
};
|
||||
}
|
||||
}
|
||||
pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream {
|
||||
let zerocopy_crate = &ctx.zerocopy_crate;
|
||||
let variants = data.variants.iter().map(|v| {
|
||||
let ident = &v.ident;
|
||||
if let Some((eq, discriminant)) = &v.discriminant {
|
||||
quote! { #ident #eq #discriminant }
|
||||
} else {
|
||||
quote! { #ident }
|
||||
}
|
||||
});
|
||||
|
||||
// Don't include any `repr(align)` when generating the tag enum, as that
|
||||
// could add padding after the tag but before any variants, which is not the
|
||||
// correct behavior.
|
||||
let repr = match repr {
|
||||
EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
|
||||
EnumRepr::Compound(c, _) => quote! { #c },
|
||||
};
|
||||
|
||||
quote! {
|
||||
#repr
|
||||
#[allow(dead_code)]
|
||||
pub enum ___ZerocopyTag {
|
||||
#(#variants,)*
|
||||
}
|
||||
|
||||
// SAFETY: `___ZerocopyTag` has no fields, and so it does not permit
|
||||
// interior mutation.
|
||||
unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag {
|
||||
fn only_derive_is_allowed_to_implement_this_trait() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
|
||||
use CompoundRepr::*;
|
||||
use PrimitiveRepr::*;
|
||||
use Repr::*;
|
||||
match repr {
|
||||
Transparent(span)
|
||||
| Compound(
|
||||
Spanned {
|
||||
t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | Usize | Isize),
|
||||
span,
|
||||
},
|
||||
_,
|
||||
) => Err(Error::new(
|
||||
*span,
|
||||
"`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`",
|
||||
)),
|
||||
Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
|
||||
Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod testutil {
|
||||
use proc_macro2::TokenStream;
|
||||
use syn::visit::{self, Visit};
|
||||
|
||||
/// Checks for hygiene violations in the generated code.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if a hygiene violation is found.
|
||||
pub(crate) fn check_hygiene(ts: TokenStream) {
|
||||
struct AmbiguousItemVisitor;
|
||||
|
||||
impl<'ast> Visit<'ast> for AmbiguousItemVisitor {
|
||||
fn visit_path(&mut self, i: &'ast syn::Path) {
|
||||
if i.segments.len() > 1 && i.segments.first().unwrap().ident == "Self" {
|
||||
panic!(
|
||||
"Found ambiguous path `{}` in generated output. \
|
||||
All associated item access must be fully qualified (e.g., `<Self as Trait>::Item`) \
|
||||
to prevent hygiene issues.",
|
||||
quote::quote!(#i)
|
||||
);
|
||||
}
|
||||
visit::visit_path(self, i);
|
||||
}
|
||||
}
|
||||
|
||||
let file = syn::parse2::<syn::File>(ts).expect("failed to parse generated output as File");
|
||||
AmbiguousItemVisitor.visit_file(&file);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_hygiene_success() {
|
||||
check_hygiene(quote::quote! {
|
||||
fn foo() {
|
||||
let _ = <Self as Trait>::Item;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Found ambiguous path `Self :: Ambiguous`")]
|
||||
fn test_check_hygiene_failure() {
|
||||
check_hygiene(quote::quote! {
|
||||
fn foo() {
|
||||
let _ = Self::Ambiguous;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user