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:
parent
25eef8d2cf
commit
6b2fc057c5
|
@ -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
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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,18 +1187,17 @@ 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) {
|
||||
auto* lhs_type = stmt->variable()->type();
|
||||
auto* rhs_type = TypeOf(ctor);
|
||||
if (!IsValidAssignment(lhs_type, rhs_type)) {
|
||||
diagnostics_.add_error(
|
||||
"constructor expression type does not match variable type",
|
||||
"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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto* var = stmt->variable();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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{
|
||||
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>());
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)];
|
||||
}
|
||||
)";
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
)";
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue