sem: Add CompoundStatement

This change introduces sem::CompoundStatement, a new base class for
statements that can hold other statements.

sem::BlockStatements now derives from sem::CompoundStatement, and
this change introduces the following new CompoundStatements:
* `sem::IfStatement`
* `sem::ElseStatement`
* `sem::ForLoopStatement`
* `sem::LoopStatement`
* `sem::SwitchStatement`.
These new CompoundStatements are now inserted into the semantic
tree as now documented in `docs/compound_statements.md`.

The `sem::BlockStatement::FindFirstParent()` methods have been
moved down to `sem::Statement`.

The `Resolver::BlockScope()` method has been replaced with
`Resolver::Scope()` which now maintains the `current_statement_`,
`current_compound_statement_ ` and `current_block_`. This
simplifies statement nesting.

The most significant change in behavior is that statements now
always have a parent, so calling Block() on the initializer or
continuing of a for-loop statement will now return the
BlockStatement that holds the for-loop. Before this would
return nullptr.

Fixed: tint:979
Change-Id: I90e38fd719da2a281ed9210e975ab96171cb6842
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57707
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
This commit is contained in:
Ben Clayton 2021-07-14 09:44:41 +00:00 committed by Tint LUCI CQ
parent 3e27a20f31
commit 6e459fecb7
20 changed files with 1229 additions and 462 deletions

115
docs/compound_statements.md Normal file
View File

@ -0,0 +1,115 @@
# Compound Statements
Compound statements are statements that can hold other statements.
This document maps the WGSL compound statements to their semantic tree representations.
## if statement
WGSL:
```
if (condition_a) {
statement_a;
} else if (condition_b) {
statement_b;
} else {
statement_c;
}
```
Semantic tree:
```
sem::IfStatement {
condition_a
sem::BlockStatement {
statement_a
}
sem::ElseStatement {
condition_b
sem::BlockStatement {
statement_b
}
}
sem::ElseStatement {
sem::BlockStatement {
statement_c
}
}
}
```
## for loop
WGSL:
```
for (initializer; condition; continuing) {
statement;
}
```
Semantic tree:
```
sem::ForLoopStatement {
sem::Statement initializer
sem::Expression condition
sem::Statement continuing
sem::LoopBlockStatement {
sem::Statement statement
}
}
```
## loop
WGSL:
```
loop (condition) {
statement_a;
continuing {
statement_b;
}
}
```
Semantic tree:
```
sem::LoopStatement {
sem::Expression condition
sem::LoopBlockStatement {
sem::Statement statement_a
sem::LoopContinuingBlockStatement {
sem::Statement statement_b
}
}
}
```
## switch statement
WGSL:
```
switch (condition) {
case literal_a, literal_b: {
statement_a;
}
default {
statement_b;
}
}
```
Semantic tree:
```
sem::SwitchStatement {
sem::Expression condition
sem::SwitchCaseBlockStatement {
sem::Statement statement_a
}
sem::SwitchCaseBlockStatement {
sem::Statement statement_b
}
}
```

View File

@ -497,58 +497,40 @@ libtint_source_set("libtint_core_all_src") {
"resolver/resolver.h",
"scope_stack.h",
"sem/array.h",
"sem/atomic_type.cc",
"sem/atomic_type.h",
"sem/binding_point.h",
"sem/bool_type.cc",
"sem/bool_type.h",
"sem/call.h",
"sem/call_target.h",
"sem/constant.cc",
"sem/constant.h",
"sem/depth_texture_type.cc",
"sem/depth_texture_type.h",
"sem/expression.h",
"sem/external_texture_type.cc",
"sem/external_texture_type.h",
"sem/f32_type.cc",
"sem/f32_type.h",
"sem/i32_type.cc",
"sem/for_loop_statement.h",
"sem/i32_type.h",
"sem/if_statement.h",
"sem/info.h",
"sem/intrinsic.h",
"sem/intrinsic_type.cc",
"sem/intrinsic_type.h",
"sem/matrix_type.cc",
"sem/loop_statement.h",
"sem/matrix_type.h",
"sem/multisampled_texture_type.cc",
"sem/multisampled_texture_type.h",
"sem/node.h",
"sem/parameter_usage.cc",
"sem/parameter_usage.h",
"sem/pipeline_stage_set.h",
"sem/pointer_type.cc",
"sem/pointer_type.h",
"sem/reference_type.cc",
"sem/reference_type.h",
"sem/sampled_texture_type.cc",
"sem/sampled_texture_type.h",
"sem/sampler_type.cc",
"sem/sampler_type.h",
"sem/storage_texture_type.cc",
"sem/storage_texture_type.h",
"sem/texture_type.cc",
"sem/switch_statement.h",
"sem/texture_type.h",
"sem/type.cc",
"sem/type.h",
"sem/type_manager.cc",
"sem/type_manager.h",
"sem/type_mappings.h",
"sem/u32_type.cc",
"sem/u32_type.h",
"sem/vector_type.cc",
"sem/vector_type.h",
"sem/void_type.cc",
"sem/void_type.h",
"source.cc",
"source.h",
@ -633,18 +615,80 @@ libtint_source_set("libtint_core_all_src") {
libtint_source_set("libtint_sem_src") {
sources = [
"sem/array.cc",
"sem/array.h",
"sem/atomic_type.cc",
"sem/atomic_type.h",
"sem/binding_point.h",
"sem/block_statement.cc",
"sem/bool_type.cc",
"sem/bool_type.h",
"sem/call.cc",
"sem/call.h",
"sem/call_target.cc",
"sem/call_target.h",
"sem/constant.cc",
"sem/constant.h",
"sem/depth_texture_type.cc",
"sem/depth_texture_type.h",
"sem/expression.cc",
"sem/expression.h",
"sem/external_texture_type.cc",
"sem/external_texture_type.h",
"sem/f32_type.cc",
"sem/f32_type.h",
"sem/for_loop_statement.cc",
"sem/for_loop_statement.h",
"sem/function.cc",
"sem/i32_type.cc",
"sem/i32_type.h",
"sem/if_statement.cc",
"sem/if_statement.h",
"sem/info.cc",
"sem/info.h",
"sem/intrinsic.cc",
"sem/intrinsic.h",
"sem/intrinsic_type.cc",
"sem/intrinsic_type.h",
"sem/loop_statement.cc",
"sem/loop_statement.h",
"sem/matrix_type.cc",
"sem/matrix_type.h",
"sem/member_accessor_expression.cc",
"sem/multisampled_texture_type.cc",
"sem/multisampled_texture_type.h",
"sem/node.cc",
"sem/node.h",
"sem/parameter_usage.cc",
"sem/parameter_usage.h",
"sem/pipeline_stage_set.h",
"sem/pointer_type.cc",
"sem/pointer_type.h",
"sem/reference_type.cc",
"sem/reference_type.h",
"sem/sampled_texture_type.cc",
"sem/sampled_texture_type.h",
"sem/sampler_type.cc",
"sem/sampler_type.h",
"sem/statement.cc",
"sem/storage_texture_type.cc",
"sem/storage_texture_type.h",
"sem/struct.cc",
"sem/switch_statement.cc",
"sem/switch_statement.h",
"sem/texture_type.cc",
"sem/texture_type.h",
"sem/type.cc",
"sem/type.h",
"sem/type_manager.cc",
"sem/type_manager.h",
"sem/type_mappings.h",
"sem/u32_type.cc",
"sem/u32_type.h",
"sem/variable.cc",
"sem/vector_type.cc",
"sem/vector_type.h",
"sem/void_type.cc",
"sem/void_type.h",
]
public_deps = [ ":libtint_core_all_src" ]

View File

@ -336,8 +336,14 @@ set(TINT_LIB_SRCS
sem/external_texture_type.h
sem/f32_type.cc
sem/f32_type.h
sem/for_loop_statement.cc
sem/for_loop_statement.h
sem/i32_type.cc
sem/i32_type.h
sem/if_statement.cc
sem/if_statement.h
sem/loop_statement.cc
sem/loop_statement.h
sem/matrix_type.cc
sem/matrix_type.h
sem/multisampled_texture_type.cc
@ -352,6 +358,8 @@ set(TINT_LIB_SRCS
sem/sampler_type.h
sem/storage_texture_type.cc
sem/storage_texture_type.h
sem/switch_statement.cc
sem/switch_statement.h
sem/texture_type.cc
sem/texture_type.h
sem/type.cc
@ -622,10 +630,10 @@ if(${TINT_BUILD_TESTS})
resolver/assignment_validation_test.cc
resolver/atomics_test.cc
resolver/atomics_validation_test.cc
resolver/block_test.cc
resolver/builtins_validation_test.cc
resolver/call_test.cc
resolver/call_validation_test.cc
resolver/compound_statement_test.cc
resolver/control_block_validation_test.cc
resolver/decoration_validation_test.cc
resolver/entry_point_validation_test.cc

View File

@ -1,166 +0,0 @@
// 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"
#include "src/sem/block_statement.h"
namespace tint {
namespace resolver {
namespace {
using ResolverBlockTest = ResolverTest;
TEST_F(ResolverBlockTest, FunctionBlock) {
// fn F() {
// var x : 32;
// }
auto* stmt = Decl(Var("x", ty.i32()));
auto* f = Func("F", {}, ty.void_(), {stmt});
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
ASSERT_TRUE(s->Block()->Is<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()->As<sem::FunctionBlockStatement>()->Function(), f);
EXPECT_EQ(s->Block()->Parent(), nullptr);
}
TEST_F(ResolverBlockTest, Block) {
// fn F() {
// {
// var x : 32;
// }
// }
auto* stmt = Decl(Var("x", ty.i32()));
auto* block = Block(stmt);
auto* f = Func("F", {}, ty.void_(), {block});
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Block()->Parent(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
ASSERT_TRUE(s->Block()->Parent()->Is<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()->Parent()->As<sem::FunctionBlockStatement>()->Function(),
f);
EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr);
}
TEST_F(ResolverBlockTest, LoopBlock) {
// fn F() {
// loop {
// var x : 32;
// }
// }
auto* stmt = Decl(Var("x", ty.i32()));
auto* loop = Loop(Block(stmt));
auto* f = Func("F", {}, ty.void_(), {loop});
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(), s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
EXPECT_EQ(s->Block()->Parent()->Parent(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()
->Parent()
->Parent()
->As<sem::FunctionBlockStatement>()
->Function(),
f);
EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
}
TEST_F(ResolverBlockTest, ForLoopBlock) {
// fn F() {
// for (var i : u32; true; i = i + 1u) {
// return;
// }
// }
auto* init = Decl(Var("i", ty.u32()));
auto* cond = Expr(true);
auto* cont = Assign("i", Add("i", 1u));
auto* stmt = Return();
auto* body = Block(stmt);
auto* for_ = For(init, cond, cont, body);
auto* f = Func("F", {}, ty.void_(), {for_});
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(init);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
}
{ // Condition expression's statement is the for-loop itself
auto* s = Sem().Get(cond);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Stmt()->Block(), nullptr);
EXPECT_EQ(
s->Stmt()->Block(),
s->Stmt()->Block()->FindFirstParent<sem::FunctionBlockStatement>());
ASSERT_TRUE(Is<sem::FunctionBlockStatement>(s->Stmt()->Block()));
}
{
auto* s = Sem().Get(cont);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
}
{
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(),
s->Block()->FindFirstParent<sem::LoopBlockStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
EXPECT_EQ(s->Block()->Parent()->Parent(),
s->Block()->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()
->Parent()
->Parent()
->As<sem::FunctionBlockStatement>()
->Function(),
f);
EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
}
}
// TODO(bclayton): Add tests for other block types
// (LoopContinuingBlockStatement, SwitchCaseBlockStatement)
} // namespace
} // namespace resolver
} // namespace tint

View File

@ -0,0 +1,384 @@
// 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"
#include "src/sem/block_statement.h"
#include "src/sem/for_loop_statement.h"
#include "src/sem/if_statement.h"
#include "src/sem/loop_statement.h"
#include "src/sem/switch_statement.h"
namespace tint {
namespace resolver {
namespace {
using ResolverCompoundStatementTest = ResolverTest;
TEST_F(ResolverCompoundStatementTest, FunctionBlock) {
// fn F() {
// var x : 32;
// }
auto* stmt = Decl(Var("x", ty.i32()));
auto* f = Func("F", {}, ty.void_(), {stmt});
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
ASSERT_TRUE(s->Block()->Is<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()->As<sem::FunctionBlockStatement>()->Function(), f);
EXPECT_EQ(s->Block()->Parent(), nullptr);
}
TEST_F(ResolverCompoundStatementTest, Block) {
// fn F() {
// {
// var x : 32;
// }
// }
auto* stmt = Decl(Var("x", ty.i32()));
auto* block = Block(stmt);
auto* f = Func("F", {}, ty.void_(), {block});
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(block);
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::BlockStatement>());
EXPECT_EQ(s, s->Block());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
}
{
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Block()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
ASSERT_TRUE(s->Block()->Parent()->Is<sem::FunctionBlockStatement>());
EXPECT_EQ(
s->Block()->Parent()->As<sem::FunctionBlockStatement>()->Function(), f);
EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr);
}
}
TEST_F(ResolverCompoundStatementTest, Loop) {
// fn F() {
// loop {
// stmt_a;
// continuing {
// stmt_b;
// }
// }
// }
auto* stmt_a = Ignore(1);
auto* stmt_b = Ignore(1);
auto* loop = Loop(Block(stmt_a), Block(stmt_b));
auto* f = Func("F", {}, ty.void_(), {loop});
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(loop);
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::LoopStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
}
{
auto* s = Sem().Get(stmt_a);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::LoopBlockStatement>());
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::LoopStatement>());
EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()));
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_TRUE(
Is<sem::FunctionBlockStatement>(s->Parent()->Parent()->Parent()));
EXPECT_EQ(s->FindFirstParent<sem::FunctionBlockStatement>()->Function(), f);
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), nullptr);
}
{
auto* s = Sem().Get(stmt_b);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent(),
s->FindFirstParent<sem::LoopContinuingBlockStatement>());
EXPECT_TRUE(Is<sem::LoopContinuingBlockStatement>(s->Parent()));
EXPECT_EQ(s->Parent()->Parent(),
s->FindFirstParent<sem::LoopBlockStatement>());
EXPECT_TRUE(Is<sem::LoopBlockStatement>(s->Parent()->Parent()));
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::LoopStatement>());
EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()->Parent()));
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(
s->Parent()->Parent()->Parent()->Parent()));
EXPECT_EQ(s->FindFirstParent<sem::FunctionBlockStatement>()->Function(), f);
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent()->Parent(), nullptr);
}
}
TEST_F(ResolverCompoundStatementTest, ForLoop) {
// fn F() {
// for (var i : u32; true; i = i + 1u) {
// return;
// }
// }
auto* init = Decl(Var("i", ty.u32()));
auto* cond = Expr(true);
auto* cont = Assign("i", Add("i", 1u));
auto* stmt = Return();
auto* body = Block(stmt);
auto* for_ = For(init, cond, cont, body);
auto* f = Func("F", {}, ty.void_(), {for_});
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(for_);
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::ForLoopStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
}
{
auto* s = Sem().Get(init);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
}
{ // Condition expression's statement is the for-loop itself
auto* e = Sem().Get(cond);
ASSERT_NE(e, nullptr);
auto* s = e->Stmt();
ASSERT_NE(s, nullptr);
ASSERT_TRUE(Is<sem::ForLoopStatement>(s));
ASSERT_NE(s->Parent(), nullptr);
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()));
}
{
auto* s = Sem().Get(cont);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
}
{
auto* s = Sem().Get(stmt);
ASSERT_NE(s, nullptr);
ASSERT_NE(s->Block(), nullptr);
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::LoopBlockStatement>());
EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()->Parent()));
EXPECT_EQ(s->Block()->Parent(),
s->FindFirstParent<sem::ForLoopStatement>());
ASSERT_TRUE(
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
EXPECT_EQ(s->Block()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Block()
->Parent()
->Parent()
->As<sem::FunctionBlockStatement>()
->Function(),
f);
EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
}
}
TEST_F(ResolverCompoundStatementTest, If) {
// fn F() {
// if (cond_a) {
// stat_a;
// } elseif (cond_b) {
// stat_b;
// } else {
// stat_c;
// }
// }
auto* cond_a = Expr(true);
auto* stmt_a = Ignore(1);
auto* cond_b = Expr(true);
auto* stmt_b = Ignore(1);
auto* stmt_c = Ignore(1);
auto* if_stmt = If(cond_a, Block(stmt_a), Else(cond_b, Block(stmt_b)),
Else(nullptr, Block(stmt_c)));
WrapInFunction(if_stmt);
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(if_stmt);
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::IfStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
}
{
auto* e = Sem().Get(cond_a);
ASSERT_NE(e, nullptr);
auto* s = e->Stmt();
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::IfStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
}
{
auto* s = Sem().Get(stmt_a);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::IfStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
}
{
auto* e = Sem().Get(cond_b);
ASSERT_NE(e, nullptr);
auto* s = e->Stmt();
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::ElseStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::IfStatement>());
EXPECT_EQ(s->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent()->Parent(), s->Block());
}
{
auto* s = Sem().Get(stmt_b);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::IfStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
}
{
auto* s = Sem().Get(stmt_c);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::IfStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
}
}
TEST_F(ResolverCompoundStatementTest, Switch) {
// fn F() {
// switch (expr) {
// case 1: {
// stmt_a;
// }
// case 2: {
// stmt_b;
// }
// default: {
// stmt_c;
// }
// }
// }
auto* expr = Expr(5);
auto* stmt_a = Ignore(1);
auto* stmt_b = Ignore(1);
auto* stmt_c = Ignore(1);
auto* swi =
Switch(expr, Case(Literal(1), Block(stmt_a)),
Case(Literal(2), Block(stmt_b)), DefaultCase(Block(stmt_c)));
WrapInFunction(swi);
ASSERT_TRUE(r()->Resolve()) << r()->error();
{
auto* s = Sem().Get(swi);
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::SwitchStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
}
{
auto* e = Sem().Get(expr);
ASSERT_NE(e, nullptr);
auto* s = e->Stmt();
ASSERT_NE(s, nullptr);
EXPECT_TRUE(s->Is<sem::SwitchStatement>());
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
}
{
auto* s = Sem().Get(stmt_a);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::SwitchCaseBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent()->Parent(),
s->FindFirstParent<sem::SwitchStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
}
{
auto* s = Sem().Get(stmt_b);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::SwitchCaseBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent()->Parent(),
s->FindFirstParent<sem::SwitchStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
}
{
auto* s = Sem().Get(stmt_c);
ASSERT_NE(s, nullptr);
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::SwitchCaseBlockStatement>());
EXPECT_EQ(s->Parent(), s->Block());
EXPECT_EQ(s->Parent()->Parent(),
s->FindFirstParent<sem::SwitchStatement>());
EXPECT_EQ(s->Parent()->Parent()->Parent(),
s->FindFirstParent<sem::FunctionBlockStatement>());
}
}
} // namespace
} // namespace resolver
} // namespace tint

View File

@ -51,7 +51,10 @@
#include "src/sem/atomic_type.h"
#include "src/sem/call.h"
#include "src/sem/depth_texture_type.h"
#include "src/sem/for_loop_statement.h"
#include "src/sem/function.h"
#include "src/sem/if_statement.h"
#include "src/sem/loop_statement.h"
#include "src/sem/member_accessor_expression.h"
#include "src/sem/multisampled_texture_type.h"
#include "src/sem/pointer_type.h"
@ -61,6 +64,7 @@
#include "src/sem/statement.h"
#include "src/sem/storage_texture_type.h"
#include "src/sem/struct.h"
#include "src/sem/switch_statement.h"
#include "src/sem/variable.h"
#include "src/utils/defer.h"
#include "src/utils/get_or_create.h"
@ -1568,16 +1572,14 @@ bool Resolver::Function(ast::Function* func) {
if (func->body()) {
Mark(func->body());
if (current_statement_) {
if (current_compound_statement_) {
TINT_ICE(Resolver, diagnostics_)
<< "Resolver::Function() called with a current statement";
<< "Resolver::Function() called with a current compound statement";
return false;
}
auto* sem_block = builder_->create<sem::FunctionBlockStatement>(func);
builder_->Sem().Add(func->body(), sem_block);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
if (!BlockScope(func->body(),
[&] { return Statements(func->body()->list()); })) {
if (!Scope(sem_block, [&] { return Statements(func->body()->list()); })) {
return false;
}
}
@ -1725,17 +1727,11 @@ bool Resolver::ValidateStatements(const ast::StatementList& stmts) {
}
bool Resolver::Statement(ast::Statement* stmt) {
sem::Statement* sem_statement;
if (stmt->As<ast::BlockStatement>()) {
sem_statement = builder_->create<sem::BlockStatement>(
stmt->As<ast::BlockStatement>(), current_statement_);
} else {
sem_statement = builder_->create<sem::Statement>(stmt, current_statement_);
if (stmt->Is<ast::CaseStatement>()) {
AddError("case statement can only be used inside a switch statement",
stmt->source());
return false;
}
builder_->Sem().Add(stmt, sem_statement);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement);
if (stmt->Is<ast::ElseStatement>()) {
TINT_ICE(Resolver, diagnostics_)
<< "Resolver::Statement() encountered an Else statement. Else "
@ -1744,15 +1740,35 @@ bool Resolver::Statement(ast::Statement* stmt) {
return false;
}
// Compound statements. These create their own sem::CompoundStatement
// bindings.
if (auto* b = stmt->As<ast::BlockStatement>()) {
return BlockStatement(b);
}
if (auto* l = stmt->As<ast::ForLoopStatement>()) {
return ForLoopStatement(l);
}
if (auto* l = stmt->As<ast::LoopStatement>()) {
return LoopStatement(l);
}
if (auto* i = stmt->As<ast::IfStatement>()) {
return IfStatement(i);
}
if (auto* s = stmt->As<ast::SwitchStatement>()) {
return SwitchStatement(s);
}
// Non-Compound statements
sem::Statement* sem_statement =
builder_->create<sem::Statement>(stmt, current_compound_statement_);
builder_->Sem().Add(stmt, sem_statement);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement);
if (auto* a = stmt->As<ast::AssignmentStatement>()) {
return Assignment(a);
}
if (auto* b = stmt->As<ast::BlockStatement>()) {
return BlockScope(b, [&] { return Statements(b->list()); });
}
if (stmt->Is<ast::BreakStatement>()) {
if (!current_block_->FindFirstParent<sem::LoopBlockStatement>() &&
!current_block_->FindFirstParent<sem::SwitchCaseBlockStatement>()) {
if (!sem_statement->FindFirstParent<sem::LoopBlockStatement>() &&
!sem_statement->FindFirstParent<sem::SwitchCaseBlockStatement>()) {
AddError("break statement must be in a loop or switch case",
stmt->source());
return false;
@ -1769,9 +1785,6 @@ bool Resolver::Statement(ast::Statement* stmt) {
}
return true;
}
if (auto* c = stmt->As<ast::CaseStatement>()) {
return CaseStatement(c);
}
if (stmt->Is<ast::ContinueStatement>()) {
// Set if we've hit the first continue statement in our parent loop
if (auto* loop_block =
@ -1793,21 +1806,9 @@ bool Resolver::Statement(ast::Statement* stmt) {
if (stmt->Is<ast::FallthroughStatement>()) {
return true;
}
if (auto* i = stmt->As<ast::IfStatement>()) {
return IfStatement(i);
}
if (auto* l = stmt->As<ast::LoopStatement>()) {
return LoopStatement(l);
}
if (auto* l = stmt->As<ast::ForLoopStatement>()) {
return ForLoopStatement(l);
}
if (auto* r = stmt->As<ast::ReturnStatement>()) {
return Return(r);
}
if (auto* s = stmt->As<ast::SwitchStatement>()) {
return Switch(s);
}
if (auto* v = stmt->As<ast::VariableDeclStatement>()) {
return VariableDeclStatement(v);
}
@ -1819,51 +1820,59 @@ bool Resolver::Statement(ast::Statement* stmt) {
}
bool Resolver::CaseStatement(ast::CaseStatement* stmt) {
auto* sem = builder_->create<sem::SwitchCaseBlockStatement>(
stmt->body(), current_compound_statement_);
builder_->Sem().Add(stmt, sem);
builder_->Sem().Add(stmt->body(), sem);
Mark(stmt->body());
for (auto* sel : stmt->selectors()) {
Mark(sel);
}
auto* sem_block = builder_->create<sem::SwitchCaseBlockStatement>(
stmt->body(), current_statement_);
builder_->Sem().Add(stmt->body(), sem_block);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
return BlockScope(stmt->body(),
[&] { return Statements(stmt->body()->list()); });
return Scope(sem, [&] { return Statements(stmt->body()->list()); });
}
bool Resolver::IfStatement(ast::IfStatement* stmt) {
Mark(stmt->condition());
if (!Expression(stmt->condition())) {
return false;
}
auto* cond_type = TypeOf(stmt->condition())->UnwrapRef();
if (!cond_type->Is<sem::Bool>()) {
AddError("if statement condition must be bool, got " +
cond_type->FriendlyName(builder_->Symbols()),
stmt->condition()->source());
return false;
}
Mark(stmt->body());
{
auto* sem_block =
builder_->create<sem::BlockStatement>(stmt->body(), current_statement_);
builder_->Sem().Add(stmt->body(), sem_block);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
if (!BlockScope(stmt->body(),
[&] { return Statements(stmt->body()->list()); })) {
auto* sem =
builder_->create<sem::IfStatement>(stmt, current_compound_statement_);
builder_->Sem().Add(stmt, sem);
return Scope(sem, [&] {
Mark(stmt->condition());
if (!Expression(stmt->condition())) {
return false;
}
}
for (auto* else_stmt : stmt->else_statements()) {
Mark(else_stmt);
auto* sem_else_stmt =
builder_->create<sem::Statement>(else_stmt, current_statement_);
builder_->Sem().Add(else_stmt, sem_else_stmt);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_else_stmt);
if (auto* cond = else_stmt->condition()) {
auto* cond_type = TypeOf(stmt->condition())->UnwrapRef();
if (!cond_type->Is<sem::Bool>()) {
AddError("if statement condition must be bool, got " +
cond_type->FriendlyName(builder_->Symbols()),
stmt->condition()->source());
return false;
}
Mark(stmt->body());
auto* body = builder_->create<sem::BlockStatement>(
stmt->body(), current_compound_statement_);
builder_->Sem().Add(stmt->body(), body);
if (!Scope(body, [&] { return Statements(stmt->body()->list()); })) {
return false;
}
for (auto* else_stmt : stmt->else_statements()) {
Mark(else_stmt);
if (!ElseStatement(else_stmt)) {
return false;
}
}
return true;
});
}
bool Resolver::ElseStatement(ast::ElseStatement* stmt) {
auto* sem =
builder_->create<sem::ElseStatement>(stmt, current_compound_statement_);
builder_->Sem().Add(stmt, sem);
return Scope(sem, [&] {
if (auto* cond = stmt->condition()) {
Mark(cond);
if (!Expression(cond)) {
return false;
@ -1877,95 +1886,93 @@ bool Resolver::IfStatement(ast::IfStatement* stmt) {
return false;
}
}
Mark(else_stmt->body());
{
auto* sem_block = builder_->create<sem::BlockStatement>(
else_stmt->body(), current_statement_);
builder_->Sem().Add(else_stmt->body(), sem_block);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block);
if (!BlockScope(else_stmt->body(),
[&] { return Statements(else_stmt->body()->list()); })) {
return false;
}
}
}
return true;
Mark(stmt->body());
auto* body = builder_->create<sem::BlockStatement>(
stmt->body(), current_compound_statement_);
builder_->Sem().Add(stmt->body(), body);
return Scope(body, [&] { return Statements(stmt->body()->list()); });
});
}
bool Resolver::BlockStatement(ast::BlockStatement* stmt) {
auto* sem = builder_->create<sem::BlockStatement>(
stmt->As<ast::BlockStatement>(), current_compound_statement_);
builder_->Sem().Add(stmt, sem);
return Scope(sem, [&] { return Statements(stmt->list()); });
}
bool Resolver::LoopStatement(ast::LoopStatement* stmt) {
// We don't call DetermineBlockStatement on the body and continuing block as
// these would make their BlockInfo siblings as in the AST, but we want the
// body BlockInfo to parent the continuing BlockInfo for semantics and
// validation. Also, we need to set their types differently.
Mark(stmt->body());
auto* sem =
builder_->create<sem::LoopStatement>(stmt, current_compound_statement_);
builder_->Sem().Add(stmt, sem);
return Scope(sem, [&] {
Mark(stmt->body());
auto* sem_block_body = builder_->create<sem::LoopBlockStatement>(
stmt->body(), current_statement_);
builder_->Sem().Add(stmt->body(), sem_block_body);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body);
return BlockScope(stmt->body(), [&] {
if (!Statements(stmt->body()->list())) {
return false;
}
if (stmt->continuing()) { // has_continuing() also checks for empty()
Mark(stmt->continuing());
}
if (stmt->has_continuing()) {
auto* sem_block_continuing =
builder_->create<sem::LoopContinuingBlockStatement>(
stmt->continuing(), current_statement_);
builder_->Sem().Add(stmt->continuing(), sem_block_continuing);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_continuing);
if (!BlockScope(stmt->continuing(),
[&] { return Statements(stmt->continuing()->list()); })) {
auto* body = builder_->create<sem::LoopBlockStatement>(
stmt->body(), current_compound_statement_);
builder_->Sem().Add(stmt->body(), body);
return Scope(body, [&] {
if (!Statements(stmt->body()->list())) {
return false;
}
}
return true;
if (stmt->continuing()) { // has_continuing() also checks for empty()
Mark(stmt->continuing());
}
if (stmt->has_continuing()) {
auto* continuing = builder_->create<sem::LoopContinuingBlockStatement>(
stmt->continuing(), current_compound_statement_);
builder_->Sem().Add(stmt->continuing(), continuing);
if (!Scope(continuing,
[&] { return Statements(stmt->continuing()->list()); })) {
return false;
}
}
return true;
});
});
}
bool Resolver::ForLoopStatement(ast::ForLoopStatement* stmt) {
Mark(stmt->body());
auto* sem_block_body = builder_->create<sem::LoopBlockStatement>(
stmt->body(), current_statement_);
builder_->Sem().Add(stmt->body(), sem_block_body);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_block_body);
TINT_SCOPED_ASSIGNMENT(current_block_, sem_block_body);
variable_stack_.push_scope();
TINT_DEFER(variable_stack_.pop_scope());
if (auto* initializer = stmt->initializer()) {
Mark(initializer);
if (!Statement(initializer)) {
return false;
}
}
if (auto* condition = stmt->condition()) {
Mark(condition);
if (!Expression(condition)) {
return false;
auto* sem = builder_->create<sem::ForLoopStatement>(
stmt, current_compound_statement_);
builder_->Sem().Add(stmt, sem);
return Scope(sem, [&] {
if (auto* initializer = stmt->initializer()) {
Mark(initializer);
if (!Statement(initializer)) {
return false;
}
}
if (!TypeOf(condition)->Is<sem::Bool>()) {
AddError("for-loop condition must be bool, got " + TypeNameOf(condition),
condition->source());
return false;
}
}
if (auto* condition = stmt->condition()) {
Mark(condition);
if (!Expression(condition)) {
return false;
}
if (auto* continuing = stmt->continuing()) {
Mark(continuing);
if (!Statement(continuing)) {
return false;
if (!TypeOf(condition)->Is<sem::Bool>()) {
AddError(
"for-loop condition must be bool, got " + TypeNameOf(condition),
condition->source());
return false;
}
}
}
return BlockScope(stmt->body(),
[&] { return Statements(stmt->body()->list()); });
if (auto* continuing = stmt->continuing()) {
Mark(continuing);
if (!Statement(continuing)) {
return false;
}
}
Mark(stmt->body());
auto* body = builder_->create<sem::LoopBlockStatement>(
stmt->body(), current_compound_statement_);
builder_->Sem().Add(stmt->body(), body);
return Scope(body, [&] { return Statements(stmt->body()->statements()); });
});
}
bool Resolver::Expressions(const ast::ExpressionList& list) {
@ -3036,7 +3043,9 @@ bool Resolver::VariableDeclStatement(const ast::VariableDeclStatement* stmt) {
}
variable_stack_.set(var->symbol(), info);
current_block_->AddDecl(var);
if (current_block_) { // Not all statements are inside a block
current_block_->AddDecl(var);
}
if (!ValidateVariable(info)) {
return false;
@ -3858,26 +3867,26 @@ bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) {
return true;
}
bool Resolver::Switch(ast::SwitchStatement* s) {
Mark(s->condition());
if (!Expression(s->condition())) {
return false;
}
for (auto* case_stmt : s->body()) {
Mark(case_stmt);
sem::Statement* sem_statement =
builder_->create<sem::Statement>(case_stmt, current_statement_);
builder_->Sem().Add(case_stmt, sem_statement);
TINT_SCOPED_ASSIGNMENT(current_statement_, sem_statement);
if (!CaseStatement(case_stmt)) {
bool Resolver::SwitchStatement(ast::SwitchStatement* stmt) {
auto* sem =
builder_->create<sem::SwitchStatement>(stmt, current_compound_statement_);
builder_->Sem().Add(stmt, sem);
return Scope(sem, [&] {
Mark(stmt->condition());
if (!Expression(stmt->condition())) {
return false;
}
}
if (!ValidateSwitch(s)) {
return false;
}
return true;
for (auto* case_stmt : stmt->body()) {
Mark(case_stmt);
if (!CaseStatement(case_stmt)) {
return false;
}
}
if (!ValidateSwitch(stmt)) {
return false;
}
return true;
});
}
bool Resolver::Assignment(ast::AssignmentStatement* a) {
@ -4032,18 +4041,22 @@ bool Resolver::ApplyStorageClassUsageToType(ast::StorageClass sc,
}
template <typename F>
bool Resolver::BlockScope(const ast::BlockStatement* block, F&& callback) {
auto* sem_block = builder_->Sem().Get<sem::BlockStatement>(block);
if (!sem_block) {
TINT_ICE(Resolver, diagnostics_)
<< "Resolver::BlockScope() called on a block for "
"which semantic information is not available";
return false;
}
TINT_SCOPED_ASSIGNMENT(current_block_,
const_cast<sem::BlockStatement*>(sem_block));
bool Resolver::Scope(sem::CompoundStatement* stmt, F&& callback) {
auto* prev_current_statement = current_statement_;
auto* prev_current_compound_statement = current_compound_statement_;
auto* prev_current_block = current_block_;
current_statement_ = stmt;
current_compound_statement_ = stmt;
current_block_ = stmt->As<sem::BlockStatement>();
variable_stack_.push_scope();
TINT_DEFER(variable_stack_.pop_scope());
TINT_DEFER({
TINT_DEFER(variable_stack_.pop_scope());
current_block_ = prev_current_block;
current_compound_statement_ = prev_current_compound_statement;
current_statement_ = prev_current_statement;
});
return callback();
}

View File

@ -242,9 +242,11 @@ class Resolver {
bool Assignment(ast::AssignmentStatement* a);
bool Binary(ast::BinaryExpression*);
bool Bitcast(ast::BitcastExpression*);
bool BlockStatement(ast::BlockStatement*);
bool Call(ast::CallExpression*);
bool CaseStatement(ast::CaseStatement*);
bool Constructor(ast::ConstructorExpression*);
bool ElseStatement(ast::ElseStatement*);
bool Expression(ast::Expression*);
bool Expressions(const ast::ExpressionList&);
bool ForLoopStatement(ast::ForLoopStatement*);
@ -260,7 +262,7 @@ class Resolver {
bool Return(ast::ReturnStatement* ret);
bool Statement(ast::Statement*);
bool Statements(const ast::StatementList&);
bool Switch(ast::SwitchStatement* s);
bool SwitchStatement(ast::SwitchStatement* s);
bool UnaryOp(ast::UnaryOpExpression*);
bool VariableDeclStatement(const ast::VariableDeclStatement*);
@ -394,11 +396,14 @@ class Resolver {
const sem::Type* type,
std::string type_name = "");
/// Constructs a new semantic BlockStatement with the given type and with
/// #current_block_ as its parent, assigns this to #current_block_, and then
/// calls `callback`. The original #current_block_ is restored on exit.
/// Assigns `stmt` to #current_statement_, #current_compound_statement_, and
/// possibly #current_block_, pushes the variable scope, then calls
/// `callback`. Before returning #current_statement_,
/// #current_compound_statement_, and #current_block_ are restored to their
/// original values, and the variable scope is popped.
/// @returns the value returned by callback
template <typename F>
bool BlockScope(const ast::BlockStatement* block, F&& callback);
bool Scope(sem::CompoundStatement* stmt, F&& callback);
/// Returns a human-readable string representation of the vector type name
/// with the given parameters.
@ -449,7 +454,6 @@ class Resolver {
ProgramBuilder* const builder_;
diag::List& diagnostics_;
std::unique_ptr<IntrinsicTable> const intrinsic_table_;
sem::BlockStatement* current_block_ = nullptr;
ScopeStack<VariableInfo*> variable_stack_;
std::unordered_map<Symbol, FunctionInfo*> symbol_to_function_;
std::vector<FunctionInfo*> entry_points_;
@ -466,6 +470,8 @@ class Resolver {
FunctionInfo* current_function_ = nullptr;
sem::Statement* current_statement_ = nullptr;
sem::CompoundStatement* current_compound_statement_ = nullptr;
sem::BlockStatement* current_block_ = nullptr;
BlockAllocator<VariableInfo> variable_infos_;
BlockAllocator<FunctionInfo> function_infos_;
};

View File

@ -21,14 +21,12 @@
TINT_INSTANTIATE_TYPEINFO(tint::sem::BlockStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::FunctionBlockStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopBlockStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopContinuingBlockStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchCaseBlockStatement);
namespace tint {
namespace sem {
BlockStatement::BlockStatement(const ast::BlockStatement* declaration,
const Statement* parent)
const CompoundStatement* parent)
: Base(declaration, parent) {}
BlockStatement::~BlockStatement() = default;
@ -47,25 +45,15 @@ FunctionBlockStatement::FunctionBlockStatement(const ast::Function* function)
FunctionBlockStatement::~FunctionBlockStatement() = default;
LoopBlockStatement::LoopBlockStatement(const ast::BlockStatement* declaration,
const Statement* parent)
: Base(declaration, parent) {}
const CompoundStatement* parent)
: Base(declaration, parent) {
TINT_ASSERT(Semantic, parent);
}
LoopBlockStatement::~LoopBlockStatement() = default;
void LoopBlockStatement::SetFirstContinue(size_t first_continue) {
first_continue_ = first_continue;
}
LoopContinuingBlockStatement::LoopContinuingBlockStatement(
const ast::BlockStatement* declaration,
const Statement* parent)
: Base(declaration, parent) {}
LoopContinuingBlockStatement::~LoopContinuingBlockStatement() = default;
SwitchCaseBlockStatement::SwitchCaseBlockStatement(
const ast::BlockStatement* declaration,
const Statement* parent)
: Base(declaration, parent) {}
SwitchCaseBlockStatement::~SwitchCaseBlockStatement() = default;
} // namespace sem
} // namespace tint

View File

@ -34,13 +34,13 @@ namespace sem {
/// Holds semantic information about a block, such as parent block and variables
/// declared in the block.
class BlockStatement : public Castable<BlockStatement, Statement> {
class BlockStatement : public Castable<BlockStatement, CompoundStatement> {
public:
/// Constructor
/// @param declaration the AST node for this block statement
/// @param parent the owning statement
BlockStatement(const ast::BlockStatement* declaration,
const Statement* parent);
const CompoundStatement* parent);
/// Destructor
~BlockStatement() override;
@ -49,33 +49,6 @@ class BlockStatement : public Castable<BlockStatement, Statement> {
/// statement
const ast::BlockStatement* Declaration() const;
/// @returns the closest enclosing block that satisfies the given predicate,
/// which may be the block itself, or nullptr if no match is found
/// @param pred a predicate that the resulting block must satisfy
template <typename Pred>
const BlockStatement* FindFirstParent(Pred&& pred) const {
const BlockStatement* curr = this;
while (curr && !pred(curr)) {
curr = curr->Block();
}
return curr;
}
/// @returns the statement itself if it matches the template type `T`,
/// otherwise the nearest enclosing block that matches `T`, or nullptr if
/// there is none.
template <typename T>
const T* FindFirstParent() const {
const BlockStatement* curr = this;
while (curr) {
if (auto* block = curr->As<T>()) {
return block;
}
curr = curr->Block();
}
return nullptr;
}
/// @returns the declarations associated with this block
const std::vector<const ast::Variable*>& Decls() const { return decls_; }
@ -105,14 +78,14 @@ class FunctionBlockStatement
ast::Function const* const function_;
};
/// Holds semantic information about a loop block or a for-loop block
/// Holds semantic information about a loop body block or for-loop body block
class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> {
public:
/// Constructor
/// @param declaration the AST node for this block statement
/// @param parent the owning statement
LoopBlockStatement(const ast::BlockStatement* declaration,
const Statement* parent);
const CompoundStatement* parent);
/// Destructor
~LoopBlockStatement() override;
@ -134,34 +107,6 @@ class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> {
size_t first_continue_ = kNoContinue;
};
/// Holds semantic information about a loop continuing block
class LoopContinuingBlockStatement
: public Castable<LoopContinuingBlockStatement, BlockStatement> {
public:
/// Constructor
/// @param declaration the AST node for this block statement
/// @param parent the owning statement
LoopContinuingBlockStatement(const ast::BlockStatement* declaration,
const Statement* parent);
/// Destructor
~LoopContinuingBlockStatement() override;
};
/// Holds semantic information about a switch case block
class SwitchCaseBlockStatement
: public Castable<SwitchCaseBlockStatement, BlockStatement> {
public:
/// Constructor
/// @param declaration the AST node for this block statement
/// @param parent the owning statement
SwitchCaseBlockStatement(const ast::BlockStatement* declaration,
const Statement* parent);
/// Destructor
~SwitchCaseBlockStatement() override;
};
} // namespace sem
} // namespace tint

View File

@ -0,0 +1,31 @@
// 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/sem/for_loop_statement.h"
#include "src/program_builder.h"
TINT_INSTANTIATE_TYPEINFO(tint::sem::ForLoopStatement);
namespace tint {
namespace sem {
ForLoopStatement::ForLoopStatement(const ast::ForLoopStatement* declaration,
CompoundStatement* parent)
: Base(declaration, parent) {}
ForLoopStatement::~ForLoopStatement() = default;
} // namespace sem
} // namespace tint

View File

@ -0,0 +1,45 @@
// 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.
#ifndef SRC_SEM_FOR_LOOP_STATEMENT_H_
#define SRC_SEM_FOR_LOOP_STATEMENT_H_
#include "src/sem/statement.h"
namespace tint {
namespace ast {
class ForLoopStatement;
} // namespace ast
} // namespace tint
namespace tint {
namespace sem {
/// Holds semantic information about a for-loop statement
class ForLoopStatement : public Castable<ForLoopStatement, CompoundStatement> {
public:
/// Constructor
/// @param declaration the AST node for this for-loop statement
/// @param parent the owning statement
ForLoopStatement(const ast::ForLoopStatement* declaration,
CompoundStatement* parent);
/// Destructor
~ForLoopStatement() override;
};
} // namespace sem
} // namespace tint
#endif // SRC_SEM_FOR_LOOP_STATEMENT_H_

38
src/sem/if_statement.cc Normal file
View File

@ -0,0 +1,38 @@
// 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/sem/if_statement.h"
#include "src/program_builder.h"
TINT_INSTANTIATE_TYPEINFO(tint::sem::IfStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::ElseStatement);
namespace tint {
namespace sem {
IfStatement::IfStatement(const ast::IfStatement* declaration,
CompoundStatement* parent)
: Base(declaration, parent) {}
IfStatement::~IfStatement() = default;
ElseStatement::ElseStatement(const ast::ElseStatement* declaration,
CompoundStatement* parent)
: Base(declaration, parent) {}
ElseStatement::~ElseStatement() = default;
} // namespace sem
} // namespace tint

59
src/sem/if_statement.h Normal file
View File

@ -0,0 +1,59 @@
// 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.
#ifndef SRC_SEM_IF_STATEMENT_H_
#define SRC_SEM_IF_STATEMENT_H_
#include "src/sem/statement.h"
// Forward declarations
namespace tint {
namespace ast {
class IfStatement;
class ElseStatement;
} // namespace ast
} // namespace tint
namespace tint {
namespace sem {
/// Holds semantic information about an if statement
class IfStatement : public Castable<IfStatement, CompoundStatement> {
public:
/// Constructor
/// @param declaration the AST node for this if statement
/// @param parent the owning statement
IfStatement(const ast::IfStatement* declaration, CompoundStatement* parent);
/// Destructor
~IfStatement() override;
};
/// Holds semantic information about an else statement
class ElseStatement : public Castable<ElseStatement, CompoundStatement> {
public:
/// Constructor
/// @param declaration the AST node for this else statement
/// @param parent the owning statement
ElseStatement(const ast::ElseStatement* declaration,
CompoundStatement* parent);
/// Destructor
~ElseStatement() override;
};
} // namespace sem
} // namespace tint
#endif // SRC_SEM_IF_STATEMENT_H_

40
src/sem/loop_statement.cc Normal file
View File

@ -0,0 +1,40 @@
// 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/sem/loop_statement.h"
#include "src/program_builder.h"
TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::LoopContinuingBlockStatement);
namespace tint {
namespace sem {
LoopStatement::LoopStatement(const ast::LoopStatement* declaration,
CompoundStatement* parent)
: Base(declaration, parent) {}
LoopStatement::~LoopStatement() = default;
LoopContinuingBlockStatement::LoopContinuingBlockStatement(
const ast::BlockStatement* declaration,
const CompoundStatement* parent)
: Base(declaration, parent) {
TINT_ASSERT(Semantic, parent);
}
LoopContinuingBlockStatement::~LoopContinuingBlockStatement() = default;
} // namespace sem
} // namespace tint

60
src/sem/loop_statement.h Normal file
View File

@ -0,0 +1,60 @@
// 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.
#ifndef SRC_SEM_LOOP_STATEMENT_H_
#define SRC_SEM_LOOP_STATEMENT_H_
#include "src/sem/block_statement.h"
// Forward declarations
namespace tint {
namespace ast {
class LoopStatement;
} // namespace ast
} // namespace tint
namespace tint {
namespace sem {
/// Holds semantic information about a loop statement
class LoopStatement : public Castable<LoopStatement, CompoundStatement> {
public:
/// Constructor
/// @param declaration the AST node for this loop statement
/// @param parent the owning statement
LoopStatement(const ast::LoopStatement* declaration,
CompoundStatement* parent);
/// Destructor
~LoopStatement() override;
};
/// Holds semantic information about a loop continuing block
class LoopContinuingBlockStatement
: public Castable<LoopContinuingBlockStatement, BlockStatement> {
public:
/// Constructor
/// @param declaration the AST node for this block statement
/// @param parent the owning statement
LoopContinuingBlockStatement(const ast::BlockStatement* declaration,
const CompoundStatement* parent);
/// Destructor
~LoopContinuingBlockStatement() override;
};
} // namespace sem
} // namespace tint
#endif // SRC_SEM_LOOP_STATEMENT_H_

View File

@ -21,32 +21,31 @@
#include "src/sem/statement.h"
TINT_INSTANTIATE_TYPEINFO(tint::sem::Statement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::CompoundStatement);
namespace tint {
namespace sem {
Statement::Statement(const ast::Statement* declaration, const Statement* parent)
Statement::Statement(const ast::Statement* declaration,
const CompoundStatement* parent)
: declaration_(declaration), parent_(parent) {}
const BlockStatement* Statement::Block() const {
auto* stmt = parent_;
while (stmt != nullptr) {
if (auto* block_stmt = stmt->As<BlockStatement>()) {
return block_stmt;
}
stmt = stmt->parent_;
}
return nullptr;
return FindFirstParent<BlockStatement>();
}
const ast::Function* Statement::Function() const {
if (auto* block = Block()) {
if (auto* fbs = block->FindFirstParent<FunctionBlockStatement>()) {
return fbs->Function();
}
if (auto* fbs = FindFirstParent<FunctionBlockStatement>()) {
return fbs->Function();
}
return nullptr;
}
CompoundStatement::CompoundStatement(const ast::Statement* declaration,
const CompoundStatement* parent)
: Base(declaration, parent) {}
CompoundStatement::~CompoundStatement() = default;
} // namespace sem
} // namespace tint

View File

@ -31,19 +31,34 @@ class BlockStatement;
namespace tint {
namespace sem {
/// Forward declaration
class CompoundStatement;
/// Statement holds the semantic information for a statement.
class Statement : public Castable<Statement, Node> {
public:
/// Constructor
/// @param declaration the AST node for this statement
/// @param parent the owning statement
Statement(const ast::Statement* declaration, const Statement* parent);
Statement(const ast::Statement* declaration, const CompoundStatement* parent);
/// @return the AST node for this statement
const ast::Statement* Declaration() const { return declaration_; }
/// @return the statement that encloses this statement
const Statement* Parent() const { return parent_; }
const CompoundStatement* Parent() const { return parent_; }
/// @returns the closest enclosing parent that satisfies the given predicate,
/// which may be the statement itself, or nullptr if no match is found
/// @param pred a predicate that the resulting block must satisfy
template <typename Pred>
const CompoundStatement* FindFirstParent(Pred&& pred) const;
/// @returns the statement itself if it matches the template type `T`,
/// otherwise the nearest enclosing statement that matches `T`, or nullptr if
/// there is none.
template <typename T>
const T* FindFirstParent() const;
/// @return the closest enclosing block for this statement
const BlockStatement* Block() const;
@ -53,9 +68,52 @@ class Statement : public Castable<Statement, Node> {
private:
ast::Statement const* const declaration_;
Statement const* const parent_;
CompoundStatement const* const parent_;
};
/// CompoundStatement is the base class of statements that can hold other
/// statements.
class CompoundStatement : public Castable<Statement, Statement> {
public:
/// Constructor
/// @param declaration the AST node for this statement
/// @param parent the owning statement
CompoundStatement(const ast::Statement* declaration,
const CompoundStatement* parent);
/// Destructor
~CompoundStatement() override;
};
template <typename Pred>
const CompoundStatement* Statement::FindFirstParent(Pred&& pred) const {
if (auto* self = As<CompoundStatement>()) {
if (pred(self)) {
return self;
}
}
const auto* curr = parent_;
while (curr && !pred(curr)) {
curr = curr->Parent();
}
return curr;
}
template <typename T>
const T* Statement::FindFirstParent() const {
if (auto* p = As<T>()) {
return p;
}
const auto* curr = parent_;
while (curr) {
if (auto* p = curr->As<T>()) {
return p;
}
curr = curr->Parent();
}
return nullptr;
}
} // namespace sem
} // namespace tint

View File

@ -0,0 +1,40 @@
// 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/sem/switch_statement.h"
#include "src/program_builder.h"
TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchStatement);
TINT_INSTANTIATE_TYPEINFO(tint::sem::SwitchCaseBlockStatement);
namespace tint {
namespace sem {
SwitchStatement::SwitchStatement(const ast::SwitchStatement* declaration,
CompoundStatement* parent)
: Base(declaration, parent) {}
SwitchStatement::~SwitchStatement() = default;
SwitchCaseBlockStatement::SwitchCaseBlockStatement(
const ast::BlockStatement* declaration,
const CompoundStatement* parent)
: Base(declaration, parent) {
TINT_ASSERT(Semantic, parent);
}
SwitchCaseBlockStatement::~SwitchCaseBlockStatement() = default;
} // namespace sem
} // namespace tint

View File

@ -0,0 +1,60 @@
// 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.
#ifndef SRC_SEM_SWITCH_STATEMENT_H_
#define SRC_SEM_SWITCH_STATEMENT_H_
#include "src/sem/block_statement.h"
// Forward declarations
namespace tint {
namespace ast {
class SwitchStatement;
} // namespace ast
} // namespace tint
namespace tint {
namespace sem {
/// Holds semantic information about an switch statement
class SwitchStatement : public Castable<SwitchStatement, CompoundStatement> {
public:
/// Constructor
/// @param declaration the AST node for this switch statement
/// @param parent the owning statement
SwitchStatement(const ast::SwitchStatement* declaration,
CompoundStatement* parent);
/// Destructor
~SwitchStatement() override;
};
/// Holds semantic information about a switch case block
class SwitchCaseBlockStatement
: public Castable<SwitchCaseBlockStatement, BlockStatement> {
public:
/// Constructor
/// @param declaration the AST node for this block statement
/// @param parent the owning statement
SwitchCaseBlockStatement(const ast::BlockStatement* declaration,
const CompoundStatement* parent);
/// Destructor
~SwitchCaseBlockStatement() override;
};
} // namespace sem
} // namespace tint
#endif // SRC_SEM_SWITCH_STATEMENT_H_

View File

@ -229,10 +229,10 @@ tint_unittests_source_set("tint_unittests_core_src") {
"../src/resolver/assignment_validation_test.cc",
"../src/resolver/atomics_test.cc",
"../src/resolver/atomics_validation_test.cc",
"../src/resolver/block_test.cc",
"../src/resolver/builtins_validation_test.cc",
"../src/resolver/call_test.cc",
"../src/resolver/call_validation_test.cc",
"../src/resolver/compound_statement_test.cc",
"../src/resolver/control_block_validation_test.cc",
"../src/resolver/decoration_validation_test.cc",
"../src/resolver/entry_point_validation_test.cc",