Skip to content

Commit 2b6d45e

Browse files
committed
introduce HasField
gherrit-pr-id: G2238bc341570838db412d880017b3f0c25ac09fa
1 parent 60f4102 commit 2b6d45e

File tree

5 files changed

+1822
-711
lines changed

5 files changed

+1822
-711
lines changed

src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,25 @@ const _: () = unsafe {
10871087
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>)
10881088
};
10891089

1090+
/// Denotes that `Self` has a given field.
1091+
///
1092+
/// # Safety
1093+
///
1094+
/// For a field `f`:
1095+
/// - if `f` is named, `FIELD_ID` is a hash of `f`'s identifier
1096+
/// - if `f` is anonymous, `FIELD_ID` is the index of `f`
1097+
/// - `Field` is a type with the same visibility as `f`
1098+
/// - `Type` has the same type as `f`
1099+
#[doc(hidden)]
1100+
pub unsafe trait HasField<Field, const FIELD_ID: u128> {
1101+
fn only_derive_is_allowed_to_implement_this_trait()
1102+
where
1103+
Self: Sized;
1104+
1105+
/// The type of the field.
1106+
type Type: ?Sized;
1107+
}
1108+
10901109
/// Analyzes whether a type is [`FromZeros`].
10911110
///
10921111
/// This derive analyzes, at compile time, whether the annotated type satisfies

src/util/macro_util.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,62 @@ macro_rules! assert_size_eq {
529529
}};
530530
}
531531

532+
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
533+
#[macro_export]
534+
macro_rules! ident_id {
535+
($field:ident) => {
536+
$crate::util::macro_util::hash_name(stringify!($field))
537+
};
538+
($field:literal) => {
539+
$field
540+
};
541+
}
542+
543+
/// Computes the hash of a string.
544+
///
545+
/// NOTE(#2749) on hash collisions: This function's output only needs to be
546+
/// deterministic within a particular compilation. Thus, if a user ever reports
547+
/// a hash collision (very unlikely given the <= 16-byte special case), we can
548+
/// strengthen the hash function at that point and publish a new version. Since
549+
/// this is computed at compile time on small strings, we can easily use more
550+
/// expensive and higher-quality hash functions if need be.
551+
#[inline(always)]
552+
#[must_use]
553+
#[allow(clippy::as_conversions, clippy::indexing_slicing, clippy::arithmetic_side_effects)]
554+
pub const fn hash_name(name: &str) -> u128 {
555+
let name = name.as_bytes();
556+
557+
// We guarantee freedom from hash collisions between any two strings of
558+
// length 16 or less by having the hashes of such strings be equal to
559+
// their value. There is still a possibility that such strings will have
560+
// the same value as the hash of a string of length > 16.
561+
if name.len() <= size_of::<u128>() {
562+
let mut bytes = [0u8; 16];
563+
564+
let mut i = 0;
565+
while i < name.len() {
566+
bytes[i] = name[i];
567+
i += 1;
568+
}
569+
570+
return u128::from_ne_bytes(bytes);
571+
};
572+
573+
// An implementation of FxHasher, although returning a u128. Probably
574+
// not as strong as it could be, but probably more collision resistant
575+
// than normal 64-bit FxHasher.
576+
let mut hash = 0u128;
577+
let mut i = 0;
578+
while i < name.len() {
579+
// This is just FxHasher's `0x517cc1b727220a95` constant
580+
// concatenated back-to-back.
581+
const K: u128 = 0x517cc1b727220a95517cc1b727220a95;
582+
hash = (hash.rotate_left(5) ^ (name[i] as u128)).wrapping_mul(K);
583+
i += 1;
584+
}
585+
hash
586+
}
587+
532588
/// Is a given source a valid instance of `Dst`?
533589
///
534590
/// If so, returns `src` casted to a `Ptr<Dst, _>`. Otherwise returns `None`.

zerocopy-derive/src/lib.rs

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@ mod ext;
4141
mod output_tests;
4242
mod repr;
4343

44-
use proc_macro2::{Span, TokenStream, TokenTree};
44+
use proc_macro2::{Span, TokenStream};
4545
use quote::{quote, ToTokens};
4646
use syn::{
47-
parse_quote, Attribute, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Expr,
48-
ExprLit, ExprUnary, GenericParam, Ident, Lit, Meta, Path, Type, UnOp, WherePredicate,
47+
parse_quote, spanned::Spanned as _, Attribute, Data, DataEnum, DataStruct, DataUnion,
48+
DeriveInput, Error, Expr, ExprLit, ExprUnary, GenericParam, Ident, Lit, Meta, Path, Type, UnOp,
49+
WherePredicate,
4950
};
5051

5152
use crate::{ext::*, repr::*};
@@ -464,11 +465,12 @@ fn derive_known_layout_inner(
464465

465466
Ok(match &ast.data {
466467
Data::Struct(strct) => {
467-
let require_trait_bound_on_field_types = if self_bounds == SelfBounds::SIZED {
468-
FieldBounds::None
469-
} else {
470-
FieldBounds::TRAILING_SELF
471-
};
468+
let require_trait_bound_on_field_types =
469+
if matches!(self_bounds, SelfBounds::All(&[Trait::Sized])) {
470+
FieldBounds::None
471+
} else {
472+
FieldBounds::TRAILING_SELF
473+
};
472474

473475
// A bound on the trailing field is required, since structs are
474476
// unsized if their trailing field is unsized. Reflecting the layout
@@ -737,6 +739,51 @@ fn derive_split_at_inner(
737739
.build())
738740
}
739741

742+
fn derive_has_field_variant(
743+
ast: &DeriveInput,
744+
data: &dyn DataExt,
745+
zerocopy_crate: &Path,
746+
) -> TokenStream {
747+
let fields = ast.data.fields();
748+
749+
if fields.is_empty() {
750+
return quote! {};
751+
}
752+
753+
let field_tokens = fields.iter().map(|(vis, ident, _)| {
754+
let ident = Ident::new(&format!("ẕ{ident}"), ident.span());
755+
quote!(
756+
#vis enum #ident {}
757+
)
758+
});
759+
760+
let has_fields = fields.iter().map(|(_, ident, ty)| {
761+
let field_token = Ident::new(&format!("ẕ{ident}"), ident.span());
762+
ImplBlockBuilder::new(
763+
ast,
764+
data,
765+
Trait::HasField {
766+
field: parse_quote!(#field_token),
767+
field_id: parse_quote!({ #zerocopy_crate::ident_id!(#ident) }),
768+
},
769+
FieldBounds::None,
770+
zerocopy_crate,
771+
)
772+
.inner_extras(quote! {
773+
type Type = #ty;
774+
})
775+
.build()
776+
});
777+
778+
quote! {
779+
#[allow(non_camel_case_types)]
780+
const _: () = {
781+
#(#field_tokens)*
782+
#(#has_fields)*
783+
};
784+
}
785+
}
786+
740787
/// A struct is `TryFromBytes` if:
741788
/// - all fields are `TryFromBytes`
742789
fn derive_try_from_bytes_struct(
@@ -815,6 +862,7 @@ fn derive_try_from_bytes_struct(
815862
zerocopy_crate,
816863
)
817864
.inner_extras(extras)
865+
.outer_extras(derive_has_field_variant(ast, strct, zerocopy_crate))
818866
.build())
819867
}
820868

@@ -894,6 +942,7 @@ fn derive_try_from_bytes_union(
894942
});
895943
ImplBlockBuilder::new(ast, unn, Trait::TryFromBytes, field_type_trait_bounds, zerocopy_crate)
896944
.inner_extras(extras)
945+
.outer_extras(derive_has_field_variant(ast, unn, zerocopy_crate))
897946
.build()
898947
}
899948

@@ -964,7 +1013,7 @@ fn try_gen_trivial_is_bit_valid(
9641013
// make this no longer true. To hedge against these, we include an explicit
9651014
// `Self: FromBytes` check in the generated `is_bit_valid`, which is
9661015
// bulletproof.
967-
if top_level == Trait::FromBytes && ast.generics.params.is_empty() {
1016+
if matches!(top_level, Trait::FromBytes) && ast.generics.params.is_empty() {
9681017
Some(quote!(
9691018
// SAFETY: See inline.
9701019
fn is_bit_valid<___ZerocopyAliasing>(
@@ -1533,9 +1582,10 @@ impl PaddingCheck {
15331582
}
15341583
}
15351584

1536-
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1585+
#[derive(Clone)]
15371586
enum Trait {
15381587
KnownLayout,
1588+
HasField { field: Type, field_id: Expr },
15391589
Immutable,
15401590
TryFromBytes,
15411591
FromZeros,
@@ -1560,6 +1610,7 @@ impl ToTokens for Trait {
15601610
// [1] https://doc.rust-lang.org/1.81.0/std/fmt/trait.Debug.html#stability
15611611
// [2] https://doc.rust-lang.org/beta/unstable-book/compiler-flags/fmt-debug.html
15621612
let s = match self {
1613+
Trait::HasField { .. } => "HasField",
15631614
Trait::KnownLayout => "KnownLayout",
15641615
Trait::Immutable => "Immutable",
15651616
Trait::TryFromBytes => "TryFromBytes",
@@ -1573,7 +1624,25 @@ impl ToTokens for Trait {
15731624
Trait::SplitAt => "SplitAt",
15741625
};
15751626
let ident = Ident::new(s, Span::call_site());
1576-
tokens.extend(core::iter::once(TokenTree::Ident(ident)));
1627+
let empty_args: syn::AngleBracketedGenericArguments = parse_quote!(<>);
1628+
let arguments: syn::AngleBracketedGenericArguments = match self {
1629+
Trait::HasField { field, field_id } => {
1630+
parse_quote!(<#field, #field_id>)
1631+
}
1632+
Trait::KnownLayout => empty_args,
1633+
Trait::Immutable => empty_args,
1634+
Trait::TryFromBytes => empty_args,
1635+
Trait::FromZeros => empty_args,
1636+
Trait::FromBytes => empty_args,
1637+
Trait::IntoBytes => empty_args,
1638+
Trait::Unaligned => empty_args,
1639+
Trait::Sized => empty_args,
1640+
Trait::ByteHash => empty_args,
1641+
Trait::ByteEq => empty_args,
1642+
Trait::SplitAt => empty_args,
1643+
};
1644+
1645+
tokens.extend(quote!(#ident #arguments));
15771646
}
15781647
}
15791648

@@ -1588,7 +1657,6 @@ impl Trait {
15881657
}
15891658
}
15901659

1591-
#[derive(Debug, Eq, PartialEq)]
15921660
enum TraitBound {
15931661
Slf,
15941662
Other(Trait),
@@ -1606,7 +1674,6 @@ impl<'a> FieldBounds<'a> {
16061674
const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
16071675
}
16081676

1609-
#[derive(Debug, Eq, PartialEq)]
16101677
enum SelfBounds<'a> {
16111678
None,
16121679
All(&'a [Trait]),
@@ -1620,16 +1687,19 @@ impl<'a> SelfBounds<'a> {
16201687
}
16211688

16221689
/// Normalizes a slice of bounds by replacing [`TraitBound::Slf`] with `slf`.
1623-
fn normalize_bounds(slf: Trait, bounds: &[TraitBound]) -> impl '_ + Iterator<Item = Trait> {
1690+
fn normalize_bounds<'a>(
1691+
slf: &'a Trait,
1692+
bounds: &'a [TraitBound],
1693+
) -> impl 'a + Iterator<Item = Trait> {
16241694
bounds.iter().map(move |bound| match bound {
1625-
TraitBound::Slf => slf,
1626-
TraitBound::Other(trt) => *trt,
1695+
TraitBound::Slf => slf.clone(),
1696+
TraitBound::Other(trt) => trt.clone(),
16271697
})
16281698
}
16291699

1630-
struct ImplBlockBuilder<'a, D: DataExt> {
1700+
struct ImplBlockBuilder<'a> {
16311701
input: &'a DeriveInput,
1632-
data: &'a D,
1702+
data: &'a dyn DataExt,
16331703
trt: Trait,
16341704
field_type_trait_bounds: FieldBounds<'a>,
16351705
zerocopy_crate: &'a Path,
@@ -1639,10 +1709,10 @@ struct ImplBlockBuilder<'a, D: DataExt> {
16391709
outer_extras: Option<TokenStream>,
16401710
}
16411711

1642-
impl<'a, D: DataExt> ImplBlockBuilder<'a, D> {
1712+
impl<'a> ImplBlockBuilder<'a> {
16431713
fn new(
16441714
input: &'a DeriveInput,
1645-
data: &'a D,
1715+
data: &'a dyn DataExt,
16461716
trt: Trait,
16471717
field_type_trait_bounds: FieldBounds<'a>,
16481718
zerocopy_crate: &'a Path,
@@ -1759,12 +1829,12 @@ impl<'a, D: DataExt> ImplBlockBuilder<'a, D> {
17591829
(FieldBounds::All(traits), _) => fields
17601830
.iter()
17611831
.map(|(_vis, _name, ty)| {
1762-
bound_tt(ty, normalize_bounds(self.trt, traits), zerocopy_crate)
1832+
bound_tt(ty, normalize_bounds(&self.trt, traits), zerocopy_crate)
17631833
})
17641834
.collect(),
17651835
(FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
17661836
(FieldBounds::Trailing(traits), [.., last]) => {
1767-
vec![bound_tt(last.2, normalize_bounds(self.trt, traits), zerocopy_crate)]
1837+
vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), zerocopy_crate)]
17681838
}
17691839
(FieldBounds::Explicit(bounds), _) => bounds,
17701840
};
@@ -1796,7 +1866,7 @@ impl<'a, D: DataExt> ImplBlockBuilder<'a, D> {
17961866
let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
17971867
SelfBounds::None => None,
17981868
SelfBounds::All(traits) => {
1799-
Some(bound_tt(&parse_quote!(Self), traits.iter().copied(), zerocopy_crate))
1869+
Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), zerocopy_crate))
18001870
}
18011871
};
18021872

@@ -1855,7 +1925,7 @@ impl<'a, D: DataExt> ImplBlockBuilder<'a, D> {
18551925
}
18561926
};
18571927

1858-
if let Some(outer_extras) = self.outer_extras {
1928+
if let Some(outer_extras) = self.outer_extras.filter(|e| !e.is_empty()) {
18591929
// So that any items defined in `#outer_extras` don't conflict with
18601930
// existing names defined in this scope.
18611931
quote! {

0 commit comments

Comments
 (0)