tint: Rework errors around variable declarations

Standarize the messages, using 'let', 'override' or 'var'.

Module-scope 'let' needs to be replaced with 'const', but baby steps.

Bug: tint:1582
Change-Id: I290aede118a30ab0f4294c89ec43005371c87b45
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/93446
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2022-06-16 08:36:30 +00:00 committed by Dawn LUCI CQ
parent 418e873ad2
commit 2a8861d20e
17 changed files with 169 additions and 96 deletions

View File

@ -433,7 +433,7 @@ Maybe<bool> ParserImpl::global_decl() {
} }
if (gc.matched) { if (gc.matched) {
if (!expect("let declaration", Token::Type::kSemicolon)) { if (!expect("'let' declaration", Token::Type::kSemicolon)) {
return Failure::kErrored; return Failure::kErrored;
} }
@ -562,7 +562,7 @@ Maybe<const ast::Variable*> ParserImpl::global_constant_decl(ast::AttributeList&
bool is_overridable = false; bool is_overridable = false;
const char* use = nullptr; const char* use = nullptr;
if (match(Token::Type::kLet)) { if (match(Token::Type::kLet)) {
use = "let declaration"; use = "'let' declaration";
} else if (match(Token::Type::kOverride)) { } else if (match(Token::Type::kOverride)) {
use = "override declaration"; use = "override declaration";
is_overridable = true; is_overridable = true;
@ -575,8 +575,18 @@ Maybe<const ast::Variable*> ParserImpl::global_constant_decl(ast::AttributeList&
return Failure::kErrored; return Failure::kErrored;
} }
bool has_initializer = false;
if (is_overridable) {
has_initializer = match(Token::Type::kEqual);
} else {
if (!expect(use, Token::Type::kEqual)) {
return Failure::kErrored;
}
has_initializer = true;
}
const ast::Expression* initializer = nullptr; const ast::Expression* initializer = nullptr;
if (match(Token::Type::kEqual)) { if (has_initializer) {
auto init = expect_const_expr(); auto init = expect_const_expr();
if (init.errored) { if (init.errored) {
return Failure::kErrored; return Failure::kErrored;
@ -1757,13 +1767,13 @@ Maybe<const ast::ReturnStatement*> ParserImpl::return_stmt() {
// | CONST variable_ident_decl EQUAL logical_or_expression // | CONST variable_ident_decl EQUAL logical_or_expression
Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() { Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
if (match(Token::Type::kLet)) { if (match(Token::Type::kLet)) {
auto decl = expect_variable_ident_decl("let declaration", auto decl = expect_variable_ident_decl("'let' declaration",
/*allow_inferred = */ true); /*allow_inferred = */ true);
if (decl.errored) { if (decl.errored) {
return Failure::kErrored; return Failure::kErrored;
} }
if (!expect("let declaration", Token::Type::kEqual)) { if (!expect("'let' declaration", Token::Type::kEqual)) {
return Failure::kErrored; return Failure::kErrored;
} }
@ -1772,7 +1782,7 @@ Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
return Failure::kErrored; return Failure::kErrored;
} }
if (!constructor.matched) { if (!constructor.matched) {
return add_error(peek(), "missing constructor for let declaration"); return add_error(peek(), "missing constructor for 'let' declaration");
} }
auto* var = create<ast::Variable>(decl->source, // source auto* var = create<ast::Variable>(decl->source, // source
@ -1803,7 +1813,7 @@ Maybe<const ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
return Failure::kErrored; return Failure::kErrored;
} }
if (!constructor_expr.matched) { if (!constructor_expr.matched) {
return add_error(peek(), "missing constructor for variable declaration"); return add_error(peek(), "missing constructor for 'var' declaration");
} }
constructor = constructor_expr.value; constructor = constructor_expr.value;

View File

@ -204,7 +204,7 @@ fn f() { x = vec2<u32>(1,2; }
TEST_F(ParserImplErrorTest, ConstVarStmtInvalid) { TEST_F(ParserImplErrorTest, ConstVarStmtInvalid) {
EXPECT("fn f() { let >; }", EXPECT("fn f() { let >; }",
R"(test.wgsl:1:14 error: expected identifier for let declaration R"(test.wgsl:1:14 error: expected identifier for 'let' declaration
fn f() { let >; } fn f() { let >; }
^ ^
)"); )");
@ -212,7 +212,7 @@ fn f() { let >; }
TEST_F(ParserImplErrorTest, ConstVarStmtMissingAssignment) { TEST_F(ParserImplErrorTest, ConstVarStmtMissingAssignment) {
EXPECT("fn f() { let a : i32; }", EXPECT("fn f() { let a : i32; }",
R"(test.wgsl:1:21 error: expected '=' for let declaration R"(test.wgsl:1:21 error: expected '=' for 'let' declaration
fn f() { let a : i32; } fn f() { let a : i32; }
^ ^
)"); )");
@ -220,7 +220,7 @@ fn f() { let a : i32; }
TEST_F(ParserImplErrorTest, ConstVarStmtMissingConstructor) { TEST_F(ParserImplErrorTest, ConstVarStmtMissingConstructor) {
EXPECT("fn f() { let a : i32 = >; }", EXPECT("fn f() { let a : i32 = >; }",
R"(test.wgsl:1:24 error: missing constructor for let declaration R"(test.wgsl:1:24 error: missing constructor for 'let' declaration
fn f() { let a : i32 = >; } fn f() { let a : i32 = >; }
^ ^
)"); )");
@ -472,7 +472,7 @@ test.wgsl:3:3 error: statement found outside of function body
TEST_F(ParserImplErrorTest, GlobalDeclConstInvalidIdentifier) { TEST_F(ParserImplErrorTest, GlobalDeclConstInvalidIdentifier) {
EXPECT("let ^ : i32 = 1;", EXPECT("let ^ : i32 = 1;",
R"(test.wgsl:1:5 error: expected identifier for let declaration R"(test.wgsl:1:5 error: expected identifier for 'let' declaration
let ^ : i32 = 1; let ^ : i32 = 1;
^ ^
)"); )");
@ -480,7 +480,7 @@ let ^ : i32 = 1;
TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) { TEST_F(ParserImplErrorTest, GlobalDeclConstMissingSemicolon) {
EXPECT("let i : i32 = 1", EXPECT("let i : i32 = 1",
R"(test.wgsl:1:16 error: expected ';' for let declaration R"(test.wgsl:1:16 error: expected ';' for 'let' declaration
let i : i32 = 1 let i : i32 = 1
^ ^
)"); )");
@ -512,7 +512,7 @@ let i : vec2<i32> = vec2<i32>(!);
TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) { TEST_F(ParserImplErrorTest, GlobalDeclConstBadConstLiteralSpaceLessThan) {
EXPECT("let i = 1 < 2;", EXPECT("let i = 1 < 2;",
R"(test.wgsl:1:11 error: expected ';' for let declaration R"(test.wgsl:1:11 error: expected ';' for 'let' declaration
let i = 1 < 2; let i = 1 < 2;
^ ^
)"); )");
@ -1215,7 +1215,7 @@ fn f() { var a : u32 }
TEST_F(ParserImplErrorTest, VarStmtInvalidAssignment) { TEST_F(ParserImplErrorTest, VarStmtInvalidAssignment) {
EXPECT("fn f() { var a : u32 = >; }", EXPECT("fn f() { var a : u32 = >; }",
R"(test.wgsl:1:24 error: missing constructor for variable declaration R"(test.wgsl:1:24 error: missing constructor for 'var' declaration
fn f() { var a : u32 = >; } fn f() { var a : u32 = >; }
^ ^
)"); )");

View File

@ -253,7 +253,7 @@ TEST_F(ForStmtErrorTest, MissingRightBrace) {
// Test a for loop with an invalid initializer statement. // Test a for loop with an invalid initializer statement.
TEST_F(ForStmtErrorTest, InvalidInitializerAsConstDecl) { TEST_F(ForStmtErrorTest, InvalidInitializerAsConstDecl) {
std::string for_str = "for (let x: i32;;) { }"; std::string for_str = "for (let x: i32;;) { }";
std::string error_str = "1:16: expected '=' for let declaration"; std::string error_str = "1:16: expected '=' for 'let' declaration";
TestForWithError(for_str, error_str); TestForWithError(for_str, error_str);
} }
@ -304,7 +304,7 @@ TEST_F(ForStmtErrorTest, InvalidContinuingMatch) {
// Test a for loop with an invalid body. // Test a for loop with an invalid body.
TEST_F(ForStmtErrorTest, InvalidBody) { TEST_F(ForStmtErrorTest, InvalidBody) {
std::string for_str = "for (;;) { let x: i32; }"; std::string for_str = "for (;;) { let x: i32; }";
std::string error_str = "1:22: expected '=' for let declaration"; std::string error_str = "1:22: expected '=' for 'let' declaration";
TestForWithError(for_str, error_str); TestForWithError(for_str, error_str);
} }

View File

@ -61,18 +61,25 @@ TEST_F(ParserImplTest, GlobalDecl_GlobalConstant) {
EXPECT_EQ(v->symbol, program.Symbols().Get("a")); EXPECT_EQ(v->symbol, program.Symbols().Get("a"));
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingInitializer) {
auto p = parser("let a : vec2<i32>;");
p->global_decl();
ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:18: expected '=' for 'let' declaration");
}
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_Invalid) { TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_Invalid) {
auto p = parser("let a : vec2<i32> 1.0;"); auto p = parser("let a : vec2<i32> 1.0;");
p->global_decl(); p->global_decl();
ASSERT_TRUE(p->has_error()); ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:19: expected ';' for let declaration"); EXPECT_EQ(p->error(), "1:19: expected '=' for 'let' declaration");
} }
TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingSemicolon) { TEST_F(ParserImplTest, GlobalDecl_GlobalConstant_MissingSemicolon) {
auto p = parser("let a : vec2<i32> = vec2<i32>(1, 2)"); auto p = parser("let a : vec2<i32> = vec2<i32>(1, 2)");
p->global_decl(); p->global_decl();
ASSERT_TRUE(p->has_error()); ASSERT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:36: expected ';' for let declaration"); EXPECT_EQ(p->error(), "1:36: expected ';' for 'let' declaration");
} }
TEST_F(ParserImplTest, GlobalDecl_TypeAlias) { TEST_F(ParserImplTest, GlobalDecl_TypeAlias) {

View File

@ -114,7 +114,7 @@ TEST_F(ParserImplTest, Statement_Variable_Invalid) {
EXPECT_TRUE(e.errored); EXPECT_TRUE(e.errored);
EXPECT_FALSE(e.matched); EXPECT_FALSE(e.matched);
EXPECT_EQ(e.value, nullptr); EXPECT_EQ(e.value, nullptr);
EXPECT_EQ(p->error(), "1:14: missing constructor for variable declaration"); EXPECT_EQ(p->error(), "1:14: missing constructor for 'var' declaration");
} }
TEST_F(ParserImplTest, Statement_Variable_MissingSemicolon) { TEST_F(ParserImplTest, Statement_Variable_MissingSemicolon) {

View File

@ -63,7 +63,7 @@ TEST_F(ParserImplTest, VariableStmt_VariableDecl_ConstructorInvalid) {
EXPECT_TRUE(e.errored); EXPECT_TRUE(e.errored);
EXPECT_EQ(e.value, nullptr); EXPECT_EQ(e.value, nullptr);
EXPECT_TRUE(p->has_error()); EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:15: missing constructor for variable declaration"); EXPECT_EQ(p->error(), "1:15: missing constructor for 'var' declaration");
} }
TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit) { TEST_F(ParserImplTest, VariableStmt_VariableDecl_ArrayInit) {
@ -160,7 +160,7 @@ TEST_F(ParserImplTest, VariableStmt_Let_MissingEqual) {
EXPECT_TRUE(e.errored); EXPECT_TRUE(e.errored);
EXPECT_EQ(e.value, nullptr); EXPECT_EQ(e.value, nullptr);
EXPECT_TRUE(p->has_error()); EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:13: expected '=' for let declaration"); EXPECT_EQ(p->error(), "1:13: expected '=' for 'let' declaration");
} }
TEST_F(ParserImplTest, VariableStmt_Let_MissingConstructor) { TEST_F(ParserImplTest, VariableStmt_Let_MissingConstructor) {
@ -170,7 +170,7 @@ TEST_F(ParserImplTest, VariableStmt_Let_MissingConstructor) {
EXPECT_TRUE(e.errored); EXPECT_TRUE(e.errored);
EXPECT_EQ(e.value, nullptr); EXPECT_EQ(e.value, nullptr);
EXPECT_TRUE(p->has_error()); EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:14: missing constructor for let declaration"); EXPECT_EQ(p->error(), "1:14: missing constructor for 'let' declaration");
} }
TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) { TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) {
@ -180,7 +180,7 @@ TEST_F(ParserImplTest, VariableStmt_Let_InvalidConstructor) {
EXPECT_TRUE(e.errored); EXPECT_TRUE(e.errored);
EXPECT_EQ(e.value, nullptr); EXPECT_EQ(e.value, nullptr);
EXPECT_TRUE(p->has_error()); EXPECT_TRUE(p->has_error());
EXPECT_EQ(p->error(), "1:15: missing constructor for let declaration"); EXPECT_EQ(p->error(), "1:15: missing constructor for 'let' declaration");
} }
} // namespace } // namespace

View File

@ -222,7 +222,7 @@ TEST_F(ResolverAssignmentValidationTest, AssignToLet_Fail) {
WrapInFunction(var, Assign(Expr(Source{{12, 34}}, "a"), 2_i)); WrapInFunction(var, Assign(Expr(Source{{12, 34}}, "a"), 2_i));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: cannot assign to const\nnote: 'a' is declared here:"); EXPECT_EQ(r()->error(), "12:34 error: cannot assign to 'let'\nnote: 'a' is declared here:");
} }
TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Handle) { TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Handle) {

View File

@ -721,7 +721,7 @@ TEST_P(VariableAttributeTest, IsValid) {
} else { } else {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
if (!IsBindingAttribute(params.kind)) { if (!IsBindingAttribute(params.kind)) {
EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for variables"); EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for module-scope 'var'");
} }
} }
} }
@ -783,11 +783,59 @@ TEST_P(ConstantAttributeTest, IsValid) {
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} else { } else {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for constants"); EXPECT_EQ(r()->error(),
"12:34 error: attribute is not valid for module-scope 'let' declaration");
} }
} }
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest, INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
ConstantAttributeTest, ConstantAttributeTest,
testing::Values(TestParams{AttributeKind::kAlign, false},
TestParams{AttributeKind::kBinding, false},
TestParams{AttributeKind::kBuiltin, false},
TestParams{AttributeKind::kGroup, false},
TestParams{AttributeKind::kId, false},
TestParams{AttributeKind::kInterpolate, false},
TestParams{AttributeKind::kInvariant, false},
TestParams{AttributeKind::kLocation, false},
TestParams{AttributeKind::kOffset, false},
TestParams{AttributeKind::kSize, false},
TestParams{AttributeKind::kStage, false},
TestParams{AttributeKind::kStride, false},
TestParams{AttributeKind::kWorkgroup, false},
TestParams{AttributeKind::kBindingAndGroup, false}));
TEST_F(ConstantAttributeTest, DuplicateAttribute) {
GlobalConst("a", ty.f32(), Expr(1.23_f),
ast::AttributeList{
create<ast::IdAttribute>(Source{{12, 34}}, 0),
create<ast::IdAttribute>(Source{{56, 78}}, 1),
});
WrapInFunction();
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate id attribute
12:34 note: first attribute declared here)");
}
using OverrideAttributeTest = TestWithParams;
TEST_P(OverrideAttributeTest, IsValid) {
auto& params = GetParam();
Override("a", ty.f32(), Expr(1.23_f), createAttributes(Source{{12, 34}}, *this, params.kind));
WrapInFunction();
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: attribute is not valid for 'override' declaration");
}
}
INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
OverrideAttributeTest,
testing::Values(TestParams{AttributeKind::kAlign, false}, testing::Values(TestParams{AttributeKind::kAlign, false},
TestParams{AttributeKind::kBinding, false}, TestParams{AttributeKind::kBinding, false},
TestParams{AttributeKind::kBuiltin, false}, TestParams{AttributeKind::kBuiltin, false},
@ -803,7 +851,7 @@ INSTANTIATE_TEST_SUITE_P(ResolverAttributeValidationTest,
TestParams{AttributeKind::kWorkgroup, false}, TestParams{AttributeKind::kWorkgroup, false},
TestParams{AttributeKind::kBindingAndGroup, false})); TestParams{AttributeKind::kBindingAndGroup, false}));
TEST_F(ConstantAttributeTest, DuplicateAttribute) { TEST_F(OverrideAttributeTest, DuplicateAttribute) {
GlobalConst("a", ty.f32(), Expr(1.23_f), GlobalConst("a", ty.f32(), Expr(1.23_f),
ast::AttributeList{ ast::AttributeList{
create<ast::IdAttribute>(Source{{12, 34}}, 0), create<ast::IdAttribute>(Source{{12, 34}}, 0),

View File

@ -249,7 +249,7 @@ TEST_F(ResolverCompoundAssignmentValidationTest, LhsConstant) {
WrapInFunction(a, CompoundAssign(Expr(Source{{56, 78}}, "a"), 1_i, ast::BinaryOp::kAdd)); WrapInFunction(a, CompoundAssign(Expr(Source{{56, 78}}, "a"), 1_i, ast::BinaryOp::kAdd));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(56:78 error: cannot assign to const EXPECT_EQ(r()->error(), R"(56:78 error: cannot assign to 'let'
12:34 note: 'a' is declared here:)"); 12:34 note: 'a' is declared here:)");
} }

View File

@ -151,7 +151,7 @@ TEST_F(ResolverIncrementDecrementValidationTest, Constant) {
WrapInFunction(a, Increment(Expr(Source{{56, 78}}, "a"))); WrapInFunction(a, Increment(Expr(Source{{56, 78}}, "a")));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(56:78 error: cannot modify constant value EXPECT_EQ(r()->error(), R"(56:78 error: cannot modify 'let'
12:34 note: 'a' is declared here:)"); 12:34 note: 'a' is declared here:)");
} }

View File

@ -98,7 +98,7 @@ TEST_P(ResolverInferredTypeParamTest, GlobalVar_Fail) {
WrapInFunction(); WrapInFunction();
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: global var declaration must specify a type"); EXPECT_EQ(r()->error(), "12:34 error: module-scope 'var' declaration must specify a type");
} }
TEST_P(ResolverInferredTypeParamTest, LocalLet_Pass) { TEST_P(ResolverInferredTypeParamTest, LocalLet_Pass) {

View File

@ -326,19 +326,19 @@ sem::Variable* Resolver::Variable(const ast::Variable* var,
// If the variable has no declared type, infer it from the RHS // If the variable has no declared type, infer it from the RHS
if (!storage_ty) { if (!storage_ty) {
if (!var->is_const && kind == VariableKind::kGlobal) { if (!var->is_const && kind == VariableKind::kGlobal) {
AddError("global var declaration must specify a type", var->source); AddError("module-scope 'var' declaration must specify a type", var->source);
return nullptr; return nullptr;
} }
storage_ty = rhs->Type()->UnwrapRef(); // Implicit load of RHS storage_ty = rhs->Type()->UnwrapRef(); // Implicit load of RHS
} }
} else if (var->is_const && !var->is_overridable && kind != VariableKind::kParameter) { } else if (var->is_const && !var->is_overridable && kind != VariableKind::kParameter) {
AddError("let declaration must have an initializer", var->source); AddError("'let' declaration must have an initializer", var->source);
return nullptr; return nullptr;
} else if (!var->type) { } else if (!var->type) {
AddError((kind == VariableKind::kGlobal) AddError((kind == VariableKind::kGlobal)
? "module scope var declaration requires a type and initializer" ? "module-scope 'var' declaration requires a type or initializer"
: "function scope var declaration requires a type or initializer", : "function-scope 'var' declaration requires a type or initializer",
var->source); var->source);
return nullptr; return nullptr;
} }
@ -368,7 +368,7 @@ sem::Variable* Resolver::Variable(const ast::Variable* var,
storage_class != ast::StorageClass::kFunction && storage_class != ast::StorageClass::kFunction &&
validator_.IsValidationEnabled(var->attributes, validator_.IsValidationEnabled(var->attributes,
ast::DisabledValidation::kIgnoreStorageClass)) { ast::DisabledValidation::kIgnoreStorageClass)) {
AddError("function variable has a non-function storage class", var->source); AddError("function-scope 'var' declaration must use 'function' storage class", var->source);
return nullptr; return nullptr;
} }
@ -519,11 +519,13 @@ sem::GlobalVariable* Resolver::GlobalVariable(const ast::Variable* var) {
auto storage_class = sem->StorageClass(); auto storage_class = sem->StorageClass();
if (!var->is_const && storage_class == ast::StorageClass::kNone) { if (!var->is_const && storage_class == ast::StorageClass::kNone) {
AddError("global variables must have a storage class", var->source); AddError("module-scope 'var' declaration must have a storage class", var->source);
return nullptr; return nullptr;
} }
if (var->is_const && storage_class != ast::StorageClass::kNone) { if (var->is_const && storage_class != ast::StorageClass::kNone) {
AddError("global constants shouldn't have a storage class", var->source); AddError(var->is_overridable ? "'override' declaration must not have a storage class"
: "'let' declaration must not have a storage class",
var->source);
return nullptr; return nullptr;
} }
@ -2069,20 +2071,15 @@ sem::Array* Resolver::Array(const ast::Array* arr) {
if (auto* ident = count_expr->As<ast::IdentifierExpression>()) { if (auto* ident = count_expr->As<ast::IdentifierExpression>()) {
// Make sure the identifier is a non-overridable module-scope constant. // Make sure the identifier is a non-overridable module-scope constant.
auto* var = sem_.ResolvedSymbol<sem::GlobalVariable>(ident); auto* var = sem_.ResolvedSymbol<sem::GlobalVariable>(ident);
if (!var || !var->Declaration()->is_const) { if (!var || !var->Declaration()->is_const || var->IsOverridable()) {
AddError("array size identifier must be a module-scope constant", size_source); AddError("array size identifier must be a literal or a module-scope 'let'",
return nullptr; size_source);
}
if (var->IsOverridable()) {
AddError("array size expression must not be pipeline-overridable", size_source);
return nullptr; return nullptr;
} }
count_expr = var->Declaration()->constructor; count_expr = var->Declaration()->constructor;
} else if (!count_expr->Is<ast::LiteralExpression>()) { } else if (!count_expr->Is<ast::LiteralExpression>()) {
AddError( AddError("array size identifier must be a literal or a module-scope 'let'",
"array size expression must be either a literal or a module-scope "
"constant",
size_source); size_source);
return nullptr; return nullptr;
} }

View File

@ -30,7 +30,8 @@ TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone); Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone);
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: global variables must have a storage class"); EXPECT_EQ(r()->error(),
"12:34 error: module-scope 'var' declaration must have a storage class");
} }
TEST_F(ResolverStorageClassValidationTest, GlobalVariableFunctionStorageClass_Fail) { TEST_F(ResolverStorageClassValidationTest, GlobalVariableFunctionStorageClass_Fail) {
@ -39,8 +40,7 @@ TEST_F(ResolverStorageClassValidationTest, GlobalVariableFunctionStorageClass_Fa
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: variables declared at module scope must not be in " "12:34 error: module-scope 'var' must not use storage class 'function'");
"the function storage class");
} }
TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArray) { TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArray) {

View File

@ -90,11 +90,21 @@ TEST_F(ResolverTypeValidationTest, GlobalVariableWithStorageClass_Pass) {
TEST_F(ResolverTypeValidationTest, GlobalLetWithStorageClass_Fail) { TEST_F(ResolverTypeValidationTest, GlobalLetWithStorageClass_Fail) {
// let<private> global_var: f32; // let<private> global_var: f32;
AST().AddGlobalVariable(create<ast::Variable>( AST().AddGlobalVariable(create<ast::Variable>(
Source{{12, 34}}, Symbols().Register("global_var"), ast::StorageClass::kPrivate, Source{{12, 34}}, Symbols().Register("global_let"), ast::StorageClass::kPrivate,
ast::Access::kUndefined, ty.f32(), true, false, Expr(1.23_f), ast::AttributeList{})); ast::Access::kUndefined, ty.f32(), true, false, Expr(1.23_f), ast::AttributeList{}));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: global constants shouldn't have a storage class"); EXPECT_EQ(r()->error(), "12:34 error: 'let' declaration must not have a storage class");
}
TEST_F(ResolverTypeValidationTest, OverrideWithStorageClass_Fail) {
// let<private> global_var: f32;
AST().AddGlobalVariable(create<ast::Variable>(
Source{{12, 34}}, Symbols().Register("global_override"), ast::StorageClass::kPrivate,
ast::Access::kUndefined, ty.f32(), true, true, Expr(1.23_f), ast::AttributeList{}));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: 'override' declaration must not have a storage class");
} }
TEST_F(ResolverTypeValidationTest, GlobalConstNoStorageClass_Pass) { TEST_F(ResolverTypeValidationTest, GlobalConstNoStorageClass_Pass) {
@ -334,7 +344,8 @@ TEST_F(ResolverTypeValidationTest, ArraySize_Overridable) {
Override("size", nullptr, Expr(10_i)); Override("size", nullptr, Expr(10_i));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate); Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size expression must not be pipeline-overridable"); EXPECT_EQ(r()->error(),
"12:34 error: array size identifier must be a literal or a module-scope 'let'");
} }
TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) { TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) {
@ -343,7 +354,8 @@ TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) {
Global("size", ty.i32(), Expr(10_i), ast::StorageClass::kPrivate); Global("size", ty.i32(), Expr(10_i), ast::StorageClass::kPrivate);
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate); Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")), ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size identifier must be a module-scope constant"); EXPECT_EQ(r()->error(),
"12:34 error: array size identifier must be a literal or a module-scope 'let'");
} }
TEST_F(ResolverTypeValidationTest, ArraySize_FunctionLet) { TEST_F(ResolverTypeValidationTest, ArraySize_FunctionLet) {
@ -355,7 +367,8 @@ TEST_F(ResolverTypeValidationTest, ArraySize_FunctionLet) {
auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size"))); auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
WrapInFunction(Block(Decl(size), Decl(a))); WrapInFunction(Block(Decl(size), Decl(a)));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size identifier must be a module-scope constant"); EXPECT_EQ(r()->error(),
"12:34 error: array size identifier must be a literal or a module-scope 'let'");
} }
TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) { TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) {
@ -364,8 +377,7 @@ TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) {
WrapInFunction(Block(Decl(a))); WrapInFunction(Block(Decl(a)));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: array size expression must be either a literal or a " "12:34 error: array size identifier must be a literal or a module-scope 'let'");
"module-scope constant");
} }
TEST_F(ResolverTypeValidationTest, RuntimeArrayInFunction_Fail) { TEST_F(ResolverTypeValidationTest, RuntimeArrayInFunction_Fail) {

View File

@ -304,7 +304,8 @@ TEST_F(ResolverValidationTest, StorageClass_FunctionVariableWorkgroupClass) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "error: function variable has a non-function storage class"); EXPECT_EQ(r()->error(),
"error: function-scope 'var' declaration must use 'function' storage class");
} }
TEST_F(ResolverValidationTest, StorageClass_FunctionVariableI32) { TEST_F(ResolverValidationTest, StorageClass_FunctionVariableI32) {
@ -317,7 +318,8 @@ TEST_F(ResolverValidationTest, StorageClass_FunctionVariableI32) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "error: function variable has a non-function storage class"); EXPECT_EQ(r()->error(),
"error: function-scope 'var' declaration must use 'function' storage class");
} }
TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) { TEST_F(ResolverValidationTest, StorageClass_SamplerExplicitStorageClass) {

View File

@ -506,16 +506,16 @@ bool Validator::GlobalVariable(
for (auto* attr : decl->attributes) { for (auto* attr : decl->attributes) {
if (decl->is_const) { if (decl->is_const) {
if (decl->is_overridable) {
if (auto* id_attr = attr->As<ast::IdAttribute>()) { if (auto* id_attr = attr->As<ast::IdAttribute>()) {
uint32_t id = id_attr->value; uint32_t id = id_attr->value;
auto it = constant_ids.find(id); auto it = constant_ids.find(id);
if (it != constant_ids.end() && it->second != var) { if (it != constant_ids.end() && it->second != var) {
AddError("pipeline constant IDs must be unique", attr->source); AddError("pipeline constant IDs must be unique", attr->source);
AddNote( AddNote("a pipeline constant with an ID of " + std::to_string(id) +
"a pipeline constant with an ID of " + std::to_string(id) + " was previously declared here:",
" was previously declared " ast::GetAttribute<ast::IdAttribute>(
"here:", it->second->Declaration()->attributes)
ast::GetAttribute<ast::IdAttribute>(it->second->Declaration()->attributes)
->source); ->source);
return false; return false;
} }
@ -524,7 +524,11 @@ bool Validator::GlobalVariable(
return false; return false;
} }
} else { } else {
AddError("attribute is not valid for constants", attr->source); AddError("attribute is not valid for 'override' declaration", attr->source);
return false;
}
} else {
AddError("attribute is not valid for module-scope 'let' declaration", attr->source);
return false; return false;
} }
} else { } else {
@ -536,17 +540,14 @@ bool Validator::GlobalVariable(
if (!(attr->IsAnyOf<ast::BindingAttribute, ast::GroupAttribute, if (!(attr->IsAnyOf<ast::BindingAttribute, ast::GroupAttribute,
ast::InternalAttribute>()) && ast::InternalAttribute>()) &&
(!is_shader_io_attribute || !has_io_storage_class)) { (!is_shader_io_attribute || !has_io_storage_class)) {
AddError("attribute is not valid for variables", attr->source); AddError("attribute is not valid for module-scope 'var'", attr->source);
return false; return false;
} }
} }
} }
if (var->StorageClass() == ast::StorageClass::kFunction) { if (var->StorageClass() == ast::StorageClass::kFunction) {
AddError( AddError("module-scope 'var' must not use storage class 'function'", decl->source);
"variables declared at module scope must not be in the function "
"storage class",
decl->source);
return false; return false;
} }
@ -559,10 +560,7 @@ bool Validator::GlobalVariable(
// Each resource variable must be declared with both group and binding // Each resource variable must be declared with both group and binding
// attributes. // attributes.
if (!binding_point) { if (!binding_point) {
AddError( AddError("resource variables require @group and @binding attributes", decl->source);
"resource variables require @group and @binding "
"attributes",
decl->source);
return false; return false;
} }
break; break;
@ -571,9 +569,7 @@ bool Validator::GlobalVariable(
if (binding_point.binding || binding_point.group) { if (binding_point.binding || binding_point.group) {
// https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding // https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding
// Must only be applied to a resource variable // Must only be applied to a resource variable
AddError( AddError("non-resource variables must not have @group or @binding attributes",
"non-resource variables must not have @group or @binding "
"attributes",
decl->source); decl->source);
return false; return false;
} }
@ -2162,7 +2158,9 @@ bool Validator::Assignment(const ast::Statement* a, const sem::Type* rhs_ty) con
return false; return false;
} }
if (decl->is_const) { if (decl->is_const) {
AddError("cannot assign to const", lhs->source); AddError(
decl->is_overridable ? "cannot assign to 'override'" : "cannot assign to 'let'",
lhs->source);
AddNote("'" + symbols_.NameFor(decl->symbol) + "' is declared here:", decl->source); AddNote("'" + symbols_.NameFor(decl->symbol) + "' is declared here:", decl->source);
return false; return false;
} }
@ -2210,7 +2208,8 @@ bool Validator::IncrementDecrementStatement(const ast::IncrementDecrementStateme
return false; return false;
} }
if (decl->is_const) { if (decl->is_const) {
AddError("cannot modify constant value", lhs->source); AddError(decl->is_overridable ? "cannot modify 'override'" : "cannot modify 'let'",
lhs->source);
AddNote("'" + symbols_.NameFor(decl->symbol) + "' is declared here:", decl->source); AddNote("'" + symbols_.NameFor(decl->symbol) + "' is declared here:", decl->source);
return false; return false;
} }

View File

@ -29,7 +29,7 @@ TEST_F(ResolverVarLetValidationTest, LetNoInitializer) {
WrapInFunction(Let(Source{{12, 34}}, "a", ty.i32(), nullptr)); WrapInFunction(Let(Source{{12, 34}}, "a", ty.i32(), nullptr));
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: let declaration must have an initializer"); EXPECT_EQ(r()->error(), "12:34 error: 'let' declaration must have an initializer");
} }
TEST_F(ResolverVarLetValidationTest, GlobalLetNoInitializer) { TEST_F(ResolverVarLetValidationTest, GlobalLetNoInitializer) {
@ -37,7 +37,7 @@ TEST_F(ResolverVarLetValidationTest, GlobalLetNoInitializer) {
GlobalConst(Source{{12, 34}}, "a", ty.i32(), nullptr); GlobalConst(Source{{12, 34}}, "a", ty.i32(), nullptr);
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: let declaration must have an initializer"); EXPECT_EQ(r()->error(), "12:34 error: 'let' declaration must have an initializer");
} }
TEST_F(ResolverVarLetValidationTest, VarNoInitializerNoType) { TEST_F(ResolverVarLetValidationTest, VarNoInitializerNoType) {
@ -46,8 +46,7 @@ TEST_F(ResolverVarLetValidationTest, VarNoInitializerNoType) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: function scope var declaration requires a type or " "12:34 error: function-scope 'var' declaration requires a type or initializer");
"initializer");
} }
TEST_F(ResolverVarLetValidationTest, GlobalVarNoInitializerNoType) { TEST_F(ResolverVarLetValidationTest, GlobalVarNoInitializerNoType) {
@ -56,8 +55,7 @@ TEST_F(ResolverVarLetValidationTest, GlobalVarNoInitializerNoType) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: module scope var declaration requires a type and " "12:34 error: module-scope 'var' declaration requires a type or initializer");
"initializer");
} }
TEST_F(ResolverVarLetValidationTest, VarTypeNotStorable) { TEST_F(ResolverVarLetValidationTest, VarTypeNotStorable) {