reader/wgsl: Remove type tracking from the parser

This is the responsibility of the resolver, not the parser.
Required to handle out-of-order declarations.

Fixed: tint:888
Bug: tint:1266
Change-Id: I0cbdbe7f721a88bba89c0394a72a20e20b600000
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/69106
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Ben Clayton 2021-11-15 20:45:50 +00:00
parent 8492d727a7
commit 04e62a12d8
16 changed files with 45 additions and 343 deletions

View File

@ -322,18 +322,6 @@ Token ParserImpl::last_token() const {
return last_token_;
}
void ParserImpl::register_type(const std::string& name,
const ast::TypeDecl* type_decl) {
registered_types_[name] = type_decl;
}
const ast::TypeDecl* ParserImpl::get_type(const std::string& name) {
if (registered_types_.find(name) == registered_types_.end()) {
return nullptr;
}
return registered_types_[name];
}
bool ParserImpl::Parse() {
translation_unit();
return !has_error();
@ -419,7 +407,6 @@ Expect<bool> ParserImpl::expect_global_decl() {
if (!expect("struct declaration", Token::Type::kSemicolon))
return Failure::kErrored;
register_type(builder_.Symbols().NameFor(str.value->name), str.value);
builder_.AST().AddTypeDecl(str.value);
return true;
}
@ -1006,10 +993,8 @@ Maybe<const ast::Alias*> ParserImpl::type_alias() {
if (!type.matched)
return add_error(peek(), "invalid type alias");
auto* alias = builder_.ty.alias(make_source_range_from(t.source()),
name.value, type.value);
register_type(name.value, alias);
return alias;
return builder_.ty.alias(make_source_range_from(t.source()), name.value,
type.value);
}
// type_decl
@ -1059,11 +1044,6 @@ Maybe<const ast::Type*> ParserImpl::type_decl(ast::DecorationList& decos) {
auto t = peek();
Source source;
if (match(Token::Type::kIdentifier, &source)) {
// TODO(crbug.com/tint/697): Remove
auto* ty = get_type(t.to_str());
if (ty == nullptr)
return add_error(t, "unknown type '" + t.to_str() + "'");
return builder_.create<ast::TypeName>(
source, builder_.Symbols().Register(t.to_str()));
}
@ -2212,7 +2192,7 @@ Maybe<const ast::Expression*> ParserImpl::primary_expression() {
return create<ast::BitcastExpression>(source, type.value, params.value);
}
if (t.IsIdentifier() && !get_type(t.to_str())) {
if (t.IsIdentifier()) {
next();
auto* ident = create<ast::IdentifierExpression>(
@ -2890,7 +2870,8 @@ Expect<const ast::Expression*> ParserImpl::expect_const_expr() {
return add_error(peek(), "unable to parse constant literal");
}
return lit.value;
} else if (!t.IsIdentifier() || get_type(t.to_str())) {
}
if (peek_is(Token::Type::kParenLeft, 1) ||
peek_is(Token::Type::kLessThan, 1)) {
auto type = expect_type("const_expr");
@ -2924,7 +2905,6 @@ Expect<const ast::Expression*> ParserImpl::expect_const_expr() {
return builder_.Construct(source, type.value, params.value);
}
}
return add_error(peek(), "unable to parse const_expr");
}

View File

@ -380,17 +380,6 @@ class ParserImpl {
/// @param source the source to associate the error with
/// @param msg the warning message
void deprecated(const Source& source, const std::string& msg);
/// Registers a declared type into the parser
/// TODO(crbug.com/tint/724): Remove
/// @param name the type name
/// @param type_decl the type declaration
void register_type(const std::string& name, const ast::TypeDecl* type_decl);
/// Retrieves a declared type
/// TODO(crbug.com/tint/724): Remove
/// @param name The name to lookup
/// @returns the declared type for `name` or `nullptr` if not found
const ast::TypeDecl* get_type(const std::string& name);
/// Parses the `translation_unit` grammar element
void translation_unit();
/// Parses the `global_decl` grammar element, erroring on parse failure.
@ -893,7 +882,6 @@ class ParserImpl {
uint32_t parse_depth_ = 0;
std::vector<Token::Type> sync_tokens_;
int silence_errors_ = 0;
std::unordered_map<std::string, const ast::TypeDecl*> registered_types_;
ProgramBuilder builder_;
size_t max_errors_ = 25;
};

View File

@ -124,25 +124,17 @@ TEST_F(ParserImplTest, ConstExpr_ConstLiteral_Invalid) {
EXPECT_EQ(p->error(), "1:1: unable to parse const_expr");
}
TEST_F(ParserImplTest, ConstExpr_RegisteredType) {
TEST_F(ParserImplTest, ConstExpr_TypeConstructor) {
auto p = parser("S(0)");
auto* mem = Member("m", ty.i32(), ast::DecorationList{});
auto* s = Structure(Sym("S"), {mem});
p->register_type("S", s);
auto e = p->expect_const_expr();
ASSERT_FALSE(e.errored);
ASSERT_TRUE(e->Is<ast::CallExpression>());
}
TEST_F(ParserImplTest, ConstExpr_NotRegisteredType) {
auto p = parser("S(0)");
auto e = p->expect_const_expr();
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(e.errored);
ASSERT_EQ(e.value, nullptr);
EXPECT_EQ(p->error(), "1:1: unable to parse const_expr");
ASSERT_NE(e->As<ast::CallExpression>()->target.type, nullptr);
ASSERT_TRUE(e->As<ast::CallExpression>()->target.type->Is<ast::TypeName>());
EXPECT_EQ(
e->As<ast::CallExpression>()->target.type->As<ast::TypeName>()->name,
p->builder().Symbols().Get("S"));
}
TEST_F(ParserImplTest, ConstExpr_Recursion) {

View File

@ -494,22 +494,6 @@ TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExpr) {
" ^\n");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstNotConstExprWithParn) {
EXPECT(
"let a = 1;\n"
"let b = a();",
"test.wgsl:2:9 error: unable to parse const_expr\n"
"let b = a();\n"
" ^\n");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstConstExprRegisteredType) {
EXPECT("let a = S0(0);",
"test.wgsl:1:9 error: unable to parse const_expr\n"
"let a = S0(0);\n"
" ^^\n");
}
TEST_F(ParserImplErrorTest, GlobalDeclConstExprMaxDepth) {
uint32_t kMaxDepth = 128;
@ -784,13 +768,6 @@ TEST_F(ParserImplErrorTest, GlobalDeclTypeAliasMissingSemicolon) {
" ^\n");
}
TEST_F(ParserImplErrorTest, GlobalDeclTypeInvalid) {
EXPECT("var x : fish;",
"test.wgsl:1:9 error: unknown type 'fish'\n"
"var x : fish;\n"
" ^^^^\n");
}
TEST_F(ParserImplErrorTest, GlobalDeclTypeDecoInvalid) {
EXPECT("var x : [[]] i32;",
"test.wgsl:1:11 error: empty decoration list\n"

View File

@ -141,15 +141,6 @@ TEST_F(ParserImplTest, FunctionHeader_MissingParenRight) {
EXPECT_EQ(p->error(), "1:10: expected ')' for function declaration");
}
TEST_F(ParserImplTest, FunctionHeader_InvalidReturnType) {
auto p = parser("fn main() -> invalid");
auto f = p->function_header();
EXPECT_FALSE(f.matched);
EXPECT_TRUE(f.errored);
EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:14: unknown type 'invalid'");
}
TEST_F(ParserImplTest, FunctionHeader_MissingReturnType) {
auto p = parser("fn main() ->");
auto f = p->function_header();

View File

@ -75,19 +75,6 @@ TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) {
ast::HasDecoration<ast::OverrideDecoration>(e.value->decorations));
}
TEST_F(ParserImplTest, GlobalConstantDecl_InvalidVariable) {
auto p = parser("let a : invalid = 1.");
auto decos = p->decoration_list();
EXPECT_FALSE(decos.errored);
EXPECT_FALSE(decos.matched);
auto e = p->global_constant_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:9: unknown type 'invalid'");
}
TEST_F(ParserImplTest, GlobalConstantDecl_InvalidExpression) {
auto p = parser("let a : f32 = if (a) {}");
auto decos = p->decoration_list();

View File

@ -44,13 +44,6 @@ TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Inferred_Invalid) {
EXPECT_EQ(p->error(), "1:16: expected ':' for variable declaration");
}
TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Invalid) {
auto p = parser("var<private> a : vec2<invalid>;");
p->expect_global_decl();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:23: unknown type 'invalid'");
}
TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_MissingSemicolon) {
auto p = parser("var<private> a : vec2<i32>");
p->expect_global_decl();
@ -120,13 +113,6 @@ type B = A;)");
EXPECT_EQ(tn->name, str->name);
}
TEST_F(ParserImplTest, GlobalDecl_TypeAlias_Invalid) {
auto p = parser("type A = invalid;");
p->expect_global_decl();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:10: unknown type 'invalid'");
}
TEST_F(ParserImplTest, GlobalDecl_TypeAlias_MissingSemicolon) {
auto p = parser("type A = i32");
p->expect_global_decl();

View File

@ -131,9 +131,8 @@ TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_Empty) {
ASSERT_TRUE(e->Is<ast::CallExpression>());
auto* call = e->As<ast::CallExpression>();
ASSERT_TRUE(call->target.type->Is<ast::TypeName>());
EXPECT_EQ(call->target.type->As<ast::TypeName>()->name,
p->builder().Symbols().Get("S"));
ASSERT_NE(call->target.name, nullptr);
EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
ASSERT_EQ(call->args.size(), 0u);
}
@ -156,9 +155,8 @@ TEST_F(ParserImplTest, PrimaryExpression_TypeDecl_StructConstructor_NotEmpty) {
ASSERT_TRUE(e->Is<ast::CallExpression>());
auto* call = e->As<ast::CallExpression>();
ASSERT_TRUE(call->target.type->Is<ast::TypeName>());
EXPECT_EQ(call->target.type->As<ast::TypeName>()->name,
p->builder().Symbols().Get("S"));
ASSERT_NE(call->target.name, nullptr);
EXPECT_EQ(call->target.name->symbol, p->builder().Symbols().Get("S"));
ASSERT_EQ(call->args.size(), 2u);
@ -273,16 +271,6 @@ TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingType) {
EXPECT_EQ(p->error(), "1:9: invalid 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());
EXPECT_EQ(p->error(), "1:9: unknown type 'invalid'");
}
TEST_F(ParserImplTest, PrimaryExpression_Bitcast_MissingLeftParen) {
auto p = parser("bitcast<f32>1)");
auto e = p->primary_expression();

View File

@ -139,22 +139,6 @@ TEST_F(ParserImplTest, StructDecl_MissingBracketLeft) {
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();
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 type 'B'");
}
TEST_F(ParserImplTest, StructDecl_InvalidDecorationDecl) {
auto p = parser("[[block struct S { a : i32; }");
auto decos = p->decoration_list();

View File

@ -162,19 +162,6 @@ TEST_F(ParserImplTest, StructMember_InvalidDecoration) {
"1:8: expected signed integer literal for size decoration");
}
TEST_F(ParserImplTest, StructMember_InvalidVariable) {
auto p = parser("[[size(4)]] a : B;");
auto decos = p->decoration_list();
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);
EXPECT_EQ(p->error(), "1:17: unknown type 'B'");
}
TEST_F(ParserImplTest, StructMember_MissingSemicolon) {
auto p = parser("a : i32");
auto decos = p->decoration_list();

View File

@ -105,21 +105,6 @@ parameters
ASSERT_EQ(1u, p->program().AST().Functions().size());
}
TEST_F(ParserImplTest, GetRegisteredType) {
auto p = parser("");
auto* alias = create<ast::Alias>(Sym("my_alias"), ty.i32());
p->register_type("my_alias", alias);
auto* got = p->get_type("my_alias");
EXPECT_EQ(got, alias);
}
TEST_F(ParserImplTest, GetUnregisteredType) {
auto p = parser("");
auto* alias = p->get_type("my_alias");
ASSERT_EQ(alias, nullptr);
}
} // namespace
} // namespace wgsl
} // namespace reader

View File

@ -110,16 +110,6 @@ TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_U32) {
EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 16u}}));
}
TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_Invalid) {
auto p = parser("texture_1d<abc>");
auto t = p->texture_sampler_types();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(t.value, nullptr);
EXPECT_FALSE(t.matched);
EXPECT_TRUE(t.errored);
EXPECT_EQ(p->error(), "1:12: unknown type 'abc'");
}
TEST_F(ParserImplTest, TextureSamplerTypes_SampledTexture_MissingType) {
auto p = parser("texture_1d<>");
auto t = p->texture_sampler_types();
@ -164,16 +154,6 @@ TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_I32) {
EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 29u}}));
}
TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_Invalid) {
auto p = parser("texture_multisampled_2d<abc>");
auto t = p->texture_sampler_types();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(t.value, nullptr);
EXPECT_FALSE(t.matched);
EXPECT_TRUE(t.errored);
EXPECT_EQ(p->error(), "1:25: unknown type 'abc'");
}
TEST_F(ParserImplTest, TextureSamplerTypes_MultisampledTexture_MissingType) {
auto p = parser("texture_multisampled_2d<>");
auto t = p->texture_sampler_types();

View File

@ -37,9 +37,6 @@ TEST_F(ParserImplTest, TypeDecl_ParsesType) {
TEST_F(ParserImplTest, TypeDecl_ParsesStruct_Ident) {
auto p = parser("type a = B");
auto* str = p->builder().Structure(p->builder().Symbols().Register("B"), {});
p->register_type("B", str);
auto t = p->type_alias();
EXPECT_FALSE(p->has_error());
EXPECT_FALSE(t.errored);
@ -82,16 +79,6 @@ TEST_F(ParserImplTest, TypeDecl_MissingEqual) {
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();
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 type 'B'");
}
} // namespace
} // namespace wgsl
} // namespace reader

View File

@ -36,10 +36,6 @@ TEST_F(ParserImplTest, TypeDecl_Invalid) {
TEST_F(ParserImplTest, TypeDecl_Identifier) {
auto p = parser("A");
auto& builder = p->builder();
auto* alias_type = builder.ty.alias("A", builder.ty.i32());
p->register_type("A", alias_type);
auto t = p->type_decl();
EXPECT_TRUE(t.matched);
EXPECT_FALSE(t.errored);
@ -50,17 +46,6 @@ TEST_F(ParserImplTest, TypeDecl_Identifier) {
EXPECT_EQ(type_name->source.range, (Source::Range{{1u, 1u}, {1u, 2u}}));
}
TEST_F(ParserImplTest, TypeDecl_Identifier_NotFound) {
auto p = parser("B");
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 type 'B'");
}
TEST_F(ParserImplTest, TypeDecl_Bool) {
auto p = parser("bool");
@ -172,24 +157,6 @@ INSTANTIATE_TEST_SUITE_P(ParserImplTest,
VecData{"vec3", 3, {}},
VecData{"vec4", 4, {}}));
class VecBadType : public ParserImplTestWithParam<VecData> {};
TEST_P(VecBadType, Handles_Unknown_Type) {
auto params = GetParam();
auto p = parser(params.input);
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 type 'unknown'");
}
INSTANTIATE_TEST_SUITE_P(ParserImplTest,
VecBadType,
testing::Values(VecData{"vec2<unknown", 2, {}},
VecData{"vec3<unknown", 3, {}},
VecData{"vec4<unknown", 4, {}}));
class VecMissingType : public ParserImplTestWithParam<VecData> {};
TEST_P(VecMissingType, Handles_Missing_Type) {
@ -358,16 +325,6 @@ TEST_F(ParserImplTest, TypeDecl_Ptr_BadStorageClass) {
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();
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 type 'unknown'");
}
TEST_F(ParserImplTest, TypeDecl_Ptr_BadAccess) {
auto p = parser("ptr<function, i32, unknown>");
auto t = p->type_decl();
@ -702,16 +659,6 @@ TEST_F(ParserImplTest, TypeDecl_Array_Runtime_Vec) {
EXPECT_EQ(t.value->source.range, (Source::Range{{1u, 1u}, {1u, 17u}}));
}
TEST_F(ParserImplTest, TypeDecl_Array_BadType) {
auto p = parser("array<unknown, 3>");
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 type 'unknown'");
}
TEST_F(ParserImplTest, TypeDecl_Array_BadSize) {
auto p = parser("array<f32, !>");
auto t = p->type_decl();
@ -851,31 +798,6 @@ INSTANTIATE_TEST_SUITE_P(ParserImplTest,
MatrixData{"mat4x3 f32>", 4, 3, {}},
MatrixData{"mat4x4 f32>", 4, 4, {}}));
class MatrixBadType : public ParserImplTestWithParam<MatrixData> {};
TEST_P(MatrixBadType, Handles_Unknown_Type) {
auto params = GetParam();
auto p = parser(params.input);
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 type 'unknown'");
}
INSTANTIATE_TEST_SUITE_P(
ParserImplTest,
MatrixBadType,
testing::Values(MatrixData{"mat2x2<unknown>", 2, 2, {}},
MatrixData{"mat2x3<unknown>", 2, 3, {}},
MatrixData{"mat2x4<unknown>", 2, 4, {}},
MatrixData{"mat3x2<unknown>", 3, 2, {}},
MatrixData{"mat3x3<unknown>", 3, 3, {}},
MatrixData{"mat3x4<unknown>", 3, 4, {}},
MatrixData{"mat4x2<unknown>", 4, 2, {}},
MatrixData{"mat4x3<unknown>", 4, 3, {}},
MatrixData{"mat4x4<unknown>", 4, 4, {}}));
class MatrixMissingType : public ParserImplTestWithParam<MatrixData> {};
TEST_P(MatrixMissingType, Handles_Missing_Type) {

View File

@ -76,14 +76,6 @@ TEST_F(ParserImplTest, VariableIdentDecl_InvalidIdent) {
ASSERT_EQ(p->error(), "1:1: expected identifier for test");
}
TEST_F(ParserImplTest, VariableIdentDecl_InvalidType) {
auto p = parser("my_var : invalid");
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);
ASSERT_EQ(p->error(), "1:10: unknown type 'invalid'");
}
TEST_F(ParserImplTest, VariableIdentDecl_NonAccessDecoFail) {
auto p = parser("my_var : [[stride(1)]] S");
@ -95,10 +87,6 @@ TEST_F(ParserImplTest, VariableIdentDecl_NonAccessDecoFail) {
ast::DecorationList decos;
decos.push_back(block_deco);
auto* s = Structure(Sym("S"), members, decos);
p->register_type("S", s);
auto decl = p->expect_variable_ident_decl("test");
ASSERT_TRUE(p->has_error());
ASSERT_TRUE(decl.errored);

View File

@ -58,16 +58,6 @@ TEST_F(ParserImplTest, VariableStmt_VariableDecl_WithInit) {
EXPECT_TRUE(e->variable->constructor->Is<ast::LiteralExpression>());
}
TEST_F(ParserImplTest, VariableStmt_VariableDecl_Invalid) {
auto p = parser("var a : invalid;");
auto e = p->variable_stmt();
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 type 'invalid'");
}
TEST_F(ParserImplTest, VariableStmt_VariableDecl_ConstructorInvalid) {
auto p = parser("var a : i32 = if(a) {}");
auto e = p->variable_stmt();
@ -165,16 +155,6 @@ TEST_F(ParserImplTest, VariableStmt_Let) {
ASSERT_EQ(e->source.range.end.column, 6u);
}
TEST_F(ParserImplTest, VariableStmt_Let_InvalidVarIdent) {
auto p = parser("let a : invalid = 1");
auto e = p->variable_stmt();
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 type 'invalid'");
}
TEST_F(ParserImplTest, VariableStmt_Let_MissingEqual) {
auto p = parser("let a : i32 1");
auto e = p->variable_stmt();