Resolver: Check that initializers and assignments are valid

This performs very basic type verification for assignments and variable initializers.

Pulls part of the validation logic out of the Validator into the Resolver.
Involves fixing up a bunch of broken tests.

Bug: tint:642
Fixed: tint:631
Change-Id: Ifbdc139ff7eeab810856e0ba9e3c380c6555ec20
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/45281
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
Ben Clayton 2021-03-18 21:14:44 +00:00 committed by Commit Bot service account
parent 25eef8d2cf
commit 6b2fc057c5
15 changed files with 262 additions and 243 deletions

View File

@ -467,6 +467,7 @@ if(${TINT_BUILD_TESTS})
inspector/inspector_test.cc
intrinsic_table_test.cc
program_test.cc
resolver/assignment_validation_test.cc
resolver/decoration_validation_test.cc
resolver/host_shareable_validation_test.cc
resolver/intrinsic_test.cc

View File

@ -653,7 +653,7 @@ class InspectorHelper : public ProgramBuilder {
if (vector_type_memo_.find(std::tie(type, count)) ==
vector_type_memo_.end()) {
vector_type_memo_[std::tie(type, count)] =
create<type::Vector>(ty.u32(), count);
create<type::Vector>(type, count);
}
return vector_type_memo_[std::tie(type, count)];
}

View File

@ -0,0 +1,144 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/resolver/resolver.h"
#include "gmock/gmock.h"
#include "src/resolver/resolver_test_helper.h"
namespace tint {
namespace resolver {
namespace {
using ResolverAssignmentValidationTest = ResolverTest;
TEST_F(ResolverAssignmentValidationTest, AssignIncompatibleTypes) {
// {
// var a : i32 = 2;
// a = 2.3;
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2.3f);
auto* assign = create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs);
WrapInFunction(var, assign);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: invalid assignment: cannot assign value of type 'f32' to a variable of type 'i32')");
}
TEST_F(ResolverAssignmentValidationTest,
AssignThroughPointerWrongeStoreType_Fail) {
// var a : f32;
// const b : ptr<function,f32> = a;
// b = 2;
const auto priv = ast::StorageClass::kFunction;
auto* var_a = Var("a", ty.f32(), priv);
auto* var_b = Const("b", ty.pointer<float>(priv), Expr("a"), {});
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs);
WrapInFunction(var_a, var_b, assign);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: invalid assignment: cannot assign value of type 'i32' to a variable of type 'f32')");
}
TEST_F(ResolverAssignmentValidationTest,
AssignCompatibleTypesInBlockStatement_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* body = create<ast::BlockStatement>(ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs),
});
WrapInFunction(var, body);
ASSERT_TRUE(r()->Resolve());
}
TEST_F(ResolverAssignmentValidationTest,
AssignIncompatibleTypesInBlockStatement_Fail) {
// {
// var a : i32 = 2;
// a = 2.3;
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2.3f);
auto* block = create<ast::BlockStatement>(ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs),
});
WrapInFunction(var, block);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: invalid assignment: cannot assign value of type 'f32' to a variable of type 'i32')");
}
TEST_F(ResolverAssignmentValidationTest,
AssignIncompatibleTypesInNestedBlockStatement_Fail) {
// {
// {
// var a : i32 = 2;
// a = 2.3;
// }
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2.3f);
auto* inner_block = create<ast::BlockStatement>(ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Source{{12, 34}}, lhs, rhs),
});
auto* outer_block = create<ast::BlockStatement>(ast::StatementList{
inner_block,
});
WrapInFunction(var, outer_block);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: invalid assignment: cannot assign value of type 'f32' to a variable of type 'i32')");
}
} // namespace
} // namespace resolver
} // namespace tint

View File

@ -157,6 +157,21 @@ bool Resolver::IsHostShareable(type::Type* type) {
return false;
}
bool Resolver::IsValidAssignment(type::Type* lhs, type::Type* rhs) {
// TODO(crbug.com/tint/659): This is a rough approximation, and is missing
// checks for writability of pointer storage class, access control, etc.
// This will need to be fixed after WGSL agrees the behavior of pointers /
// references.
// Check:
if (lhs->UnwrapIfNeeded() != rhs->UnwrapIfNeeded()) {
// Try RHS dereference
if (lhs->UnwrapIfNeeded() != rhs->UnwrapAll()) {
return false;
}
}
return true;
}
bool Resolver::ResolveInternal() {
for (auto* ty : builder_->Types()) {
if (auto* str = ty->As<type::Struct>()) {
@ -251,7 +266,23 @@ bool Resolver::Statement(ast::Statement* stmt) {
ScopedAssignment<semantic::Statement*> sa(current_statement_, sem_statement);
if (auto* a = stmt->As<ast::AssignmentStatement>()) {
return Expression(a->lhs()) && Expression(a->rhs());
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;
}
if (auto* b = stmt->As<ast::BlockStatement>()) {
return BlockStatement(b);
@ -1156,16 +1187,15 @@ bool Resolver::VariableDeclStatement(const ast::VariableDeclStatement* stmt) {
if (!Expression(ctor)) {
return false;
}
if (auto* sce = ctor->As<ast::ScalarConstructorExpression>()) {
auto* lhs_type = stmt->variable()->type()->UnwrapAliasIfNeeded();
auto* rhs_type = sce->literal()->type()->UnwrapAliasIfNeeded();
if (lhs_type != rhs_type) {
diagnostics_.add_error(
"constructor expression type does not match variable type",
stmt->source());
return false;
}
auto* lhs_type = stmt->variable()->type();
auto* rhs_type = TypeOf(ctor);
if (!IsValidAssignment(lhs_type, rhs_type)) {
diagnostics_.add_error(
"variable of type '" + lhs_type->FriendlyName(builder_->Symbols()) +
"' cannot be initialized with a value of type '" +
rhs_type->FriendlyName(builder_->Symbols()) + "'",
stmt->source());
return false;
}
}

View File

@ -77,6 +77,13 @@ class Resolver {
/// @returns true if the given type is host-shareable
static bool IsHostShareable(type::Type* type);
/// @param lhs the assignment store type (non-pointer)
/// @param rhs the assignment source type (non-pointer or pointer with
/// auto-deref)
/// @returns true an expression of type `rhs` can be assigned to a variable,
/// structure member or array element of type `lhs`
static bool IsValidAssignment(type::Type* lhs, type::Type* rhs);
private:
/// Structure holding semantic information about a variable.
/// Used to build the semantic::Variable nodes at the end of resolving.
@ -191,7 +198,6 @@ class Resolver {
// AST and Type traversal methods
// Each return true on success, false on failure.
bool ArrayAccessor(ast::ArrayAccessorExpression*);
bool ValidateBinary(ast::BinaryExpression* expr);
bool Binary(ast::BinaryExpression*);
bool Bitcast(ast::BitcastExpression*);
bool BlockStatement(const ast::BlockStatement*);
@ -215,6 +221,10 @@ class Resolver {
bool UnaryOp(ast::UnaryOpExpression*);
bool VariableDeclStatement(const ast::VariableDeclStatement*);
// AST and Type validation methods
// Each return true on success, false on failure.
bool ValidateBinary(ast::BinaryExpression* expr);
/// @returns the semantic information for the array `arr`, building it if it
/// hasn't been constructed already. If an error is raised, nullptr is
/// returned.

View File

@ -76,77 +76,75 @@ type::Type* ty_mat3x3(const ProgramBuilder::TypesBuilder& ty) {
}
TEST_F(ResolverTest, Stmt_Assign) {
auto* lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* lhs = Expr("v");
auto* rhs = Expr(2.3f);
auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
WrapInFunction(assign);
WrapInFunction(v, assign);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_TRUE(TypeOf(lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(rhs)->Is<type::F32>());
EXPECT_EQ(StmtOf(lhs), assign);
EXPECT_EQ(StmtOf(rhs), assign);
}
TEST_F(ResolverTest, Stmt_Case) {
auto* lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* lhs = Expr("v");
auto* rhs = Expr(2.3f);
auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
auto* body = create<ast::BlockStatement>(ast::StatementList{
assign,
});
auto* body = Block(assign);
ast::CaseSelectorList lit;
lit.push_back(create<ast::SintLiteral>(ty.i32(), 3));
auto* cse = create<ast::CaseStatement>(lit, body);
WrapInFunction(cse);
WrapInFunction(v, cse);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_TRUE(TypeOf(lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(rhs)->Is<type::F32>());
EXPECT_EQ(StmtOf(lhs), assign);
EXPECT_EQ(StmtOf(rhs), assign);
}
TEST_F(ResolverTest, Stmt_Block) {
auto* lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* lhs = Expr("v");
auto* rhs = Expr(2.3f);
auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
auto* block = create<ast::BlockStatement>(ast::StatementList{
assign,
});
WrapInFunction(block);
auto* block = Block(assign);
WrapInFunction(v, block);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_TRUE(TypeOf(lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(rhs)->Is<type::F32>());
EXPECT_EQ(StmtOf(lhs), assign);
EXPECT_EQ(StmtOf(rhs), assign);
}
TEST_F(ResolverTest, Stmt_Else) {
auto* lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* lhs = Expr("v");
auto* rhs = Expr(2.3f);
auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
auto* body = create<ast::BlockStatement>(ast::StatementList{
assign,
});
auto* body = Block(assign);
auto* cond = Expr(3);
auto* stmt = create<ast::ElseStatement>(cond, body);
WrapInFunction(stmt);
WrapInFunction(v, stmt);
EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -154,7 +152,7 @@ TEST_F(ResolverTest, Stmt_Else) {
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_TRUE(TypeOf(stmt->condition())->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(rhs)->Is<type::F32>());
EXPECT_EQ(StmtOf(lhs), assign);
EXPECT_EQ(StmtOf(rhs), assign);
@ -162,25 +160,24 @@ TEST_F(ResolverTest, Stmt_Else) {
}
TEST_F(ResolverTest, Stmt_If) {
auto* else_lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* else_lhs = Expr("v");
auto* else_rhs = Expr(2.3f);
auto* else_body = create<ast::BlockStatement>(ast::StatementList{
create<ast::AssignmentStatement>(else_lhs, else_rhs),
});
auto* else_body = Block(create<ast::AssignmentStatement>(else_lhs, else_rhs));
auto* else_cond = Expr(3);
auto* else_stmt = create<ast::ElseStatement>(else_cond, else_body);
auto* lhs = Expr(2);
auto* lhs = Expr("v");
auto* rhs = Expr(2.3f);
auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
auto* body = create<ast::BlockStatement>(ast::StatementList{assign});
auto* body = Block(assign);
auto* cond = Expr(true);
auto* stmt =
create<ast::IfStatement>(cond, body, ast::ElseStatementList{else_stmt});
WrapInFunction(stmt);
WrapInFunction(v, stmt);
EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -190,9 +187,9 @@ TEST_F(ResolverTest, Stmt_If) {
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_TRUE(TypeOf(stmt->condition())->Is<type::Bool>());
EXPECT_TRUE(TypeOf(else_lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(else_lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(else_rhs)->Is<type::F32>());
EXPECT_TRUE(TypeOf(lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(rhs)->Is<type::F32>());
EXPECT_EQ(StmtOf(lhs), assign);
EXPECT_EQ(StmtOf(rhs), assign);
@ -201,22 +198,19 @@ TEST_F(ResolverTest, Stmt_If) {
}
TEST_F(ResolverTest, Stmt_Loop) {
auto* body_lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* body_lhs = Expr("v");
auto* body_rhs = Expr(2.3f);
auto* body = create<ast::BlockStatement>(ast::StatementList{
create<ast::AssignmentStatement>(body_lhs, body_rhs),
});
auto* continuing_lhs = Expr(2);
auto* body = Block(create<ast::AssignmentStatement>(body_lhs, body_rhs));
auto* continuing_lhs = Expr("v");
auto* continuing_rhs = Expr(2.3f);
auto* continuing = create<ast::BlockStatement>(
ast::StatementList{
create<ast::AssignmentStatement>(continuing_lhs, continuing_rhs),
});
auto* continuing = create<ast::BlockStatement>(ast::StatementList{
create<ast::AssignmentStatement>(continuing_lhs, continuing_rhs),
});
auto* stmt = create<ast::LoopStatement>(body, continuing);
WrapInFunction(stmt);
WrapInFunction(v, stmt);
EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -224,9 +218,9 @@ TEST_F(ResolverTest, Stmt_Loop) {
ASSERT_NE(TypeOf(body_rhs), nullptr);
ASSERT_NE(TypeOf(continuing_lhs), nullptr);
ASSERT_NE(TypeOf(continuing_rhs), nullptr);
EXPECT_TRUE(TypeOf(body_lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(body_lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(body_rhs)->Is<type::F32>());
EXPECT_TRUE(TypeOf(continuing_lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(continuing_lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(continuing_rhs)->Is<type::F32>());
}
@ -250,12 +244,11 @@ TEST_F(ResolverTest, Stmt_Return_WithoutValue) {
}
TEST_F(ResolverTest, Stmt_Switch) {
auto* lhs = Expr(2);
auto* v = Var("v", ty.f32(), ast::StorageClass::kFunction);
auto* lhs = Expr("v");
auto* rhs = Expr(2.3f);
auto* body = create<ast::BlockStatement>(ast::StatementList{
create<ast::AssignmentStatement>(lhs, rhs),
});
auto* body = Block(create<ast::AssignmentStatement>(lhs, rhs));
ast::CaseSelectorList lit;
lit.push_back(create<ast::SintLiteral>(ty.i32(), 3));
@ -263,7 +256,7 @@ TEST_F(ResolverTest, Stmt_Switch) {
cases.push_back(create<ast::CaseStatement>(lit, body));
auto* stmt = create<ast::SwitchStatement>(Expr(2), cases);
WrapInFunction(stmt);
WrapInFunction(v, stmt);
EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -272,7 +265,7 @@ TEST_F(ResolverTest, Stmt_Switch) {
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_TRUE(TypeOf(stmt->condition())->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->Is<type::I32>());
EXPECT_TRUE(TypeOf(lhs)->UnwrapAll()->Is<type::F32>());
EXPECT_TRUE(TypeOf(rhs)->Is<type::F32>());
}

View File

@ -157,7 +157,7 @@ TEST_F(ResolverValidationTest,
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(3:3 error: constructor expression type does not match variable type)");
R"(3:3 error: variable of type 'i32' cannot be initialized with a value of type 'u32')");
}
TEST_F(ResolverValidationTest,
@ -174,7 +174,7 @@ TEST_F(ResolverValidationTest,
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(3:3 error: constructor expression type does not match variable type)");
R"(3:3 error: variable of type 'MyInt' cannot be initialized with a value of type 'u32')");
}
TEST_F(ResolverValidationTest, Expr_Error_Unknown) {

View File

@ -24,22 +24,22 @@ using BoundArrayAccessorsTest = TransformTest;
TEST_F(BoundArrayAccessorsTest, Ptrs_Clamp) {
auto* src = R"(
var a : array<f32, 3>;
var<storage> a : array<f32, 3>;
const c : u32 = 1u;
fn f() -> void {
const b : ptr<function, f32> = a[c];
const b : ptr<storage, f32> = a[c];
}
)";
auto* expect = R"(
var a : array<f32, 3>;
var<storage> a : array<f32, 3>;
const c : u32 = 1u;
fn f() -> void {
const b : ptr<function, f32> = a[min(u32(c), 2u)];
const b : ptr<storage, f32> = a[min(u32(c), 2u)];
}
)";

View File

@ -30,7 +30,7 @@ fn main() -> void {
var f1 : f32 = 2.0;
var f2 : f32 = 3.0;
var f3 : f32 = 4.0;
var i : i32 = array<f32, 4>(f0, f1, f2, f3)[2];
var i : f32 = array<f32, 4>(f0, f1, f2, f3)[2];
}
)";
@ -42,7 +42,7 @@ fn main() -> void {
var f2 : f32 = 3.0;
var f3 : f32 = 4.0;
const tint_symbol_1 : array<f32, 4> = array<f32, 4>(f0, f1, f2, f3);
var i : i32 = tint_symbol_1[2];
var i : f32 = tint_symbol_1[2];
}
)";
@ -55,7 +55,7 @@ TEST_F(HlslTest, PromoteArrayInitializerToConstVar_ArrayInArray) {
auto* src = R"(
[[stage(vertex)]]
fn main() -> void {
var i : i32 = array<array<f32, 2>, 2>(array<f32, 2>(1.0, 2.0), array<f32, 2>(3.0, 4.0))[0][1];
var i : f32 = array<array<f32, 2>, 2>(array<f32, 2>(1.0, 2.0), array<f32, 2>(3.0, 4.0))[0][1];
}
)";
@ -65,7 +65,7 @@ fn main() -> void {
const tint_symbol_1 : array<f32, 2> = array<f32, 2>(1.0, 2.0);
const tint_symbol_2 : array<f32, 2> = array<f32, 2>(3.0, 4.0);
const tint_symbol_3 : array<array<f32, 2>, 2> = array<array<f32, 2>, 2>(tint_symbol_1, tint_symbol_2);
var i : i32 = tint_symbol_3[0][1];
var i : f32 = tint_symbol_3[0][1];
}
)";

View File

@ -298,7 +298,7 @@ fn main() -> void {
TEST_F(VertexPullingTest, TwoAttributesSameBuffer) {
auto* src = R"(
[[location(0)]] var<in> var_a : f32;
[[location(1)]] var<in> var_b : array<f32, 4>;
[[location(1)]] var<in> var_b : vec4<f32>;
[[stage(vertex)]]
fn main() -> void {}
@ -316,7 +316,7 @@ struct TintVertexData {
var<private> var_a : f32;
var<private> var_b : array<f32, 4>;
var<private> var_b : vec4<f32>;
[[stage(vertex)]]
fn main() -> void {
@ -346,9 +346,9 @@ fn main() -> void {
TEST_F(VertexPullingTest, FloatVectorAttributes) {
auto* src = R"(
[[location(0)]] var<in> var_a : array<f32, 2>;
[[location(1)]] var<in> var_b : array<f32, 3>;
[[location(2)]] var<in> var_c : array<f32, 4>;
[[location(0)]] var<in> var_a : vec2<f32>;
[[location(1)]] var<in> var_b : vec3<f32>;
[[location(2)]] var<in> var_c : vec4<f32>;
[[stage(vertex)]]
fn main() -> void {}
@ -368,11 +368,11 @@ struct TintVertexData {
_tint_vertex_data : [[stride(4)]] array<u32>;
};
var<private> var_a : array<f32, 2>;
var<private> var_a : vec2<f32>;
var<private> var_b : array<f32, 3>;
var<private> var_b : vec3<f32>;
var<private> var_c : array<f32, 4>;
var<private> var_c : vec4<f32>;
[[stage(vertex)]]
fn main() -> void {

View File

@ -484,16 +484,7 @@ bool ValidatorImpl::ValidateAssign(const ast::AssignmentStatement* assign) {
return false;
}
auto* lhs_result_type = program_->Sem().Get(lhs)->Type()->UnwrapIfNeeded();
if (auto* lhs_reference_type = As<type::Pointer>(lhs_result_type)) {
auto* lhs_store_type = lhs_reference_type->type()->UnwrapIfNeeded();
if (lhs_store_type != rhs_result_type) {
add_error(assign->source(), "v-000x",
"invalid assignment: can't assign value of type '" +
rhs_result_type->type_name() + "' to '" +
lhs_store_type->type_name() + "'");
return false;
}
} else {
if (!Is<type::Pointer>(lhs_result_type)) {
if (!ValidateBadAssignmentToIdentifier(assign)) {
return false;
}

View File

@ -139,157 +139,6 @@ TEST_F(ValidatorTest, AssignThroughPointer_Pass) {
EXPECT_TRUE(v.ValidateAssign(assign)) << v.error();
}
TEST_F(ValidatorTest, AssignIncompatibleTypes_Fail) {
// {
// var a :i32 = 2;
// a = 2.3;
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
RegisterVariable(var);
auto* lhs = Expr("a");
auto* rhs = Expr(2.3f);
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_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: can't assign value of type "
"'__f32' to '__i32'");
}
TEST_F(ValidatorTest, AssignThroughPointerWrongeStoreType_Fail) {
// var a :f32;
// const b : ptr<function,f32> = a;
// b = 2;
const auto priv = ast::StorageClass::kFunction;
auto* var_a = Var("a", ty.f32(), priv, Expr(2), {});
auto* var_b = Const("b", ty.pointer<float>(priv), Expr("a"), {});
RegisterVariable(var_a);
RegisterVariable(var_b);
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_FALSE(v.ValidateAssign(assign));
EXPECT_EQ(v.error(),
"12:34 v-000x: invalid assignment: can't assign value of type "
"'__i32' to '__f32'");
}
TEST_F(ValidatorTest, AssignCompatibleTypesInBlockStatement_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* 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_TRUE(v.ValidateStatements(body)) << v.error();
}
TEST_F(ValidatorTest, AssignIncompatibleTypesInBlockStatement_Fail) {
// {
// var a :i32 = 2;
// a = 2.3;
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2.3f);
auto* block = create<ast::BlockStatement>(ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Source{Source::Location{12, 34}}, lhs,
rhs),
});
WrapInFunction(block);
ValidatorImpl& v = Build();
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_FALSE(v.ValidateStatements(block));
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: can't assign value of type "
"'__f32' to '__i32'");
}
TEST_F(ValidatorTest, AssignIncompatibleTypesInNestedBlockStatement_Fail) {
// {
// {
// var a :i32 = 2;
// a = 2.3;
// }
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* lhs = Expr("a");
auto* rhs = Expr(2.3f);
auto* inner_block = create<ast::BlockStatement>(ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Source{Source::Location{12, 34}}, lhs,
rhs),
});
auto* outer_block = create<ast::BlockStatement>(ast::StatementList{
inner_block,
});
WrapInFunction(outer_block);
ValidatorImpl& v = Build();
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
EXPECT_FALSE(v.ValidateStatements(outer_block));
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: can't assign value of type "
"'__f32' to '__i32'");
}
TEST_F(ValidatorTest, GlobalVariableWithStorageClass_Pass) {
// var<in> global_var: f32;
auto* var = Global(Source{Source::Location{12, 34}}, "global_var", ty.f32(),

View File

@ -172,7 +172,7 @@ OpStore %7 %6
TEST_F(BuilderTest, FunctionVar_Const) {
auto* init = vec3<f32>(1.f, 1.f, 3.f);
auto* v = Const("var", ty.f32(), init);
auto* v = Const("var", ty.vec3<f32>(), init);
WrapInFunction(v);

View File

@ -69,7 +69,7 @@ TEST_F(BuilderTest, IdentifierExpression_GlobalVar) {
TEST_F(BuilderTest, IdentifierExpression_FunctionConst) {
auto* init = vec3<f32>(1.f, 1.f, 3.f);
auto* v = Const("var", ty.f32(), init);
auto* v = Const("var", ty.vec3<f32>(), init);
auto* expr = Expr("var");
WrapInFunction(v, expr);

View File

@ -169,6 +169,7 @@ source_set("tint_unittests_core_src") {
"../src/intrinsic_table_test.cc",
"../src/program_builder_test.cc",
"../src/program_test.cc",
"../src/resolver/assignment_validation_test.cc",
"../src/resolver/decoration_validation_test.cc",
"../src/resolver/host_shareable_validation_test.cc",
"../src/resolver/intrinsic_test.cc",