Repository: google/zerocopy (Rust)

Issue:
zerocopy-derive lost ability to parse raw identifiers in 0.8.13
zerocopy-derive used to support struct fields with raw identifiers, like `r#type`, up to 0.8.12, but this was broken in 0.8.13. For example:

```rust
use zerocopy::byteorder::network_endian::U16;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};

#[repr(C)]
#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Eq, Clone)]
pub struct IcmpHeader {
    r#type: u8,
    code: u8,
    checksum: U16,
    id: U16,
    sequence: U16,
}
```

This compiles successfully with this `Cargo.toml`:

```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2021"

[dependencies]
zerocopy = { version = "=0.8.12", features = ["derive"] }
```

But fails with this `Cargo.toml`:

```toml
[package]
name = "foo"
version = "0.1.0"
edition = "2021"

[dependencies]
zerocopy = { version = "=0.8.13", features = ["derive"] }
```

with this error message:

```
% cargo build
    Updating crates.io index
   Compiling foo v0.1.0 (/usr/local/google/home/etryzelaar/foo)
error: proc-macro derive panicked
 --> src/lib.rs:5:10
  |
5 | #[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug, PartialEq, Eq, Clone)]
  |          ^^^^^^^^^^^
  |
  = help: message: `"__Zerocopy_Field_r#type"` is not a valid identifier
```


Discussion:
Sorry about that! We need to revise our implementation so that we're suffixing generated idents, not prefixing them.



Patch:
diff --git a/zerocopy-derive/src/enum.rs b/zerocopy-derive/src/enum.rs
index 50e632d383..c41a19f8fb 100644
--- a/zerocopy-derive/src/enum.rs
+++ b/zerocopy-derive/src/enum.rs
@@ -43,7 +43,8 @@ pub(crate) fn generate_tag_enum(repr: &EnumRepr, data: &DataEnum) -> TokenStream
 }
 
 fn tag_ident(variant_ident: &Ident) -> Ident {
-    Ident::new(&format!("___ZEROCOPY_TAG_{}", variant_ident), variant_ident.span())
+    let variant_ident_str = crate::ext::to_ident_str(variant_ident);
+    Ident::new(&format!("___ZEROCOPY_TAG_{}", variant_ident_str), variant_ident.span())
 }
 
 /// Generates a constant for the tag associated with each variant of the enum.
@@ -84,7 +85,8 @@ fn generate_tag_consts(data: &DataEnum) -> TokenStream {
 }
 
 fn variant_struct_ident(variant_ident: &Ident) -> Ident {
-    Ident::new(&format!("___ZerocopyVariantStruct_{}", variant_ident), variant_ident.span())
+    let variant_ident_str = crate::ext::to_ident_str(variant_ident);
+    Ident::new(&format!("___ZerocopyVariantStruct_{}", variant_ident_str), variant_ident.span())
 }
 
 /// Generates variant structs for the given enum variant.
@@ -162,7 +164,8 @@ fn generate_variants_union(generics: &Generics, data: &DataEnum) -> TokenStream
 
         // Field names are prefixed with `__field_` to prevent name collision with
         // the `__nonempty` field.
-        let field_name = Ident::new(&format!("__field_{}", &variant.ident), variant.ident.span());
+        let field_name_str = crate::ext::to_ident_str(&variant.ident);
+        let field_name = Ident::new(&format!("__field_{}", field_name_str), variant.ident.span());
         let variant_struct_ident = variant_struct_ident(&variant.ident);
 
         Some(quote! {
diff --git a/zerocopy-derive/src/ext.rs b/zerocopy-derive/src/ext.rs
index c3e2034b4c..7281129982 100644
--- a/zerocopy-derive/src/ext.rs
+++ b/zerocopy-derive/src/ext.rs
@@ -112,3 +112,12 @@ fn map_fields<'a>(
         })
         .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
+    }
+}
diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs
index 1bd7b69955..22e9dba9c6 100644
--- a/zerocopy-derive/src/lib.rs
+++ b/zerocopy-derive/src/lib.rs
@@ -318,8 +318,10 @@ fn derive_known_layout_inner(
 
             // Generate a valid ident for a type-level handle to a field of a
             // given `name`.
-            let field_index =
-                |name| Ident::new(&format!("__Zerocopy_Field_{}", name), ident.span());
+            let field_index = |name: &TokenStream| {
+                let name = to_ident_str(name);
+                Ident::new(&format!("__Zerocopy_Field_{}", name), ident.span())
+            };
 
             let field_indices: Vec<_> =
                 fields.iter().map(|(_vis, name, _ty)| field_index(name)).collect();


Test patch:
diff --git a/zerocopy-derive/tests/enum_try_from_bytes.rs b/zerocopy-derive/tests/enum_try_from_bytes.rs
index b3f5884281..263d67a1cd 100644
--- a/zerocopy-derive/tests/enum_try_from_bytes.rs
+++ b/zerocopy-derive/tests/enum_try_from_bytes.rs
@@ -642,3 +642,11 @@ mod issue_2051 {
         I32_MUL,
     }
 }
+
+#[derive(imp::TryFromBytes)]
+#[repr(u8)]
+enum RawIdentifierVariant {
+    r#type,
+}
+
+util_assert_impl_all!(RawIdentifierVariant: imp::TryFromBytes);
diff --git a/zerocopy-derive/tests/struct_known_layout.rs b/zerocopy-derive/tests/struct_known_layout.rs
index a316818fdf..69427d1d97 100644
--- a/zerocopy-derive/tests/struct_known_layout.rs
+++ b/zerocopy-derive/tests/struct_known_layout.rs
@@ -112,3 +112,11 @@ struct Packet<P> {
 }
 
 util_assert_impl_all!(Packet<imp::u8>: imp::KnownLayout);
+
+#[derive(imp::KnownLayout)]
+#[repr(C)]
+struct RawIdentifier {
+    r#type: u8,
+}
+
+util_assert_impl_all!(RawIdentifier: imp::KnownLayout);