Skip to content

Commit dae16a5

Browse files
authored
[derive] Move find_zero_variant to enum module (#2889)
gherrit-pr-id: G3cfee091c2de68a64a643bafaf57c57427dd4914
1 parent 51c8f9d commit dae16a5

File tree

2 files changed

+82
-83
lines changed

2 files changed

+82
-83
lines changed

zerocopy-derive/src/enum.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
use proc_macro2::TokenStream;
1010
use quote::quote;
1111
use syn::{
12-
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Fields, Generics, Ident,
13-
Index, Path,
12+
parse_quote, spanned::Spanned as _, DataEnum, DeriveInput, Error, Expr, ExprLit, ExprUnary,
13+
Fields, Generics, Ident, Index, Lit, Path, UnOp,
1414
};
1515

1616
use crate::{
@@ -450,3 +450,81 @@ pub(crate) fn derive_is_bit_valid(
450450
}
451451
})
452452
}
453+
454+
/// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
455+
/// zero. If `Err(bool)` is returned, the boolean is true if the enum has
456+
/// unknown discriminants (e.g. discriminants set to const expressions which we
457+
/// can't evaluate in a proc macro). If the enum has unknown discriminants, then
458+
/// it might have a zero variant that we just can't detect.
459+
pub(crate) fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
460+
// Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
461+
// the discriminant type may be signed or unsigned. Since we only care about
462+
// tracking the discriminant when it's less than or equal to zero, we can
463+
// avoid u128 -> i128 conversions and bounds checking by making the "next
464+
// discriminant" value implicitly negative.
465+
// Technically 64 bits is enough, but 128 is better for future compatibility
466+
// with https://github.com/rust-lang/rust/issues/56071
467+
let mut next_negative_discriminant = Some(0);
468+
469+
// Sometimes we encounter explicit discriminants that we can't know the
470+
// value of (e.g. a constant expression that requires evaluation). These
471+
// could evaluate to zero or a negative number, but we can't assume that
472+
// they do (no false positives allowed!). So we treat them like strictly-
473+
// positive values that can't result in any zero variants, and track whether
474+
// we've encountered any unknown discriminants.
475+
let mut has_unknown_discriminants = false;
476+
477+
for (i, v) in enm.variants.iter().enumerate() {
478+
match v.discriminant.as_ref() {
479+
// Implicit discriminant
480+
None => {
481+
match next_negative_discriminant.as_mut() {
482+
Some(0) => return Ok(i),
483+
// n is nonzero so subtraction is always safe
484+
Some(n) => *n -= 1,
485+
None => (),
486+
}
487+
}
488+
// Explicit positive discriminant
489+
Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
490+
match int.base10_parse::<u128>().ok() {
491+
Some(0) => return Ok(i),
492+
Some(_) => next_negative_discriminant = None,
493+
None => {
494+
// Numbers should never fail to parse, but just in case:
495+
has_unknown_discriminants = true;
496+
next_negative_discriminant = None;
497+
}
498+
}
499+
}
500+
// Explicit negative discriminant
501+
Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
502+
Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
503+
match int.base10_parse::<u128>().ok() {
504+
Some(0) => return Ok(i),
505+
// x is nonzero so subtraction is always safe
506+
Some(x) => next_negative_discriminant = Some(x - 1),
507+
None => {
508+
// Numbers should never fail to parse, but just in
509+
// case:
510+
has_unknown_discriminants = true;
511+
next_negative_discriminant = None;
512+
}
513+
}
514+
}
515+
// Unknown negative discriminant (e.g. const repr)
516+
_ => {
517+
has_unknown_discriminants = true;
518+
next_negative_discriminant = None;
519+
}
520+
},
521+
// Unknown discriminant (e.g. const expr)
522+
_ => {
523+
has_unknown_discriminants = true;
524+
next_negative_discriminant = None;
525+
}
526+
}
527+
}
528+
529+
Err(has_unknown_discriminants)
530+
}

zerocopy-derive/src/lib.rs

Lines changed: 2 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ use proc_macro2::{Span, TokenStream};
5353
use quote::{quote, ToTokens};
5454
use syn::{
5555
parse_quote, spanned::Spanned as _, Attribute, Data, DataEnum, DataStruct, DataUnion,
56-
DeriveInput, Error, Expr, ExprLit, ExprUnary, GenericParam, Ident, Lit, Meta, Path, Type, UnOp,
57-
WherePredicate,
56+
DeriveInput, Error, Expr, ExprLit, GenericParam, Ident, Lit, Meta, Path, Type, WherePredicate,
5857
};
5958

6059
use crate::{repr::*, util::*};
@@ -1100,84 +1099,6 @@ fn derive_from_zeros_struct(
11001099
.build()
11011100
}
11021101

1103-
/// Returns `Ok(index)` if variant `index` of the enum has a discriminant of
1104-
/// zero. If `Err(bool)` is returned, the boolean is true if the enum has
1105-
/// unknown discriminants (e.g. discriminants set to const expressions which we
1106-
/// can't evaluate in a proc macro). If the enum has unknown discriminants, then
1107-
/// it might have a zero variant that we just can't detect.
1108-
fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
1109-
// Discriminants can be anywhere in the range [i128::MIN, u128::MAX] because
1110-
// the discriminant type may be signed or unsigned. Since we only care about
1111-
// tracking the discriminant when it's less than or equal to zero, we can
1112-
// avoid u128 -> i128 conversions and bounds checking by making the "next
1113-
// discriminant" value implicitly negative.
1114-
// Technically 64 bits is enough, but 128 is better for future compatibility
1115-
// with https://github.com/rust-lang/rust/issues/56071
1116-
let mut next_negative_discriminant = Some(0);
1117-
1118-
// Sometimes we encounter explicit discriminants that we can't know the
1119-
// value of (e.g. a constant expression that requires evaluation). These
1120-
// could evaluate to zero or a negative number, but we can't assume that
1121-
// they do (no false positives allowed!). So we treat them like strictly-
1122-
// positive values that can't result in any zero variants, and track whether
1123-
// we've encountered any unknown discriminants.
1124-
let mut has_unknown_discriminants = false;
1125-
1126-
for (i, v) in enm.variants.iter().enumerate() {
1127-
match v.discriminant.as_ref() {
1128-
// Implicit discriminant
1129-
None => {
1130-
match next_negative_discriminant.as_mut() {
1131-
Some(0) => return Ok(i),
1132-
// n is nonzero so subtraction is always safe
1133-
Some(n) => *n -= 1,
1134-
None => (),
1135-
}
1136-
}
1137-
// Explicit positive discriminant
1138-
Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
1139-
match int.base10_parse::<u128>().ok() {
1140-
Some(0) => return Ok(i),
1141-
Some(_) => next_negative_discriminant = None,
1142-
None => {
1143-
// Numbers should never fail to parse, but just in case:
1144-
has_unknown_discriminants = true;
1145-
next_negative_discriminant = None;
1146-
}
1147-
}
1148-
}
1149-
// Explicit negative discriminant
1150-
Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
1151-
Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
1152-
match int.base10_parse::<u128>().ok() {
1153-
Some(0) => return Ok(i),
1154-
// x is nonzero so subtraction is always safe
1155-
Some(x) => next_negative_discriminant = Some(x - 1),
1156-
None => {
1157-
// Numbers should never fail to parse, but just in
1158-
// case:
1159-
has_unknown_discriminants = true;
1160-
next_negative_discriminant = None;
1161-
}
1162-
}
1163-
}
1164-
// Unknown negative discriminant (e.g. const repr)
1165-
_ => {
1166-
has_unknown_discriminants = true;
1167-
next_negative_discriminant = None;
1168-
}
1169-
},
1170-
// Unknown discriminant (e.g. const expr)
1171-
_ => {
1172-
has_unknown_discriminants = true;
1173-
next_negative_discriminant = None;
1174-
}
1175-
}
1176-
}
1177-
1178-
Err(has_unknown_discriminants)
1179-
}
1180-
11811102
/// An enum is `FromZeros` if:
11821103
/// - one of the variants has a discriminant of `0`
11831104
/// - that variant's fields are all `FromZeros`
@@ -1199,7 +1120,7 @@ fn derive_from_zeros_enum(
11991120
| Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => return Err(Error::new(Span::call_site(), "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout")),
12001121
}
12011122

1202-
let zero_variant = match find_zero_variant(enm) {
1123+
let zero_variant = match r#enum::find_zero_variant(enm) {
12031124
Ok(index) => enm.variants.iter().nth(index).unwrap(),
12041125
// Has unknown variants
12051126
Err(true) => {

0 commit comments

Comments
 (0)