wsgl parser: Add ParserImpl::Maybe<T>

And use it for the non-ParserImpl::expect_xxx() methods.

Another step towards supporting multiple error messages, as the caller can now test to see if the specific call errored, or didn't match, instead of using a global error state.

Makes reading the control flow conditionals a bit easier too.

Change-Id: Ie8627b8499ec9079167965da2a566401cd6bd903
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/32102
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index b04a509..a4e4e71 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -77,6 +77,9 @@
 template <typename T>
 using Expect = ParserImpl::Expect<T>;
 
+template <typename T>
+using Maybe = ParserImpl::Maybe<T>;
+
 /// Controls the maximum number of times we'll call into the const_expr function
 /// from itself. This is to guard against stack overflow when there is an
 /// excessive number of type constructors inside the const_expr.
@@ -217,66 +220,70 @@
 
   auto decos = decoration_list();
 
-  auto gv = global_variable_decl(decos);
+  // FUDGE - Abort early if we enter with an error state to avoid accumulating
+  // multiple error messages.
+  // TODO(ben-clayton) - remove this once resynchronization is implemented.
   if (has_error())
     return Failure::kErrored;
 
-  if (gv != nullptr) {
+  auto gv = global_variable_decl(decos.value);
+  if (gv.errored)
+    return Failure::kErrored;
+  if (gv.matched) {
     if (!expect("variable declaration", Token::Type::kSemicolon))
       return Failure::kErrored;
 
-    module_.AddGlobalVariable(std::move(gv));
+    module_.AddGlobalVariable(std::move(gv.value));
     return true;
   }
 
   auto gc = global_constant_decl();
-  if (has_error())
+  if (gc.errored) {
     return Failure::kErrored;
-
-  if (gc != nullptr) {
+  }
+  if (gc.matched) {
     if (!expect("constant declaration", Token::Type::kSemicolon))
       return Failure::kErrored;
 
-    module_.AddGlobalVariable(std::move(gc));
+    module_.AddGlobalVariable(std::move(gc.value));
     return true;
   }
 
-  auto* ta = type_alias();
-  if (has_error())
+  auto ta = type_alias();
+  if (ta.errored)
     return Failure::kErrored;
 
-  if (ta != nullptr) {
+  if (ta.matched) {
     if (!expect("type alias", Token::Type::kSemicolon))
       return Failure::kErrored;
 
-    module_.AddConstructedType(ta);
+    module_.AddConstructedType(ta.value);
     return true;
   }
 
-  auto str = struct_decl(decos);
-  if (has_error())
+  auto str = struct_decl(decos.value);
+  if (str.errored)
     return Failure::kErrored;
 
-  if (str != nullptr) {
+  if (str.matched) {
     if (!expect("struct declaration", Token::Type::kSemicolon))
       return Failure::kErrored;
 
-    auto* type = ctx_.type_mgr().Get(std::move(str));
+    auto* type = ctx_.type_mgr().Get(std::move(str.value));
     register_constructed(type->AsStruct()->name(), type);
     module_.AddConstructedType(type);
     return true;
   }
 
-  auto func = function_decl(decos);
-  if (has_error())
+  auto func = function_decl(decos.value);
+  if (func.errored)
     return Failure::kErrored;
-
-  if (func != nullptr) {
-    module_.AddFunction(std::move(func));
+  if (func.matched) {
+    module_.AddFunction(std::move(func.value));
     return true;
   }
 
-  if (decos.size() > 0) {
+  if (decos.value.size() > 0) {
     add_error(peek(), "expected declaration after decorations");
   } else {
     add_error(peek(), "invalid token");
@@ -287,15 +294,19 @@
 // global_variable_decl
 //  : variable_decoration_list* variable_decl
 //  | variable_decoration_list* variable_decl EQUAL const_expr
-std::unique_ptr<ast::Variable> ParserImpl::global_variable_decl(
+Maybe<std::unique_ptr<ast::Variable>> ParserImpl::global_variable_decl(
     ast::DecorationList& decos) {
-  auto var = variable_decl();
-  if (has_error() || var == nullptr)
-    return nullptr;
+  auto decl = variable_decl();
+  if (decl.errored)
+    return Failure::kErrored;
+  if (!decl.matched)
+    return Failure::kNoMatch;
+
+  auto var = std::move(decl.value);
 
   auto var_decos = cast_decorations<ast::VariableDecoration>(decos);
   if (var_decos.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   if (var_decos.value.size() > 0) {
     auto dv = std::make_unique<ast::DecoratedVariable>(std::move(var));
@@ -306,34 +317,34 @@
   if (match(Token::Type::kEqual)) {
     auto expr = expect_const_expr();
     if (expr.errored)
-      return nullptr;
+      return Failure::kErrored;
     var->set_constructor(std::move(expr.value));
   }
-  return var;
+  return std::move(var);
 }
 
 // global_constant_decl
 //  : CONST variable_ident_decl EQUAL const_expr
-std::unique_ptr<ast::Variable> ParserImpl::global_constant_decl() {
+Maybe<std::unique_ptr<ast::Variable>> ParserImpl::global_constant_decl() {
   if (!match(Token::Type::kConst))
-    return nullptr;
+    return Failure::kNoMatch;
 
   const char* use = "constant declaration";
 
   auto decl = expect_variable_ident_decl(use);
   if (decl.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto var = std::make_unique<ast::Variable>(
       decl->source, decl->name, ast::StorageClass::kNone, decl->type);
   var->set_is_const(true);
 
   if (!expect(use, Token::Type::kEqual))
-    return nullptr;
+    return Failure::kErrored;
 
   auto init = expect_const_expr();
   if (init.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   var->set_constructor(std::move(init.value));
 
@@ -342,20 +353,21 @@
 
 // variable_decl
 //   : VAR variable_storage_decoration? variable_ident_decl
-std::unique_ptr<ast::Variable> ParserImpl::variable_decl() {
+Maybe<std::unique_ptr<ast::Variable>> ParserImpl::variable_decl() {
   if (!match(Token::Type::kVar))
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto sc = variable_storage_decoration();
-  if (has_error())
-    return nullptr;
+  if (sc.errored)
+    return Failure::kErrored;
 
   auto decl = expect_variable_ident_decl("variable declaration");
   if (decl.errored)
-    return nullptr;
+    return Failure::kErrored;
 
-  return std::make_unique<ast::Variable>(decl->source, decl->name, sc,
-                                         decl->type);
+  return std::make_unique<ast::Variable>(
+      decl->source, decl->name,
+      sc.matched ? sc.value : ast::StorageClass::kNone, decl->type);
 }
 
 // texture_sampler_types
@@ -364,88 +376,81 @@
 //  | sampled_texture_type LESS_THAN type_decl GREATER_THAN
 //  | multisampled_texture_type LESS_THAN type_decl GREATER_THAN
 //  | storage_texture_type LESS_THAN image_storage_type GREATER_THAN
-ast::type::Type* ParserImpl::texture_sampler_types() {
-  auto* type = sampler_type();
-  if (type != nullptr) {
+Maybe<ast::type::Type*> ParserImpl::texture_sampler_types() {
+  auto type = sampler_type();
+  if (type.matched)
     return type;
-  }
 
   type = depth_texture_type();
-  if (type != nullptr) {
-    return type;
-  }
+  if (type.matched)
+    return type.value;
 
   auto dim = sampled_texture_type();
-  if (dim != ast::type::TextureDimension::kNone) {
+  if (dim.matched) {
     const char* use = "sampled texture type";
 
     if (!expect(use, Token::Type::kLessThan))
-      return nullptr;
+      return Failure::kErrored;
 
-    auto* subtype = type_decl();
-    if (has_error())
-      return nullptr;
-    if (subtype == nullptr) {
-      add_error(peek().source(), "invalid subtype", use);
-      return nullptr;
-    }
+    auto subtype = type_decl();
+    if (subtype.errored)
+      return Failure::kErrored;
+    if (!subtype.matched)
+      return add_error(peek().source(), "invalid subtype", use);
 
     if (!expect(use, Token::Type::kGreaterThan))
-      return nullptr;
+      return Failure::kErrored;
 
-    return ctx_.type_mgr().Get(
-        std::make_unique<ast::type::SampledTextureType>(dim, subtype));
+    return ctx_.type_mgr().Get(std::make_unique<ast::type::SampledTextureType>(
+        dim.value, subtype.value));
   }
 
-  dim = multisampled_texture_type();
-  if (dim != ast::type::TextureDimension::kNone) {
+  auto ms_dim = multisampled_texture_type();
+  if (ms_dim.matched) {
     const char* use = "multisampled texture type";
 
     if (!expect(use, Token::Type::kLessThan))
-      return nullptr;
+      return Failure::kErrored;
 
-    auto* subtype = type_decl();
-    if (has_error())
-      return nullptr;
-    if (subtype == nullptr) {
-      add_error(peek().source(), "invalid subtype", use);
-      return nullptr;
-    }
+    auto subtype = type_decl();
+    if (subtype.errored)
+      return Failure::kErrored;
+    if (!subtype.matched)
+      return add_error(peek().source(), "invalid subtype", use);
 
     if (!expect(use, Token::Type::kGreaterThan))
-      return nullptr;
+      return Failure::kErrored;
 
     return ctx_.type_mgr().Get(
-        std::make_unique<ast::type::MultisampledTextureType>(dim, subtype));
+        std::make_unique<ast::type::MultisampledTextureType>(ms_dim.value,
+                                                             subtype.value));
   }
 
-  ast::type::TextureDimension storage_dim;
-  ast::AccessControl access;
-  std::tie(storage_dim, access) = storage_texture_type();
-  if (storage_dim != ast::type::TextureDimension::kNone) {
+  auto storage = storage_texture_type();
+  if (storage.matched) {
     const char* use = "storage texture type";
 
     if (!expect(use, Token::Type::kLessThan))
-      return nullptr;
+      return Failure::kErrored;
 
     auto format = expect_image_storage_type(use);
     if (format.errored)
-      return nullptr;
+      return Failure::kErrored;
 
     if (!expect(use, Token::Type::kGreaterThan))
-      return nullptr;
+      return Failure::kErrored;
 
     return ctx_.type_mgr().Get(std::make_unique<ast::type::StorageTextureType>(
-        storage_dim, access, format.value));
+        storage->first, storage->second, format.value));
   }
 
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 // sampler_type
 //  : SAMPLER
 //  | SAMPLER_COMPARISON
-ast::type::Type* ParserImpl::sampler_type() {
+Maybe<ast::type::Type*> ParserImpl::sampler_type() {
   if (match(Token::Type::kSampler))
     return ctx_.type_mgr().Get(std::make_unique<ast::type::SamplerType>(
         ast::type::SamplerKind::kSampler));
@@ -454,7 +459,7 @@
     return ctx_.type_mgr().Get(std::make_unique<ast::type::SamplerType>(
         ast::type::SamplerKind::kComparisonSampler));
 
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 // sampled_texture_type
@@ -465,7 +470,7 @@
 //  | TEXTURE_SAMPLED_3D
 //  | TEXTURE_SAMPLED_CUBE
 //  | TEXTURE_SAMPLED_CUBE_ARRAY
-ast::type::TextureDimension ParserImpl::sampled_texture_type() {
+Maybe<ast::type::TextureDimension> ParserImpl::sampled_texture_type() {
   if (match(Token::Type::kTextureSampled1d))
     return ast::type::TextureDimension::k1d;
 
@@ -487,16 +492,16 @@
   if (match(Token::Type::kTextureSampledCubeArray))
     return ast::type::TextureDimension::kCubeArray;
 
-  return ast::type::TextureDimension::kNone;
+  return Failure::kNoMatch;
 }
 
 // multisampled_texture_type
 //  : TEXTURE_MULTISAMPLED_2D
-ast::type::TextureDimension ParserImpl::multisampled_texture_type() {
+Maybe<ast::type::TextureDimension> ParserImpl::multisampled_texture_type() {
   if (match(Token::Type::kTextureMultisampled2d))
     return ast::type::TextureDimension::k2d;
 
-  return ast::type::TextureDimension::kNone;
+  return Failure::kNoMatch;
 }
 
 // storage_texture_type
@@ -520,43 +525,57 @@
 //  | TEXTURE_STORAGE_WO_2D
 //  | TEXTURE_STORAGE_WO_2D_ARRAY
 //  | TEXTURE_STORAGE_WO_3D
-std::pair<ast::type::TextureDimension, ast::AccessControl>
+Maybe<std::pair<ast::type::TextureDimension, ast::AccessControl>>
 ParserImpl::storage_texture_type() {
-  if (match(Token::Type::kTextureStorageReadonly1d))
-    return {ast::type::TextureDimension::k1d, ast::AccessControl::kReadOnly};
+  using Ret = std::pair<ast::type::TextureDimension, ast::AccessControl>;
+  if (match(Token::Type::kTextureStorageReadonly1d)) {
+    return Ret{ast::type::TextureDimension::k1d, ast::AccessControl::kReadOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageReadonly1dArray))
-    return {ast::type::TextureDimension::k1dArray,
-            ast::AccessControl::kReadOnly};
+  if (match(Token::Type::kTextureStorageReadonly1dArray)) {
+    return Ret{ast::type::TextureDimension::k1dArray,
+               ast::AccessControl::kReadOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageReadonly2d))
-    return {ast::type::TextureDimension::k2d, ast::AccessControl::kReadOnly};
+  if (match(Token::Type::kTextureStorageReadonly2d)) {
+    return Ret{ast::type::TextureDimension::k2d, ast::AccessControl::kReadOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageReadonly2dArray))
-    return {ast::type::TextureDimension::k2dArray,
-            ast::AccessControl::kReadOnly};
+  if (match(Token::Type::kTextureStorageReadonly2dArray)) {
+    return Ret{ast::type::TextureDimension::k2dArray,
+               ast::AccessControl::kReadOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageReadonly3d))
-    return {ast::type::TextureDimension::k3d, ast::AccessControl::kReadOnly};
+  if (match(Token::Type::kTextureStorageReadonly3d)) {
+    return Ret{ast::type::TextureDimension::k3d, ast::AccessControl::kReadOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageWriteonly1d))
-    return {ast::type::TextureDimension::k1d, ast::AccessControl::kWriteOnly};
+  if (match(Token::Type::kTextureStorageWriteonly1d)) {
+    return Ret{ast::type::TextureDimension::k1d,
+               ast::AccessControl::kWriteOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageWriteonly1dArray))
-    return {ast::type::TextureDimension::k1dArray,
-            ast::AccessControl::kWriteOnly};
+  if (match(Token::Type::kTextureStorageWriteonly1dArray)) {
+    return Ret{ast::type::TextureDimension::k1dArray,
+               ast::AccessControl::kWriteOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageWriteonly2d))
-    return {ast::type::TextureDimension::k2d, ast::AccessControl::kWriteOnly};
+  if (match(Token::Type::kTextureStorageWriteonly2d)) {
+    return Ret{ast::type::TextureDimension::k2d,
+               ast::AccessControl::kWriteOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageWriteonly2dArray))
-    return {ast::type::TextureDimension::k2dArray,
-            ast::AccessControl::kWriteOnly};
+  if (match(Token::Type::kTextureStorageWriteonly2dArray)) {
+    return Ret{ast::type::TextureDimension::k2dArray,
+               ast::AccessControl::kWriteOnly};
+  }
 
-  if (match(Token::Type::kTextureStorageWriteonly3d))
-    return {ast::type::TextureDimension::k3d, ast::AccessControl::kWriteOnly};
+  if (match(Token::Type::kTextureStorageWriteonly3d)) {
+    return Ret{ast::type::TextureDimension::k3d,
+               ast::AccessControl::kWriteOnly};
+  }
 
-  return {ast::type::TextureDimension::kNone, ast::AccessControl::kReadOnly};
+  return Failure::kNoMatch;
 }
 
 // depth_texture_type
@@ -564,7 +583,7 @@
 //  | TEXTURE_DEPTH_2D_ARRAY
 //  | TEXTURE_DEPTH_CUBE
 //  | TEXTURE_DEPTH_CUBE_ARRAY
-ast::type::Type* ParserImpl::depth_texture_type() {
+Maybe<ast::type::Type*> ParserImpl::depth_texture_type() {
   if (match(Token::Type::kTextureDepth2d))
     return ctx_.type_mgr().Get(std::make_unique<ast::type::DepthTextureType>(
         ast::type::TextureDimension::k2d));
@@ -581,7 +600,7 @@
     return ctx_.type_mgr().Get(std::make_unique<ast::type::DepthTextureType>(
         ast::type::TextureDimension::kCubeArray));
 
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 // image_storage_type
@@ -742,42 +761,39 @@
     return Failure::kErrored;
 
   auto t = peek();
-  auto* type = type_decl();
-  if (has_error())
+  auto type = type_decl();
+  if (type.errored)
     return Failure::kErrored;
+  if (!type.matched)
+    return add_error(t.source(), "invalid type", use);
 
-  if (type == nullptr) {
-    add_error(t.source(), "invalid type", use);
-    return Failure::kErrored;
-  }
-
-  return TypedIdentifier{type, ident.value, ident.source};
+  return TypedIdentifier{type.value, ident.value, ident.source};
 }
 
 // variable_storage_decoration
 //   : LESS_THAN storage_class GREATER_THAN
-ast::StorageClass ParserImpl::variable_storage_decoration() {
+Maybe<ast::StorageClass> ParserImpl::variable_storage_decoration() {
   if (!match(Token::Type::kLessThan))
-    return ast::StorageClass::kNone;
+    return Failure::kNoMatch;
 
   const char* use = "variable decoration";
 
   auto sc = expect_storage_class(use);
   if (sc.errored)
-    return ast::StorageClass::kNone;
+    return Failure::kErrored;
 
   if (!expect(use, Token::Type::kGreaterThan))
-    return ast::StorageClass::kNone;
+    return Failure::kErrored;
 
   return sc.value;
 }
 
 // type_alias
 //   : TYPE IDENT EQUAL type_decl
-ast::type::Type* ParserImpl::type_alias() {
+Maybe<ast::type::Type*> ParserImpl::type_alias() {
   auto t = peek();
   if (!t.IsType())
-    return nullptr;
+    return Failure::kNoMatch;
 
   next();  // Consume the peek
 
@@ -785,21 +801,19 @@
 
   auto name = expect_ident(use);
   if (name.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   if (!expect(use, Token::Type::kEqual))
-    return nullptr;
+    return Failure::kErrored;
 
-  auto* type = type_decl();
-  if (has_error())
-    return nullptr;
-  if (type == nullptr) {
-    add_error(peek(), "invalid type alias");
-    return nullptr;
-  }
+  auto type = type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (!type.matched)
+    return add_error(peek(), "invalid type alias");
 
   auto* alias = ctx_.type_mgr().Get(
-      std::make_unique<ast::type::AliasType>(name.value, type));
+      std::make_unique<ast::type::AliasType>(name.value, type.value));
   register_constructed(name.value, alias);
 
   return alias->AsAlias();
@@ -829,14 +843,13 @@
 //   | MAT4x3 LESS_THAN type_decl GREATER_THAN
 //   | MAT4x4 LESS_THAN type_decl GREATER_THAN
 //   | texture_sampler_types
-ast::type::Type* ParserImpl::type_decl() {
+Maybe<ast::type::Type*> ParserImpl::type_decl() {
   auto t = peek();
   if (match(Token::Type::kIdentifier)) {
     auto* ty = get_constructed(t.to_str());
-    if (ty == nullptr) {
-      add_error(t, "unknown constructed type '" + t.to_str() + "'");
-      return nullptr;
-    }
+    if (ty == nullptr)
+      return add_error(t, "unknown constructed type '" + t.to_str() + "'");
+
     return ty;
   }
 
@@ -853,40 +866,42 @@
     return ctx_.type_mgr().Get(std::make_unique<ast::type::U32Type>());
 
   if (t.IsVec2() || t.IsVec3() || t.IsVec4()) {
-    return expect_type_decl_vector(t).value;
+    next();  // Consume the peek
+    return expect_type_decl_vector(t);
   }
 
   if (match(Token::Type::kPtr))
-    return expect_type_decl_pointer().value;
+    return expect_type_decl_pointer();
 
   auto decos = decoration_list();
-  if (has_error())
-    return nullptr;
+  if (decos.errored)
+    return Failure::kErrored;
 
   if (match(Token::Type::kArray)) {
-    auto array_decos = cast_decorations<ast::ArrayDecoration>(decos);
+    auto array_decos = cast_decorations<ast::ArrayDecoration>(decos.value);
     if (array_decos.errored)
-      return nullptr;
-    return expect_type_decl_array(std::move(array_decos.value)).value;
+      return Failure::kErrored;
+
+    return expect_type_decl_array(std::move(array_decos.value));
   }
 
-  expect_decorations_consumed(decos);
+  if (!expect_decorations_consumed(decos.value))
+    return Failure::kErrored;
 
   if (t.IsMat2x2() || t.IsMat2x3() || t.IsMat2x4() || t.IsMat3x2() ||
       t.IsMat3x3() || t.IsMat3x4() || t.IsMat4x2() || t.IsMat4x3() ||
       t.IsMat4x4()) {
-    return expect_type_decl_matrix(t).value;
+    next();  // Consume the peek
+    return expect_type_decl_matrix(t);
   }
 
-  auto* texture_or_sampler = texture_sampler_types();
-  if (has_error()) {
-    return nullptr;
-  }
-  if (texture_or_sampler != nullptr) {
-    return texture_or_sampler;
-  }
+  auto texture_or_sampler = texture_sampler_types();
+  if (texture_or_sampler.errored)
+    return Failure::kErrored;
+  if (texture_or_sampler.matched)
+    return texture_or_sampler.value;
 
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 Expect<ast::type::Type*> ParserImpl::expect_type_decl_pointer() {
@@ -902,22 +917,20 @@
   if (!expect(use, Token::Type::kComma))
     return Failure::kErrored;
 
-  auto* subtype = type_decl();
-  if (has_error())
+  auto subtype = type_decl();
+  if (subtype.errored)
     return Failure::kErrored;
-  if (subtype == nullptr)
+  if (!subtype.matched)
     return add_error(peek().source(), "missing type", use);
 
   if (!expect(use, Token::Type::kGreaterThan))
     return Failure::kErrored;
 
   return ctx_.type_mgr().Get(
-      std::make_unique<ast::type::PointerType>(subtype, sc.value));
+      std::make_unique<ast::type::PointerType>(subtype.value, sc.value));
 }
 
 Expect<ast::type::Type*> ParserImpl::expect_type_decl_vector(Token t) {
-  next();  // Consume the peek
-
   uint32_t count = 2;
   if (t.IsVec3())
     count = 3;
@@ -929,18 +942,17 @@
   if (!expect(use, Token::Type::kLessThan))
     return Failure::kErrored;
 
-  auto* subtype = type_decl();
-  if (has_error())
+  auto subtype = type_decl();
+  if (subtype.errored)
     return Failure::kErrored;
-  if (subtype == nullptr) {
+  if (!subtype.matched)
     return add_error(peek().source(), "unable to determine subtype", use);
-  }
 
   if (!expect(use, Token::Type::kGreaterThan))
     return Failure::kErrored;
 
   return ctx_.type_mgr().Get(
-      std::make_unique<ast::type::VectorType>(subtype, count));
+      std::make_unique<ast::type::VectorType>(subtype.value, count));
 }
 
 Expect<ast::type::Type*> ParserImpl::expect_type_decl_array(
@@ -950,10 +962,10 @@
   if (!expect(use, Token::Type::kLessThan))
     return Failure::kErrored;
 
-  auto* subtype = type_decl();
-  if (has_error())
+  auto subtype = type_decl();
+  if (subtype.errored)
     return Failure::kErrored;
-  if (subtype == nullptr)
+  if (!subtype.matched)
     return add_error(peek(), "invalid type for array declaration");
 
   uint32_t size = 0;
@@ -967,14 +979,12 @@
   if (!expect(use, Token::Type::kGreaterThan))
     return Failure::kErrored;
 
-  auto ty = std::make_unique<ast::type::ArrayType>(subtype, size);
+  auto ty = std::make_unique<ast::type::ArrayType>(subtype.value, size);
   ty->set_decorations(std::move(decos));
   return ctx_.type_mgr().Get(std::move(ty));
 }
 
 Expect<ast::type::Type*> ParserImpl::expect_type_decl_matrix(Token t) {
-  next();  // Consume the peek
-
   uint32_t rows = 2;
   uint32_t columns = 2;
   if (t.IsMat3x2() || t.IsMat3x3() || t.IsMat3x4()) {
@@ -988,22 +998,22 @@
     columns = 4;
   }
 
-  t = next();
-  if (!t.IsLessThan())
-    return add_error(t, "missing < for matrix");
+  const char* use = "matrix";
 
-  auto* subtype = type_decl();
-  if (has_error())
+  if (!expect(use, Token::Type::kLessThan))
     return Failure::kErrored;
-  if (subtype == nullptr)
-    return add_error(peek(), "unable to determine subtype for matrix");
 
-  t = next();
-  if (!t.IsGreaterThan())
-    return add_error(t, "missing > for matrix");
+  auto subtype = type_decl();
+  if (subtype.errored)
+    return Failure::kErrored;
+  if (!subtype.matched)
+    return add_error(peek().source(), "unable to determine subtype", use);
+
+  if (!expect(use, Token::Type::kGreaterThan))
+    return Failure::kErrored;
 
   return ctx_.type_mgr().Get(
-      std::make_unique<ast::type::MatrixType>(subtype, rows, columns));
+      std::make_unique<ast::type::MatrixType>(subtype.value, rows, columns));
 }
 
 // storage_class
@@ -1050,25 +1060,25 @@
 
 // struct_decl
 //   : struct_decoration_decl* STRUCT IDENT struct_body_decl
-std::unique_ptr<ast::type::StructType> ParserImpl::struct_decl(
+Maybe<std::unique_ptr<ast::type::StructType>> ParserImpl::struct_decl(
     ast::DecorationList& decos) {
   auto t = peek();
   auto source = t.source();
 
   if (!match(Token::Type::kStruct))
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto struct_decos = cast_decorations<ast::StructDecoration>(decos);
   if (struct_decos.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto name = expect_ident("struct declaration");
   if (name.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto body = expect_struct_body_decl();
   if (body.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   return std::make_unique<ast::type::StructType>(
       name.value,
@@ -1085,10 +1095,10 @@
 
         while (!peek().IsBraceRight() && !peek().IsEof()) {
           auto decos = decoration_list();
-          if (has_error())
+          if (decos.errored)
             return Failure::kErrored;
 
-          auto mem = expect_struct_member(decos);
+          auto mem = expect_struct_member(decos.value);
           if (mem.errored)
             return Failure::kErrored;
 
@@ -1122,6 +1132,8 @@
     return Failure::kErrored;
 
   auto member_decos = cast_decorations<ast::StructMemberDecoration>(decos);
+  if (member_decos.errored)
+    return Failure::kErrored;
 
   if (!expect("struct member", Token::Type::kSemicolon))
     return Failure::kErrored;
@@ -1132,71 +1144,66 @@
 
 // function_decl
 //   : function_header body_stmt
-std::unique_ptr<ast::Function> ParserImpl::function_decl(
+Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_decl(
     ast::DecorationList& decos) {
   auto f = function_header();
-  if (f == nullptr || has_error())
-    return nullptr;
+  if (f.errored)
+    return Failure::kErrored;
+  if (!f.matched)
+    return Failure::kNoMatch;
 
   auto func_decos = cast_decorations<ast::FunctionDecoration>(decos);
   if (func_decos.errored)
-    return nullptr;
-  f->set_decorations(std::move(func_decos.value));
+    return Failure::kErrored;
+
+  f.value->set_decorations(std::move(func_decos.value));
 
   auto body = expect_body_stmt();
   if (body.errored)
-    return nullptr;
+    return Failure::kErrored;
 
-  f->set_body(std::move(body.value));
-  return f;
+  f.value->set_body(std::move(body.value));
+  return std::move(f.value);
 }
 
 // function_type_decl
 //   : type_decl
 //   | VOID
-ast::type::Type* ParserImpl::function_type_decl() {
-  auto t = peek();
-  if (t.IsVoid()) {
-    next();  // Consume the peek
+Maybe<ast::type::Type*> ParserImpl::function_type_decl() {
+  if (match(Token::Type::kVoid))
     return ctx_.type_mgr().Get(std::make_unique<ast::type::VoidType>());
-  }
+
   return type_decl();
 }
 
 // function_header
 //   : FN IDENT PAREN_LEFT param_list PAREN_RIGHT ARROW function_type_decl
-std::unique_ptr<ast::Function> ParserImpl::function_header() {
+Maybe<std::unique_ptr<ast::Function>> ParserImpl::function_header() {
   Source source;
   if (!match(Token::Type::kFn, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
   const char* use = "function declaration";
 
   auto name = expect_ident(use);
   if (name.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto params = expect_paren_block(use, [&] { return expect_param_list(); });
-
   if (params.errored)
-    return nullptr;
+    return Failure::kErrored;
 
-  auto t = next();
-  if (!t.IsArrow()) {
-    add_error(t, "missing -> for function declaration");
-    return nullptr;
-  }
+  if (!expect(use, Token::Type::kArrow))
+    return Failure::kErrored;
 
-  auto* type = function_type_decl();
-  if (has_error())
-    return nullptr;
-  if (type == nullptr) {
-    add_error(peek(), "unable to determine function return type");
-    return nullptr;
-  }
+  auto type = function_type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (!type.matched)
+    return add_error(peek(), "unable to determine function return type");
 
   return std::make_unique<ast::Function>(source, name.value,
-                                         std::move(params.value), type);
+                                         std::move(params.value), type.value);
 }
 
 // param_list
@@ -1274,13 +1281,12 @@
   return expect_paren_block(
       "", [&]() -> Expect<std::unique_ptr<ast::Expression>> {
         auto expr = logical_or_expression();
-        if (has_error())
+        if (expr.errored)
           return Failure::kErrored;
-
-        if (expr == nullptr)
+        if (!expr.matched)
           return add_error(peek(), "unable to parse expression");
 
-        return expr;
+        return std::move(expr.value);
       });
 }
 
@@ -1291,12 +1297,12 @@
 
   for (;;) {
     auto stmt = statement();
-    if (has_error())
+    if (stmt.errored)
       return Failure::kErrored;
-    if (!stmt)
+    if (!stmt.matched)
       break;
 
-    ret->append(std::move(stmt));
+    ret->append(std::move(stmt.value));
   }
 
   return ret;
@@ -1316,84 +1322,84 @@
 //   | DISCARD SEMICOLON
 //   | assignment_stmt SEMICOLON
 //   | body_stmt?
-std::unique_ptr<ast::Statement> ParserImpl::statement() {
+Maybe<std::unique_ptr<ast::Statement>> ParserImpl::statement() {
   while (match(Token::Type::kSemicolon)) {
     // Skip empty statements
   }
 
   auto t = peek();
   auto ret_stmt = return_stmt();
-  if (has_error())
-    return nullptr;
-  if (ret_stmt != nullptr) {
+  if (ret_stmt.errored)
+    return Failure::kErrored;
+  if (ret_stmt.matched) {
     if (!expect("return statement", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
-    return ret_stmt;
+    return std::move(ret_stmt.value);
   }
 
   auto stmt_if = if_stmt();
-  if (has_error())
-    return nullptr;
-  if (stmt_if != nullptr)
-    return stmt_if;
+  if (stmt_if.errored)
+    return Failure::kErrored;
+  if (stmt_if.matched)
+    return std::move(stmt_if.value);
 
   auto sw = switch_stmt();
-  if (has_error())
-    return nullptr;
-  if (sw != nullptr)
-    return sw;
+  if (sw.errored)
+    return Failure::kErrored;
+  if (sw.matched)
+    return std::move(sw.value);
 
   auto loop = loop_stmt();
-  if (has_error())
-    return nullptr;
-  if (loop != nullptr)
-    return loop;
+  if (loop.errored)
+    return Failure::kErrored;
+  if (loop.matched)
+    return std::move(loop.value);
 
   auto stmt_for = for_stmt();
-  if (has_error())
-    return nullptr;
-  if (stmt_for != nullptr)
-    return stmt_for;
+  if (stmt_for.errored)
+    return Failure::kErrored;
+  if (stmt_for.matched)
+    return std::move(stmt_for.value);
 
   auto func = func_call_stmt();
-  if (has_error())
-    return nullptr;
-  if (func != nullptr) {
+  if (func.errored)
+    return Failure::kErrored;
+  if (func.matched) {
     if (!expect("function call", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
-    return func;
+    return std::move(func.value);
   }
 
   auto var = variable_stmt();
-  if (has_error())
-    return nullptr;
-  if (var != nullptr) {
+  if (var.errored)
+    return Failure::kErrored;
+  if (var.matched) {
     if (!expect("variable declaration", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
-    return var;
+    return std::move(var.value);
   }
 
   auto b = break_stmt();
-  if (has_error())
-    return nullptr;
-  if (b != nullptr) {
+  if (b.errored)
+    return Failure::kErrored;
+  if (b.matched) {
     if (!expect("break statement", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
-    return b;
+    return std::move(b.value);
   }
 
   auto cont = continue_stmt();
-  if (has_error())
-    return nullptr;
-  if (cont != nullptr) {
+  if (cont.errored)
+    return Failure::kErrored;
+  if (cont.matched) {
     if (!expect("continue statement", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
-    return cont;
+    return std::move(cont.value);
   }
 
   if (t.IsDiscard()) {
@@ -1401,160 +1407,151 @@
     next();  // Consume the peek
 
     if (!expect("discard statement", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
     return std::make_unique<ast::DiscardStatement>(source);
   }
 
   auto assign = assignment_stmt();
-  if (has_error())
-    return nullptr;
-  if (assign != nullptr) {
+  if (assign.errored)
+    return Failure::kErrored;
+  if (assign.matched) {
     if (!expect("assignment statement", Token::Type::kSemicolon))
-      return nullptr;
+      return Failure::kErrored;
 
-    return assign;
+    return std::move(assign.value);
   }
 
   t = peek();
   if (t.IsBraceLeft()) {
     auto body = expect_body_stmt();
     if (body.errored)
-      return nullptr;
+      return Failure::kErrored;
     return std::move(body.value);
   }
 
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 // return_stmt
 //   : RETURN logical_or_expression?
-std::unique_ptr<ast::ReturnStatement> ParserImpl::return_stmt() {
+Maybe<std::unique_ptr<ast::ReturnStatement>> ParserImpl::return_stmt() {
   Source source;
   if (!match(Token::Type::kReturn, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
-  std::unique_ptr<ast::Expression> expr = nullptr;
-  if (!peek().IsSemicolon()) {
-    expr = logical_or_expression();
-    if (has_error())
-      return nullptr;
-  }
-  return std::make_unique<ast::ReturnStatement>(source, std::move(expr));
+  if (peek().IsSemicolon())
+    return std::make_unique<ast::ReturnStatement>(source, nullptr);
+
+  auto expr = logical_or_expression();
+  if (expr.errored)
+    return Failure::kErrored;
+  // TODO(bclayton): Check matched?
+  return std::make_unique<ast::ReturnStatement>(source, std::move(expr.value));
 }
 
 // variable_stmt
 //   : variable_decl
 //   | variable_decl EQUAL logical_or_expression
 //   | CONST variable_ident_decl EQUAL logical_or_expression
-std::unique_ptr<ast::VariableDeclStatement> ParserImpl::variable_stmt() {
-  auto t = peek();
-  if (t.IsConst()) {
-    next();  // Consume the peek
-
+Maybe<std::unique_ptr<ast::VariableDeclStatement>> ParserImpl::variable_stmt() {
+  if (match(Token::Type::kConst)) {
     auto decl = expect_variable_ident_decl("constant declaration");
-    if (has_error())
-      return nullptr;
+    if (decl.errored)
+      return Failure::kErrored;
 
     if (!expect("constant declaration", Token::Type::kEqual))
-      return nullptr;
+      return Failure::kErrored;
 
     auto constructor = logical_or_expression();
-    if (has_error())
-      return nullptr;
-    if (constructor == nullptr) {
-      add_error(peek(), "missing constructor for const declaration");
-      return nullptr;
-    }
+    if (constructor.errored)
+      return Failure::kErrored;
+    if (!constructor.matched)
+      return add_error(peek(), "missing constructor for const declaration");
 
     auto var = std::make_unique<ast::Variable>(
         decl->source, decl->name, ast::StorageClass::kNone, decl->type);
     var->set_is_const(true);
-    var->set_constructor(std::move(constructor));
+    var->set_constructor(std::move(constructor.value));
 
     return std::make_unique<ast::VariableDeclStatement>(decl->source,
                                                         std::move(var));
   }
 
   auto var = variable_decl();
-  if (has_error())
-    return nullptr;
-  if (var == nullptr)
-    return nullptr;
+  if (var.errored)
+    return Failure::kErrored;
+  if (!var.matched)
+    return Failure::kNoMatch;
 
   if (match(Token::Type::kEqual)) {
     auto constructor = logical_or_expression();
-    if (has_error())
-      return nullptr;
-    if (constructor == nullptr) {
-      add_error(peek(), "missing constructor for variable declaration");
-      return nullptr;
-    }
-    var->set_constructor(std::move(constructor));
+    if (constructor.errored)
+      return Failure::kErrored;
+    if (!constructor.matched)
+      return add_error(peek(), "missing constructor for variable declaration");
+
+    var.value->set_constructor(std::move(constructor.value));
   }
 
-  return std::make_unique<ast::VariableDeclStatement>(var->source(),
-                                                      std::move(var));
+  return std::make_unique<ast::VariableDeclStatement>(var.value->source(),
+                                                      std::move(var.value));
 }
 
 // if_stmt
 //   : IF paren_rhs_stmt body_stmt elseif_stmt? else_stmt?
-std::unique_ptr<ast::IfStatement> ParserImpl::if_stmt() {
+Maybe<std::unique_ptr<ast::IfStatement>> ParserImpl::if_stmt() {
   Source source;
   if (!match(Token::Type::kIf, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto condition = expect_paren_rhs_stmt();
   if (condition.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto body = expect_body_stmt();
   if (body.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto elseif = elseif_stmt();
-  if (has_error())
-    return nullptr;
+  if (elseif.errored)
+    return Failure::kErrored;
 
   auto el = else_stmt();
-  if (has_error())
-    return nullptr;
+  if (el.errored)
+    return Failure::kErrored;
 
   auto stmt = std::make_unique<ast::IfStatement>(
       source, std::move(condition.value), std::move(body.value));
-  if (el != nullptr) {
-    elseif.push_back(std::move(el));
+  if (el.matched) {
+    elseif.value.push_back(std::move(el.value));
   }
-  stmt->set_else_statements(std::move(elseif));
+  stmt->set_else_statements(std::move(elseif.value));
 
   return stmt;
 }
 
 // elseif_stmt
 //   : ELSE_IF paren_rhs_stmt body_stmt elseif_stmt?
-ast::ElseStatementList ParserImpl::elseif_stmt() {
-  auto t = peek();
-  if (!t.IsElseIf())
-    return {};
+Maybe<ast::ElseStatementList> ParserImpl::elseif_stmt() {
+  Source source;
+  if (!match(Token::Type::kElseIf, &source))
+    return Failure::kNoMatch;
 
   ast::ElseStatementList ret;
   for (;;) {
-    auto source = t.source();
-    next();  // Consume the peek
-
     auto condition = expect_paren_rhs_stmt();
     if (condition.errored)
-      return {};
+      return Failure::kErrored;
 
     auto body = expect_body_stmt();
     if (body.errored)
-      return {};
+      return Failure::kErrored;
 
     ret.push_back(std::make_unique<ast::ElseStatement>(
         source, std::move(condition.value), std::move(body.value)));
 
-    t = peek();
-    if (!t.IsElseIf())
+    if (!match(Token::Type::kElseIf, &source))
       break;
   }
 
@@ -1563,60 +1560,58 @@
 
 // else_stmt
 //   : ELSE body_stmt
-std::unique_ptr<ast::ElseStatement> ParserImpl::else_stmt() {
-  auto t = peek();
-  if (!t.IsElse())
-    return nullptr;
-
-  auto source = t.source();
-  next();  // Consume the peek
+Maybe<std::unique_ptr<ast::ElseStatement>> ParserImpl::else_stmt() {
+  Source source;
+  if (!match(Token::Type::kElse, &source))
+    return Failure::kNoMatch;
 
   auto body = expect_body_stmt();
   if (body.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   return std::make_unique<ast::ElseStatement>(source, std::move(body.value));
 }
 
 // switch_stmt
 //   : SWITCH paren_rhs_stmt BRACKET_LEFT switch_body+ BRACKET_RIGHT
-std::unique_ptr<ast::SwitchStatement> ParserImpl::switch_stmt() {
+Maybe<std::unique_ptr<ast::SwitchStatement>> ParserImpl::switch_stmt() {
   Source source;
   if (!match(Token::Type::kSwitch, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto condition = expect_paren_rhs_stmt();
   if (condition.errored)
-    return nullptr;
+    return Failure::kErrored;
 
-  ast::CaseStatementList body;
-  bool ok = expect_brace_block_old("switch statement", [&] {
-    for (;;) {
-      auto stmt = switch_body();
-      if (has_error())
-        return false;
-      if (stmt == nullptr)
-        break;
+  auto body = expect_brace_block("switch statement",
+                                 [&]() -> Expect<ast::CaseStatementList> {
+                                   ast::CaseStatementList list;
+                                   for (;;) {
+                                     auto stmt = switch_body();
+                                     if (stmt.errored)
+                                       return Failure::kErrored;
+                                     if (!stmt.matched)
+                                       break;
 
-      body.push_back(std::move(stmt));
-    }
-    return true;
-  });
+                                     list.push_back(std::move(stmt.value));
+                                   }
+                                   return std::move(list);
+                                 });
 
-  if (!ok)
-    return nullptr;
+  if (body.errored)
+    return Failure::kErrored;
 
   return std::make_unique<ast::SwitchStatement>(
-      source, std::move(condition.value), std::move(body));
+      source, std::move(condition.value), std::move(body.value));
 }
 
 // switch_body
 //   : CASE case_selectors COLON BRACKET_LEFT case_body BRACKET_RIGHT
 //   | DEFAULT COLON BRACKET_LEFT case_body BRACKET_RIGHT
-std::unique_ptr<ast::CaseStatement> ParserImpl::switch_body() {
+Maybe<std::unique_ptr<ast::CaseStatement>> ParserImpl::switch_body() {
   auto t = peek();
   if (!t.IsCase() && !t.IsDefault())
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto source = t.source();
   next();  // Consume the peek
@@ -1625,26 +1620,25 @@
   stmt->set_source(source);
   if (t.IsCase()) {
     auto selectors = expect_case_selectors();
-    if (has_error())
-      return nullptr;
-    if (selectors.value.empty()) {
-      add_error(peek(), "unable to parse case selectors");
-      return nullptr;
-    }
+    if (selectors.errored)
+      return Failure::kErrored;
+
     stmt->set_selectors(std::move(selectors.value));
   }
 
   const char* use = "case statement";
 
   if (!expect(use, Token::Type::kColon))
-    return nullptr;
+    return Failure::kErrored;
 
-  auto body = expect_brace_block_old(use, [&] { return case_body(); });
+  auto body = expect_brace_block(use, [&] { return case_body(); });
 
-  if (body == nullptr)
-    return nullptr;
+  if (body.errored)
+    return Failure::kErrored;
+  if (!body.matched)
+    return add_error(body.source, "expected case body");
 
-  stmt->set_body(std::move(body));
+  stmt->set_body(std::move(body.value));
 
   return stmt;
 }
@@ -1657,14 +1651,14 @@
   for (;;) {
     auto t = peek();
     auto cond = const_literal();
-    if (has_error())
+    if (cond.errored)
       return Failure::kErrored;
-    if (cond == nullptr)
+    if (!cond.matched)
       break;
-    if (!cond->IsInt())
+    if (!cond.value->IsInt())
       return add_error(t, "invalid case selector must be an integer value");
 
-    std::unique_ptr<ast::IntLiteral> selector(cond.release()->AsInt());
+    std::unique_ptr<ast::IntLiteral> selector(cond.value.release()->AsInt());
     selectors.push_back(std::move(selector));
   }
 
@@ -1678,7 +1672,7 @@
 //   :
 //   | statement case_body
 //   | FALLTHROUGH SEMICOLON
-std::unique_ptr<ast::BlockStatement> ParserImpl::case_body() {
+Maybe<std::unique_ptr<ast::BlockStatement>> ParserImpl::case_body() {
   auto ret = std::make_unique<ast::BlockStatement>();
   for (;;) {
     auto t = peek();
@@ -1687,19 +1681,19 @@
       next();  // Consume the peek
 
       if (!expect("fallthrough statement", Token::Type::kSemicolon))
-        return nullptr;
+        return Failure::kErrored;
 
       ret->append(std::make_unique<ast::FallthroughStatement>(source));
       break;
     }
 
     auto stmt = statement();
-    if (has_error())
-      return {};
-    if (stmt == nullptr)
+    if (stmt.errored)
+      return Failure::kErrored;
+    if (!stmt.matched)
       break;
 
-    ret->append(std::move(stmt));
+    ret->append(std::move(stmt.value));
   }
 
   return ret;
@@ -1707,23 +1701,23 @@
 
 // loop_stmt
 //   : LOOP BRACKET_LEFT statements continuing_stmt? BRACKET_RIGHT
-std::unique_ptr<ast::LoopStatement> ParserImpl::loop_stmt() {
+Maybe<std::unique_ptr<ast::LoopStatement>> ParserImpl::loop_stmt() {
   Source source;
   if (!match(Token::Type::kLoop, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
-  return expect_brace_block_old(
-      "loop", [&]() -> std::unique_ptr<ast::LoopStatement> {
+  return expect_brace_block(
+      "loop", [&]() -> Maybe<std::unique_ptr<ast::LoopStatement>> {
         auto body = expect_statements();
         if (body.errored)
-          return nullptr;
+          return Failure::kErrored;
 
         auto continuing = continuing_stmt();
-        if (has_error())
-          return nullptr;
+        if (continuing.errored)
+          return Failure::kErrored;
 
         return std::make_unique<ast::LoopStatement>(
-            source, std::move(body.value), std::move(continuing));
+            source, std::move(body.value), std::move(continuing.value));
       });
 }
 
@@ -1736,78 +1730,91 @@
 
 ForHeader::~ForHeader() = default;
 
+// (variable_stmt | assignment_stmt | func_call_stmt)?
+Maybe<std::unique_ptr<ast::Statement>> ParserImpl::for_header_initializer() {
+  auto call = func_call_stmt();
+  if (call.errored)
+    return Failure::kErrored;
+  if (call.matched)
+    return std::move(call.value);
+
+  auto var = variable_stmt();
+  if (var.errored)
+    return Failure::kErrored;
+  if (var.matched)
+    return std::move(var.value);
+
+  auto assign = assignment_stmt();
+  if (assign.errored)
+    return Failure::kErrored;
+  if (assign.matched)
+    return std::move(assign.value);
+
+  return Failure::kNoMatch;
+}
+
+// (assignment_stmt | func_call_stmt)?
+Maybe<std::unique_ptr<ast::Statement>> ParserImpl::for_header_continuing() {
+  auto call_stmt = func_call_stmt();
+  if (call_stmt.errored)
+    return Failure::kErrored;
+  if (call_stmt.matched)
+    return std::move(call_stmt.value);
+
+  auto assign = assignment_stmt();
+  if (assign.errored)
+    return Failure::kErrored;
+  if (assign.matched)
+    return std::move(assign.value);
+
+  return Failure::kNoMatch;
+}
+
 // for_header
 //   : (variable_stmt | assignment_stmt | func_call_stmt)?
 //   SEMICOLON
 //      logical_or_expression? SEMICOLON
 //      (assignment_stmt | func_call_stmt)?
 Expect<std::unique_ptr<ForHeader>> ParserImpl::expect_for_header() {
-  std::unique_ptr<ast::Statement> initializer = nullptr;
-  if (initializer == nullptr) {
-    initializer = func_call_stmt();
-    if (has_error()) {
-      return Failure::kErrored;
-    }
-  }
-  if (initializer == nullptr) {
-    initializer = variable_stmt();
-    if (has_error()) {
-      return Failure::kErrored;
-    }
-  }
-  if (initializer == nullptr) {
-    initializer = assignment_stmt();
-    if (has_error()) {
-      return Failure::kErrored;
-    }
-  }
+  auto initializer = for_header_initializer();
+  if (initializer.errored)
+    return Failure::kErrored;
 
   if (!expect("initializer in for loop", Token::Type::kSemicolon))
     return Failure::kErrored;
 
   auto condition = logical_or_expression();
-  if (has_error()) {
+  if (condition.errored)
     return Failure::kErrored;
-  }
 
   if (!expect("condition in for loop", Token::Type::kSemicolon))
     return Failure::kErrored;
 
-  std::unique_ptr<ast::Statement> continuing = nullptr;
-  if (continuing == nullptr) {
-    continuing = func_call_stmt();
-    if (has_error()) {
-      return Failure::kErrored;
-    }
-  }
-  if (continuing == nullptr) {
-    continuing = assignment_stmt();
-    if (has_error()) {
-      return Failure::kErrored;
-    }
-  }
+  auto continuing = for_header_continuing();
+  if (continuing.errored)
+    return Failure::kErrored;
 
-  return std::make_unique<ForHeader>(
-      std::move(initializer), std::move(condition), std::move(continuing));
+  return std::make_unique<ForHeader>(std::move(initializer.value),
+                                     std::move(condition.value),
+                                     std::move(continuing.value));
 }
 
 // for_statement
 //   : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT
-std::unique_ptr<ast::Statement> ParserImpl::for_stmt() {
+Maybe<std::unique_ptr<ast::Statement>> ParserImpl::for_stmt() {
   Source source;
   if (!match(Token::Type::kFor, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto header =
       expect_paren_block("for loop", [&] { return expect_for_header(); });
   if (header.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   auto body =
       expect_brace_block("for loop", [&] { return expect_statements(); });
-
   if (body.errored)
-    return nullptr;
+    return Failure::kErrored;
 
   // The for statement is a syntactic sugar on top of the loop statement.
   // We create corresponding nodes in ast with the exact same behaviour
@@ -1852,11 +1859,11 @@
 
 // func_call_stmt
 //    : IDENT PAREN_LEFT argument_expression_list* PAREN_RIGHT
-std::unique_ptr<ast::CallStatement> ParserImpl::func_call_stmt() {
+Maybe<std::unique_ptr<ast::CallStatement>> ParserImpl::func_call_stmt() {
   auto t = peek();
   auto t2 = peek(1);
   if (!t.IsIdentifier() || !t2.IsParenLeft())
-    return nullptr;
+    return Failure::kNoMatch;
 
   auto source = t.source();
 
@@ -1871,12 +1878,12 @@
   if (!t.IsParenRight() && !t.IsEof()) {
     auto list = expect_argument_expression_list();
     if (list.errored)
-      return nullptr;
+      return Failure::kErrored;
     params = std::move(list.value);
   }
 
   if (!expect("call statement", Token::Type::kParenRight))
-    return nullptr;
+    return Failure::kErrored;
 
   return std::make_unique<ast::CallStatement>(
       std::make_unique<ast::CallExpression>(
@@ -1886,31 +1893,31 @@
 
 // break_stmt
 //   : BREAK
-std::unique_ptr<ast::BreakStatement> ParserImpl::break_stmt() {
+Maybe<std::unique_ptr<ast::BreakStatement>> ParserImpl::break_stmt() {
   Source source;
   if (!match(Token::Type::kBreak, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
   return std::make_unique<ast::BreakStatement>(source);
 }
 
 // continue_stmt
 //   : CONTINUE
-std::unique_ptr<ast::ContinueStatement> ParserImpl::continue_stmt() {
+Maybe<std::unique_ptr<ast::ContinueStatement>> ParserImpl::continue_stmt() {
   Source source;
   if (!match(Token::Type::kContinue, &source))
-    return nullptr;
+    return Failure::kNoMatch;
 
   return std::make_unique<ast::ContinueStatement>(source);
 }
 
 // continuing_stmt
 //   : CONTINUING body_stmt
-std::unique_ptr<ast::BlockStatement> ParserImpl::continuing_stmt() {
+Maybe<std::unique_ptr<ast::BlockStatement>> ParserImpl::continuing_stmt() {
   if (!match(Token::Type::kContinuing))
     return std::make_unique<ast::BlockStatement>();
 
-  return expect_body_stmt().value;
+  return expect_body_stmt();
 }
 
 // primary_expression
@@ -1919,90 +1926,78 @@
 //   | const_literal
 //   | paren_rhs_stmt
 //   | BITCAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
-std::unique_ptr<ast::Expression> ParserImpl::primary_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::primary_expression() {
   auto t = peek();
   auto source = t.source();
 
   auto lit = const_literal();
-  if (has_error())
-    return nullptr;
-  if (lit != nullptr) {
-    return std::make_unique<ast::ScalarConstructorExpression>(source,
-                                                              std::move(lit));
-  }
+  if (lit.errored)
+    return Failure::kErrored;
+  if (lit.matched)
+    return std::make_unique<ast::ScalarConstructorExpression>(
+        source, std::move(lit.value));
 
-  t = peek();
   if (t.IsParenLeft()) {
     auto paren = expect_paren_rhs_stmt();
     if (paren.errored)
-      return nullptr;
+      return Failure::kErrored;
 
     return std::move(paren.value);
   }
 
-  if (t.IsBitcast()) {
-    auto src = t;
+  if (match(Token::Type::kBitcast)) {
+    const char* use = "bitcast expression";
 
-    next();  // Consume the peek
+    if (!expect(use, Token::Type::kLessThan))
+      return Failure::kErrored;
 
-    t = next();
-    if (!t.IsLessThan()) {
-      add_error(t, "missing < for bitcast expression");
-      return nullptr;
-    }
+    auto type = type_decl();
+    if (type.errored)
+      return Failure::kErrored;
+    if (!type.matched)
+      return add_error(peek().source(), "missing type", use);
 
-    auto* type = type_decl();
-    if (has_error())
-      return nullptr;
-    if (type == nullptr) {
-      add_error(peek(), "missing type for bitcast expression");
-      return nullptr;
-    }
-
-    t = next();
-    if (!t.IsGreaterThan()) {
-      add_error(t, "missing > for bitcast expression");
-      return nullptr;
-    }
+    if (!expect(use, Token::Type::kGreaterThan))
+      return Failure::kErrored;
 
     auto params = expect_paren_rhs_stmt();
     if (params.errored)
-      return nullptr;
+      return Failure::kErrored;
 
-    return std::make_unique<ast::BitcastExpression>(source, type,
+    return std::make_unique<ast::BitcastExpression>(source, type.value,
                                                     std::move(params.value));
-  } else if (t.IsIdentifier()) {
-    next();  // Consume the peek
-
-    return std::make_unique<ast::IdentifierExpression>(source, t.to_str());
   }
 
-  auto* type = type_decl();
-  if (has_error())
-    return nullptr;
-  if (type != nullptr) {
+  if (match(Token::Type::kIdentifier))
+    return std::make_unique<ast::IdentifierExpression>(t.source(), t.to_str());
+
+  auto type = type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (type.matched) {
     auto expr = expect_paren_block(
         "type constructor",
         [&]() -> Expect<std::unique_ptr<ast::TypeConstructorExpression>> {
           t = peek();
           if (t.IsParenRight() || t.IsEof())
             return std::make_unique<ast::TypeConstructorExpression>(
-                source, type, ast::ExpressionList{});
+                source, type.value, ast::ExpressionList{});
 
           auto params = expect_argument_expression_list();
           if (params.errored)
             return Failure::kErrored;
 
           return std::make_unique<ast::TypeConstructorExpression>(
-              source, type, std::move(params.value));
+              source, type.value, std::move(params.value));
         });
 
     if (expr.errored)
-      return nullptr;
+      return Failure::kErrored;
 
     return std::move(expr.value);
   }
-  return nullptr;
+
+  return Failure::kNoMatch;
 }
 
 // postfix_expr
@@ -2010,98 +2005,88 @@
 //   | BRACE_LEFT logical_or_expression BRACE_RIGHT postfix_expr
 //   | PAREN_LEFT argument_expression_list* PAREN_RIGHT postfix_expr
 //   | PERIOD IDENTIFIER postfix_expr
-std::unique_ptr<ast::Expression> ParserImpl::postfix_expr(
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::postfix_expr(
     std::unique_ptr<ast::Expression> prefix) {
-  std::unique_ptr<ast::Expression> expr = nullptr;
-
-  auto t = peek();
-  auto source = t.source();
-  if (t.IsBracketLeft()) {
-    next();  // Consume the peek
-
+  Source source;
+  if (match(Token::Type::kBracketLeft, &source)) {
     auto param = logical_or_expression();
-    if (has_error())
-      return nullptr;
-    if (param == nullptr) {
-      add_error(peek(), "unable to parse expression inside []");
-      return nullptr;
-    }
+    if (param.errored)
+      return Failure::kErrored;
+    if (!param.matched)
+      return add_error(peek(), "unable to parse expression inside []");
 
-    t = next();
-    if (!t.IsBracketRight()) {
-      add_error(t, "missing ] for array accessor");
-      return nullptr;
-    }
-    expr = std::make_unique<ast::ArrayAccessorExpression>(
-        source, std::move(prefix), std::move(param));
+    if (!expect("array accessor", Token::Type::kBracketRight))
+      return Failure::kErrored;
 
-  } else if (t.IsParenLeft()) {
-    next();  // Consume the peek
+    return postfix_expr(std::make_unique<ast::ArrayAccessorExpression>(
+        source, std::move(prefix), std::move(param.value)));
+  }
 
+  if (match(Token::Type::kParenLeft, &source)) {
     ast::ExpressionList params;
 
-    t = peek();
+    auto t = peek();
     if (!t.IsParenRight() && !t.IsEof()) {
       auto list = expect_argument_expression_list();
       if (list.errored)
-        return nullptr;
+        return Failure::kErrored;
       params = std::move(list.value);
     }
 
     if (!expect("call expression", Token::Type::kParenRight))
-      return nullptr;
+      return Failure::kErrored;
 
-    expr = std::make_unique<ast::CallExpression>(source, std::move(prefix),
-                                                 std::move(params));
-  } else if (t.IsPeriod()) {
-    next();  // Consume the peek
+    return postfix_expr(std::make_unique<ast::CallExpression>(
+        source, std::move(prefix), std::move(params)));
+  }
 
+  if (match(Token::Type::kPeriod)) {
     auto ident = expect_ident("member accessor");
     if (ident.errored)
-      return nullptr;
+      return Failure::kErrored;
 
-    expr = std::make_unique<ast::MemberAccessorExpression>(
+    return postfix_expr(std::make_unique<ast::MemberAccessorExpression>(
         ident.source, std::move(prefix),
-        std::make_unique<ast::IdentifierExpression>(ident.source, ident.value));
-  } else {
-    return prefix;
+        std::make_unique<ast::IdentifierExpression>(ident.source,
+                                                    ident.value)));
   }
-  return postfix_expr(std::move(expr));
+
+  return prefix;
 }
 
 // postfix_expression
 //   : primary_expression postfix_expr
-std::unique_ptr<ast::Expression> ParserImpl::postfix_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::postfix_expression() {
   auto prefix = primary_expression();
-  if (has_error())
-    return nullptr;
-  if (prefix == nullptr)
-    return nullptr;
+  if (prefix.errored)
+    return Failure::kErrored;
+  if (!prefix.matched)
+    return Failure::kNoMatch;
 
-  return postfix_expr(std::move(prefix));
+  return postfix_expr(std::move(prefix.value));
 }
 
 // argument_expression_list
 //   : (logical_or_expression COMMA)* logical_or_expression
 Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list() {
   auto arg = logical_or_expression();
-  if (has_error())
+  if (arg.errored)
     return Failure::kErrored;
-  if (arg == nullptr)
+  if (!arg.matched)
     return add_error(peek(), "unable to parse argument expression");
 
   ast::ExpressionList ret;
-  ret.push_back(std::move(arg));
+  ret.push_back(std::move(arg.value));
 
   while (match(Token::Type::kComma)) {
     arg = logical_or_expression();
-    if (has_error())
+    if (arg.errored)
       return Failure::kErrored;
-    if (arg == nullptr) {
+    if (!arg.matched) {
       return add_error(peek(),
                        "unable to parse argument expression after comma");
     }
-    ret.push_back(std::move(arg));
+    ret.push_back(std::move(arg.value));
   }
   return ret;
 }
@@ -2110,7 +2095,7 @@
 //   : postfix_expression
 //   | MINUS unary_expression
 //   | BANG unary_expression
-std::unique_ptr<ast::Expression> ParserImpl::unary_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::unary_expression() {
   auto t = peek();
   auto source = t.source();
   if (t.IsMinus() || t.IsBang()) {
@@ -2123,15 +2108,14 @@
       op = ast::UnaryOp::kNot;
 
     auto expr = unary_expression();
-    if (has_error())
-      return nullptr;
-    if (expr == nullptr) {
-      add_error(peek(),
-                "unable to parse right side of " + name + " expression");
-      return nullptr;
-    }
+    if (expr.errored)
+      return Failure::kErrored;
+    if (!expr.matched)
+      return add_error(peek(),
+                       "unable to parse right side of " + name + " expression");
+
     return std::make_unique<ast::UnaryOpExpression>(source, op,
-                                                    std::move(expr));
+                                                    std::move(expr.value));
   }
   return postfix_expression();
 }
@@ -2160,26 +2144,28 @@
   next();  // Consume the peek
 
   auto rhs = unary_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr) {
+  if (!rhs.matched) {
     return add_error(peek(),
                      "unable to parse right side of " + name + " expression");
   }
+
   return expect_multiplicative_expr(std::make_unique<ast::BinaryExpression>(
-      source, op, std::move(lhs), std::move(rhs)));
+      source, op, std::move(lhs), std::move(rhs.value)));
 }
 
 // multiplicative_expression
 //   : unary_expression multiplicative_expr
-std::unique_ptr<ast::Expression> ParserImpl::multiplicative_expression() {
+Maybe<std::unique_ptr<ast::Expression>>
+ParserImpl::multiplicative_expression() {
   auto lhs = unary_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_multiplicative_expr(std::move(lhs)).value;
+  return expect_multiplicative_expr(std::move(lhs.value));
 }
 
 // additive_expr
@@ -2202,24 +2188,25 @@
   next();  // Consume the peek
 
   auto rhs = multiplicative_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr)
+  if (!rhs.matched)
     return add_error(peek(), "unable to parse right side of + expression");
+
   return expect_additive_expr(std::make_unique<ast::BinaryExpression>(
-      source, op, std::move(lhs), std::move(rhs)));
+      source, op, std::move(lhs), std::move(rhs.value)));
 }
 
 // additive_expression
 //   : multiplicative_expression additive_expr
-std::unique_ptr<ast::Expression> ParserImpl::additive_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::additive_expression() {
   auto lhs = multiplicative_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_additive_expr(std::move(lhs)).value;
+  return expect_additive_expr(std::move(lhs.value));
 }
 
 // shift_expr
@@ -2249,26 +2236,26 @@
   }
 
   auto rhs = additive_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr) {
+  if (!rhs.matched) {
     return add_error(peek(), std::string("unable to parse right side of ") +
                                  name + " expression");
   }
   return expect_shift_expr(std::make_unique<ast::BinaryExpression>(
-      source, op, std::move(lhs), std::move(rhs)));
-}
+      source, op, std::move(lhs), std::move(rhs.value)));
+}  // namespace wgsl
 
 // shift_expression
 //   : additive_expression shift_expr
-std::unique_ptr<ast::Expression> ParserImpl::shift_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::shift_expression() {
   auto lhs = additive_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_shift_expr(std::move(lhs)).value;
+  return expect_shift_expr(std::move(lhs.value));
 }
 
 // relational_expr
@@ -2297,27 +2284,27 @@
   next();  // Consume the peek
 
   auto rhs = shift_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr) {
+  if (!rhs.matched) {
     return add_error(peek(),
                      "unable to parse right side of " + name + " expression");
   }
 
   return expect_relational_expr(std::make_unique<ast::BinaryExpression>(
-      source, op, std::move(lhs), std::move(rhs)));
+      source, op, std::move(lhs), std::move(rhs.value)));
 }
 
 // relational_expression
 //   : shift_expression relational_expr
-std::unique_ptr<ast::Expression> ParserImpl::relational_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::relational_expression() {
   auto lhs = shift_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_relational_expr(std::move(lhs)).value;
+  return expect_relational_expr(std::move(lhs.value));
 }
 
 // equality_expr
@@ -2340,27 +2327,27 @@
   next();  // Consume the peek
 
   auto rhs = relational_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr) {
+  if (!rhs.matched) {
     return add_error(peek(),
                      "unable to parse right side of " + name + " expression");
   }
 
   return expect_equality_expr(std::make_unique<ast::BinaryExpression>(
-      source, op, std::move(lhs), std::move(rhs)));
+      source, op, std::move(lhs), std::move(rhs.value)));
 }
 
 // equality_expression
 //   : relational_expression equality_expr
-std::unique_ptr<ast::Expression> ParserImpl::equality_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::equality_expression() {
   auto lhs = relational_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_equality_expr(std::move(lhs)).value;
+  return expect_equality_expr(std::move(lhs.value));
 }
 
 // and_expr
@@ -2376,25 +2363,25 @@
   next();  // Consume the peek
 
   auto rhs = equality_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr)
+  if (!rhs.matched)
     return add_error(peek(), "unable to parse right side of & expression");
 
   return expect_and_expr(std::make_unique<ast::BinaryExpression>(
-      source, ast::BinaryOp::kAnd, std::move(lhs), std::move(rhs)));
+      source, ast::BinaryOp::kAnd, std::move(lhs), std::move(rhs.value)));
 }
 
 // and_expression
 //   : equality_expression and_expr
-std::unique_ptr<ast::Expression> ParserImpl::and_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::and_expression() {
   auto lhs = equality_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_and_expr(std::move(lhs)).value;
+  return expect_and_expr(std::move(lhs.value));
 }
 
 // exclusive_or_expr
@@ -2402,33 +2389,30 @@
 //   | XOR and_expression exclusive_or_expr
 Expect<std::unique_ptr<ast::Expression>> ParserImpl::expect_exclusive_or_expr(
     std::unique_ptr<ast::Expression> lhs) {
-  auto t = peek();
-  if (!t.IsXor())
+  Source source;
+  if (!match(Token::Type::kXor, &source))
     return lhs;
 
-  auto source = t.source();
-  next();  // Consume the peek
-
   auto rhs = and_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr)
+  if (!rhs.matched)
     return add_error(peek(), "unable to parse right side of ^ expression");
 
   return expect_exclusive_or_expr(std::make_unique<ast::BinaryExpression>(
-      source, ast::BinaryOp::kXor, std::move(lhs), std::move(rhs)));
+      source, ast::BinaryOp::kXor, std::move(lhs), std::move(rhs.value)));
 }
 
 // exclusive_or_expression
 //   : and_expression exclusive_or_expr
-std::unique_ptr<ast::Expression> ParserImpl::exclusive_or_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::exclusive_or_expression() {
   auto lhs = and_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_exclusive_or_expr(std::move(lhs)).value;
+  return expect_exclusive_or_expr(std::move(lhs.value));
 }
 
 // inclusive_or_expr
@@ -2436,33 +2420,30 @@
 //   | OR exclusive_or_expression inclusive_or_expr
 Expect<std::unique_ptr<ast::Expression>> ParserImpl::expect_inclusive_or_expr(
     std::unique_ptr<ast::Expression> lhs) {
-  auto t = peek();
-  if (!t.IsOr())
+  Source source;
+  if (!match(Token::Type::kOr))
     return lhs;
 
-  auto source = t.source();
-  next();  // Consume the peek
-
   auto rhs = exclusive_or_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr)
+  if (!rhs.matched)
     return add_error(peek(), "unable to parse right side of | expression");
 
   return expect_inclusive_or_expr(std::make_unique<ast::BinaryExpression>(
-      source, ast::BinaryOp::kOr, std::move(lhs), std::move(rhs)));
+      source, ast::BinaryOp::kOr, std::move(lhs), std::move(rhs.value)));
 }
 
 // inclusive_or_expression
 //   : exclusive_or_expression inclusive_or_expr
-std::unique_ptr<ast::Expression> ParserImpl::inclusive_or_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::inclusive_or_expression() {
   auto lhs = exclusive_or_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_inclusive_or_expr(std::move(lhs)).value;
+  return expect_inclusive_or_expr(std::move(lhs.value));
 }
 
 // logical_and_expr
@@ -2478,25 +2459,26 @@
   next();  // Consume the peek
 
   auto rhs = inclusive_or_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr)
+  if (!rhs.matched)
     return add_error(peek(), "unable to parse right side of && expression");
 
   return expect_logical_and_expr(std::make_unique<ast::BinaryExpression>(
-      source, ast::BinaryOp::kLogicalAnd, std::move(lhs), std::move(rhs)));
+      source, ast::BinaryOp::kLogicalAnd, std::move(lhs),
+      std::move(rhs.value)));
 }
 
 // logical_and_expression
 //   : inclusive_or_expression logical_and_expr
-std::unique_ptr<ast::Expression> ParserImpl::logical_and_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::logical_and_expression() {
   auto lhs = inclusive_or_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_logical_and_expr(std::move(lhs)).value;
+  return expect_logical_and_expr(std::move(lhs.value));
 }
 
 // logical_or_expr
@@ -2504,63 +2486,55 @@
 //   | OR_OR logical_and_expression logical_or_expr
 Expect<std::unique_ptr<ast::Expression>> ParserImpl::expect_logical_or_expr(
     std::unique_ptr<ast::Expression> lhs) {
-  auto t = peek();
-  if (!t.IsOrOr())
+  Source source;
+  if (!match(Token::Type::kOrOr))
     return lhs;
 
-  auto source = t.source();
-  next();  // Consume the peek
-
   auto rhs = logical_and_expression();
-  if (has_error())
+  if (rhs.errored)
     return Failure::kErrored;
-  if (rhs == nullptr)
+  if (!rhs.matched)
     return add_error(peek(), "unable to parse right side of || expression");
 
   return expect_logical_or_expr(std::make_unique<ast::BinaryExpression>(
-      source, ast::BinaryOp::kLogicalOr, std::move(lhs), std::move(rhs)));
+      source, ast::BinaryOp::kLogicalOr, std::move(lhs), std::move(rhs.value)));
 }
 
 // logical_or_expression
 //   : logical_and_expression logical_or_expr
-std::unique_ptr<ast::Expression> ParserImpl::logical_or_expression() {
+Maybe<std::unique_ptr<ast::Expression>> ParserImpl::logical_or_expression() {
   auto lhs = logical_and_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  return expect_logical_or_expr(std::move(lhs)).value;
+  return expect_logical_or_expr(std::move(lhs.value));
 }
 
 // assignment_stmt
 //   : unary_expression EQUAL logical_or_expression
-std::unique_ptr<ast::AssignmentStatement> ParserImpl::assignment_stmt() {
+Maybe<std::unique_ptr<ast::AssignmentStatement>> ParserImpl::assignment_stmt() {
   auto t = peek();
   auto source = t.source();
 
   auto lhs = unary_expression();
-  if (has_error())
-    return nullptr;
-  if (lhs == nullptr)
-    return nullptr;
+  if (lhs.errored)
+    return Failure::kErrored;
+  if (!lhs.matched)
+    return Failure::kNoMatch;
 
-  t = next();
-  if (!t.IsEqual()) {
-    add_error(t, "missing = for assignment");
-    return nullptr;
-  }
+  if (!expect("assignment", Token::Type::kEqual))
+    return Failure::kErrored;
 
   auto rhs = logical_or_expression();
-  if (has_error())
-    return nullptr;
-  if (rhs == nullptr) {
-    add_error(peek(), "unable to parse right side of assignment");
-    return nullptr;
-  }
+  if (rhs.errored)
+    return Failure::kErrored;
+  if (!rhs.matched)
+    return add_error(peek(), "unable to parse right side of assignment");
 
-  return std::make_unique<ast::AssignmentStatement>(source, std::move(lhs),
-                                                    std::move(rhs));
+  return std::make_unique<ast::AssignmentStatement>(
+      source, std::move(lhs.value), std::move(rhs.value));
 }
 
 // const_literal
@@ -2569,7 +2543,7 @@
 //   | FLOAT_LITERAL
 //   | TRUE
 //   | FALSE
-std::unique_ptr<ast::Literal> ParserImpl::const_literal() {
+Maybe<std::unique_ptr<ast::Literal>> ParserImpl::const_literal() {
   auto t = peek();
   if (match(Token::Type::kTrue)) {
     auto* type = ctx_.type_mgr().Get(std::make_unique<ast::type::BoolType>());
@@ -2591,7 +2565,7 @@
     auto* type = ctx_.type_mgr().Get(std::make_unique<ast::type::F32Type>());
     return std::make_unique<ast::FloatLiteral>(type, t.to_f32());
   }
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 // const_expr
@@ -2612,8 +2586,10 @@
 
   auto source = t.source();
 
-  auto* type = type_decl();
-  if (type != nullptr) {
+  auto type = type_decl();
+  if (type.errored)
+    return Failure::kErrored;
+  if (type.matched) {
     auto params = expect_paren_block(
         "type constructor", [&]() -> Expect<ast::ExpressionList> {
           ast::ExpressionList list;
@@ -2634,42 +2610,52 @@
       return Failure::kErrored;
 
     return std::make_unique<ast::TypeConstructorExpression>(
-        source, type, std::move(params.value));
+        source, type.value, std::move(params.value));
   }
 
   auto lit = const_literal();
-  if (has_error())
+  if (lit.errored)
     return Failure::kErrored;
-  if (lit == nullptr) {
-    add_error(peek(), "unable to parse const literal");
-    return Failure::kErrored;
-  }
-  return std::make_unique<ast::ScalarConstructorExpression>(source,
-                                                            std::move(lit));
+  if (!lit.matched)
+    return add_error(peek(), "unable to parse const literal");
+
+  return std::make_unique<ast::ScalarConstructorExpression>(
+      source, std::move(lit.value));
 }
 
-ast::DecorationList ParserImpl::decoration_list() {
+Maybe<ast::DecorationList> ParserImpl::decoration_list() {
+  bool matched = false;
   ast::DecorationList decos;
-  while (decoration_bracketed_list(decos)) {
+
+  while (true) {
+    auto list = decoration_bracketed_list(decos);
+    if (list.errored)
+      return Failure::kErrored;
+    if (!list.matched)
+      break;
+
+    matched = true;
   }
+
+  if (!matched)
+    return Failure::kNoMatch;
+
   return decos;
 }
 
-bool ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
+Maybe<bool> ParserImpl::decoration_bracketed_list(ast::DecorationList& decos) {
   if (!match(Token::Type::kAttrLeft)) {
-    return false;
+    return Failure::kNoMatch;
   }
 
   Source source;
-  if (match(Token::Type::kAttrRight, &source)) {
-    add_error(source, "empty decoration list");
-    return false;
-  }
+  if (match(Token::Type::kAttrRight, &source))
+    return add_error(source, "empty decoration list");
 
   while (true) {
     auto deco = expect_decoration();
     if (deco.errored)
-      return false;
+      return Failure::kErrored;
 
     decos.emplace_back(std::move(deco.value));
 
@@ -2682,87 +2668,90 @@
       // e.g. [[location(1) set(2)]]
       //                    ^^^ expected comma
       expect("decoration list", Token::Type::kComma);
-      return false;
+      return Failure::kErrored;
     }
 
-    return expect("decoration list", Token::Type::kAttrRight);
+    if (!expect("decoration list", Token::Type::kAttrRight))
+      return Failure::kErrored;
+
+    return true;
   }
 }
 
 Expect<std::unique_ptr<ast::Decoration>> ParserImpl::expect_decoration() {
   auto t = peek();
   auto deco = decoration();
-  if (has_error())
+  if (deco.errored)
     return Failure::kErrored;
-  if (deco == nullptr)
-    return add_error(t, "expected decoration");
-  return std::move(deco);
+  if (deco.matched)
+    return std::move(deco.value);
+  return add_error(t, "expected decoration");
 }
 
-std::unique_ptr<ast::Decoration> ParserImpl::decoration() {
-  using Result = std::unique_ptr<ast::Decoration>;
+Maybe<std::unique_ptr<ast::Decoration>> ParserImpl::decoration() {
+  using Result = Maybe<std::unique_ptr<ast::Decoration>>;
   auto t = next();
   if (t.IsLocation()) {
     const char* use = "location decoration";
-    return expect_paren_block_old(use, [&]() -> Result {
+    return expect_paren_block(use, [&]() -> Result {
       auto val = expect_positive_sint(use);
       if (val.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::LocationDecoration>(val.value, val.source);
     });
   }
   if (t.IsBinding()) {
     const char* use = "binding decoration";
-    return expect_paren_block_old(use, [&]() -> Result {
+    return expect_paren_block(use, [&]() -> Result {
       auto val = expect_positive_sint(use);
       if (val.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::BindingDecoration>(val.value, val.source);
     });
   }
   if (t.IsSet()) {
     const char* use = "set decoration";
-    return expect_paren_block_old(use, [&]() -> Result {
+    return expect_paren_block(use, [&]() -> Result {
       auto val = expect_positive_sint(use);
       if (val.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::SetDecoration>(val.value, val.source);
     });
   }
   if (t.IsBuiltin()) {
-    return expect_paren_block_old("builtin decoration", [&]() -> Result {
+    return expect_paren_block("builtin decoration", [&]() -> Result {
       auto builtin = expect_builtin();
       if (builtin.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::BuiltinDecoration>(builtin.value,
                                                       builtin.source);
     });
   }
   if (t.IsWorkgroupSize()) {
-    return expect_paren_block_old("workgroup_size decoration", [&]() -> Result {
+    return expect_paren_block("workgroup_size decoration", [&]() -> Result {
       uint32_t x;
       uint32_t y = 1;
       uint32_t z = 1;
 
       auto val = expect_nonzero_positive_sint("workgroup_size x parameter");
       if (val.errored)
-        return nullptr;
+        return Failure::kErrored;
       x = val.value;
 
       if (match(Token::Type::kComma)) {
         val = expect_nonzero_positive_sint("workgroup_size y parameter");
         if (val.errored)
-          return nullptr;
+          return Failure::kErrored;
         y = val.value;
 
         if (match(Token::Type::kComma)) {
           val = expect_nonzero_positive_sint("workgroup_size z parameter");
           if (val.errored)
-            return nullptr;
+            return Failure::kErrored;
           z = val.value;
         }
       }
@@ -2771,10 +2760,10 @@
     });
   }
   if (t.IsStage()) {
-    return expect_paren_block_old("stage decoration", [&]() -> Result {
+    return expect_paren_block("stage decoration", [&]() -> Result {
       auto stage = expect_pipeline_stage();
       if (stage.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::StageDecoration>(stage.value, stage.source);
     });
@@ -2784,26 +2773,26 @@
   }
   if (t.IsStride()) {
     const char* use = "stride decoration";
-    return expect_paren_block_old(use, [&]() -> Result {
+    return expect_paren_block(use, [&]() -> Result {
       auto val = expect_nonzero_positive_sint(use);
       if (val.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::StrideDecoration>(val.value, t.source());
     });
   }
   if (t.IsOffset()) {
     const char* use = "offset decoration";
-    return expect_paren_block_old(use, [&]() -> Result {
+    return expect_paren_block(use, [&]() -> Result {
       auto val = expect_positive_sint(use);
       if (val.errored)
-        return nullptr;
+        return Failure::kErrored;
 
       return std::make_unique<ast::StructMemberOffsetDecoration>(val.value,
                                                                  t.source());
     });
   }
-  return nullptr;
+  return Failure::kNoMatch;
 }
 
 template <typename T>
@@ -2909,24 +2898,6 @@
 }
 
 template <typename F, typename T>
-T ParserImpl::expect_block_old(Token::Type start,
-                               Token::Type end,
-                               const std::string& use,
-                               F&& body) {
-  if (!expect(use, start)) {
-    return {};
-  }
-  auto res = body();
-  if (has_error()) {
-    return {};
-  }
-  if (!expect(use, end)) {
-    return {};
-  }
-  return res;
-}
-
-template <typename F, typename T>
 T ParserImpl::expect_block(Token::Type start,
                            Token::Type end,
                            const std::string& use,
@@ -2945,18 +2916,6 @@
 }
 
 template <typename F, typename T>
-T ParserImpl::expect_paren_block_old(const std::string& use, F&& body) {
-  return expect_block_old(Token::Type::kParenLeft, Token::Type::kParenRight,
-                          use, std::forward<F>(body));
-}
-
-template <typename F, typename T>
-T ParserImpl::expect_brace_block_old(const std::string& use, F&& body) {
-  return expect_block_old(Token::Type::kBraceLeft, Token::Type::kBraceRight,
-                          use, std::forward<F>(body));
-}
-
-template <typename F, typename T>
 T ParserImpl::expect_paren_block(const std::string& use, F&& body) {
   return expect_block(Token::Type::kParenLeft, Token::Type::kParenRight, use,
                       std::forward<F>(body));
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 5bddd68..ab3efea 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -80,10 +80,11 @@
 
 /// ParserImpl for WGSL source data
 class ParserImpl {
-  /// Failure holds enumerator values used for the constructing an Expect in the
-  /// errored state.
+  /// Failure holds enumerator values used for the constructing an Expect and
+  /// Match in an errored state.
   struct Failure {
     enum Errored { kErrored };
+    enum NoMatch { kNoMatch };
   };
 
  public:
@@ -135,6 +136,74 @@
     bool errored = false;
   };
 
+  /// Maybe is the return type of the parser methods that attempts to match a
+  /// grammar and return a parsed value of type T, or may parse part of the
+  /// grammar and then hit a parse error.
+  /// In the case of a successful grammar match, the Maybe will have |matched|
+  /// set to true.
+  /// In the case of a parse error the called method will have called
+  /// |add_error()| and the Maybe will have |errored| set to true.
+  template <typename T>
+  struct Maybe {
+    inline Maybe(std::nullptr_t) = delete;  // NOLINT
+
+    /// Constructor for a successful parse.
+    /// @param val the result value of the parse
+    /// @param s the optional source of the value
+    template <typename U>
+    inline Maybe(U&& val, const Source& s = {})  // NOLINT
+        : value(std::forward<U>(val)), source(s), matched(true) {}
+
+    /// Constructor for parse error state.
+    inline Maybe(Failure::Errored) : errored(true) {}  // NOLINT
+
+    /// Constructor for the no-match state.
+    inline Maybe(Failure::NoMatch) {}  // NOLINT
+
+    /// Constructor from an Expect.
+    template <typename U>
+    inline Maybe(const Expect<U>& e)  // NOLINT
+        : value(e.value),
+          source(e.value),
+          errored(e.errored),
+          matched(!e.errored) {}
+
+    /// Move from an Expect.
+    template <typename U>
+    inline Maybe(Expect<U>&& e)  // NOLINT
+        : value(std::move(e.value)),
+          source(std::move(e.source)),
+          errored(e.errored),
+          matched(!e.errored) {}
+
+    /// Copy constructor
+    inline Maybe(const Maybe&) = default;
+    /// Move constructor
+    inline Maybe(Maybe&&) = default;
+    /// Assignment operator
+    /// @return this Maybe
+    inline Maybe& operator=(const Maybe&) = default;
+    /// Assignment move operator
+    /// @return this Maybe
+    inline Maybe& operator=(Maybe&&) = default;
+
+    /// @return a pointer to |value|. |errored| must be false to call.
+    inline T* operator->() {
+      assert(!errored);
+      return &value;
+    }
+
+    /// The value of a successful parse.
+    /// Zero-initialized when there was a parse error.
+    T value{};
+    /// Optional source of the value.
+    Source source;
+    /// True if there was a error parsing.
+    bool errored = false;
+    /// True if there was a error parsing.
+    bool matched = false;
+  };
+
   /// TypedIdentifier holds a parsed identifier and type. Returned by
   /// variable_ident_decl().
   struct TypedIdentifier {
@@ -223,14 +292,14 @@
   /// `variable_decoration_list*` provided as |decos|.
   /// @returns the variable parsed or nullptr
   /// @param decos the list of decorations for the variable declaration.
-  std::unique_ptr<ast::Variable> global_variable_decl(
+  Maybe<std::unique_ptr<ast::Variable>> global_variable_decl(
       ast::DecorationList& decos);
   /// Parses a `global_constant_decl` grammar element
   /// @returns the const object or nullptr
-  std::unique_ptr<ast::Variable> global_constant_decl();
+  Maybe<std::unique_ptr<ast::Variable>> global_constant_decl();
   /// Parses a `variable_decl` grammar element
   /// @returns the parsed variable or nullptr otherwise
-  std::unique_ptr<ast::Variable> variable_decl();
+  Maybe<std::unique_ptr<ast::Variable>> variable_decl();
   /// Parses a `variable_ident_decl` grammar element, erroring on parse
   /// failure.
   /// @param use a description of what was being parsed if an error was raised.
@@ -238,13 +307,13 @@
   Expect<TypedIdentifier> expect_variable_ident_decl(const std::string& use);
   /// Parses a `variable_storage_decoration` grammar element
   /// @returns the storage class or StorageClass::kNone if none matched
-  ast::StorageClass variable_storage_decoration();
+  Maybe<ast::StorageClass> variable_storage_decoration();
   /// Parses a `type_alias` grammar element
   /// @returns the type alias or nullptr on error
-  ast::type::Type* type_alias();
+  Maybe<ast::type::Type*> type_alias();
   /// Parses a `type_decl` grammar element
   /// @returns the parsed Type or nullptr if none matched.
-  ast::type::Type* type_decl();
+  Maybe<ast::type::Type*> type_decl();
   /// Parses a `storage_class` grammar element, erroring on parse failure.
   /// @param use a description of what was being parsed if an error was raised.
   /// @returns the storage class or StorageClass::kNone if none matched
@@ -253,7 +322,7 @@
   /// `struct_decoration_decl*` provided as |decos|.
   /// @returns the struct type or nullptr on error
   /// @param decos the list of decorations for the struct declaration.
-  std::unique_ptr<ast::type::StructType> struct_decl(
+  Maybe<std::unique_ptr<ast::type::StructType>> struct_decl(
       ast::DecorationList& decos);
   /// Parses a `struct_body_decl` grammar element, erroring on parse failure.
   /// @returns the struct members
@@ -269,39 +338,40 @@
   /// `function_decoration_decl*` provided as |decos|.
   /// @param decos the list of decorations for the function declaration.
   /// @returns the parsed function, nullptr otherwise
-  std::unique_ptr<ast::Function> function_decl(ast::DecorationList& decos);
+  Maybe<std::unique_ptr<ast::Function>> function_decl(
+      ast::DecorationList& decos);
   /// Parses a `texture_sampler_types` grammar element
   /// @returns the parsed Type or nullptr if none matched.
-  ast::type::Type* texture_sampler_types();
+  Maybe<ast::type::Type*> texture_sampler_types();
   /// Parses a `sampler_type` grammar element
   /// @returns the parsed Type or nullptr if none matched.
-  ast::type::Type* sampler_type();
+  Maybe<ast::type::Type*> sampler_type();
   /// Parses a `multisampled_texture_type` grammar element
   /// @returns returns the multisample texture dimension or kNone if none
   /// matched.
-  ast::type::TextureDimension multisampled_texture_type();
+  Maybe<ast::type::TextureDimension> multisampled_texture_type();
   /// Parses a `sampled_texture_type` grammar element
   /// @returns returns the sample texture dimension or kNone if none matched.
-  ast::type::TextureDimension sampled_texture_type();
+  Maybe<ast::type::TextureDimension> sampled_texture_type();
   /// Parses a `storage_texture_type` grammar element
   /// @returns returns the storage texture dimension and the storage access.
   ///          Returns kNone and kRead if none matched.
-  std::pair<ast::type::TextureDimension, ast::AccessControl>
+  Maybe<std::pair<ast::type::TextureDimension, ast::AccessControl>>
   storage_texture_type();
   /// Parses a `depth_texture_type` grammar element
   /// @returns the parsed Type or nullptr if none matched.
-  ast::type::Type* depth_texture_type();
+  Maybe<ast::type::Type*> depth_texture_type();
   /// Parses a `image_storage_type` grammar element
-  /// @param use a description of what was being parsed if an error was raised.
+  /// @param use a description of what was being parsed if an error was raised
   /// @returns returns the image format or kNone if none matched.
   Expect<ast::type::ImageFormat> expect_image_storage_type(
       const std::string& use);
   /// Parses a `function_type_decl` grammar element
   /// @returns the parsed type or nullptr otherwise
-  ast::type::Type* function_type_decl();
+  Maybe<ast::type::Type*> function_type_decl();
   /// Parses a `function_header` grammar element
   /// @returns the parsed function nullptr otherwise
-  std::unique_ptr<ast::Function> function_header();
+  Maybe<std::unique_ptr<ast::Function>> function_header();
   /// Parses a `param_list` grammar element, erroring on parse failure.
   /// @returns the parsed variables
   Expect<ast::VariableList> expect_param_list();
@@ -324,64 +394,64 @@
   Expect<std::unique_ptr<ast::BlockStatement>> expect_statements();
   /// Parses a `statement` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::Statement> statement();
+  Maybe<std::unique_ptr<ast::Statement>> statement();
   /// Parses a `break_stmt` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::BreakStatement> break_stmt();
+  Maybe<std::unique_ptr<ast::BreakStatement>> break_stmt();
   /// Parses a `return_stmt` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::ReturnStatement> return_stmt();
+  Maybe<std::unique_ptr<ast::ReturnStatement>> return_stmt();
   /// Parses a `continue_stmt` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::ContinueStatement> continue_stmt();
+  Maybe<std::unique_ptr<ast::ContinueStatement>> continue_stmt();
   /// Parses a `variable_stmt` grammar element
   /// @returns the parsed variable or nullptr
-  std::unique_ptr<ast::VariableDeclStatement> variable_stmt();
+  Maybe<std::unique_ptr<ast::VariableDeclStatement>> variable_stmt();
   /// Parses a `if_stmt` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::IfStatement> if_stmt();
+  Maybe<std::unique_ptr<ast::IfStatement>> if_stmt();
   /// Parses a `elseif_stmt` grammar element
   /// @returns the parsed elements
-  ast::ElseStatementList elseif_stmt();
+  Maybe<ast::ElseStatementList> elseif_stmt();
   /// Parses a `else_stmt` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::ElseStatement> else_stmt();
+  Maybe<std::unique_ptr<ast::ElseStatement>> else_stmt();
   /// Parses a `switch_stmt` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::SwitchStatement> switch_stmt();
+  Maybe<std::unique_ptr<ast::SwitchStatement>> switch_stmt();
   /// Parses a `switch_body` grammar element
   /// @returns the parsed statement or nullptr
-  std::unique_ptr<ast::CaseStatement> switch_body();
+  Maybe<std::unique_ptr<ast::CaseStatement>> switch_body();
   /// Parses a `case_selectors` grammar element
   /// @returns the list of literals
   Expect<ast::CaseSelectorList> expect_case_selectors();
   /// Parses a `case_body` grammar element
   /// @returns the parsed statements
-  std::unique_ptr<ast::BlockStatement> case_body();
+  Maybe<std::unique_ptr<ast::BlockStatement>> case_body();
   /// Parses a `func_call_stmt` grammar element
   /// @returns the parsed function call or nullptr
-  std::unique_ptr<ast::CallStatement> func_call_stmt();
+  Maybe<std::unique_ptr<ast::CallStatement>> func_call_stmt();
   /// Parses a `loop_stmt` grammar element
   /// @returns the parsed loop or nullptr
-  std::unique_ptr<ast::LoopStatement> loop_stmt();
+  Maybe<std::unique_ptr<ast::LoopStatement>> loop_stmt();
   /// Parses a `for_header` grammar element, erroring on parse failure.
   /// @returns the parsed for header or nullptr
   Expect<std::unique_ptr<ForHeader>> expect_for_header();
   /// Parses a `for_stmt` grammar element
   /// @returns the parsed for loop or nullptr
-  std::unique_ptr<ast::Statement> for_stmt();
+  Maybe<std::unique_ptr<ast::Statement>> for_stmt();
   /// Parses a `continuing_stmt` grammar element
   /// @returns the parsed statements
-  std::unique_ptr<ast::BlockStatement> continuing_stmt();
+  Maybe<std::unique_ptr<ast::BlockStatement>> continuing_stmt();
   /// Parses a `const_literal` grammar element
   /// @returns the const literal parsed or nullptr if none found
-  std::unique_ptr<ast::Literal> const_literal();
+  Maybe<std::unique_ptr<ast::Literal>> const_literal();
   /// Parses a `const_expr` grammar element, erroring on parse failure.
   /// @returns the parsed constructor expression or nullptr on error
   Expect<std::unique_ptr<ast::ConstructorExpression>> expect_const_expr();
   /// Parses a `primary_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> primary_expression();
+  Maybe<std::unique_ptr<ast::Expression>> primary_expression();
   /// Parses a `argument_expression_list` grammar element, erroring on parse
   /// failure.
   /// @returns the list of arguments
@@ -389,14 +459,14 @@
   /// Parses the recursive portion of the postfix_expression
   /// @param prefix the left side of the expression
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> postfix_expr(
+  Maybe<std::unique_ptr<ast::Expression>> postfix_expr(
       std::unique_ptr<ast::Expression> prefix);
   /// Parses a `postfix_expression` grammar elment
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> postfix_expression();
+  Maybe<std::unique_ptr<ast::Expression>> postfix_expression();
   /// Parses a `unary_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> unary_expression();
+  Maybe<std::unique_ptr<ast::Expression>> unary_expression();
   /// Parses the recursive part of the `multiplicative_expression`, erroring on
   /// parse failure.
   /// @param lhs the left side of the expression
@@ -405,7 +475,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `multiplicative_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> multiplicative_expression();
+  Maybe<std::unique_ptr<ast::Expression>> multiplicative_expression();
   /// Parses the recursive part of the `additive_expression`, erroring on parse
   /// failure.
   /// @param lhs the left side of the expression
@@ -414,7 +484,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `additive_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> additive_expression();
+  Maybe<std::unique_ptr<ast::Expression>> additive_expression();
   /// Parses the recursive part of the `shift_expression`, erroring on parse
   /// failure.
   /// @param lhs the left side of the expression
@@ -423,7 +493,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `shift_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> shift_expression();
+  Maybe<std::unique_ptr<ast::Expression>> shift_expression();
   /// Parses the recursive part of the `relational_expression`, erroring on
   /// parse failure.
   /// @param lhs the left side of the expression
@@ -432,7 +502,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `relational_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> relational_expression();
+  Maybe<std::unique_ptr<ast::Expression>> relational_expression();
   /// Parses the recursive part of the `equality_expression`, erroring on parse
   /// failure.
   /// @param lhs the left side of the expression
@@ -441,7 +511,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `equality_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> equality_expression();
+  Maybe<std::unique_ptr<ast::Expression>> equality_expression();
   /// Parses the recursive part of the `and_expression`, erroring on parse
   /// failure.
   /// @param lhs the left side of the expression
@@ -450,7 +520,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `and_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> and_expression();
+  Maybe<std::unique_ptr<ast::Expression>> and_expression();
   /// Parses the recursive part of the `exclusive_or_expression`, erroring on
   /// parse failure.
   /// @param lhs the left side of the expression
@@ -459,7 +529,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `exclusive_or_expression` grammar elememnt
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> exclusive_or_expression();
+  Maybe<std::unique_ptr<ast::Expression>> exclusive_or_expression();
   /// Parses the recursive part of the `inclusive_or_expression`, erroring on
   /// parse failure.
   /// @param lhs the left side of the expression
@@ -468,7 +538,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses the `inclusive_or_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> inclusive_or_expression();
+  Maybe<std::unique_ptr<ast::Expression>> inclusive_or_expression();
   /// Parses the recursive part of the `logical_and_expression`, erroring on
   /// parse failure.
   /// @param lhs the left side of the expression
@@ -477,7 +547,7 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses a `logical_and_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> logical_and_expression();
+  Maybe<std::unique_ptr<ast::Expression>> logical_and_expression();
   /// Parses the recursive part of the `logical_or_expression`, erroring on
   /// parse failure.
   /// @param lhs the left side of the expression
@@ -486,18 +556,18 @@
       std::unique_ptr<ast::Expression> lhs);
   /// Parses a `logical_or_expression` grammar element
   /// @returns the parsed expression or nullptr
-  std::unique_ptr<ast::Expression> logical_or_expression();
+  Maybe<std::unique_ptr<ast::Expression>> logical_or_expression();
   /// Parses a `assignment_stmt` grammar element
   /// @returns the parsed assignment or nullptr
-  std::unique_ptr<ast::AssignmentStatement> assignment_stmt();
+  Maybe<std::unique_ptr<ast::AssignmentStatement>> assignment_stmt();
   /// Parses one or more bracketed decoration lists.
   /// @return the parsed decoration list, or an empty list on error.
-  ast::DecorationList decoration_list();
+  Maybe<ast::DecorationList> decoration_list();
   /// Parses a list of decorations between `ATTR_LEFT` and `ATTR_RIGHT`
   /// brackets.
   /// @param decos the list to append newly parsed decorations to.
   /// @return true if any decorations were be parsed, otherwise false.
-  bool decoration_bracketed_list(ast::DecorationList& decos);
+  Maybe<bool> decoration_bracketed_list(ast::DecorationList& decos);
   /// Parses a single decoration of the following types:
   /// * `struct_decoration`
   /// * `struct_member_decoration`
@@ -506,7 +576,7 @@
   /// * `global_const_decoration`
   /// * `function_decoration`
   /// @return the parsed decoration, or nullptr.
-  std::unique_ptr<ast::Decoration> decoration();
+  Maybe<std::unique_ptr<ast::Decoration>> decoration();
   /// Parses a single decoration, reporting an error if the next token does not
   /// represent a decoration.
   /// @see #decoration for the full list of decorations this method parses.
@@ -568,7 +638,7 @@
   /// @param body a function or lambda that is called to parse the lexical block
   /// body, with the signature: `Expect<Result>()`.
   /// @return the value returned by |body| if no errors are raised, otherwise
-  /// a zero-initialized |T|.
+  /// an Expect with error state.
   template <typename F, typename T = ReturnType<F>>
   T expect_block(Token::Type start,
                  Token::Type end,
@@ -581,7 +651,7 @@
   /// @param body a function or lambda that is called to parse the lexical block
   /// body, with the signature: `Expect<Result>()`.
   /// @return the value returned by |body| if no errors are raised, otherwise
-  /// a zero-initialized |T|.
+  /// an Expect with error state.
   template <typename F, typename T = ReturnType<F>>
   T expect_paren_block(const std::string& use, F&& body);
   /// A convenience function that calls |expect_block| passing
@@ -591,24 +661,10 @@
   /// @param body a function or lambda that is called to parse the lexical block
   /// body, with the signature: `Expect<Result>()`.
   /// @return the value returned by |body| if no errors are raised, otherwise
-  /// a zero-initialized |T|.
+  /// an Expect with error state.
   template <typename F, typename T = ReturnType<F>>
   T expect_brace_block(const std::string& use, F&& body);
 
-  // Versions of expect_block(), expect_paren_block_old(),
-  // expect_brace_block_old() that do not take an Expect for T.
-  // These will be removed in the near future.
-  // TODO(ben-clayton) - migrate remaining uses of these.
-  template <typename F, typename T = ReturnType<F>>
-  T expect_block_old(Token::Type start,
-                     Token::Type end,
-                     const std::string& use,
-                     F&& body);
-  template <typename F, typename T = ReturnType<F>>
-  T expect_paren_block_old(const std::string& use, F&& body);
-  template <typename F, typename T = ReturnType<F>>
-  T expect_brace_block_old(const std::string& use, F&& body);
-
   /// Downcasts all the decorations in |list| to the type |T|, raising a parser
   /// error if any of the decorations aren't of the type |T|.
   template <typename T>
@@ -627,6 +683,9 @@
   Expect<std::unique_ptr<ast::ConstructorExpression>>
   expect_const_expr_internal(uint32_t depth);
 
+  Maybe<std::unique_ptr<ast::Statement>> for_header_initializer();
+  Maybe<std::unique_ptr<ast::Statement>> for_header_continuing();
+
   Context& ctx_;
   diag::List diags_;
   std::unique_ptr<Lexer> lexer_;
diff --git a/src/reader/wgsl/parser_impl_additive_expression_test.cc b/src/reader/wgsl/parser_impl_additive_expression_test.cc
index a80ab60..7a93975 100644
--- a/src/reader/wgsl/parser_impl_additive_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_additive_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, AdditiveExpression_Parses_Plus) {
   auto* p = parser("a + true");
   auto e = p->additive_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kAdd, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,11 +51,13 @@
 TEST_F(ParserImplTest, AdditiveExpression_Parses_Minus) {
   auto* p = parser("a - true");
   auto e = p->additive_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kSubtract, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -70,24 +74,30 @@
 TEST_F(ParserImplTest, AdditiveExpression_InvalidLHS) {
   auto* p = parser("if (a) {} + true");
   auto e = p->additive_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, AdditiveExpression_InvalidRHS) {
   auto* p = parser("true + if (a) {}");
   auto e = p->additive_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: unable to parse right side of + expression");
 }
 
 TEST_F(ParserImplTest, AdditiveExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->additive_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_and_expression_test.cc b/src/reader/wgsl/parser_impl_and_expression_test.cc
index 6bd1039..2b85958 100644
--- a/src/reader/wgsl/parser_impl_and_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_and_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, AndExpression_Parses) {
   auto* p = parser("a & true");
   auto e = p->and_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kAnd, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,24 +51,30 @@
 TEST_F(ParserImplTest, AndExpression_InvalidLHS) {
   auto* p = parser("if (a) {} & true");
   auto e = p->and_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, AndExpression_InvalidRHS) {
   auto* p = parser("true & if (a) {}");
   auto e = p->and_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: unable to parse right side of & expression");
 }
 
 TEST_F(ParserImplTest, AndExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->and_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
index fd7778f..304781e 100644
--- a/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_assignment_stmt_test.cc
@@ -31,21 +31,23 @@
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToVariable) {
   auto* p = parser("a = 123");
   auto e = p->assignment_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsAssign());
-  ASSERT_NE(e->lhs(), nullptr);
-  ASSERT_NE(e->rhs(), nullptr);
+  ASSERT_TRUE(e.value->IsAssign());
+  ASSERT_NE(e.value->lhs(), nullptr);
+  ASSERT_NE(e.value->rhs(), nullptr);
 
-  ASSERT_TRUE(e->lhs()->IsIdentifier());
-  auto* ident = e->lhs()->AsIdentifier();
+  ASSERT_TRUE(e.value->lhs()->IsIdentifier());
+  auto* ident = e.value->lhs()->AsIdentifier();
   EXPECT_EQ(ident->name(), "a");
 
-  ASSERT_TRUE(e->rhs()->IsConstructor());
-  ASSERT_TRUE(e->rhs()->AsConstructor()->IsScalarConstructor());
+  ASSERT_TRUE(e.value->rhs()->IsConstructor());
+  ASSERT_TRUE(e.value->rhs()->AsConstructor()->IsScalarConstructor());
 
-  auto* init = e->rhs()->AsConstructor()->AsScalarConstructor();
+  auto* init = e.value->rhs()->AsConstructor()->AsScalarConstructor();
   ASSERT_NE(init->literal(), nullptr);
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 123);
@@ -54,22 +56,24 @@
 TEST_F(ParserImplTest, AssignmentStmt_Parses_ToMember) {
   auto* p = parser("a.b.c[2].d = 123");
   auto e = p->assignment_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsAssign());
-  ASSERT_NE(e->lhs(), nullptr);
-  ASSERT_NE(e->rhs(), nullptr);
+  ASSERT_TRUE(e.value->IsAssign());
+  ASSERT_NE(e.value->lhs(), nullptr);
+  ASSERT_NE(e.value->rhs(), nullptr);
 
-  ASSERT_TRUE(e->rhs()->IsConstructor());
-  ASSERT_TRUE(e->rhs()->AsConstructor()->IsScalarConstructor());
-  auto* init = e->rhs()->AsConstructor()->AsScalarConstructor();
+  ASSERT_TRUE(e.value->rhs()->IsConstructor());
+  ASSERT_TRUE(e.value->rhs()->AsConstructor()->IsScalarConstructor());
+  auto* init = e.value->rhs()->AsConstructor()->AsScalarConstructor();
   ASSERT_NE(init->literal(), nullptr);
   ASSERT_TRUE(init->literal()->IsSint());
   EXPECT_EQ(init->literal()->AsSint()->value(), 123);
 
-  ASSERT_TRUE(e->lhs()->IsMemberAccessor());
-  auto* mem = e->lhs()->AsMemberAccessor();
+  ASSERT_TRUE(e.value->lhs()->IsMemberAccessor());
+  auto* mem = e.value->lhs()->AsMemberAccessor();
 
   ASSERT_TRUE(mem->member()->IsIdentifier());
   auto* ident = mem->member()->AsIdentifier();
@@ -106,23 +110,29 @@
 TEST_F(ParserImplTest, AssignmentStmt_MissingEqual) {
   auto* p = parser("a.b.c[2].d 123");
   auto e = p->assignment_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:12: missing = for assignment");
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_EQ(p->error(), "1:12: expected '=' for assignment");
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_InvalidLHS) {
   auto* p = parser("if (true) {} = 123");
   auto e = p->assignment_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, AssignmentStmt_InvalidRHS) {
   auto* p = parser("a.b.c[2].d = if (true) {}");
   auto e = p->assignment_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:14: unable to parse right side of assignment");
 }
 
diff --git a/src/reader/wgsl/parser_impl_break_stmt_test.cc b/src/reader/wgsl/parser_impl_break_stmt_test.cc
index 4eba159..8fd49c3 100644
--- a/src/reader/wgsl/parser_impl_break_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_break_stmt_test.cc
@@ -25,9 +25,10 @@
 TEST_F(ParserImplTest, BreakStmt) {
   auto* p = parser("break");
   auto e = p->break_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsBreak());
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsBreak());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_call_stmt_test.cc b/src/reader/wgsl/parser_impl_call_stmt_test.cc
index 2ab8b19..ded8f50 100644
--- a/src/reader/wgsl/parser_impl_call_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_call_stmt_test.cc
@@ -28,10 +28,12 @@
   auto* p = parser("a();");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
 
-  ASSERT_TRUE(e->IsCall());
-  auto* c = e->AsCall()->expr();
+  ASSERT_TRUE(e.value->IsCall());
+  auto* c = e.value->AsCall()->expr();
 
   ASSERT_TRUE(c->func()->IsIdentifier());
   auto* func = c->func()->AsIdentifier();
@@ -44,10 +46,12 @@
   auto* p = parser("a(1, b, 2 + 3 / b);");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
 
-  ASSERT_TRUE(e->IsCall());
-  auto* c = e->AsCall()->expr();
+  ASSERT_TRUE(e.value->IsCall());
+  auto* c = e.value->AsCall()->expr();
 
   ASSERT_TRUE(c->func()->IsIdentifier());
   auto* func = c->func()->AsIdentifier();
@@ -62,21 +66,27 @@
 TEST_F(ParserImplTest, Statement_Call_Missing_RightParen) {
   auto* p = parser("a(");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
   EXPECT_EQ(p->error(), "1:3: expected ')' for call statement");
 }
 
 TEST_F(ParserImplTest, Statement_Call_Missing_Semi) {
   auto* p = parser("a()");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
   EXPECT_EQ(p->error(), "1:4: expected ';' for function call");
 }
 
 TEST_F(ParserImplTest, Statement_Call_Bad_ArgList) {
   auto* p = parser("a(b c);");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
   EXPECT_EQ(p->error(), "1:5: expected ')' for call statement");
 }
 
diff --git a/src/reader/wgsl/parser_impl_case_body_test.cc b/src/reader/wgsl/parser_impl_case_body_test.cc
index b878037..b840d70 100644
--- a/src/reader/wgsl/parser_impl_case_body_test.cc
+++ b/src/reader/wgsl/parser_impl_case_body_test.cc
@@ -25,7 +25,9 @@
   auto* p = parser("");
   auto e = p->case_body();
   ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_EQ(e->size(), 0u);
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  EXPECT_EQ(e.value->size(), 0u);
 }
 
 TEST_F(ParserImplTest, CaseBody_Statements) {
@@ -35,31 +37,39 @@
 
   auto e = p->case_body();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e->size(), 2u);
-  EXPECT_TRUE(e->get(0)->IsVariableDecl());
-  EXPECT_TRUE(e->get(1)->IsAssign());
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  ASSERT_EQ(e.value->size(), 2u);
+  EXPECT_TRUE(e.value->get(0)->IsVariableDecl());
+  EXPECT_TRUE(e.value->get(1)->IsAssign());
 }
 
 TEST_F(ParserImplTest, CaseBody_InvalidStatement) {
   auto* p = parser("a =");
   auto e = p->case_body();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, CaseBody_Fallthrough) {
   auto* p = parser("fallthrough;");
   auto e = p->case_body();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e->size(), 1u);
-  EXPECT_TRUE(e->get(0)->IsFallthrough());
+  EXPECT_FALSE(e.errored);
+  EXPECT_TRUE(e.matched);
+  ASSERT_EQ(e.value->size(), 1u);
+  EXPECT_TRUE(e.value->get(0)->IsFallthrough());
 }
 
 TEST_F(ParserImplTest, CaseBody_Fallthrough_MissingSemicolon) {
   auto* p = parser("fallthrough");
   auto e = p->case_body();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:12: expected ';' for fallthrough statement");
 }
 
diff --git a/src/reader/wgsl/parser_impl_const_literal_test.cc b/src/reader/wgsl/parser_impl_const_literal_test.cc
index 3a5420a..28b0771 100644
--- a/src/reader/wgsl/parser_impl_const_literal_test.cc
+++ b/src/reader/wgsl/parser_impl_const_literal_test.cc
@@ -28,59 +28,73 @@
 TEST_F(ParserImplTest, ConstLiteral_Int) {
   auto* p = parser("-234");
   auto c = p->const_literal();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(c, nullptr);
-  ASSERT_TRUE(c->IsSint());
-  EXPECT_EQ(c->AsSint()->value(), -234);
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c.value->IsSint());
+  EXPECT_EQ(c.value->AsSint()->value(), -234);
 }
 
 TEST_F(ParserImplTest, ConstLiteral_Uint) {
   auto* p = parser("234u");
   auto c = p->const_literal();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(c, nullptr);
-  ASSERT_TRUE(c->IsUint());
-  EXPECT_EQ(c->AsUint()->value(), 234u);
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c.value->IsUint());
+  EXPECT_EQ(c.value->AsUint()->value(), 234u);
 }
 
 TEST_F(ParserImplTest, ConstLiteral_Float) {
   auto* p = parser("234.e12");
   auto c = p->const_literal();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(c, nullptr);
-  ASSERT_TRUE(c->IsFloat());
-  EXPECT_FLOAT_EQ(c->AsFloat()->value(), 234e12f);
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c.value->IsFloat());
+  EXPECT_FLOAT_EQ(c.value->AsFloat()->value(), 234e12f);
 }
 
 TEST_F(ParserImplTest, ConstLiteral_InvalidFloat) {
   auto* p = parser("1.2e+256");
   auto c = p->const_literal();
-  ASSERT_EQ(c, nullptr);
+  EXPECT_FALSE(c.matched);
+  EXPECT_FALSE(c.errored);
+  ASSERT_EQ(c.value, nullptr);
 }
 
 TEST_F(ParserImplTest, ConstLiteral_True) {
   auto* p = parser("true");
   auto c = p->const_literal();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(c, nullptr);
-  ASSERT_TRUE(c->IsBool());
-  EXPECT_TRUE(c->AsBool()->IsTrue());
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c.value->IsBool());
+  EXPECT_TRUE(c.value->AsBool()->IsTrue());
 }
 
 TEST_F(ParserImplTest, ConstLiteral_False) {
   auto* p = parser("false");
   auto c = p->const_literal();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(c, nullptr);
-  ASSERT_TRUE(c->IsBool());
-  EXPECT_TRUE(c->AsBool()->IsFalse());
+  EXPECT_TRUE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_NE(c.value, nullptr);
+  ASSERT_TRUE(c.value->IsBool());
+  EXPECT_TRUE(c.value->AsBool()->IsFalse());
 }
 
 TEST_F(ParserImplTest, ConstLiteral_NoMatch) {
   auto* p = parser("another-token");
   auto c = p->const_literal();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_EQ(c, nullptr);
+  EXPECT_FALSE(c.matched);
+  EXPECT_FALSE(c.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_EQ(c.value, nullptr);
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_continue_stmt_test.cc b/src/reader/wgsl/parser_impl_continue_stmt_test.cc
index 8207e57..1a7f644 100644
--- a/src/reader/wgsl/parser_impl_continue_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_continue_stmt_test.cc
@@ -25,9 +25,10 @@
 TEST_F(ParserImplTest, ContinueStmt) {
   auto* p = parser("continue");
   auto e = p->continue_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsContinue());
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsContinue());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_continuing_stmt_test.cc b/src/reader/wgsl/parser_impl_continuing_stmt_test.cc
index 9f2ab58..e29b1d4 100644
--- a/src/reader/wgsl/parser_impl_continuing_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_continuing_stmt_test.cc
@@ -24,16 +24,20 @@
 TEST_F(ParserImplTest, ContinuingStmt) {
   auto* p = parser("continuing { discard; }");
   auto e = p->continuing_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e->size(), 1u);
-  ASSERT_TRUE(e->get(0)->IsDiscard());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value->size(), 1u);
+  ASSERT_TRUE(e.value->get(0)->IsDiscard());
 }
 
 TEST_F(ParserImplTest, ContinuingStmt_InvalidBody) {
   auto* p = parser("continuing { discard }");
   auto e = p->continuing_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:22: expected ';' for discard statement");
 }
 
diff --git a/src/reader/wgsl/parser_impl_depth_texture_type_test.cc b/src/reader/wgsl/parser_impl_depth_texture_type_test.cc
index d86c03c..35119bb 100644
--- a/src/reader/wgsl/parser_impl_depth_texture_type_test.cc
+++ b/src/reader/wgsl/parser_impl_depth_texture_type_test.cc
@@ -24,48 +24,58 @@
 
 TEST_F(ParserImplTest, DepthTextureType_Invalid) {
   auto* p = parser("1234");
-  auto* t = p->depth_texture_type();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->depth_texture_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, DepthTextureType_2d) {
   auto* p = parser("texture_depth_2d");
-  auto* t = p->depth_texture_type();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsDepth());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsDepth());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, DepthTextureType_2dArray) {
   auto* p = parser("texture_depth_2d_array");
-  auto* t = p->depth_texture_type();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsDepth());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2dArray);
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsDepth());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2dArray);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, DepthTextureType_Cube) {
   auto* p = parser("texture_depth_cube");
-  auto* t = p->depth_texture_type();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsDepth());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::kCube);
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsDepth());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::kCube);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, DepthTextureType_CubeArray) {
   auto* p = parser("texture_depth_cube_array");
-  auto* t = p->depth_texture_type();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsDepth());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::kCubeArray);
+  auto t = p->depth_texture_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsDepth());
+  EXPECT_EQ(t.value->AsTexture()->dim(),
+            ast::type::TextureDimension::kCubeArray);
   EXPECT_FALSE(p->has_error());
 }
 
diff --git a/src/reader/wgsl/parser_impl_else_stmt_test.cc b/src/reader/wgsl/parser_impl_else_stmt_test.cc
index f83fa4a..08313f2 100644
--- a/src/reader/wgsl/parser_impl_else_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_else_stmt_test.cc
@@ -25,26 +25,32 @@
 TEST_F(ParserImplTest, ElseStmt) {
   auto* p = parser("else { a = b; c = d; }");
   auto e = p->else_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsElse());
-  ASSERT_EQ(e->condition(), nullptr);
-  EXPECT_EQ(e->body()->size(), 2u);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsElse());
+  ASSERT_EQ(e.value->condition(), nullptr);
+  EXPECT_EQ(e.value->body()->size(), 2u);
 }
 
 TEST_F(ParserImplTest, ElseStmt_InvalidBody) {
   auto* p = parser("else { fn main() -> void {}}");
   auto e = p->else_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: expected '}'");
 }
 
 TEST_F(ParserImplTest, ElseStmt_MissingBody) {
   auto* p = parser("else");
   auto e = p->else_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:5: expected '{'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
index 93ce334..dd1ae17 100644
--- a/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_elseif_stmt_test.cc
@@ -25,43 +25,51 @@
 TEST_F(ParserImplTest, ElseIfStmt) {
   auto* p = parser("elseif (a == 4) { a = b; c = d; }");
   auto e = p->elseif_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e.size(), 1u);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value.size(), 1u);
 
-  ASSERT_TRUE(e[0]->IsElse());
-  ASSERT_NE(e[0]->condition(), nullptr);
-  ASSERT_TRUE(e[0]->condition()->IsBinary());
-  EXPECT_EQ(e[0]->body()->size(), 2u);
+  ASSERT_TRUE(e.value[0]->IsElse());
+  ASSERT_NE(e.value[0]->condition(), nullptr);
+  ASSERT_TRUE(e.value[0]->condition()->IsBinary());
+  EXPECT_EQ(e.value[0]->body()->size(), 2u);
 }
 
 TEST_F(ParserImplTest, ElseIfStmt_Multiple) {
   auto* p = parser("elseif (a == 4) { a = b; c = d; } elseif(c) { d = 2; }");
   auto e = p->elseif_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e.size(), 2u);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value.size(), 2u);
 
-  ASSERT_TRUE(e[0]->IsElse());
-  ASSERT_NE(e[0]->condition(), nullptr);
-  ASSERT_TRUE(e[0]->condition()->IsBinary());
-  EXPECT_EQ(e[0]->body()->size(), 2u);
+  ASSERT_TRUE(e.value[0]->IsElse());
+  ASSERT_NE(e.value[0]->condition(), nullptr);
+  ASSERT_TRUE(e.value[0]->condition()->IsBinary());
+  EXPECT_EQ(e.value[0]->body()->size(), 2u);
 
-  ASSERT_TRUE(e[1]->IsElse());
-  ASSERT_NE(e[1]->condition(), nullptr);
-  ASSERT_TRUE(e[1]->condition()->IsIdentifier());
-  EXPECT_EQ(e[1]->body()->size(), 1u);
+  ASSERT_TRUE(e.value[1]->IsElse());
+  ASSERT_NE(e.value[1]->condition(), nullptr);
+  ASSERT_TRUE(e.value[1]->condition()->IsIdentifier());
+  EXPECT_EQ(e.value[1]->body()->size(), 1u);
 }
 
 TEST_F(ParserImplTest, ElseIfStmt_InvalidBody) {
   auto* p = parser("elseif (true) { fn main() -> void {}}");
   auto e = p->elseif_stmt();
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:17: expected '}'");
 }
 
 TEST_F(ParserImplTest, ElseIfStmt_MissingBody) {
   auto* p = parser("elseif (true)");
   auto e = p->elseif_stmt();
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:14: expected '{'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_equality_expression_test.cc b/src/reader/wgsl/parser_impl_equality_expression_test.cc
index c3fefe1..9801d00 100644
--- a/src/reader/wgsl/parser_impl_equality_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_equality_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, EqualityExpression_Parses_Equal) {
   auto* p = parser("a == true");
   auto e = p->equality_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kEqual, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,11 +51,13 @@
 TEST_F(ParserImplTest, EqualityExpression_Parses_NotEqual) {
   auto* p = parser("a != true");
   auto e = p->equality_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kNotEqual, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -70,24 +74,30 @@
 TEST_F(ParserImplTest, EqualityExpression_InvalidLHS) {
   auto* p = parser("if (a) {} == true");
   auto e = p->equality_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, EqualityExpression_InvalidRHS) {
   auto* p = parser("true == if (a) {}");
   auto e = p->equality_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: unable to parse right side of == expression");
 }
 
 TEST_F(ParserImplTest, EqualityExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->equality_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_error_msg_test.cc b/src/reader/wgsl/parser_impl_error_msg_test.cc
index 222134e..08d84eb 100644
--- a/src/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/reader/wgsl/parser_impl_error_msg_test.cc
@@ -56,21 +56,21 @@
 
 TEST_F(ParserImplErrorTest, ArrayIndexExprMissingRBracket) {
   EXPECT("fn f() -> void { x = y[1; }",
-         "test.wgsl:1:25 error: missing ] for array accessor\n"
+         "test.wgsl:1:25 error: expected ']' for array accessor\n"
          "fn f() -> void { x = y[1; }\n"
          "                        ^\n");
 }
 
 TEST_F(ParserImplErrorTest, AssignmentStmtMissingAssignment) {
   EXPECT("fn f() -> void { a; }",
-         "test.wgsl:1:19 error: missing = for assignment\n"
+         "test.wgsl:1:19 error: expected '=' for assignment\n"
          "fn f() -> void { a; }\n"
          "                  ^\n");
 }
 
 TEST_F(ParserImplErrorTest, AssignmentStmtMissingAssignment2) {
   EXPECT("fn f() -> void { a : i32; }",
-         "test.wgsl:1:20 error: missing = for assignment\n"
+         "test.wgsl:1:20 error: expected '=' for assignment\n"
          "fn f() -> void { a : i32; }\n"
          "                   ^\n");
 }
@@ -91,14 +91,14 @@
 
 TEST_F(ParserImplErrorTest, BitcastExprMissingLessThan) {
   EXPECT("fn f() -> void { x = bitcast(y); }",
-         "test.wgsl:1:29 error: missing < for bitcast expression\n"
+         "test.wgsl:1:29 error: expected '<' for bitcast expression\n"
          "fn f() -> void { x = bitcast(y); }\n"
          "                            ^\n");
 }
 
 TEST_F(ParserImplErrorTest, BitcastExprMissingGreaterThan) {
   EXPECT("fn f() -> void { x = bitcast<u32(y); }",
-         "test.wgsl:1:33 error: missing > for bitcast expression\n"
+         "test.wgsl:1:33 error: expected '>' for bitcast expression\n"
          "fn f() -> void { x = bitcast<u32(y); }\n"
          "                                ^\n");
 }
@@ -402,7 +402,7 @@
 
 TEST_F(ParserImplErrorTest, FunctionDeclMissingArrow) {
   EXPECT("fn f() void {}",
-         "test.wgsl:1:8 error: missing -> for function declaration\n"
+         "test.wgsl:1:8 error: expected '->' for function declaration\n"
          "fn f() void {}\n"
          "       ^^^^\n");
 }
@@ -995,14 +995,14 @@
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingLessThan) {
   EXPECT("var i : mat4x4;",
-         "test.wgsl:1:15 error: missing < for matrix\n"
+         "test.wgsl:1:15 error: expected '<' for matrix\n"
          "var i : mat4x4;\n"
          "              ^\n");
 }
 
 TEST_F(ParserImplErrorTest, GlobalDeclVarMatrixMissingGreaterThan) {
   EXPECT("var i : mat4x4<u32;",
-         "test.wgsl:1:19 error: missing > for matrix\n"
+         "test.wgsl:1:19 error: expected '>' for matrix\n"
          "var i : mat4x4<u32;\n"
          "                  ^\n");
 }
diff --git a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
index 54d9e4c..39f34e4 100644
--- a/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_exclusive_or_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, ExclusiveOrExpression_Parses) {
   auto* p = parser("a ^ true");
   auto e = p->exclusive_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kXor, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,24 +51,30 @@
 TEST_F(ParserImplTest, ExclusiveOrExpression_InvalidLHS) {
   auto* p = parser("if (a) {} ^ true");
   auto e = p->exclusive_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, ExclusiveOrExpression_InvalidRHS) {
   auto* p = parser("true ^ if (a) {}");
   auto e = p->exclusive_or_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:8: unable to parse right side of ^ expression");
 }
 
 TEST_F(ParserImplTest, ExclusiveOrExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->exclusive_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_for_stmt_test.cc b/src/reader/wgsl/parser_impl_for_stmt_test.cc
index 3520a58..fe9b63f 100644
--- a/src/reader/wgsl/parser_impl_for_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_for_stmt_test.cc
@@ -161,8 +161,10 @@
     auto* p_for = parser(for_str);
     auto e_for = p_for->for_stmt();
 
-    ASSERT_TRUE(p_for->has_error());
-    ASSERT_EQ(e_for, nullptr);
+    EXPECT_FALSE(e_for.matched);
+    EXPECT_TRUE(e_for.errored);
+    EXPECT_TRUE(p_for->has_error());
+    ASSERT_EQ(e_for.value, nullptr);
     EXPECT_EQ(p_for->error(), error_str);
   }
 };
diff --git a/src/reader/wgsl/parser_impl_function_decl_test.cc b/src/reader/wgsl/parser_impl_function_decl_test.cc
index 2de8075..68ba11a 100644
--- a/src/reader/wgsl/parser_impl_function_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decl_test.cc
@@ -26,56 +26,64 @@
 
 TEST_F(ParserImplTest, FunctionDecl) {
   auto* p = parser("fn main(a : i32, b : f32) -> void { return; }");
-  auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(f, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto f = p->function_decl(decos.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->name(), "main");
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
+  EXPECT_EQ(f.value->name(), "main");
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
 
-  ASSERT_EQ(f->params().size(), 2u);
-  EXPECT_EQ(f->params()[0]->name(), "a");
-  EXPECT_EQ(f->params()[1]->name(), "b");
+  ASSERT_EQ(f.value->params().size(), 2u);
+  EXPECT_EQ(f.value->params()[0]->name(), "a");
+  EXPECT_EQ(f.value->params()[1]->name(), "b");
 
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
 
-  auto* body = f->body();
+  auto* body = f.value->body();
   ASSERT_EQ(body->size(), 1u);
   EXPECT_TRUE(body->get(0)->IsReturn());
 }
 
 TEST_F(ParserImplTest, FunctionDecl_DecorationList) {
   auto* p = parser("[[workgroup_size(2, 3, 4)]] fn main() -> void { return; }");
-  auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(f, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decos.errored);
+  ASSERT_TRUE(decos.matched);
+  auto f = p->function_decl(decos.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->name(), "main");
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
-  ASSERT_EQ(f->params().size(), 0u);
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
+  EXPECT_EQ(f.value->name(), "main");
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
+  ASSERT_EQ(f.value->params().size(), 0u);
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
 
-  auto& decos = f->decorations();
-  ASSERT_EQ(decos.size(), 1u);
-  ASSERT_TRUE(decos[0]->IsWorkgroup());
+  auto& decorations = f.value->decorations();
+  ASSERT_EQ(decorations.size(), 1u);
+  ASSERT_TRUE(decorations[0]->IsWorkgroup());
 
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
-  std::tie(x, y, z) = decos[0]->AsWorkgroup()->values();
+  std::tie(x, y, z) = decorations[0]->AsWorkgroup()->values();
   EXPECT_EQ(x, 2u);
   EXPECT_EQ(y, 3u);
   EXPECT_EQ(z, 4u);
 
-  auto* body = f->body();
+  auto* body = f.value->body();
   ASSERT_EQ(body->size(), 1u);
   EXPECT_TRUE(body->get(0)->IsReturn());
 }
@@ -84,38 +92,42 @@
   auto* p = parser(R"(
 [[workgroup_size(2, 3, 4), workgroup_size(5, 6, 7)]]
 fn main() -> void { return; })");
-  auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(f, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decos.errored);
+  ASSERT_TRUE(decos.matched);
+  auto f = p->function_decl(decos.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->name(), "main");
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
-  ASSERT_EQ(f->params().size(), 0u);
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
+  EXPECT_EQ(f.value->name(), "main");
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
+  ASSERT_EQ(f.value->params().size(), 0u);
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
 
-  auto& decos = f->decorations();
-  ASSERT_EQ(decos.size(), 2u);
+  auto& decorations = f.value->decorations();
+  ASSERT_EQ(decorations.size(), 2u);
 
   uint32_t x = 0;
   uint32_t y = 0;
   uint32_t z = 0;
-  ASSERT_TRUE(decos[0]->IsWorkgroup());
-  std::tie(x, y, z) = decos[0]->AsWorkgroup()->values();
+  ASSERT_TRUE(decorations[0]->IsWorkgroup());
+  std::tie(x, y, z) = decorations[0]->AsWorkgroup()->values();
   EXPECT_EQ(x, 2u);
   EXPECT_EQ(y, 3u);
   EXPECT_EQ(z, 4u);
 
-  ASSERT_TRUE(decos[1]->IsWorkgroup());
-  std::tie(x, y, z) = decos[1]->AsWorkgroup()->values();
+  ASSERT_TRUE(decorations[1]->IsWorkgroup());
+  std::tie(x, y, z) = decorations[1]->AsWorkgroup()->values();
   EXPECT_EQ(x, 5u);
   EXPECT_EQ(y, 6u);
   EXPECT_EQ(z, 7u);
 
-  auto* body = f->body();
+  auto* body = f.value->body();
   ASSERT_EQ(body->size(), 1u);
   EXPECT_TRUE(body->get(0)->IsReturn());
 }
@@ -126,19 +138,23 @@
 [[workgroup_size(5, 6, 7)]]
 fn main() -> void { return; })");
   auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(f, nullptr);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decorations.errored);
+  ASSERT_TRUE(decorations.matched);
+  auto f = p->function_decl(decorations.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(f.errored);
+  EXPECT_TRUE(f.matched);
+  ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->name(), "main");
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
-  ASSERT_EQ(f->params().size(), 0u);
-  ASSERT_NE(f->return_type(), nullptr);
-  EXPECT_TRUE(f->return_type()->IsVoid());
+  EXPECT_EQ(f.value->name(), "main");
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
+  ASSERT_EQ(f.value->params().size(), 0u);
+  ASSERT_NE(f.value->return_type(), nullptr);
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
 
-  auto& decos = f->decorations();
+  auto& decos = f.value->decorations();
   ASSERT_EQ(decos.size(), 2u);
 
   uint32_t x = 0;
@@ -156,38 +172,50 @@
   EXPECT_EQ(y, 6u);
   EXPECT_EQ(z, 7u);
 
-  auto* body = f->body();
+  auto* body = f.value->body();
   ASSERT_EQ(body->size(), 1u);
   EXPECT_TRUE(body->get(0)->IsReturn());
 }
 
 TEST_F(ParserImplTest, FunctionDecl_InvalidHeader) {
   auto* p = parser("fn main() -> { }");
-  auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto f = p->function_decl(decos.value);
+  EXPECT_TRUE(f.errored);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:14: unable to determine function return type");
 }
 
 TEST_F(ParserImplTest, FunctionDecl_InvalidBody) {
   auto* p = parser("fn main() -> void { return }");
-  auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto f = p->function_decl(decos.value);
+  EXPECT_TRUE(f.errored);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:28: expected ';' for return statement");
 }
 
 TEST_F(ParserImplTest, FunctionDecl_MissingLeftBrace) {
   auto* p = parser("fn main() -> void return; }");
-  auto decorations = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  auto f = p->function_decl(decorations);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto f = p->function_decl(decos.value);
+  EXPECT_TRUE(f.errored);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:19: expected '{'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_function_decoration_list_test.cc b/src/reader/wgsl/parser_impl_function_decoration_list_test.cc
index 3010a46..3250490 100644
--- a/src/reader/wgsl/parser_impl_function_decoration_list_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decoration_list_test.cc
@@ -25,11 +25,13 @@
 TEST_F(ParserImplTest, FunctionDecorationList_Parses) {
   auto* p = parser("[[workgroup_size(2), workgroup_size(3, 4, 5)]]");
   auto decos = p->decoration_list();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(decos.size(), 2u);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 2u);
 
-  auto deco_0 = ast::As<ast::FunctionDecoration>(std::move(decos[0]));
-  auto deco_1 = ast::As<ast::FunctionDecoration>(std::move(decos[1]));
+  auto deco_0 = ast::As<ast::FunctionDecoration>(std::move(decos.value[0]));
+  auto deco_1 = ast::As<ast::FunctionDecoration>(std::move(decos.value[1]));
   ASSERT_NE(deco_0, nullptr);
   ASSERT_NE(deco_1, nullptr);
 
@@ -49,47 +51,59 @@
 
 TEST_F(ParserImplTest, FunctionDecorationList_Empty) {
   auto* p = parser("[[]]");
-  ast::DecorationList decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:3: empty decoration list");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(p->error(), "1:3: empty decoration list");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_Invalid) {
   auto* p = parser("[[invalid]]");
-  ast::DecorationList decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decos.empty());
-  ASSERT_EQ(p->error(), "1:3: expected decoration");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(), "1:3: expected decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_ExtraComma) {
   auto* p = parser("[[workgroup_size(2), ]]");
-  ast::DecorationList decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:22: expected decoration");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(p->error(), "1:22: expected decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_MissingComma) {
   auto* p = parser("[[workgroup_size(2) workgroup_size(2)]]");
-  ast::DecorationList decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:21: expected ',' for decoration list");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(p->error(), "1:21: expected ',' for decoration list");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_BadDecoration) {
   auto* p = parser("[[workgroup_size()]]");
-  ast::DecorationList decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(
       p->error(),
       "1:18: expected signed integer literal for workgroup_size x parameter");
 }
 
 TEST_F(ParserImplTest, FunctionDecorationList_MissingRightAttr) {
   auto* p = parser("[[workgroup_size(2), workgroup_size(3, 4, 5)");
-  ast::DecorationList decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:45: expected ']]' for decoration list");
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(p->error(), "1:45: expected ']]' for decoration list");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_function_decoration_test.cc b/src/reader/wgsl/parser_impl_function_decoration_test.cc
index 90c9344..0518569 100644
--- a/src/reader/wgsl/parser_impl_function_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_function_decoration_test.cc
@@ -26,9 +26,11 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup) {
   auto* p = parser("workgroup_size(4)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr) << p->error();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco.value));
   ASSERT_NE(func_deco, nullptr);
   ASSERT_TRUE(func_deco->IsWorkgroup());
 
@@ -44,9 +46,11 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_2Param) {
   auto* p = parser("workgroup_size(4, 5)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr) << p->error();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco.value));
   ASSERT_NE(func_deco, nullptr) << p->error();
   ASSERT_TRUE(func_deco->IsWorkgroup());
 
@@ -62,9 +66,11 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_3Param) {
   auto* p = parser("workgroup_size(4, 5, 6)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr) << p->error();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco.value));
   ASSERT_NE(func_deco, nullptr);
   ASSERT_TRUE(func_deco->IsWorkgroup());
 
@@ -80,16 +86,20 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_TooManyValues) {
   auto* p = parser("workgroup_size(1, 2, 3, 4)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Invalid_X_Value) {
   auto* p = parser("workgroup_size(-2, 5, 6)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:16: workgroup_size x parameter must be greater than 0");
 }
@@ -97,8 +107,10 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Invalid_Y_Value) {
   auto* p = parser("workgroup_size(4, 0, 6)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:19: workgroup_size y parameter must be greater than 0");
 }
@@ -106,8 +118,10 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Invalid_Z_Value) {
   auto* p = parser("workgroup_size(4, 5, -3)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:22: workgroup_size z parameter must be greater than 0");
 }
@@ -115,24 +129,30 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_MissingLeftParam) {
   auto* p = parser("workgroup_size 4, 5, 6)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:16: expected '(' for workgroup_size decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_MissingRightParam) {
   auto* p = parser("workgroup_size(4, 5, 6");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:23: expected ')' for workgroup_size decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_MissingValues) {
   auto* p = parser("workgroup_size()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:16: expected signed integer literal for workgroup_size x parameter");
@@ -141,8 +161,10 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_X_Value) {
   auto* p = parser("workgroup_size(, 2, 3)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:16: expected signed integer literal for workgroup_size x parameter");
@@ -151,16 +173,20 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Y_Comma) {
   auto* p = parser("workgroup_size(1 2, 3)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:18: expected ')' for workgroup_size decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Y_Value) {
   auto* p = parser("workgroup_size(1, , 3)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:19: expected signed integer literal for workgroup_size y parameter");
@@ -169,16 +195,20 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Z_Comma) {
   auto* p = parser("workgroup_size(1, 2 3)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:21: expected ')' for workgroup_size decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Z_Value) {
   auto* p = parser("workgroup_size(1, 2, )");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:22: expected signed integer literal for workgroup_size z parameter");
@@ -187,8 +217,10 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_X_Invalid) {
   auto* p = parser("workgroup_size(nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:16: expected signed integer literal for workgroup_size x parameter");
@@ -197,8 +229,10 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Y_Invalid) {
   auto* p = parser("workgroup_size(2, nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:19: expected signed integer literal for workgroup_size y parameter");
@@ -207,8 +241,10 @@
 TEST_F(ParserImplTest, FunctionDecoration_Workgroup_Missing_Z_Invalid) {
   auto* p = parser("workgroup_size(2, 3, nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(
       p->error(),
       "1:22: expected signed integer literal for workgroup_size z parameter");
@@ -217,9 +253,11 @@
 TEST_F(ParserImplTest, FunctionDecoration_Stage) {
   auto* p = parser("stage(compute)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr) << p->error();
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco));
+  auto func_deco = ast::As<ast::FunctionDecoration>(std::move(deco.value));
   ASSERT_NE(func_deco, nullptr);
   ASSERT_TRUE(func_deco->IsStage());
   EXPECT_EQ(func_deco->AsStage()->value(), ast::PipelineStage::kCompute);
@@ -228,32 +266,40 @@
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingValue) {
   auto* p = parser("stage()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: invalid value for stage decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingInvalid) {
   auto* p = parser("stage(nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: invalid value for stage decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingLeftParen) {
   auto* p = parser("stage compute)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: expected '(' for stage decoration");
 }
 
 TEST_F(ParserImplTest, FunctionDecoration_Stage_MissingRightParen) {
   auto* p = parser("stage(compute");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:14: expected ')' for stage decoration");
 }
 
diff --git a/src/reader/wgsl/parser_impl_function_header_test.cc b/src/reader/wgsl/parser_impl_function_header_test.cc
index 20a94ff..a7f000b 100644
--- a/src/reader/wgsl/parser_impl_function_header_test.cc
+++ b/src/reader/wgsl/parser_impl_function_header_test.cc
@@ -27,76 +27,94 @@
   auto* p = parser("fn main(a : i32, b: f32) -> void");
   auto f = p->function_header();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(f, nullptr);
+  EXPECT_TRUE(f.matched);
+  EXPECT_FALSE(f.errored);
+  ASSERT_NE(f.value, nullptr);
 
-  EXPECT_EQ(f->name(), "main");
-  ASSERT_EQ(f->params().size(), 2u);
-  EXPECT_EQ(f->params()[0]->name(), "a");
-  EXPECT_EQ(f->params()[1]->name(), "b");
-  EXPECT_TRUE(f->return_type()->IsVoid());
+  EXPECT_EQ(f.value->name(), "main");
+  ASSERT_EQ(f.value->params().size(), 2u);
+  EXPECT_EQ(f.value->params()[0]->name(), "a");
+  EXPECT_EQ(f.value->params()[1]->name(), "b");
+  EXPECT_TRUE(f.value->return_type()->IsVoid());
 }
 
 TEST_F(ParserImplTest, FunctionHeader_MissingIdent) {
   auto* p = parser("fn () ->");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:4: expected identifier for function declaration");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_InvalidIdent) {
   auto* p = parser("fn 133main() -> i32");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:4: expected identifier for function declaration");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_MissingParenLeft) {
   auto* p = parser("fn main) -> i32");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected '(' for function declaration");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_InvalidParamList) {
   auto* p = parser("fn main(a :i32,) -> i32");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:16: expected identifier for parameter");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_MissingParenRight) {
   auto* p = parser("fn main( -> i32");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:10: expected ')' for function declaration");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_MissingArrow) {
   auto* p = parser("fn main() i32");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
-  EXPECT_EQ(p->error(), "1:11: missing -> for function declaration");
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
+  EXPECT_EQ(p->error(), "1:11: expected '->' for function declaration");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_InvalidReturnType) {
   auto* p = parser("fn main() -> invalid");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:14: unknown constructed type 'invalid'");
 }
 
 TEST_F(ParserImplTest, FunctionHeader_MissingReturnType) {
   auto* p = parser("fn main() ->");
   auto f = p->function_header();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(f, nullptr);
+  EXPECT_FALSE(f.matched);
+  EXPECT_TRUE(f.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(f.value, nullptr);
   EXPECT_EQ(p->error(), "1:13: unable to determine function return type");
 }
 
diff --git a/src/reader/wgsl/parser_impl_function_type_decl_test.cc b/src/reader/wgsl/parser_impl_function_type_decl_test.cc
index 30bc605..14bf09d 100644
--- a/src/reader/wgsl/parser_impl_function_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_function_type_decl_test.cc
@@ -31,9 +31,11 @@
   auto* v = tm()->Get(std::make_unique<ast::type::VoidType>());
 
   auto* p = parser("void");
-  auto* e = p->function_type_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, v);
+  auto e = p->function_type_decl();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value, v);
 }
 
 TEST_F(ParserImplTest, FunctionTypeDecl_Type) {
@@ -41,16 +43,20 @@
   auto* vec2 = tm()->Get(std::make_unique<ast::type::VectorType>(f32, 2));
 
   auto* p = parser("vec2<f32>");
-  auto* e = p->function_type_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, vec2);
+  auto e = p->function_type_decl();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value, vec2);
 }
 
 TEST_F(ParserImplTest, FunctionTypeDecl_InvalidType) {
   auto* p = parser("vec2<invalid>");
-  auto* e = p->function_type_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  auto e = p->function_type_decl();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: unknown constructed type 'invalid'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
index fd2719b..929be79 100644
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -26,52 +26,62 @@
 TEST_F(ParserImplTest, GlobalConstantDecl) {
   auto* p = parser("const a : f32 = 1.");
   auto e = p->global_constant_decl();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
 
-  EXPECT_TRUE(e->is_const());
-  EXPECT_EQ(e->name(), "a");
-  ASSERT_NE(e->type(), nullptr);
-  EXPECT_TRUE(e->type()->IsF32());
+  EXPECT_TRUE(e.value->is_const());
+  EXPECT_EQ(e.value->name(), "a");
+  ASSERT_NE(e.value->type(), nullptr);
+  EXPECT_TRUE(e.value->type()->IsF32());
 
-  EXPECT_EQ(e->source().range.begin.line, 1u);
-  EXPECT_EQ(e->source().range.begin.column, 7u);
-  EXPECT_EQ(e->source().range.end.line, 1u);
-  EXPECT_EQ(e->source().range.end.column, 8u);
+  EXPECT_EQ(e.value->source().range.begin.line, 1u);
+  EXPECT_EQ(e.value->source().range.begin.column, 7u);
+  EXPECT_EQ(e.value->source().range.end.line, 1u);
+  EXPECT_EQ(e.value->source().range.end.column, 8u);
 
-  ASSERT_NE(e->constructor(), nullptr);
-  EXPECT_TRUE(e->constructor()->IsConstructor());
+  ASSERT_NE(e.value->constructor(), nullptr);
+  EXPECT_TRUE(e.value->constructor()->IsConstructor());
 }
 
 TEST_F(ParserImplTest, GlobalConstantDecl_MissingEqual) {
   auto* p = parser("const a: f32 1.");
   auto e = p->global_constant_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:14: expected '=' for constant declaration");
 }
 
 TEST_F(ParserImplTest, GlobalConstantDecl_InvalidVariable) {
   auto* p = parser("const a: invalid = 1.");
   auto e = p->global_constant_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:10: unknown constructed type 'invalid'");
 }
 
 TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) {
   auto* p = parser("const a: f32 = if (a) {}");
   auto e = p->global_constant_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:16: unable to parse const literal");
 }
 
 TEST_F(ParserImplTest, GlobalConstantDecl_MissingExpression) {
   auto* p = parser("const a: f32 =");
   auto e = p->global_constant_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:15: unable to parse const literal");
 }
 
diff --git a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
index 49494c1..d6dbcbd 100644
--- a/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_variable_decl_test.cc
@@ -25,130 +25,161 @@
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithoutConstructor) {
   auto* p = parser("var<out> a : f32");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_variable_decl(decos.value);
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
 
-  EXPECT_EQ(e->name(), "a");
-  EXPECT_TRUE(e->type()->IsF32());
-  EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
+  EXPECT_EQ(e.value->name(), "a");
+  EXPECT_TRUE(e.value->type()->IsF32());
+  EXPECT_EQ(e.value->storage_class(), ast::StorageClass::kOutput);
 
-  EXPECT_EQ(e->source().range.begin.line, 1u);
-  EXPECT_EQ(e->source().range.begin.column, 10u);
-  EXPECT_EQ(e->source().range.end.line, 1u);
-  EXPECT_EQ(e->source().range.end.column, 11u);
+  EXPECT_EQ(e.value->source().range.begin.line, 1u);
+  EXPECT_EQ(e.value->source().range.begin.column, 10u);
+  EXPECT_EQ(e.value->source().range.end.line, 1u);
+  EXPECT_EQ(e.value->source().range.end.column, 11u);
 
-  ASSERT_EQ(e->constructor(), nullptr);
-  ASSERT_FALSE(e->IsDecorated());
+  ASSERT_EQ(e.value->constructor(), nullptr);
+  ASSERT_FALSE(e.value->IsDecorated());
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithConstructor) {
   auto* p = parser("var<out> a : f32 = 1.");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_variable_decl(decos.value);
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
 
-  EXPECT_EQ(e->name(), "a");
-  EXPECT_TRUE(e->type()->IsF32());
-  EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
+  EXPECT_EQ(e.value->name(), "a");
+  EXPECT_TRUE(e.value->type()->IsF32());
+  EXPECT_EQ(e.value->storage_class(), ast::StorageClass::kOutput);
 
-  EXPECT_EQ(e->source().range.begin.line, 1u);
-  EXPECT_EQ(e->source().range.begin.column, 10u);
-  EXPECT_EQ(e->source().range.end.line, 1u);
-  EXPECT_EQ(e->source().range.end.column, 11u);
+  EXPECT_EQ(e.value->source().range.begin.line, 1u);
+  EXPECT_EQ(e.value->source().range.begin.column, 10u);
+  EXPECT_EQ(e.value->source().range.end.line, 1u);
+  EXPECT_EQ(e.value->source().range.end.column, 11u);
 
-  ASSERT_NE(e->constructor(), nullptr);
-  ASSERT_TRUE(e->constructor()->IsConstructor());
-  ASSERT_TRUE(e->constructor()->AsConstructor()->IsScalarConstructor());
+  ASSERT_NE(e.value->constructor(), nullptr);
+  ASSERT_TRUE(e.value->constructor()->IsConstructor());
+  ASSERT_TRUE(e.value->constructor()->AsConstructor()->IsScalarConstructor());
 
-  ASSERT_FALSE(e->IsDecorated());
+  ASSERT_FALSE(e.value->IsDecorated());
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithDecoration) {
   auto* p = parser("[[binding(2), set(1)]] var<out> a : f32");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  auto e = p->global_variable_decl(decos.value);
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsDecorated());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsDecorated());
 
-  EXPECT_EQ(e->name(), "a");
-  ASSERT_NE(e->type(), nullptr);
-  EXPECT_TRUE(e->type()->IsF32());
-  EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
+  EXPECT_EQ(e.value->name(), "a");
+  ASSERT_NE(e.value->type(), nullptr);
+  EXPECT_TRUE(e.value->type()->IsF32());
+  EXPECT_EQ(e.value->storage_class(), ast::StorageClass::kOutput);
 
-  EXPECT_EQ(e->source().range.begin.line, 1u);
-  EXPECT_EQ(e->source().range.begin.column, 33u);
-  EXPECT_EQ(e->source().range.end.line, 1u);
-  EXPECT_EQ(e->source().range.end.column, 34u);
+  EXPECT_EQ(e.value->source().range.begin.line, 1u);
+  EXPECT_EQ(e.value->source().range.begin.column, 33u);
+  EXPECT_EQ(e.value->source().range.end.line, 1u);
+  EXPECT_EQ(e.value->source().range.end.column, 34u);
 
-  ASSERT_EQ(e->constructor(), nullptr);
+  ASSERT_EQ(e.value->constructor(), nullptr);
 
-  ASSERT_TRUE(e->IsDecorated());
-  auto* v = e->AsDecorated();
+  ASSERT_TRUE(e.value->IsDecorated());
+  auto* v = e.value->AsDecorated();
 
-  auto& decos = v->decorations();
-  ASSERT_EQ(decos.size(), 2u);
-  ASSERT_TRUE(decos[0]->IsBinding());
-  ASSERT_TRUE(decos[1]->IsSet());
+  auto& decorations = v->decorations();
+  ASSERT_EQ(decorations.size(), 2u);
+  ASSERT_TRUE(decorations[0]->IsBinding());
+  ASSERT_TRUE(decorations[1]->IsSet());
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_WithDecoration_MulitpleGroups) {
   auto* p = parser("[[binding(2)]] [[set(1)]] var<out> a : f32");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+
+  auto e = p->global_variable_decl(decos.value);
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsDecorated());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsDecorated());
 
-  EXPECT_EQ(e->name(), "a");
-  ASSERT_NE(e->type(), nullptr);
-  EXPECT_TRUE(e->type()->IsF32());
-  EXPECT_EQ(e->storage_class(), ast::StorageClass::kOutput);
+  EXPECT_EQ(e.value->name(), "a");
+  ASSERT_NE(e.value->type(), nullptr);
+  EXPECT_TRUE(e.value->type()->IsF32());
+  EXPECT_EQ(e.value->storage_class(), ast::StorageClass::kOutput);
 
-  EXPECT_EQ(e->source().range.begin.line, 1u);
-  EXPECT_EQ(e->source().range.begin.column, 36u);
-  EXPECT_EQ(e->source().range.end.line, 1u);
-  EXPECT_EQ(e->source().range.end.column, 37u);
+  EXPECT_EQ(e.value->source().range.begin.line, 1u);
+  EXPECT_EQ(e.value->source().range.begin.column, 36u);
+  EXPECT_EQ(e.value->source().range.end.line, 1u);
+  EXPECT_EQ(e.value->source().range.end.column, 37u);
 
-  ASSERT_EQ(e->constructor(), nullptr);
+  ASSERT_EQ(e.value->constructor(), nullptr);
 
-  ASSERT_TRUE(e->IsDecorated());
-  auto* v = e->AsDecorated();
+  ASSERT_TRUE(e.value->IsDecorated());
+  auto* v = e.value->AsDecorated();
 
-  auto& decos = v->decorations();
-  ASSERT_EQ(decos.size(), 2u);
-  ASSERT_TRUE(decos[0]->IsBinding());
-  ASSERT_TRUE(decos[1]->IsSet());
+  auto& decorations = v->decorations();
+  ASSERT_EQ(decorations.size(), 2u);
+  ASSERT_TRUE(decorations[0]->IsBinding());
+  ASSERT_TRUE(decorations[1]->IsSet());
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidDecoration) {
   auto* p = parser("[[binding()]] var<out> a : f32");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+
+  auto e = p->global_variable_decl(decos.value);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:11: expected signed integer literal for binding decoration");
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidConstExpr) {
   auto* p = parser("var<out> a : f32 = if (a) {}");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_variable_decl(decos.value);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:20: unable to parse const literal");
 }
 
 TEST_F(ParserImplTest, GlobalVariableDecl_InvalidVariableDecl) {
   auto* p = parser("var<invalid> a : f32;");
-  auto decorations = p->decoration_list();
-  auto e = p->global_variable_decl(decorations);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_variable_decl(decos.value);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:5: invalid storage class for variable decoration");
 }
 
diff --git a/src/reader/wgsl/parser_impl_if_stmt_test.cc b/src/reader/wgsl/parser_impl_if_stmt_test.cc
index 50c3827..8dea243 100644
--- a/src/reader/wgsl/parser_impl_if_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_if_stmt_test.cc
@@ -26,82 +26,98 @@
 TEST_F(ParserImplTest, IfStmt) {
   auto* p = parser("if (a == 4) { a = b; c = d; }");
   auto e = p->if_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsIf());
-  ASSERT_NE(e->condition(), nullptr);
-  ASSERT_TRUE(e->condition()->IsBinary());
-  EXPECT_EQ(e->body()->size(), 2u);
-  EXPECT_EQ(e->else_statements().size(), 0u);
+  ASSERT_TRUE(e.value->IsIf());
+  ASSERT_NE(e.value->condition(), nullptr);
+  ASSERT_TRUE(e.value->condition()->IsBinary());
+  EXPECT_EQ(e.value->body()->size(), 2u);
+  EXPECT_EQ(e.value->else_statements().size(), 0u);
 }
 
 TEST_F(ParserImplTest, IfStmt_WithElse) {
   auto* p =
       parser("if (a == 4) { a = b; c = d; } elseif(c) { d = 2; } else {}");
   auto e = p->if_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsIf());
-  ASSERT_NE(e->condition(), nullptr);
-  ASSERT_TRUE(e->condition()->IsBinary());
-  EXPECT_EQ(e->body()->size(), 2u);
+  ASSERT_TRUE(e.value->IsIf());
+  ASSERT_NE(e.value->condition(), nullptr);
+  ASSERT_TRUE(e.value->condition()->IsBinary());
+  EXPECT_EQ(e.value->body()->size(), 2u);
 
-  ASSERT_EQ(e->else_statements().size(), 2u);
-  ASSERT_NE(e->else_statements()[0]->condition(), nullptr);
-  ASSERT_TRUE(e->else_statements()[0]->condition()->IsIdentifier());
-  EXPECT_EQ(e->else_statements()[0]->body()->size(), 1u);
+  ASSERT_EQ(e.value->else_statements().size(), 2u);
+  ASSERT_NE(e.value->else_statements()[0]->condition(), nullptr);
+  ASSERT_TRUE(e.value->else_statements()[0]->condition()->IsIdentifier());
+  EXPECT_EQ(e.value->else_statements()[0]->body()->size(), 1u);
 
-  ASSERT_EQ(e->else_statements()[1]->condition(), nullptr);
-  EXPECT_EQ(e->else_statements()[1]->body()->size(), 0u);
+  ASSERT_EQ(e.value->else_statements()[1]->condition(), nullptr);
+  EXPECT_EQ(e.value->else_statements()[1]->body()->size(), 0u);
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidCondition) {
   auto* p = parser("if (a = 3) {}");
   auto e = p->if_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: expected ')'");
 }
 
 TEST_F(ParserImplTest, IfStmt_MissingCondition) {
   auto* p = parser("if {}");
   auto e = p->if_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:4: expected '('");
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidBody) {
   auto* p = parser("if (a) { fn main() -> void {}}");
   auto e = p->if_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected '}'");
 }
 
 TEST_F(ParserImplTest, IfStmt_MissingBody) {
   auto* p = parser("if (a)");
   auto e = p->if_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:7: expected '{'");
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidElseif) {
   auto* p = parser("if (a) {} elseif (a) { fn main() -> a{}}");
   auto e = p->if_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:24: expected '}'");
 }
 
 TEST_F(ParserImplTest, IfStmt_InvalidElse) {
   auto* p = parser("if (a) {} else { fn main() -> a{}}");
   auto e = p->if_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:18: expected '}'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
index e7451e7..073c2be 100644
--- a/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_inclusive_or_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, InclusiveOrExpression_Parses) {
   auto* p = parser("a | true");
   auto e = p->inclusive_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kOr, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,24 +51,30 @@
 TEST_F(ParserImplTest, InclusiveOrExpression_InvalidLHS) {
   auto* p = parser("if (a) {} | true");
   auto e = p->inclusive_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, InclusiveOrExpression_InvalidRHS) {
   auto* p = parser("true | if (a) {}");
   auto e = p->inclusive_or_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: unable to parse right side of | expression");
 }
 
 TEST_F(ParserImplTest, InclusiveOrExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->inclusive_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
index 03b102b..c11de2a 100644
--- a/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_logical_and_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, LogicalAndExpression_Parses) {
   auto* p = parser("a && true");
   auto e = p->logical_and_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kLogicalAnd, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,24 +51,30 @@
 TEST_F(ParserImplTest, LogicalAndExpression_InvalidLHS) {
   auto* p = parser("if (a) {} && true");
   auto e = p->logical_and_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, LogicalAndExpression_InvalidRHS) {
   auto* p = parser("true && if (a) {}");
   auto e = p->logical_and_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: unable to parse right side of && expression");
 }
 
 TEST_F(ParserImplTest, LogicalAndExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->logical_and_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
index 444e871..3925590 100644
--- a/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_logical_or_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, LogicalOrExpression_Parses) {
   auto* p = parser("a || true");
   auto e = p->logical_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kLogicalOr, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,24 +51,30 @@
 TEST_F(ParserImplTest, LogicalOrExpression_InvalidLHS) {
   auto* p = parser("if (a) {} || true");
   auto e = p->logical_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, LogicalOrExpression_InvalidRHS) {
   auto* p = parser("true || if (a) {}");
   auto e = p->logical_or_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: unable to parse right side of || expression");
 }
 
 TEST_F(ParserImplTest, LogicalOrExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->logical_or_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_loop_stmt_test.cc b/src/reader/wgsl/parser_impl_loop_stmt_test.cc
index 41aea2f..6b4aa2f 100644
--- a/src/reader/wgsl/parser_impl_loop_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_loop_stmt_test.cc
@@ -24,76 +24,92 @@
 TEST_F(ParserImplTest, LoopStmt_BodyNoContinuing) {
   auto* p = parser("loop { discard; }");
   auto e = p->loop_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_EQ(e->body()->size(), 1u);
-  EXPECT_TRUE(e->body()->get(0)->IsDiscard());
+  ASSERT_EQ(e.value->body()->size(), 1u);
+  EXPECT_TRUE(e.value->body()->get(0)->IsDiscard());
 
-  EXPECT_EQ(e->continuing()->size(), 0u);
+  EXPECT_EQ(e.value->continuing()->size(), 0u);
 }
 
 TEST_F(ParserImplTest, LoopStmt_BodyWithContinuing) {
   auto* p = parser("loop { discard; continuing { discard; }}");
   auto e = p->loop_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_EQ(e->body()->size(), 1u);
-  EXPECT_TRUE(e->body()->get(0)->IsDiscard());
+  ASSERT_EQ(e.value->body()->size(), 1u);
+  EXPECT_TRUE(e.value->body()->get(0)->IsDiscard());
 
-  EXPECT_EQ(e->continuing()->size(), 1u);
-  EXPECT_TRUE(e->continuing()->get(0)->IsDiscard());
+  EXPECT_EQ(e.value->continuing()->size(), 1u);
+  EXPECT_TRUE(e.value->continuing()->get(0)->IsDiscard());
 }
 
 TEST_F(ParserImplTest, LoopStmt_NoBodyNoContinuing) {
   auto* p = parser("loop { }");
   auto e = p->loop_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_EQ(e->body()->size(), 0u);
-  ASSERT_EQ(e->continuing()->size(), 0u);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_EQ(e.value->body()->size(), 0u);
+  ASSERT_EQ(e.value->continuing()->size(), 0u);
 }
 
 TEST_F(ParserImplTest, LoopStmt_NoBodyWithContinuing) {
   auto* p = parser("loop { continuing { discard; }}");
   auto e = p->loop_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_EQ(e->body()->size(), 0u);
-  ASSERT_EQ(e->continuing()->size(), 1u);
-  EXPECT_TRUE(e->continuing()->get(0)->IsDiscard());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_EQ(e.value->body()->size(), 0u);
+  ASSERT_EQ(e.value->continuing()->size(), 1u);
+  EXPECT_TRUE(e.value->continuing()->get(0)->IsDiscard());
 }
 
 TEST_F(ParserImplTest, LoopStmt_MissingBracketLeft) {
   auto* p = parser("loop discard; }");
   auto e = p->loop_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
 }
 
 TEST_F(ParserImplTest, LoopStmt_MissingBracketRight) {
   auto* p = parser("loop { discard; ");
   auto e = p->loop_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:17: expected '}' for loop");
 }
 
 TEST_F(ParserImplTest, LoopStmt_InvalidStatements) {
   auto* p = parser("loop { discard }");
   auto e = p->loop_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:16: expected ';' for discard statement");
 }
 
 TEST_F(ParserImplTest, LoopStmt_InvalidContinuing) {
   auto* p = parser("loop { continuing { discard }}");
   auto e = p->loop_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:29: expected ';' for discard statement");
 }
 
diff --git a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
index 3c6c1fa..d311020 100644
--- a/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_multiplicative_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Multiply) {
   auto* p = parser("a * true");
   auto e = p->multiplicative_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kMultiply, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,11 +51,13 @@
 TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Divide) {
   auto* p = parser("a / true");
   auto e = p->multiplicative_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kDivide, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -70,11 +74,13 @@
 TEST_F(ParserImplTest, MultiplicativeExpression_Parses_Modulo) {
   auto* p = parser("a % true");
   auto e = p->multiplicative_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kModulo, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -91,24 +97,30 @@
 TEST_F(ParserImplTest, MultiplicativeExpression_InvalidLHS) {
   auto* p = parser("if (a) {} * true");
   auto e = p->multiplicative_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, MultiplicativeExpression_InvalidRHS) {
   auto* p = parser("true * if (a) {}");
   auto e = p->multiplicative_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:8: unable to parse right side of * expression");
 }
 
 TEST_F(ParserImplTest, MultiplicativeExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->multiplicative_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_postfix_expression_test.cc b/src/reader/wgsl/parser_impl_postfix_expression_test.cc
index 31ca9c6..952103b 100644
--- a/src/reader/wgsl/parser_impl_postfix_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_postfix_expression_test.cc
@@ -31,11 +31,13 @@
 TEST_F(ParserImplTest, PostfixExpression_Array_ConstantIndex) {
   auto* p = parser("a[1]");
   auto e = p->postfix_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsArrayAccessor());
-  auto* ary = e->AsArrayAccessor();
+  ASSERT_TRUE(e.value->IsArrayAccessor());
+  auto* ary = e.value->AsArrayAccessor();
 
   ASSERT_TRUE(ary->array()->IsIdentifier());
   auto* ident = ary->array()->AsIdentifier();
@@ -51,11 +53,13 @@
 TEST_F(ParserImplTest, PostfixExpression_Array_ExpressionIndex) {
   auto* p = parser("a[1 + b / 4]");
   auto e = p->postfix_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsArrayAccessor());
-  auto* ary = e->AsArrayAccessor();
+  ASSERT_TRUE(e.value->IsArrayAccessor());
+  auto* ary = e.value->AsArrayAccessor();
 
   ASSERT_TRUE(ary->array()->IsIdentifier());
   auto* ident = ary->array()->AsIdentifier();
@@ -67,35 +71,43 @@
 TEST_F(ParserImplTest, PostfixExpression_Array_MissingIndex) {
   auto* p = parser("a[]");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: unable to parse expression inside []");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Array_MissingRightBrace) {
   auto* p = parser("a[1");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:4: missing ] for array accessor");
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:4: expected ']' for array accessor");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Array_InvalidIndex) {
   auto* p = parser("a[if(a() {})]");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: unable to parse expression inside []");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Call_Empty) {
   auto* p = parser("a()");
   auto e = p->postfix_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsCall());
-  auto* c = e->AsCall();
+  ASSERT_TRUE(e.value->IsCall());
+  auto* c = e.value->AsCall();
 
   ASSERT_TRUE(c->func()->IsIdentifier());
   auto* func = c->func()->AsIdentifier();
@@ -107,11 +119,13 @@
 TEST_F(ParserImplTest, PostfixExpression_Call_WithArgs) {
   auto* p = parser("test(1, b, 2 + 3 / b)");
   auto e = p->postfix_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsCall());
-  auto* c = e->AsCall();
+  ASSERT_TRUE(e.value->IsCall());
+  auto* c = e.value->AsCall();
 
   ASSERT_TRUE(c->func()->IsIdentifier());
   auto* func = c->func()->AsIdentifier();
@@ -126,35 +140,43 @@
 TEST_F(ParserImplTest, PostfixExpression_Call_InvalidArg) {
   auto* p = parser("a(if(a) {})");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: unable to parse argument expression");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Call_HangingComma) {
   auto* p = parser("a(b, )");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:6: unable to parse argument expression after comma");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_Call_MissingRightParen) {
   auto* p = parser("a(");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: expected ')' for call expression");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_MemberAccessor) {
   auto* p = parser("a.b");
   auto e = p->postfix_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsMemberAccessor());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsMemberAccessor());
 
-  auto* m = e->AsMemberAccessor();
+  auto* m = e.value->AsMemberAccessor();
   ASSERT_TRUE(m->structure()->IsIdentifier());
   EXPECT_EQ(m->structure()->AsIdentifier()->name(), "a");
 
@@ -165,25 +187,31 @@
 TEST_F(ParserImplTest, PostfixExpression_MemberAccesssor_InvalidIdent) {
   auto* p = parser("a.if");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: expected identifier for member accessor");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_MemberAccessor_MissingIdent) {
   auto* p = parser("a.");
   auto e = p->postfix_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: expected identifier for member accessor");
 }
 
 TEST_F(ParserImplTest, PostfixExpression_NonMatch_returnLHS) {
   auto* p = parser("a b");
   auto e = p->postfix_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_primary_expression_test.cc b/src/reader/wgsl/parser_impl_primary_expression_test.cc
index 41c218c..3ef055d 100644
--- a/src/reader/wgsl/parser_impl_primary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_primary_expression_test.cc
@@ -35,21 +35,25 @@
 TEST_F(ParserImplTest, PrimaryExpression_Ident) {
   auto* p = parser("a");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
-  auto* ident = e->AsIdentifier();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
+  auto* ident = e.value->AsIdentifier();
   EXPECT_EQ(ident->name(), "a");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl) {
   auto* p = parser("vec4<i32>(1, 2, 3, 4))");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
-  auto* ty = e->AsConstructor()->AsTypeConstructor();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsConstructor());
+  ASSERT_TRUE(e.value->AsConstructor()->IsTypeConstructor());
+  auto* ty = e.value->AsConstructor()->AsTypeConstructor();
 
   ASSERT_EQ(ty->values().size(), 4u);
   const auto& val = ty->values();
@@ -81,11 +85,13 @@
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_ZeroConstructor) {
   auto* p = parser("vec4<i32>()");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
-  auto* ty = e->AsConstructor()->AsTypeConstructor();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsConstructor());
+  ASSERT_TRUE(e.value->AsConstructor()->IsTypeConstructor());
+  auto* ty = e.value->AsConstructor()->AsTypeConstructor();
 
   ASSERT_EQ(ty->values().size(), 0u);
 }
@@ -93,43 +99,53 @@
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidTypeDecl) {
   auto* p = parser("vec4<if>(2., 3., 4., 5.)");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:6: unable to determine subtype for vector");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_MissingLeftParen) {
   auto* p = parser("vec4<f32> 2., 3., 4., 5.)");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:11: expected '(' for type constructor");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_MissingRightParen) {
   auto* p = parser("vec4<f32>(2., 3., 4., 5.");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:25: expected ')' for type constructor");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_InvalidValue) {
   auto* p = parser("i32(if(a) {})");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:5: unable to parse argument expression");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_ConstLiteral_True) {
   auto* p = parser("true");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsScalarConstructor());
-  auto* init = e->AsConstructor()->AsScalarConstructor();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsConstructor());
+  ASSERT_TRUE(e.value->AsConstructor()->IsScalarConstructor());
+  auto* init = e.value->AsConstructor()->AsScalarConstructor();
   ASSERT_TRUE(init->literal()->IsBool());
   EXPECT_TRUE(init->literal()->AsBool()->IsTrue());
 }
@@ -137,32 +153,40 @@
 TEST_F(ParserImplTest, PrimaryExpression_ParenExpr) {
   auto* p = parser("(a == b)");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsBinary());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsBinary());
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingRightParen) {
   auto* p = parser("(a == b");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected ')'");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_MissingExpr) {
   auto* p = parser("()");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:2: unable to parse expression");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_ParenExpr_InvalidExpr) {
   auto* p = parser("(if (a) {})");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:2: unable to parse expression");
 }
 
@@ -171,12 +195,14 @@
 
   auto* p = parser("f32(1)");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsConstructor());
-  ASSERT_TRUE(e->AsConstructor()->IsTypeConstructor());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsConstructor());
+  ASSERT_TRUE(e.value->AsConstructor()->IsTypeConstructor());
 
-  auto* c = e->AsConstructor()->AsTypeConstructor();
+  auto* c = e.value->AsConstructor()->AsTypeConstructor();
   ASSERT_EQ(c->type(), f32_type);
   ASSERT_EQ(c->values().size(), 1u);
 
@@ -189,11 +215,13 @@
 
   auto* p = parser("bitcast<f32>(1)");
   auto e = p->primary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsBitcast());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsBitcast());
 
-  auto* c = e->AsBitcast();
+  auto* c = e.value->AsBitcast();
   ASSERT_EQ(c->type(), f32_type);
 
   ASSERT_TRUE(c->expr()->IsConstructor());
@@ -203,56 +231,70 @@
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingGreaterThan) {
   auto* p = parser("bitcast<f32(1)");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
-  EXPECT_EQ(p->error(), "1:12: missing > for bitcast expression");
+  EXPECT_EQ(p->error(), "1:12: expected '>' for bitcast expression");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingType) {
   auto* p = parser("bitcast<>(1)");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:9: missing type for bitcast expression");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_InvalidType) {
   auto* p = parser("bitcast<invalid>(1)");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:9: unknown constructed type 'invalid'");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingLeftParen) {
   auto* p = parser("bitcast<f32>1)");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:13: expected '('");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingRightParen) {
   auto* p = parser("bitcast<f32>(1");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:15: expected ')'");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingExpression) {
   auto* p = parser("bitcast<f32>()");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:14: unable to parse expression");
 }
 
 TEST_F(ParserImplTest, PrimaryExpression_bitcast_InvalidExpression) {
   auto* p = parser("bitcast<f32>(if (a) {})");
   auto e = p->primary_expression();
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
   EXPECT_EQ(p->error(), "1:14: unable to parse expression");
 }
 
diff --git a/src/reader/wgsl/parser_impl_relational_expression_test.cc b/src/reader/wgsl/parser_impl_relational_expression_test.cc
index 982f496..be712b9 100644
--- a/src/reader/wgsl/parser_impl_relational_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_relational_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, RelationalExpression_Parses_LessThan) {
   auto* p = parser("a < true");
   auto e = p->relational_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kLessThan, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,11 +51,13 @@
 TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThan) {
   auto* p = parser("a > true");
   auto e = p->relational_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kGreaterThan, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -70,11 +74,13 @@
 TEST_F(ParserImplTest, RelationalExpression_Parses_LessThanEqual) {
   auto* p = parser("a <= true");
   auto e = p->relational_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kLessThanEqual, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -91,11 +97,13 @@
 TEST_F(ParserImplTest, RelationalExpression_Parses_GreaterThanEqual) {
   auto* p = parser("a >= true");
   auto e = p->relational_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kGreaterThanEqual, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -112,24 +120,28 @@
 TEST_F(ParserImplTest, RelationalExpression_InvalidLHS) {
   auto* p = parser("if (a) {} < true");
   auto e = p->relational_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, RelationalExpression_InvalidRHS) {
   auto* p = parser("true < if (a) {}");
   auto e = p->relational_expression();
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:8: unable to parse right side of < expression");
 }
 
 TEST_F(ParserImplTest, RelationalExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->relational_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc b/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
index c0d6895..9406bdc 100644
--- a/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
+++ b/src/reader/wgsl/parser_impl_sampled_texture_type_test.cc
@@ -25,105 +25,134 @@
 TEST_F(ParserImplTest, SampledTextureType_Invalid) {
   auto* p = parser("1234");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::kNone);
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_1d_Old) {
   auto* p = parser("texture_sampled_1d");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k1d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k1d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_1dArray_Old) {
   auto* p = parser("texture_sampled_1d_array");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k1dArray);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k1dArray);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_2d_Old) {
   auto* p = parser("texture_sampled_2d");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k2d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k2d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_2dArray_Old) {
   auto* p = parser("texture_sampled_2d_array");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k2dArray);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k2dArray);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_3d_Old) {
   auto* p = parser("texture_sampled_3d");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k3d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k3d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_Cube_Old) {
   auto* p = parser("texture_sampled_cube");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::kCube);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::kCube);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_kCubeArray_Old) {
   auto* p = parser("texture_sampled_cube_array");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::kCubeArray);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::kCubeArray);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_1d) {
   auto* p = parser("texture_1d");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k1d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k1d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_1dArray) {
   auto* p = parser("texture_1d_array");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k1dArray);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k1dArray);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_2d) {
   auto* p = parser("texture_2d");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k2d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k2d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_2dArray) {
   auto* p = parser("texture_2d_array");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k2dArray);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k2dArray);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_3d) {
   auto* p = parser("texture_3d");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::k3d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::k3d);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_Cube) {
   auto* p = parser("texture_cube");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::kCube);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::kCube);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SampledTextureType_kCubeArray) {
   auto* p = parser("texture_cube_array");
   auto t = p->sampled_texture_type();
-  EXPECT_EQ(t, ast::type::TextureDimension::kCubeArray);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, ast::type::TextureDimension::kCubeArray);
   EXPECT_FALSE(p->has_error());
 }
 
diff --git a/src/reader/wgsl/parser_impl_sampler_type_test.cc b/src/reader/wgsl/parser_impl_sampler_type_test.cc
index db71a20..caf313c 100644
--- a/src/reader/wgsl/parser_impl_sampler_type_test.cc
+++ b/src/reader/wgsl/parser_impl_sampler_type_test.cc
@@ -24,26 +24,32 @@
 
 TEST_F(ParserImplTest, SamplerType_Invalid) {
   auto* p = parser("1234");
-  auto* t = p->sampler_type();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->sampler_type();
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(t.value, nullptr);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SamplerType_Sampler) {
   auto* p = parser("sampler");
-  auto* t = p->sampler_type();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsSampler());
-  EXPECT_FALSE(t->AsSampler()->IsComparison());
+  auto t = p->sampler_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsSampler());
+  EXPECT_FALSE(t.value->AsSampler()->IsComparison());
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, SamplerType_ComparisonSampler) {
   auto* p = parser("sampler_comparison");
-  auto* t = p->sampler_type();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsSampler());
-  EXPECT_TRUE(t->AsSampler()->IsComparison());
+  auto t = p->sampler_type();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsSampler());
+  EXPECT_TRUE(t.value->AsSampler()->IsComparison());
   EXPECT_FALSE(p->has_error());
 }
 
diff --git a/src/reader/wgsl/parser_impl_shift_expression_test.cc b/src/reader/wgsl/parser_impl_shift_expression_test.cc
index 2acf06b..2122bfd 100644
--- a/src/reader/wgsl/parser_impl_shift_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_shift_expression_test.cc
@@ -28,11 +28,13 @@
 TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftLeft) {
   auto* p = parser("a << true");
   auto e = p->shift_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kShiftLeft, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -49,11 +51,13 @@
 TEST_F(ParserImplTest, ShiftExpression_Parses_ShiftRight) {
   auto* p = parser("a >> true");
   auto e = p->shift_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsBinary());
-  auto* rel = e->AsBinary();
+  ASSERT_TRUE(e.value->IsBinary());
+  auto* rel = e.value->AsBinary();
   EXPECT_EQ(ast::BinaryOp::kShiftRight, rel->op());
 
   ASSERT_TRUE(rel->lhs()->IsIdentifier());
@@ -70,24 +74,30 @@
 TEST_F(ParserImplTest, ShiftExpression_InvalidLHS) {
   auto* p = parser("if (a) {} << true");
   auto e = p->shift_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_EQ(e.value, nullptr);
 }
 
 TEST_F(ParserImplTest, ShiftExpression_InvalidRHS) {
   auto* p = parser("true << if (a) {}");
   auto e = p->shift_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:9: unable to parse right side of << expression");
 }
 
 TEST_F(ParserImplTest, ShiftExpression_NoOr_ReturnsLHS) {
   auto* p = parser("a true");
   auto e = p->shift_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIdentifier());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsIdentifier());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_statement_test.cc b/src/reader/wgsl/parser_impl_statement_test.cc
index 2cebedf..57c5cb1 100644
--- a/src/reader/wgsl/parser_impl_statement_test.cc
+++ b/src/reader/wgsl/parser_impl_statement_test.cc
@@ -27,25 +27,25 @@
   auto* p = parser("return;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  EXPECT_TRUE(e->IsReturn());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsReturn());
 }
 
 TEST_F(ParserImplTest, Statement_Semicolon) {
   auto* p = parser(";");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(e, nullptr);
 }
 
 TEST_F(ParserImplTest, Statement_Return_NoValue) {
   auto* p = parser("return;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-
-  ASSERT_TRUE(e->IsReturn());
-  auto* ret = e->AsReturn();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsReturn());
+  auto* ret = e.value->AsReturn();
   ASSERT_EQ(ret->value(), nullptr);
 }
 
@@ -53,10 +53,11 @@
   auto* p = parser("return a + b * (.1 - .2);");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
 
-  ASSERT_TRUE(e->IsReturn());
-  auto* ret = e->AsReturn();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsReturn());
+  auto* ret = e.value->AsReturn();
   ASSERT_NE(ret->value(), nullptr);
   EXPECT_TRUE(ret->value()->IsBinary());
 }
@@ -64,16 +65,20 @@
 TEST_F(ParserImplTest, Statement_Return_MissingSemi) {
   auto* p = parser("return");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:7: expected ';' for return statement");
 }
 
 TEST_F(ParserImplTest, Statement_Return_Invalid) {
   auto* p = parser("return if(a) {};");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected ';' for return statement");
 }
 
@@ -81,15 +86,18 @@
   auto* p = parser("if (a) {}");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsIf());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsIf());
 }
 
 TEST_F(ParserImplTest, Statement_If_Invalid) {
   auto* p = parser("if (a) { fn main() -> {}}");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:10: expected '}'");
 }
 
@@ -97,23 +105,28 @@
   auto* p = parser("var a : i32 = 1;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsVariableDecl());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsVariableDecl());
 }
 
 TEST_F(ParserImplTest, Statement_Variable_Invalid) {
   auto* p = parser("var a : i32 =;");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:14: missing constructor for variable declaration");
 }
 
 TEST_F(ParserImplTest, Statement_Variable_MissingSemicolon) {
   auto* p = parser("var a : i32");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:12: expected ';' for variable declaration");
 }
 
@@ -121,15 +134,18 @@
   auto* p = parser("switch (a) {}");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsSwitch());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsSwitch());
 }
 
 TEST_F(ParserImplTest, Statement_Switch_Invalid) {
   auto* p = parser("switch (a) { case: {}}");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:18: unable to parse case selectors");
 }
 
@@ -137,15 +153,18 @@
   auto* p = parser("loop {}");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsLoop());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsLoop());
 }
 
 TEST_F(ParserImplTest, Statement_Loop_Invalid) {
   auto* p = parser("loop discard; }");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: expected '{' for loop");
 }
 
@@ -153,23 +172,28 @@
   auto* p = parser("a = b;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  EXPECT_TRUE(e->IsAssign());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsAssign());
 }
 
 TEST_F(ParserImplTest, Statement_Assignment_Invalid) {
   auto* p = parser("a = if(b) {};");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:5: unable to parse right side of assignment");
 }
 
 TEST_F(ParserImplTest, Statement_Assignment_MissingSemicolon) {
   auto* p = parser("a = b");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: expected ';' for assignment statement");
 }
 
@@ -177,15 +201,18 @@
   auto* p = parser("break;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  EXPECT_TRUE(e->IsBreak());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsBreak());
 }
 
 TEST_F(ParserImplTest, Statement_Break_MissingSemicolon) {
   auto* p = parser("break");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: expected ';' for break statement");
 }
 
@@ -193,15 +220,18 @@
   auto* p = parser("continue;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  EXPECT_TRUE(e->IsContinue());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsContinue());
 }
 
 TEST_F(ParserImplTest, Statement_Continue_MissingSemicolon) {
   auto* p = parser("continue");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:9: expected ';' for continue statement");
 }
 
@@ -209,15 +239,19 @@
   auto* p = parser("discard;");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  EXPECT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsDiscard());
+  ASSERT_NE(e.value, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsDiscard());
 }
 
 TEST_F(ParserImplTest, Statement_Discard_MissingSemicolon) {
   auto* p = parser("discard");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
   EXPECT_EQ(p->error(), "1:8: expected ';' for discard statement");
 }
 
@@ -225,16 +259,19 @@
   auto* p = parser("{ var i: i32; }");
   auto e = p->statement();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsBlock());
-  EXPECT_TRUE(e->AsBlock()->get(0)->IsVariableDecl());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_TRUE(e.value->IsBlock());
+  EXPECT_TRUE(e.value->AsBlock()->get(0)->IsVariableDecl());
 }
 
 TEST_F(ParserImplTest, Statement_Body_Invalid) {
   auto* p = parser("{ fn main() -> {}}");
   auto e = p->statement();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:3: expected '}'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_storage_texture_type_test.cc b/src/reader/wgsl/parser_impl_storage_texture_type_test.cc
index fd9ba94..2f6df88 100644
--- a/src/reader/wgsl/parser_impl_storage_texture_type_test.cc
+++ b/src/reader/wgsl/parser_impl_storage_texture_type_test.cc
@@ -25,168 +25,208 @@
 TEST_F(ParserImplTest, StorageTextureType_Invalid) {
   auto* p = parser("abc");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::kNone);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Readonly1d_Old) {
   auto* p = parser("texture_ro_1d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Readonly1dArray_Old) {
   auto* p = parser("texture_ro_1d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Readonly2d_Old) {
   auto* p = parser("texture_ro_2d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Readonly2dArray_Old) {
   auto* p = parser("texture_ro_2d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Readonly3d_Old) {
   auto* p = parser("texture_ro_3d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k3d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k3d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Writeonly1d_Old) {
   auto* p = parser("texture_wo_1d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Writeonly1dArray_Old) {
   auto* p = parser("texture_wo_1d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Writeonly2d_Old) {
   auto* p = parser("texture_wo_2d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Writeonly2dArray_Old) {
   auto* p = parser("texture_wo_2d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_Writeonly3d_Old) {
   auto* p = parser("texture_wo_3d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k3d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k3d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_ro_1d) {
   auto* p = parser("texture_storage_ro_1d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_ro_1dArray) {
   auto* p = parser("texture_storage_ro_1d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_ro_2d) {
   auto* p = parser("texture_storage_ro_2d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_ro_2dArray) {
   auto* p = parser("texture_storage_ro_2d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_ro_3d) {
   auto* p = parser("texture_storage_ro_3d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k3d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kReadOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k3d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kReadOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_wo_1d) {
   auto* p = parser("texture_storage_wo_1d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_wo_1dArray) {
   auto* p = parser("texture_storage_wo_1d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k1dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k1dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_wo_2d) {
   auto* p = parser("texture_storage_wo_2d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_wo_2dArray) {
   auto* p = parser("texture_storage_wo_2d_array");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k2dArray);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k2dArray);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, StorageTextureType_wo_3d) {
   auto* p = parser("texture_storage_wo_3d");
   auto t = p->storage_texture_type();
-  EXPECT_EQ(std::get<0>(t), ast::type::TextureDimension::k3d);
-  EXPECT_EQ(std::get<1>(t), ast::AccessControl::kWriteOnly);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  EXPECT_EQ(std::get<0>(t.value), ast::type::TextureDimension::k3d);
+  EXPECT_EQ(std::get<1>(t.value), ast::AccessControl::kWriteOnly);
   EXPECT_FALSE(p->has_error());
 }
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decl_test.cc
index 78aafaa..81fac22 100644
--- a/src/reader/wgsl/parser_impl_struct_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decl_test.cc
@@ -29,14 +29,19 @@
   [[offset(4)]] b : f32;
 })");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 0u);
-  auto s = p->struct_decl(decos);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(s, nullptr);
-  ASSERT_EQ(s->name(), "S");
-  ASSERT_EQ(s->impl()->members().size(), 2u);
-  EXPECT_EQ(s->impl()->members()[0]->name(), "a");
-  EXPECT_EQ(s->impl()->members()[1]->name(), "b");
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 0u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s.value->name(), "S");
+  ASSERT_EQ(s.value->impl()->members().size(), 2u);
+  EXPECT_EQ(s.value->impl()->members()[0]->name(), "a");
+  EXPECT_EQ(s.value->impl()->members()[1]->name(), "b");
 }
 
 TEST_F(ParserImplTest, StructDecl_ParsesWithDecoration) {
@@ -46,16 +51,21 @@
   b : f32;
 })");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 1u);
-  auto s = p->struct_decl(decos);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(s, nullptr);
-  ASSERT_EQ(s->name(), "B");
-  ASSERT_EQ(s->impl()->members().size(), 2u);
-  EXPECT_EQ(s->impl()->members()[0]->name(), "a");
-  EXPECT_EQ(s->impl()->members()[1]->name(), "b");
-  ASSERT_EQ(s->impl()->decorations().size(), 1u);
-  EXPECT_TRUE(s->impl()->decorations()[0]->IsBlock());
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 1u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s.value->name(), "B");
+  ASSERT_EQ(s.value->impl()->members().size(), 2u);
+  EXPECT_EQ(s.value->impl()->members()[0]->name(), "a");
+  EXPECT_EQ(s.value->impl()->members()[1]->name(), "b");
+  ASSERT_EQ(s.value->impl()->decorations().size(), 1u);
+  EXPECT_TRUE(s.value->impl()->decorations()[0]->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecl_ParsesWithMultipleDecoration) {
@@ -66,75 +76,115 @@
   b : f32;
 })");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 2u);
-  auto s = p->struct_decl(decos);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(s, nullptr);
-  ASSERT_EQ(s->name(), "S");
-  ASSERT_EQ(s->impl()->members().size(), 2u);
-  EXPECT_EQ(s->impl()->members()[0]->name(), "a");
-  EXPECT_EQ(s->impl()->members()[1]->name(), "b");
-  ASSERT_EQ(s->impl()->decorations().size(), 2u);
-  EXPECT_TRUE(s->impl()->decorations()[0]->IsBlock());
-  EXPECT_TRUE(s->impl()->decorations()[1]->IsBlock());
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 2u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s.value->name(), "S");
+  ASSERT_EQ(s.value->impl()->members().size(), 2u);
+  EXPECT_EQ(s.value->impl()->members()[0]->name(), "a");
+  EXPECT_EQ(s.value->impl()->members()[1]->name(), "b");
+  ASSERT_EQ(s.value->impl()->decorations().size(), 2u);
+  EXPECT_TRUE(s.value->impl()->decorations()[0]->IsBlock());
+  EXPECT_TRUE(s.value->impl()->decorations()[1]->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecl_EmptyMembers) {
   auto* p = parser("struct S {}");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 0u);
-  auto s = p->struct_decl(decos);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(s, nullptr);
-  ASSERT_EQ(s->impl()->members().size(), 0u);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 0u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(s.errored);
+  EXPECT_TRUE(s.matched);
+  ASSERT_NE(s.value, nullptr);
+  ASSERT_EQ(s.value->impl()->members().size(), 0u);
 }
 
 TEST_F(ParserImplTest, StructDecl_MissingIdent) {
   auto* p = parser("struct {}");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 0u);
-  auto s = p->struct_decl(decos);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(s, nullptr);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 0u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_TRUE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: expected identifier for struct declaration");
 }
 
 TEST_F(ParserImplTest, StructDecl_MissingBracketLeft) {
   auto* p = parser("struct S }");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 0u);
-  auto s = p->struct_decl(decos);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(s, nullptr);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 0u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_TRUE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected '{' for struct declaration");
 }
 
 TEST_F(ParserImplTest, StructDecl_InvalidStructBody) {
   auto* p = parser("struct S { a : B; }");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 0u);
-  auto s = p->struct_decl(decos);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(s, nullptr);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 0u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_TRUE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:16: unknown constructed type 'B'");
 }
 
 TEST_F(ParserImplTest, StructDecl_InvalidStructDecorationDecl) {
   auto* p = parser("[[block struct S { a : i32; }");
   auto decos = p->decoration_list();
-  auto s = p->struct_decl(decos);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(s, nullptr);
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_FALSE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected ']]' for decoration list");
 }
 
 TEST_F(ParserImplTest, StructDecl_MissingStruct) {
   auto* p = parser("[[block]] S {}");
   auto decos = p->decoration_list();
-  ASSERT_EQ(decos.size(), 1u);
-  auto s = p->struct_decl(decos);
-  ASSERT_FALSE(p->has_error());
-  ASSERT_EQ(s, nullptr);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 1u);
+
+  auto s = p->struct_decl(decos.value);
+  EXPECT_FALSE(s.errored);
+  EXPECT_FALSE(s.matched);
+  EXPECT_EQ(s.value, nullptr);
+
+  EXPECT_FALSE(p->has_error());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
index 0549a29..f33356d 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_decl_test.cc
@@ -24,25 +24,31 @@
 TEST_F(ParserImplTest, StructDecorationDecl_Parses) {
   auto* p = parser("[[block]]");
   auto decos = p->decoration_list();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_EQ(decos.size(), 1u);
-  auto struct_deco = ast::As<ast::StructDecoration>(std::move(decos[0]));
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 1u);
+  auto struct_deco = ast::As<ast::StructDecoration>(std::move(decos.value[0]));
   EXPECT_TRUE(struct_deco->IsBlock());
 }
 
 TEST_F(ParserImplTest, StructDecorationDecl_MissingAttrRight) {
   auto* p = parser("[[block");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
   EXPECT_EQ(p->error(), "1:8: expected ']]' for decoration list");
 }
 
 TEST_F(ParserImplTest, StructDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[invalid]]");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(decos.size(), 0u);
-  EXPECT_TRUE(decos.empty());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_decoration_test.cc b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
index 84c6e22..f04658e 100644
--- a/src/reader/wgsl/parser_impl_struct_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_decoration_test.cc
@@ -40,8 +40,10 @@
 
   auto deco = p->decoration();
   ASSERT_FALSE(p->has_error());
-  ASSERT_NE(deco, nullptr);
-  auto struct_deco = ast::As<ast::StructDecoration>(std::move(deco));
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto struct_deco = ast::As<ast::StructDecoration>(std::move(deco.value));
   ASSERT_NE(struct_deco, nullptr);
   EXPECT_EQ(struct_deco->IsBlock(), params.is_block);
 }
@@ -52,7 +54,9 @@
 TEST_F(ParserImplTest, StructDecoration_NoMatch) {
   auto* p = parser("not-a-stage");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
+  EXPECT_FALSE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_EQ(deco.value, nullptr);
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc b/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
index 36cc93d..df92f11 100644
--- a/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_decoration_decl_test.cc
@@ -25,40 +25,50 @@
 TEST_F(ParserImplTest, StructMemberDecorationDecl_EmptyStr) {
   auto* p = parser("");
   auto decos = p->decoration_list();
-  ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(decos.size(), 0u);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 0u);
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_EmptyBlock) {
   auto* p = parser("[[]]");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(decos.size(), 0u);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 0u);
   EXPECT_EQ(p->error(), "1:3: empty decoration list");
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_Single) {
   auto* p = parser("[[offset(4)]]");
   auto decos = p->decoration_list();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_EQ(decos.size(), 1u);
-  auto deco = ast::As<ast::StructMemberDecoration>(std::move(decos[0]));
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 1u);
+  auto deco = ast::As<ast::StructMemberDecoration>(std::move(decos.value[0]));
   ASSERT_NE(deco, nullptr);
   EXPECT_TRUE(deco->IsOffset());
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_InvalidDecoration) {
   auto* p = parser("[[offset(nan)]]");
-  p->decoration_list();
-  ASSERT_TRUE(p->has_error()) << p->error();
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error()) << p->error();
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
   EXPECT_EQ(p->error(),
             "1:10: expected signed integer literal for offset decoration");
 }
 
 TEST_F(ParserImplTest, StructMemberDecorationDecl_MissingClose) {
   auto* p = parser("[[offset(4)");
-  p->decoration_list();
-  ASSERT_TRUE(p->has_error()) << p->error();
+  auto decos = p->decoration_list();
+  EXPECT_TRUE(p->has_error()) << p->error();
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
   EXPECT_EQ(p->error(), "1:12: expected ']]' for decoration list");
 }
 
diff --git a/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc b/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc
index ec25733..8872c8e 100644
--- a/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_decoration_test.cc
@@ -25,10 +25,13 @@
 TEST_F(ParserImplTest, StructMemberDecoration_Offset) {
   auto* p = parser("offset(4)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr);
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
   ASSERT_FALSE(p->has_error());
 
-  auto member_deco = ast::As<ast::StructMemberDecoration>(std::move(deco));
+  auto member_deco =
+      ast::As<ast::StructMemberDecoration>(std::move(deco.value));
   ASSERT_NE(member_deco, nullptr);
   ASSERT_TRUE(member_deco->IsOffset());
 
@@ -39,24 +42,30 @@
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingLeftParen) {
   auto* p = parser("offset 4)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: expected '(' for offset decoration");
 }
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingRightParen) {
   auto* p = parser("offset(4");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected ')' for offset decoration");
 }
 
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingValue) {
   auto* p = parser("offset()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:8: expected signed integer literal for offset decoration");
 }
@@ -64,8 +73,10 @@
 TEST_F(ParserImplTest, StructMemberDecoration_Offset_MissingInvalid) {
   auto* p = parser("offset(nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:8: expected signed integer literal for offset decoration");
 }
diff --git a/src/reader/wgsl/parser_impl_struct_member_test.cc b/src/reader/wgsl/parser_impl_struct_member_test.cc
index 563ece6..b63ee50 100644
--- a/src/reader/wgsl/parser_impl_struct_member_test.cc
+++ b/src/reader/wgsl/parser_impl_struct_member_test.cc
@@ -29,8 +29,11 @@
 
   auto* p = parser("a : i32;");
   auto decos = p->decoration_list();
-  EXPECT_EQ(decos.size(), 0u);
-  auto m = p->expect_struct_member(decos);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 0u);
+
+  auto m = p->expect_struct_member(decos.value);
   ASSERT_FALSE(p->has_error());
   ASSERT_FALSE(m.errored);
   ASSERT_NE(m.value, nullptr);
@@ -50,8 +53,11 @@
 
   auto* p = parser("[[offset(2)]] a : i32;");
   auto decos = p->decoration_list();
-  EXPECT_EQ(decos.size(), 1u);
-  auto m = p->expect_struct_member(decos);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 1u);
+
+  auto m = p->expect_struct_member(decos.value);
   ASSERT_FALSE(p->has_error());
   ASSERT_FALSE(m.errored);
   ASSERT_NE(m.value, nullptr);
@@ -74,8 +80,11 @@
   auto* p = parser(R"([[offset(2)]]
 [[offset(4)]] a : i32;)");
   auto decos = p->decoration_list();
-  EXPECT_EQ(decos.size(), 2u);
-  auto m = p->expect_struct_member(decos);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+  EXPECT_EQ(decos.value.size(), 2u);
+
+  auto m = p->expect_struct_member(decos.value);
   ASSERT_FALSE(p->has_error());
   ASSERT_FALSE(m.errored);
   ASSERT_NE(m.value, nullptr);
@@ -97,7 +106,10 @@
 TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
   auto* p = parser("[[offset(nan)]] a : i32;");
   auto decos = p->decoration_list();
-  auto m = p->expect_struct_member(decos);
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+
+  auto m = p->expect_struct_member(decos.value);
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(m.errored);
   ASSERT_EQ(m.value, nullptr);
@@ -108,7 +120,10 @@
 TEST_F(ParserImplTest, StructMember_InvalidVariable) {
   auto* p = parser("[[offset(4)]] a : B;");
   auto decos = p->decoration_list();
-  auto m = p->expect_struct_member(decos);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_TRUE(decos.matched);
+
+  auto m = p->expect_struct_member(decos.value);
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(m.errored);
   ASSERT_EQ(m.value, nullptr);
@@ -118,7 +133,10 @@
 TEST_F(ParserImplTest, StructMember_MissingSemicolon) {
   auto* p = parser("a : i32");
   auto decos = p->decoration_list();
-  auto m = p->expect_struct_member(decos);
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+
+  auto m = p->expect_struct_member(decos.value);
   ASSERT_TRUE(p->has_error());
   ASSERT_TRUE(m.errored);
   ASSERT_EQ(m.value, nullptr);
diff --git a/src/reader/wgsl/parser_impl_switch_body_test.cc b/src/reader/wgsl/parser_impl_switch_body_test.cc
index 6b4078c..69af79f 100644
--- a/src/reader/wgsl/parser_impl_switch_body_test.cc
+++ b/src/reader/wgsl/parser_impl_switch_body_test.cc
@@ -25,110 +25,136 @@
 TEST_F(ParserImplTest, SwitchBody_Case) {
   auto* p = parser("case 1: { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsCase());
-  EXPECT_FALSE(e->IsDefault());
-  ASSERT_EQ(e->body()->size(), 1u);
-  EXPECT_TRUE(e->body()->get(0)->IsAssign());
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsCase());
+  EXPECT_FALSE(e.value->IsDefault());
+  ASSERT_EQ(e.value->body()->size(), 1u);
+  EXPECT_TRUE(e.value->body()->get(0)->IsAssign());
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_InvalidConstLiteral) {
   auto* p = parser("case a == 4: { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: unable to parse case selectors");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_InvalidSelector_bool) {
   auto* p = parser("case true: { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: invalid case selector must be an integer value");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MissingConstLiteral) {
   auto* p = parser("case: { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:5: unable to parse case selectors");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MissingColon) {
   auto* p = parser("case 1 { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected ':' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketLeft) {
   auto* p = parser("case 1: a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:9: expected '{' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_MissingBracketRight) {
   auto* p = parser("case 1: { a = 4; ");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:18: expected '}' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Case_InvalidCaseBody) {
   auto* p = parser("case 1: { fn main() -> void {} }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:11: expected '}' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default) {
   auto* p = parser("default: { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsCase());
-  EXPECT_TRUE(e->IsDefault());
-  ASSERT_EQ(e->body()->size(), 1u);
-  EXPECT_TRUE(e->body()->get(0)->IsAssign());
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsCase());
+  EXPECT_TRUE(e.value->IsDefault());
+  ASSERT_EQ(e.value->body()->size(), 1u);
+  EXPECT_TRUE(e.value->body()->get(0)->IsAssign());
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_MissingColon) {
   auto* p = parser("default { a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:9: expected ':' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketLeft) {
   auto* p = parser("default: a = 4; }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:10: expected '{' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_MissingBracketRight) {
   auto* p = parser("default: { a = 4; ");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:19: expected '}' for case statement");
 }
 
 TEST_F(ParserImplTest, SwitchBody_Default_InvalidCaseBody) {
   auto* p = parser("default: { fn main() -> void {} }");
   auto e = p->switch_body();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(e.errored);
+  EXPECT_FALSE(e.matched);
+  EXPECT_EQ(e.value, nullptr);
   EXPECT_EQ(p->error(), "1:12: expected '}' for case statement");
 }
 
diff --git a/src/reader/wgsl/parser_impl_switch_stmt_test.cc b/src/reader/wgsl/parser_impl_switch_stmt_test.cc
index 15b6a5c..d0ea352 100644
--- a/src/reader/wgsl/parser_impl_switch_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_switch_stmt_test.cc
@@ -29,21 +29,25 @@
   case 2: {}
 })");
   auto e = p->switch_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsSwitch());
-  ASSERT_EQ(e->body().size(), 2u);
-  EXPECT_FALSE(e->body()[0]->IsDefault());
-  EXPECT_FALSE(e->body()[1]->IsDefault());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsSwitch());
+  ASSERT_EQ(e.value->body().size(), 2u);
+  EXPECT_FALSE(e.value->body()[0]->IsDefault());
+  EXPECT_FALSE(e.value->body()[1]->IsDefault());
 }
 
 TEST_F(ParserImplTest, SwitchStmt_Empty) {
   auto* p = parser("switch(a) { }");
   auto e = p->switch_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsSwitch());
-  ASSERT_EQ(e->body().size(), 0u);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsSwitch());
+  ASSERT_EQ(e.value->body().size(), 0u);
 }
 
 TEST_F(ParserImplTest, SwitchStmt_DefaultInMiddle) {
@@ -53,45 +57,55 @@
   case 2: {}
 })");
   auto e = p->switch_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsSwitch());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsSwitch());
 
-  ASSERT_EQ(e->body().size(), 3u);
-  ASSERT_FALSE(e->body()[0]->IsDefault());
-  ASSERT_TRUE(e->body()[1]->IsDefault());
-  ASSERT_FALSE(e->body()[2]->IsDefault());
+  ASSERT_EQ(e.value->body().size(), 3u);
+  ASSERT_FALSE(e.value->body()[0]->IsDefault());
+  ASSERT_TRUE(e.value->body()[1]->IsDefault());
+  ASSERT_FALSE(e.value->body()[2]->IsDefault());
 }
 
 TEST_F(ParserImplTest, SwitchStmt_InvalidExpression) {
   auto* p = parser("switch(a=b) {}");
   auto e = p->switch_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected ')'");
 }
 
 TEST_F(ParserImplTest, SwitchStmt_MissingExpression) {
   auto* p = parser("switch {}");
   auto e = p->switch_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:8: expected '('");
 }
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketLeft) {
   auto* p = parser("switch(a) }");
   auto e = p->switch_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:11: expected '{' for switch statement");
 }
 
 TEST_F(ParserImplTest, SwitchStmt_MissingBracketRight) {
   auto* p = parser("switch(a) {");
   auto e = p->switch_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:12: expected '}' for switch statement");
 }
 
@@ -100,8 +114,10 @@
   case: {}
 })");
   auto e = p->switch_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "2:7: unable to parse case selectors");
 }
 
diff --git a/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc b/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
index 0e82af0..b040fad 100644
--- a/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
+++ b/src/reader/wgsl/parser_impl_texture_sampler_types_test.cc
@@ -26,325 +26,395 @@
 
 TEST_F(ParserImplTest, TextureSamplerTypes_Invalid) {
   auto* p = parser("1234");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_FALSE(t.errored);
   EXPECT_FALSE(p->has_error());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_Sampler) {
   auto* p = parser("sampler");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsSampler());
-  ASSERT_FALSE(t->AsSampler()->IsComparison());
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsSampler());
+  ASSERT_FALSE(t.value->AsSampler()->IsComparison());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SamplerComparison) {
   auto* p = parser("sampler_comparison");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsSampler());
-  ASSERT_TRUE(t->AsSampler()->IsComparison());
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsSampler());
+  ASSERT_TRUE(t.value->AsSampler()->IsComparison());
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_DepthTexture) {
   auto* p = parser("texture_depth_2d");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsDepth());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsDepth());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_F32_Old) {
   auto* p = parser("texture_sampled_1d<f32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsF32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k1d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsF32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k1d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_I32_Old) {
   auto* p = parser("texture_sampled_2d<i32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsI32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsI32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32_Old) {
   auto* p = parser("texture_sampled_3d<u32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsU32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k3d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsU32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k3d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_Invalid_Old) {
   auto* p = parser("texture_sampled_1d<abc>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:20: unknown constructed type 'abc'");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType_Old) {
   auto* p = parser("texture_sampled_1d<>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:20: invalid subtype for sampled texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingLessThan_Old) {
   auto* p = parser("texture_sampled_1d");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:19: expected '<' for sampled texture type");
 }
 
 TEST_F(ParserImplTest,
        TextureSamplerTypes_SampledTexture_MissingGreaterThan_Old) {
   auto* p = parser("texture_sampled_1d<u32");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:23: expected '>' for sampled texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_F32) {
   auto* p = parser("texture_1d<f32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsF32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k1d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsF32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k1d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_I32) {
   auto* p = parser("texture_2d<i32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsI32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsI32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32) {
   auto* p = parser("texture_3d<u32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsU32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k3d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsU32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k3d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_Invalid) {
   auto* p = parser("texture_1d<abc>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:12: unknown constructed type 'abc'");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType) {
   auto* p = parser("texture_1d<>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:12: invalid subtype for sampled texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingLessThan) {
   auto* p = parser("texture_1d");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:11: expected '<' for sampled texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingGreaterThan) {
   auto* p = parser("texture_1d<u32");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:15: expected '>' for sampled texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_I32) {
   auto* p = parser("texture_multisampled_2d<i32>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsMultisampled());
-  ASSERT_TRUE(t->AsTexture()->AsMultisampled()->type()->IsI32());
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsMultisampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsMultisampled()->type()->IsI32());
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_Invalid) {
   auto* p = parser("texture_multisampled_2d<abc>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:25: unknown constructed type 'abc'");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_MissingType) {
   auto* p = parser("texture_multisampled_2d<>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_TRUE(p->has_error());
-  EXPECT_EQ(t, nullptr);
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:25: invalid subtype for multisampled texture type");
 }
 
 TEST_F(ParserImplTest,
        TextureSamplerTypes_MultisampledTexture_MissingLessThan) {
   auto* p = parser("texture_multisampled_2d");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:24: expected '<' for multisampled texture type");
 }
 
 TEST_F(ParserImplTest,
        TextureSamplerTypes_MultisampledTexture_MissingGreaterThan) {
   auto* p = parser("texture_multisampled_2d<u32");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:28: expected '>' for multisampled texture type");
 }
 
 TEST_F(ParserImplTest,
        TextureSamplerTypes_StorageTexture_Readonly1dR8Unorm_Old) {
   auto* p = parser("texture_ro_1d<r8unorm>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsStorage());
-  EXPECT_EQ(t->AsTexture()->AsStorage()->image_format(),
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsStorage());
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->image_format(),
             ast::type::ImageFormat::kR8Unorm);
-  EXPECT_EQ(t->AsTexture()->AsStorage()->access(),
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->access(),
             ast::AccessControl::kReadOnly);
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k1d);
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k1d);
 }
 
 TEST_F(ParserImplTest,
        TextureSamplerTypes_StorageTexture_Writeonly2dR16Float_Old) {
   auto* p = parser("texture_wo_2d<r16float>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsStorage());
-  EXPECT_EQ(t->AsTexture()->AsStorage()->image_format(),
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsStorage());
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->image_format(),
             ast::type::ImageFormat::kR16Float);
-  EXPECT_EQ(t->AsTexture()->AsStorage()->access(),
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->access(),
             ast::AccessControl::kWriteOnly);
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidType_Old) {
   auto* p = parser("texture_ro_1d<abc>");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:15: invalid format for storage texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingType_Old) {
   auto* p = parser("texture_wo_1d<>");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:15: invalid format for storage texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingLessThan_Old) {
   auto* p = parser("texture_ro_1d");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:14: expected '<' for storage texture type");
 }
 
 TEST_F(ParserImplTest,
        TextureSamplerTypes_StorageTexture_MissingGreaterThan_Old) {
   auto* p = parser("texture_wo_1d<r8unorm");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:22: expected '>' for storage texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Readonly1dR8Unorm) {
   auto* p = parser("texture_storage_ro_1d<r8unorm>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsStorage());
-  EXPECT_EQ(t->AsTexture()->AsStorage()->image_format(),
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsStorage());
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->image_format(),
             ast::type::ImageFormat::kR8Unorm);
-  EXPECT_EQ(t->AsTexture()->AsStorage()->access(),
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->access(),
             ast::AccessControl::kReadOnly);
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k1d);
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k1d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_Writeonly2dR16Float) {
   auto* p = parser("texture_storage_wo_2d<r16float>");
-  auto* t = p->texture_sampler_types();
+  auto t = p->texture_sampler_types();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsStorage());
-  EXPECT_EQ(t->AsTexture()->AsStorage()->image_format(),
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsStorage());
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->image_format(),
             ast::type::ImageFormat::kR16Float);
-  EXPECT_EQ(t->AsTexture()->AsStorage()->access(),
+  EXPECT_EQ(t.value->AsTexture()->AsStorage()->access(),
             ast::AccessControl::kWriteOnly);
-  EXPECT_EQ(t->AsTexture()->dim(), ast::type::TextureDimension::k2d);
+  EXPECT_EQ(t.value->AsTexture()->dim(), ast::type::TextureDimension::k2d);
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_InvalidType) {
   auto* p = parser("texture_storage_ro_1d<abc>");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:23: invalid format for storage texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingType) {
   auto* p = parser("texture_storage_ro_1d<>");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:23: invalid format for storage texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingLessThan) {
   auto* p = parser("texture_storage_ro_1d");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:22: expected '<' for storage texture type");
 }
 
 TEST_F(ParserImplTest, TextureSamplerTypes_StorageTexture_MissingGreaterThan) {
   auto* p = parser("texture_storage_ro_1d<r8unorm");
-  auto* t = p->texture_sampler_types();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->texture_sampler_types();
+  EXPECT_EQ(t.value, nullptr);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(t.errored);
   EXPECT_EQ(p->error(), "1:30: expected '>' for storage texture type");
 }
 
diff --git a/src/reader/wgsl/parser_impl_type_alias_test.cc b/src/reader/wgsl/parser_impl_type_alias_test.cc
index c580729..df97a4c 100644
--- a/src/reader/wgsl/parser_impl_type_alias_test.cc
+++ b/src/reader/wgsl/parser_impl_type_alias_test.cc
@@ -30,11 +30,13 @@
   auto* i32 = tm()->Get(std::make_unique<ast::type::I32Type>());
 
   auto* p = parser("type a = i32");
-  auto* t = p->type_alias();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsAlias());
-  auto* alias = t->AsAlias();
+  auto t = p->type_alias();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(t.errored);
+  EXPECT_TRUE(t.matched);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsAlias());
+  auto* alias = t.value->AsAlias();
   ASSERT_TRUE(alias->type()->IsI32());
   ASSERT_EQ(alias->type(), i32);
 }
@@ -45,11 +47,13 @@
   auto* p = parser("type a = B");
   p->register_constructed("B", &str);
 
-  auto* t = p->type_alias();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(t, nullptr);
-  ASSERT_TRUE(t->IsAlias());
-  auto* alias = t->AsAlias();
+  auto t = p->type_alias();
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(t.errored);
+  EXPECT_TRUE(t.matched);
+  ASSERT_NE(t.value, nullptr);
+  ASSERT_TRUE(t.value->IsAlias());
+  auto* alias = t.value->AsAlias();
   EXPECT_EQ(alias->name(), "a");
   ASSERT_TRUE(alias->type()->IsStruct());
 
@@ -59,33 +63,41 @@
 
 TEST_F(ParserImplTest, TypeDecl_MissingIdent) {
   auto* p = parser("type = i32");
-  auto* t = p->type_alias();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: expected identifier for type alias");
 }
 
 TEST_F(ParserImplTest, TypeDecl_InvalidIdent) {
   auto* p = parser("type 123 = i32");
-  auto* t = p->type_alias();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
   EXPECT_EQ(p->error(), "1:6: expected identifier for type alias");
 }
 
 TEST_F(ParserImplTest, TypeDecl_MissingEqual) {
   auto* p = parser("type a i32");
-  auto* t = p->type_alias();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
   EXPECT_EQ(p->error(), "1:8: expected '=' for type alias");
 }
 
 TEST_F(ParserImplTest, TypeDecl_InvalidType) {
   auto* p = parser("type a = B");
-  auto* t = p->type_alias();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_alias();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(t.value, nullptr);
   EXPECT_EQ(p->error(), "1:10: unknown constructed type 'B'");
 }
 
diff --git a/src/reader/wgsl/parser_impl_type_decl_test.cc b/src/reader/wgsl/parser_impl_type_decl_test.cc
index 20e6b2f..f0cc10d 100644
--- a/src/reader/wgsl/parser_impl_type_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_type_decl_test.cc
@@ -37,8 +37,10 @@
 
 TEST_F(ParserImplTest, TypeDecl_Invalid) {
   auto* p = parser("1234");
-  auto* t = p->type_decl();
-  EXPECT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_EQ(t.errored, false);
+  EXPECT_EQ(t.matched, false);
+  EXPECT_EQ(t.value, nullptr);
   EXPECT_FALSE(p->has_error());
 }
 
@@ -52,12 +54,14 @@
 
   p->register_constructed("A", alias_type);
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, alias_type);
-  ASSERT_TRUE(t->IsAlias());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, alias_type);
+  ASSERT_TRUE(t.value->IsAlias());
 
-  auto* alias = t->AsAlias();
+  auto* alias = t.value->AsAlias();
   EXPECT_EQ(alias->name(), "A");
   EXPECT_EQ(alias->type(), int_type);
 }
@@ -65,8 +69,10 @@
 TEST_F(ParserImplTest, TypeDecl_Identifier_NotFound) {
   auto* p = parser("B");
 
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:1: unknown constructed type 'B'");
 }
@@ -76,10 +82,12 @@
 
   auto* bool_type = tm()->Get(std::make_unique<ast::type::BoolType>());
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, bool_type);
-  ASSERT_TRUE(t->IsBool());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, bool_type);
+  ASSERT_TRUE(t.value->IsBool());
 }
 
 TEST_F(ParserImplTest, TypeDecl_F32) {
@@ -87,10 +95,12 @@
 
   auto* float_type = tm()->Get(std::make_unique<ast::type::F32Type>());
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, float_type);
-  ASSERT_TRUE(t->IsF32());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, float_type);
+  ASSERT_TRUE(t.value->IsF32());
 }
 
 TEST_F(ParserImplTest, TypeDecl_I32) {
@@ -98,10 +108,12 @@
 
   auto* int_type = tm()->Get(std::make_unique<ast::type::I32Type>());
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, int_type);
-  ASSERT_TRUE(t->IsI32());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, int_type);
+  ASSERT_TRUE(t.value->IsI32());
 }
 
 TEST_F(ParserImplTest, TypeDecl_U32) {
@@ -109,10 +121,12 @@
 
   auto* uint_type = tm()->Get(std::make_unique<ast::type::U32Type>());
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, uint_type);
-  ASSERT_TRUE(t->IsU32());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, uint_type);
+  ASSERT_TRUE(t.value->IsU32());
 }
 
 struct VecData {
@@ -129,11 +143,13 @@
 TEST_P(VecTest, Parse) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  EXPECT_TRUE(t->IsVector());
-  EXPECT_EQ(t->AsVector()->size(), params.count);
+  EXPECT_TRUE(t.value->IsVector());
+  EXPECT_EQ(t.value->AsVector()->size(), params.count);
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
                          VecTest,
@@ -146,8 +162,10 @@
 TEST_P(VecMissingGreaterThanTest, Handles_Missing_GreaterThan) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:9: expected '>' for vector");
 }
@@ -162,8 +180,10 @@
 TEST_P(VecMissingLessThanTest, Handles_Missing_GreaterThan) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:5: expected '<' for vector");
 }
@@ -178,8 +198,10 @@
 TEST_P(VecBadType, Handles_Unknown_Type) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:6: unknown constructed type 'unknown'");
 }
@@ -194,8 +216,10 @@
 TEST_P(VecMissingType, Handles_Missing_Type) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:6: unable to determine subtype for vector");
 }
@@ -207,24 +231,28 @@
 
 TEST_F(ParserImplTest, TypeDecl_Ptr) {
   auto* p = parser("ptr<function, f32>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsPointer());
+  ASSERT_TRUE(t.value->IsPointer());
 
-  auto* ptr = t->AsPointer();
+  auto* ptr = t.value->AsPointer();
   ASSERT_TRUE(ptr->type()->IsF32());
   ASSERT_EQ(ptr->storage_class(), ast::StorageClass::kFunction);
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_ToVec) {
   auto* p = parser("ptr<function, vec2<f32>>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsPointer());
+  ASSERT_TRUE(t.value->IsPointer());
 
-  auto* ptr = t->AsPointer();
+  auto* ptr = t.value->AsPointer();
   ASSERT_TRUE(ptr->type()->IsVector());
   ASSERT_EQ(ptr->storage_class(), ast::StorageClass::kFunction);
 
@@ -235,76 +263,94 @@
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingLessThan) {
   auto* p = parser("ptr private, f32>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:5: expected '<' for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingGreaterThan) {
   auto* p = parser("ptr<function, f32");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:18: expected '>' for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingComma) {
   auto* p = parser("ptr<function f32>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:14: expected ',' for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingStorageClass) {
   auto* p = parser("ptr<, f32>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingParams) {
   auto* p = parser("ptr<>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_MissingType) {
   auto* p = parser("ptr<function,>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:14: missing type for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_BadStorageClass) {
   auto* p = parser("ptr<unknown, f32>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:5: invalid storage class for ptr declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Ptr_BadType) {
   auto* p = parser("ptr<function, unknown>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:15: unknown constructed type 'unknown'");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array) {
   auto* p = parser("array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsArray());
+  ASSERT_TRUE(t.value->IsArray());
 
-  auto* a = t->AsArray();
+  auto* a = t.value->AsArray();
   ASSERT_FALSE(a->IsRuntimeArray());
   ASSERT_EQ(a->size(), 5u);
   ASSERT_TRUE(a->type()->IsF32());
@@ -313,12 +359,14 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride) {
   auto* p = parser("[[stride(16)]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsArray());
+  ASSERT_TRUE(t.value->IsArray());
 
-  auto* a = t->AsArray();
+  auto* a = t.value->AsArray();
   ASSERT_FALSE(a->IsRuntimeArray());
   ASSERT_EQ(a->size(), 5u);
   ASSERT_TRUE(a->type()->IsF32());
@@ -328,12 +376,14 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Stride) {
   auto* p = parser("[[stride(16)]] array<f32>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsArray());
+  ASSERT_TRUE(t.value->IsArray());
 
-  auto* a = t->AsArray();
+  auto* a = t.value->AsArray();
   ASSERT_TRUE(a->IsRuntimeArray());
   ASSERT_TRUE(a->type()->IsF32());
   ASSERT_TRUE(a->has_array_stride());
@@ -342,12 +392,14 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_MultipleDecorations_OneBlock) {
   auto* p = parser("[[stride(16), stride(32)]] array<f32>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsArray());
+  ASSERT_TRUE(t.value->IsArray());
 
-  auto* a = t->AsArray();
+  auto* a = t.value->AsArray();
   ASSERT_TRUE(a->IsRuntimeArray());
   ASSERT_TRUE(a->type()->IsF32());
 
@@ -361,12 +413,14 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_MultipleDecorations_MultipleBlocks) {
   auto* p = parser("[[stride(16)]] [[stride(32)]] array<f32>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsArray());
+  ASSERT_TRUE(t.value->IsArray());
 
-  auto* a = t->AsArray();
+  auto* a = t.value->AsArray();
   ASSERT_TRUE(a->IsRuntimeArray());
   ASSERT_TRUE(a->type()->IsF32());
 
@@ -380,48 +434,60 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_Decoration_MissingArray) {
   auto* p = parser("[[stride(16)]] f32");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: unexpected decorations");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Decoration_MissingClosingAttr) {
   auto* p = parser("[[stride(16) array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:14: expected ']]' for decoration list");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Decoration_UnknownDecoration) {
   auto* p = parser("[[unknown 16]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:3: expected decoration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingLeftParen) {
   auto* p = parser("[[stride 4)]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected '(' for stride decoration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingRightParen) {
   auto* p = parser("[[stride(4]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:11: expected ')' for stride decoration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride_MissingValue) {
   auto* p = parser("[[stride()]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:10: expected signed integer literal for stride decoration");
@@ -429,8 +495,10 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue) {
   auto* p = parser("[[stride(invalid)]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:10: expected signed integer literal for stride decoration");
@@ -438,76 +506,94 @@
 
 TEST_F(ParserImplTest, TypeDecl_Array_Stride_InvalidValue_Negative) {
   auto* p = parser("[[stride(-1)]] array<f32, 5>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: stride decoration must be greater than 0");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_Runtime) {
   auto* p = parser("array<u32>");
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  ASSERT_TRUE(t->IsArray());
+  ASSERT_TRUE(t.value->IsArray());
 
-  auto* a = t->AsArray();
+  auto* a = t.value->AsArray();
   ASSERT_TRUE(a->IsRuntimeArray());
   ASSERT_TRUE(a->type()->IsU32());
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_BadType) {
   auto* p = parser("array<unknown, 3>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:7: unknown constructed type 'unknown'");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_ZeroSize) {
   auto* p = parser("array<f32, 0>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:12: array size must be greater than 0");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_NegativeSize) {
   auto* p = parser("array<f32, -1>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:12: array size must be greater than 0");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
   auto* p = parser("array<f32, invalid>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:12: expected signed integer literal for array size");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_MissingLessThan) {
   auto* p = parser("array f32>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:7: expected '<' for array declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_MissingGreaterThan) {
   auto* p = parser("array<f32");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:10: expected '>' for array declaration");
 }
 
 TEST_F(ParserImplTest, TypeDecl_Array_MissingComma) {
   auto* p = parser("array<f32 3>");
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:11: expected '>' for array declaration");
 }
@@ -527,11 +613,13 @@
 TEST_P(MatrixTest, Parse) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
   ASSERT_FALSE(p->has_error());
-  EXPECT_TRUE(t->IsMatrix());
-  auto* mat = t->AsMatrix();
+  EXPECT_TRUE(t.value->IsMatrix());
+  auto* mat = t.value->AsMatrix();
   EXPECT_EQ(mat->rows(), params.rows);
   EXPECT_EQ(mat->columns(), params.columns);
 }
@@ -553,10 +641,12 @@
 TEST_P(MatrixMissingGreaterThanTest, Handles_Missing_GreaterThan) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:11: missing > for matrix");
+  ASSERT_EQ(p->error(), "1:11: expected '>' for matrix");
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
                          MatrixMissingGreaterThanTest,
@@ -575,10 +665,12 @@
 TEST_P(MatrixMissingLessThanTest, Handles_Missing_GreaterThan) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:8: missing < for matrix");
+  ASSERT_EQ(p->error(), "1:8: expected '<' for matrix");
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplTest,
                          MatrixMissingLessThanTest,
@@ -597,8 +689,10 @@
 TEST_P(MatrixBadType, Handles_Unknown_Type) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:8: unknown constructed type 'unknown'");
 }
@@ -619,8 +713,10 @@
 TEST_P(MatrixMissingType, Handles_Missing_Type) {
   auto params = GetParam();
   auto* p = parser(params.input);
-  auto* t = p->type_decl();
-  ASSERT_EQ(t, nullptr);
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.errored);
+  EXPECT_FALSE(t.matched);
+  ASSERT_EQ(t.value, nullptr);
   ASSERT_TRUE(p->has_error());
   ASSERT_EQ(p->error(), "1:8: unable to determine subtype for matrix");
 }
@@ -642,11 +738,13 @@
   auto* type = tm()->Get(std::make_unique<ast::type::SamplerType>(
       ast::type::SamplerKind::kSampler));
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, type);
-  ASSERT_TRUE(t->IsSampler());
-  ASSERT_FALSE(t->AsSampler()->IsComparison());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, type);
+  ASSERT_TRUE(t.value->IsSampler());
+  ASSERT_FALSE(t.value->AsSampler()->IsComparison());
 }
 
 TEST_F(ParserImplTest, TypeDecl_Texture_Old) {
@@ -656,12 +754,14 @@
   auto* type = tm()->Get(std::make_unique<ast::type::SampledTextureType>(
       ast::type::TextureDimension::kCube, &f32));
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr) << p->error();
-  EXPECT_EQ(t, type);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsF32());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr) << p->error();
+  EXPECT_EQ(t.value, type);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsF32());
 }
 
 TEST_F(ParserImplTest, TypeDecl_Texture) {
@@ -671,12 +771,14 @@
   auto* type = tm()->Get(std::make_unique<ast::type::SampledTextureType>(
       ast::type::TextureDimension::kCube, &f32));
 
-  auto* t = p->type_decl();
-  ASSERT_NE(t, nullptr);
-  EXPECT_EQ(t, type);
-  ASSERT_TRUE(t->IsTexture());
-  ASSERT_TRUE(t->AsTexture()->IsSampled());
-  ASSERT_TRUE(t->AsTexture()->AsSampled()->type()->IsF32());
+  auto t = p->type_decl();
+  EXPECT_TRUE(t.matched);
+  EXPECT_FALSE(t.errored);
+  ASSERT_NE(t.value, nullptr);
+  EXPECT_EQ(t.value, type);
+  ASSERT_TRUE(t.value->IsTexture());
+  ASSERT_TRUE(t.value->AsTexture()->IsSampled());
+  ASSERT_TRUE(t.value->AsTexture()->AsSampled()->type()->IsF32());
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_unary_expression_test.cc b/src/reader/wgsl/parser_impl_unary_expression_test.cc
index 0484162..d9c645c 100644
--- a/src/reader/wgsl/parser_impl_unary_expression_test.cc
+++ b/src/reader/wgsl/parser_impl_unary_expression_test.cc
@@ -29,11 +29,13 @@
 TEST_F(ParserImplTest, UnaryExpression_Postix) {
   auto* p = parser("a[2]");
   auto e = p->unary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
 
-  ASSERT_TRUE(e->IsArrayAccessor());
-  auto* ary = e->AsArrayAccessor();
+  ASSERT_TRUE(e.value->IsArrayAccessor());
+  auto* ary = e.value->AsArrayAccessor();
   ASSERT_TRUE(ary->array()->IsIdentifier());
   auto* ident = ary->array()->AsIdentifier();
   EXPECT_EQ(ident->name(), "a");
@@ -48,11 +50,13 @@
 TEST_F(ParserImplTest, UnaryExpression_Minus) {
   auto* p = parser("- 1");
   auto e = p->unary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsUnaryOp());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsUnaryOp());
 
-  auto* u = e->AsUnaryOp();
+  auto* u = e.value->AsUnaryOp();
   ASSERT_EQ(u->op(), ast::UnaryOp::kNegation);
 
   ASSERT_TRUE(u->expr()->IsConstructor());
@@ -66,19 +70,23 @@
 TEST_F(ParserImplTest, UnaryExpression_Minus_InvalidRHS) {
   auto* p = parser("-if(a) {}");
   auto e = p->unary_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:2: unable to parse right side of - expression");
 }
 
 TEST_F(ParserImplTest, UnaryExpression_Bang) {
   auto* p = parser("!1");
   auto e = p->unary_expression();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsUnaryOp());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsUnaryOp());
 
-  auto* u = e->AsUnaryOp();
+  auto* u = e.value->AsUnaryOp();
   ASSERT_EQ(u->op(), ast::UnaryOp::kNot);
 
   ASSERT_TRUE(u->expr()->IsConstructor());
@@ -92,8 +100,10 @@
 TEST_F(ParserImplTest, UnaryExpression_Bang_InvalidRHS) {
   auto* p = parser("!if (a) {}");
   auto e = p->unary_expression();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:2: unable to parse right side of ! expression");
 }
 
diff --git a/src/reader/wgsl/parser_impl_variable_decl_test.cc b/src/reader/wgsl/parser_impl_variable_decl_test.cc
index 35edb24..3ff51ac 100644
--- a/src/reader/wgsl/parser_impl_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decl_test.cc
@@ -25,23 +25,27 @@
 TEST_F(ParserImplTest, VariableDecl_Parses) {
   auto* p = parser("var my_var : f32");
   auto var = p->variable_decl();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(var, nullptr);
-  ASSERT_EQ(var->name(), "my_var");
-  ASSERT_NE(var->type(), nullptr);
-  ASSERT_TRUE(var->type()->IsF32());
+  EXPECT_FALSE(p->has_error());
+  EXPECT_TRUE(var.matched);
+  EXPECT_FALSE(var.errored);
+  ASSERT_NE(var.value, nullptr);
+  EXPECT_EQ(var.value->name(), "my_var");
+  EXPECT_NE(var.value->type(), nullptr);
+  EXPECT_TRUE(var.value->type()->IsF32());
 
-  ASSERT_EQ(var->source().range.begin.line, 1u);
-  ASSERT_EQ(var->source().range.begin.column, 5u);
-  ASSERT_EQ(var->source().range.end.line, 1u);
-  ASSERT_EQ(var->source().range.end.column, 11u);
+  EXPECT_EQ(var.value->source().range.begin.line, 1u);
+  EXPECT_EQ(var.value->source().range.begin.column, 5u);
+  EXPECT_EQ(var.value->source().range.end.line, 1u);
+  EXPECT_EQ(var.value->source().range.end.column, 11u);
 }
 
 TEST_F(ParserImplTest, VariableDecl_MissingVar) {
   auto* p = parser("my_var : f32");
   auto v = p->variable_decl();
-  ASSERT_EQ(v, nullptr);
-  ASSERT_FALSE(p->has_error());
+  EXPECT_EQ(v.value, nullptr);
+  EXPECT_FALSE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_FALSE(p->has_error());
 
   auto t = p->next();
   ASSERT_TRUE(t.IsIdentifier());
@@ -50,31 +54,37 @@
 TEST_F(ParserImplTest, VariableDecl_InvalidIdentDecl) {
   auto* p = parser("var my_var f32");
   auto v = p->variable_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(v, nullptr);
-  ASSERT_EQ(p->error(), "1:12: expected ':' for variable declaration");
+  EXPECT_FALSE(v.matched);
+  EXPECT_TRUE(v.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(v.value, nullptr);
+  EXPECT_EQ(p->error(), "1:12: expected ':' for variable declaration");
 }
 
 TEST_F(ParserImplTest, VariableDecl_WithStorageClass) {
   auto* p = parser("var<private> my_var : f32");
   auto v = p->variable_decl();
-  ASSERT_FALSE(p->has_error());
-  ASSERT_NE(v, nullptr);
-  EXPECT_EQ(v->name(), "my_var");
-  EXPECT_TRUE(v->type()->IsF32());
-  EXPECT_EQ(v->storage_class(), ast::StorageClass::kPrivate);
+  EXPECT_TRUE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_FALSE(p->has_error());
+  ASSERT_NE(v.value, nullptr);
+  EXPECT_EQ(v.value->name(), "my_var");
+  EXPECT_TRUE(v.value->type()->IsF32());
+  EXPECT_EQ(v.value->storage_class(), ast::StorageClass::kPrivate);
 
-  EXPECT_EQ(v->source().range.begin.line, 1u);
-  EXPECT_EQ(v->source().range.begin.column, 14u);
-  EXPECT_EQ(v->source().range.end.line, 1u);
-  EXPECT_EQ(v->source().range.end.column, 20u);
+  EXPECT_EQ(v.value->source().range.begin.line, 1u);
+  EXPECT_EQ(v.value->source().range.begin.column, 14u);
+  EXPECT_EQ(v.value->source().range.end.line, 1u);
+  EXPECT_EQ(v.value->source().range.end.column, 20u);
 }
 
 TEST_F(ParserImplTest, VariableDecl_InvalidStorageClass) {
   auto* p = parser("var<unknown> my_var : f32");
   auto v = p->variable_decl();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(v, nullptr);
+  EXPECT_FALSE(v.matched);
+  EXPECT_TRUE(v.errored);
+  EXPECT_TRUE(p->has_error());
+  EXPECT_EQ(v.value, nullptr);
   EXPECT_EQ(p->error(), "1:5: invalid storage class for variable decoration");
 }
 
diff --git a/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc b/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc
index dcbac85..a9271b9 100644
--- a/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decoration_list_test.cc
@@ -27,10 +27,12 @@
   auto* p = parser(R"([[location(4), builtin(position)]])");
   auto decos = p->decoration_list();
   ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_EQ(decos.size(), 2u);
+  ASSERT_FALSE(decos.errored);
+  ASSERT_TRUE(decos.matched);
+  ASSERT_EQ(decos.value.size(), 2u);
 
-  auto deco_0 = ast::As<ast::VariableDecoration>(std::move(decos[0]));
-  auto deco_1 = ast::As<ast::VariableDecoration>(std::move(decos[1]));
+  auto deco_0 = ast::As<ast::VariableDecoration>(std::move(decos.value[0]));
+  auto deco_1 = ast::As<ast::VariableDecoration>(std::move(decos.value[1]));
   ASSERT_NE(deco_0, nullptr);
   ASSERT_NE(deco_1, nullptr);
 
@@ -43,44 +45,62 @@
 TEST_F(ParserImplTest, VariableDecorationList_Empty) {
   auto* p = parser(R"([[]])");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:3: empty decoration list");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(), "1:3: empty decoration list");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_Invalid) {
   auto* p = parser(R"([[invalid]])");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_TRUE(decos.empty());
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(), "1:3: expected decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_ExtraComma) {
   auto* p = parser(R"([[builtin(position), ]])");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:22: expected decoration");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(), "1:22: expected decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_MissingComma) {
   auto* p = parser(R"([[binding(4) location(5)]])");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:14: expected ',' for decoration list");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(), "1:14: expected ',' for decoration list");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_BadDecoration) {
   auto* p = parser(R"([[location(bad)]])");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(),
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(),
             "1:12: expected signed integer literal for location decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecorationList_InvalidBuiltin) {
   auto* p = parser("[[builtin(invalid)]]");
   auto decos = p->decoration_list();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:11: invalid value for builtin decoration");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  EXPECT_TRUE(decos.value.empty());
+  EXPECT_EQ(p->error(), "1:11: invalid value for builtin decoration");
 }
 
 }  // namespace
diff --git a/src/reader/wgsl/parser_impl_variable_decoration_test.cc b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
index 5ada60c..f99ae14 100644
--- a/src/reader/wgsl/parser_impl_variable_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decoration_test.cc
@@ -28,8 +28,10 @@
 TEST_F(ParserImplTest, VariableDecoration_Location) {
   auto* p = parser("location(4)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr);
-  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco.value));
   ASSERT_NE(var_deco, nullptr);
   ASSERT_FALSE(p->has_error());
   ASSERT_TRUE(var_deco->IsLocation());
@@ -41,24 +43,30 @@
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingLeftParen) {
   auto* p = parser("location 4)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected '(' for location decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingRightParen) {
   auto* p = parser("location(4");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:11: expected ')' for location decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingValue) {
   auto* p = parser("location()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:10: expected signed integer literal for location decoration");
 }
@@ -66,8 +74,10 @@
 TEST_F(ParserImplTest, VariableDecoration_Location_MissingInvalid) {
   auto* p = parser("location(nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:10: expected signed integer literal for location decoration");
 }
@@ -88,8 +98,10 @@
   auto* p = parser(std::string("builtin(") + params.input + ")");
 
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr);
-  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco.value));
   ASSERT_FALSE(p->has_error()) << p->error();
   ASSERT_NE(var_deco, nullptr);
   ASSERT_TRUE(var_deco->IsBuiltin());
@@ -115,48 +127,60 @@
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingLeftParen) {
   auto* p = parser("builtin position)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected '(' for builtin decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingRightParen) {
   auto* p = parser("builtin(position");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:17: expected ')' for builtin decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingValue) {
   auto* p = parser("builtin()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_InvalidValue) {
   auto* p = parser("builtin(other_thingy)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: invalid value for builtin decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Builtin_MissingInvalid) {
   auto* p = parser("builtin(3)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected identifier for builtin");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Binding) {
   auto* p = parser("binding(4)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr);
-  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco.value));
   ASSERT_NE(var_deco, nullptr);
   ASSERT_FALSE(p->has_error());
   ASSERT_TRUE(var_deco->IsBinding());
@@ -168,24 +192,30 @@
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingLeftParen) {
   auto* p = parser("binding 4)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: expected '(' for binding decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingRightParen) {
   auto* p = parser("binding(4");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:10: expected ')' for binding decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingValue) {
   auto* p = parser("binding()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:9: expected signed integer literal for binding decoration");
 }
@@ -193,8 +223,10 @@
 TEST_F(ParserImplTest, VariableDecoration_Binding_MissingInvalid) {
   auto* p = parser("binding(nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:9: expected signed integer literal for binding decoration");
 }
@@ -202,8 +234,10 @@
 TEST_F(ParserImplTest, VariableDecoration_set) {
   auto* p = parser("set(4)");
   auto deco = p->decoration();
-  ASSERT_NE(deco, nullptr);
-  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco));
+  EXPECT_TRUE(deco.matched);
+  EXPECT_FALSE(deco.errored);
+  ASSERT_NE(deco.value, nullptr);
+  auto var_deco = ast::As<ast::VariableDecoration>(std::move(deco.value));
   ASSERT_FALSE(p->has_error());
   ASSERT_NE(var_deco.get(), nullptr);
   ASSERT_TRUE(var_deco->IsSet());
@@ -215,24 +249,30 @@
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingLeftParen) {
   auto* p = parser("set 2)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:5: expected '(' for set decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingRightParen) {
   auto* p = parser("set(2");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:6: expected ')' for set decoration");
 }
 
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingValue) {
   auto* p = parser("set()");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:5: expected signed integer literal for set decoration");
 }
@@ -240,8 +280,10 @@
 TEST_F(ParserImplTest, VariableDecoration_Set_MissingInvalid) {
   auto* p = parser("set(nan)");
   auto deco = p->decoration();
-  ASSERT_EQ(deco, nullptr);
-  ASSERT_TRUE(p->has_error());
+  EXPECT_FALSE(deco.matched);
+  EXPECT_TRUE(deco.errored);
+  EXPECT_EQ(deco.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(),
             "1:5: expected signed integer literal for set decoration");
 }
diff --git a/src/reader/wgsl/parser_impl_variable_stmt_test.cc b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
index 8a984c9..a0fba95 100644
--- a/src/reader/wgsl/parser_impl_variable_stmt_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_stmt_test.cc
@@ -26,96 +26,114 @@
 TEST_F(ParserImplTest, VariableStmt_VariableDecl) {
   auto* p = parser("var a : i32;");
   auto e = p->variable_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsVariableDecl());
-  ASSERT_NE(e->variable(), nullptr);
-  EXPECT_EQ(e->variable()->name(), "a");
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsVariableDecl());
+  ASSERT_NE(e.value->variable(), nullptr);
+  EXPECT_EQ(e.value->variable()->name(), "a");
 
-  ASSERT_EQ(e->source().range.begin.line, 1u);
-  ASSERT_EQ(e->source().range.begin.column, 5u);
-  ASSERT_EQ(e->source().range.end.line, 1u);
-  ASSERT_EQ(e->source().range.end.column, 6u);
+  ASSERT_EQ(e.value->source().range.begin.line, 1u);
+  ASSERT_EQ(e.value->source().range.begin.column, 5u);
+  ASSERT_EQ(e.value->source().range.end.line, 1u);
+  ASSERT_EQ(e.value->source().range.end.column, 6u);
 
-  EXPECT_EQ(e->variable()->constructor(), nullptr);
+  EXPECT_EQ(e.value->variable()->constructor(), nullptr);
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_WithInit) {
   auto* p = parser("var a : i32 = 1;");
   auto e = p->variable_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsVariableDecl());
-  ASSERT_NE(e->variable(), nullptr);
-  EXPECT_EQ(e->variable()->name(), "a");
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsVariableDecl());
+  ASSERT_NE(e.value->variable(), nullptr);
+  EXPECT_EQ(e.value->variable()->name(), "a");
 
-  ASSERT_EQ(e->source().range.begin.line, 1u);
-  ASSERT_EQ(e->source().range.begin.column, 5u);
-  ASSERT_EQ(e->source().range.end.line, 1u);
-  ASSERT_EQ(e->source().range.end.column, 6u);
+  ASSERT_EQ(e.value->source().range.begin.line, 1u);
+  ASSERT_EQ(e.value->source().range.begin.column, 5u);
+  ASSERT_EQ(e.value->source().range.end.line, 1u);
+  ASSERT_EQ(e.value->source().range.end.column, 6u);
 
-  ASSERT_NE(e->variable()->constructor(), nullptr);
-  EXPECT_TRUE(e->variable()->constructor()->IsConstructor());
+  ASSERT_NE(e.value->variable()->constructor(), nullptr);
+  EXPECT_TRUE(e.value->variable()->constructor()->IsConstructor());
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_Invalid) {
   auto* p = parser("var a : invalid;");
   auto e = p->variable_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:9: unknown constructed type 'invalid'");
 }
 
 TEST_F(ParserImplTest, VariableStmt_VariableDecl_ConstructorInvalid) {
   auto* p = parser("var a : i32 = if(a) {}");
   auto e = p->variable_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:15: missing constructor for variable declaration");
 }
 
 TEST_F(ParserImplTest, VariableStmt_Const) {
   auto* p = parser("const a : i32 = 1");
   auto e = p->variable_stmt();
-  ASSERT_FALSE(p->has_error()) << p->error();
-  ASSERT_NE(e, nullptr);
-  ASSERT_TRUE(e->IsVariableDecl());
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  ASSERT_NE(e.value, nullptr);
+  ASSERT_TRUE(e.value->IsVariableDecl());
 
-  ASSERT_EQ(e->source().range.begin.line, 1u);
-  ASSERT_EQ(e->source().range.begin.column, 7u);
-  ASSERT_EQ(e->source().range.end.line, 1u);
-  ASSERT_EQ(e->source().range.end.column, 8u);
+  ASSERT_EQ(e.value->source().range.begin.line, 1u);
+  ASSERT_EQ(e.value->source().range.begin.column, 7u);
+  ASSERT_EQ(e.value->source().range.end.line, 1u);
+  ASSERT_EQ(e.value->source().range.end.column, 8u);
 }
 
 TEST_F(ParserImplTest, VariableStmt_Const_InvalidVarIdent) {
   auto* p = parser("const a : invalid = 1");
   auto e = p->variable_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:11: unknown constructed type 'invalid'");
 }
 
 TEST_F(ParserImplTest, VariableStmt_Const_MissingEqual) {
   auto* p = parser("const a : i32 1");
   auto e = p->variable_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:15: expected '=' for constant declaration");
 }
 
 TEST_F(ParserImplTest, VariableStmt_Const_MissingConstructor) {
   auto* p = parser("const a : i32 =");
   auto e = p->variable_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:16: missing constructor for const declaration");
 }
 
 TEST_F(ParserImplTest, VariableStmt_Const_InvalidConstructor) {
   auto* p = parser("const a : i32 = if (a) {}");
   auto e = p->variable_stmt();
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(e, nullptr);
+  EXPECT_FALSE(e.matched);
+  EXPECT_TRUE(e.errored);
+  EXPECT_EQ(e.value, nullptr);
+  EXPECT_TRUE(p->has_error());
   EXPECT_EQ(p->error(), "1:17: missing constructor for const declaration");
 }
 
diff --git a/src/reader/wgsl/parser_impl_variable_storage_decoration_test.cc b/src/reader/wgsl/parser_impl_variable_storage_decoration_test.cc
index 70e7a63..75daebf 100644
--- a/src/reader/wgsl/parser_impl_variable_storage_decoration_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_storage_decoration_test.cc
@@ -39,8 +39,10 @@
   auto* p = parser(std::string("<") + params.input + ">");
 
   auto sc = p->variable_storage_decoration();
-  ASSERT_FALSE(p->has_error());
-  EXPECT_EQ(sc, params.result);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(sc.errored);
+  EXPECT_TRUE(sc.matched);
+  EXPECT_EQ(sc.value, params.result);
 
   auto t = p->next();
   EXPECT_TRUE(t.IsEof());
@@ -64,24 +66,27 @@
 TEST_F(ParserImplTest, VariableStorageDecoration_NoMatch) {
   auto* p = parser("<not-a-storage-class>");
   auto sc = p->variable_storage_decoration();
-  ASSERT_EQ(sc, ast::StorageClass::kNone);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:2: invalid storage class for variable decoration");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+  EXPECT_EQ(p->error(), "1:2: invalid storage class for variable decoration");
 }
 
 TEST_F(ParserImplTest, VariableStorageDecoration_Empty) {
   auto* p = parser("<>");
   auto sc = p->variable_storage_decoration();
-  ASSERT_EQ(sc, ast::StorageClass::kNone);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:2: invalid storage class for variable decoration");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+  EXPECT_EQ(p->error(), "1:2: invalid storage class for variable decoration");
 }
 
 TEST_F(ParserImplTest, VariableStorageDecoration_MissingLessThan) {
   auto* p = parser("in>");
   auto sc = p->variable_storage_decoration();
-  ASSERT_EQ(sc, ast::StorageClass::kNone);
-  ASSERT_FALSE(p->has_error());
+  EXPECT_FALSE(p->has_error());
+  EXPECT_FALSE(sc.errored);
+  EXPECT_FALSE(sc.matched);
 
   auto t = p->next();
   ASSERT_TRUE(t.IsIn());
@@ -90,9 +95,10 @@
 TEST_F(ParserImplTest, VariableStorageDecoration_MissingGreaterThan) {
   auto* p = parser("<in");
   auto sc = p->variable_storage_decoration();
-  ASSERT_EQ(sc, ast::StorageClass::kNone);
-  ASSERT_TRUE(p->has_error());
-  ASSERT_EQ(p->error(), "1:4: expected '>' for variable decoration");
+  EXPECT_TRUE(p->has_error());
+  EXPECT_TRUE(sc.errored);
+  EXPECT_FALSE(sc.matched);
+  EXPECT_EQ(p->error(), "1:4: expected '>' for variable decoration");
 }
 
 }  // namespace