Move assignment validation from Validator to Resolver
* With this change, ProgramBuilder::WrapInStatement(expr), which
produced an "expr = expr" assignment expression, would fail validation
in some cases like for call expressions. Replaced this with a
declaration of a variable with type inferred from expr.
* Moved existing validation tests to resolver\assignment_validation.cc,
and added missing tests: AssignFromPointer_Fail.
* Fixed broken tests as a result of this change.
Bug: tint:642
Change-Id: I6a738b67edb0e116a46e936d652c2dcb4389281e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/46161
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/program_builder.cc b/src/program_builder.cc
index bebb8be..ac01248 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -15,6 +15,7 @@
#include "src/program_builder.h"
#include "src/ast/assignment_statement.h"
+#include "src/ast/call_statement.h"
#include "src/ast/variable_decl_statement.h"
#include "src/demangler.h"
#include "src/semantic/expression.h"
@@ -126,9 +127,8 @@
}
ast::Statement* ProgramBuilder::WrapInStatement(ast::Expression* expr) {
- // TODO(ben-clayton): This is valid enough for the Resolver, but the LHS
- // may not be assignable, and so may not validate.
- return create<ast::AssignmentStatement>(expr, expr);
+ // Create a temporary variable of inferred type from expr.
+ return Decl(Var(symbols_.New(), nullptr, ast::StorageClass::kFunction, expr));
}
ast::Statement* ProgramBuilder::WrapInStatement(ast::Statement* stmt) {
diff --git a/src/resolver/assignment_validation_test.cc b/src/resolver/assignment_validation_test.cc
index ce975ee..f4e8489 100644
--- a/src/resolver/assignment_validation_test.cc
+++ b/src/resolver/assignment_validation_test.cc
@@ -16,6 +16,8 @@
#include "gmock/gmock.h"
#include "src/resolver/resolver_test_helper.h"
+#include "src/type/access_control_type.h"
+#include "src/type/storage_texture_type.h"
namespace tint {
namespace resolver {
@@ -139,6 +141,143 @@
R"(12:34 error: invalid assignment: cannot assign value of type 'f32' to a variable of type 'i32')");
}
+TEST_F(ResolverAssignmentValidationTest, AssignToScalar_Fail) {
+ // var my_var : i32 = 2;
+ // 1 = my_var;
+
+ auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
+ auto* lhs = Expr(1);
+ auto* rhs = Expr("my_var");
+
+ auto* assign = create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs);
+ WrapInFunction(Decl(var), assign);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error v-000x: invalid assignment: left-hand-side does not "
+ "reference storage: i32");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypes_Pass) {
+ // var a :i32 = 2;
+ // a = 2
+ auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+
+ auto* lhs = Expr("a");
+ auto* rhs = Expr(2);
+
+ auto* assign = create<ast::AssignmentStatement>(
+ Source{Source::Location{12, 34}}, lhs, rhs);
+ WrapInFunction(Decl(var), assign);
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+ AssignCompatibleTypesThroughAlias_Pass) {
+ // alias myint = i32;
+ // var a :myint = 2;
+ // a = 2
+ auto* myint = ty.alias("myint", ty.i32());
+ auto* var = Var("a", myint, ast::StorageClass::kNone, Expr(2));
+
+ auto* lhs = Expr("a");
+ auto* rhs = Expr(2);
+
+ auto* assign = create<ast::AssignmentStatement>(
+ Source{Source::Location{12, 34}}, lhs, rhs);
+ WrapInFunction(Decl(var), assign);
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest,
+ AssignCompatibleTypesInferRHSLoad_Pass) {
+ // var a : i32 = 2;
+ // var b : i32 = 3;
+ // a = b;
+ auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+ auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3));
+
+ auto* lhs = Expr("a");
+ auto* rhs = Expr("b");
+
+ auto* assign = create<ast::AssignmentStatement>(
+ Source{Source::Location{12, 34}}, lhs, rhs);
+ WrapInFunction(Decl(var_a), Decl(var_b), assign);
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignThroughPointer_Pass) {
+ // var a :i32;
+ // const b : ptr<function,i32> = a;
+ // b = 2;
+ const auto func = ast::StorageClass::kFunction;
+ auto* var_a = Var("a", ty.i32(), func, Expr(2), {});
+ auto* var_b = Const("b", ty.pointer<int>(func), Expr("a"), {});
+
+ auto* lhs = Expr("b");
+ auto* rhs = Expr(2);
+
+ auto* assign = create<ast::AssignmentStatement>(
+ Source{Source::Location{12, 34}}, lhs, rhs);
+ WrapInFunction(Decl(var_a), Decl(var_b), assign);
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignToConstant_Fail) {
+ // {
+ // const a :i32 = 2;
+ // a = 2
+ // }
+ auto* var = Const("a", ty.i32(), Expr(2));
+
+ auto* lhs = Expr("a");
+ auto* rhs = Expr(2);
+
+ auto* body = create<ast::BlockStatement>(ast::StatementList{
+ create<ast::VariableDeclStatement>(var),
+ create<ast::AssignmentStatement>(Source{Source::Location{12, 34}}, lhs,
+ rhs),
+ });
+
+ WrapInFunction(body);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error v-0021: cannot re-assign a constant: 'a'");
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignFromPointer_Fail) {
+ // var a : [[access(read)]] texture_storage_1d<rgba8unorm>;
+ // var b : [[access(read)]] texture_storage_1d<rgba8unorm>;
+ // a = b;
+
+ auto* tex_type = create<type::StorageTexture>(
+ type::TextureDimension::k1d, type::ImageFormat::kRgba8Unorm,
+ type::StorageTexture::SubtypeFor(type::ImageFormat::kRgba8Unorm,
+ Types()));
+ auto* tex_ac =
+ create<type::AccessControl>(ast::AccessControl::kReadOnly, tex_type);
+
+ auto* var_a = Var("a", tex_ac, ast::StorageClass::kFunction);
+ auto* var_b = Var("b", tex_ac, ast::StorageClass::kFunction);
+
+ auto* lhs = Expr("a");
+ auto* rhs = Expr("b");
+
+ auto* assign = create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs);
+ WrapInFunction(Decl(var_a), Decl(var_b), assign);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error v-000x: invalid assignment: right-hand-side is not "
+ "storable: ptr<function, [[access(read)]] "
+ "texture_storage_1d<rgba8unorm>>");
+}
+
} // namespace
} // namespace resolver
} // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 38ed168..3c8dfcc 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -333,23 +333,7 @@
ScopedAssignment<semantic::Statement*> sa(current_statement_, sem_statement);
if (auto* a = stmt->As<ast::AssignmentStatement>()) {
- if (!Expression(a->lhs()) || !Expression(a->rhs())) {
- return false;
- }
- // TODO(crbug.com/tint/659): This logic needs updating once pointers are
- // pinned down in the WGSL spec.
- auto* lhs_type = TypeOf(a->lhs())->UnwrapAll();
- auto* rhs_type = TypeOf(a->rhs());
- if (!IsValidAssignment(lhs_type, rhs_type)) {
- diagnostics_.add_error(
- "invalid assignment: cannot assign value of type '" +
- rhs_type->FriendlyName(builder_->Symbols()) +
- "' to a variable of type '" +
- lhs_type->FriendlyName(builder_->Symbols()) + "'",
- stmt->source());
- return false;
- }
- return true;
+ return Assignment(a);
}
if (auto* b = stmt->As<ast::BlockStatement>()) {
return BlockStatement(b);
@@ -1770,6 +1754,74 @@
return true;
}
+bool Resolver::ValidateAssignment(const ast::AssignmentStatement* a) {
+ auto* lhs = a->lhs();
+ auto* rhs = a->rhs();
+
+ // TODO(crbug.com/tint/659): This logic needs updating once pointers are
+ // pinned down in the WGSL spec.
+ auto* lhs_type = TypeOf(lhs)->UnwrapAll();
+ auto* rhs_type = TypeOf(rhs);
+ if (!IsValidAssignment(lhs_type, rhs_type)) {
+ diagnostics_.add_error("invalid assignment: cannot assign value of type '" +
+ rhs_type->FriendlyName(builder_->Symbols()) +
+ "' to a variable of type '" +
+ lhs_type->FriendlyName(builder_->Symbols()) +
+ "'",
+ a->source());
+ return false;
+ }
+
+ // Pointers are not storable in WGSL, but the right-hand side must be
+ // storable. The raw right-hand side might be a pointer value which must be
+ // loaded (dereferenced) to provide the value to be stored.
+ auto* rhs_result_type = TypeOf(rhs)->UnwrapAll();
+ if (!IsStorable(rhs_result_type)) {
+ diagnostics_.add_error(
+ "v-000x",
+ "invalid assignment: right-hand-side is not storable: " +
+ TypeOf(rhs)->FriendlyName(builder_->Symbols()),
+ a->source());
+ return false;
+ }
+
+ // lhs must be a pointer or a constant
+ auto* lhs_result_type = TypeOf(lhs)->UnwrapIfNeeded();
+ if (!lhs_result_type->Is<type::Pointer>()) {
+ // In case lhs is a constant identifier, output a nicer message as it's
+ // likely to be a common programmer error.
+ if (auto* ident = lhs->As<ast::IdentifierExpression>()) {
+ VariableInfo* var;
+ if (variable_stack_.get(ident->symbol(), &var) &&
+ var->declaration->is_const()) {
+ diagnostics_.add_error(
+ "v-0021",
+ "cannot re-assign a constant: '" +
+ builder_->Symbols().NameFor(ident->symbol()) + "'",
+ a->source());
+ return false;
+ }
+ }
+
+ // Issue a generic error.
+ diagnostics_.add_error(
+ "v-000x",
+ "invalid assignment: left-hand-side does not reference storage: " +
+ TypeOf(lhs)->FriendlyName(builder_->Symbols()),
+ a->source());
+ return false;
+ }
+
+ return true;
+}
+
+bool Resolver::Assignment(ast::AssignmentStatement* a) {
+ if (!Expression(a->lhs()) || !Expression(a->rhs())) {
+ return false;
+ }
+ return ValidateAssignment(a);
+}
+
bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
type::Type* ty,
Source usage) {
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 3f39d73..2aa09874 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -223,6 +223,7 @@
bool VariableDeclStatement(const ast::VariableDeclStatement*);
bool Return(ast::ReturnStatement* ret);
bool Switch(ast::SwitchStatement* s);
+ bool Assignment(ast::AssignmentStatement* a);
// AST and Type validation methods
// Each return true on success, false on failure.
@@ -232,6 +233,7 @@
bool ValidateStructure(const type::Struct* st);
bool ValidateReturn(const ast::ReturnStatement* ret);
bool ValidateSwitch(const ast::SwitchStatement* s);
+ bool ValidateAssignment(const ast::AssignmentStatement* a);
/// @returns the semantic information for the array `arr`, building it if it
/// hasn't been constructed already. If an error is raised, nullptr is
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 3aa69a5..fa0e933 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -639,14 +639,13 @@
TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
auto* my_var_a = Expr("my_var");
- auto* my_var_b = Expr("my_var");
auto* var = Const("my_var", ty.f32());
- auto* assign = create<ast::AssignmentStatement>(my_var_a, my_var_b);
+ auto* decl = Decl(Var("b", ty.f32(), ast::StorageClass::kFunction, my_var_a));
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
- assign,
+ decl,
},
ast::DecorationList{});
@@ -654,11 +653,8 @@
ASSERT_NE(TypeOf(my_var_a), nullptr);
EXPECT_TRUE(TypeOf(my_var_a)->Is<type::F32>());
- EXPECT_EQ(StmtOf(my_var_a), assign);
- ASSERT_NE(TypeOf(my_var_b), nullptr);
- EXPECT_TRUE(TypeOf(my_var_b)->Is<type::F32>());
- EXPECT_EQ(StmtOf(my_var_b), assign);
- EXPECT_TRUE(CheckVarUsers(var, {my_var_a, my_var_b}));
+ EXPECT_EQ(StmtOf(my_var_a), decl);
+ EXPECT_TRUE(CheckVarUsers(var, {my_var_a}));
}
TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) {
diff --git a/src/validator/validator_impl.cc b/src/validator/validator_impl.cc
index a00c5fa..b817892 100644
--- a/src/validator/validator_impl.cc
+++ b/src/validator/validator_impl.cc
@@ -238,9 +238,6 @@
if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
return ValidateDeclStatement(v);
}
- if (auto* a = stmt->As<ast::AssignmentStatement>()) {
- return ValidateAssign(a);
- }
if (auto* s = stmt->As<ast::SwitchStatement>()) {
return ValidateSwitch(s);
}
@@ -272,66 +269,6 @@
return true;
}
-bool ValidatorImpl::ValidateBadAssignmentToIdentifier(
- const ast::AssignmentStatement* assign) {
- auto* ident = assign->lhs()->As<ast::IdentifierExpression>();
- if (!ident) {
- // It wasn't an identifier in the first place.
- return true;
- }
- const ast::Variable* var;
- if (variable_stack_.get(ident->symbol(), &var)) {
- // Give a nicer message if the LHS of the assignment is a const identifier.
- // It's likely to be a common programmer error.
- if (var->is_const()) {
- add_error(assign->source(), "v-0021",
- "cannot re-assign a constant: '" +
- program_->Symbols().NameFor(ident->symbol()) + "'");
- return false;
- }
- } else {
- // The identifier is not defined.
- // Shouldn't reach here. This should already have been caught when
- // validation expressions in the Resolver
- TINT_UNREACHABLE(diagnostics());
- return false;
- }
- return true;
-}
-
-bool ValidatorImpl::ValidateAssign(const ast::AssignmentStatement* assign) {
- if (!assign) {
- return false;
- }
- auto* lhs = assign->lhs();
- auto* rhs = assign->rhs();
-
- // Pointers are not storable in WGSL, but the right-hand side must be
- // storable. The raw right-hand side might be a pointer value which must be
- // loaded (dereferenced) to provide the value to be stored.
- auto* rhs_result_type = program_->Sem().Get(rhs)->Type()->UnwrapAll();
- if (!IsStorable(rhs_result_type)) {
- add_error(assign->source(), "v-000x",
- "invalid assignment: right-hand-side is not storable: " +
- program_->Sem().Get(rhs)->Type()->type_name());
- return false;
- }
- auto* lhs_result_type = program_->Sem().Get(lhs)->Type()->UnwrapIfNeeded();
- if (!Is<type::Pointer>(lhs_result_type)) {
- if (!ValidateBadAssignmentToIdentifier(assign)) {
- return false;
- }
- // Issue a generic error.
- add_error(
- assign->source(), "v-000x",
- "invalid assignment: left-hand-side does not reference storage: " +
- program_->Sem().Get(lhs)->Type()->type_name());
- return false;
- }
-
- return true;
-}
-
bool ValidatorImpl::IsStorable(type::Type* type) {
if (type == nullptr) {
return false;
diff --git a/src/validator/validator_impl.h b/src/validator/validator_impl.h
index 04edf95..e84de4e 100644
--- a/src/validator/validator_impl.h
+++ b/src/validator/validator_impl.h
@@ -84,16 +84,6 @@
/// @param stmt the statement to check
/// @returns true if the validation was successful
bool ValidateStatement(const ast::Statement* stmt);
- /// Validates an assignment
- /// @param assign the assignment to check
- /// @returns true if the validation was successful
- bool ValidateAssign(const ast::AssignmentStatement* assign);
- /// Validates a bad assignment to an identifier. Issues an error
- /// and returns false if the left hand side is an identifier.
- /// @param assign the assignment to check
- /// @returns true if the LHS of theassignment is not an identifier expression
- bool ValidateBadAssignmentToIdentifier(
- const ast::AssignmentStatement* assign);
/// Validates declaration name uniqueness
/// @param decl is the new declaration to be added
/// @returns true if no previous declaration with the `decl` 's name
diff --git a/src/validator/validator_test.cc b/src/validator/validator_test.cc
index b5d8dba..1e3b5c7 100644
--- a/src/validator/validator_test.cc
+++ b/src/validator/validator_test.cc
@@ -21,123 +21,6 @@
class ValidatorTest : public ValidatorTestHelper, public testing::Test {};
-TEST_F(ValidatorTest, AssignToScalar_Fail) {
- // var my_var : i32 = 2;
- // 1 = my_var;
-
- auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
- RegisterVariable(var);
-
- auto* lhs = Expr(1);
- auto* rhs = Expr("my_var");
-
- SetSource(Source{Source::Location{12, 34}});
- auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
- WrapInFunction(assign);
-
- ValidatorImpl& v = Build();
-
- // TODO(sarahM0): Invalidate assignment to scalar.
- EXPECT_FALSE(v.ValidateAssign(assign));
- ASSERT_TRUE(v.has_error());
- // TODO(sarahM0): figure out what should be the error number.
- EXPECT_EQ(v.error(),
- "12:34 v-000x: invalid assignment: left-hand-side does not "
- "reference storage: __i32");
-}
-
-TEST_F(ValidatorTest, AssignCompatibleTypes_Pass) {
- // var a :i32 = 2;
- // a = 2
- auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
- RegisterVariable(var);
-
- auto* lhs = Expr("a");
- auto* rhs = Expr(2);
-
- auto* assign = create<ast::AssignmentStatement>(
- Source{Source::Location{12, 34}}, lhs, rhs);
- WrapInFunction(assign);
-
- ValidatorImpl& v = Build();
-
- ASSERT_NE(TypeOf(lhs), nullptr);
- ASSERT_NE(TypeOf(rhs), nullptr);
-
- EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
-}
-
-TEST_F(ValidatorTest, AssignCompatibleTypesThroughAlias_Pass) {
- // alias myint = i32;
- // var a :myint = 2;
- // a = 2
- auto* myint = ty.alias("myint", ty.i32());
- auto* var = Var("a", myint, ast::StorageClass::kNone, Expr(2));
- RegisterVariable(var);
-
- auto* lhs = Expr("a");
- auto* rhs = Expr(2);
-
- auto* assign = create<ast::AssignmentStatement>(
- Source{Source::Location{12, 34}}, lhs, rhs);
- WrapInFunction(assign);
-
- ValidatorImpl& v = Build();
-
- ASSERT_NE(TypeOf(lhs), nullptr);
- ASSERT_NE(TypeOf(rhs), nullptr);
-
- EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
-}
-
-TEST_F(ValidatorTest, AssignCompatibleTypesInferRHSLoad_Pass) {
- // var a :i32 = 2;
- // var b :i32 = 3;
- // a = b;
- auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
- auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3));
- RegisterVariable(var_a);
- RegisterVariable(var_b);
-
- auto* lhs = Expr("a");
- auto* rhs = Expr("b");
-
- auto* assign = create<ast::AssignmentStatement>(
- Source{Source::Location{12, 34}}, lhs, rhs);
- WrapInFunction(assign);
-
- ValidatorImpl& v = Build();
-
- ASSERT_NE(TypeOf(lhs), nullptr);
- ASSERT_NE(TypeOf(rhs), nullptr);
-
- EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
-}
-
-TEST_F(ValidatorTest, AssignThroughPointer_Pass) {
- // var a :i32;
- // const b : ptr<function,i32> = a;
- // b = 2;
- const auto func = ast::StorageClass::kFunction;
- auto* var_a = Var("a", ty.i32(), func, Expr(2), {});
- auto* var_b = Const("b", ty.pointer<int>(func), Expr("a"), {});
- RegisterVariable(var_a);
- RegisterVariable(var_b);
-
- auto* lhs = Expr("b");
- auto* rhs = Expr(2);
-
- auto* assign = create<ast::AssignmentStatement>(
- Source{Source::Location{12, 34}}, lhs, rhs);
- WrapInFunction(assign);
-
- ValidatorImpl& v = Build();
-
- ASSERT_NE(TypeOf(lhs), nullptr);
- ASSERT_NE(TypeOf(rhs), nullptr);
-
- EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
-}
TEST_F(ValidatorTest, GlobalVariableWithStorageClass_Pass) {
// var<in> global_var: f32;
@@ -215,33 +98,6 @@
"12:34 v-0011: redeclared global identifier 'global_var'");
}
-TEST_F(ValidatorTest, AssignToConstant_Fail) {
- // {
- // const a :i32 = 2;
- // a = 2
- // }
- auto* var = Const("a", ty.i32(), Expr(2));
-
- auto* lhs = Expr("a");
- auto* rhs = Expr(2);
-
- auto* body = create<ast::BlockStatement>(ast::StatementList{
- create<ast::VariableDeclStatement>(var),
- create<ast::AssignmentStatement>(Source{Source::Location{12, 34}}, lhs,
- rhs),
- });
-
- WrapInFunction(body);
-
- ValidatorImpl& v = Build();
-
- ASSERT_NE(TypeOf(lhs), nullptr);
- ASSERT_NE(TypeOf(rhs), nullptr);
-
- EXPECT_FALSE(v.ValidateStatements(body));
- EXPECT_EQ(v.error(), "12:34 v-0021: cannot re-assign a constant: 'a'");
-}
-
TEST_F(ValidatorTest, GlobalVariableFunctionVariableNotUnique_Pass) {
// fn my_func -> void {
// var a: f32 = 2.0;