mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-17 17:05:31 +00:00
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>
This commit is contained in:
committed by
Commit Bot service account
parent
39a65a1d1e
commit
e09989ae34
@@ -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 @@ TEST_F(ResolverAssignmentValidationTest,
|
||||
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
|
||||
|
||||
@@ -333,23 +333,7 @@ bool Resolver::Statement(ast::Statement* stmt) {
|
||||
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 @@ bool Resolver::Switch(ast::SwitchStatement* s) {
|
||||
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) {
|
||||
|
||||
@@ -223,6 +223,7 @@ class Resolver {
|
||||
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 @@ class Resolver {
|
||||
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
|
||||
|
||||
@@ -639,14 +639,13 @@ TEST_F(ResolverTest, Expr_Identifier_GlobalConstant) {
|
||||
|
||||
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 @@ TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user