Repository: apollographql/apollo-rs
Pull request #381 (2022-12-08 10:29:27)

fix(compiler,parser): do not panic during HIR creation
Ref #256, probably not a *full* fix yet, but that issue is quite broad.

I set out to simply change every panic in HIR creation to returning a `None`, but while writing tests I triggered panics in the parser, so I tried to address those too.

- This removes some panics from type name parsing. For example, `field: []` does not panic anymore. Instead it produces a syntax error and an incomplete List type.
- Fixes the parser completely bailing out of parsing a list of field definitions when encountering a syntax error in one field.
   ```graphql
   type A {
      fieldA: [] # ← has error, missing item type
      fieldB: Int
      fieldC: Int
   }
   ```
   Previously fieldB and fieldC would not be parsed, now they are.
- Replaces panics when creating the HIR with `Option`s. This means that AST nodes with errors are *ignored* by the HIR. *Most* cases where AST nodes are missing already cause syntax errors in the parser, so users can still tell that something is wrong, and the validation etc. should not be expected to be perfect if there are syntax errors anyways.

   There are some cases where we ignore more of the tree than we need to. eg. if an object type definition is missing a name, we ignore the entire thing, even though we could still do useful validation on the directives and fields. We could add that stuff in the future by allowing for certain optional or invalid values in the HIR. As it is, silently ignoring those cases is still an improvement over panicking, I think (+ it's not actually silent because you do get a parse error already).


Patch:
diff --git a/crates/apollo-compiler/src/database/hir.rs b/crates/apollo-compiler/src/database/hir.rs
index a67e20e..d56d746 100644
--- a/crates/apollo-compiler/src/database/hir.rs
+++ b/crates/apollo-compiler/src/database/hir.rs
@@ -2359,4 +2359,26 @@ mod tests {
         assert_eq!(default_values[2], "98765432109876540000");
         assert_eq!(default_values[3], "98765432109876540000");
     }
+
+    #[test]
+    fn syntax_errors() {
+        let compiler = ApolloCompiler::new(
+            "type Person {
+                id: ID!
+                name: String
+                appearedIn: [Film]s
+                directed: [Film]
+            }",
+        );
+        let person = compiler
+            .db
+            .find_object_type_by_name("Person".into())
+            .unwrap();
+        let hir_field_names: Vec<_> = person
+            .fields_definition
+            .iter()
+            .map(|field| field.name())
+            .collect();
+        assert_eq!(hir_field_names, ["id", "name", "appearedIn", "directed"]);
+    }
 }
diff --git a/crates/apollo-compiler/src/database/hir_db.rs b/crates/apollo-compiler/src/database/hir_db.rs
index 2609f07..7699210 100644
--- a/crates/apollo-compiler/src/database/hir_db.rs
+++ b/crates/apollo-compiler/src/database/hir_db.rs
@@ -5,6 +5,9 @@ use uuid::Uuid;
 
 use crate::{hir::*, AstDatabase, InputDatabase};
 
+// HIR creators *ignore* missing data entirely. *Only* missing data
+// as a result of parser errors should be ignored.
+
 #[salsa::query_group(HirStorage)]
 pub trait HirDatabase: InputDatabase + AstDatabase {
     // fn definitions(&self) -> Arc<Vec<ast::Definition>>;
@@ -184,7 +187,7 @@ fn fragments(db: &dyn HirDatabase) -> Arc<Vec<FragmentDefinition>> {
         .into_iter()
         .filter_map(|definition| match definition {
             ast::Definition::FragmentDefinition(fragment_def) => {
-                Some(fragment_definition(db, fragment_def))
+                fragment_definition(db, fragment_def)
             }
             _ => None,
         })
@@ -227,7 +230,7 @@ fn object_types(db: &dyn HirDatabase) -> Arc<Vec<ObjectTypeDefinition>> {
         .definitions()
         .into_iter()
         .filter_map(|definition| match definition {
-            ast::Definition::ObjectTypeDefinition(obj_def) => Some(object_type_definition(obj_def)),
+            ast::Definition::ObjectTypeDefinition(obj_def) => object_type_definition(obj_def),
             _ => None,
         })
         .collect();
@@ -241,9 +244,7 @@ fn scalars(db: &dyn HirDatabase) -> Arc<Vec<ScalarTypeDefinition>> {
         .definitions()
         .into_iter()
         .filter_map(|definition| match definition {
-            ast::Definition::ScalarTypeDefinition(scalar_def) => {
-                Some(scalar_definition(scalar_def))
-            }
+            ast::Definition::ScalarTypeDefinition(scalar_def) => scalar_definition(scalar_def),
             _ => None,
         })
         .collect();
@@ -259,7 +260,7 @@ fn enums(db: &dyn HirDatabase) -> Arc<Vec<EnumTypeDefinition>> {
         .definitions()
         .into_iter()
         .filter_map(|definition| match definition {
-            ast::Definition::EnumTypeDefinition(enum_def) => Some(enum_definition(enum_def)),
+            ast::Definition::EnumTypeDefinition(enum_def) => enum_definition(enum_def),
             _ => None,
         })
         .collect();
@@ -273,7 +274,7 @@ fn unions(db: &dyn HirDatabase) -> Arc<Vec<UnionTypeDefinition>> {
         .definitions()
         .into_iter()
         .filter_map(|definition| match definition {
-            ast::Definition::UnionTypeDefinition(union_def) => Some(union_definition(union_def)),
+            ast::Definition::UnionTypeDefinition(union_def) => union_definition(union_def),
             _ => None,
         })
         .collect();
@@ -288,7 +289,7 @@ fn interfaces(db: &dyn HirDatabase) -> Arc<Vec<InterfaceTypeDefinition>> {
         .into_iter()
         .filter_map(|definition| match definition {
             ast::Definition::InterfaceTypeDefinition(interface_def) => {
-                Some(interface_definition(interface_def))
+                interface_definition(interface_def)
             }
             _ => None,
         })
@@ -304,7 +305,7 @@ fn directive_definitions(db: &dyn HirDatabase) -> Arc<Vec<DirectiveDefinition>>
         .into_iter()
         .filter_map(|definition| match definition {
             ast::Definition::DirectiveDefinition(directive_def) => {
-                Some(directive_definition(directive_def))
+                directive_definition(directive_def)
             }
             _ => None,
         })
@@ -323,7 +324,7 @@ fn input_objects(db: &dyn HirDatabase) -> Arc<Vec<InputObjectTypeDefinition>> {
         .into_iter()
         .filter_map(|definition| match definition {
             ast::Definition::InputObjectTypeDefinition(input_obj) => {
-                Some(input_object_definition(input_obj))
+                input_object_definition(input_obj)
             }
             _ => None,
         })
@@ -371,20 +372,12 @@ fn operation_definition(
 fn fragment_definition(
     db: &dyn HirDatabase,
     fragment_def: ast::FragmentDefinition,
-) -> FragmentDefinition {
-    let name = name(
-        fragment_def
-            .fragment_name()
-            .expect("Fragment Definition must have a name")
-            .name(),
-    );
+) -> Option<FragmentDefinition> {
+    let name = name(fragment_def.fragment_name()?.name())?;
     let type_condition = fragment_def
-        .type_condition()
-        .expect("Fragment Definition must have a type condition")
-        .named_type()
-        .expect("Type Condition must have a name")
-        .name()
-        .expect("Name must have text")
+        .type_condition()?
+        .named_type()?
+        .name()?
         .text()
         .to_string();
     let selection_set = selection_set(
@@ -395,14 +388,14 @@ fn fragment_definition(
     let directives = directives(fragment_def.directives());
     let ast_ptr = SyntaxNodePtr::new(fragment_def.syntax());
 
-    FragmentDefinition {
+    Some(FragmentDefinition {
         id: Uuid::new_v4(),
         name,
         type_condition,
         selection_set,
         directives,
         ast_ptr,
-    }
+    })
 }
 
 fn schema_definition(schema_def: ast::SchemaDefinition) -> SchemaDefinition {
@@ -420,16 +413,18 @@ fn schema_definition(schema_def: ast::SchemaDefinition) -> SchemaDefinition {
     }
 }
 
-fn object_type_definition(obj_def: ast::ObjectTypeDefinition) -> ObjectTypeDefinition {
+fn object_type_definition(obj_def: ast::ObjectTypeDefinition) -> Option<ObjectTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(obj_def.description());
-    let name = name(obj_def.name());
+    let name = name(obj_def.name())?;
     let implements_interfaces = implements_interfaces(obj_def.implements_interfaces());
     let directives = directives(obj_def.directives());
     let fields_definition = fields_definition(obj_def.fields_definition());
     let ast_ptr = SyntaxNodePtr::new(obj_def.syntax());
 
-    ObjectTypeDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(ObjectTypeDefinition {
         id,
         description,
         name,
@@ -437,42 +432,46 @@ fn object_type_definition(obj_def: ast::ObjectTypeDefinition) -> ObjectTypeDefin
         directives,
         fields_definition,
         ast_ptr,
-    }
+    })
 }
 
-fn scalar_definition(scalar_def: ast::ScalarTypeDefinition) -> ScalarTypeDefinition {
+fn scalar_definition(scalar_def: ast::ScalarTypeDefinition) -> Option<ScalarTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(scalar_def.description());
-    let name = name(scalar_def.name());
+    let name = name(scalar_def.name())?;
     let directives = directives(scalar_def.directives());
     let ast_ptr = SyntaxNodePtr::new(scalar_def.syntax());
 
-    ScalarTypeDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(ScalarTypeDefinition {
         id,
         description,
         name,
         directives,
         ast_ptr: Some(ast_ptr),
         built_in: false,
-    }
+    })
 }
 
-fn enum_definition(enum_def: ast::EnumTypeDefinition) -> EnumTypeDefinition {
+fn enum_definition(enum_def: ast::EnumTypeDefinition) -> Option<EnumTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(enum_def.description());
-    let name = name(enum_def.name());
+    let name = name(enum_def.name())?;
     let directives = directives(enum_def.directives());
     let enum_values_definition = enum_values_definition(enum_def.enum_values_definition());
     let ast_ptr = SyntaxNodePtr::new(enum_def.syntax());
 
-    EnumTypeDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(EnumTypeDefinition {
         id,
         description,
         name,
         directives,
         enum_values_definition,
         ast_ptr,
-    }
+    })
 }
 
 fn enum_values_definition(
@@ -483,7 +482,7 @@ fn enum_values_definition(
             let enum_values = enum_values
                 .enum_value_definitions()
                 .into_iter()
-                .map(enum_value_definition)
+                .filter_map(enum_value_definition)
                 .collect();
             Arc::new(enum_values)
         }
@@ -491,36 +490,38 @@ fn enum_values_definition(
     }
 }
 
-fn enum_value_definition(enum_value_def: ast::EnumValueDefinition) -> EnumValueDefinition {
+fn enum_value_definition(enum_value_def: ast::EnumValueDefinition) -> Option<EnumValueDefinition> {
     let description = description(enum_value_def.description());
-    let enum_value = enum_value(enum_value_def.enum_value());
+    let enum_value = enum_value(enum_value_def.enum_value())?;
     let directives = directives(enum_value_def.directives());
     let ast_ptr = SyntaxNodePtr::new(enum_value_def.syntax());
 
-    EnumValueDefinition {
+    Some(EnumValueDefinition {
         description,
         enum_value,
         directives,
         ast_ptr,
-    }
+    })
 }
 
-fn union_definition(union_def: ast::UnionTypeDefinition) -> UnionTypeDefinition {
+fn union_definition(union_def: ast::UnionTypeDefinition) -> Option<UnionTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(union_def.description());
-    let name = name(union_def.name());
+    let name = name(union_def.name())?;
     let directives = directives(union_def.directives());
     let union_members = union_members(union_def.union_member_types());
     let ast_ptr = SyntaxNodePtr::new(union_def.syntax());
 
-    UnionTypeDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(UnionTypeDefinition {
         id,
         description,
         name,
         directives,
         union_members,
         ast_ptr,
-    }
+    })
 }
 
 fn union_members(union_members: Option<ast::UnionMemberTypes>) -> Arc<Vec<UnionMember>> {
@@ -529,7 +530,7 @@ fn union_members(union_members: Option<ast::UnionMemberTypes>) -> Arc<Vec<UnionM
             let mems = members
                 .named_types()
                 .into_iter()
-                .map(union_member)
+                .filter_map(union_member)
                 .collect();
             Arc::new(mems)
         }
@@ -537,23 +538,27 @@ fn union_members(union_members: Option<ast::UnionMemberTypes>) -> Arc<Vec<UnionM
     }
 }
 
-fn union_member(member: ast::NamedType) -> UnionMember {
-    let name = name(member.name());
+fn union_member(member: ast::NamedType) -> Option<UnionMember> {
+    let name = name(member.name())?;
     let ast_ptr = SyntaxNodePtr::new(member.syntax());
 
-    UnionMember { name, ast_ptr }
+    Some(UnionMember { name, ast_ptr })
 }
 
-fn interface_definition(interface_def: ast::InterfaceTypeDefinition) -> InterfaceTypeDefinition {
+fn interface_definition(
+    interface_def: ast::InterfaceTypeDefinition,
+) -> Option<InterfaceTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(interface_def.description());
-    let name = name(interface_def.name());
+    let name = name(interface_def.name())?;
     let implements_interfaces = implements_interfaces(interface_def.implements_interfaces());
     let directives = directives(interface_def.directives());
     let fields_definition = fields_definition(interface_def.fields_definition());
     let ast_ptr = SyntaxNodePtr::new(interface_def.syntax());
 
-    InterfaceTypeDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(InterfaceTypeDefinition {
         id,
         description,
         name,
@@ -561,18 +566,20 @@ fn interface_definition(interface_def: ast::InterfaceTypeDefinition) -> Interfac
         directives,
         fields_definition,
         ast_ptr,
-    }
+    })
 }
 
-fn directive_definition(directive_def: ast::DirectiveDefinition) -> DirectiveDefinition {
-    let name = name(directive_def.name());
+fn directive_definition(directive_def: ast::DirectiveDefinition) -> Option<DirectiveDefinition> {
+    let name = name(directive_def.name())?;
     let description = description(directive_def.description());
     let arguments = arguments_definition(directive_def.arguments_definition());
     let repeatable = directive_def.repeatable_token().is_some();
     let directive_locations = directive_locations(directive_def.directive_locations());
     let ast_ptr = SyntaxNodePtr::new(directive_def.syntax());
 
-    DirectiveDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(DirectiveDefinition {
         id: Uuid::new_v4(),
         description,
         name,
@@ -580,25 +587,29 @@ fn directive_definition(directive_def: ast::DirectiveDefinition) -> DirectiveDef
         repeatable,
         directive_locations,
         ast_ptr: Some(ast_ptr),
-    }
+    })
 }
 
-fn input_object_definition(input_obj: ast::InputObjectTypeDefinition) -> InputObjectTypeDefinition {
+fn input_object_definition(
+    input_obj: ast::InputObjectTypeDefinition,
+) -> Option<InputObjectTypeDefinition> {
     let id = Uuid::new_v4();
     let description = description(input_obj.description());
-    let name = name(input_obj.name());
+    let name = name(input_obj.name())?;
     let directives = directives(input_obj.directives());
     let input_fields_definition = input_fields_definition(input_obj.input_fields_definition());
     let ast_ptr = SyntaxNodePtr::new(input_obj.syntax());
 
-    InputObjectTypeDefinition {
+    // TODO(@goto-bus-stop) when a name is missing on this,
+    // we might still want to produce a HIR node, so we can validate other parts of the definition
+    Some(InputObjectTypeDefinition {
         id,
         description,
         name,
         directives,
         input_fields_definition,
         ast_ptr,
-    }
+    })
 }
 
 fn add_object_type_id_to_schema(db: &dyn HirDatabase) -> Arc<Vec<RootOperationTypeDefinition>> {
@@ -641,12 +652,12 @@ fn implements_interfaces(
         .flat_map(|interfaces| {
             let types: Vec<ImplementsInterface> = interfaces
                 .named_types()
-                .map(|n| {
-                    let name = n.name().expect("Name must have text");
-                    ImplementsInterface {
+                .filter_map(|n| {
+                    let name = n.name()?;
+                    Some(ImplementsInterface {
                         interface: name_hir_node(name),
                         ast_ptr: SyntaxNodePtr::new(n.syntax()),
-                    }
+                    })
                 })
                 .collect();
             types
@@ -663,7 +674,7 @@ fn fields_definition(
         Some(fields_def) => {
             let fields: Vec<FieldDefinition> = fields_def
                 .field_definitions()
-                .map(field_definition)
+                .filter_map(field_definition)
                 .collect();
             Arc::new(fields)
         }
@@ -671,22 +682,22 @@ fn fields_definition(
     }
 }
 
-fn field_definition(field: ast::FieldDefinition) -> FieldDefinition {
+fn field_definition(field: ast::FieldDefinition) -> Option<FieldDefinition> {
     let description = description(field.description());
-    let name = name(field.name());
+    let name = name(field.name())?;
     let arguments = arguments_definition(field.arguments_definition());
-    let ty = ty(field.ty().expect("Field must have a type"));
+    let ty = ty(field.ty()?)?;
     let directives = directives(field.directives());
     let ast_ptr = SyntaxNodePtr::new(field.syntax());
 
-    FieldDefinition {
+    Some(FieldDefinition {
         description,
         name,
         arguments,
         ty,
         directives,
         ast_ptr,
-    }
+    })
 }
 
 fn arguments_definition(
@@ -722,29 +733,29 @@ fn input_value_definitions(
     input_values: AstChildren<ast::InputValueDefinition>,
 ) -> Arc<Vec<InputValueDefinition>> {
     let input_values: Vec<InputValueDefinition> = input_values
-        .map(|input| {
+        .filter_map(|input| {
             let description = description(input.description());
-            let name = name(input.name());
-            let ty = ty(input.ty().expect("Input Definition must have a type"));
+            let name = name(input.name())?;
+            let ty = ty(input.ty()?)?;
             let default_value = default_value(input.default_value());
             let directives = directives(input.directives());
             let ast_ptr = SyntaxNodePtr::new(input.syntax());
 
-            InputValueDefinition {
+            Some(InputValueDefinition {
                 description,
                 name,
                 ty,
                 default_value,
                 directives,
                 ast_ptr: Some(ast_ptr),
-            }
+            })
         })
         .collect();
     Arc::new(input_values)
 }
 
 fn default_value(default_value: Option<ast::DefaultValue>) -> Option<DefaultValue> {
-    default_value.map(|val| value(val.value().expect("Default Value must have a value token")))
+    default_value.and_then(|val| val.value()).and_then(value)
 }
 
 fn root_operation_type_definition(
@@ -752,20 +763,16 @@ fn root_operation_type_definition(
 ) -> Arc<Vec<RootOperationTypeDefinition>> {
     let type_defs: Vec<RootOperationTypeDefinition> = root_type_def
         .into_iter()
-        .map(|ty| {
+        .filter_map(|ty| {
             let operation_type = operation_type(ty.operation_type());
-            let named_type = named_type(
-                ty.named_type()
-                    .expect("Root Operation Type Definition must have Named Type.")
-                    .name(),
-            );
+            let named_type = named_type(ty.named_type()?.name()?);
             let ast_ptr = SyntaxNodePtr::new(ty.syntax());
 
-            RootOperationTypeDefinition {
+            Some(RootOperationTypeDefinition {
                 operation_type,
                 named_type,
                 ast_ptr: Some(ast_ptr),
-            }
+            })
         })
         .collect();
 
@@ -797,7 +804,7 @@ fn variable_definitions(
             let variable_definitions = vars
                 .variable_definitions()
                 .into_iter()
-                .map(variable_definition)
+                .filter_map(variable_definition)
                 .collect();
             Arc::new(variable_definitions)
         }
@@ -805,51 +812,45 @@ fn variable_definitions(
     }
 }
 
-fn variable_definition(var: ast::VariableDefinition) -> VariableDefinition {
-    let name = name(
-        var.variable()
-            .expect("values::Variable Definition must have a variable")
-            .name(),
-    );
+fn variable_definition(var: ast::VariableDefinition) -> Option<VariableDefinition> {
+    let name = name(var.variable()?.name())?;
     let directives = directives(var.directives());
     let default_value = default_value(var.default_value());
-    let ty = ty(var
-        .ty()
-        .expect("values::Variable Definition must have a type"));
+    let ty = ty(var.ty()?)?;
     let ast_ptr = SyntaxNodePtr::new(var.syntax());
 
-    VariableDefinition {
+    Some(VariableDefinition {
         name,
         directives,
         ty,
         default_value,
         ast_ptr,
-    }
+    })
 }
 
-fn ty(ty_: ast::Type) -> Type {
+fn ty(ty_: ast::Type) -> Option<Type> {
     match ty_ {
-        ast::Type::NamedType(name) => named_type(name.name()),
-        ast::Type::ListType(list) => Type::List {
-            ty: Box::new(ty(list.ty().expect("List Type must have a type"))),
+        ast::Type::NamedType(name) => name.name().map(named_type),
+        ast::Type::ListType(list) => Some(Type::List {
+            ty: Box::new(ty(list.ty()?)?),
             ast_ptr: Some(SyntaxNodePtr::new(list.syntax())),
-        },
+        }),
         ast::Type::NonNullType(non_null) => {
             if let Some(n) = non_null.named_type() {
-                let named_type = named_type(n.name());
-                Type::NonNull {
+                let named_type = n.name().map(named_type)?;
+                Some(Type::NonNull {
                     ty: Box::new(named_type),
                     ast_ptr: Some(SyntaxNodePtr::new(n.syntax())),
-                }
+                })
             } else if let Some(list) = non_null.list_type() {
                 let list_type = Type::List {
-                    ty: Box::new(ty(list.ty().expect("List Type must have a type"))),
+                    ty: Box::new(ty(list.ty()?)?),
                     ast_ptr: Some(SyntaxNodePtr::new(list.syntax())),
                 };
-                Type::NonNull {
+                Some(Type::NonNull {
                     ty: Box::new(list_type),
                     ast_ptr: Some(SyntaxNodePtr::new(list.syntax())),
-                }
+                })
             } else {
                 // TODO: parser should have caught an error if there wasn't
                 // either a named type or list type. Figure out a graceful way
@@ -860,9 +861,7 @@ fn ty(ty_: ast::Type) -> Type {
     }
 }
 
-fn named_type(n: Option<ast::Name>) -> Type {
-    let name = n.expect("Named Type must have a name");
-
+fn named_type(name: ast::Name) -> Type {
     Type::Named {
         name: name.text().to_string(),
         ast_ptr: Some(SyntaxNodePtr::new(name.syntax())),
@@ -888,55 +887,59 @@ fn directive_locations(
 fn directives(directives: Option<ast::Directives>) -> Arc<Vec<Directive>> {
     match directives {
         Some(directives) => {
-            let directives = directives.directives().into_iter().map(directive).collect();
+            let directives = directives
+                .directives()
+                .into_iter()
+                .filter_map(directive)
+                .collect();
             Arc::new(directives)
         }
         None => Arc::new(Vec::new()),
     }
 }
 
-fn directive(directive: ast::Directive) -> Directive {
-    let name = name(directive.name());
+fn directive(directive: ast::Directive) -> Option<Directive> {
+    let name = name(directive.name())?;
     let arguments = arguments(directive.arguments());
     let ast_ptr = SyntaxNodePtr::new(directive.syntax());
 
-    Directive {
+    Some(Directive {
         name,
         arguments,
         ast_ptr,
-    }
+    })
 }
 
 fn arguments(arguments: Option<ast::Arguments>) -> Arc<Vec<Argument>> {
     match arguments {
         Some(arguments) => {
-            let arguments = arguments.arguments().into_iter().map(argument).collect();
+            let arguments = arguments
+                .arguments()
+                .into_iter()
+                .filter_map(argument)
+                .collect();
             Arc::new(arguments)
         }
         None => Arc::new(Vec::new()),
     }
 }
 
-fn argument(argument: ast::Argument) -> Argument {
-    let name = name(argument.name());
-    let value = value(argument.value().expect("Argument must have a value"));
+fn argument(argument: ast::Argument) -> Option<Argument> {
+    let name = name(argument.name())?;
+    let value = value(argument.value()?)?;
     let ast_ptr = SyntaxNodePtr::new(argument.syntax());
 
-    Argument {
+    Some(Argument {
         name,
         value,
         ast_ptr,
-    }
+    })
 }
 
-fn value(val: ast::Value) -> Value {
-    match val {
+fn value(val: ast::Value) -> Option<Value> {
+    let hir_val = match val {
         ast::Value::Variable(var) => Value::Variable(Variable {
-            name: var
-                .name()
-                .expect("Variable must have text")
-                .text()
-                .to_string(),
+            name: var.name()?.text().to_string(),
             ast_ptr: SyntaxNodePtr::new(var.syntax()),
         }),
         ast::Value::StringValue(string_val) => Value::String(string_val.into()),
@@ -945,23 +948,24 @@ fn value(val: ast::Value) -> Value {
         ast::Value::IntValue(int) => Value::Int(Float::new(f64::try_from(int).unwrap())),
         ast::Value::BooleanValue(bool) => Value::Boolean(bool.try_into().unwrap()),
         ast::Value::NullValue(_) => Value::Null,
-        ast::Value::EnumValue(enum_) => Value::Enum(name(enum_.name())),
+        ast::Value::EnumValue(enum_) => Value::Enum(name(enum_.name())?),
         ast::Value::ListValue(list) => {
-            let list: Vec<Value> = list.values().map(value).collect();
+            let list: Vec<Value> = list.values().filter_map(value).collect();
             Value::List(list)
         }
         ast::Value::ObjectValue(object) => {
             let object_values: Vec<(Name, Value)> = object
                 .object_fields()
-                .map(|o| {
-                    let name = name(o.name());
-                    let value = value(o.value().expect("Object Value must have a value"));
-                    (name, value)
+                .filter_map(|o| {
+                    let name = name(o.name())?;
+                    let value = value(o.value()?)?;
+                    Some((name, value))
                 })
                 .collect();
             Value::Object(object_values)
         }
-    }
+    };
+    Some(hir_val)
 }
 
 fn selection_set(
@@ -973,7 +977,7 @@ fn selection_set(
         Some(sel) => sel
             .selections()
             .into_iter()
-            .map(|sel| selection(db, sel, parent_obj_ty.as_ref().cloned()))
+            .filter_map(|sel| selection(db, sel, parent_obj_ty.as_ref().cloned()))
             .collect(),
         None => Vec::new(),
     };
@@ -987,20 +991,17 @@ fn selection(
     db: &dyn HirDatabase,
     selection: ast::Selection,
     parent_obj_ty: Option<String>,
-) -> Selection {
+) -> Option<Selection> {
     match selection {
         ast::Selection::Field(sel_field) => {
-            let field = field(db, sel_field, parent_obj_ty);
-            Selection::Field(field)
+            field(db, sel_field, parent_obj_ty).map(Selection::Field)
         }
         ast::Selection::FragmentSpread(fragment) => {
-            let fragment_spread = fragment_spread(fragment);
-            Selection::FragmentSpread(fragment_spread)
-        }
-        ast::Selection::InlineFragment(fragment) => {
-            let inline_fragment = inline_fragment(db, fragment, parent_obj_ty);
-            Selection::InlineFragment(inline_fragment)
+            fragment_spread(fragment).map(Selection::FragmentSpread)
         }
+        ast::Selection::InlineFragment(fragment) => Some(Selection::InlineFragment(
+            inline_fragment(db, fragment, parent_obj_ty),
+        )),
     }
 }
 
@@ -1009,13 +1010,9 @@ fn inline_fragment(
     fragment: ast::InlineFragment,
     parent_obj: Option<String>,
 ) -> Arc<InlineFragment> {
-    let type_condition = fragment.type_condition().map(|tc| {
-        let tc = tc
-            .named_type()
-            .expect("Type Condition must have a name")
-            .name()
-            .expect("Name must have text");
-        name_hir_node(tc)
+    let type_condition = fragment.type_condition().and_then(|tc| {
+        let tc = tc.named_type()?.name()?;
+        Some(name_hir_node(tc))
     });
     let directives = directives(fragment.directives());
     let new_parent_obj = if let Some(type_condition) = type_condition.clone() {
@@ -1035,13 +1032,8 @@ fn inline_fragment(
     Arc::new(fragment_data)
 }
 
-fn fragment_spread(fragment: ast::FragmentSpread) -> Arc<FragmentSpread> {
-    let name = name(
-        fragment
-            .fragment_name()
-            .expect("Fragment Spread must have a name")
-            .name(),
-    );
+fn fragment_spread(fragment: ast::FragmentSpread) -> Option<Arc<FragmentSpread>> {
+    let name = name(fragment.fragment_name()?.name())?;
     let directives = directives(fragment.directives());
     let ast_ptr = SyntaxNodePtr::new(fragment.syntax());
 
@@ -1050,11 +1042,15 @@ fn fragment_spread(fragment: ast::FragmentSpread) -> Arc<FragmentSpread> {
         directives,
         ast_ptr,
     };
-    Arc::new(fragment_data)
+    Some(Arc::new(fragment_data))
 }
 
-fn field(db: &dyn HirDatabase, field: ast::Field, parent_obj: Option<String>) -> Arc<Field> {
-    let name = name(field.name());
+fn field(
+    db: &dyn HirDatabase,
+    field: ast::Field,
+    parent_obj: Option<String>,
+) -> Option<Arc<Field>> {
+    let name = name(field.name())?;
     let alias = alias(field.alias());
     let new_parent_obj = parent_ty(db, name.src(), parent_obj.clone());
     let selection_set = selection_set(db, field.selection_set(), new_parent_obj);
@@ -1071,7 +1067,7 @@ fn field(db: &dyn HirDatabase, field: ast::Field, parent_obj: Option<String>) ->
         arguments,
         ast_ptr,
     };
-    Arc::new(field_data)
+    Some(Arc::new(field_data))
 }
 
 fn parent_ty(db: &dyn HirDatabase, field_name: &str, parent_obj: Option<String>) -> Option<String> {
@@ -1089,8 +1085,8 @@ fn parent_ty(db: &dyn HirDatabase, field_name: &str, parent_obj: Option<String>)
     }
 }
 
-fn name(name: Option<ast::Name>) -> Name {
-    name_hir_node(name.expect("Field must have a name"))
+fn name(name: Option<ast::Name>) -> Option<Name> {
+    name.map(name_hir_node)
 }
 
 fn name_hir_node(name: ast::Name) -> Name {
@@ -1100,31 +1096,20 @@ fn name_hir_node(name: ast::Name) -> Name {
     }
 }
 
-fn enum_value(enum_value: Option<ast::EnumValue>) -> Name {
-    let name = enum_value
-        .expect("Enum value must have a name")
-        .name()
-        .expect("Name must have text");
-    name_hir_node(name)
+fn enum_value(enum_value: Option<ast::EnumValue>) -> Option<Name> {
+    let name = enum_value?.name()?;
+    Some(name_hir_node(name))
 }
 
 fn description(description: Option<ast::Description>) -> Option<String> {
-    description.map(|desc| {
-        desc.string_value()
-            .expect("Description must have text")
-            .into()
-    })
+    description.and_then(|desc| Some(desc.string_value()?.into()))
 }
 
 fn alias(alias: Option<ast::Alias>) -> Option<Arc<Alias>> {
-    alias.map(|alias| {
-        let name = alias
-            .name()
-            .expect("Alias must have a name")
-            .text()
-            .to_string();
+    alias.and_then(|alias| {
+        let name = alias.name()?.text().to_string();
         let alias_data = Alias(name);
-        Arc::new(alias_data)
+        Some(Arc::new(alias_data))
     })
 }
 
@@ -1395,7 +1380,7 @@ fn scalar_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<ScalarTypeExtension>>
         .filter_map(|definition| match definition {
             ast::Definition::ScalarTypeExtension(def) => Some(ScalarTypeExtension {
                 directives: directives(def.directives()),
-                name: name(def.name()),
+                name: name(def.name())?,
                 ast_ptr: SyntaxNodePtr::new(def.syntax()),
             }),
             _ => None,
@@ -1413,7 +1398,7 @@ fn object_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<ObjectTypeExtension>>
         .filter_map(|definition| match definition {
             ast::Definition::ObjectTypeExtension(def) => Some(ObjectTypeExtension {
                 directives: directives(def.directives()),
-                name: name(def.name()),
+                name: name(def.name())?,
                 implements_interfaces: implements_interfaces(def.implements_interfaces()),
                 fields_definition: fields_definition(def.fields_definition()),
                 ast_ptr: SyntaxNodePtr::new(def.syntax()),
@@ -1433,7 +1418,7 @@ fn interface_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<InterfaceTypeExten
         .filter_map(|definition| match definition {
             ast::Definition::InterfaceTypeExtension(def) => Some(InterfaceTypeExtension {
                 directives: directives(def.directives()),
-                name: name(def.name()),
+                name: name(def.name())?,
                 implements_interfaces: implements_interfaces(def.implements_interfaces()),
                 fields_definition: fields_definition(def.fields_definition()),
                 ast_ptr: SyntaxNodePtr::new(def.syntax()),
@@ -1453,7 +1438,7 @@ fn union_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<UnionTypeExtension>> {
         .filter_map(|definition| match definition {
             ast::Definition::UnionTypeExtension(def) => Some(UnionTypeExtension {
                 directives: directives(def.directives()),
-                name: name(def.name()),
+                name: name(def.name())?,
                 union_members: union_members(def.union_member_types()),
                 ast_ptr: SyntaxNodePtr::new(def.syntax()),
             }),
@@ -1472,7 +1457,7 @@ fn enum_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<EnumTypeExtension>> {
         .filter_map(|definition| match definition {
             ast::Definition::EnumTypeExtension(def) => Some(EnumTypeExtension {
                 directives: directives(def.directives()),
-                name: name(def.name()),
+                name: name(def.name())?,
                 enum_values_definition: enum_values_definition(def.enum_values_definition()),
                 ast_ptr: SyntaxNodePtr::new(def.syntax()),
             }),
@@ -1491,7 +1476,7 @@ fn input_object_type_extensions(db: &dyn HirDatabase) -> Arc<Vec<InputObjectType
         .filter_map(|definition| match definition {
             ast::Definition::InputObjectTypeExtension(def) => Some(InputObjectTypeExtension {
                 directives: directives(def.directives()),
-                name: name(def.name()),
+                name: name(def.name())?,
                 input_fields_definition: input_fields_definition(def.input_fields_definition()),
                 ast_ptr: SyntaxNodePtr::new(def.syntax()),
             }),
diff --git a/crates/apollo-parser/src/parser/grammar/field.rs b/crates/apollo-parser/src/parser/grammar/field.rs
index 27b3f65..97924fa 100644
--- a/crates/apollo-parser/src/parser/grammar/field.rs
+++ b/crates/apollo-parser/src/parser/grammar/field.rs
@@ -63,7 +63,10 @@ pub(crate) fn field(p: &mut Parser) {
 pub(crate) fn fields_definition(p: &mut Parser) {
     let _g = p.start_node(SyntaxKind::FIELDS_DEFINITION);
     p.bump(S!['{']);
-    field_definition(p);
+    while let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
+        // Guaranteed to eat at least one token if the next token is a Name or StringValue
+        field_definition(p);
+    }
     p.expect(T!['}'], S!['}']);
 }
 
@@ -72,42 +75,35 @@ pub(crate) fn fields_definition(p: &mut Parser) {
 /// *FieldDefinition*:
 ///     Description? Name ArgumentsDefinition? **:** Type Directives?
 pub(crate) fn field_definition(p: &mut Parser) {
-    if let Some(TokenKind::Name | TokenKind::StringValue) = p.peek() {
-        let guard = p.start_node(SyntaxKind::FIELD_DEFINITION);
+    let _guard = p.start_node(SyntaxKind::FIELD_DEFINITION);
 
-        if let Some(TokenKind::StringValue) = p.peek() {
-            description::description(p);
-        }
+    if let Some(TokenKind::StringValue) = p.peek() {
+        description::description(p);
+    }
 
-        name::name(p);
+    name::name(p);
 
-        if let Some(T!['(']) = p.peek() {
-            argument::arguments_definition(p);
-        }
+    if let Some(T!['(']) = p.peek() {
+        argument::arguments_definition(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() {
-                        directive::directives(p);
-                    }
-                    if p.peek().is_some() {
-                        guard.finish_node();
-                        return field_definition(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() {
+                    directive::directives(p);
                 }
-                _ => {
-                    p.err("expected a Type");
+                if p.peek().is_some() {
+                    return;
                 }
             }
-        } else {
-            p.err("expected a type");
+            _ => {
+                p.err("expected a Type");
+            }
         }
-    }
-
-    if let Some(T!['}']) = p.peek() {
-        return;
+    } else {
+        p.err("expected a type");
     }
 }
diff --git a/crates/apollo-parser/src/parser/grammar/ty.rs b/crates/apollo-parser/src/parser/grammar/ty.rs
index e07ed74..2aadd98 100644
--- a/crates/apollo-parser/src/parser/grammar/ty.rs
+++ b/crates/apollo-parser/src/parser/grammar/ty.rs
@@ -19,8 +19,10 @@ use crate::{parser::grammar::name, Parser, SyntaxKind, Token, TokenKind, S, T};
 // unwrap them once the last possible child has been parsed. Nodes are then
 // created in the processing stage of this parsing rule.
 pub(crate) fn ty(p: &mut Parser) {
-    let ty = parse(p);
-    process(ty, p);
+    match parse(p) {
+        Ok(ty) => process(ty, p),
+        Err(token) => p.err_at_token(&token, "expected a type"),
+    }
 }
 
 #[derive(Debug)]
@@ -29,7 +31,7 @@ enum TokenTy {
         nullable: Option<Token>,
         open_token: Token,
         close_token: Option<Token>,
-        inner: Box<TokenTy>,
+        inner: Option<Box<TokenTy>>,
         comma: Option<Token>,
         trailing_ws: Option<Token>,
     },
@@ -41,11 +43,24 @@ enum TokenTy {
     },
 }
 
-fn parse(p: &mut Parser) -> TokenTy {
+/// Returns the type on success, or the TokenKind that caused an error.
+///
+/// When errors occur deeper inside nested types like lists, this function
+/// pushes errors *inside* the list to the parser, and returns an Ok() with
+/// an incomplete type.
+fn parse(p: &mut Parser) -> Result<TokenTy, Token> {
     let token = p.pop();
     let mut types = match token.kind() {
         T!['['] => {
-            let inner = parse(p);
+            let inner = match parse(p) {
+                Ok(ty) => Some(Box::new(ty)),
+                Err(token) => {
+                    // TODO(@goto-bus-stop) ideally the span here would point to the entire list
+                    // type, so both opening and closing brackets `[]`.
+                    p.err_at_token(&token, "expected item type");
+                    None
+                }
+            };
             let close_token = if let Some(T![']']) = p.peek() {
                 Some(p.pop())
             } else {
@@ -53,7 +68,7 @@ fn parse(p: &mut Parser) -> TokenTy {
             };
 
             TokenTy::List {
-                inner: Box::new(inner),
+                inner,
                 open_token: token,
                 close_token,
                 nullable: None,
@@ -67,8 +82,7 @@ fn parse(p: &mut Parser) -> TokenTy {
             comma: None,
             trailing_ws: None,
         },
-        // TODO(@lrlna): this should not panic
-        token => panic!("unexpected token, {:?}", token),
+        _ => return Err(token),
     };
 
     // Deal with nullable types
@@ -94,7 +108,7 @@ fn parse(p: &mut Parser) -> TokenTy {
         };
     }
 
-    types
+    Ok(types)
 }
 
 fn process(ty: TokenTy, p: &mut Parser) {
@@ -109,12 +123,12 @@ fn process(ty: TokenTy, p: &mut Parser) {
         } => match nullable {
             Some(nullable_token) => {
                 let _non_null_g = p.start_node(SyntaxKind::NON_NULL_TYPE);
-                process_list(p, open_token, *inner, close_token);
+                process_list(p, open_token, inner, close_token);
                 p.push_ast(S![!], nullable_token);
                 process_ignored_tokens(comma, p, trailing_ws);
             }
             None => {
-                process_list(p, open_token, *inner, close_token);
+                process_list(p, open_token, inner, close_token);
                 process_ignored_tokens(comma, p, trailing_ws);
             }
         },
@@ -148,10 +162,17 @@ fn process_ignored_tokens(comma: Option<Token>, p: &mut Parser, whitespace: Opti
     }
 }
 
-fn process_list(p: &mut Parser, open_token: Token, inner: TokenTy, close_token: Option<Token>) {
+fn process_list(
+    p: &mut Parser,
+    open_token: Token,
+    inner: Option<Box<TokenTy>>,
+    close_token: Option<Token>,
+) {
     let _list_g = p.start_node(SyntaxKind::LIST_TYPE);
     p.push_ast(S!['['], open_token);
-    process(inner, p);
+    if let Some(inner) = inner {
+        process(*inner, p);
+    }
     if let Some(close_token) = close_token {
         p.push_ast(S![']'], close_token);
     }
diff --git a/crates/apollo-parser/src/parser/mod.rs b/crates/apollo-parser/src/parser/mod.rs
index 6617da4..1599c40 100644
--- a/crates/apollo-parser/src/parser/mod.rs
+++ b/crates/apollo-parser/src/parser/mod.rs
@@ -189,7 +189,18 @@ impl<'a> Parser<'a> {
         self.accept_errors = false;
     }
 
-    /// Create a parser error and push it into the error vector.
+    /// Create a parser error at a given location and push it into the error vector.
+    pub(crate) fn err_at_token(&mut self, current: &Token, message: &str) {
+        let err = if current.kind == TokenKind::Eof {
+            Error::eof(message, current.index())
+        } else {
+            // this needs to be the computed location
+            Error::with_loc(message, current.data().to_string(), current.index())
+        };
+        self.push_err(err);
+    }
+
+    /// Create a parser error at the current location and push it into the error vector.
     pub(crate) fn err(&mut self, message: &str) {
         let current = if let Some(current) = self.current() {
             current
@@ -205,7 +216,7 @@ impl<'a> Parser<'a> {
         self.push_err(err);
     }
 
-    /// Create a parser error and push it into the error vector.
+    /// Create a parser error at the current location and eat the responsible token.
     pub(crate) fn err_and_pop(&mut self, message: &str) {
         if self.current().is_none() {
             return;


Test patch:
diff --git a/crates/apollo-parser/test_data/parser/err/0043_type_with_trailing_garbage.graphql b/crates/apollo-parser/test_data/parser/err/0043_type_with_trailing_garbage.graphql
new file mode 100644
index 0000000..3874d61
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0043_type_with_trailing_garbage.graphql
@@ -0,0 +1,6 @@
+type Person {
+  id: ID!
+  appearedIn: [Film]s
+  name: String
+  directed: [Film]
+}
diff --git a/crates/apollo-parser/test_data/parser/err/0043_type_with_trailing_garbage.txt b/crates/apollo-parser/test_data/parser/err/0043_type_with_trailing_garbage.txt
new file mode 100644
index 0000000..6e66d74
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0043_type_with_trailing_garbage.txt
@@ -0,0 +1,61 @@
+- DOCUMENT@0..82
+    - OBJECT_TYPE_DEFINITION@0..82
+        - type_KW@0..4 "type"
+        - WHITESPACE@4..5 " "
+        - NAME@5..12
+            - IDENT@5..11 "Person"
+            - WHITESPACE@11..12 " "
+        - FIELDS_DEFINITION@12..82
+            - L_CURLY@12..13 "{"
+            - WHITESPACE@13..16 "\n  "
+            - FIELD_DEFINITION@16..26
+                - NAME@16..18
+                    - IDENT@16..18 "id"
+                - COLON@18..19 ":"
+                - WHITESPACE@19..20 " "
+                - NON_NULL_TYPE@20..26
+                    - NAMED_TYPE@20..22
+                        - NAME@20..22
+                            - IDENT@20..22 "ID"
+                    - BANG@22..23 "!"
+                    - WHITESPACE@23..26 "\n  "
+            - FIELD_DEFINITION@26..44
+                - NAME@26..36
+                    - IDENT@26..36 "appearedIn"
+                - COLON@36..37 ":"
+                - WHITESPACE@37..38 " "
+                - LIST_TYPE@38..44
+                    - L_BRACK@38..39 "["
+                    - NAMED_TYPE@39..43
+                        - NAME@39..43
+                            - IDENT@39..43 "Film"
+                    - R_BRACK@43..44 "]"
+            - FIELD_DEFINITION@44..48
+                - NAME@44..48
+                    - IDENT@44..45 "s"
+                    - WHITESPACE@45..48 "\n  "
+            - FIELD_DEFINITION@48..63
+                - NAME@48..52
+                    - IDENT@48..52 "name"
+                - COLON@52..53 ":"
+                - WHITESPACE@53..54 " "
+                - NAMED_TYPE@54..60
+                    - NAME@54..60
+                        - IDENT@54..60 "String"
+                - WHITESPACE@60..63 "\n  "
+            - FIELD_DEFINITION@63..80
+                - NAME@63..71
+                    - IDENT@63..71 "directed"
+                - COLON@71..72 ":"
+                - WHITESPACE@72..73 " "
+                - LIST_TYPE@73..79
+                    - L_BRACK@73..74 "["
+                    - NAMED_TYPE@74..78
+                        - NAME@74..78
+                            - IDENT@74..78 "Film"
+                    - R_BRACK@78..79 "]"
+                - WHITESPACE@79..80 "\n"
+            - R_CURLY@80..81 "}"
+            - WHITESPACE@81..82 "\n"
+- ERROR@48:52 "expected a type" name
+recursion limit: 4096, high: 0
\ No newline at end of file
diff --git a/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.graphql b/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.graphql
new file mode 100644
index 0000000..ceb9544
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.graphql
@@ -0,0 +1,4 @@
+type List {
+  field: [] @coolDirective
+  deepError: [[[Item Type]]]
+}
diff --git a/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt b/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt
new file mode 100644
index 0000000..059440f
--- /dev/null
+++ b/crates/apollo-parser/test_data/parser/err/0044_list_type_with_no_type.txt
@@ -0,0 +1,52 @@
+- DOCUMENT@0..65
+    - OBJECT_TYPE_DEFINITION@0..63
+        - type_KW@0..4 "type"
+        - WHITESPACE@4..5 " "
+        - NAME@5..10
+            - IDENT@5..9 "List"
+            - WHITESPACE@9..10 " "
+        - FIELDS_DEFINITION@10..63
+            - L_CURLY@10..11 "{"
+            - WHITESPACE@11..14 "\n  "
+            - FIELD_DEFINITION@14..40
+                - NAME@14..19
+                    - IDENT@14..19 "field"
+                - COLON@19..20 ":"
+                - WHITESPACE@20..21 " "
+                - LIST_TYPE@21..22
+                    - L_BRACK@21..22 "["
+                - WHITESPACE@22..23 " "
+                - DIRECTIVES@23..40
+                    - DIRECTIVE@23..40
+                        - AT@23..24 "@"
+                        - NAME@24..40
+                            - IDENT@24..37 "coolDirective"
+                            - WHITESPACE@37..40 "\n  "
+            - FIELD_DEFINITION@40..59
+                - NAME@40..49
+                    - IDENT@40..49 "deepError"
+                - COLON@49..50 ":"
+                - WHITESPACE@50..51 " "
+                - LIST_TYPE@51..59
+                    - L_BRACK@51..52 "["
+                    - LIST_TYPE@52..59
+                        - L_BRACK@52..53 "["
+                        - LIST_TYPE@53..59
+                            - L_BRACK@53..54 "["
+                            - NAMED_TYPE@54..58
+                                - NAME@54..58
+                                    - IDENT@54..58 "Item"
+                            - WHITESPACE@58..59 " "
+            - FIELD_DEFINITION@59..63
+                - NAME@59..63
+                    - IDENT@59..63 "Type"
+    - WHITESPACE@63..64 "\n"
+    - WHITESPACE@64..65 "\n"
+- ERROR@22:23 "expected item type" ]
+- ERROR@64:65 "expected a type" ]
+- ERROR@64:65 "expected R_CURLY, got ]" ]
+- ERROR@64:65 "expected a StringValue, Name or OperationDefinition" ]
+- ERROR@65:66 "expected a StringValue, Name or OperationDefinition" ]
+- ERROR@66:67 "expected a StringValue, Name or OperationDefinition" ]
+- ERROR@68:69 "expected a StringValue, Name or OperationDefinition" }
+recursion limit: 4096, high: 0
\ No newline at end of file