From 1fb992a22087afac7fb55d8c5ca5b711718cf058 Mon Sep 17 00:00:00 2001
From: Henri Chataing <henrichataing@google.com>
Date: Tue, 17 Jan 2023 12:14:26 +0100
Subject: [PATCH] Annotate AST nodes with loc, size information

Test: cargo build && cargo test
Change-Id: Ibede334254e6faa7920139a68747792e87ed05a3
---
 tools/pdl/src/ast.rs                          | 187 ++++----
 tools/pdl/src/backends/intermediate.rs        |  53 +-
 tools/pdl/src/backends/json.rs                |   4 +-
 tools/pdl/src/backends/rust.rs                |  20 +-
 tools/pdl/src/backends/rust/declarations.rs   |  11 +-
 tools/pdl/src/backends/rust/parser.rs         |  15 +-
 tools/pdl/src/backends/rust/serializer.rs     |  13 +-
 tools/pdl/src/backends/rust/types.rs          |   9 +-
 .../src/backends/rust_no_allocation/mod.rs    |  19 +-
 .../rust_no_allocation/packet_parser.rs       |  57 +--
 .../rust_no_allocation/packet_serializer.rs   |  53 +-
 .../src/backends/rust_no_allocation/test.rs   |  55 +--
 tools/pdl/src/lint.rs                         | 452 ++++++++++--------
 tools/pdl/src/parser.rs                       | 269 ++++++-----
 14 files changed, 652 insertions(+), 565 deletions(-)

diff --git a/tools/pdl/src/ast.rs b/tools/pdl/src/ast.rs
index e837b2882d5..4e708c04219 100644
--- a/tools/pdl/src/ast.rs
+++ b/tools/pdl/src/ast.rs
@@ -30,6 +30,11 @@ pub struct SourceRange {
     pub end: SourceLocation,
 }
 
+pub trait Annotation: fmt::Debug + Serialize {
+    type FieldAnnotation: Default + fmt::Debug;
+    type DeclAnnotation: Default + fmt::Debug;
+}
+
 #[derive(Debug, Serialize)]
 #[serde(tag = "kind", rename = "comment")]
 pub struct Comment {
@@ -70,34 +75,32 @@ pub struct Constraint {
 
 #[derive(Debug, Serialize, Clone)]
 #[serde(tag = "kind")]
-pub enum Field {
+pub enum FieldDesc {
     #[serde(rename = "checksum_field")]
-    Checksum { loc: SourceRange, field_id: String },
+    Checksum { field_id: String },
     #[serde(rename = "padding_field")]
-    Padding { loc: SourceRange, size: usize },
+    Padding { size: usize },
     #[serde(rename = "size_field")]
-    Size { loc: SourceRange, field_id: String, width: usize },
+    Size { field_id: String, width: usize },
     #[serde(rename = "count_field")]
-    Count { loc: SourceRange, field_id: String, width: usize },
+    Count { field_id: String, width: usize },
     #[serde(rename = "elementsize_field")]
-    ElementSize { loc: SourceRange, field_id: String, width: usize },
+    ElementSize { field_id: String, width: usize },
     #[serde(rename = "body_field")]
-    Body { loc: SourceRange },
+    Body,
     #[serde(rename = "payload_field")]
-    Payload { loc: SourceRange, size_modifier: Option<String> },
+    Payload { size_modifier: Option<String> },
     #[serde(rename = "fixed_field")]
     Fixed {
-        loc: SourceRange,
         width: Option<usize>,
         value: Option<usize>,
         enum_id: Option<String>,
         tag_id: Option<String>,
     },
     #[serde(rename = "reserved_field")]
-    Reserved { loc: SourceRange, width: usize },
+    Reserved { width: usize },
     #[serde(rename = "array_field")]
     Array {
-        loc: SourceRange,
         id: String,
         width: Option<usize>,
         type_id: Option<String>,
@@ -105,11 +108,20 @@ pub enum Field {
         size: Option<usize>,
     },
     #[serde(rename = "scalar_field")]
-    Scalar { loc: SourceRange, id: String, width: usize },
+    Scalar { id: String, width: usize },
     #[serde(rename = "typedef_field")]
-    Typedef { loc: SourceRange, id: String, type_id: String },
+    Typedef { id: String, type_id: String },
     #[serde(rename = "group_field")]
-    Group { loc: SourceRange, group_id: String, constraints: Vec<Constraint> },
+    Group { group_id: String, constraints: Vec<Constraint> },
+}
+
+#[derive(Debug, Serialize)]
+pub struct Field<A: Annotation> {
+    pub loc: SourceRange,
+    #[serde(skip_serializing)]
+    pub annot: A::FieldAnnotation,
+    #[serde(flatten)]
+    pub desc: FieldDesc,
 }
 
 #[derive(Debug, Serialize)]
@@ -121,42 +133,49 @@ pub struct TestCase {
 
 #[derive(Debug, Serialize)]
 #[serde(tag = "kind")]
-pub enum Decl {
+pub enum DeclDesc<A: Annotation> {
     #[serde(rename = "checksum_declaration")]
-    Checksum { id: String, loc: SourceRange, function: String, width: usize },
+    Checksum { id: String, function: String, width: usize },
     #[serde(rename = "custom_field_declaration")]
-    CustomField { id: String, loc: SourceRange, width: Option<usize>, function: String },
+    CustomField { id: String, width: Option<usize>, function: String },
     #[serde(rename = "enum_declaration")]
-    Enum { id: String, loc: SourceRange, tags: Vec<Tag>, width: usize },
+    Enum { id: String, tags: Vec<Tag>, width: usize },
     #[serde(rename = "packet_declaration")]
     Packet {
         id: String,
-        loc: SourceRange,
         constraints: Vec<Constraint>,
-        fields: Vec<Field>,
+        fields: Vec<Field<A>>,
         parent_id: Option<String>,
     },
     #[serde(rename = "struct_declaration")]
     Struct {
         id: String,
-        loc: SourceRange,
         constraints: Vec<Constraint>,
-        fields: Vec<Field>,
+        fields: Vec<Field<A>>,
         parent_id: Option<String>,
     },
     #[serde(rename = "group_declaration")]
-    Group { id: String, loc: SourceRange, fields: Vec<Field> },
+    Group { id: String, fields: Vec<Field<A>> },
     #[serde(rename = "test_declaration")]
-    Test { loc: SourceRange, type_id: String, test_cases: Vec<TestCase> },
+    Test { type_id: String, test_cases: Vec<TestCase> },
 }
 
 #[derive(Debug, Serialize)]
-pub struct File {
+pub struct Decl<A: Annotation> {
+    pub loc: SourceRange,
+    #[serde(skip_serializing)]
+    pub annot: A::DeclAnnotation,
+    #[serde(flatten)]
+    pub desc: DeclDesc<A>,
+}
+
+#[derive(Debug, Serialize)]
+pub struct File<A: Annotation> {
     pub version: String,
     pub file: FileId,
     pub comments: Vec<Comment>,
     pub endianness: Endianness,
-    pub declarations: Vec<Decl>,
+    pub declarations: Vec<Decl<A>>,
 }
 
 impl SourceLocation {
@@ -213,8 +232,8 @@ impl ops::Add<SourceRange> for SourceRange {
     }
 }
 
-impl File {
-    pub fn new(file: FileId) -> File {
+impl<A: Annotation> File<A> {
+    pub fn new(file: FileId) -> File<A> {
         File {
             version: "1,0".to_owned(),
             comments: vec![],
@@ -230,92 +249,68 @@ impl File {
     }
 }
 
-impl Decl {
-    pub fn loc(&self) -> &SourceRange {
-        match self {
-            Decl::Checksum { loc, .. }
-            | Decl::CustomField { loc, .. }
-            | Decl::Enum { loc, .. }
-            | Decl::Packet { loc, .. }
-            | Decl::Struct { loc, .. }
-            | Decl::Group { loc, .. }
-            | Decl::Test { loc, .. } => loc,
-        }
-    }
-
+impl<A: Annotation> Decl<A> {
     pub fn id(&self) -> Option<&str> {
-        match self {
-            Decl::Test { .. } => None,
-            Decl::Checksum { id, .. }
-            | Decl::CustomField { id, .. }
-            | Decl::Enum { id, .. }
-            | Decl::Packet { id, .. }
-            | Decl::Struct { id, .. }
-            | Decl::Group { id, .. } => Some(id),
+        match &self.desc {
+            DeclDesc::Test { .. } => None,
+            DeclDesc::Checksum { id, .. }
+            | DeclDesc::CustomField { id, .. }
+            | DeclDesc::Enum { id, .. }
+            | DeclDesc::Packet { id, .. }
+            | DeclDesc::Struct { id, .. }
+            | DeclDesc::Group { id, .. } => Some(id),
         }
     }
-}
 
-impl Field {
-    pub fn loc(&self) -> &SourceRange {
-        match self {
-            Field::Checksum { loc, .. }
-            | Field::Padding { loc, .. }
-            | Field::Size { loc, .. }
-            | Field::ElementSize { loc, .. }
-            | Field::Count { loc, .. }
-            | Field::Body { loc, .. }
-            | Field::Payload { loc, .. }
-            | Field::Fixed { loc, .. }
-            | Field::Reserved { loc, .. }
-            | Field::Array { loc, .. }
-            | Field::Scalar { loc, .. }
-            | Field::Typedef { loc, .. }
-            | Field::Group { loc, .. } => loc,
-        }
+    pub fn new(loc: SourceRange, desc: DeclDesc<A>) -> Decl<A> {
+        Decl { loc, annot: Default::default(), desc }
     }
+}
 
+impl<A: Annotation> Field<A> {
     pub fn id(&self) -> Option<&str> {
-        match self {
-            Field::Checksum { .. }
-            | Field::Padding { .. }
-            | Field::Size { .. }
-            | Field::ElementSize { .. }
-            | Field::Count { .. }
-            | Field::Body { .. }
-            | Field::Payload { .. }
-            | Field::Fixed { .. }
-            | Field::Reserved { .. }
-            | Field::Group { .. } => None,
-            Field::Array { id, .. } | Field::Scalar { id, .. } | Field::Typedef { id, .. } => {
-                Some(id)
-            }
+        match &self.desc {
+            FieldDesc::Checksum { .. }
+            | FieldDesc::Padding { .. }
+            | FieldDesc::Size { .. }
+            | FieldDesc::Count { .. }
+            | FieldDesc::ElementSize { .. }
+            | FieldDesc::Body
+            | FieldDesc::Payload { .. }
+            | FieldDesc::Fixed { .. }
+            | FieldDesc::Reserved { .. }
+            | FieldDesc::Group { .. } => None,
+            FieldDesc::Array { id, .. }
+            | FieldDesc::Scalar { id, .. }
+            | FieldDesc::Typedef { id, .. } => Some(id),
         }
     }
 
     pub fn is_bitfield(&self, scope: &lint::Scope<'_>) -> bool {
-        match self {
-            Field::Size { .. }
-            | Field::Count { .. }
-            | Field::Fixed { .. }
-            | Field::Reserved { .. }
-            | Field::Scalar { .. } => true,
-            Field::Typedef { type_id, .. } => {
+        match &self.desc {
+            FieldDesc::Size { .. }
+            | FieldDesc::Count { .. }
+            | FieldDesc::ElementSize { .. }
+            | FieldDesc::Fixed { .. }
+            | FieldDesc::Reserved { .. }
+            | FieldDesc::Scalar { .. } => true,
+            FieldDesc::Typedef { type_id, .. } => {
                 let field = scope.typedef.get(type_id.as_str());
-                matches!(field, Some(Decl::Enum { .. }))
+                matches!(field, Some(Decl { desc: DeclDesc::Enum { .. }, .. }))
             }
             _ => false,
         }
     }
 
     pub fn width(&self, scope: &lint::Scope<'_>) -> Option<usize> {
-        match self {
-            Field::Scalar { width, .. }
-            | Field::Size { width, .. }
-            | Field::Count { width, .. }
-            | Field::Reserved { width, .. } => Some(*width),
-            Field::Typedef { type_id, .. } => match scope.typedef.get(type_id.as_str()) {
-                Some(Decl::Enum { width, .. }) => Some(*width),
+        match &self.desc {
+            FieldDesc::Scalar { width, .. }
+            | FieldDesc::Size { width, .. }
+            | FieldDesc::Count { width, .. }
+            | FieldDesc::ElementSize { width, .. }
+            | FieldDesc::Reserved { width, .. } => Some(*width),
+            FieldDesc::Typedef { type_id, .. } => match scope.typedef.get(type_id.as_str()) {
+                Some(Decl { desc: DeclDesc::Enum { width, .. }, .. }) => Some(*width),
                 _ => None,
             },
             // TODO(mgeisler): padding, arrays, etc.
diff --git a/tools/pdl/src/backends/intermediate.rs b/tools/pdl/src/backends/intermediate.rs
index 67b506cf406..1ef6acab6da 100644
--- a/tools/pdl/src/backends/intermediate.rs
+++ b/tools/pdl/src/backends/intermediate.rs
@@ -1,6 +1,7 @@
 use std::collections::{hash_map::Entry, HashMap};
 
 use crate::ast;
+use crate::parser;
 
 pub struct Schema<'a> {
     pub packets_and_structs: HashMap<&'a str, PacketOrStruct<'a>>,
@@ -82,7 +83,7 @@ pub enum ComputedOffset<'a> {
     Alias(ComputedOffsetId<'a>),
 }
 
-pub fn generate(file: &ast::File) -> Result<Schema, String> {
+pub fn generate(file: &parser::ast::File) -> Result<Schema, String> {
     let mut schema = Schema { packets_and_structs: HashMap::new(), enums: HashMap::new() };
     match file.endianness.value {
         ast::EndiannessValue::LittleEndian => {}
@@ -96,13 +97,13 @@ pub fn generate(file: &ast::File) -> Result<Schema, String> {
     Ok(schema)
 }
 
-fn process_decl<'a>(schema: &mut Schema<'a>, decl: &'a ast::Decl) {
-    match decl {
-        ast::Decl::Enum { id, tags, width, .. } => process_enum(schema, id, tags, *width),
-        ast::Decl::Packet { id, fields, .. } | ast::Decl::Struct { id, fields, .. } => {
+fn process_decl<'a>(schema: &mut Schema<'a>, decl: &'a parser::ast::Decl) {
+    match &decl.desc {
+        ast::DeclDesc::Enum { id, tags, width, .. } => process_enum(schema, id, tags, *width),
+        ast::DeclDesc::Packet { id, fields, .. } | ast::DeclDesc::Struct { id, fields, .. } => {
             process_packet_or_struct(schema, id, fields)
         }
-        ast::Decl::Group { .. } => todo!(),
+        ast::DeclDesc::Group { .. } => todo!(),
         _ => unimplemented!("type {decl:?} not supported"),
     }
 }
@@ -119,11 +120,18 @@ fn process_enum<'a>(schema: &mut Schema<'a>, id: &'a str, tags: &'a [ast::Tag],
     );
 }
 
-fn process_packet_or_struct<'a>(schema: &mut Schema<'a>, id: &'a str, fields: &'a [ast::Field]) {
+fn process_packet_or_struct<'a>(
+    schema: &mut Schema<'a>,
+    id: &'a str,
+    fields: &'a [parser::ast::Field],
+) {
     schema.packets_and_structs.insert(id, compute_getters(schema, fields));
 }
 
-fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketOrStruct<'a> {
+fn compute_getters<'a>(
+    schema: &Schema<'a>,
+    fields: &'a [parser::ast::Field],
+) -> PacketOrStruct<'a> {
     let mut prev_pos_id = None;
     let mut curr_pos_id = ComputedOffsetId::HeaderStart;
     let mut computed_values = HashMap::new();
@@ -142,16 +150,16 @@ fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketO
         // populate this only if we are an array with a knowable size
         let mut next_prev_pos_id = None;
 
-        let next_pos = match field {
-            ast::Field::Reserved { width, .. } => {
+        let next_pos = match &field.desc {
+            ast::FieldDesc::Reserved { width } => {
                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
             }
-            ast::Field::Scalar { id, width, .. } => {
+            ast::FieldDesc::Scalar { id, width } => {
                 computed_offsets
                     .insert(ComputedOffsetId::FieldOffset(id), ComputedOffset::Alias(curr_pos_id));
                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
             }
-            ast::Field::Fixed { width, enum_id, .. } => {
+            ast::FieldDesc::Fixed { width, enum_id, .. } => {
                 let offset = match (width, enum_id) {
                     (Some(width), _) => *width,
                     (_, Some(enum_id)) => schema.enums[enum_id.as_str()].width,
@@ -159,32 +167,32 @@ fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketO
                 };
                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, offset as i64)
             }
-            ast::Field::Size { field_id, width, .. } => {
+            ast::FieldDesc::Size { field_id, width } => {
                 computed_values.insert(
                     ComputedValueId::FieldSize(field_id),
                     ComputedValue::ValueAt { offset: curr_pos_id, width: *width },
                 );
                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
             }
-            ast::Field::Count { field_id, width, .. } => {
+            ast::FieldDesc::Count { field_id, width } => {
                 computed_values.insert(
                     ComputedValueId::FieldCount(field_id.as_str()),
                     ComputedValue::ValueAt { offset: curr_pos_id, width: *width },
                 );
                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
             }
-            ast::Field::ElementSize { field_id, width, .. } => {
+            ast::FieldDesc::ElementSize { field_id, width } => {
                 computed_values.insert(
                     ComputedValueId::FieldElementSize(field_id),
                     ComputedValue::ValueAt { offset: curr_pos_id, width: *width },
                 );
                 ComputedOffset::ConstantPlusOffsetInBits(curr_pos_id, *width as i64)
             }
-            ast::Field::Group { .. } => {
+            ast::FieldDesc::Group { .. } => {
                 unimplemented!("this should be removed by the linter...")
             }
-            ast::Field::Checksum { .. } => unimplemented!("checksum not supported"),
-            ast::Field::Body { .. } => {
+            ast::FieldDesc::Checksum { .. } => unimplemented!("checksum not supported"),
+            ast::FieldDesc::Body => {
                 computed_offsets.insert(
                     ComputedOffsetId::FieldOffset("_body_"),
                     ComputedOffset::Alias(curr_pos_id),
@@ -202,7 +210,7 @@ fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketO
                 computed_offsets.insert(ComputedOffsetId::FieldEndOffset("_body_"), end_offset);
                 end_offset
             }
-            ast::Field::Payload { size_modifier, .. } => {
+            ast::FieldDesc::Payload { size_modifier } => {
                 if size_modifier.is_some() {
                     unimplemented!("size modifiers not supported")
                 }
@@ -223,13 +231,12 @@ fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketO
                 computed_offsets.insert(ComputedOffsetId::FieldEndOffset("_payload_"), end_offset);
                 end_offset
             }
-            ast::Field::Array {
+            ast::FieldDesc::Array {
                 id,
                 width,
                 type_id,
                 size_modifier,
                 size: statically_known_count,
-                ..
             } => {
                 if size_modifier.is_some() {
                     unimplemented!("size modifiers not supported")
@@ -395,14 +402,14 @@ fn compute_getters<'a>(schema: &Schema<'a>, fields: &'a [ast::Field]) -> PacketO
                     ComputedOffset::Alias(ComputedOffsetId::TrailerStart)
                 }
             }
-            ast::Field::Padding { size, .. } => {
+            ast::FieldDesc::Padding { size } => {
                 if let Some(prev_pos_id) = prev_pos_id {
                     ComputedOffset::ConstantPlusOffsetInBits(prev_pos_id, *size as i64)
                 } else {
                     panic!("padding must follow array field with known total size")
                 }
             }
-            ast::Field::Typedef { id, type_id, .. } => {
+            ast::FieldDesc::Typedef { id, type_id } => {
                 computed_offsets
                     .insert(ComputedOffsetId::FieldOffset(id), ComputedOffset::Alias(curr_pos_id));
 
diff --git a/tools/pdl/src/backends/json.rs b/tools/pdl/src/backends/json.rs
index f113cbc5608..edf38d355df 100644
--- a/tools/pdl/src/backends/json.rs
+++ b/tools/pdl/src/backends/json.rs
@@ -1,9 +1,9 @@
 //! Rust compiler backend.
 
-use crate::ast;
+use crate::parser;
 
 /// Turn the AST into a JSON representation.
-pub fn generate(file: &ast::File) -> Result<String, String> {
+pub fn generate(file: &parser::ast::File) -> Result<String, String> {
     serde_json::to_string_pretty(&file)
         .map_err(|err| format!("could not JSON serialize grammar: {err}"))
 }
diff --git a/tools/pdl/src/backends/rust.rs b/tools/pdl/src/backends/rust.rs
index 9ed3746fcdd..97408c4b3c2 100644
--- a/tools/pdl/src/backends/rust.rs
+++ b/tools/pdl/src/backends/rust.rs
@@ -12,6 +12,8 @@ use crate::{ast, lint};
 use quote::{format_ident, quote};
 use std::path::Path;
 
+use crate::parser::ast as parser_ast;
+
 mod declarations;
 mod parser;
 mod preamble;
@@ -49,7 +51,7 @@ fn generate_packet_decl(
     // Packet:
     id: &str,
     _constraints: &[ast::Constraint],
-    fields: &[ast::Field],
+    fields: &[parser_ast::Field],
     _parent_id: Option<&str>,
 ) -> proc_macro2::TokenStream {
     // TODO(mgeisler): use the convert_case crate to convert between
@@ -237,10 +239,14 @@ fn generate_enum_decl(id: &str, tags: &[ast::Tag]) -> proc_macro2::TokenStream {
     }
 }
 
-fn generate_decl(scope: &lint::Scope<'_>, file: &ast::File, decl: &ast::Decl) -> String {
-    match decl {
-        ast::Decl::Packet { id, constraints, fields, parent_id, .. }
-        | ast::Decl::Struct { id, constraints, fields, parent_id, .. } => generate_packet_decl(
+fn generate_decl(
+    scope: &lint::Scope<'_>,
+    file: &parser_ast::File,
+    decl: &parser_ast::Decl,
+) -> String {
+    match &decl.desc {
+        ast::DeclDesc::Packet { id, constraints, fields, parent_id, .. }
+        | ast::DeclDesc::Struct { id, constraints, fields, parent_id, .. } => generate_packet_decl(
             scope,
             file.endianness.value,
             id,
@@ -249,7 +255,7 @@ fn generate_decl(scope: &lint::Scope<'_>, file: &ast::File, decl: &ast::Decl) ->
             parent_id.as_deref(),
         )
         .to_string(),
-        ast::Decl::Enum { id, tags, .. } => generate_enum_decl(id, tags).to_string(),
+        ast::DeclDesc::Enum { id, tags, .. } => generate_enum_decl(id, tags).to_string(),
         _ => todo!("unsupported Decl::{:?}", decl),
     }
 }
@@ -258,7 +264,7 @@ fn generate_decl(scope: &lint::Scope<'_>, file: &ast::File, decl: &ast::Decl) ->
 ///
 /// The code is not formatted, pipe it through `rustfmt` to get
 /// readable source code.
-pub fn generate(sources: &ast::SourceDatabase, file: &ast::File) -> String {
+pub fn generate(sources: &ast::SourceDatabase, file: &parser_ast::File) -> String {
     let mut code = String::new();
 
     let source = sources.get(file.file).expect("could not read source");
diff --git a/tools/pdl/src/backends/rust/declarations.rs b/tools/pdl/src/backends/rust/declarations.rs
index 8120c5174a9..d332d3034a0 100644
--- a/tools/pdl/src/backends/rust/declarations.rs
+++ b/tools/pdl/src/backends/rust/declarations.rs
@@ -1,5 +1,6 @@
 use crate::ast;
 use crate::backends::rust::types;
+use crate::parser::ast as parser_ast;
 use quote::{format_ident, quote};
 
 pub struct FieldDeclarations {
@@ -11,23 +12,23 @@ impl FieldDeclarations {
         FieldDeclarations { code: Vec::new() }
     }
 
-    pub fn add(&mut self, field: &ast::Field) {
-        self.code.push(match field {
-            ast::Field::Scalar { id, width, .. } => {
+    pub fn add(&mut self, field: &parser_ast::Field) {
+        self.code.push(match &field.desc {
+            ast::FieldDesc::Scalar { id, width } => {
                 let id = format_ident!("{id}");
                 let field_type = types::Integer::new(*width);
                 quote! {
                     #id: #field_type,
                 }
             }
-            ast::Field::Typedef { id, type_id, .. } => {
+            ast::FieldDesc::Typedef { id, type_id } => {
                 let id = format_ident!("{id}");
                 let field_type = format_ident!("{type_id}");
                 quote! {
                     #id: #field_type,
                 }
             }
-            ast::Field::Reserved { .. } => {
+            ast::FieldDesc::Reserved { .. } => {
                 // Nothing to do here.
                 quote! {}
             }
diff --git a/tools/pdl/src/backends/rust/parser.rs b/tools/pdl/src/backends/rust/parser.rs
index f9091c02435..877b49f777c 100644
--- a/tools/pdl/src/backends/rust/parser.rs
+++ b/tools/pdl/src/backends/rust/parser.rs
@@ -1,11 +1,12 @@
 use crate::backends::rust::{mask_bits, types};
+use crate::parser::ast as parser_ast;
 use crate::{ast, lint};
 use quote::{format_ident, quote};
 
 /// A single bit-field.
 struct BitField<'a> {
     shift: usize, // The shift to apply to this field.
-    field: &'a ast::Field,
+    field: &'a parser_ast::Field,
 }
 
 pub struct FieldParser<'a> {
@@ -38,7 +39,7 @@ impl<'a> FieldParser<'a> {
         }
     }
 
-    pub fn add(&mut self, field: &'a ast::Field) {
+    pub fn add(&mut self, field: &'a parser_ast::Field) {
         if field.is_bitfield(self.scope) {
             self.add_bit_field(field);
             return;
@@ -47,7 +48,7 @@ impl<'a> FieldParser<'a> {
         todo!("not yet supported: {field:?}")
     }
 
-    fn add_bit_field(&mut self, field: &'a ast::Field) {
+    fn add_bit_field(&mut self, field: &'a parser_ast::Field) {
         self.chunk.push(BitField { shift: self.shift, field });
         self.shift += field.width(self.scope).unwrap();
         if self.shift % 8 != 0 {
@@ -112,14 +113,14 @@ impl<'a> FieldParser<'a> {
                 v = quote! { #v as #value_type };
             }
 
-            self.code.push(match field {
-                ast::Field::Scalar { id, .. } => {
+            self.code.push(match &field.desc {
+                ast::FieldDesc::Scalar { id, .. } => {
                     let id = format_ident!("{id}");
                     quote! {
                         let #id = #v;
                     }
                 }
-                ast::Field::Typedef { id, type_id, .. } => {
+                ast::FieldDesc::Typedef { id, type_id } => {
                     let id = format_ident!("{id}");
                     let type_id = format_ident!("{type_id}");
                     let from_u = format_ident!("from_u{}", value_type.width);
@@ -130,7 +131,7 @@ impl<'a> FieldParser<'a> {
                         let #id = #type_id::#from_u(#v).unwrap();
                     }
                 }
-                ast::Field::Reserved { .. } => {
+                ast::FieldDesc::Reserved { .. } => {
                     // Nothing to do here.
                     quote! {}
                 }
diff --git a/tools/pdl/src/backends/rust/serializer.rs b/tools/pdl/src/backends/rust/serializer.rs
index 5fd5fb13032..a57ba41f4a2 100644
--- a/tools/pdl/src/backends/rust/serializer.rs
+++ b/tools/pdl/src/backends/rust/serializer.rs
@@ -1,4 +1,5 @@
 use crate::backends::rust::{mask_bits, types};
+use crate::parser::ast as parser_ast;
 use crate::{ast, lint};
 use quote::{format_ident, quote};
 
@@ -36,7 +37,7 @@ impl<'a> FieldSerializer<'a> {
         }
     }
 
-    pub fn add(&mut self, field: &ast::Field) {
+    pub fn add(&mut self, field: &parser_ast::Field) {
         if field.is_bitfield(self.scope) {
             self.add_bit_field(field);
             return;
@@ -45,11 +46,11 @@ impl<'a> FieldSerializer<'a> {
         todo!("not yet supported: {field:?}")
     }
 
-    fn add_bit_field(&mut self, field: &ast::Field) {
+    fn add_bit_field(&mut self, field: &parser_ast::Field) {
         let width = field.width(self.scope).unwrap();
 
-        match field {
-            ast::Field::Scalar { id, width, .. } => {
+        match &field.desc {
+            ast::FieldDesc::Scalar { id, width } => {
                 let field_name = format_ident!("{id}");
                 let field_type = types::Integer::new(*width);
                 if field_type.width > *width {
@@ -66,7 +67,7 @@ impl<'a> FieldSerializer<'a> {
                 }
                 self.chunk.push(BitField { value: quote!(self.#field_name), shift: self.shift });
             }
-            ast::Field::Typedef { id, .. } => {
+            ast::FieldDesc::Typedef { id, .. } => {
                 let field_name = format_ident!("{id}");
                 let field_type = types::Integer::new(width);
                 let to_u = format_ident!("to_u{}", field_type.width);
@@ -77,7 +78,7 @@ impl<'a> FieldSerializer<'a> {
                     shift: self.shift,
                 });
             }
-            ast::Field::Reserved { .. } => {
+            ast::FieldDesc::Reserved { .. } => {
                 // Nothing to do here.
             }
             _ => todo!(),
diff --git a/tools/pdl/src/backends/rust/types.rs b/tools/pdl/src/backends/rust/types.rs
index 55d2f28046a..259a1e6387f 100644
--- a/tools/pdl/src/backends/rust/types.rs
+++ b/tools/pdl/src/backends/rust/types.rs
@@ -1,6 +1,7 @@
 //! Utility functions for dealing with Rust integer types.
 
 use crate::ast;
+use crate::parser::ast as parser_ast;
 use quote::{format_ident, quote};
 
 /// A Rust integer type such as `u8`.
@@ -33,13 +34,13 @@ impl quote::ToTokens for Integer {
     }
 }
 
-pub fn rust_type(field: &ast::Field) -> proc_macro2::TokenStream {
-    match field {
-        ast::Field::Scalar { width, .. } => {
+pub fn rust_type(field: &parser_ast::Field) -> proc_macro2::TokenStream {
+    match &field.desc {
+        ast::FieldDesc::Scalar { width, .. } => {
             let field_type = Integer::new(*width);
             quote!(#field_type)
         }
-        ast::Field::Typedef { type_id, .. } => {
+        ast::FieldDesc::Typedef { type_id, .. } => {
             let field_type = format_ident!("{type_id}");
             quote!(#field_type)
         }
diff --git a/tools/pdl/src/backends/rust_no_allocation/mod.rs b/tools/pdl/src/backends/rust_no_allocation/mod.rs
index de5dd800d92..a84f43d3bc7 100644
--- a/tools/pdl/src/backends/rust_no_allocation/mod.rs
+++ b/tools/pdl/src/backends/rust_no_allocation/mod.rs
@@ -23,6 +23,7 @@ use proc_macro2::TokenStream;
 use quote::quote;
 
 use crate::ast;
+use crate::parser;
 
 use self::{
     enums::generate_enum, packet_parser::generate_packet,
@@ -31,7 +32,7 @@ use self::{
 
 use super::intermediate::Schema;
 
-pub fn generate(file: &ast::File, schema: &Schema) -> Result<String, String> {
+pub fn generate(file: &parser::ast::File, schema: &Schema) -> Result<String, String> {
     match file.endianness.value {
         ast::EndiannessValue::LittleEndian => {}
         _ => unimplemented!("Only little_endian endianness supported"),
@@ -43,9 +44,9 @@ pub fn generate(file: &ast::File, schema: &Schema) -> Result<String, String> {
 
     let mut children = HashMap::<&str, Vec<&str>>::new();
     for decl in &file.declarations {
-        match decl {
-            ast::Decl::Packet { id, parent_id: Some(parent_id), .. }
-            | ast::Decl::Struct { id, parent_id: Some(parent_id), .. } => {
+        match &decl.desc {
+            ast::DeclDesc::Packet { id, parent_id: Some(parent_id), .. }
+            | ast::DeclDesc::Struct { id, parent_id: Some(parent_id), .. } => {
                 children.entry(parent_id.as_str()).or_default().push(id.as_str());
             }
             _ => {}
@@ -69,14 +70,14 @@ pub fn generate(file: &ast::File, schema: &Schema) -> Result<String, String> {
 }
 
 fn generate_decl(
-    decl: &ast::Decl,
+    decl: &parser::ast::Decl,
     schema: &Schema,
     children: &HashMap<&str, Vec<&str>>,
 ) -> Result<TokenStream, String> {
-    match decl {
-        ast::Decl::Enum { id, tags, width, .. } => Ok(generate_enum(id, tags, *width)),
-        ast::Decl::Packet { id, fields, parent_id, .. }
-        | ast::Decl::Struct { id, fields, parent_id, .. } => {
+    match &decl.desc {
+        ast::DeclDesc::Enum { id, tags, width, .. } => Ok(generate_enum(id, tags, *width)),
+        ast::DeclDesc::Packet { id, fields, parent_id, .. }
+        | ast::DeclDesc::Struct { id, fields, parent_id, .. } => {
             let parser = generate_packet(
                 id,
                 fields,
diff --git a/tools/pdl/src/backends/rust_no_allocation/packet_parser.rs b/tools/pdl/src/backends/rust_no_allocation/packet_parser.rs
index 8fbf9b12e83..2382131fdee 100644
--- a/tools/pdl/src/backends/rust_no_allocation/packet_parser.rs
+++ b/tools/pdl/src/backends/rust_no_allocation/packet_parser.rs
@@ -4,6 +4,7 @@ use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
 
 use crate::ast;
+use crate::parser;
 
 use crate::backends::intermediate::{
     ComputedOffsetId, ComputedValueId, PacketOrStruct, PacketOrStructLength, Schema,
@@ -14,7 +15,7 @@ use super::utils::get_integer_type;
 
 pub fn generate_packet(
     id: &str,
-    fields: &[ast::Field],
+    fields: &[parser::ast::Field],
     parent_id: Option<&str>,
     schema: &Schema,
     curr_schema: &PacketOrStruct,
@@ -38,22 +39,22 @@ pub fn generate_packet(
         );
 
     let field_getters = fields.iter().map(|field| {
-        match field {
-            ast::Field::Padding { .. }
-            | ast::Field::Reserved { .. }
-            | ast::Field::Fixed { .. }
-            | ast::Field::ElementSize { .. }
-            | ast::Field::Count { .. }
-            | ast::Field::Size { .. } => {
+        match &field.desc {
+            ast::FieldDesc::Padding { .. }
+            | ast::FieldDesc::Reserved { .. }
+            | ast::FieldDesc::Fixed { .. }
+            | ast::FieldDesc::ElementSize { .. }
+            | ast::FieldDesc::Count { .. }
+            | ast::FieldDesc::Size { .. } => {
                 // no-op, no getter generated for this type
                 quote! {}
             }
-            ast::Field::Group { .. } => unreachable!(),
-            ast::Field::Checksum { .. } => {
+            ast::FieldDesc::Group { .. } => unreachable!(),
+            ast::FieldDesc::Checksum { .. } => {
                 unimplemented!("checksums not yet supported with this backend")
             }
-            ast::Field::Payload { .. } | ast::Field::Body { .. } => {
-                let name = if matches!(field, ast::Field::Payload { .. }) { "_payload_"} else { "_body_"};
+            ast::FieldDesc::Payload { .. } | ast::FieldDesc::Body => {
+                let name = if matches!(field.desc, ast::FieldDesc::Payload { .. }) { "_payload_"} else { "_body_"};
                 let payload_start_offset = ComputedOffsetId::FieldOffset(name).call_fn();
                 let payload_end_offset = ComputedOffsetId::FieldEndOffset(name).call_fn();
                 quote! {
@@ -74,7 +75,7 @@ pub fn generate_packet(
                     }
                 }
             }
-            ast::Field::Array { id, width, type_id, .. } => {
+            ast::FieldDesc::Array { id, width, type_id, .. } => {
                 let (elem_type, return_type) = if let Some(width) = width {
                     let ident = get_integer_type(*width);
                     (ident.clone(), quote!{ #ident })
@@ -140,7 +141,7 @@ pub fn generate_packet(
                     }
                 }
             }
-            ast::Field::Scalar { id, width, .. } => {
+            ast::FieldDesc::Scalar { id, width } => {
                 let try_getter_name = format_ident!("try_get_{id}");
                 let getter_name = format_ident!("get_{id}");
                 let offset = ComputedOffsetId::FieldOffset(id).call_fn();
@@ -156,7 +157,7 @@ pub fn generate_packet(
                     }
                 }
             }
-            ast::Field::Typedef { id, type_id, .. } => {
+            ast::FieldDesc::Typedef { id, type_id } => {
                 let try_getter_name = format_ident!("try_get_{id}");
                 let getter_name = format_ident!("get_{id}");
 
@@ -213,25 +214,25 @@ pub fn generate_packet(
         quote! { parent }
     };
 
-    let field_validators = fields.iter().map(|field| match field {
-        ast::Field::Checksum { .. } => unimplemented!(),
-        ast::Field::Group { .. } => unreachable!(),
-        ast::Field::Padding { .. }
-        | ast::Field::Size { .. }
-        | ast::Field::Count { .. }
-        | ast::Field::ElementSize { .. }
-        | ast::Field::Body { .. }
-        | ast::Field::Fixed { .. }
-        | ast::Field::Reserved { .. } => {
+    let field_validators = fields.iter().map(|field| match &field.desc {
+        ast::FieldDesc::Checksum { .. } => unimplemented!(),
+        ast::FieldDesc::Group { .. } => unreachable!(),
+        ast::FieldDesc::Padding { .. }
+        | ast::FieldDesc::Size { .. }
+        | ast::FieldDesc::Count { .. }
+        | ast::FieldDesc::ElementSize { .. }
+        | ast::FieldDesc::Body
+        | ast::FieldDesc::Fixed { .. }
+        | ast::FieldDesc::Reserved { .. } => {
             quote! {}
         }
-        ast::Field::Payload { .. } => {
+        ast::FieldDesc::Payload { .. } => {
             quote! {
                 self.try_get_payload()?;
                 self.try_get_raw_payload()?;
             }
         }
-        ast::Field::Array { id, .. } => {
+        ast::FieldDesc::Array { id, .. } => {
             let iter_ident = format_ident!("try_get_{id}_iter");
             quote! {
                 for elem in self.#iter_ident()? {
@@ -239,7 +240,7 @@ pub fn generate_packet(
                 }
             }
         }
-        ast::Field::Scalar { id, .. } | ast::Field::Typedef { id, .. } => {
+        ast::FieldDesc::Scalar { id, .. } | ast::FieldDesc::Typedef { id, .. } => {
             let getter_ident = format_ident!("try_get_{id}");
             quote! { self.#getter_ident()?; }
         }
diff --git a/tools/pdl/src/backends/rust_no_allocation/packet_serializer.rs b/tools/pdl/src/backends/rust_no_allocation/packet_serializer.rs
index d4d14e695d2..55071cdb22f 100644
--- a/tools/pdl/src/backends/rust_no_allocation/packet_serializer.rs
+++ b/tools/pdl/src/backends/rust_no_allocation/packet_serializer.rs
@@ -9,6 +9,7 @@ use crate::{
         intermediate::{ComputedValue, ComputedValueId, PacketOrStruct, Schema},
         rust_no_allocation::utils::get_integer_type,
     },
+    parser,
 };
 
 fn standardize_child(id: &str) -> &str {
@@ -21,7 +22,7 @@ fn standardize_child(id: &str) -> &str {
 pub fn generate_packet_serializer(
     id: &str,
     parent_id: Option<&str>,
-    fields: &[ast::Field],
+    fields: &[parser::ast::Field],
     schema: &Schema,
     curr_schema: &PacketOrStruct,
     children: &HashMap<&str, Vec<&str>>,
@@ -31,25 +32,25 @@ pub fn generate_packet_serializer(
     let builder_fields = fields
         .iter()
         .filter_map(|field| {
-            match field {
-                ast::Field::Padding { .. }
-                | ast::Field::Reserved { .. }
-                | ast::Field::Fixed { .. }
-                | ast::Field::ElementSize { .. }
-                | ast::Field::Count { .. }
-                | ast::Field::Size { .. } => {
+            match &field.desc {
+                ast::FieldDesc::Padding { .. }
+                | ast::FieldDesc::Reserved { .. }
+                | ast::FieldDesc::Fixed { .. }
+                | ast::FieldDesc::ElementSize { .. }
+                | ast::FieldDesc::Count { .. }
+                | ast::FieldDesc::Size { .. } => {
                     // no-op, no getter generated for this type
                     None
                 }
-                ast::Field::Group { .. } => unreachable!(),
-                ast::Field::Checksum { .. } => {
+                ast::FieldDesc::Group { .. } => unreachable!(),
+                ast::FieldDesc::Checksum { .. } => {
                     unimplemented!("checksums not yet supported with this backend")
                 }
-                ast::Field::Body { .. } | ast::Field::Payload { .. } => {
+                ast::FieldDesc::Body | ast::FieldDesc::Payload { .. } => {
                     let type_ident = format_ident!("{id}Child");
                     Some(("_child_", quote! { #type_ident }))
                 }
-                ast::Field::Array { id, width, type_id, .. } => {
+                ast::FieldDesc::Array { id, width, type_id, .. } => {
                     let element_type = if let Some(width) = width {
                         get_integer_type(*width)
                     } else if let Some(type_id) = type_id {
@@ -63,11 +64,11 @@ pub fn generate_packet_serializer(
                     };
                     Some((id.as_str(), quote! { Box<[#element_type]> }))
                 }
-                ast::Field::Scalar { id, width, .. } => {
+                ast::FieldDesc::Scalar { id, width } => {
                     let id_type = get_integer_type(*width);
                     Some((id.as_str(), quote! { #id_type }))
                 }
-                ast::Field::Typedef { id, type_id, .. } => {
+                ast::FieldDesc::Typedef { id, type_id } => {
                     let type_ident = if schema.enums.contains_key(type_id.as_str()) {
                         format_ident!("{type_id}")
                     } else {
@@ -85,9 +86,9 @@ pub fn generate_packet_serializer(
     let mut has_child = false;
 
     let serializer = fields.iter().map(|field| {
-        match field {
-            ast::Field::Checksum { .. } | ast::Field::Group { .. } => unimplemented!(),
-            ast::Field::Padding { size, .. } => {
+        match &field.desc {
+            ast::FieldDesc::Checksum { .. } | ast::FieldDesc::Group { .. } => unimplemented!(),
+            ast::FieldDesc::Padding { size, .. } => {
                 quote! {
                     if (most_recent_array_size_in_bits > #size * 8) {
                         return Err(SerializeError::NegativePadding);
@@ -95,7 +96,7 @@ pub fn generate_packet_serializer(
                     writer.write_bits((#size * 8 - most_recent_array_size_in_bits) as usize, || Ok(0u64))?;
                 }
             },
-            ast::Field::Size { field_id, width, .. } => {
+            ast::FieldDesc::Size { field_id, width } => {
                 let field_id = standardize_child(field_id);
                 let field_ident = format_ident!("{field_id}");
 
@@ -137,11 +138,11 @@ pub fn generate_packet_serializer(
                     })?;
                 }
             }
-            ast::Field::Count { field_id, width, .. } => {
+            ast::FieldDesc::Count { field_id, width } => {
                 let field_ident = format_ident!("{field_id}");
                 quote! { writer.write_bits(#width, || u64::try_from(self.#field_ident.len()).or(Err(SerializeError::IntegerConversionFailure)))?; }
             }
-            ast::Field::ElementSize { field_id, width, .. } => {
+            ast::FieldDesc::ElementSize { field_id, width } => {
                 // TODO(aryarahul) - add validation for elementsize against all the other elements
                 let field_ident = format_ident!("{field_id}");
                 quote! {
@@ -158,14 +159,14 @@ pub fn generate_packet_serializer(
                     writer.write_bits(#width, || get_element_size() )?;
                 }
             }
-            ast::Field::Reserved { width, .. } => {
+            ast::FieldDesc::Reserved { width, .. } => {
                 quote!{ writer.write_bits(#width, || Ok(0u64))?; }
             }
-            ast::Field::Scalar { width, id, .. } => {
+            ast::FieldDesc::Scalar { width, id } => {
                 let field_ident = format_ident!("{id}");
                 quote! { writer.write_bits(#width, || Ok(self.#field_ident))?; }
             }
-            ast::Field::Fixed { width, enum_id, value, tag_id, .. } => {
+            ast::FieldDesc::Fixed { width, enum_id, value, tag_id } => {
                 let width = if let Some(width) = width {
                     quote! { #width }
                 } else if let Some(enum_id) = enum_id {
@@ -186,11 +187,11 @@ pub fn generate_packet_serializer(
                 };
                 quote!{ writer.write_bits(#width, || Ok(#value))?; }
             }
-            ast::Field::Body { .. } | ast::Field::Payload { .. } => {
+            ast::FieldDesc::Body | ast::FieldDesc::Payload { .. } => {
                 has_child = true;
                 quote! { self._child_.serialize(writer)?; }
             }
-            ast::Field::Array { width, id, .. } => {
+            ast::FieldDesc::Array { width, id, .. } => {
                 let id_ident = format_ident!("{id}");
                 if let Some(width) = width {
                     quote! {
@@ -209,7 +210,7 @@ pub fn generate_packet_serializer(
                      }
                 }
             }
-            ast::Field::Typedef { id, .. } => {
+            ast::FieldDesc::Typedef { id, .. } => {
                 let id_ident = format_ident!("{id}");
                 quote! { self.#id_ident.serialize(writer)?; }
             }
diff --git a/tools/pdl/src/backends/rust_no_allocation/test.rs b/tools/pdl/src/backends/rust_no_allocation/test.rs
index 49851fba337..f6bc989fe9e 100644
--- a/tools/pdl/src/backends/rust_no_allocation/test.rs
+++ b/tools/pdl/src/backends/rust_no_allocation/test.rs
@@ -203,33 +203,34 @@ pub fn generate_test_file() -> Result<String, String> {
     let pdl = include_str!("../../../tests/canonical/le_rust_noalloc_test_file.pdl");
     let ast = parse_inline(&mut ast::SourceDatabase::new(), "test.pdl".to_owned(), pdl.to_owned())
         .expect("could not parse reference PDL");
-    let packet_lookup = ast
-        .declarations
-        .iter()
-        .filter_map(|decl| match decl {
-            ast::Decl::Packet { id, fields, .. } | ast::Decl::Struct { id, fields, .. } => Some((
-                id.as_str(),
-                fields
-                    .iter()
-                    .filter_map(|field| match field {
-                        ast::Field::Body { .. } | ast::Field::Payload { .. } => {
-                            Some(("payload", None))
-                        }
-                        ast::Field::Array { id, type_id, .. } => match type_id {
-                            Some(type_id) => Some((id.as_str(), Some(type_id.as_str()))),
-                            None => Some((id.as_str(), None)),
-                        },
-                        ast::Field::Typedef { id, type_id, .. } => {
-                            Some((id.as_str(), Some(type_id.as_str())))
-                        }
-                        ast::Field::Scalar { id, .. } => Some((id.as_str(), None)),
-                        _ => None,
-                    })
-                    .collect::<HashMap<_, _>>(),
-            )),
-            _ => None,
-        })
-        .collect::<HashMap<_, _>>();
+    let packet_lookup =
+        ast.declarations
+            .iter()
+            .filter_map(|decl| match &decl.desc {
+                ast::DeclDesc::Packet { id, fields, .. }
+                | ast::DeclDesc::Struct { id, fields, .. } => Some((
+                    id.as_str(),
+                    fields
+                        .iter()
+                        .filter_map(|field| match &field.desc {
+                            ast::FieldDesc::Body { .. } | ast::FieldDesc::Payload { .. } => {
+                                Some(("payload", None))
+                            }
+                            ast::FieldDesc::Array { id, type_id, .. } => match type_id {
+                                Some(type_id) => Some((id.as_str(), Some(type_id.as_str()))),
+                                None => Some((id.as_str(), None)),
+                            },
+                            ast::FieldDesc::Typedef { id, type_id, .. } => {
+                                Some((id.as_str(), Some(type_id.as_str())))
+                            }
+                            ast::FieldDesc::Scalar { id, .. } => Some((id.as_str(), None)),
+                            _ => None,
+                        })
+                        .collect::<HashMap<_, _>>(),
+                )),
+                _ => None,
+            })
+            .collect::<HashMap<_, _>>();
 
     for PacketTest { packet, tests } in test_vectors.iter() {
         if !pdl.contains(packet) {
diff --git a/tools/pdl/src/lint.rs b/tools/pdl/src/lint.rs
index f0234ad6503..1b083b9257b 100644
--- a/tools/pdl/src/lint.rs
+++ b/tools/pdl/src/lint.rs
@@ -5,7 +5,41 @@ use codespan_reporting::term::termcolor;
 use std::collections::HashMap;
 use std::ptr;
 
-use crate::ast::*;
+use crate::{ast::*, parser};
+
+pub mod ast {
+    use serde::Serialize;
+
+    // Field and declaration size information.
+    #[derive(Default, Debug)]
+    #[allow(unused)]
+    pub enum Size {
+        // Constant size in bits.
+        Static(usize),
+        // Size indicated at packet parsing by
+        // a size or count field.
+        Dynamic,
+        // The size cannot be determined statically or at runtime.
+        // The packet assumes the largest possible size.
+        #[default]
+        Unknown,
+    }
+
+    #[derive(Debug, Serialize)]
+    pub struct Annotation();
+
+    impl crate::ast::Annotation for Annotation {
+        type FieldAnnotation = Size;
+        type DeclAnnotation = Size;
+    }
+
+    #[allow(unused)]
+    pub type Field = crate::ast::Field<Annotation>;
+    #[allow(unused)]
+    pub type Decl = crate::ast::Decl<Annotation>;
+    #[allow(unused)]
+    pub type File = crate::ast::File<Annotation>;
+}
 
 /// Aggregate linter diagnostics.
 #[derive(Debug)]
@@ -24,57 +58,57 @@ pub trait Lintable {
 #[derive(Debug)]
 pub struct Scope<'d> {
     // Collection of Group, Packet, Enum, Struct, Checksum, and CustomField declarations.
-    pub typedef: HashMap<String, &'d Decl>,
+    pub typedef: HashMap<String, &'d parser::ast::Decl>,
 
     // Collection of Packet, Struct, and Group scope declarations.
-    pub scopes: HashMap<&'d Decl, PacketScope<'d>>,
+    pub scopes: HashMap<&'d parser::ast::Decl, PacketScope<'d>>,
 }
 
 /// Gather information about a Packet, Struct, or Group declaration.
 #[derive(Debug)]
 pub struct PacketScope<'d> {
     // Checksum starts, indexed by the checksum field id.
-    checksums: HashMap<String, &'d Field>,
+    checksums: HashMap<String, &'d parser::ast::Field>,
 
     // Size or count fields, indexed by the field id.
-    sizes: HashMap<String, &'d Field>,
+    sizes: HashMap<String, &'d parser::ast::Field>,
 
     // Payload or body field.
-    payload: Option<&'d Field>,
+    payload: Option<&'d parser::ast::Field>,
 
     // Typedef, scalar, array fields.
-    named: HashMap<String, &'d Field>,
+    named: HashMap<String, &'d parser::ast::Field>,
 
     // Group fields.
-    groups: HashMap<String, &'d Field>,
+    groups: HashMap<String, &'d parser::ast::Field>,
 
     // Flattened field declarations.
     // Contains field declarations from the original Packet, Struct, or Group,
     // where Group fields have been substituted by their body.
     // Constrained Scalar or Typedef Group fields are substituted by a Fixed
     // field.
-    fields: Vec<&'d Field>,
+    fields: Vec<&'d parser::ast::Field>,
 
     // Constraint declarations gathered from Group inlining.
     constraints: HashMap<String, &'d Constraint>,
 
     // Local and inherited field declarations. Only named fields are preserved.
     // Saved here for reference for parent constraint resolving.
-    all_fields: HashMap<String, &'d Field>,
+    all_fields: HashMap<String, &'d parser::ast::Field>,
 
     // Local and inherited constraint declarations.
     // Saved here for constraint conflict checks.
     all_constraints: HashMap<String, &'d Constraint>,
 }
 
-impl std::cmp::Eq for &Decl {}
-impl<'d> std::cmp::PartialEq for &'d Decl {
+impl std::cmp::Eq for &parser::ast::Decl {}
+impl<'d> std::cmp::PartialEq for &'d parser::ast::Decl {
     fn eq(&self, other: &Self) -> bool {
         std::ptr::eq(*self, *other)
     }
 }
 
-impl<'d> std::hash::Hash for &'d Decl {
+impl<'d> std::hash::Hash for &'d parser::ast::Decl {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
         std::ptr::hash(*self, state);
     }
@@ -128,9 +162,9 @@ fn bit_width(val: usize) -> usize {
 
 impl<'d> PacketScope<'d> {
     /// Insert a field declaration into a packet scope.
-    fn insert(&mut self, field: &'d Field, result: &mut LintDiagnostics) {
-        match field {
-            Field::Checksum { loc, field_id, .. } => {
+    fn insert(&mut self, field: &'d parser::ast::Field, result: &mut LintDiagnostics) {
+        match &field.desc {
+            FieldDesc::Checksum { field_id, .. } => {
                 self.checksums.insert(field_id.clone(), field).map(|prev| {
                     result.push(
                         Diagnostic::error()
@@ -139,8 +173,8 @@ impl<'d> PacketScope<'d> {
                                 field_id
                             ))
                             .with_labels(vec![
-                                loc.primary(),
-                                prev.loc()
+                                field.loc.primary(),
+                                prev.loc
                                     .secondary()
                                     .with_message("checksum start is first declared here"),
                             ]),
@@ -148,12 +182,12 @@ impl<'d> PacketScope<'d> {
                 })
             }
 
-            Field::Padding { .. }
-            | Field::Reserved { .. }
-            | Field::Fixed { .. }
-            | Field::ElementSize { .. } => None,
+            FieldDesc::Padding { .. }
+            | FieldDesc::Reserved { .. }
+            | FieldDesc::Fixed { .. }
+            | FieldDesc::ElementSize { .. } => None,
 
-            Field::Size { loc, field_id, .. } | Field::Count { loc, field_id, .. } => {
+            FieldDesc::Size { field_id, .. } | FieldDesc::Count { field_id, .. } => {
                 self.sizes.insert(field_id.clone(), field).map(|prev| {
                     result.push(
                         Diagnostic::error()
@@ -162,23 +196,21 @@ impl<'d> PacketScope<'d> {
                                 field_id
                             ))
                             .with_labels(vec![
-                                loc.primary(),
-                                prev.loc().secondary().with_message("size is first declared here"),
+                                field.loc.primary(),
+                                prev.loc.secondary().with_message("size is first declared here"),
                             ]),
                     )
                 })
             }
 
-            Field::Body { loc, .. } | Field::Payload { loc, .. } => {
+            FieldDesc::Body { .. } | FieldDesc::Payload { .. } => {
                 if let Some(prev) = self.payload.as_ref() {
                     result.push(
                         Diagnostic::error()
                             .with_message("redeclaration of payload or body field")
                             .with_labels(vec![
-                                loc.primary(),
-                                prev.loc()
-                                    .secondary()
-                                    .with_message("payload is first declared here"),
+                                field.loc.primary(),
+                                prev.loc.secondary().with_message("payload is first declared here"),
                             ]),
                     )
                 }
@@ -186,21 +218,21 @@ impl<'d> PacketScope<'d> {
                 None
             }
 
-            Field::Array { loc, id, .. }
-            | Field::Scalar { loc, id, .. }
-            | Field::Typedef { loc, id, .. } => self
+            FieldDesc::Array { id, .. }
+            | FieldDesc::Scalar { id, .. }
+            | FieldDesc::Typedef { id, .. } => self
                 .named
                 .insert(id.clone(), field)
-                .map(|prev| result.err_redeclared(id, "field", loc, prev.loc())),
+                .map(|prev| result.err_redeclared(id, "field", &field.loc, &prev.loc)),
 
-            Field::Group { loc, group_id, .. } => {
+            FieldDesc::Group { group_id, .. } => {
                 self.groups.insert(group_id.clone(), field).map(|prev| {
                     result.push(
                         Diagnostic::error()
                             .with_message(format!("duplicate group `{}` insertion", group_id))
                             .with_labels(vec![
-                                loc.primary(),
-                                prev.loc()
+                                field.loc.primary(),
+                                prev.loc
                                     .secondary()
                                     .with_message(format!("`{}` is first used here", group_id)),
                             ]),
@@ -253,7 +285,7 @@ impl<'d> PacketScope<'d> {
         &mut self,
         scope: &Scope,
         packet_scope: &PacketScope<'d>,
-        group: &'d Field,
+        group: &'d parser::ast::Field,
         constraints: impl Iterator<Item = &'d Constraint>,
         result: &mut LintDiagnostics,
     ) {
@@ -274,8 +306,8 @@ impl<'d> PacketScope<'d> {
                 err_redeclared_by_group(
                     result,
                     format!("inserted group redeclares checksum start for `{}`", id),
-                    group.loc(),
-                    prev.loc(),
+                    &group.loc,
+                    &prev.loc,
                 )
             }
         }
@@ -284,8 +316,8 @@ impl<'d> PacketScope<'d> {
                 err_redeclared_by_group(
                     result,
                     format!("inserted group redeclares size or count for `{}`", id),
-                    group.loc(),
-                    prev.loc(),
+                    &group.loc,
+                    &prev.loc,
                 )
             }
         }
@@ -293,8 +325,8 @@ impl<'d> PacketScope<'d> {
             (Some(prev), Some(next)) => err_redeclared_by_group(
                 result,
                 "inserted group redeclares payload or body field",
-                next.loc(),
-                prev.loc(),
+                &next.loc,
+                &prev.loc,
             ),
             (None, Some(payload)) => self.payload = Some(payload),
             _ => (),
@@ -304,8 +336,8 @@ impl<'d> PacketScope<'d> {
                 err_redeclared_by_group(
                     result,
                     format!("inserted group redeclares field `{}`", id),
-                    group.loc(),
-                    prev.loc(),
+                    &group.loc,
+                    &prev.loc,
                 )
             }
         }
@@ -339,8 +371,11 @@ impl<'d> PacketScope<'d> {
 
     /// Return the field immediately preceding the selected field, or None
     /// if no such field exists.
-    fn get_preceding_field(&self, searched_field: &Field) -> Option<&Field> {
-        let mut preceding_field: Option<&Field> = None;
+    fn get_preceding_field(
+        &self,
+        searched_field: &parser::ast::Field,
+    ) -> Option<&parser::ast::Field> {
+        let mut preceding_field: Option<&parser::ast::Field> = None;
         for field in self.fields.iter() {
             if ptr::eq(*field, searched_field) {
                 break;
@@ -360,8 +395,8 @@ impl<'d> PacketScope<'d> {
                         Diagnostic::warning()
                             .with_message(format!("declaration of `{}` shadows parent field", id))
                             .with_labels(vec![
-                                f.loc().primary(),
-                                prev.loc()
+                                f.loc.primary(),
+                                prev.loc
                                     .secondary()
                                     .with_message(format!("`{}` is first declared here", id)),
                             ]),
@@ -381,7 +416,11 @@ fn lint_constraint(
 ) {
     // Validate constraint value types.
     match (packet_scope.all_fields.get(&constraint.id), &constraint.value, &constraint.tag_id) {
-        (Some(Field::Scalar { loc: field_loc, width, .. }), Some(value), _) => {
+        (
+            Some(Field { loc: field_loc, desc: FieldDesc::Scalar { width, .. }, .. }),
+            Some(value),
+            _,
+        ) => {
             if bit_width(*value) > *width {
                 result.push(
                     Diagnostic::error().with_message("invalid integer literal").with_labels(vec![
@@ -395,16 +434,15 @@ fn lint_constraint(
             }
         }
 
-        (Some(Field::Scalar { loc: field_loc, .. }), None, _) => {
-            result.push(Diagnostic::error().with_message("invalid literal type").with_labels(vec![
+        (Some(Field { loc: field_loc, desc: FieldDesc::Scalar { .. }, .. }), None, _) => result
+            .push(Diagnostic::error().with_message("invalid literal type").with_labels(vec![
                 constraint.loc.primary().with_message("expected integer literal"),
                 field_loc.secondary().with_message("the value is used here"),
-            ]))
-        }
+            ])),
 
-        (Some(Field::Typedef { type_id, loc: field_loc, .. }), _, _) => {
+        (Some(Field { loc: field_loc, desc: FieldDesc::Typedef { type_id, .. }, .. }), _, _) => {
             match (scope.typedef.get(type_id), &constraint.tag_id) {
-                (Some(Decl::Enum { tags, .. }), Some(tag_id)) => {
+                (Some(Decl { desc: DeclDesc::Enum { tags, .. }, .. }), Some(tag_id)) => {
                     if !tags.iter().any(|t| &t.id == tag_id) {
                         result.push(
                             Diagnostic::error()
@@ -451,7 +489,7 @@ fn lint_constraint(
 }
 
 impl<'d> Scope<'d> {
-    pub fn new(file: &File) -> Result<Scope<'_>, LintDiagnostics> {
+    pub fn new(file: &parser::ast::File) -> Result<Scope<'_>, LintDiagnostics> {
         let mut lint_diagnostics = LintDiagnostics::new();
         let scope = file.scope(&mut lint_diagnostics);
 
@@ -476,20 +514,20 @@ impl<'d> Scope<'d> {
     //      - undeclared Packet or Struct parents,
     //      - recursive Group insertion,
     //      - recursive Packet or Struct inheritance.
-    fn finalize(&mut self, result: &mut LintDiagnostics) -> Vec<&'d Decl> {
+    fn finalize(&mut self, result: &mut LintDiagnostics) -> Vec<&'d parser::ast::Decl> {
         // Auxiliary function implementing BFS on Packet tree.
         enum Mark {
             Temporary,
             Permanent,
         }
         struct Context<'d> {
-            list: Vec<&'d Decl>,
-            visited: HashMap<&'d Decl, Mark>,
-            scopes: HashMap<&'d Decl, PacketScope<'d>>,
+            list: Vec<&'d parser::ast::Decl>,
+            visited: HashMap<&'d parser::ast::Decl, Mark>,
+            scopes: HashMap<&'d parser::ast::Decl, PacketScope<'d>>,
         }
 
         fn bfs<'s, 'd>(
-            decl: &'d Decl,
+            decl: &'d parser::ast::Decl,
             context: &'s mut Context<'d>,
             scope: &Scope<'d>,
             result: &mut LintDiagnostics,
@@ -504,18 +542,17 @@ impl<'d> Scope<'d> {
                                 decl.kind(),
                                 decl.id().unwrap()
                             ))
-                            .with_labels(vec![decl.loc().primary()]),
+                            .with_labels(vec![decl.loc.primary()]),
                     );
                     return None;
                 }
                 _ => (),
             }
 
-            let (parent_id, fields) = match decl {
-                Decl::Packet { parent_id, fields, .. } | Decl::Struct { parent_id, fields, .. } => {
-                    (parent_id.as_ref(), fields)
-                }
-                Decl::Group { fields, .. } => (None, fields),
+            let (parent_id, fields) = match &decl.desc {
+                DeclDesc::Packet { parent_id, fields, .. }
+                | DeclDesc::Struct { parent_id, fields, .. } => (parent_id.as_ref(), fields),
+                DeclDesc::Group { fields, .. } => (None, fields),
                 _ => return None,
             };
 
@@ -524,8 +561,8 @@ impl<'d> Scope<'d> {
 
             // Iterate over Struct and Group fields.
             for f in fields {
-                match f {
-                    Field::Group { group_id, constraints, .. } => {
+                match &f.desc {
+                    FieldDesc::Group { group_id, constraints, .. } => {
                         match scope.typedef.get(group_id) {
                             None => result.push(
                                 Diagnostic::error()
@@ -533,9 +570,9 @@ impl<'d> Scope<'d> {
                                         "undeclared group identifier `{}`",
                                         group_id
                                     ))
-                                    .with_labels(vec![f.loc().primary()]),
+                                    .with_labels(vec![f.loc.primary()]),
                             ),
-                            Some(group_decl @ Decl::Group { .. }) => {
+                            Some(group_decl @ Decl { desc: DeclDesc::Group { .. }, .. }) => {
                                 // Recurse to flatten the inserted group.
                                 if let Some(rscope) = bfs(group_decl, context, scope, result) {
                                     // Inline the group fields and constraints into
@@ -549,12 +586,12 @@ impl<'d> Scope<'d> {
                                         "invalid group field identifier `{}`",
                                         group_id
                                     ))
-                                    .with_labels(vec![f.loc().primary()])
+                                    .with_labels(vec![f.loc.primary()])
                                     .with_notes(vec!["hint: expected group identifier".to_owned()]),
                             ),
                         }
                     }
-                    Field::Typedef { type_id, .. } => {
+                    FieldDesc::Typedef { type_id, .. } => {
                         lscope.fields.push(f);
                         match scope.typedef.get(type_id) {
                             None => result.push(
@@ -563,9 +600,9 @@ impl<'d> Scope<'d> {
                                         "undeclared typedef identifier `{}`",
                                         type_id
                                     ))
-                                    .with_labels(vec![f.loc().primary()]),
+                                    .with_labels(vec![f.loc.primary()]),
                             ),
-                            Some(struct_decl @ Decl::Struct { .. }) => {
+                            Some(struct_decl @ Decl { desc: DeclDesc::Struct { .. }, .. }) => {
                                 bfs(struct_decl, context, scope, result);
                             }
                             Some(_) => (),
@@ -577,24 +614,29 @@ impl<'d> Scope<'d> {
 
             // Iterate over parent declaration.
             let parent = parent_id.and_then(|id| scope.typedef.get(id));
-            match (decl, parent) {
-                (Decl::Packet { parent_id: Some(_), .. }, None)
-                | (Decl::Struct { parent_id: Some(_), .. }, None) => result.push(
+            match (&decl.desc, parent) {
+                (DeclDesc::Packet { parent_id: Some(_), .. }, None)
+                | (DeclDesc::Struct { parent_id: Some(_), .. }, None) => result.push(
                     Diagnostic::error()
                         .with_message(format!(
                             "undeclared parent identifier `{}`",
                             parent_id.unwrap()
                         ))
-                        .with_labels(vec![decl.loc().primary()])
-                        .with_notes(vec![format!("hint: expected {} parent", decl.kind())]),
-                ),
-                (Decl::Packet { .. }, Some(Decl::Struct { .. }))
-                | (Decl::Struct { .. }, Some(Decl::Packet { .. })) => result.push(
-                    Diagnostic::error()
-                        .with_message(format!("invalid parent identifier `{}`", parent_id.unwrap()))
-                        .with_labels(vec![decl.loc().primary()])
+                        .with_labels(vec![decl.loc.primary()])
                         .with_notes(vec![format!("hint: expected {} parent", decl.kind())]),
                 ),
+                (DeclDesc::Packet { .. }, Some(Decl { desc: DeclDesc::Struct { .. }, .. }))
+                | (DeclDesc::Struct { .. }, Some(Decl { desc: DeclDesc::Packet { .. }, .. })) => {
+                    result.push(
+                        Diagnostic::error()
+                            .with_message(format!(
+                                "invalid parent identifier `{}`",
+                                parent_id.unwrap()
+                            ))
+                            .with_labels(vec![decl.loc.primary()])
+                            .with_notes(vec![format!("hint: expected {} parent", decl.kind())]),
+                    )
+                }
                 (_, Some(parent_decl)) => {
                     if let Some(rscope) = bfs(parent_decl, context, scope, result) {
                         // Import the parent fields and constraints into the current scope.
@@ -623,22 +665,22 @@ impl<'d> Scope<'d> {
     }
 }
 
-impl Field {
+impl parser::ast::Field {
     fn kind(&self) -> &str {
-        match self {
-            Field::Checksum { .. } => "payload",
-            Field::Padding { .. } => "padding",
-            Field::Size { .. } => "size",
-            Field::Count { .. } => "count",
-            Field::ElementSize { .. } => "elementsize",
-            Field::Body { .. } => "body",
-            Field::Payload { .. } => "payload",
-            Field::Fixed { .. } => "fixed",
-            Field::Reserved { .. } => "reserved",
-            Field::Group { .. } => "group",
-            Field::Array { .. } => "array",
-            Field::Scalar { .. } => "scalar",
-            Field::Typedef { .. } => "typedef",
+        match &self.desc {
+            FieldDesc::Checksum { .. } => "payload",
+            FieldDesc::Padding { .. } => "padding",
+            FieldDesc::Size { .. } => "size",
+            FieldDesc::Count { .. } => "count",
+            FieldDesc::ElementSize { .. } => "elementsize",
+            FieldDesc::Body { .. } => "body",
+            FieldDesc::Payload { .. } => "payload",
+            FieldDesc::Fixed { .. } => "fixed",
+            FieldDesc::Reserved { .. } => "reserved",
+            FieldDesc::Group { .. } => "group",
+            FieldDesc::Array { .. } => "array",
+            FieldDesc::Scalar { .. } => "scalar",
+            FieldDesc::Typedef { .. } => "typedef",
         }
     }
 }
@@ -676,20 +718,20 @@ fn lint_enum(tags: &[Tag], width: usize, result: &mut LintDiagnostics) {
 fn lint_checksum(
     scope: &Scope,
     packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     field_id: &str,
     result: &mut LintDiagnostics,
 ) {
     // Checksum field must be declared before
     // the checksum start. The field must be a typedef with
     // a valid checksum type.
-    let checksum_loc = decl.loc();
+    let checksum_loc = &decl.loc;
 
     match packet_scope.named.get(field_id) {
-        Some(Field::Typedef { loc: field_loc, type_id, .. }) => {
+        Some(Field { loc: field_loc, desc: FieldDesc::Typedef { type_id, .. }, .. }) => {
             // Check declaration type of checksum field.
             match scope.typedef.get(type_id) {
-                Some(Decl::Checksum { .. }) => (),
+                Some(Decl { desc: DeclDesc::Checksum { .. }, .. }) => (),
                 Some(decl) => result.push(
                     Diagnostic::error()
                         .with_message(format!("checksum start uses invalid field `{}`", field_id))
@@ -713,7 +755,7 @@ fn lint_checksum(
                 .with_message(format!("checksum start uses invalid field `{}`", field_id))
                 .with_labels(vec![
                     checksum_loc.primary(),
-                    field.loc().secondary().with_message(format!(
+                    field.loc.secondary().with_message(format!(
                         "`{}` is declared as {} field, expected typedef",
                         field_id,
                         field.kind()
@@ -728,7 +770,7 @@ fn lint_checksum(
 fn lint_size(
     _scope: &Scope,
     packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     field_id: &str,
     _width: usize,
     result: &mut LintDiagnostics,
@@ -738,16 +780,16 @@ fn lint_size(
     // The field must reference a valid body, payload or array
     // field.
 
-    let size_loc = decl.loc();
+    let size_loc = &decl.loc;
 
     if field_id == "_payload_" {
         return match packet_scope.payload.as_ref() {
-            Some(Field::Body { .. }) => result.push(
+            Some(Field { desc: FieldDesc::Body { .. }, .. }) => result.push(
                 Diagnostic::error()
                     .with_message("size field uses undeclared payload field, did you mean _body_ ?")
                     .with_labels(vec![size_loc.primary()]),
             ),
-            Some(Field::Payload { .. }) => (),
+            Some(Field { desc: FieldDesc::Payload { .. }, .. }) => (),
             Some(_) => unreachable!(),
             None => result.push(
                 Diagnostic::error()
@@ -758,12 +800,12 @@ fn lint_size(
     }
     if field_id == "_body_" {
         return match packet_scope.payload.as_ref() {
-            Some(Field::Payload { .. }) => result.push(
+            Some(Field { desc: FieldDesc::Payload { .. }, .. }) => result.push(
                 Diagnostic::error()
                     .with_message("size field uses undeclared body field, did you mean _payload_ ?")
                     .with_labels(vec![size_loc.primary()]),
             ),
-            Some(Field::Body { .. }) => (),
+            Some(Field { desc: FieldDesc::Body { .. }, .. }) => (),
             Some(_) => unreachable!(),
             None => result.push(
                 Diagnostic::error()
@@ -774,20 +816,23 @@ fn lint_size(
     }
 
     match packet_scope.named.get(field_id) {
-        Some(Field::Array { size: Some(_), loc: array_loc, .. }) => result.push(
-            Diagnostic::warning()
-                .with_message(format!("size field uses array `{}` with static size", field_id))
-                .with_labels(vec![
-                    size_loc.primary(),
-                    array_loc.secondary().with_message(format!("`{}` is declared here", field_id)),
-                ]),
-        ),
-        Some(Field::Array { .. }) => (),
+        Some(Field { loc: array_loc, desc: FieldDesc::Array { size: Some(_), .. }, .. }) => result
+            .push(
+                Diagnostic::warning()
+                    .with_message(format!("size field uses array `{}` with static size", field_id))
+                    .with_labels(vec![
+                        size_loc.primary(),
+                        array_loc
+                            .secondary()
+                            .with_message(format!("`{}` is declared here", field_id)),
+                    ]),
+            ),
+        Some(Field { desc: FieldDesc::Array { .. }, .. }) => (),
         Some(field) => result.push(
             Diagnostic::error()
                 .with_message(format!("invalid `{}` field type", field_id))
                 .with_labels(vec![
-                    field.loc().primary().with_message(format!(
+                    field.loc.primary().with_message(format!(
                         "`{}` is declared as {}",
                         field_id,
                         field.kind()
@@ -806,7 +851,7 @@ fn lint_size(
 fn lint_count(
     _scope: &Scope,
     packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     field_id: &str,
     _width: usize,
     result: &mut LintDiagnostics,
@@ -815,23 +860,26 @@ fn lint_count(
     // The field must reference a valid array field.
     // Warning if the array already has a known size.
 
-    let count_loc = decl.loc();
+    let count_loc = &decl.loc;
     match packet_scope.named.get(field_id) {
-        Some(Field::Array { size: Some(_), loc: array_loc, .. }) => result.push(
-            Diagnostic::warning()
-                .with_message(format!("count field uses array `{}` with static size", field_id))
-                .with_labels(vec![
-                    count_loc.primary(),
-                    array_loc.secondary().with_message(format!("`{}` is declared here", field_id)),
-                ]),
-        ),
+        Some(Field { loc: array_loc, desc: FieldDesc::Array { size: Some(_), .. }, .. }) => result
+            .push(
+                Diagnostic::warning()
+                    .with_message(format!("count field uses array `{}` with static size", field_id))
+                    .with_labels(vec![
+                        count_loc.primary(),
+                        array_loc
+                            .secondary()
+                            .with_message(format!("`{}` is declared here", field_id)),
+                    ]),
+            ),
 
-        Some(Field::Array { .. }) => (),
+        Some(Field { desc: FieldDesc::Array { .. }, .. }) => (),
         Some(field) => result.push(
             Diagnostic::error()
                 .with_message(format!("invalid `{}` field type", field_id))
                 .with_labels(vec![
-                    field.loc().primary().with_message(format!(
+                    field.loc.primary().with_message(format!(
                         "`{}` is declared as {}",
                         field_id,
                         field.kind()
@@ -851,7 +899,7 @@ fn lint_count(
 fn lint_fixed(
     scope: &Scope,
     _packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     width: &Option<usize>,
     value: &Option<usize>,
     enum_id: &Option<String>,
@@ -860,7 +908,7 @@ fn lint_fixed(
 ) {
     // By parsing constraint, we already have that either
     // (width and value) or (enum_id and tag_id) are Some.
-    let fixed_loc = decl.loc();
+    let fixed_loc = decl.loc;
 
     if width.is_some() {
         // The value of a fixed field should have .
@@ -876,7 +924,7 @@ fn lint_fixed(
         // The fixed field should reference a valid enum id and tag id
         // association.
         match scope.typedef.get(enum_id.as_ref().unwrap()) {
-            Some(Decl::Enum { tags, .. }) => {
+            Some(Decl { desc: DeclDesc::Enum { tags, .. }, .. }) => {
                 match tags.iter().find(|t| &t.id == tag_id.as_ref().unwrap()) {
                     Some(_) => (),
                     None => result.push(
@@ -915,7 +963,7 @@ fn lint_fixed(
 fn lint_array(
     scope: &Scope,
     _packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     _width: &Option<usize>,
     type_id: &Option<String>,
     _size_modifier: &Option<String>,
@@ -927,13 +975,13 @@ fn lint_array(
     // type_id must reference a valid enum or packet type.
     // TODO(hchataing) unbounded arrays should have a matching size
     // or count field
-    let array_loc = decl.loc();
+    let array_loc = decl.loc;
 
     if type_id.is_some() {
         match scope.typedef.get(type_id.as_ref().unwrap()) {
-            Some(Decl::Enum { .. })
-            | Some(Decl::Struct { .. })
-            | Some(Decl::CustomField { .. }) => (),
+            Some(Decl { desc: DeclDesc::Enum { .. }, .. })
+            | Some(Decl { desc: DeclDesc::Struct { .. }, .. })
+            | Some(Decl { desc: DeclDesc::CustomField { .. }, .. }) => (),
             Some(decl) => result.push(
                 Diagnostic::error()
                     .with_message(format!(
@@ -961,12 +1009,12 @@ fn lint_array(
 fn lint_padding(
     _scope: &Scope,
     packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     _size: usize,
     result: &mut LintDiagnostics,
 ) {
     // The padding field must follow an array field.
-    let padding_loc = decl.loc();
+    let padding_loc = decl.loc;
 
     match packet_scope.get_preceding_field(decl) {
         None => result.push(
@@ -977,14 +1025,14 @@ fn lint_padding(
                     "hint: padding fields must be placed after an array field".to_owned()
                 ]),
         ),
-        Some(Field::Array { .. }) => (),
+        Some(Field { desc: FieldDesc::Array { .. }, .. }) => (),
         Some(preceding_field) => result.push(
             Diagnostic::error()
                 .with_message(format!(
                     "padding field cannot be placed after {} field",
                     preceding_field.kind()
                 ))
-                .with_labels(vec![padding_loc.primary(), preceding_field.loc().secondary()])
+                .with_labels(vec![padding_loc.primary(), preceding_field.loc.secondary()])
                 .with_notes(vec![
                     "hint: padding fields must be placed after an array field".to_owned()
                 ]),
@@ -996,20 +1044,20 @@ fn lint_padding(
 fn lint_typedef(
     scope: &Scope,
     _packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     type_id: &str,
     result: &mut LintDiagnostics,
 ) {
     // The typedef field must reference a valid struct, enum,
     // custom_field, or checksum type.
     // TODO(hchataing) checksum fields should have a matching checksum start
-    let typedef_loc = decl.loc();
+    let typedef_loc = decl.loc;
 
     match scope.typedef.get(type_id) {
-        Some(Decl::Enum { .. })
-        | Some(Decl::Struct { .. })
-        | Some(Decl::CustomField { .. })
-        | Some(Decl::Checksum { .. }) => (),
+        Some(Decl { desc: DeclDesc::Enum { .. }, .. })
+        | Some(Decl { desc: DeclDesc::Struct { .. }, .. })
+        | Some(Decl { desc: DeclDesc::CustomField { .. }, .. })
+        | Some(Decl { desc: DeclDesc::Checksum { .. }, .. }) => (),
 
         Some(decl) => result.push(
             Diagnostic::error()
@@ -1034,42 +1082,43 @@ fn lint_typedef(
 fn lint_field(
     scope: &Scope,
     packet_scope: &PacketScope,
-    decl: &Field,
+    decl: &parser::ast::Field,
     result: &mut LintDiagnostics,
 ) {
-    match decl {
-        Field::Checksum { field_id, .. } => {
+    match &decl.desc {
+        FieldDesc::Checksum { field_id, .. } => {
             lint_checksum(scope, packet_scope, decl, field_id, result)
         }
-        Field::Size { field_id, width, .. } => {
+        FieldDesc::Size { field_id, width, .. } => {
             lint_size(scope, packet_scope, decl, field_id, *width, result)
         }
-        Field::Count { field_id, width, .. } => {
+        FieldDesc::Count { field_id, width, .. } => {
             lint_count(scope, packet_scope, decl, field_id, *width, result)
         }
-        Field::ElementSize { .. } => { /* TODO(aryarahul) */ }
-        Field::Fixed { width, value, enum_id, tag_id, .. } => {
+        FieldDesc::ElementSize { .. } => { /* TODO(aryarahul) */ }
+        FieldDesc::Fixed { width, value, enum_id, tag_id, .. } => {
             lint_fixed(scope, packet_scope, decl, width, value, enum_id, tag_id, result)
         }
-        Field::Array { width, type_id, size_modifier, size, .. } => {
+        FieldDesc::Array { width, type_id, size_modifier, size, .. } => {
             lint_array(scope, packet_scope, decl, width, type_id, size_modifier, size, result)
         }
-        Field::Typedef { type_id, .. } => lint_typedef(scope, packet_scope, decl, type_id, result),
-        Field::Padding { size, .. } => lint_padding(scope, packet_scope, decl, *size, result),
-        Field::Reserved { .. }
-        | Field::Scalar { .. }
-        | Field::Body { .. }
-        | Field::Payload { .. } => (),
-        Field::Group { .. } => unreachable!(),
+        FieldDesc::Typedef { type_id, .. } => {
+            lint_typedef(scope, packet_scope, decl, type_id, result)
+        }
+        FieldDesc::Padding { size, .. } => lint_padding(scope, packet_scope, decl, *size, result),
+        FieldDesc::Reserved { .. }
+        | FieldDesc::Scalar { .. }
+        | FieldDesc::Body { .. }
+        | FieldDesc::Payload { .. } => (),
+        FieldDesc::Group { .. } => unreachable!(),
     }
 }
 
 // Helper for linting a packet declaration.
 fn lint_packet(
     scope: &Scope,
-    decl: &Decl,
+    decl: &parser::ast::Decl,
     id: &str,
-    loc: &SourceRange,
     constraints: &[Constraint],
     parent_id: &Option<String>,
     result: &mut LintDiagnostics,
@@ -1088,7 +1137,7 @@ fn lint_packet(
                     "packet `{}` has field constraints, but no parent declaration",
                     id
                 ))
-                .with_labels(vec![loc.primary()])
+                .with_labels(vec![decl.loc.primary()])
                 .with_notes(vec!["hint: expected parent declaration".to_owned()]),
         )
     }
@@ -1105,9 +1154,8 @@ fn lint_packet(
 // Helper for linting a struct declaration.
 fn lint_struct(
     scope: &Scope,
-    decl: &Decl,
+    decl: &parser::ast::Decl,
     id: &str,
-    loc: &SourceRange,
     constraints: &[Constraint],
     parent_id: &Option<String>,
     result: &mut LintDiagnostics,
@@ -1126,7 +1174,7 @@ fn lint_struct(
                     "struct `{}` has field constraints, but no parent declaration",
                     id
                 ))
-                .with_labels(vec![loc.primary()])
+                .with_labels(vec![decl.loc.primary()])
                 .with_notes(vec!["hint: expected parent declaration".to_owned()]),
         )
     }
@@ -1140,10 +1188,10 @@ fn lint_struct(
     }
 }
 
-impl Decl {
+impl parser::ast::Decl {
     fn constraints(&self) -> impl Iterator<Item = &Constraint> {
-        match self {
-            Decl::Packet { constraints, .. } | Decl::Struct { constraints, .. } => {
+        match &self.desc {
+            DeclDesc::Packet { constraints, .. } | DeclDesc::Struct { constraints, .. } => {
                 Some(constraints.iter())
             }
             _ => None,
@@ -1153,10 +1201,10 @@ impl Decl {
     }
 
     fn scope<'d>(&'d self, result: &mut LintDiagnostics) -> Option<PacketScope<'d>> {
-        match self {
-            Decl::Packet { fields, .. }
-            | Decl::Struct { fields, .. }
-            | Decl::Group { fields, .. } => {
+        match &self.desc {
+            DeclDesc::Packet { fields, .. }
+            | DeclDesc::Struct { fields, .. }
+            | DeclDesc::Group { fields, .. } => {
                 let mut scope = PacketScope {
                     checksums: HashMap::new(),
                     sizes: HashMap::new(),
@@ -1179,36 +1227,36 @@ impl Decl {
     }
 
     fn lint<'d>(&'d self, scope: &Scope<'d>, result: &mut LintDiagnostics) {
-        match self {
-            Decl::Checksum { .. } | Decl::CustomField { .. } => (),
-            Decl::Enum { tags, width, .. } => lint_enum(tags, *width, result),
-            Decl::Packet { id, loc, constraints, parent_id, .. } => {
-                lint_packet(scope, self, id, loc, constraints, parent_id, result)
+        match &self.desc {
+            DeclDesc::Checksum { .. } | DeclDesc::CustomField { .. } => (),
+            DeclDesc::Enum { tags, width, .. } => lint_enum(tags, *width, result),
+            DeclDesc::Packet { id, constraints, parent_id, .. } => {
+                lint_packet(scope, self, id, constraints, parent_id, result)
             }
-            Decl::Struct { id, loc, constraints, parent_id, .. } => {
-                lint_struct(scope, self, id, loc, constraints, parent_id, result)
+            DeclDesc::Struct { id, constraints, parent_id, .. } => {
+                lint_struct(scope, self, id, constraints, parent_id, result)
             }
             // Groups are finalizeed before linting, to make sure
             // potential errors are raised only once.
-            Decl::Group { .. } => (),
-            Decl::Test { .. } => (),
+            DeclDesc::Group { .. } => (),
+            DeclDesc::Test { .. } => (),
         }
     }
 
     fn kind(&self) -> &str {
-        match self {
-            Decl::Checksum { .. } => "checksum",
-            Decl::CustomField { .. } => "custom field",
-            Decl::Enum { .. } => "enum",
-            Decl::Packet { .. } => "packet",
-            Decl::Struct { .. } => "struct",
-            Decl::Group { .. } => "group",
-            Decl::Test { .. } => "test",
+        match &self.desc {
+            DeclDesc::Checksum { .. } => "checksum",
+            DeclDesc::CustomField { .. } => "custom field",
+            DeclDesc::Enum { .. } => "enum",
+            DeclDesc::Packet { .. } => "packet",
+            DeclDesc::Struct { .. } => "struct",
+            DeclDesc::Group { .. } => "group",
+            DeclDesc::Test { .. } => "test",
         }
     }
 }
 
-impl File {
+impl parser::ast::File {
     fn scope<'d>(&'d self, result: &mut LintDiagnostics) -> Scope<'d> {
         let mut scope = Scope { typedef: HashMap::new(), scopes: HashMap::new() };
 
@@ -1219,7 +1267,7 @@ impl File {
         for decl in &self.declarations {
             if let Some(id) = decl.id() {
                 if let Some(prev) = scope.typedef.insert(id.to_string(), decl) {
-                    result.err_redeclared(id, decl.kind(), decl.loc(), prev.loc())
+                    result.err_redeclared(id, decl.kind(), &decl.loc, &prev.loc)
                 }
             }
             if let Some(lscope) = decl.scope(result) {
@@ -1232,7 +1280,7 @@ impl File {
     }
 }
 
-impl Lintable for File {
+impl Lintable for parser::ast::File {
     fn lint(&self) -> LintDiagnostics {
         Scope::new(self).err().unwrap_or_else(LintDiagnostics::new)
     }
diff --git a/tools/pdl/src/parser.rs b/tools/pdl/src/parser.rs
index a08c2bceb27..13e32605e58 100644
--- a/tools/pdl/src/parser.rs
+++ b/tools/pdl/src/parser.rs
@@ -1,10 +1,25 @@
-use super::ast;
 use codespan_reporting::diagnostic::Diagnostic;
 use codespan_reporting::files;
 use pest::iterators::{Pair, Pairs};
 use pest::{Parser, Token};
 use std::iter::{Filter, Peekable};
 
+pub mod ast {
+    use serde::Serialize;
+
+    #[derive(Debug, Serialize)]
+    pub struct Annotation;
+
+    impl crate::ast::Annotation for Annotation {
+        type FieldAnnotation = ();
+        type DeclAnnotation = ();
+    }
+
+    pub type Field = crate::ast::Field<Annotation>;
+    pub type Decl = crate::ast::Decl<Annotation>;
+    pub type File = crate::ast::File<Annotation>;
+}
+
 // Generate the PDL parser.
 // TODO: use #[grammar = "pdl.pest"]
 // currently not possible because CARGO_MANIFEST_DIR is not set
@@ -139,11 +154,11 @@ pub struct PDLParser;
 
 type Node<'i> = Pair<'i, Rule>;
 type NodeIterator<'i> = Peekable<Filter<Pairs<'i, Rule>, fn(&Node<'i>) -> bool>>;
-type Context<'a> = (ast::FileId, &'a Vec<usize>);
+type Context<'a> = (crate::ast::FileId, &'a Vec<usize>);
 
 trait Helpers<'i> {
     fn children(self) -> NodeIterator<'i>;
-    fn as_loc(&self, context: &Context) -> ast::SourceRange;
+    fn as_loc(&self, context: &Context) -> crate::ast::SourceRange;
     fn as_string(&self) -> String;
     fn as_usize(&self) -> Result<usize, String>;
 }
@@ -153,12 +168,12 @@ impl<'i> Helpers<'i> for Node<'i> {
         self.into_inner().filter((|n| n.as_rule() != Rule::COMMENT) as fn(&Self) -> bool).peekable()
     }
 
-    fn as_loc(&self, context: &Context) -> ast::SourceRange {
+    fn as_loc(&self, context: &Context) -> crate::ast::SourceRange {
         let span = self.as_span();
-        ast::SourceRange {
+        crate::ast::SourceRange {
             file: context.0,
-            start: ast::SourceLocation::new(span.start_pos().pos(), context.1),
-            end: ast::SourceLocation::new(span.end_pos().pos(), context.1),
+            start: crate::ast::SourceLocation::new(span.start_pos().pos(), context.1),
+            end: crate::ast::SourceLocation::new(span.end_pos().pos(), context.1),
         }
     }
 
@@ -245,22 +260,22 @@ fn parse_size_modifier_opt(iter: &mut NodeIterator<'_>) -> Option<String> {
     maybe(iter, Rule::size_modifier).map(|n| n.as_string())
 }
 
-fn parse_endianness(node: Node<'_>, context: &Context) -> Result<ast::Endianness, String> {
+fn parse_endianness(node: Node<'_>, context: &Context) -> Result<crate::ast::Endianness, String> {
     if node.as_rule() != Rule::endianness_declaration {
         err_unexpected_rule(Rule::endianness_declaration, node.as_rule())
     } else {
-        Ok(ast::Endianness {
+        Ok(crate::ast::Endianness {
             loc: node.as_loc(context),
             value: match node.as_str() {
-                "little_endian_packets" => ast::EndiannessValue::LittleEndian,
-                "big_endian_packets" => ast::EndiannessValue::BigEndian,
+                "little_endian_packets" => crate::ast::EndiannessValue::LittleEndian,
+                "big_endian_packets" => crate::ast::EndiannessValue::BigEndian,
                 _ => unreachable!(),
             },
         })
     }
 }
 
-fn parse_constraint(node: Node<'_>, context: &Context) -> Result<ast::Constraint, String> {
+fn parse_constraint(node: Node<'_>, context: &Context) -> Result<crate::ast::Constraint, String> {
     if node.as_rule() != Rule::constraint {
         err_unexpected_rule(Rule::constraint, node.as_rule())
     } else {
@@ -268,19 +283,19 @@ fn parse_constraint(node: Node<'_>, context: &Context) -> Result<ast::Constraint
         let mut children = node.children();
         let id = parse_identifier(&mut children)?;
         let (tag_id, value) = parse_identifier_or_integer(&mut children)?;
-        Ok(ast::Constraint { id, loc, value, tag_id })
+        Ok(crate::ast::Constraint { id, loc, value, tag_id })
     }
 }
 
 fn parse_constraint_list_opt(
     iter: &mut NodeIterator<'_>,
     context: &Context,
-) -> Result<Vec<ast::Constraint>, String> {
+) -> Result<Vec<crate::ast::Constraint>, String> {
     maybe(iter, Rule::constraint_list)
         .map_or(Ok(vec![]), |n| n.children().map(|n| parse_constraint(n, context)).collect())
 }
 
-fn parse_enum_tag(node: Node<'_>, context: &Context) -> Result<ast::Tag, String> {
+fn parse_enum_tag(node: Node<'_>, context: &Context) -> Result<crate::ast::Tag, String> {
     if node.as_rule() != Rule::enum_tag {
         err_unexpected_rule(Rule::enum_tag, node.as_rule())
     } else {
@@ -288,14 +303,14 @@ fn parse_enum_tag(node: Node<'_>, context: &Context) -> Result<ast::Tag, String>
         let mut children = node.children();
         let id = parse_identifier(&mut children)?;
         let value = parse_integer(&mut children)?;
-        Ok(ast::Tag { id, loc, value })
+        Ok(crate::ast::Tag { id, loc, value })
     }
 }
 
 fn parse_enum_tag_list(
     iter: &mut NodeIterator<'_>,
     context: &Context,
-) -> Result<Vec<ast::Tag>, String> {
+) -> Result<Vec<crate::ast::Tag>, String> {
     expect(iter, Rule::enum_tag_list)
         .and_then(|n| n.children().map(|n| parse_enum_tag(n, context)).collect())
 }
@@ -304,84 +319,88 @@ fn parse_field(node: Node<'_>, context: &Context) -> Result<ast::Field, String>
     let loc = node.as_loc(context);
     let rule = node.as_rule();
     let mut children = node.children();
-    Ok(match rule {
-        Rule::checksum_field => {
-            let field_id = parse_identifier(&mut children)?;
-            ast::Field::Checksum { loc, field_id }
-        }
-        Rule::padding_field => {
-            let size = parse_integer(&mut children)?;
-            ast::Field::Padding { loc, size }
-        }
-        Rule::size_field => {
-            let field_id = match children.next() {
-                Some(n) if n.as_rule() == Rule::identifier => n.as_string(),
-                Some(n) if n.as_rule() == Rule::payload_identifier => n.as_string(),
-                Some(n) if n.as_rule() == Rule::body_identifier => n.as_string(),
-                Some(n) => err_unexpected_rule(Rule::identifier, n.as_rule())?,
-                None => err_missing_rule(Rule::identifier)?,
-            };
-            let width = parse_integer(&mut children)?;
-            ast::Field::Size { loc, field_id, width }
-        }
-        Rule::count_field => {
-            let field_id = parse_identifier(&mut children)?;
-            let width = parse_integer(&mut children)?;
-            ast::Field::Count { loc, field_id, width }
-        }
-        Rule::elementsize_field => {
-            let field_id = parse_identifier(&mut children)?;
-            let width = parse_integer(&mut children)?;
-            ast::Field::ElementSize { loc, field_id, width }
-        }
-        Rule::body_field => ast::Field::Body { loc },
-        Rule::payload_field => {
-            let size_modifier = parse_size_modifier_opt(&mut children);
-            ast::Field::Payload { loc, size_modifier }
-        }
-        Rule::fixed_field => {
-            let (tag_id, value) = parse_identifier_or_integer(&mut children)?;
-            let (enum_id, width) = parse_identifier_or_integer(&mut children)?;
-            ast::Field::Fixed { loc, enum_id, tag_id, width, value }
-        }
-        Rule::reserved_field => {
-            let width = parse_integer(&mut children)?;
-            ast::Field::Reserved { loc, width }
-        }
-        Rule::array_field => {
-            let id = parse_identifier(&mut children)?;
-            let (type_id, width) = parse_identifier_or_integer(&mut children)?;
-            let (size, size_modifier) = match children.next() {
-                Some(n) if n.as_rule() == Rule::integer => (Some(n.as_usize()?), None),
-                Some(n) if n.as_rule() == Rule::size_modifier => (None, Some(n.as_string())),
-                Some(n) => {
-                    return Err(format!(
-                        "expected rule {:?} or {:?}, got {:?}",
-                        Rule::integer,
-                        Rule::size_modifier,
-                        n.as_rule()
-                    ))
-                }
-                None => (None, None),
-            };
-            ast::Field::Array { loc, id, type_id, width, size, size_modifier }
-        }
-        Rule::scalar_field => {
-            let id = parse_identifier(&mut children)?;
-            let width = parse_integer(&mut children)?;
-            ast::Field::Scalar { loc, id, width }
-        }
-        Rule::typedef_field => {
-            let id = parse_identifier(&mut children)?;
-            let type_id = parse_identifier(&mut children)?;
-            ast::Field::Typedef { loc, id, type_id }
-        }
-        Rule::group_field => {
-            let group_id = parse_identifier(&mut children)?;
-            let constraints = parse_constraint_list_opt(&mut children, context)?;
-            ast::Field::Group { loc, group_id, constraints }
-        }
-        _ => return Err(format!("expected rule *_field, got {:?}", rule)),
+    Ok(crate::ast::Field {
+        loc,
+        annot: Default::default(),
+        desc: match rule {
+            Rule::checksum_field => {
+                let field_id = parse_identifier(&mut children)?;
+                crate::ast::FieldDesc::Checksum { field_id }
+            }
+            Rule::padding_field => {
+                let size = parse_integer(&mut children)?;
+                crate::ast::FieldDesc::Padding { size }
+            }
+            Rule::size_field => {
+                let field_id = match children.next() {
+                    Some(n) if n.as_rule() == Rule::identifier => n.as_string(),
+                    Some(n) if n.as_rule() == Rule::payload_identifier => n.as_string(),
+                    Some(n) if n.as_rule() == Rule::body_identifier => n.as_string(),
+                    Some(n) => err_unexpected_rule(Rule::identifier, n.as_rule())?,
+                    None => err_missing_rule(Rule::identifier)?,
+                };
+                let width = parse_integer(&mut children)?;
+                crate::ast::FieldDesc::Size { field_id, width }
+            }
+            Rule::count_field => {
+                let field_id = parse_identifier(&mut children)?;
+                let width = parse_integer(&mut children)?;
+                crate::ast::FieldDesc::Count { field_id, width }
+            }
+            Rule::elementsize_field => {
+                let field_id = parse_identifier(&mut children)?;
+                let width = parse_integer(&mut children)?;
+                crate::ast::FieldDesc::ElementSize { field_id, width }
+            }
+            Rule::body_field => crate::ast::FieldDesc::Body,
+            Rule::payload_field => {
+                let size_modifier = parse_size_modifier_opt(&mut children);
+                crate::ast::FieldDesc::Payload { size_modifier }
+            }
+            Rule::fixed_field => {
+                let (tag_id, value) = parse_identifier_or_integer(&mut children)?;
+                let (enum_id, width) = parse_identifier_or_integer(&mut children)?;
+                crate::ast::FieldDesc::Fixed { enum_id, tag_id, width, value }
+            }
+            Rule::reserved_field => {
+                let width = parse_integer(&mut children)?;
+                crate::ast::FieldDesc::Reserved { width }
+            }
+            Rule::array_field => {
+                let id = parse_identifier(&mut children)?;
+                let (type_id, width) = parse_identifier_or_integer(&mut children)?;
+                let (size, size_modifier) = match children.next() {
+                    Some(n) if n.as_rule() == Rule::integer => (Some(n.as_usize()?), None),
+                    Some(n) if n.as_rule() == Rule::size_modifier => (None, Some(n.as_string())),
+                    Some(n) => {
+                        return Err(format!(
+                            "expected rule {:?} or {:?}, got {:?}",
+                            Rule::integer,
+                            Rule::size_modifier,
+                            n.as_rule()
+                        ))
+                    }
+                    None => (None, None),
+                };
+                crate::ast::FieldDesc::Array { id, type_id, width, size, size_modifier }
+            }
+            Rule::scalar_field => {
+                let id = parse_identifier(&mut children)?;
+                let width = parse_integer(&mut children)?;
+                crate::ast::FieldDesc::Scalar { id, width }
+            }
+            Rule::typedef_field => {
+                let id = parse_identifier(&mut children)?;
+                let type_id = parse_identifier(&mut children)?;
+                crate::ast::FieldDesc::Typedef { id, type_id }
+            }
+            Rule::group_field => {
+                let group_id = parse_identifier(&mut children)?;
+                let constraints = parse_constraint_list_opt(&mut children, context)?;
+                crate::ast::FieldDesc::Group { group_id, constraints }
+            }
+            _ => return Err(format!("expected rule *_field, got {:?}", rule)),
+        },
     })
 }
 
@@ -403,7 +422,7 @@ fn parse_field_list_opt<'i>(
 
 fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String> {
     let mut toplevel_comments = vec![];
-    let mut file = ast::File::new(context.0);
+    let mut file = crate::ast::File::new(context.0);
 
     let mut comment_start = vec![];
     for token in root.clone().tokens() {
@@ -411,11 +430,11 @@ fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String
             Token::Start { rule: Rule::COMMENT, pos } => comment_start.push(pos),
             Token::End { rule: Rule::COMMENT, pos } => {
                 let start_pos = comment_start.pop().unwrap();
-                file.comments.push(ast::Comment {
-                    loc: ast::SourceRange {
+                file.comments.push(crate::ast::Comment {
+                    loc: crate::ast::SourceRange {
                         file: context.0,
-                        start: ast::SourceLocation::new(start_pos.pos(), context.1),
-                        end: ast::SourceLocation::new(pos.pos(), context.1),
+                        start: crate::ast::SourceLocation::new(start_pos.pos(), context.1),
+                        end: crate::ast::SourceLocation::new(pos.pos(), context.1),
                     },
                     text: start_pos.span(&pos).as_str().to_owned(),
                 })
@@ -434,21 +453,30 @@ fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String
                 let id = parse_identifier(&mut children)?;
                 let width = parse_integer(&mut children)?;
                 let function = parse_string(&mut children)?;
-                file.declarations.push(ast::Decl::Checksum { id, loc, function, width })
+                file.declarations.push(crate::ast::Decl::new(
+                    loc,
+                    crate::ast::DeclDesc::Checksum { id, function, width },
+                ))
             }
             Rule::custom_field_declaration => {
                 let mut children = node.children();
                 let id = parse_identifier(&mut children)?;
                 let width = parse_integer_opt(&mut children)?;
                 let function = parse_string(&mut children)?;
-                file.declarations.push(ast::Decl::CustomField { id, loc, function, width })
+                file.declarations.push(crate::ast::Decl::new(
+                    loc,
+                    crate::ast::DeclDesc::CustomField { id, function, width },
+                ))
             }
             Rule::enum_declaration => {
                 let mut children = node.children();
                 let id = parse_identifier(&mut children)?;
                 let width = parse_integer(&mut children)?;
                 let tags = parse_enum_tag_list(&mut children, context)?;
-                file.declarations.push(ast::Decl::Enum { id, loc, width, tags })
+                file.declarations.push(crate::ast::Decl::new(
+                    loc,
+                    crate::ast::DeclDesc::Enum { id, width, tags },
+                ))
             }
             Rule::packet_declaration => {
                 let mut children = node.children();
@@ -456,13 +484,10 @@ fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String
                 let parent_id = parse_identifier_opt(&mut children)?;
                 let constraints = parse_constraint_list_opt(&mut children, context)?;
                 let fields = parse_field_list_opt(&mut children, context)?;
-                file.declarations.push(ast::Decl::Packet {
-                    id,
+                file.declarations.push(crate::ast::Decl::new(
                     loc,
-                    parent_id,
-                    constraints,
-                    fields,
-                })
+                    crate::ast::DeclDesc::Packet { id, parent_id, constraints, fields },
+                ))
             }
             Rule::struct_declaration => {
                 let mut children = node.children();
@@ -470,19 +495,17 @@ fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String
                 let parent_id = parse_identifier_opt(&mut children)?;
                 let constraints = parse_constraint_list_opt(&mut children, context)?;
                 let fields = parse_field_list_opt(&mut children, context)?;
-                file.declarations.push(ast::Decl::Struct {
-                    id,
+                file.declarations.push(crate::ast::Decl::new(
                     loc,
-                    parent_id,
-                    constraints,
-                    fields,
-                })
+                    crate::ast::DeclDesc::Struct { id, parent_id, constraints, fields },
+                ))
             }
             Rule::group_declaration => {
                 let mut children = node.children();
                 let id = parse_identifier(&mut children)?;
                 let fields = parse_field_list(&mut children, context)?;
-                file.declarations.push(ast::Decl::Group { id, loc, fields })
+                file.declarations
+                    .push(crate::ast::Decl::new(loc, crate::ast::DeclDesc::Group { id, fields }))
             }
             Rule::test_declaration => {}
             Rule::EOI => (),
@@ -498,10 +521,10 @@ fn parse_toplevel(root: Node<'_>, context: &Context) -> Result<ast::File, String
 /// The file is added to the compilation database under the provided
 /// name.
 pub fn parse_inline(
-    sources: &mut ast::SourceDatabase,
+    sources: &mut crate::ast::SourceDatabase,
     name: String,
     source: String,
-) -> Result<ast::File, Diagnostic<ast::FileId>> {
+) -> Result<ast::File, Diagnostic<crate::ast::FileId>> {
     let root = PDLParser::parse(Rule::file, &source)
         .map_err(|e| {
             Diagnostic::error()
@@ -520,9 +543,9 @@ pub fn parse_inline(
 /// database. Returns the constructed AST, or a descriptive error
 /// message in case of syntax error.
 pub fn parse_file(
-    sources: &mut ast::SourceDatabase,
+    sources: &mut crate::ast::SourceDatabase,
     name: String,
-) -> Result<ast::File, Diagnostic<ast::FileId>> {
+) -> Result<ast::File, Diagnostic<crate::ast::FileId>> {
     let source = std::fs::read_to_string(&name).map_err(|e| {
         Diagnostic::error().with_message(format!("failed to read input file '{}': {}", &name, e))
     })?;
@@ -537,12 +560,12 @@ mod test {
     fn endianness_is_set() {
         // The file starts out with a placeholder little-endian value.
         // This tests that we update it while parsing.
-        let mut db = ast::SourceDatabase::new();
+        let mut db = crate::ast::SourceDatabase::new();
         let file =
             parse_inline(&mut db, String::from("stdin"), String::from("  big_endian_packets  "))
                 .unwrap();
-        assert_eq!(file.endianness.value, ast::EndiannessValue::BigEndian);
-        assert_ne!(file.endianness.loc, ast::SourceRange::default());
+        assert_eq!(file.endianness.value, crate::ast::EndiannessValue::BigEndian);
+        assert_ne!(file.endianness.loc, crate::ast::SourceRange::default());
     }
 
     #[test]
-- 
GitLab