| 1 | |
| 2 | |
| 3 | use proc_macro2::{Span, TokenStream};
|
| 4 | use syn::{
|
| 5 | parse_quote, Data, DataEnum, DataStruct, DataUnion, Error, Expr, ExprLit, ExprUnary, Lit, UnOp,
|
| 6 | WherePredicate,
|
| 7 | };
|
| 8 |
|
| 9 | use crate::{
|
| 10 | derive::try_from_bytes::derive_try_from_bytes,
|
| 11 | repr::{CompoundRepr, EnumRepr, Repr, Spanned},
|
| 12 | util::{enum_size_from_repr, Ctx, FieldBounds, ImplBlockBuilder, Trait, TraitBound},
|
| 13 | };
|
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | pub(crate) fn find_zero_variant(enm: &DataEnum) -> Result<usize, bool> {
|
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | let mut next_negative_discriminant = Some(0);
|
| 28 |
|
| 29 | |
| 30 | |
| 31 | |
| 32 | |
| 33 | |
| 34 | |
| 35 | let mut has_unknown_discriminants = false;
|
| 36 |
|
| 37 | for (i, v) in enm.variants.iter().enumerate() {
|
| 38 | match v.discriminant.as_ref() {
|
| 39 | |
| 40 | None => {
|
| 41 | match next_negative_discriminant.as_mut() {
|
| 42 | Some(0) => return Ok(i),
|
| 43 | |
| 44 | Some(n) => *n -= 1,
|
| 45 | None => (),
|
| 46 | }
|
| 47 | }
|
| 48 | |
| 49 | Some((_, Expr::Lit(ExprLit { lit: Lit::Int(int), .. }))) => {
|
| 50 | match int.base10_parse::<u128>().ok() {
|
| 51 | Some(0) => return Ok(i),
|
| 52 | Some(_) => next_negative_discriminant = None,
|
| 53 | None => {
|
| 54 | |
| 55 | has_unknown_discriminants = true;
|
| 56 | next_negative_discriminant = None;
|
| 57 | }
|
| 58 | }
|
| 59 | }
|
| 60 | |
| 61 | Some((_, Expr::Unary(ExprUnary { op: UnOp::Neg(_), expr, .. }))) => match &**expr {
|
| 62 | Expr::Lit(ExprLit { lit: Lit::Int(int), .. }) => {
|
| 63 | match int.base10_parse::<u128>().ok() {
|
| 64 | Some(0) => return Ok(i),
|
| 65 | |
| 66 | Some(x) => next_negative_discriminant = Some(x - 1),
|
| 67 | None => {
|
| 68 | |
| 69 | |
| 70 | has_unknown_discriminants = true;
|
| 71 | next_negative_discriminant = None;
|
| 72 | }
|
| 73 | }
|
| 74 | }
|
| 75 | |
| 76 | _ => {
|
| 77 | has_unknown_discriminants = true;
|
| 78 | next_negative_discriminant = None;
|
| 79 | }
|
| 80 | },
|
| 81 | |
| 82 | _ => {
|
| 83 | has_unknown_discriminants = true;
|
| 84 | next_negative_discriminant = None;
|
| 85 | }
|
| 86 | }
|
| 87 | }
|
| 88 |
|
| 89 | Err(has_unknown_discriminants)
|
| 90 | }
|
| 91 | pub(crate) fn derive_from_zeros(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
|
| 92 | let try_from_bytes = derive_try_from_bytes(ctx, top_level)?;
|
| 93 | let from_zeros = match &ctx.ast.data {
|
| 94 | Data::Struct(strct) => derive_from_zeros_struct(ctx, strct),
|
| 95 | Data::Enum(enm) => derive_from_zeros_enum(ctx, enm)?,
|
| 96 | Data::Union(unn) => derive_from_zeros_union(ctx, unn),
|
| 97 | };
|
| 98 | Ok(IntoIterator::into_iter([try_from_bytes, from_zeros]).collect())
|
| 99 | }
|
| 100 | pub(crate) fn derive_from_bytes(ctx: &Ctx, top_level: Trait) -> Result<TokenStream, Error> {
|
| 101 | let from_zeros = derive_from_zeros(ctx, top_level)?;
|
| 102 | let from_bytes = match &ctx.ast.data {
|
| 103 | Data::Struct(strct) => derive_from_bytes_struct(ctx, strct),
|
| 104 | Data::Enum(enm) => derive_from_bytes_enum(ctx, enm)?,
|
| 105 | Data::Union(unn) => derive_from_bytes_union(ctx, unn),
|
| 106 | };
|
| 107 |
|
| 108 | Ok(IntoIterator::into_iter([from_zeros, from_bytes]).collect())
|
| 109 | }
|
| 110 | fn derive_from_zeros_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
|
| 111 | ImplBlockBuilder::new(ctx, strct, Trait::FromZeros, FieldBounds::ALL_SELF).build()
|
| 112 | }
|
| 113 | fn derive_from_zeros_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
|
| 114 | let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
| 115 |
|
| 116 | |
| 117 | |
| 118 | match repr {
|
| 119 | Repr::Compound(Spanned { t: CompoundRepr::C | CompoundRepr::Primitive(_), span: _ }, _) => {
|
| 120 | }
|
| 121 | Repr::Transparent(_) | Repr::Compound(Spanned { t: CompoundRepr::Rust, span: _ }, _) => {
|
| 122 | return ctx.error_or_skip(
|
| 123 | Error::new(
|
| 124 | Span::call_site(),
|
| 125 | "must have #[repr(C)] or #[repr(Int)] attribute in order to guarantee this type's memory layout",
|
| 126 | ),
|
| 127 | );
|
| 128 | }
|
| 129 | }
|
| 130 |
|
| 131 | let zero_variant = match find_zero_variant(enm) {
|
| 132 | Ok(index) => enm.variants.iter().nth(index).unwrap(),
|
| 133 | |
| 134 | Err(true) => {
|
| 135 | return ctx.error_or_skip(Error::new_spanned(
|
| 136 | &ctx.ast,
|
| 137 | "FromZeros only supported on enums with a variant that has a discriminant of `0`\n\
|
| 138 | help: This enum has discriminants which are not literal integers. One of those may \
|
| 139 | define or imply which variant has a discriminant of zero. Use a literal integer to \
|
| 140 | define or imply the variant with a discriminant of zero.",
|
| 141 | ));
|
| 142 | }
|
| 143 | |
| 144 | Err(false) => {
|
| 145 | return ctx.error_or_skip(Error::new_spanned(
|
| 146 | &ctx.ast,
|
| 147 | "FromZeros only supported on enums with a variant that has a discriminant of `0`",
|
| 148 | ));
|
| 149 | }
|
| 150 | };
|
| 151 |
|
| 152 | let zerocopy_crate = &ctx.zerocopy_crate;
|
| 153 | let explicit_bounds = zero_variant
|
| 154 | .fields
|
| 155 | .iter()
|
| 156 | .map(|field| {
|
| 157 | let ty = &field.ty;
|
| 158 | parse_quote! { #ty: #zerocopy_crate::FromZeros }
|
| 159 | })
|
| 160 | .collect::<Vec<WherePredicate>>();
|
| 161 |
|
| 162 | Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromZeros, FieldBounds::Explicit(explicit_bounds))
|
| 163 | .build())
|
| 164 | }
|
| 165 | fn derive_from_zeros_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
|
| 166 | let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
|
| 167 | ImplBlockBuilder::new(ctx, unn, Trait::FromZeros, field_type_trait_bounds).build()
|
| 168 | }
|
| 169 | fn derive_from_bytes_struct(ctx: &Ctx, strct: &DataStruct) -> TokenStream {
|
| 170 | ImplBlockBuilder::new(ctx, strct, Trait::FromBytes, FieldBounds::ALL_SELF).build()
|
| 171 | }
|
| 172 | fn derive_from_bytes_enum(ctx: &Ctx, enm: &DataEnum) -> Result<TokenStream, Error> {
|
| 173 | let repr = EnumRepr::from_attrs(&ctx.ast.attrs)?;
|
| 174 |
|
| 175 | let variants_required = 1usize << enum_size_from_repr(&repr)?;
|
| 176 | if enm.variants.len() != variants_required {
|
| 177 | return ctx.error_or_skip(Error::new_spanned(
|
| 178 | &ctx.ast,
|
| 179 | format!(
|
| 180 | "FromBytes only supported on {} enum with {} variants",
|
| 181 | repr.repr_type_name(),
|
| 182 | variants_required
|
| 183 | ),
|
| 184 | ));
|
| 185 | }
|
| 186 |
|
| 187 | Ok(ImplBlockBuilder::new(ctx, enm, Trait::FromBytes, FieldBounds::ALL_SELF).build())
|
| 188 | }
|
| 189 | fn derive_from_bytes_union(ctx: &Ctx, unn: &DataUnion) -> TokenStream {
|
| 190 | let field_type_trait_bounds = FieldBounds::All(&[TraitBound::Slf]);
|
| 191 | ImplBlockBuilder::new(ctx, unn, Trait::FromBytes, field_type_trait_bounds).build()
|
| 192 | }
|