| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 | use std::num::NonZeroU32;
|
| 12 |
|
| 13 | use proc_macro2::{Span, TokenStream};
|
| 14 | use quote::{quote, quote_spanned, ToTokens};
|
| 15 | use syn::{
|
| 16 | parse_quote, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error,
|
| 17 | Expr, ExprLit, Field, GenericParam, Ident, Index, Lit, LitStr, Meta, Path, Type, Variant,
|
| 18 | Visibility, WherePredicate,
|
| 19 | };
|
| 20 |
|
| 21 | use crate::repr::{CompoundRepr, EnumRepr, PrimitiveRepr, Repr, Spanned};
|
| 22 |
|
| 23 | pub(crate) struct Ctx {
|
| 24 | pub(crate) ast: DeriveInput,
|
| 25 | pub(crate) zerocopy_crate: Path,
|
| 26 |
|
| 27 | |
| 28 | |
| 29 | pub(crate) skip_on_error: bool,
|
| 30 |
|
| 31 | |
| 32 | pub(crate) on_error_span: Option<proc_macro2::Span>,
|
| 33 | }
|
| 34 |
|
| 35 | impl Ctx {
|
| 36 | |
| 37 | |
| 38 | pub(crate) fn try_from_derive_input(ast: DeriveInput) -> Result<Self, Error> {
|
| 39 | let mut path = parse_quote!(::zerocopy);
|
| 40 | let mut skip_on_error = false;
|
| 41 | let mut on_error_span = None;
|
| 42 |
|
| 43 | for attr in &ast.attrs {
|
| 44 | if let Meta::List(ref meta_list) = attr.meta {
|
| 45 | if meta_list.path.is_ident("zerocopy") {
|
| 46 | attr.parse_nested_meta(|meta| {
|
| 47 | if meta.path.is_ident("crate") {
|
| 48 | let expr = meta.value().and_then(|value| value.parse());
|
| 49 | if let Ok(Expr::Lit(ExprLit { lit: Lit::Str(lit), .. })) = expr {
|
| 50 | if let Ok(path_lit) = lit.parse::<Ident>() {
|
| 51 | path = parse_quote!(::#path_lit);
|
| 52 | return Ok(());
|
| 53 | }
|
| 54 | }
|
| 55 |
|
| 56 | return Err(Error::new(
|
| 57 | Span::call_site(),
|
| 58 | "`crate` attribute requires a path as the value",
|
| 59 | ));
|
| 60 | }
|
| 61 |
|
| 62 | if meta.path.is_ident("on_error") {
|
| 63 | on_error_span = Some(meta.path.span());
|
| 64 | let value = meta.value()?;
|
| 65 | let s: LitStr = value.parse()?;
|
| 66 | match s.value().as_str() {
|
| 67 | "skip" => skip_on_error = true,
|
| 68 | "fail" => skip_on_error = false,
|
| 69 | _ => return Err(Error::new(
|
| 70 | s.span(),
|
| 71 | "unrecognized value for `on_error` attribute from `zerocopy`; expected `skip` or `fail`",
|
| 72 | )),
|
| 73 | }
|
| 74 | return Ok(());
|
| 75 | }
|
| 76 |
|
| 77 | Err(Error::new(
|
| 78 | Span::call_site(),
|
| 79 | format!(
|
| 80 | "unknown attribute encountered: {}",
|
| 81 | meta.path.into_token_stream()
|
| 82 | ),
|
| 83 | ))
|
| 84 | })?;
|
| 85 | }
|
| 86 | }
|
| 87 | }
|
| 88 |
|
| 89 | Ok(Self { ast, zerocopy_crate: path, skip_on_error, on_error_span })
|
| 90 | }
|
| 91 |
|
| 92 | pub(crate) fn with_input(&self, input: &DeriveInput) -> Self {
|
| 93 | Self {
|
| 94 | ast: input.clone(),
|
| 95 | zerocopy_crate: self.zerocopy_crate.clone(),
|
| 96 | skip_on_error: self.skip_on_error,
|
| 97 | on_error_span: self.on_error_span,
|
| 98 | }
|
| 99 | }
|
| 100 |
|
| 101 | pub(crate) fn skip_on_error(mut self) -> Self {
|
| 102 | self.skip_on_error = true;
|
| 103 | self
|
| 104 | }
|
| 105 |
|
| 106 | pub(crate) fn core_path(&self) -> TokenStream {
|
| 107 | let zerocopy_crate = &self.zerocopy_crate;
|
| 108 | quote!(#zerocopy_crate::util::macro_util::core_reexport)
|
| 109 | }
|
| 110 |
|
| 111 | pub(crate) fn cfg_compile_error(&self) -> TokenStream {
|
| 112 | |
| 113 | |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | |
| 119 | if cfg!(zerocopy_unstable_linux) {
|
| 120 | quote!()
|
| 121 | } else if let Some(span) = self.on_error_span {
|
| 122 | let core = self.core_path();
|
| 123 | let error_message =
|
| 124 | "`on_error` is experimental; pass '--cfg zerocopy_unstable_linux' to enable";
|
| 125 | quote::quote_spanned! {span=>
|
| 126 | #[allow(unused_attributes, unexpected_cfgs)]
|
| 127 | const _: () = {
|
| 128 | #[cfg(not(zerocopy_unstable_linux))]
|
| 129 | #core::compile_error!(#error_message);
|
| 130 | };
|
| 131 | }
|
| 132 | } else {
|
| 133 | quote!()
|
| 134 | }
|
| 135 | }
|
| 136 |
|
| 137 | pub(crate) fn error_or_skip<E>(&self, error: E) -> Result<TokenStream, E> {
|
| 138 | if self.skip_on_error {
|
| 139 | Ok(self.cfg_compile_error())
|
| 140 | } else {
|
| 141 | Err(error)
|
| 142 | }
|
| 143 | }
|
| 144 | }
|
| 145 |
|
| 146 | pub(crate) trait DataExt {
|
| 147 | |
| 148 | |
| 149 | |
| 150 | |
| 151 | |
| 152 | |
| 153 | |
| 154 | |
| 155 | fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)>;
|
| 156 |
|
| 157 | fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)>;
|
| 158 |
|
| 159 | fn tag(&self) -> Option<Ident>;
|
| 160 | }
|
| 161 |
|
| 162 | impl DataExt for Data {
|
| 163 | fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
| 164 | match self {
|
| 165 | Data::Struct(strc) => strc.fields(),
|
| 166 | Data::Enum(enm) => enm.fields(),
|
| 167 | Data::Union(un) => un.fields(),
|
| 168 | }
|
| 169 | }
|
| 170 |
|
| 171 | fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
| 172 | match self {
|
| 173 | Data::Struct(strc) => strc.variants(),
|
| 174 | Data::Enum(enm) => enm.variants(),
|
| 175 | Data::Union(un) => un.variants(),
|
| 176 | }
|
| 177 | }
|
| 178 |
|
| 179 | fn tag(&self) -> Option<Ident> {
|
| 180 | match self {
|
| 181 | Data::Struct(strc) => strc.tag(),
|
| 182 | Data::Enum(enm) => enm.tag(),
|
| 183 | Data::Union(un) => un.tag(),
|
| 184 | }
|
| 185 | }
|
| 186 | }
|
| 187 |
|
| 188 | impl DataExt for DataStruct {
|
| 189 | fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
| 190 | map_fields(&self.fields)
|
| 191 | }
|
| 192 |
|
| 193 | fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
| 194 | vec![(None, self.fields())]
|
| 195 | }
|
| 196 |
|
| 197 | fn tag(&self) -> Option<Ident> {
|
| 198 | None
|
| 199 | }
|
| 200 | }
|
| 201 |
|
| 202 | impl DataExt for DataEnum {
|
| 203 | fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
| 204 | map_fields(self.variants.iter().flat_map(|var| &var.fields))
|
| 205 | }
|
| 206 |
|
| 207 | fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
| 208 | self.variants.iter().map(|var| (Some(var), map_fields(&var.fields))).collect()
|
| 209 | }
|
| 210 |
|
| 211 | fn tag(&self) -> Option<Ident> {
|
| 212 | Some(Ident::new("___ZerocopyTag", Span::call_site()))
|
| 213 | }
|
| 214 | }
|
| 215 |
|
| 216 | impl DataExt for DataUnion {
|
| 217 | fn fields(&self) -> Vec<(&Visibility, TokenStream, &Type)> {
|
| 218 | map_fields(&self.fields.named)
|
| 219 | }
|
| 220 |
|
| 221 | fn variants(&self) -> Vec<(Option<&Variant>, Vec<(&Visibility, TokenStream, &Type)>)> {
|
| 222 | vec![(None, self.fields())]
|
| 223 | }
|
| 224 |
|
| 225 | fn tag(&self) -> Option<Ident> {
|
| 226 | None
|
| 227 | }
|
| 228 | }
|
| 229 |
|
| 230 | fn map_fields<'a>(
|
| 231 | fields: impl 'a + IntoIterator<Item = &'a Field>,
|
| 232 | ) -> Vec<(&'a Visibility, TokenStream, &'a Type)> {
|
| 233 | fields
|
| 234 | .into_iter()
|
| 235 | .enumerate()
|
| 236 | .map(|(idx, f)| {
|
| 237 | (
|
| 238 | &f.vis,
|
| 239 | f.ident
|
| 240 | .as_ref()
|
| 241 | .map(ToTokens::to_token_stream)
|
| 242 | .unwrap_or_else(|| Index::from(idx).to_token_stream()),
|
| 243 | &f.ty,
|
| 244 | )
|
| 245 | })
|
| 246 | .collect()
|
| 247 | }
|
| 248 |
|
| 249 | pub(crate) fn to_ident_str(t: &impl ToString) -> String {
|
| 250 | let s = t.to_string();
|
| 251 | if let Some(stripped) = s.strip_prefix("r#") {
|
| 252 | stripped.to_string()
|
| 253 | } else {
|
| 254 | s
|
| 255 | }
|
| 256 | }
|
| 257 |
|
| 258 | |
| 259 | |
| 260 | pub(crate) enum PaddingCheck {
|
| 261 | |
| 262 | |
| 263 | Struct,
|
| 264 | |
| 265 | ReprCStruct,
|
| 266 | |
| 267 | Union,
|
| 268 | |
| 269 | |
| 270 | |
| 271 | |
| 272 | Enum { tag_type_definition: TokenStream },
|
| 273 | }
|
| 274 |
|
| 275 | impl PaddingCheck {
|
| 276 | |
| 277 | |
| 278 | pub(crate) fn validator_trait_and_macro_idents(&self) -> (Ident, Ident) {
|
| 279 | let (trt, mcro) = match self {
|
| 280 | PaddingCheck::Struct => ("PaddingFree", "struct_padding"),
|
| 281 | PaddingCheck::ReprCStruct => ("DynamicPaddingFree", "repr_c_struct_has_padding"),
|
| 282 | PaddingCheck::Union => ("PaddingFree", "union_padding"),
|
| 283 | PaddingCheck::Enum { .. } => ("PaddingFree", "enum_padding"),
|
| 284 | };
|
| 285 |
|
| 286 | let trt = Ident::new(trt, Span::call_site());
|
| 287 | let mcro = Ident::new(mcro, Span::call_site());
|
| 288 | (trt, mcro)
|
| 289 | }
|
| 290 |
|
| 291 | |
| 292 | |
| 293 | pub(crate) fn validator_macro_context(&self) -> Option<&TokenStream> {
|
| 294 | match self {
|
| 295 | PaddingCheck::Struct | PaddingCheck::ReprCStruct | PaddingCheck::Union => None,
|
| 296 | PaddingCheck::Enum { tag_type_definition } => Some(tag_type_definition),
|
| 297 | }
|
| 298 | }
|
| 299 | }
|
| 300 |
|
| 301 | #[derive(Clone)]
|
| 302 | pub(crate) enum Trait {
|
| 303 | KnownLayout,
|
| 304 | HasTag,
|
| 305 | HasField {
|
| 306 | variant_id: Box<Expr>,
|
| 307 | field: Box<Type>,
|
| 308 | field_id: Box<Expr>,
|
| 309 | },
|
| 310 | ProjectField {
|
| 311 | variant_id: Box<Expr>,
|
| 312 | field: Box<Type>,
|
| 313 | field_id: Box<Expr>,
|
| 314 | invariants: Box<Type>,
|
| 315 | },
|
| 316 | Immutable,
|
| 317 | TryFromBytes,
|
| 318 | FromZeros,
|
| 319 | FromBytes,
|
| 320 | IntoBytes,
|
| 321 | Unaligned,
|
| 322 | Sized,
|
| 323 | ByteHash,
|
| 324 | ByteEq,
|
| 325 | SplitAt,
|
| 326 | }
|
| 327 |
|
| 328 | impl ToTokens for Trait {
|
| 329 | fn to_tokens(&self, tokens: &mut TokenStream) {
|
| 330 | |
| 331 | |
| 332 | |
| 333 | |
| 334 | |
| 335 | |
| 336 | |
| 337 | |
| 338 | |
| 339 | let s = match self {
|
| 340 | Trait::HasField { .. } => "HasField",
|
| 341 | Trait::ProjectField { .. } => "ProjectField",
|
| 342 | Trait::KnownLayout => "KnownLayout",
|
| 343 | Trait::HasTag => "HasTag",
|
| 344 | Trait::Immutable => "Immutable",
|
| 345 | Trait::TryFromBytes => "TryFromBytes",
|
| 346 | Trait::FromZeros => "FromZeros",
|
| 347 | Trait::FromBytes => "FromBytes",
|
| 348 | Trait::IntoBytes => "IntoBytes",
|
| 349 | Trait::Unaligned => "Unaligned",
|
| 350 | Trait::Sized => "Sized",
|
| 351 | Trait::ByteHash => "ByteHash",
|
| 352 | Trait::ByteEq => "ByteEq",
|
| 353 | Trait::SplitAt => "SplitAt",
|
| 354 | };
|
| 355 | let ident = Ident::new(s, Span::call_site());
|
| 356 | let arguments: Option<syn::AngleBracketedGenericArguments> = match self {
|
| 357 | Trait::HasField { variant_id, field, field_id } => {
|
| 358 | Some(parse_quote!(<#field, #variant_id, #field_id>))
|
| 359 | }
|
| 360 | Trait::ProjectField { variant_id, field, field_id, invariants } => {
|
| 361 | Some(parse_quote!(<#field, #invariants, #variant_id, #field_id>))
|
| 362 | }
|
| 363 | Trait::KnownLayout
|
| 364 | | Trait::HasTag
|
| 365 | | Trait::Immutable
|
| 366 | | Trait::TryFromBytes
|
| 367 | | Trait::FromZeros
|
| 368 | | Trait::FromBytes
|
| 369 | | Trait::IntoBytes
|
| 370 | | Trait::Unaligned
|
| 371 | | Trait::Sized
|
| 372 | | Trait::ByteHash
|
| 373 | | Trait::ByteEq
|
| 374 | | Trait::SplitAt => None,
|
| 375 | };
|
| 376 | tokens.extend(quote!(#ident #arguments));
|
| 377 | }
|
| 378 | }
|
| 379 |
|
| 380 | impl Trait {
|
| 381 | pub(crate) fn crate_path(&self, ctx: &Ctx) -> Path {
|
| 382 | let zerocopy_crate = &ctx.zerocopy_crate;
|
| 383 | let core = ctx.core_path();
|
| 384 | match self {
|
| 385 | Self::Sized => parse_quote!(#core::marker::#self),
|
| 386 | _ => parse_quote!(#zerocopy_crate::#self),
|
| 387 | }
|
| 388 | }
|
| 389 | }
|
| 390 |
|
| 391 | pub(crate) enum TraitBound {
|
| 392 | Slf,
|
| 393 | Other(Trait),
|
| 394 | }
|
| 395 |
|
| 396 | pub(crate) enum FieldBounds<'a> {
|
| 397 | None,
|
| 398 | All(&'a [TraitBound]),
|
| 399 | Trailing(&'a [TraitBound]),
|
| 400 | Explicit(Vec<WherePredicate>),
|
| 401 | }
|
| 402 |
|
| 403 | impl<'a> FieldBounds<'a> {
|
| 404 | pub(crate) const ALL_SELF: FieldBounds<'a> = FieldBounds::All(&[TraitBound::Slf]);
|
| 405 | pub(crate) const TRAILING_SELF: FieldBounds<'a> = FieldBounds::Trailing(&[TraitBound::Slf]);
|
| 406 | }
|
| 407 |
|
| 408 | pub(crate) enum SelfBounds<'a> {
|
| 409 | None,
|
| 410 | All(&'a [Trait]),
|
| 411 | }
|
| 412 |
|
| 413 | |
| 414 | |
| 415 | #[allow(clippy::needless_lifetimes)]
|
| 416 | impl<'a> SelfBounds<'a> {
|
| 417 | pub(crate) const SIZED: Self = Self::All(&[Trait::Sized]);
|
| 418 | }
|
| 419 |
|
| 420 | |
| 421 | pub(crate) fn normalize_bounds<'a>(
|
| 422 | slf: &'a Trait,
|
| 423 | bounds: &'a [TraitBound],
|
| 424 | ) -> impl 'a + Iterator<Item = Trait> {
|
| 425 | bounds.iter().map(move |bound| match bound {
|
| 426 | TraitBound::Slf => slf.clone(),
|
| 427 | TraitBound::Other(trt) => trt.clone(),
|
| 428 | })
|
| 429 | }
|
| 430 |
|
| 431 | pub(crate) struct ImplBlockBuilder<'a> {
|
| 432 | ctx: &'a Ctx,
|
| 433 | data: &'a dyn DataExt,
|
| 434 | trt: Trait,
|
| 435 | field_type_trait_bounds: FieldBounds<'a>,
|
| 436 | self_type_trait_bounds: SelfBounds<'a>,
|
| 437 | padding_check: Option<PaddingCheck>,
|
| 438 | param_extras: Vec<GenericParam>,
|
| 439 | inner_extras: Option<TokenStream>,
|
| 440 | outer_extras: Option<TokenStream>,
|
| 441 | }
|
| 442 |
|
| 443 | impl<'a> ImplBlockBuilder<'a> {
|
| 444 | pub(crate) fn new(
|
| 445 | ctx: &'a Ctx,
|
| 446 | data: &'a dyn DataExt,
|
| 447 | trt: Trait,
|
| 448 | field_type_trait_bounds: FieldBounds<'a>,
|
| 449 | ) -> Self {
|
| 450 | Self {
|
| 451 | ctx,
|
| 452 | data,
|
| 453 | trt,
|
| 454 | field_type_trait_bounds,
|
| 455 | self_type_trait_bounds: SelfBounds::None,
|
| 456 | padding_check: None,
|
| 457 | param_extras: Vec::new(),
|
| 458 | inner_extras: None,
|
| 459 | outer_extras: None,
|
| 460 | }
|
| 461 | }
|
| 462 |
|
| 463 | pub(crate) fn self_type_trait_bounds(mut self, self_type_trait_bounds: SelfBounds<'a>) -> Self {
|
| 464 | self.self_type_trait_bounds = self_type_trait_bounds;
|
| 465 | self
|
| 466 | }
|
| 467 |
|
| 468 | pub(crate) fn padding_check<P: Into<Option<PaddingCheck>>>(mut self, padding_check: P) -> Self {
|
| 469 | self.padding_check = padding_check.into();
|
| 470 | self
|
| 471 | }
|
| 472 |
|
| 473 | pub(crate) fn param_extras(mut self, param_extras: Vec<GenericParam>) -> Self {
|
| 474 | self.param_extras.extend(param_extras);
|
| 475 | self
|
| 476 | }
|
| 477 |
|
| 478 | pub(crate) fn inner_extras(mut self, inner_extras: TokenStream) -> Self {
|
| 479 | self.inner_extras = Some(inner_extras);
|
| 480 | self
|
| 481 | }
|
| 482 |
|
| 483 | pub(crate) fn outer_extras<T: Into<Option<TokenStream>>>(mut self, outer_extras: T) -> Self {
|
| 484 | self.outer_extras = outer_extras.into();
|
| 485 | self
|
| 486 | }
|
| 487 |
|
| 488 | pub(crate) fn build(self) -> TokenStream {
|
| 489 | |
| 490 | |
| 491 | |
| 492 | |
| 493 | |
| 494 | |
| 495 | |
| 496 | |
| 497 | |
| 498 | |
| 499 | |
| 500 | |
| 501 | |
| 502 | |
| 503 | |
| 504 | |
| 505 | |
| 506 | |
| 507 | |
| 508 | |
| 509 | |
| 510 | |
| 511 | |
| 512 | |
| 513 | |
| 514 | |
| 515 | |
| 516 | |
| 517 | |
| 518 | |
| 519 | |
| 520 | |
| 521 | |
| 522 | |
| 523 | |
| 524 | |
| 525 | |
| 526 | |
| 527 | |
| 528 | |
| 529 | |
| 530 | |
| 531 | |
| 532 | |
| 533 | |
| 534 | |
| 535 | |
| 536 | |
| 537 | |
| 538 | |
| 539 | |
| 540 | |
| 541 | |
| 542 | |
| 543 | |
| 544 | |
| 545 | |
| 546 | |
| 547 |
|
| 548 | let type_ident = &self.ctx.ast.ident;
|
| 549 | let trait_path = self.trt.crate_path(self.ctx);
|
| 550 | let fields = self.data.fields();
|
| 551 | let variants = self.data.variants();
|
| 552 | let tag = self.data.tag();
|
| 553 | let zerocopy_crate = &self.ctx.zerocopy_crate;
|
| 554 |
|
| 555 | fn bound_tt(ty: &Type, traits: impl Iterator<Item = Trait>, ctx: &Ctx) -> WherePredicate {
|
| 556 | let traits = traits.map(|t| t.crate_path(ctx));
|
| 557 | parse_quote!(#ty: #(#traits)+*)
|
| 558 | }
|
| 559 | let field_type_bounds: Vec<_> = match (self.field_type_trait_bounds, &fields[..]) {
|
| 560 | (FieldBounds::All(traits), _) => fields
|
| 561 | .iter()
|
| 562 | .map(|(_vis, _name, ty)| {
|
| 563 | bound_tt(ty, normalize_bounds(&self.trt, traits), self.ctx)
|
| 564 | })
|
| 565 | .collect(),
|
| 566 | (FieldBounds::None, _) | (FieldBounds::Trailing(..), []) => vec![],
|
| 567 | (FieldBounds::Trailing(traits), [.., last]) => {
|
| 568 | vec![bound_tt(last.2, normalize_bounds(&self.trt, traits), self.ctx)]
|
| 569 | }
|
| 570 | (FieldBounds::Explicit(bounds), _) => bounds,
|
| 571 | };
|
| 572 |
|
| 573 | let padding_check_bound = self
|
| 574 | .padding_check
|
| 575 | .map(|check| {
|
| 576 | |
| 577 | |
| 578 | |
| 579 | |
| 580 | let repr =
|
| 581 | Repr::<PrimitiveRepr, NonZeroU32>::from_attrs(&self.ctx.ast.attrs).unwrap();
|
| 582 | let core = self.ctx.core_path();
|
| 583 | let option = quote! { #core::option::Option };
|
| 584 | let nonzero = quote! { #core::num::NonZeroUsize };
|
| 585 | let none = quote! { #option::None::<#nonzero> };
|
| 586 | let repr_align =
|
| 587 | repr.get_align().map(|spanned| {
|
| 588 | let n = spanned.t.get();
|
| 589 | quote_spanned! { spanned.span => (#nonzero::new(#n as usize)) }
|
| 590 | }).unwrap_or(quote! { (#none) });
|
| 591 | let repr_packed =
|
| 592 | repr.get_packed().map(|packed| {
|
| 593 | let n = packed.get();
|
| 594 | quote! { (#nonzero::new(#n as usize)) }
|
| 595 | }).unwrap_or(quote! { (#none) });
|
| 596 | let variant_types = variants.iter().map(|(_, fields)| {
|
| 597 | let types = fields.iter().map(|(_vis, _name, ty)| ty);
|
| 598 | quote!([#((#types)),*])
|
| 599 | });
|
| 600 | let validator_context = check.validator_macro_context();
|
| 601 | let (trt, validator_macro) = check.validator_trait_and_macro_idents();
|
| 602 | let t = tag.iter();
|
| 603 | parse_quote! {
|
| 604 | (): #zerocopy_crate::util::macro_util::#trt<
|
| 605 | Self,
|
| 606 | {
|
| 607 | #validator_context
|
| 608 | #zerocopy_crate::#validator_macro!(Self, #repr_align, #repr_packed, #(#t,)* #(#variant_types),*)
|
| 609 | }
|
| 610 | >
|
| 611 | }
|
| 612 | });
|
| 613 |
|
| 614 | let self_bounds: Option<WherePredicate> = match self.self_type_trait_bounds {
|
| 615 | SelfBounds::None => None,
|
| 616 | SelfBounds::All(traits) => {
|
| 617 | Some(bound_tt(&parse_quote!(Self), traits.iter().cloned(), self.ctx))
|
| 618 | }
|
| 619 | };
|
| 620 |
|
| 621 | let zerocopy_bounds =
|
| 622 | field_type_bounds
|
| 623 | .into_iter()
|
| 624 | .chain(padding_check_bound)
|
| 625 | .chain(self_bounds)
|
| 626 | .map(|bound| {
|
| 627 | if self.ctx.skip_on_error {
|
| 628 | parse_quote!(for<'zc> #bound)
|
| 629 | } else {
|
| 630 | bound.clone()
|
| 631 | }
|
| 632 | })
|
| 633 | .collect::<Vec<_>>();
|
| 634 |
|
| 635 | let bounds = self
|
| 636 | .ctx
|
| 637 | .ast
|
| 638 | .generics
|
| 639 | .where_clause
|
| 640 | .as_ref()
|
| 641 | .map(|where_clause| where_clause.predicates.iter())
|
| 642 | .into_iter()
|
| 643 | .flatten()
|
| 644 | .chain(zerocopy_bounds.iter());
|
| 645 |
|
| 646 | |
| 647 | let mut params: Vec<_> = self
|
| 648 | .ctx
|
| 649 | .ast
|
| 650 | .generics
|
| 651 | .params
|
| 652 | .clone()
|
| 653 | .into_iter()
|
| 654 | .map(|mut param| {
|
| 655 | match &mut param {
|
| 656 | GenericParam::Type(ty) => ty.default = None,
|
| 657 | GenericParam::Const(cnst) => cnst.default = None,
|
| 658 | GenericParam::Lifetime(_) => {}
|
| 659 | }
|
| 660 | parse_quote!(#param)
|
| 661 | })
|
| 662 | .chain(self.param_extras)
|
| 663 | .collect();
|
| 664 |
|
| 665 | |
| 666 | |
| 667 | params.sort_by_cached_key(|param| match param {
|
| 668 | GenericParam::Lifetime(_) => 0,
|
| 669 | GenericParam::Type(_) => 1,
|
| 670 | GenericParam::Const(_) => 2,
|
| 671 | });
|
| 672 |
|
| 673 | |
| 674 | |
| 675 | let param_idents = self.ctx.ast.generics.params.iter().map(|param| match param {
|
| 676 | GenericParam::Type(ty) => {
|
| 677 | let ident = &ty.ident;
|
| 678 | quote!(#ident)
|
| 679 | }
|
| 680 | GenericParam::Lifetime(l) => {
|
| 681 | let ident = &l.lifetime;
|
| 682 | quote!(#ident)
|
| 683 | }
|
| 684 | GenericParam::Const(cnst) => {
|
| 685 | let ident = &cnst.ident;
|
| 686 | quote!({#ident})
|
| 687 | }
|
| 688 | });
|
| 689 |
|
| 690 | let inner_extras = self.inner_extras;
|
| 691 | let allow_trivial_bounds =
|
| 692 | if self.ctx.skip_on_error { quote!(#[allow(trivial_bounds)]) } else { quote!() };
|
| 693 | let impl_tokens = quote! {
|
| 694 | #allow_trivial_bounds
|
| 695 | unsafe impl < #(#params),* > #trait_path for #type_ident < #(#param_idents),* >
|
| 696 | where
|
| 697 | #(#bounds,)*
|
| 698 | {
|
| 699 | fn only_derive_is_allowed_to_implement_this_trait() {}
|
| 700 |
|
| 701 | #inner_extras
|
| 702 | }
|
| 703 | };
|
| 704 |
|
| 705 | let outer_extras = self.outer_extras.filter(|e| !e.is_empty());
|
| 706 | let cfg_compile_error = self.ctx.cfg_compile_error();
|
| 707 | const_block([Some(cfg_compile_error), Some(impl_tokens), outer_extras])
|
| 708 | }
|
| 709 | }
|
| 710 |
|
| 711 | |
| 712 | |
| 713 | |
| 714 | |
| 715 | |
| 716 | |
| 717 | |
| 718 | #[allow(unused)]
|
| 719 | trait BoolExt {
|
| 720 | fn then_some<T>(self, t: T) -> Option<T>;
|
| 721 | }
|
| 722 |
|
| 723 | impl BoolExt for bool {
|
| 724 | fn then_some<T>(self, t: T) -> Option<T> {
|
| 725 | if self {
|
| 726 | Some(t)
|
| 727 | } else {
|
| 728 | None
|
| 729 | }
|
| 730 | }
|
| 731 | }
|
| 732 |
|
| 733 | pub(crate) fn const_block(items: impl IntoIterator<Item = Option<TokenStream>>) -> TokenStream {
|
| 734 | let items = items.into_iter().flatten();
|
| 735 | quote! {
|
| 736 | #[allow(
|
| 737 | // FIXME(#553): Add a test that generates a warning when
|
| 738 | // `#[allow(deprecated)]` isn't present.
|
| 739 | deprecated,
|
| 740 | // Required on some rustc versions due to a lint that is only
|
| 741 | // triggered when `derive(KnownLayout)` is applied to `repr(C)`
|
| 742 | // structs that are generated by macros. See #2177 for details.
|
| 743 | private_bounds,
|
| 744 | non_local_definitions,
|
| 745 | non_camel_case_types,
|
| 746 | non_upper_case_globals,
|
| 747 | non_snake_case,
|
| 748 | non_ascii_idents,
|
| 749 | clippy::missing_inline_in_public_items,
|
| 750 | )]
|
| 751 | #[deny(ambiguous_associated_items)]
|
| 752 | |
| 753 | |
| 754 | #[automatically_derived]
|
| 755 | const _: () = {
|
| 756 | #(#items)*
|
| 757 | };
|
| 758 | }
|
| 759 | }
|
| 760 | pub(crate) fn generate_tag_enum(ctx: &Ctx, repr: &EnumRepr, data: &DataEnum) -> TokenStream {
|
| 761 | let zerocopy_crate = &ctx.zerocopy_crate;
|
| 762 | let variants = data.variants.iter().map(|v| {
|
| 763 | let ident = &v.ident;
|
| 764 | if let Some((eq, discriminant)) = &v.discriminant {
|
| 765 | quote! { #ident #eq #discriminant }
|
| 766 | } else {
|
| 767 | quote! { #ident }
|
| 768 | }
|
| 769 | });
|
| 770 |
|
| 771 | |
| 772 | |
| 773 | |
| 774 | let repr = match repr {
|
| 775 | EnumRepr::Transparent(span) => quote::quote_spanned! { *span => #[repr(transparent)] },
|
| 776 | EnumRepr::Compound(c, _) => quote! { #c },
|
| 777 | };
|
| 778 |
|
| 779 | quote! {
|
| 780 | #repr
|
| 781 | #[allow(dead_code)]
|
| 782 | pub enum ___ZerocopyTag {
|
| 783 | #(#variants,)*
|
| 784 | }
|
| 785 |
|
| 786 | |
| 787 | |
| 788 | unsafe impl #zerocopy_crate::Immutable for ___ZerocopyTag {
|
| 789 | fn only_derive_is_allowed_to_implement_this_trait() {}
|
| 790 | }
|
| 791 | }
|
| 792 | }
|
| 793 | pub(crate) fn enum_size_from_repr(repr: &EnumRepr) -> Result<usize, Error> {
|
| 794 | use CompoundRepr::*;
|
| 795 | use PrimitiveRepr::*;
|
| 796 | use Repr::*;
|
| 797 | match repr {
|
| 798 | Transparent(span)
|
| 799 | | Compound(
|
| 800 | Spanned {
|
| 801 | t: C | Rust | Primitive(U32 | I32 | U64 | I64 | U128 | I128 | Usize | Isize),
|
| 802 | span,
|
| 803 | },
|
| 804 | _,
|
| 805 | ) => Err(Error::new(
|
| 806 | *span,
|
| 807 | "`FromBytes` only supported on enums with `#[repr(...)]` attributes `u8`, `i8`, `u16`, or `i16`",
|
| 808 | )),
|
| 809 | Compound(Spanned { t: Primitive(U8 | I8), span: _ }, _align) => Ok(8),
|
| 810 | Compound(Spanned { t: Primitive(U16 | I16), span: _ }, _align) => Ok(16),
|
| 811 | }
|
| 812 | }
|
| 813 |
|
| 814 | #[cfg(test)]
|
| 815 | pub(crate) mod testutil {
|
| 816 | use proc_macro2::TokenStream;
|
| 817 | use syn::visit::{self, Visit};
|
| 818 |
|
| 819 | |
| 820 | |
| 821 | |
| 822 | |
| 823 | |
| 824 | pub(crate) fn check_hygiene(ts: TokenStream) {
|
| 825 | struct AmbiguousItemVisitor;
|
| 826 |
|
| 827 | impl<'ast> Visit<'ast> for AmbiguousItemVisitor {
|
| 828 | fn visit_path(&mut self, i: &'ast syn::Path) {
|
| 829 | if i.segments.len() > 1 && i.segments.first().unwrap().ident == "Self" {
|
| 830 | panic!(
|
| 831 | "Found ambiguous path `{}` in generated output. \
|
| 832 | All associated item access must be fully qualified (e.g., `<Self as Trait>::Item`) \
|
| 833 | to prevent hygiene issues.",
|
| 834 | quote::quote!(#i)
|
| 835 | );
|
| 836 | }
|
| 837 | visit::visit_path(self, i);
|
| 838 | }
|
| 839 | }
|
| 840 |
|
| 841 | let file = syn::parse2::<syn::File>(ts).expect("failed to parse generated output as File");
|
| 842 | AmbiguousItemVisitor.visit_file(&file);
|
| 843 | }
|
| 844 |
|
| 845 | #[test]
|
| 846 | fn test_check_hygiene_success() {
|
| 847 | check_hygiene(quote::quote! {
|
| 848 | fn foo() {
|
| 849 | let _ = <Self as Trait>::Item;
|
| 850 | }
|
| 851 | });
|
| 852 | }
|
| 853 |
|
| 854 | #[test]
|
| 855 | #[should_panic(expected = "Found ambiguous path `Self :: Ambiguous`")]
|
| 856 | fn test_check_hygiene_failure() {
|
| 857 | check_hygiene(quote::quote! {
|
| 858 | fn foo() {
|
| 859 | let _ = Self::Ambiguous;
|
| 860 | }
|
| 861 | });
|
| 862 | }
|
| 863 | }
|