Repository: apollographql/apollo-rs
Pull request #721 (2023-11-03 12:01:05)

chore(parser): remove recursion when parsing non-nested lists of nodes
GraphQL has many list-like things that have to be non-empty. In those cases I try to explicitly parse one node and error if not found, then parse with a `while let` if the next token indicates another of the same node is coming. I wrapped it up in a helper function/macro first but it's frankly just as much code at each usage site so I didn't find it worth it for this very small pattern.

Fixes https://github.com/apollographql/apollo-rs/issues/666
Also fixes https://github.com/apollographql/apollo-rs/issues/659, and the same for unions (`union X = A || B |` is now illegal)

goto-bus-stop: I think we shouldn't consider the exact number a breaking change as it is tied to the internal details of how we parse stuff. Last time the correction was *increasing* the number which would trigger the limit on inputs that previously didn't, which made it a breaking change. This time any input that worked in the past still works, just more inputs are accepted than before. But it's worth calling it out better in the changelog

Patch:
diff --git a/crates/apollo-parser/CHANGELOG.md b/crates/apollo-parser/CHANGELOG.md
index 10b174e..cf97a99 100644
--- a/crates/apollo-parser/CHANGELOG.md
+++ b/crates/apollo-parser/CHANGELOG.md
@@ -16,6 +16,41 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ## Maintenance
 
 ## Documentation -->
+# [x.x.x] (unreleased) - 2023-mm-dd
+
+## Fixes
+
+- **Less recursion in parser implementation - [goto-bus-stop], [pull/721] fixing [issue/666]**
+  The parser previously used recursive functions while parsing some repetitive nodes, like members of an enum:
+  ```graphql
+  enum Alphabet { A B C D E F G etc }
+  ```
+  Even though this is a flat list, each member would use a recursive call. Having many members, or fields in a type
+  definition, or arguments in a directive, would all contribute to the recursion limit.
+
+  Those cases are now using iteration instead and no longer contribute to the recursion limit. The default recursion limit
+  is unchanged at 500, but you could reduce it depending on your needs.
+
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[pull/721]: https://github.com/apollographql/apollo-rs/pull/721
+[issue/666]: https://github.com/apollographql/apollo-rs/issues/666
+
+- **Fix overly permissive parsing of `implements` lists and `union` member types - [goto-bus-stop], [pull/721] fixing [issue/659]**
+  Previously these definitions were all accepted, despite missing or excessive `&` and `|` separators:
+  ```graphql
+  type Ty implements A B
+  type Ty implements A && B
+  type Ty implements A & B &
+
+  union Ty = A B
+  union Ty = A || B
+  union Ty = A | B |
+  ```
+  Now they report a syntax error.
+
+[goto-bus-stop]: https://github.com/goto-bus-stop
+[pull/721]: https://github.com/apollographql/apollo-rs/pull/721
+[issue/659]: https://github.com/apollographql/apollo-rs/issues/659
 
 # [0.7.2](https://crates.io/crates/apollo-parser/0.7.2) - 2023-11-03
 
diff --git a/crates/apollo-parser/src/parser/grammar/argument.rs b/crates/apollo-parser/src/parser/grammar/argument.rs
index 6d348bf..aa36c1e 100644
--- a/crates/apollo-parser/src/parser/grammar/argument.rs
+++ b/crates/apollo-parser/src/parser/grammar/argument.rs
@@ -7,29 +7,12 @@ use crate::{
 ///
 /// *Argument*:
 ///    Name **:** Value
-pub(crate) fn argument(p: &mut Parser, mut is_argument: bool) {
-    if let Some(TokenKind::Name) = p.peek() {
-        let guard = p.start_node(SyntaxKind::ARGUMENT);
-        name::name(p);
-        if let Some(T![:]) = p.peek() {
-            p.bump(S![:]);
-            value::value(p, false);
-            is_argument = true;
-            if p.peek().is_some() {
-                guard.finish_node();
-                // TODO: use a loop instead of recursion
-                if p.recursion_limit.check_and_increment() {
-                    p.limit_err("parser recursion limit reached");
-                    return;
-                }
-                argument(p, is_argument);
-                p.recursion_limit.decrement();
-                return;
-            }
-        }
-    }
-    if !is_argument {
-        p.err("expected an Argument");
+pub(crate) fn argument(p: &mut Parser) {
+    let _guard = p.start_node(SyntaxKind::ARGUMENT);
+    name::name(p);
+    if let Some(T![:]) = p.peek() {
+        p.bump(S![:]);
+        value::value(p, false);
     }
 }
 
@@ -40,7 +23,14 @@ pub(crate) fn argument(p: &mut Parser, mut is_argument: bool) {
 pub(crate) fn arguments(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::ARGUMENTS);
     p.bump(S!['(']);
-    argument(p, false);
+    if let Some(TokenKind::Name) = p.peek() {
+        argument(p);
+    } else {
+        p.err("expected an Argument");
+    }
+    while let Some(TokenKind::Name) = p.peek() {
+        argument(p);
+    }
     p.expect(T![')'], S![')']);
 }
 
@@ -51,7 +41,14 @@ pub(crate) fn arguments(p: &mut Parser) {
 pub(crate) fn arguments_definition(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::ARGUMENTS_DEFINITION);
     p.bump(S!['(']);
-    input::input_value_definition(p, false);
+    if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+        input::input_value_definition(p);
+    } else {
+        p.err("expected an Argument Definition");
+    }
+    while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+        input::input_value_definition(p);
+    }
     p.expect(T![')'], S![')']);
 }
 
diff --git a/crates/apollo-parser/src/parser/grammar/directive.rs b/crates/apollo-parser/src/parser/grammar/directive.rs
index 5edcef9..4e477b3 100644
--- a/crates/apollo-parser/src/parser/grammar/directive.rs
+++ b/crates/apollo-parser/src/parser/grammar/directive.rs
@@ -27,7 +27,14 @@ pub(crate) fn directive_definition(p: &mut Parser) {
     if let Some(T!['(']) = p.peek() {
         let _g = p.start_node(SyntaxKind::ARGUMENTS_DEFINITION);
         p.bump(S!['(']);
-        input::input_value_definition(p, false);
+        if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+            input::input_value_definition(p);
+        } else {
+            p.err("expected an Argument Definition");
+        }
+        while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+            input::input_value_definition(p);
+        }
         p.expect(T![')'], S![')']);
     }
 
diff --git a/crates/apollo-parser/src/parser/grammar/document.rs b/crates/apollo-parser/src/parser/grammar/document.rs
index 267f04f..fb409fd 100644
--- a/crates/apollo-parser/src/parser/grammar/document.rs
+++ b/crates/apollo-parser/src/parser/grammar/document.rs
@@ -59,26 +59,6 @@ fn select_definition(def: String, p: &mut Parser) {
     }
 }
 
-pub(crate) fn is_definition(def: String) -> bool {
-    matches!(
-        def.as_str(),
-        "directive"
-            | "enum"
-            | "extend"
-            | "fragment"
-            | "input"
-            | "interface"
-            | "type"
-            | "query"
-            | "mutation"
-            | "subscription"
-            | "{"
-            | "scalar"
-            | "schema"
-            | "union"
-    )
-}
-
 #[cfg(test)]
 mod test {
     use crate::{cst, cst::CstNode, Parser};
diff --git a/crates/apollo-parser/src/parser/grammar/enum_.rs b/crates/apollo-parser/src/parser/grammar/enum_.rs
index a058175..7b5bd58 100644
--- a/crates/apollo-parser/src/parser/grammar/enum_.rs
+++ b/crates/apollo-parser/src/parser/grammar/enum_.rs
@@ -79,6 +79,10 @@ pub(crate) fn enum_values_definition(p: &mut Parser) {
         _ => p.err("expected Enum Value Definition"),
     }
 
+    while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+        enum_value_definition(p);
+    }
+
     p.expect(T!['}'], S!['}']);
 }
 
@@ -88,7 +92,7 @@ pub(crate) fn enum_values_definition(p: &mut Parser) {
 ///     Description? EnumValue Directives?
 pub(crate) fn enum_value_definition(p: &mut Parser) {
     if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
-        let guard = p.start_node(SyntaxKind::ENUM_VALUE_DEFINITION);
+        let _guard = p.start_node(SyntaxKind::ENUM_VALUE_DEFINITION);
 
         if let Some(TokenKind::StringValue) = p.peek() {
             description::description(p);
@@ -99,20 +103,5 @@ pub(crate) fn enum_value_definition(p: &mut Parser) {
         if let Some(T![@]) = p.peek() {
             directive::directives(p);
         }
-        if p.peek().is_some() {
-            guard.finish_node();
-            // TODO: use a loop instead of recursion
-            if p.recursion_limit.check_and_increment() {
-                p.limit_err("parser recursion limit reached");
-                return;
-            }
-            enum_value_definition(p);
-            p.recursion_limit.decrement();
-            return;
-        }
-    }
-
-    if let Some(T!['}']) = p.peek() {
-        return;
     }
 }
diff --git a/crates/apollo-parser/src/parser/grammar/input.rs b/crates/apollo-parser/src/parser/grammar/input.rs
index 3276f1a..5d21002 100644
--- a/crates/apollo-parser/src/parser/grammar/input.rs
+++ b/crates/apollo-parser/src/parser/grammar/input.rs
@@ -72,7 +72,15 @@ pub(crate) fn input_object_type_extension(p: &mut Parser) {
 pub(crate) fn input_fields_definition(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::INPUT_FIELDS_DEFINITION);
     p.bump(S!['{']);
-    input_value_definition(p, false);
+    if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+        input_value_definition(p);
+    } else {
+        p.err("expected an Input Value Definition");
+    }
+    while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+        input_value_definition(p);
+    }
+
     p.expect(T!['}'], S!['}']);
 }
 
@@ -80,49 +88,31 @@ pub(crate) fn input_fields_definition(p: &mut Parser) {
 ///
 /// *InputValueDefinition*:
 ///     Description? Name **:** Type DefaultValue? Directives?
-pub(crate) fn input_value_definition(p: &mut Parser, is_input: bool) {
-    if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
-        let guard = p.start_node(SyntaxKind::INPUT_VALUE_DEFINITION);
+pub(crate) fn input_value_definition(p: &mut Parser) {
+    let _guard = p.start_node(SyntaxKind::INPUT_VALUE_DEFINITION);
 
-        if let Some(TokenKind::StringValue) = p.peek() {
-            description::description(p);
-        }
+    if let Some(TokenKind::StringValue) = p.peek() {
+        description::description(p);
+    }
 
-        name::name(p);
-
-        if let Some(T![:]) = p.peek() {
-            p.bump(S![:]);
-            match p.peek() {
-                Some(TokenKind::Name) | Some(T!['[']) => {
-                    ty::ty(p);
-                    if let Some(T![=]) = p.peek() {
-                        value::default_value(p);
-                    }
-
-                    if let Some(T![@]) = p.peek() {
-                        directive::directives(p);
-                    }
-
-                    if p.peek().is_some() {
-                        guard.finish_node();
-                        // TODO: use a loop instead of recursion
-                        if p.recursion_limit.check_and_increment() {
-                            p.limit_err("parser recursion limit reached");
-                            return;
-                        }
-                        input_value_definition(p, true);
-                        p.recursion_limit.decrement();
-                        return;
-                    }
+    name::name(p);
+
+    if let Some(T![:]) = p.peek() {
+        p.bump(S![:]);
+        match p.peek() {
+            Some(TokenKind::Name) | Some(T!['[']) => {
+                ty::ty(p);
+                if let Some(T![=]) = p.peek() {
+                    value::default_value(p);
+                }
+
+                if let Some(T![@]) = p.peek() {
+                    directive::directives(p);
                 }
-                _ => p.err("expected a Type"),
             }
-        } else {
-            p.err("expected a Name");
+            _ => p.err("expected a Type"),
         }
-    }
-    // TODO @lrlna: this can be simplified a little bit, and follow the pattern of FieldDefinition
-    if !is_input {
-        p.err("expected an Input Value Definition");
+    } else {
+        p.err("expected a Name");
     }
 }
diff --git a/crates/apollo-parser/src/parser/grammar/object.rs b/crates/apollo-parser/src/parser/grammar/object.rs
index 4eb6f5e..36a43ec 100644
--- a/crates/apollo-parser/src/parser/grammar/object.rs
+++ b/crates/apollo-parser/src/parser/grammar/object.rs
@@ -1,7 +1,7 @@
 #![allow(clippy::needless_return)]
 
 use crate::{
-    parser::grammar::{description, directive, document::is_definition, field, name, ty},
+    parser::grammar::{description, directive, field, name, ty},
     Parser, SyntaxKind, TokenKind, S, T,
 };
 
@@ -91,35 +91,22 @@ pub(crate) fn implements_interfaces(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::IMPLEMENTS_INTERFACES);
     p.bump(SyntaxKind::implements_KW);
 
-    implements_interface(p, false);
-}
+    if let Some(T![&]) = p.peek() {
+        p.bump(S![&]);
+    }
 
-fn implements_interface(p: &mut Parser, is_interfaces: bool) {
-    match p.peek() {
-        Some(T![&]) => {
-            p.bump(S![&]);
-            implements_interface(p, is_interfaces)
-        }
-        Some(TokenKind::Name) => {
-            ty::named_type(p);
-            if let Some(node) = p.peek_data() {
-                if !is_definition(node) {
-                    // TODO: use a loop instead of recursion
-                    if p.recursion_limit.check_and_increment() {
-                        p.limit_err("parser recursion limit reached");
-                        return;
-                    }
-                    implements_interface(p, true);
-                    p.recursion_limit.decrement()
-                }
+    if let Some(TokenKind::Name) = p.peek() {
+        ty::named_type(p);
+    } else {
+        p.err("expected an Interface name");
+    }
 
-                return;
-            }
-        }
-        _ => {
-            if !is_interfaces {
-                p.err("expected an Object Type Definition");
-            }
+    while let Some(T![&]) = p.peek() {
+        p.bump(S![&]);
+        if let Some(TokenKind::Name) = p.peek() {
+            ty::named_type(p);
+        } else {
+            p.err("expected an Interface name");
         }
     }
 }
diff --git a/crates/apollo-parser/src/parser/grammar/union_.rs b/crates/apollo-parser/src/parser/grammar/union_.rs
index 4ab2b4f..7ff52ac 100644
--- a/crates/apollo-parser/src/parser/grammar/union_.rs
+++ b/crates/apollo-parser/src/parser/grammar/union_.rs
@@ -1,7 +1,7 @@
 #![allow(clippy::needless_return)]
 
 use crate::{
-    parser::grammar::{description, directive, document::is_definition, name, ty},
+    parser::grammar::{description, directive, name, ty},
     Parser, SyntaxKind, TokenKind, S, T,
 };
 
@@ -75,35 +75,22 @@ pub(crate) fn union_member_types(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::UNION_MEMBER_TYPES);
     p.bump(S![=]);
 
-    union_member_type(p, false);
-}
+    if let Some(T![|]) = p.peek() {
+        p.bump(S![|]);
+    }
 
-fn union_member_type(p: &mut Parser, is_union: bool) {
-    match p.peek() {
-        Some(T![|]) => {
-            p.bump(S![|]);
-            union_member_type(p, is_union);
-        }
-        Some(TokenKind::Name) => {
-            ty::named_type(p);
-            if let Some(node) = p.peek_data() {
-                if !is_definition(node) {
-                    // TODO: use a loop instead of recursion
-                    if p.recursion_limit.check_and_increment() {
-                        p.limit_err("parser recursion limit reached");
-                        return;
-                    }
-                    union_member_type(p, true);
-                    p.recursion_limit.decrement();
-                }
+    if let Some(TokenKind::Name) = p.peek() {
+        ty::named_type(p);
+    } else {
+        p.err("expected Union Member Types");
+    }
 
-                return;
-            }
-        }
-        _ => {
-            if !is_union {
-                p.err("expected Union Member Types");
-            }
+    while let Some(T![|]) = p.peek() {
+        p.bump(S![|]);
+        if let Some(TokenKind::Name) = p.peek() {
+            ty::named_type(p);
+        } else {
+            p.err("expected Union Member Type");
         }
     }
 }
diff --git a/crates/apollo-parser/src/parser/grammar/value.rs b/crates/apollo-parser/src/parser/grammar/value.rs
index 7dcf171..115df2f 100644
--- a/crates/apollo-parser/src/parser/grammar/value.rs
+++ b/crates/apollo-parser/src/parser/grammar/value.rs
@@ -110,26 +110,16 @@ pub(crate) fn object_value(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::OBJECT_VALUE);
     p.bump(S!['{']);
 
-    match p.peek() {
-        Some(TokenKind::Name) => {
-            if p.recursion_limit.check_and_increment() {
-                p.limit_err("parser recursion limit reached");
-                return;
-            }
-            object_field(p);
-            p.recursion_limit.decrement();
-
-            if let Some(T!['}']) = p.peek() {
-                p.bump(S!['}']);
-            } else {
-                p.err("expected }");
-            }
-        }
-        Some(T!['}']) => {
-            p.bump(S!['}']);
-        }
-        _ => p.err("expected Object Value"),
+    if let Some(TokenKind::Name) = p.peek() {
+        object_field(p);
+    } else {
+        p.err("expected Object Value");
+    }
+    while let Some(TokenKind::Name) = p.peek() {
+        object_field(p);
     }
+
+    p.expect(T!['}'], S!['}']);
 }
 
 /// See: https://spec.graphql.org/October2021/#ObjectField
@@ -137,25 +127,17 @@ pub(crate) fn object_value(p: &mut Parser) {
 /// *ObjectField*:
 ///     Name **:** Value
 pub(crate) fn object_field(p: &mut Parser) {
-    if let Some(TokenKind::Name) = p.peek() {
-        let guard = p.start_node(SyntaxKind::OBJECT_FIELD);
-        name::name(p);
-
-        if let Some(T![:]) = p.peek() {
-            p.bump(S![:]);
-            value(p, true);
-            if p.peek().is_some() {
-                guard.finish_node();
+    let _guard = p.start_node(SyntaxKind::OBJECT_FIELD);
+    name::name(p);
 
-                // TODO: use a loop instead of recursion
-                if p.recursion_limit.check_and_increment() {
-                    p.limit_err("parser recursion limit reached");
-                    return;
-                }
-                object_field(p);
-                p.recursion_limit.decrement();
-            }
+    if let Some(T![:]) = p.peek() {
+        p.bump(S![:]);
+        if p.recursion_limit.check_and_increment() {
+            p.limit_err("parser recursion limit reached");
+            return;
         }
+        value(p, true);
+        p.recursion_limit.decrement()
     }
 }
 
diff --git a/crates/apollo-parser/src/parser/grammar/variable.rs b/crates/apollo-parser/src/parser/grammar/variable.rs
index b937909..baeec86 100644
--- a/crates/apollo-parser/src/parser/grammar/variable.rs
+++ b/crates/apollo-parser/src/parser/grammar/variable.rs
@@ -12,10 +12,14 @@ pub(crate) fn variable_definitions(p: &mut Parser) {
     p.bump(S!['(']);
 
     if let Some(T![$]) = p.peek() {
-        variable_definition(p, false);
+        variable_definition(p);
     } else {
         p.err("expected a Variable Definition")
     }
+    while let Some(T![$]) = p.peek() {
+        variable_definition(p);
+    }
+
     p.expect(T![')'], S![')']);
 }
 
@@ -23,41 +27,25 @@ pub(crate) fn variable_definitions(p: &mut Parser) {
 ///
 /// *VariableDefinition*:
 ///     Variable **:** Type DefaultValue? Directives?
-pub(crate) fn variable_definition(p: &mut Parser, is_variable: bool) {
-    if let Some(T![$]) = p.peek() {
-        let guard = p.start_node(SyntaxKind::VARIABLE_DEFINITION);
-        variable(p);
+pub(crate) fn variable_definition(p: &mut Parser) {
+    let _guard = p.start_node(SyntaxKind::VARIABLE_DEFINITION);
+    variable(p);
 
-        if let Some(T![:]) = p.peek() {
-            p.bump(S![:]);
-            if let Some(TokenKind::Name | TokenKind::LBracket) = p.peek() {
-                ty::ty(p);
-                if let Some(T![=]) = p.peek() {
-                    value::default_value(p);
-                }
-                if let Some(T![@]) = p.peek() {
-                    directive::directives(p)
-                }
-                if p.peek().is_some() {
-                    guard.finish_node();
-                    // TODO: use a loop instead of recursion
-                    if p.recursion_limit.check_and_increment() {
-                        p.limit_err("parser recursion limit reached");
-                        return;
-                    }
-                    variable_definition(p, true);
-                    p.recursion_limit.decrement();
-                    return;
-                }
+    if let Some(T![:]) = p.peek() {
+        p.bump(S![:]);
+        if let Some(TokenKind::Name | TokenKind::LBracket) = p.peek() {
+            ty::ty(p);
+            if let Some(T![=]) = p.peek() {
+                value::default_value(p);
+            }
+            if let Some(T![@]) = p.peek() {
+                directive::directives(p)
             }
-            p.err("expected a Type");
         } else {
-            p.err("expected a Name");
+            p.err("expected a Type");
         }
-    }
-
-    if !is_variable {
-        p.err("expected a Variable Definition");
+    } else {
+        p.err("expected a Name");
     }
 }
 


Test patch:
diff --git a/crates/apollo-parser/test_data/parser/err/0002_enum_definition_with_missing_name.txt b/crates/apollo-parser/test_data/parser/err/0002_enum_definition_with_missing_name.txt
index da65c18..8785c15 100644
--- a/crates/apollo-parser/test_data/parser/err/0002_enum_definition_with_missing_name.txt
+++ b/crates/apollo-parser/test_data/parser/err/0002_enum_definition_with_missing_name.txt
@@ -27,4 +27,4 @@
             - WHITESPACE@44..45 "\n"
             - R_CURLY@45..46 "}"
 - ERROR@5:6 "expected a Name" {
-recursion limit: 500, high: 4
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0004_enum_definition_with_missing_curly.txt b/crates/apollo-parser/test_data/parser/err/0004_enum_definition_with_missing_curly.txt
index 33d7d6a..c9786c4 100644
--- a/crates/apollo-parser/test_data/parser/err/0004_enum_definition_with_missing_curly.txt
+++ b/crates/apollo-parser/test_data/parser/err/0004_enum_definition_with_missing_curly.txt
@@ -18,4 +18,4 @@
                     - NAME@23..27
                         - IDENT@23..27 "WEST"
 - ERROR@27:27 "expected R_CURLY, got EOF" EOF
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0005_enum_extension_with_missing_name.txt b/crates/apollo-parser/test_data/parser/err/0005_enum_extension_with_missing_name.txt
index e0a13d4..803239a 100644
--- a/crates/apollo-parser/test_data/parser/err/0005_enum_extension_with_missing_name.txt
+++ b/crates/apollo-parser/test_data/parser/err/0005_enum_extension_with_missing_name.txt
@@ -19,4 +19,4 @@
             - WHITESPACE@32..33 "\n"
             - R_CURLY@33..34 "}"
 - ERROR@12:13 "expected a Name" {
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0010_input_definition_with_missing_name.txt b/crates/apollo-parser/test_data/parser/err/0010_input_definition_with_missing_name.txt
index 8583f62..ec3a970 100644
--- a/crates/apollo-parser/test_data/parser/err/0010_input_definition_with_missing_name.txt
+++ b/crates/apollo-parser/test_data/parser/err/0010_input_definition_with_missing_name.txt
@@ -27,4 +27,4 @@
             - WHITESPACE@33..34 "\n"
             - R_CURLY@34..35 "}"
 - ERROR@6:7 "expected a Name" {
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0012_input_extension_with_missing_name.txt b/crates/apollo-parser/test_data/parser/err/0012_input_extension_with_missing_name.txt
index a38f152..42c8424 100644
--- a/crates/apollo-parser/test_data/parser/err/0012_input_extension_with_missing_name.txt
+++ b/crates/apollo-parser/test_data/parser/err/0012_input_extension_with_missing_name.txt
@@ -18,4 +18,4 @@
             - WHITESPACE@28..29 "\n"
             - R_CURLY@29..30 "}"
 - ERROR@13:14 "expected a Name" {
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0020_union_definition_with_missing_name.txt b/crates/apollo-parser/test_data/parser/err/0020_union_definition_with_missing_name.txt
index 382283e..2211d6a 100644
--- a/crates/apollo-parser/test_data/parser/err/0020_union_definition_with_missing_name.txt
+++ b/crates/apollo-parser/test_data/parser/err/0020_union_definition_with_missing_name.txt
@@ -15,4 +15,4 @@
                 - NAME@16..22
                     - IDENT@16..22 "Person"
 - ERROR@6:7 "expected a Name" =
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0022_union_extension_with_missing_name.txt b/crates/apollo-parser/test_data/parser/err/0022_union_extension_with_missing_name.txt
index efd7db3..73907e1 100644
--- a/crates/apollo-parser/test_data/parser/err/0022_union_extension_with_missing_name.txt
+++ b/crates/apollo-parser/test_data/parser/err/0022_union_extension_with_missing_name.txt
@@ -17,4 +17,4 @@
                 - NAME@23..29
                     - IDENT@23..29 "Person"
 - ERROR@13:14 "expected a Name" =
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt b/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt
index 19c7663..281d25f 100644
--- a/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt
+++ b/crates/apollo-parser/test_data/parser/err/0026_invalid_definition_squished_between_two_valid_definitions.txt
@@ -56,4 +56,4 @@
             - WHITESPACE@138..139 "\n"
             - R_CURLY@139..140 "}"
 - ERROR@99:113 "expected definition" uasdf21230jkdw
-recursion limit: 500, high: 4
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0031_argument_with_erronous_string_value.txt b/crates/apollo-parser/test_data/parser/err/0031_argument_with_erronous_string_value.txt
index 394dc44..235547a 100644
--- a/crates/apollo-parser/test_data/parser/err/0031_argument_with_erronous_string_value.txt
+++ b/crates/apollo-parser/test_data/parser/err/0031_argument_with_erronous_string_value.txt
@@ -18,4 +18,4 @@
             - R_CURLY@15..16 "}"
 - ERROR@13:25 "unexpected escaped character" "\string ID"
 - ERROR@25:26 "expected a valid Value" )
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0032_input_value_with_erronous_string_value.txt b/crates/apollo-parser/test_data/parser/err/0032_input_value_with_erronous_string_value.txt
index 76ce073..8117b02 100644
--- a/crates/apollo-parser/test_data/parser/err/0032_input_value_with_erronous_string_value.txt
+++ b/crates/apollo-parser/test_data/parser/err/0032_input_value_with_erronous_string_value.txt
@@ -37,5 +37,4 @@
             - R_CURLY@57..58 "}"
 - ERROR@50:70 "unexpected escaped character" "\a reference image"
 - ERROR@70:71 "expected a Type" )
-- ERROR@70:71 "expected an Input Value Definition" )
 recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0033_directive_with_erronous_string_value.txt b/crates/apollo-parser/test_data/parser/err/0033_directive_with_erronous_string_value.txt
index 1353b66..b0aa1a6 100644
--- a/crates/apollo-parser/test_data/parser/err/0033_directive_with_erronous_string_value.txt
+++ b/crates/apollo-parser/test_data/parser/err/0033_directive_with_erronous_string_value.txt
@@ -68,4 +68,4 @@
             - R_CURLY@118..119 "}"
 - ERROR@105:128 "unexpected escaped character" "\ errronous string \""
 - ERROR@128:129 "expected a valid Value" )
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0034_unterminated_string_value_in_list.txt b/crates/apollo-parser/test_data/parser/err/0034_unterminated_string_value_in_list.txt
index f2f3461..b9ec562 100644
--- a/crates/apollo-parser/test_data/parser/err/0034_unterminated_string_value_in_list.txt
+++ b/crates/apollo-parser/test_data/parser/err/0034_unterminated_string_value_in_list.txt
@@ -47,4 +47,4 @@ type Vehicle @key(fields: "
 }
 
 - ERROR@237:237 "expected R_PAREN, got EOF" EOF
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0035_unterminated_string_value_in_object_value.txt b/crates/apollo-parser/test_data/parser/err/0035_unterminated_string_value_in_object_value.txt
index 3bc2846..36272c2 100644
--- a/crates/apollo-parser/test_data/parser/err/0035_unterminated_string_value_in_object_value.txt
+++ b/crates/apollo-parser/test_data/parser/err/0035_unterminated_string_value_in_object_value.txt
@@ -52,8 +52,8 @@
 - ERROR@108:116 "unterminated string value" ")
   }
 }
-- ERROR@116:116 "expected }" EOF
-- ERROR@116:116 "expected }" EOF
+- ERROR@116:116 "expected R_CURLY, got EOF" EOF
+- ERROR@116:116 "expected R_CURLY, got EOF" EOF
 - ERROR@116:116 "expected R_PAREN, got EOF" EOF
 - ERROR@116:116 "expected R_CURLY, got EOF" EOF
-recursion limit: 500, high: 6
\ No newline at end of file
+recursion limit: 500, high: 4
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0036_unterminated_string_in_default_value.txt b/crates/apollo-parser/test_data/parser/err/0036_unterminated_string_in_default_value.txt
index a3b7b77..a4e5b94 100644
--- a/crates/apollo-parser/test_data/parser/err/0036_unterminated_string_in_default_value.txt
+++ b/crates/apollo-parser/test_data/parser/err/0036_unterminated_string_in_default_value.txt
@@ -40,4 +40,4 @@
 - ERROR@112:112 "expected R_PAREN, got EOF" EOF
 - ERROR@112:112 "expected a type" EOF
 - ERROR@112:112 "expected R_CURLY, got EOF" EOF
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0050_invalid_implements_list.graphql b/crates/apollo-parser/test_data/parser/err/0050_invalid_implements_list.graphql
new file mode 100644
index 0000000..3a43bfd
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0050_invalid_implements_list.graphql
@@ -0,0 +1,5 @@
+type Obj implements A & { field: Int }
+
+type Obj implements A B { field: Int }
+
+type Obj implements A && B { field: Int }
diff --git a/crates/apollo-parser/test_data/parser/err/0050_invalid_implements_list.txt b/crates/apollo-parser/test_data/parser/err/0050_invalid_implements_list.txt
new file mode 100644
index 0000000..6969c0b
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0050_invalid_implements_list.txt
@@ -0,0 +1,98 @@
+- DOCUMENT@0..122
+    - OBJECT_TYPE_DEFINITION@0..38
+        - type_KW@0..4 "type"
+        - WHITESPACE@4..5 " "
+        - NAME@5..8
+            - IDENT@5..8 "Obj"
+        - WHITESPACE@8..9 " "
+        - IMPLEMENTS_INTERFACES@9..23
+            - implements_KW@9..19 "implements"
+            - WHITESPACE@19..20 " "
+            - NAMED_TYPE@20..21
+                - NAME@20..21
+                    - IDENT@20..21 "A"
+            - WHITESPACE@21..22 " "
+            - AMP@22..23 "&"
+        - WHITESPACE@23..24 " "
+        - FIELDS_DEFINITION@24..38
+            - L_CURLY@24..25 "{"
+            - WHITESPACE@25..26 " "
+            - FIELD_DEFINITION@26..36
+                - NAME@26..31
+                    - IDENT@26..31 "field"
+                - COLON@31..32 ":"
+                - WHITESPACE@32..33 " "
+                - NAMED_TYPE@33..36
+                    - NAME@33..36
+                        - IDENT@33..36 "Int"
+            - WHITESPACE@36..37 " "
+            - R_CURLY@37..38 "}"
+    - WHITESPACE@38..40 "\n\n"
+    - OBJECT_TYPE_DEFINITION@40..61
+        - type_KW@40..44 "type"
+        - WHITESPACE@44..45 " "
+        - NAME@45..48
+            - IDENT@45..48 "Obj"
+        - WHITESPACE@48..49 " "
+        - IMPLEMENTS_INTERFACES@49..61
+            - implements_KW@49..59 "implements"
+            - WHITESPACE@59..60 " "
+            - NAMED_TYPE@60..61
+                - NAME@60..61
+                    - IDENT@60..61 "A"
+    - WHITESPACE@61..62 " "
+    - ERROR@62..63 "B"
+    - WHITESPACE@63..64 " "
+    - OPERATION_DEFINITION@64..78
+        - SELECTION_SET@64..78
+            - L_CURLY@64..65 "{"
+            - WHITESPACE@65..66 " "
+            - FIELD@66..76
+                - ALIAS@66..72
+                    - NAME@66..71
+                        - IDENT@66..71 "field"
+                    - COLON@71..72 ":"
+                - WHITESPACE@72..73 " "
+                - NAME@73..76
+                    - IDENT@73..76 "Int"
+            - WHITESPACE@76..77 " "
+            - R_CURLY@77..78 "}"
+    - WHITESPACE@78..80 "\n\n"
+    - OBJECT_TYPE_DEFINITION@80..121
+        - type_KW@80..84 "type"
+        - WHITESPACE@84..85 " "
+        - NAME@85..88
+            - IDENT@85..88 "Obj"
+        - WHITESPACE@88..89 " "
+        - IMPLEMENTS_INTERFACES@89..106
+            - implements_KW@89..99 "implements"
+            - WHITESPACE@99..100 " "
+            - NAMED_TYPE@100..101
+                - NAME@100..101
+                    - IDENT@100..101 "A"
+            - WHITESPACE@101..102 " "
+            - AMP@102..103 "&"
+            - AMP@103..104 "&"
+            - WHITESPACE@104..105 " "
+            - NAMED_TYPE@105..106
+                - NAME@105..106
+                    - IDENT@105..106 "B"
+        - WHITESPACE@106..107 " "
+        - FIELDS_DEFINITION@107..121
+            - L_CURLY@107..108 "{"
+            - WHITESPACE@108..109 " "
+            - FIELD_DEFINITION@109..119
+                - NAME@109..114
+                    - IDENT@109..114 "field"
+                - COLON@114..115 ":"
+                - WHITESPACE@115..116 " "
+                - NAMED_TYPE@116..119
+                    - NAME@116..119
+                        - IDENT@116..119 "Int"
+            - WHITESPACE@119..120 " "
+            - R_CURLY@120..121 "}"
+    - WHITESPACE@121..122 "\n"
+- ERROR@24:25 "expected an Interface name" {
+- ERROR@62:63 "expected definition" B
+- ERROR@103:104 "expected an Interface name" &
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0051_union_with_invalid_members_list.graphql b/crates/apollo-parser/test_data/parser/err/0051_union_with_invalid_members_list.graphql
new file mode 100644
index 0000000..21de319
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0051_union_with_invalid_members_list.graphql
@@ -0,0 +1,8 @@
+"Missing separator"
+union SearchResult = Photo Person
+
+"Double separator"
+union SearchResult2 = Photo || Person
+
+"Dangling separator"
+union SearchResult3 = | Photo | Person |
diff --git a/crates/apollo-parser/test_data/parser/err/0051_union_with_invalid_members_list.txt b/crates/apollo-parser/test_data/parser/err/0051_union_with_invalid_members_list.txt
new file mode 100644
index 0000000..0de0487
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0051_union_with_invalid_members_list.txt
@@ -0,0 +1,75 @@
+- DOCUMENT@0..175
+    - UNION_TYPE_DEFINITION@0..46
+        - DESCRIPTION@0..19
+            - STRING_VALUE@0..19
+                - STRING@0..19 "\"Missing separator\""
+        - WHITESPACE@19..20 "\n"
+        - union_KW@20..25 "union"
+        - WHITESPACE@25..26 " "
+        - NAME@26..38
+            - IDENT@26..38 "SearchResult"
+        - WHITESPACE@38..39 " "
+        - UNION_MEMBER_TYPES@39..46
+            - EQ@39..40 "="
+            - WHITESPACE@40..41 " "
+            - NAMED_TYPE@41..46
+                - NAME@41..46
+                    - IDENT@41..46 "Photo"
+    - WHITESPACE@46..47 " "
+    - ERROR@47..53 "Person"
+    - WHITESPACE@53..55 "\n\n"
+    - UNION_TYPE_DEFINITION@55..111
+        - DESCRIPTION@55..73
+            - STRING_VALUE@55..73
+                - STRING@55..73 "\"Double separator\""
+        - WHITESPACE@73..74 "\n"
+        - union_KW@74..79 "union"
+        - WHITESPACE@79..80 " "
+        - NAME@80..93
+            - IDENT@80..93 "SearchResult2"
+        - WHITESPACE@93..94 " "
+        - UNION_MEMBER_TYPES@94..111
+            - EQ@94..95 "="
+            - WHITESPACE@95..96 " "
+            - NAMED_TYPE@96..101
+                - NAME@96..101
+                    - IDENT@96..101 "Photo"
+            - WHITESPACE@101..102 " "
+            - PIPE@102..103 "|"
+            - PIPE@103..104 "|"
+            - WHITESPACE@104..105 " "
+            - NAMED_TYPE@105..111
+                - NAME@105..111
+                    - IDENT@105..111 "Person"
+    - WHITESPACE@111..113 "\n\n"
+    - UNION_TYPE_DEFINITION@113..174
+        - DESCRIPTION@113..133
+            - STRING_VALUE@113..133
+                - STRING@113..133 "\"Dangling separator\""
+        - WHITESPACE@133..134 "\n"
+        - union_KW@134..139 "union"
+        - WHITESPACE@139..140 " "
+        - NAME@140..153
+            - IDENT@140..153 "SearchResult3"
+        - WHITESPACE@153..154 " "
+        - UNION_MEMBER_TYPES@154..174
+            - EQ@154..155 "="
+            - WHITESPACE@155..156 " "
+            - PIPE@156..157 "|"
+            - WHITESPACE@157..158 " "
+            - NAMED_TYPE@158..163
+                - NAME@158..163
+                    - IDENT@158..163 "Photo"
+            - WHITESPACE@163..164 " "
+            - PIPE@164..165 "|"
+            - WHITESPACE@165..166 " "
+            - NAMED_TYPE@166..172
+                - NAME@166..172
+                    - IDENT@166..172 "Person"
+            - WHITESPACE@172..173 " "
+            - PIPE@173..174 "|"
+    - WHITESPACE@174..175 "\n"
+- ERROR@47:53 "expected definition" Person
+- ERROR@103:104 "expected Union Member Type" |
+- ERROR@175:175 "expected Union Member Type" EOF
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0008_directive_definition_with_arguments.txt b/crates/apollo-parser/test_data/parser/ok/0008_directive_definition_with_arguments.txt
index 2b9b24d..8867016 100644
--- a/crates/apollo-parser/test_data/parser/ok/0008_directive_definition_with_arguments.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0008_directive_definition_with_arguments.txt
@@ -37,4 +37,4 @@
             - WHITESPACE@66..67 " "
             - DIRECTIVE_LOCATION@67..75
                 - MUTATION_KW@67..75 "MUTATION"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0009_directive_definition_repeatable.txt b/crates/apollo-parser/test_data/parser/ok/0009_directive_definition_repeatable.txt
index 568df33..da99642 100644
--- a/crates/apollo-parser/test_data/parser/ok/0009_directive_definition_repeatable.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0009_directive_definition_repeatable.txt
@@ -39,4 +39,4 @@
             - WHITESPACE@77..78 " "
             - DIRECTIVE_LOCATION@78..86
                 - MUTATION_KW@78..86 "MUTATION"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0010_enum_type_definition.txt b/crates/apollo-parser/test_data/parser/ok/0010_enum_type_definition.txt
index c930d21..5e30e51 100644
--- a/crates/apollo-parser/test_data/parser/ok/0010_enum_type_definition.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0010_enum_type_definition.txt
@@ -39,4 +39,4 @@
                         - IDENT@91..95 "WEST"
             - WHITESPACE@95..96 "\n"
             - R_CURLY@96..97 "}"
-recursion limit: 500, high: 4
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0011_enum_type_extension.txt b/crates/apollo-parser/test_data/parser/ok/0011_enum_type_extension.txt
index 1284f19..13a5cbc 100644
--- a/crates/apollo-parser/test_data/parser/ok/0011_enum_type_extension.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0011_enum_type_extension.txt
@@ -27,4 +27,4 @@
                         - IDENT@47..51 "WEST"
             - WHITESPACE@51..52 "\n"
             - R_CURLY@52..53 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0012_fragment_definition.txt b/crates/apollo-parser/test_data/parser/ok/0012_fragment_definition.txt
index 664236e..2148eaf 100644
--- a/crates/apollo-parser/test_data/parser/ok/0012_fragment_definition.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0012_fragment_definition.txt
@@ -45,4 +45,4 @@
                     - R_PAREN@80..81 ")"
             - WHITESPACE@81..82 "\n"
             - R_CURLY@82..83 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0014_input_definition.txt b/crates/apollo-parser/test_data/parser/ok/0014_input_definition.txt
index d50f59e..4f59aec 100644
--- a/crates/apollo-parser/test_data/parser/ok/0014_input_definition.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0014_input_definition.txt
@@ -29,4 +29,4 @@
                     - BANG@51..52 "!"
             - WHITESPACE@52..53 "\n"
             - R_CURLY@53..54 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0015_input_extension.txt b/crates/apollo-parser/test_data/parser/ok/0015_input_extension.txt
index 0572648..50c4590 100644
--- a/crates/apollo-parser/test_data/parser/ok/0015_input_extension.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0015_input_extension.txt
@@ -26,4 +26,4 @@
                         - IDENT@47..53 "String"
             - WHITESPACE@53..54 "\n"
             - R_CURLY@54..55 "}"
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0019_object_type_extension.txt b/crates/apollo-parser/test_data/parser/ok/0019_object_type_extension.txt
index d8f8d68..62af7bc 100644
--- a/crates/apollo-parser/test_data/parser/ok/0019_object_type_extension.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0019_object_type_extension.txt
@@ -51,4 +51,4 @@
                         - IDENT@93..96 "Url"
             - WHITESPACE@96..97 "\n"
             - R_CURLY@97..98 "}"
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0021_operation_type_definition_with_arguments.txt b/crates/apollo-parser/test_data/parser/ok/0021_operation_type_definition_with_arguments.txt
index 74b70b9..f21a529 100644
--- a/crates/apollo-parser/test_data/parser/ok/0021_operation_type_definition_with_arguments.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0021_operation_type_definition_with_arguments.txt
@@ -41,4 +41,4 @@
                     - IDENT@65..70 "treat"
             - WHITESPACE@70..71 "\n"
             - R_CURLY@71..72 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0022_operation_type_definition_with_arguments_and_directives.txt b/crates/apollo-parser/test_data/parser/ok/0022_operation_type_definition_with_arguments_and_directives.txt
index f64aa0b..136e3c7 100644
--- a/crates/apollo-parser/test_data/parser/ok/0022_operation_type_definition_with_arguments_and_directives.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0022_operation_type_definition_with_arguments_and_directives.txt
@@ -53,4 +53,4 @@
                     - IDENT@86..91 "treat"
             - WHITESPACE@91..92 "\n"
             - R_CURLY@92..93 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.graphql b/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.graphql
index 4ba620d..40f9173 100644
--- a/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.graphql
+++ b/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.graphql
@@ -1,1 +1,5 @@
-union SearchResult = Photo | Person
\ No newline at end of file
+union SearchResult = Photo | Person
+
+union MultiLine =
+  | Photo
+  | Person
diff --git a/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.txt b/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.txt
index 617180e..6c0778f 100644
--- a/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0027_union_type_definition.txt
@@ -1,4 +1,4 @@
-- DOCUMENT@0..35
+- DOCUMENT@0..76
     - UNION_TYPE_DEFINITION@0..35
         - union_KW@0..5 "union"
         - WHITESPACE@5..6 " "
@@ -17,4 +17,26 @@
             - NAMED_TYPE@29..35
                 - NAME@29..35
                     - IDENT@29..35 "Person"
-recursion limit: 500, high: 2
\ No newline at end of file
+    - WHITESPACE@35..37 "\n\n"
+    - UNION_TYPE_DEFINITION@37..75
+        - union_KW@37..42 "union"
+        - WHITESPACE@42..43 " "
+        - NAME@43..52
+            - IDENT@43..52 "MultiLine"
+        - WHITESPACE@52..53 " "
+        - UNION_MEMBER_TYPES@53..75
+            - EQ@53..54 "="
+            - WHITESPACE@54..57 "\n  "
+            - PIPE@57..58 "|"
+            - WHITESPACE@58..59 " "
+            - NAMED_TYPE@59..64
+                - NAME@59..64
+                    - IDENT@59..64 "Photo"
+            - WHITESPACE@64..67 "\n  "
+            - PIPE@67..68 "|"
+            - WHITESPACE@68..69 " "
+            - NAMED_TYPE@69..75
+                - NAME@69..75
+                    - IDENT@69..75 "Person"
+    - WHITESPACE@75..76 "\n"
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0028_union_type_definition_followed_by_object_definition.txt b/crates/apollo-parser/test_data/parser/ok/0028_union_type_definition_followed_by_object_definition.txt
index 7b2a451..6d6dec9 100644
--- a/crates/apollo-parser/test_data/parser/ok/0028_union_type_definition_followed_by_object_definition.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0028_union_type_definition_followed_by_object_definition.txt
@@ -37,4 +37,4 @@
                         - IDENT@60..63 "Int"
             - WHITESPACE@63..64 "\n"
             - R_CURLY@64..65 "}"
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0029_union_type_extension.txt b/crates/apollo-parser/test_data/parser/ok/0029_union_type_extension.txt
index c830a60..43edf76 100644
--- a/crates/apollo-parser/test_data/parser/ok/0029_union_type_extension.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0029_union_type_extension.txt
@@ -25,4 +25,4 @@
             - NAMED_TYPE@48..54
                 - NAME@48..54
                     - IDENT@48..54 "Person"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0030_values.txt b/crates/apollo-parser/test_data/parser/ok/0030_values.txt
index 230e0be..7f48144 100644
--- a/crates/apollo-parser/test_data/parser/ok/0030_values.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0030_values.txt
@@ -83,4 +83,4 @@
                     - R_PAREN@111..112 ")"
             - WHITESPACE@112..113 "\n"
             - R_CURLY@113..114 "}"
-recursion limit: 500, high: 8
\ No newline at end of file
+recursion limit: 500, high: 2
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0031_variables_with_default.txt b/crates/apollo-parser/test_data/parser/ok/0031_variables_with_default.txt
index ac550db..b0cf83f 100644
--- a/crates/apollo-parser/test_data/parser/ok/0031_variables_with_default.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0031_variables_with_default.txt
@@ -50,4 +50,4 @@
                     - IDENT@66..72 "animal"
             - WHITESPACE@72..73 "\n"
             - R_CURLY@73..74 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0032_supergraph.txt b/crates/apollo-parser/test_data/parser/ok/0032_supergraph.txt
index d457e54..9c976f1 100644
--- a/crates/apollo-parser/test_data/parser/ok/0032_supergraph.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0032_supergraph.txt
@@ -4184,4 +4184,4 @@
                         - IDENT@7486..7492 "String"
             - WHITESPACE@7492..7493 "\n"
             - R_CURLY@7493..7494 "}"
-recursion limit: 500, high: 7
\ No newline at end of file
+recursion limit: 500, high: 2
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0033_directive_on_argument_definition.txt b/crates/apollo-parser/test_data/parser/ok/0033_directive_on_argument_definition.txt
index 7842e29..597a5fa 100644
--- a/crates/apollo-parser/test_data/parser/ok/0033_directive_on_argument_definition.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0033_directive_on_argument_definition.txt
@@ -45,4 +45,4 @@
                         - IDENT@85..89 "User"
             - WHITESPACE@89..90 "\n"
             - R_CURLY@90..91 "}"
-recursion limit: 500, high: 1
\ No newline at end of file
+recursion limit: 500, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0036_parses_variable_definition_with_list_type.txt b/crates/apollo-parser/test_data/parser/ok/0036_parses_variable_definition_with_list_type.txt
index 2723589..2dca650 100644
--- a/crates/apollo-parser/test_data/parser/ok/0036_parses_variable_definition_with_list_type.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0036_parses_variable_definition_with_list_type.txt
@@ -44,4 +44,4 @@
                     - R_PAREN@57..58 ")"
             - WHITESPACE@58..59 "\n"
             - R_CURLY@59..60 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0039_variable_with_directives.txt b/crates/apollo-parser/test_data/parser/ok/0039_variable_with_directives.txt
index 3dd4037..b02c9c3 100644
--- a/crates/apollo-parser/test_data/parser/ok/0039_variable_with_directives.txt
+++ b/crates/apollo-parser/test_data/parser/ok/0039_variable_with_directives.txt
@@ -66,4 +66,4 @@
                     - IDENT@103..109 "animal"
             - WHITESPACE@109..110 "\n"
             - R_CURLY@110..111 "}"
-recursion limit: 500, high: 2
\ No newline at end of file
+recursion limit: 500, high: 1
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/ok/0041_implements_list.graphql b/crates/apollo-parser/test_data/parser/ok/0041_implements_list.graphql
new file mode 100644
index 0000000..f9c66ad
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/ok/0041_implements_list.graphql
@@ -0,0 +1,12 @@
+"Just one interface"
+type One implements A { field: Int! }
+
+"Several interfaces"
+type Two implements A & B & C { field: Int! }
+
+"&-prefixed"
+type Three implements
+  & A
+  & B
+  & C
+{ field: Int! }
diff --git a/crates/apollo-parser/test_data/parser/ok/0041_implements_list.txt b/crates/apollo-parser/test_data/parser/ok/0041_implements_list.txt
new file mode 100644
index 0000000..2988567
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/ok/0041_implements_list.txt
@@ -0,0 +1,127 @@
+- DOCUMENT@0..197
+    - OBJECT_TYPE_DEFINITION@0..58
+        - DESCRIPTION@0..20
+            - STRING_VALUE@0..20
+                - STRING@0..20 "\"Just one interface\""
+        - WHITESPACE@20..21 "\n"
+        - type_KW@21..25 "type"
+        - WHITESPACE@25..26 " "
+        - NAME@26..29
+            - IDENT@26..29 "One"
+        - WHITESPACE@29..30 " "
+        - IMPLEMENTS_INTERFACES@30..42
+            - implements_KW@30..40 "implements"
+            - WHITESPACE@40..41 " "
+            - NAMED_TYPE@41..42
+                - NAME@41..42
+                    - IDENT@41..42 "A"
+        - WHITESPACE@42..43 " "
+        - FIELDS_DEFINITION@43..58
+            - L_CURLY@43..44 "{"
+            - WHITESPACE@44..45 " "
+            - FIELD_DEFINITION@45..56
+                - NAME@45..50
+                    - IDENT@45..50 "field"
+                - COLON@50..51 ":"
+                - WHITESPACE@51..52 " "
+                - NON_NULL_TYPE@52..56
+                    - NAMED_TYPE@52..55
+                        - NAME@52..55
+                            - IDENT@52..55 "Int"
+                    - BANG@55..56 "!"
+            - WHITESPACE@56..57 " "
+            - R_CURLY@57..58 "}"
+    - WHITESPACE@58..60 "\n\n"
+    - OBJECT_TYPE_DEFINITION@60..126
+        - DESCRIPTION@60..80
+            - STRING_VALUE@60..80
+                - STRING@60..80 "\"Several interfaces\""
+        - WHITESPACE@80..81 "\n"
+        - type_KW@81..85 "type"
+        - WHITESPACE@85..86 " "
+        - NAME@86..89
+            - IDENT@86..89 "Two"
+        - WHITESPACE@89..90 " "
+        - IMPLEMENTS_INTERFACES@90..110
+            - implements_KW@90..100 "implements"
+            - WHITESPACE@100..101 " "
+            - NAMED_TYPE@101..102
+                - NAME@101..102
+                    - IDENT@101..102 "A"
+            - WHITESPACE@102..103 " "
+            - AMP@103..104 "&"
+            - WHITESPACE@104..105 " "
+            - NAMED_TYPE@105..106
+                - NAME@105..106
+                    - IDENT@105..106 "B"
+            - WHITESPACE@106..107 " "
+            - AMP@107..108 "&"
+            - WHITESPACE@108..109 " "
+            - NAMED_TYPE@109..110
+                - NAME@109..110
+                    - IDENT@109..110 "C"
+        - WHITESPACE@110..111 " "
+        - FIELDS_DEFINITION@111..126
+            - L_CURLY@111..112 "{"
+            - WHITESPACE@112..113 " "
+            - FIELD_DEFINITION@113..124
+                - NAME@113..118
+                    - IDENT@113..118 "field"
+                - COLON@118..119 ":"
+                - WHITESPACE@119..120 " "
+                - NON_NULL_TYPE@120..124
+                    - NAMED_TYPE@120..123
+                        - NAME@120..123
+                            - IDENT@120..123 "Int"
+                    - BANG@123..124 "!"
+            - WHITESPACE@124..125 " "
+            - R_CURLY@125..126 "}"
+    - WHITESPACE@126..128 "\n\n"
+    - OBJECT_TYPE_DEFINITION@128..196
+        - DESCRIPTION@128..140
+            - STRING_VALUE@128..140
+                - STRING@128..140 "\"&-prefixed\""
+        - WHITESPACE@140..141 "\n"
+        - type_KW@141..145 "type"
+        - WHITESPACE@145..146 " "
+        - NAME@146..151
+            - IDENT@146..151 "Three"
+        - WHITESPACE@151..152 " "
+        - IMPLEMENTS_INTERFACES@152..180
+            - implements_KW@152..162 "implements"
+            - WHITESPACE@162..165 "\n  "
+            - AMP@165..166 "&"
+            - WHITESPACE@166..167 " "
+            - NAMED_TYPE@167..168
+                - NAME@167..168
+                    - IDENT@167..168 "A"
+            - WHITESPACE@168..171 "\n  "
+            - AMP@171..172 "&"
+            - WHITESPACE@172..173 " "
+            - NAMED_TYPE@173..174
+                - NAME@173..174
+                    - IDENT@173..174 "B"
+            - WHITESPACE@174..177 "\n  "
+            - AMP@177..178 "&"
+            - WHITESPACE@178..179 " "
+            - NAMED_TYPE@179..180
+                - NAME@179..180
+                    - IDENT@179..180 "C"
+        - WHITESPACE@180..181 "\n"
+        - FIELDS_DEFINITION@181..196
+            - L_CURLY@181..182 "{"
+            - WHITESPACE@182..183 " "
+            - FIELD_DEFINITION@183..194
+                - NAME@183..188
+                    - IDENT@183..188 "field"
+                - COLON@188..189 ":"
+                - WHITESPACE@189..190 " "
+                - NON_NULL_TYPE@190..194
+                    - NAMED_TYPE@190..193
+                        - NAME@190..193
+                            - IDENT@190..193 "Int"
+                    - BANG@193..194 "!"
+            - WHITESPACE@194..195 " "
+            - R_CURLY@195..196 "}"
+    - WHITESPACE@196..197 "\n"
+recursion limit: 500, high: 0
\ No newline at end of file