[banjo] Add support for parsing fidl IR JSON into structs.

This lands initial support for parsing fidl IR JSON. The next step will
be to take the deserialized structs and merge it into our AST
representation so that the types can be used as easily as banjo types.

The JSON schema doesn't match up entirely with the output from fidlc, so
the representations doesn't line up with the schema perfectly.

The test is not comprehensive as it is missing many variations of
structs.

Tested: fx run-host-tests banjo_host_tests
Change-Id: Id8603e829a876ddfdfa09c755fd085403b897854
diff --git a/tools/banjo/banjo/BUILD.gn b/tools/banjo/banjo/BUILD.gn
index 3dee0f5..df08b3d 100644
--- a/tools/banjo/banjo/BUILD.gn
+++ b/tools/banjo/banjo/BUILD.gn
@@ -12,8 +12,10 @@
   deps = [
     "//third_party/rust_crates:failure",
     "//third_party/rust_crates:heck",
+    "//third_party/rust_crates:lazy_static",
     "//third_party/rust_crates:pest",
     "//third_party/rust_crates:pest_derive",
+    "//third_party/rust_crates:regex",
     "//third_party/rust_crates:serde",
     "//third_party/rust_crates:serde_derive",
     "//third_party/rust_crates:serde_json",
@@ -27,8 +29,10 @@
   deps = [
     "//third_party/rust_crates:failure",
     "//third_party/rust_crates:heck",
+    "//third_party/rust_crates:lazy_static",
     "//third_party/rust_crates:pest",
     "//third_party/rust_crates:pest_derive",
+    "//third_party/rust_crates:regex",
     "//third_party/rust_crates:serde",
     "//third_party/rust_crates:serde_derive",
     "//third_party/rust_crates:serde_json",
@@ -48,6 +52,8 @@
       ":banjo_lib($host_toolchain)",
       "//third_party/rust_crates:pest",
       "//third_party/rust_crates:pretty_assertions",
+      "//third_party/rust_crates:serde",
+      "//third_party/rust_crates:serde_json",
     ]
     source_root = "test/tests.rs"
   }
diff --git a/tools/banjo/banjo/src/ast.rs b/tools/banjo/banjo/src/ast.rs
index e171459..ab4a69b 100644
--- a/tools/banjo/banjo/src/ast.rs
+++ b/tools/banjo/banjo/src/ast.rs
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 use {
+    crate::fidl,
     crate::Rule,
     failure::Fail,
     pest::iterators::{Pair, Pairs},
@@ -1035,7 +1036,10 @@
         Ok(())
     }
 
-    pub fn parse(pair_vec: Vec<Pairs<'_, Rule>>) -> Result<Self, ParseError> {
+    pub fn parse(
+        pair_vec: Vec<Pairs<'_, Rule>>,
+        _fidl_vec: Vec<fidl::Ir>,
+    ) -> Result<Self, ParseError> {
         let mut primary_namespace = None;
         let mut namespaces = BTreeMap::default();
 
diff --git a/tools/banjo/banjo/src/fidl.rs b/tools/banjo/banjo/src/fidl.rs
new file mode 100644
index 0000000..383a36b
--- /dev/null
+++ b/tools/banjo/banjo/src/fidl.rs
@@ -0,0 +1,435 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Based on https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/host/fidl/schema.json
+
+use {
+    lazy_static::lazy_static,
+    regex::Regex,
+    serde::{Deserialize, Deserializer},
+    serde_derive::{Deserialize, Serialize},
+    std::collections::BTreeMap,
+};
+
+lazy_static! {
+    pub static ref IDENTIFIER_RE: Regex =
+        Regex::new(r#"^[A-Za-z]([_A-Za-z0-9]*[A-Za-z0-9])?$"#).unwrap();
+    pub static ref COMPOUND_IDENTIFIER_RE: Regex =
+        Regex::new(r#"([_A-Za-z][_A-Za-z0-9]*-)*[_A-Za-z][_A-Za-z0-9]*/[_A-Za-z][_A-Za-z0-9]*"#)
+            .unwrap();
+    pub static ref LIBRARY_IDENTIFIER_RE: Regex =
+        Regex::new(r#"^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$"#).unwrap();
+    pub static ref VERSION_RE: Regex = Regex::new(r#"^[0-9]+\.[0-9]+\.[0-9]+$"#).unwrap();
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct Ordinal(#[serde(deserialize_with = "validate_ordinal")] pub u32);
+
+/// Validates that ordinal is non-zero.
+fn validate_ordinal<'de, D>(deserializer: D) -> Result<u32, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let ordinal = u32::deserialize(deserializer)?;
+    if ordinal == 0 {
+        return Err(serde::de::Error::custom("Ordinal must not be equal to 0"));
+    }
+    Ok(ordinal)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[repr(transparent)]
+pub struct Count(pub u32);
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct Identifier(#[serde(deserialize_with = "validate_identifier")] pub String);
+
+fn validate_identifier<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let id = String::deserialize(deserializer)?;
+    if !IDENTIFIER_RE.is_match(&id) {
+        return Err(serde::de::Error::custom(format!("Invalid identifier: {}", id)));
+    }
+    Ok(id)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct CompoundIdentifier(
+    #[serde(deserialize_with = "validate_compound_identifier")] pub String,
+);
+
+fn validate_compound_identifier<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let id = String::deserialize(deserializer)?;
+    if !COMPOUND_IDENTIFIER_RE.is_match(&id) {
+        return Err(serde::de::Error::custom(format!("Invalid compound identifier: {}", id)));
+    }
+    Ok(id)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct LibraryIdentifier(#[serde(deserialize_with = "validate_library_identifier")] pub String);
+
+fn validate_library_identifier<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let id = String::deserialize(deserializer)?;
+    if !LIBRARY_IDENTIFIER_RE.is_match(&id) {
+        return Err(serde::de::Error::custom(format!("Invalid library identifier: {}", id)));
+    }
+    Ok(id)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct Version(#[serde(deserialize_with = "validate_version")] pub String);
+
+fn validate_version<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let version = String::deserialize(deserializer)?;
+    if !VERSION_RE.is_match(&version) {
+        return Err(serde::de::Error::custom(format!("Invalid version: {}", version)));
+    }
+    Ok(version)
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum Declaration {
+    Const,
+    Enum,
+    Interface,
+    Struct,
+    Table,
+    Union,
+    XUnion,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct DeclarationsMap(pub BTreeMap<CompoundIdentifier, Declaration>);
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Library {
+    pub name: LibraryIdentifier,
+    pub declarations: DeclarationsMap,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Location {
+    pub filename: String,
+    pub line: u32,
+    pub column: u32,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum HandleSubtype {
+    Handle,
+    Process,
+    Thread,
+    Vmo,
+    Channel,
+    Event,
+    Port,
+    Interrupt,
+    Debuglog,
+    Socket,
+    Resource,
+    Eventpair,
+    Job,
+    Vmar,
+    Fifo,
+    Guest,
+    Timer,
+    Bti,
+    Profile,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum IntegerType {
+    Int8,
+    Int16,
+    Int32,
+    Int64,
+    Uint8,
+    Uint16,
+    Uint32,
+    Uint64,
+}
+
+// TODO(surajmalhotra): Implement conversion begtween IntegerType and PrimitiveSubtype.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PrimitiveSubtype {
+    Bool,
+    Float32,
+    Float64,
+    Int8,
+    Int16,
+    Int32,
+    Int64,
+    Uint8,
+    Uint16,
+    Uint32,
+    Uint64,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum Type {
+    Array {
+        element_type: Box<Type>,
+        element_count: Count,
+    },
+    Vector {
+        element_type: Box<Type>,
+        maybe_element_count: Option<Count>,
+        nullable: bool,
+    },
+    #[serde(rename = "string")]
+    Str {
+        maybe_element_count: Option<Count>,
+        nullable: bool,
+    },
+    Handle {
+        subtype: HandleSubtype,
+        nullable: bool,
+    },
+    Request {
+        subtype: CompoundIdentifier,
+        nullable: bool,
+    },
+    Primitive {
+        subtype: PrimitiveSubtype,
+    },
+    Identifier {
+        identifier: CompoundIdentifier,
+        nullable: bool,
+    },
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum Literal {
+    #[serde(rename = "string")]
+    Str {
+        value: String,
+    },
+    Numeric {
+        value: String,
+    },
+    True,
+    False,
+    #[serde(rename = "default")]
+    _Default,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum Constant {
+    Identifier { identifier: CompoundIdentifier },
+    Literal { literal: Literal },
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Const {
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    #[serde(rename = "type")]
+    pub _type: Type,
+    pub value: Constant,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct EnumMember {
+    pub name: Identifier,
+    pub location: Option<Location>,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub value: Constant,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Enum {
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    #[serde(rename = "type")]
+    pub _type: IntegerType,
+    pub members: Vec<EnumMember>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Attribute {
+    pub name: String,
+    pub value: String,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct MethodParameter {
+    #[serde(rename = "type")]
+    pub _type: Type,
+    pub name: Identifier,
+    pub location: Option<Location>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+    pub alignment: Count,
+    pub offset: Count,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Method {
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub ordinal: Ordinal,
+    pub generated_ordinal: Ordinal,
+    pub name: Identifier,
+    pub location: Option<Location>,
+    pub has_request: bool,
+    pub maybe_request: Option<Vec<MethodParameter>>,
+    pub maybe_request_size: Option<Count>,
+    pub maybe_request_alignment: Option<Count>,
+    pub has_response: bool,
+    pub maybe_response: Option<Vec<MethodParameter>>,
+    pub maybe_response_size: Option<Count>,
+    pub maybe_response_alignment: Option<Count>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Interface {
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    pub methods: Vec<Method>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct StructMember {
+    #[serde(rename = "type")]
+    pub _type: Type,
+    pub name: Identifier,
+    pub location: Option<Location>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+    pub alignment: Count,
+    pub offset: Count,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub maybe_default_value: Option<Constant>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Struct {
+    pub max_handles: Option<Count>,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    pub anonymous: Option<bool>,
+    pub members: Vec<StructMember>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
+pub struct TableMember {
+    pub ordinal: Ordinal,
+    pub reserved: bool,
+    #[serde(rename = "type")]
+    pub _type: Option<Type>,
+    pub name: Option<Identifier>,
+    pub location: Option<Location>,
+    pub size: Option<Count>,
+    pub max_out_of_line: Option<Count>,
+    pub alignment: Option<Count>,
+    pub offset: Option<Count>,
+    pub maybe_default_value: Option<Constant>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Table {
+    pub max_handles: Option<Count>,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    pub members: Vec<TableMember>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct UnionMember {
+    #[serde(rename = "type")]
+    pub _type: Type,
+    pub name: Identifier,
+    pub location: Option<Location>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+    pub alignment: Count,
+    pub offset: Count,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Union {
+    pub max_handles: Option<Count>,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    pub members: Vec<UnionMember>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+    pub alignment: Count,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct XUnionMember {
+    pub ordinal: Ordinal,
+    #[serde(rename = "type")]
+    pub _type: Type,
+    pub name: Identifier,
+    pub location: Option<Location>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+    pub alignment: Count,
+    pub offset: Count,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct XUnion {
+    pub max_handles: Option<Count>,
+    pub maybe_attributes: Option<Vec<Attribute>>,
+    pub name: CompoundIdentifier,
+    pub location: Option<Location>,
+    pub members: Vec<XUnionMember>,
+    pub size: Count,
+    pub max_out_of_line: Count,
+    pub alignment: Count,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Ir {
+    pub version: Version,
+    pub name: LibraryIdentifier,
+    pub library_dependencies: Vec<Library>,
+    pub const_declarations: Vec<Const>,
+    pub enum_declarations: Vec<Enum>,
+    pub interface_declarations: Vec<Interface>,
+    pub struct_declarations: Vec<Struct>,
+    pub table_declarations: Vec<Table>,
+    pub union_declarations: Vec<Union>,
+    pub xunion_declarations: Vec<XUnion>,
+    pub declaration_order: Vec<CompoundIdentifier>,
+    pub declarations: DeclarationsMap,
+}
diff --git a/tools/banjo/banjo/src/lib.rs b/tools/banjo/banjo/src/lib.rs
index 9c663b8..a9ebf1b 100644
--- a/tools/banjo/banjo/src/lib.rs
+++ b/tools/banjo/banjo/src/lib.rs
@@ -8,4 +8,5 @@
 
 pub mod ast;
 pub mod backends;
+pub mod fidl;
 pub mod parser;
diff --git a/tools/banjo/banjo/src/main.rs b/tools/banjo/banjo/src/main.rs
index 76b0867..ed95d6a 100644
--- a/tools/banjo/banjo/src/main.rs
+++ b/tools/banjo/banjo/src/main.rs
@@ -18,6 +18,7 @@
 
 mod ast;
 mod backends;
+mod fidl;
 mod parser;
 
 #[derive(Debug)]
@@ -72,10 +73,16 @@
     #[structopt(short = "b", long = "backend")]
     backend: BackendName,
 
-    /// Files to process
+    /// Banjo IDL files to process. These are expected to be in the format described by
+    /// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/docs/ddk/banjo-tutorial.md#reference
     #[structopt(short = "f", long = "files", parse(from_os_str))]
     input: Vec<PathBuf>,
 
+    /// FIDL IR JSON files to process. These files are expected to be in the format described by
+    /// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/host/fidl/schema.json
+    #[structopt(short = "i", long = "fidl-ir", parse(from_os_str))]
+    fidl_ir: Vec<PathBuf>,
+
     /// Don't include default zx types
     #[structopt(long = "omit-zx")]
     no_zx: bool,
@@ -109,6 +116,7 @@
 
     let opt = Opt::from_iter(args);
     let mut pair_vec = Vec::new();
+    let mut fidl_vec = Vec::new();
     let files: Vec<String> = opt
         .input
         .iter()
@@ -120,6 +128,17 @@
         })
         .collect();
 
+    let fidl_files: Vec<String> = opt
+        .fidl_ir
+        .iter()
+        .map(|filename| {
+            let mut f = File::open(filename).expect(&format!("{} not found", filename.display()));
+            let mut contents = String::new();
+            f.read_to_string(&mut contents).expect("something went wrong reading the file");
+            contents
+        })
+        .collect();
+
     if !opt.no_zx {
         let zx_file = include_str!("../zx.banjo");
         pair_vec.push(BanjoParser::parse(Rule::file, zx_file)?);
@@ -127,8 +146,11 @@
     for file in files.iter() {
         pair_vec.push(BanjoParser::parse(Rule::file, file.as_str())?);
     }
+    for file in fidl_files.iter() {
+        fidl_vec.push(serde_json::from_str(file.as_str())?);
+    }
 
-    let ast = BanjoAst::parse(pair_vec)?;
+    let ast = BanjoAst::parse(pair_vec, fidl_vec)?;
     let mut output: Box<dyn io::Write> = if let Some(output) = opt.output {
         Box::new(File::create(output)?)
     } else {
diff --git a/tools/banjo/banjo/test/fidl/test.fidl.json b/tools/banjo/banjo/test/fidl/test.fidl.json
new file mode 100644
index 0000000..898c3a7
--- /dev/null
+++ b/tools/banjo/banjo/test/fidl/test.fidl.json
@@ -0,0 +1,535 @@
+{
+  "version": "0.0.1",
+  "name": "fidl.test.misc",
+  "library_dependencies": [],
+  "const_declarations": [],
+  "enum_declarations": [],
+  "interface_declarations": [],
+  "struct_declarations": [
+    {
+      "name": "fidl.test.misc/Int64Struct",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 7,
+        "column": 7
+      },
+      "anonymous": false,
+      "members": [
+        {
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "x",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 8,
+            "column": 10
+          },
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 0,
+          "max_handles": 0
+        }
+      ],
+      "size": 8,
+      "max_out_of_line": 0,
+      "alignment": 8,
+      "max_handles": 0
+    },
+    {
+      "name": "fidl.test.misc/HasOptionalFieldStruct",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 11,
+        "column": 7
+      },
+      "anonymous": false,
+      "members": [
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/Int64Struct",
+            "nullable": true
+          },
+          "name": "x",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 12,
+            "column": 17
+          },
+          "size": 8,
+          "max_out_of_line": 8,
+          "alignment": 8,
+          "offset": 0,
+          "max_handles": 0
+        }
+      ],
+      "size": 8,
+      "max_out_of_line": 8,
+      "alignment": 8,
+      "max_handles": 0
+    },
+    {
+      "name": "fidl.test.misc/Has2OptionalFieldStruct",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 15,
+        "column": 7
+      },
+      "anonymous": false,
+      "members": [
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/Int64Struct",
+            "nullable": true
+          },
+          "name": "x",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 16,
+            "column": 17
+          },
+          "size": 8,
+          "max_out_of_line": 8,
+          "alignment": 8,
+          "offset": 0,
+          "max_handles": 0
+        },
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/Int64Struct",
+            "nullable": true
+          },
+          "name": "y",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 17,
+            "column": 17
+          },
+          "size": 8,
+          "max_out_of_line": 8,
+          "alignment": 8,
+          "offset": 8,
+          "max_handles": 0
+        }
+      ],
+      "size": 16,
+      "max_out_of_line": 16,
+      "alignment": 8,
+      "max_handles": 0
+    },
+    {
+      "name": "fidl.test.misc/Empty",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 20,
+        "column": 7
+      },
+      "anonymous": false,
+      "members": [],
+      "size": 1,
+      "max_out_of_line": 0,
+      "alignment": 1,
+      "max_handles": 0
+    },
+    {
+      "name": "fidl.test.misc/EmptyStructSandwich",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 23,
+        "column": 7
+      },
+      "anonymous": false,
+      "members": [
+        {
+          "type": {
+            "kind": "string",
+            "nullable": false
+          },
+          "name": "before",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 24,
+            "column": 11
+          },
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 0,
+          "max_handles": 0
+        },
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/Empty",
+            "nullable": false
+          },
+          "name": "e",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 25,
+            "column": 10
+          },
+          "size": 1,
+          "max_out_of_line": 0,
+          "alignment": 1,
+          "offset": 16,
+          "max_handles": 0
+        },
+        {
+          "type": {
+            "kind": "string",
+            "nullable": false
+          },
+          "name": "after",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 26,
+            "column": 11
+          },
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 24,
+          "max_handles": 0
+        }
+      ],
+      "size": 40,
+      "max_out_of_line": 4294967295,
+      "alignment": 8,
+      "max_handles": 0
+    },
+    {
+      "name": "fidl.test.misc/InlineXUnionInStruct",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 70,
+        "column": 7
+      },
+      "anonymous": false,
+      "members": [
+        {
+          "type": {
+            "kind": "string",
+            "nullable": false
+          },
+          "name": "before",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 71,
+            "column": 11
+          },
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 0,
+          "max_handles": 0
+        },
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/SampleXUnion",
+            "nullable": false
+          },
+          "name": "xu",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 72,
+            "column": 17
+          },
+          "size": 24,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 16,
+          "max_handles": 0
+        },
+        {
+          "type": {
+            "kind": "string",
+            "nullable": false
+          },
+          "name": "after",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 73,
+            "column": 11
+          },
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 40,
+          "max_handles": 0
+        }
+      ],
+      "size": 56,
+      "max_out_of_line": 4294967295,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "table_declarations": [
+    {
+      "name": "fidl.test.misc/SimpleTable",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 37,
+        "column": 6
+      },
+      "members": [
+        {
+          "ordinal": 1,
+          "reserved": false,
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "x",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 38,
+            "column": 13
+          },
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "max_handles": 0
+        },
+        {
+          "ordinal": 2,
+          "reserved": true
+        },
+        {
+          "ordinal": 3,
+          "reserved": true
+        },
+        {
+          "ordinal": 4,
+          "reserved": true
+        },
+        {
+          "ordinal": 5,
+          "reserved": false,
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "y",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 42,
+            "column": 13
+          },
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "max_handles": 0
+        }
+      ],
+      "size": 16,
+      "max_out_of_line": 48,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "union_declarations": [
+    {
+      "name": "fidl.test.misc/SimpleUnion",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 29,
+        "column": 6
+      },
+      "members": [
+        {
+          "type": {
+            "kind": "primitive",
+            "subtype": "int32"
+          },
+          "name": "i32",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 30,
+            "column": 10
+          },
+          "size": 4,
+          "max_out_of_line": 0,
+          "alignment": 4,
+          "offset": 8
+        },
+        {
+          "type": {
+            "kind": "primitive",
+            "subtype": "int64"
+          },
+          "name": "i64",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 31,
+            "column": 10
+          },
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 8
+        },
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/Int64Struct",
+            "nullable": false
+          },
+          "name": "s",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 32,
+            "column": 16
+          },
+          "size": 8,
+          "max_out_of_line": 0,
+          "alignment": 8,
+          "offset": 8
+        },
+        {
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/Int64Struct",
+            "nullable": true
+          },
+          "name": "os",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 33,
+            "column": 17
+          },
+          "size": 8,
+          "max_out_of_line": 8,
+          "alignment": 8,
+          "offset": 8
+        },
+        {
+          "type": {
+            "kind": "string",
+            "nullable": false
+          },
+          "name": "str",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 34,
+            "column": 11
+          },
+          "size": 16,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 8
+        }
+      ],
+      "size": 24,
+      "max_out_of_line": 4294967295,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "xunion_declarations": [
+    {
+      "name": "fidl.test.misc/SampleXUnion",
+      "location": {
+        "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+        "line": 64,
+        "column": 7
+      },
+      "members": [
+        {
+          "ordinal": 702498725,
+          "type": {
+            "kind": "primitive",
+            "subtype": "int32"
+          },
+          "name": "i",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 65,
+            "column": 10
+          },
+          "size": 4,
+          "max_out_of_line": 0,
+          "alignment": 4,
+          "offset": 0
+        },
+        {
+          "ordinal": 1865512531,
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/SimpleUnion",
+            "nullable": false
+          },
+          "name": "su",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 66,
+            "column": 16
+          },
+          "size": 24,
+          "max_out_of_line": 4294967295,
+          "alignment": 8,
+          "offset": 0
+        },
+        {
+          "ordinal": 811936989,
+          "type": {
+            "kind": "identifier",
+            "identifier": "fidl.test.misc/SimpleTable",
+            "nullable": false
+          },
+          "name": "st",
+          "location": {
+            "filename": "../../sdk/lib/fidl/cpp/fidl_test.fidl",
+            "line": 67,
+            "column": 16
+          },
+          "size": 16,
+          "max_out_of_line": 48,
+          "alignment": 8,
+          "offset": 0
+        }
+      ],
+      "size": 24,
+      "max_out_of_line": 4294967295,
+      "alignment": 8,
+      "max_handles": 0
+    }
+  ],
+  "declaration_order": [
+    "fidl.test.misc/Has2OptionalFieldStruct",
+    "fidl.test.misc/OptionalXUnionInStruct",
+    "fidl.test.misc/HasOptionalFieldStruct",
+    "fidl.test.misc/OlderSimpleTable",
+    "fidl.test.misc/NewerSimpleTable",
+    "fidl.test.misc/SimpleTable",
+    "fidl.test.misc/Int64Struct",
+    "fidl.test.misc/SimpleUnion",
+    "fidl.test.misc/SampleXUnion",
+    "fidl.test.misc/InlineXUnionInStruct",
+    "fidl.test.misc/XUnionInTable",
+    "fidl.test.misc/Empty",
+    "fidl.test.misc/EmptyStructSandwich"
+  ],
+  "declarations": {
+    "fidl.test.misc/Int64Struct": "struct",
+    "fidl.test.misc/HasOptionalFieldStruct": "struct",
+    "fidl.test.misc/Has2OptionalFieldStruct": "struct",
+    "fidl.test.misc/Empty": "struct",
+    "fidl.test.misc/EmptyStructSandwich": "struct",
+    "fidl.test.misc/InlineXUnionInStruct": "struct",
+    "fidl.test.misc/OptionalXUnionInStruct": "struct",
+    "fidl.test.misc/SimpleTable": "table",
+    "fidl.test.misc/OlderSimpleTable": "table",
+    "fidl.test.misc/NewerSimpleTable": "table",
+    "fidl.test.misc/XUnionInTable": "table",
+    "fidl.test.misc/SimpleUnion": "union",
+    "fidl.test.misc/SampleXUnion": "xunion"
+  }
+}
diff --git a/tools/banjo/banjo/test/fidl_tests.rs b/tools/banjo/banjo/test/fidl_tests.rs
new file mode 100644
index 0000000..1526de8
--- /dev/null
+++ b/tools/banjo/banjo/test/fidl_tests.rs
@@ -0,0 +1,587 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![cfg(test)]
+
+use pretty_assertions::assert_eq;
+use serde_json;
+use std::collections::BTreeMap;
+
+#[test]
+fn fidl_ir() {
+    use banjo_lib::fidl;
+    let input = include_str!("fidl/test.fidl.json");
+    let mut decls = BTreeMap::new();
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/Int64Struct".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/HasOptionalFieldStruct".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/Has2OptionalFieldStruct".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/Empty".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/EmptyStructSandwich".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/InlineXUnionInStruct".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/OptionalXUnionInStruct".to_string()),
+        fidl::Declaration::Struct,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/SimpleTable".to_string()),
+        fidl::Declaration::Table,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/OlderSimpleTable".to_string()),
+        fidl::Declaration::Table,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/NewerSimpleTable".to_string()),
+        fidl::Declaration::Table,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/XUnionInTable".to_string()),
+        fidl::Declaration::Table,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/SimpleUnion".to_string()),
+        fidl::Declaration::Union,
+    );
+    decls.insert(
+        fidl::CompoundIdentifier("fidl.test.misc/SampleXUnion".to_string()),
+        fidl::Declaration::XUnion,
+    );
+    let decls = decls;
+
+    let expected = fidl::Ir {
+        version: fidl::Version("0.0.1".to_string()),
+        name: fidl::LibraryIdentifier("fidl.test.misc".to_string()),
+        const_declarations: vec![],
+        enum_declarations: vec![],
+        interface_declarations: vec![],
+        struct_declarations: vec![
+            fidl::Struct {
+                max_handles: Some(fidl::Count(0)),
+                maybe_attributes: None,
+                name: fidl::CompoundIdentifier("fidl.test.misc/Int64Struct".to_string()),
+                location: Some(fidl::Location {
+                    filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                    line: 7,
+                    column: 7,
+                }),
+                anonymous: Some(false),
+                members: vec![fidl::StructMember {
+                    _type: fidl::Type::Primitive { subtype: fidl::PrimitiveSubtype::Int64 },
+                    name: fidl::Identifier("x".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 8,
+                        column: 10,
+                    }),
+                    size: fidl::Count(8),
+                    max_out_of_line: fidl::Count(0),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(0),
+                    maybe_attributes: None,
+                    maybe_default_value: None,
+                }],
+                size: fidl::Count(8),
+                max_out_of_line: fidl::Count(0),
+            },
+            fidl::Struct {
+                max_handles: Some(fidl::Count(0)),
+                maybe_attributes: None,
+                name: fidl::CompoundIdentifier("fidl.test.misc/HasOptionalFieldStruct".to_string()),
+                location: Some(fidl::Location {
+                    filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                    line: 11,
+                    column: 7,
+                }),
+                anonymous: Some(false),
+                members: vec![fidl::StructMember {
+                    _type: fidl::Type::Identifier {
+                        identifier: fidl::CompoundIdentifier(
+                            "fidl.test.misc/Int64Struct".to_string(),
+                        ),
+                        nullable: true,
+                    },
+                    name: fidl::Identifier("x".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 12,
+                        column: 17,
+                    }),
+                    size: fidl::Count(8),
+                    max_out_of_line: fidl::Count(8),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(0),
+                    maybe_attributes: None,
+                    maybe_default_value: None,
+                }],
+                size: fidl::Count(8),
+                max_out_of_line: fidl::Count(8),
+            },
+            fidl::Struct {
+                max_handles: Some(fidl::Count(0)),
+                maybe_attributes: None,
+                name: fidl::CompoundIdentifier(
+                    "fidl.test.misc/Has2OptionalFieldStruct".to_string(),
+                ),
+                location: Some(fidl::Location {
+                    filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                    line: 15,
+                    column: 7,
+                }),
+                anonymous: Some(false),
+                members: vec![
+                    fidl::StructMember {
+                        _type: fidl::Type::Identifier {
+                            identifier: fidl::CompoundIdentifier(
+                                "fidl.test.misc/Int64Struct".to_string(),
+                            ),
+                            nullable: true,
+                        },
+                        name: fidl::Identifier("x".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 16,
+                            column: 17,
+                        }),
+                        size: fidl::Count(8),
+                        max_out_of_line: fidl::Count(8),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(0),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                    fidl::StructMember {
+                        _type: fidl::Type::Identifier {
+                            identifier: fidl::CompoundIdentifier(
+                                "fidl.test.misc/Int64Struct".to_string(),
+                            ),
+                            nullable: true,
+                        },
+                        name: fidl::Identifier("y".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 17,
+                            column: 17,
+                        }),
+                        size: fidl::Count(8),
+                        max_out_of_line: fidl::Count(8),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(8),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                ],
+                size: fidl::Count(16),
+                max_out_of_line: fidl::Count(16),
+            },
+            fidl::Struct {
+                max_handles: Some(fidl::Count(0)),
+                maybe_attributes: None,
+                name: fidl::CompoundIdentifier("fidl.test.misc/Empty".to_string()),
+                location: Some(fidl::Location {
+                    filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                    line: 20,
+                    column: 7,
+                }),
+                anonymous: Some(false),
+                members: vec![],
+                size: fidl::Count(1),
+                max_out_of_line: fidl::Count(0),
+            },
+            fidl::Struct {
+                max_handles: Some(fidl::Count(0)),
+                maybe_attributes: None,
+                name: fidl::CompoundIdentifier("fidl.test.misc/EmptyStructSandwich".to_string()),
+                location: Some(fidl::Location {
+                    filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                    line: 23,
+                    column: 7,
+                }),
+                anonymous: Some(false),
+                members: vec![
+                    fidl::StructMember {
+                        _type: fidl::Type::Str { maybe_element_count: None, nullable: false },
+                        name: fidl::Identifier("before".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 24,
+                            column: 11,
+                        }),
+                        size: fidl::Count(16),
+                        max_out_of_line: fidl::Count(4294967295),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(0),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                    fidl::StructMember {
+                        _type: fidl::Type::Identifier {
+                            identifier: fidl::CompoundIdentifier(
+                                "fidl.test.misc/Empty".to_string(),
+                            ),
+                            nullable: false,
+                        },
+                        name: fidl::Identifier("e".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 25,
+                            column: 10,
+                        }),
+                        size: fidl::Count(1),
+                        max_out_of_line: fidl::Count(0),
+                        alignment: fidl::Count(1),
+                        offset: fidl::Count(16),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                    fidl::StructMember {
+                        _type: fidl::Type::Str { maybe_element_count: None, nullable: false },
+                        name: fidl::Identifier("after".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 26,
+                            column: 11,
+                        }),
+                        size: fidl::Count(16),
+                        max_out_of_line: fidl::Count(4294967295),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(24),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                ],
+                size: fidl::Count(40),
+                max_out_of_line: fidl::Count(4294967295),
+            },
+            fidl::Struct {
+                max_handles: Some(fidl::Count(0)),
+                maybe_attributes: None,
+                name: fidl::CompoundIdentifier("fidl.test.misc/InlineXUnionInStruct".to_string()),
+                location: Some(fidl::Location {
+                    filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                    line: 70,
+                    column: 7,
+                }),
+                anonymous: Some(false),
+                members: vec![
+                    fidl::StructMember {
+                        _type: fidl::Type::Str { maybe_element_count: None, nullable: false },
+                        name: fidl::Identifier("before".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 71,
+                            column: 11,
+                        }),
+                        size: fidl::Count(16),
+                        max_out_of_line: fidl::Count(4294967295),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(0),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                    fidl::StructMember {
+                        _type: fidl::Type::Identifier {
+                            identifier: fidl::CompoundIdentifier(
+                                "fidl.test.misc/SampleXUnion".to_string(),
+                            ),
+                            nullable: false,
+                        },
+                        name: fidl::Identifier("xu".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 72,
+                            column: 17,
+                        }),
+                        size: fidl::Count(24),
+                        max_out_of_line: fidl::Count(4294967295),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(16),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                    fidl::StructMember {
+                        _type: fidl::Type::Str { maybe_element_count: None, nullable: false },
+                        name: fidl::Identifier("after".to_string()),
+                        location: Some(fidl::Location {
+                            filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                            line: 73,
+                            column: 11,
+                        }),
+                        size: fidl::Count(16),
+                        max_out_of_line: fidl::Count(4294967295),
+                        alignment: fidl::Count(8),
+                        offset: fidl::Count(40),
+                        maybe_attributes: None,
+                        maybe_default_value: None,
+                    },
+                ],
+                size: fidl::Count(56),
+                max_out_of_line: fidl::Count(4294967295),
+            },
+        ],
+        table_declarations: vec![fidl::Table {
+            max_handles: Some(fidl::Count(0)),
+            maybe_attributes: None,
+            name: fidl::CompoundIdentifier("fidl.test.misc/SimpleTable".to_string()),
+            location: Some(fidl::Location {
+                filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                line: 37,
+                column: 6,
+            }),
+            members: vec![
+                fidl::TableMember {
+                    ordinal: fidl::Ordinal(1),
+                    reserved: false,
+                    _type: Some(fidl::Type::Primitive { subtype: fidl::PrimitiveSubtype::Int64 }),
+                    name: Some(fidl::Identifier("x".to_string())),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 38,
+                        column: 13,
+                    }),
+                    size: Some(fidl::Count(8)),
+                    max_out_of_line: Some(fidl::Count(0)),
+                    alignment: Some(fidl::Count(8)),
+                    offset: None,
+                    maybe_default_value: None,
+                },
+                fidl::TableMember {
+                    ordinal: fidl::Ordinal(2),
+                    reserved: true,
+                    ..Default::default()
+                },
+                fidl::TableMember {
+                    ordinal: fidl::Ordinal(3),
+                    reserved: true,
+                    ..Default::default()
+                },
+                fidl::TableMember {
+                    ordinal: fidl::Ordinal(4),
+                    reserved: true,
+                    ..Default::default()
+                },
+                fidl::TableMember {
+                    ordinal: fidl::Ordinal(5),
+                    reserved: false,
+                    _type: Some(fidl::Type::Primitive { subtype: fidl::PrimitiveSubtype::Int64 }),
+                    name: Some(fidl::Identifier("y".to_string())),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 42,
+                        column: 13,
+                    }),
+                    size: Some(fidl::Count(8)),
+                    max_out_of_line: Some(fidl::Count(0)),
+                    alignment: Some(fidl::Count(8)),
+                    offset: None,
+                    maybe_default_value: None,
+                },
+            ],
+            size: fidl::Count(16),
+            max_out_of_line: fidl::Count(48),
+        }],
+        union_declarations: vec![fidl::Union {
+            max_handles: Some(fidl::Count(0)),
+            maybe_attributes: None,
+            name: fidl::CompoundIdentifier("fidl.test.misc/SimpleUnion".to_string()),
+            location: Some(fidl::Location {
+                filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                line: 29,
+                column: 6,
+            }),
+            members: vec![
+                fidl::UnionMember {
+                    _type: fidl::Type::Primitive { subtype: fidl::PrimitiveSubtype::Int32 },
+                    name: fidl::Identifier("i32".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 30,
+                        column: 10,
+                    }),
+                    size: fidl::Count(4),
+                    max_out_of_line: fidl::Count(0),
+                    alignment: fidl::Count(4),
+                    offset: fidl::Count(8),
+                    maybe_attributes: None,
+                },
+                fidl::UnionMember {
+                    _type: fidl::Type::Primitive { subtype: fidl::PrimitiveSubtype::Int64 },
+                    name: fidl::Identifier("i64".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 31,
+                        column: 10,
+                    }),
+                    size: fidl::Count(8),
+                    max_out_of_line: fidl::Count(0),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(8),
+                    maybe_attributes: None,
+                },
+                fidl::UnionMember {
+                    _type: fidl::Type::Identifier {
+                        identifier: fidl::CompoundIdentifier(
+                            "fidl.test.misc/Int64Struct".to_string(),
+                        ),
+                        nullable: false,
+                    },
+                    name: fidl::Identifier("s".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 32,
+                        column: 16,
+                    }),
+                    size: fidl::Count(8),
+                    max_out_of_line: fidl::Count(0),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(8),
+                    maybe_attributes: None,
+                },
+                fidl::UnionMember {
+                    _type: fidl::Type::Identifier {
+                        identifier: fidl::CompoundIdentifier(
+                            "fidl.test.misc/Int64Struct".to_string(),
+                        ),
+                        nullable: true,
+                    },
+                    name: fidl::Identifier("os".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 33,
+                        column: 17,
+                    }),
+                    size: fidl::Count(8),
+                    max_out_of_line: fidl::Count(8),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(8),
+                    maybe_attributes: None,
+                },
+                fidl::UnionMember {
+                    _type: fidl::Type::Str { maybe_element_count: None, nullable: false },
+                    name: fidl::Identifier("str".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 34,
+                        column: 11,
+                    }),
+                    size: fidl::Count(16),
+                    max_out_of_line: fidl::Count(4294967295),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(8),
+                    maybe_attributes: None,
+                },
+            ],
+            size: fidl::Count(24),
+            max_out_of_line: fidl::Count(4294967295),
+            alignment: fidl::Count(8),
+        }],
+        xunion_declarations: vec![fidl::XUnion {
+            max_handles: Some(fidl::Count(0)),
+            maybe_attributes: None,
+            name: fidl::CompoundIdentifier("fidl.test.misc/SampleXUnion".to_string()),
+            location: Some(fidl::Location {
+                filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                line: 64,
+                column: 7,
+            }),
+            members: vec![
+                fidl::XUnionMember {
+                    ordinal: fidl::Ordinal(702498725),
+                    _type: fidl::Type::Primitive { subtype: fidl::PrimitiveSubtype::Int32 },
+                    name: fidl::Identifier("i".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 65,
+                        column: 10,
+                    }),
+                    size: fidl::Count(4),
+                    max_out_of_line: fidl::Count(0),
+                    alignment: fidl::Count(4),
+                    offset: fidl::Count(0),
+                    maybe_attributes: None,
+                },
+                fidl::XUnionMember {
+                    ordinal: fidl::Ordinal(1865512531),
+                    _type: fidl::Type::Identifier {
+                        identifier: fidl::CompoundIdentifier(
+                            "fidl.test.misc/SimpleUnion".to_string(),
+                        ),
+                        nullable: false,
+                    },
+                    name: fidl::Identifier("su".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 66,
+                        column: 16,
+                    }),
+                    size: fidl::Count(24),
+                    max_out_of_line: fidl::Count(4294967295),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(0),
+                    maybe_attributes: None,
+                },
+                fidl::XUnionMember {
+                    ordinal: fidl::Ordinal(811936989),
+                    _type: fidl::Type::Identifier {
+                        identifier: fidl::CompoundIdentifier(
+                            "fidl.test.misc/SimpleTable".to_string(),
+                        ),
+                        nullable: false,
+                    },
+                    name: fidl::Identifier("st".to_string()),
+                    location: Some(fidl::Location {
+                        filename: "../../sdk/lib/fidl/cpp/fidl_test.fidl".to_string(),
+                        line: 67,
+                        column: 16,
+                    }),
+                    size: fidl::Count(16),
+                    max_out_of_line: fidl::Count(48),
+                    alignment: fidl::Count(8),
+                    offset: fidl::Count(0),
+                    maybe_attributes: None,
+                },
+            ],
+            size: fidl::Count(24),
+            max_out_of_line: fidl::Count(4294967295),
+            alignment: fidl::Count(8),
+        }],
+        declaration_order: vec![
+            fidl::CompoundIdentifier("fidl.test.misc/Has2OptionalFieldStruct".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/OptionalXUnionInStruct".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/HasOptionalFieldStruct".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/OlderSimpleTable".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/NewerSimpleTable".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/SimpleTable".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/Int64Struct".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/SimpleUnion".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/SampleXUnion".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/InlineXUnionInStruct".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/XUnionInTable".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/Empty".to_string()),
+            fidl::CompoundIdentifier("fidl.test.misc/EmptyStructSandwich".to_string()),
+        ],
+        declarations: fidl::DeclarationsMap(decls),
+        library_dependencies: vec![],
+    };
+
+    let ir: fidl::Ir = serde_json::from_str(input).unwrap();
+    assert_eq!(ir, expected)
+}
diff --git a/tools/banjo/banjo/test/tests.rs b/tools/banjo/banjo/test/tests.rs
index 19e9846..c7ce109 100644
--- a/tools/banjo/banjo/test/tests.rs
+++ b/tools/banjo/banjo/test/tests.rs
@@ -6,6 +6,7 @@
 
 mod ast_tests;
 mod codegen_tests;
+mod fidl_tests;
 
 /// Asserts the left and right hand side are the same ignoring new line characters
 #[macro_export]
@@ -61,7 +62,7 @@
                     )*
                 };
 
-                let ast = banjo_lib::ast::BanjoAst::parse(pair_vec).unwrap();
+                let ast = banjo_lib::ast::BanjoAst::parse(pair_vec, Vec::new()).unwrap();
                 {
                     let mut backend: Box<dyn backends::Backend<_>> =
                         Box::new(backends::$backend::new(&mut output));