Create a semantic class for block statements

Semantic information about block statements the resolver would
temporarily create while resolving is now exposed in a
sem::BlockStatement class.

In the process, semantic information about statements in general is
overhauled so that a statement has a reference to its parent
statement, regardless of whether this is a block.

Bug: tint:799
Bug: tint:800
Change-Id: I8771511c5274ea74741b8c86f0f55cbc39810888
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/50904
Commit-Queue: Alastair Donaldson <allydonaldson@googlemail.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 1e2a90b..8c0a9fe 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -552,6 +552,7 @@
 libtint_source_set("libtint_sem_src") {
   sources = [
     "sem/array.cc",
+    "sem/block_statement.cc",
     "sem/call.cc",
     "sem/call_target.cc",
     "sem/expression.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7dd8be9..77d3eeb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -235,6 +235,8 @@
   sem/array.cc
   sem/array.h
   sem/binding_point.h
+  sem/block_statement.cc
+  sem/block_statement.h
   sem/call_target.cc
   sem/call_target.h
   sem/call.cc
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 145bdc0..b303548 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -145,13 +145,6 @@
 
 Resolver::~Resolver() = default;
 
-Resolver::BlockInfo::BlockInfo(const ast::BlockStatement* b,
-                               Resolver::BlockInfo::Type ty,
-                               Resolver::BlockInfo* p)
-    : block(b), type(ty), parent(p) {}
-
-Resolver::BlockInfo::~BlockInfo() = default;
-
 void Resolver::set_referenced_from_function_if_needed(VariableInfo* var,
                                                       bool local) {
   if (current_function_ == nullptr) {
@@ -1230,7 +1223,18 @@
 
   if (func->body()) {
     Mark(func->body());
-    if (!BlockStatement(func->body())) {
+    if (current_statement_) {
+      TINT_ICE(diagnostics_)
+          << "Resolver::Function() called with a current statement";
+      return false;
+    }
+    sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
+        func->body(), nullptr, sem::BlockStatement::Type::kGeneric);
+    builder_->Sem().Add(func->body(), sem_block);
+    ScopedAssignment<sem::Statement*> sa_function_body(current_statement_,
+                                                       sem_block);
+    if (!BlockScope(func->body(),
+                    [&] { return Statements(func->body()->list()); })) {
       return false;
     }
   }
@@ -1256,11 +1260,6 @@
   return true;
 }
 
-bool Resolver::BlockStatement(const ast::BlockStatement* stmt) {
-  return BlockScope(stmt, BlockInfo::Type::kGeneric,
-                    [&] { return Statements(stmt->list()); });
-}
-
 bool Resolver::Statements(const ast::StatementList& stmts) {
   for (auto* stmt : stmts) {
     Mark(stmt);
@@ -1272,21 +1271,36 @@
 }
 
 bool Resolver::Statement(ast::Statement* stmt) {
-  auto* sem_statement =
-      builder_->create<sem::Statement>(stmt, current_block_->block);
+  sem::Statement* sem_statement;
+  if (stmt->As<ast::BlockStatement>()) {
+    sem_statement = builder_->create<sem::BlockStatement>(
+        stmt->As<ast::BlockStatement>(), current_statement_,
+        sem::BlockStatement::Type::kGeneric);
+  } else {
+    sem_statement = builder_->create<sem::Statement>(stmt, current_statement_);
+  }
   builder_->Sem().Add(stmt, sem_statement);
 
   ScopedAssignment<sem::Statement*> sa(current_statement_, sem_statement);
 
+  if (stmt->Is<ast::ElseStatement>()) {
+    TINT_ICE(diagnostics_)
+        << "Resolver::Statement() encountered an Else statement. Else "
+           "statements are embedded in If statements, so should never be "
+           "encountered as top-level statements";
+    return false;
+  }
+
   if (auto* a = stmt->As<ast::AssignmentStatement>()) {
     return Assignment(a);
   }
   if (auto* b = stmt->As<ast::BlockStatement>()) {
-    return BlockStatement(b);
+    return BlockScope(b, [&] { return Statements(b->list()); });
   }
   if (stmt->Is<ast::BreakStatement>()) {
-    if (!current_block_->FindFirstParent(BlockInfo::Type::kLoop) &&
-        !current_block_->FindFirstParent(BlockInfo::Type::kSwitchCase)) {
+    if (!current_block_->FindFirstParent(sem::BlockStatement::Type::kLoop) &&
+        !current_block_->FindFirstParent(
+            sem::BlockStatement::Type::kSwitchCase)) {
       diagnostics_.add_error("break statement must be in a loop or switch case",
                              stmt->source());
       return false;
@@ -1303,9 +1317,10 @@
   if (stmt->Is<ast::ContinueStatement>()) {
     // Set if we've hit the first continue statement in our parent loop
     if (auto* loop_block =
-            current_block_->FindFirstParent(BlockInfo::Type::kLoop)) {
-      if (loop_block->first_continue == size_t(~0)) {
-        loop_block->first_continue = loop_block->decls.size();
+            current_block_->FindFirstParent(sem::BlockStatement::Type::kLoop)) {
+      if (loop_block->FirstContinue() == size_t(~0)) {
+        const_cast<sem::BlockStatement*>(loop_block)
+            ->SetFirstContinue(loop_block->Decls().size());
       }
     } else {
       diagnostics_.add_error("continue statement must be in a loop",
@@ -1325,28 +1340,7 @@
     return IfStatement(i);
   }
   if (auto* l = stmt->As<ast::LoopStatement>()) {
-    // 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(l->body());
-    return BlockScope(l->body(), BlockInfo::Type::kLoop, [&] {
-      if (!Statements(l->body()->list())) {
-        return false;
-      }
-
-      if (l->continuing()) {  // has_continuing() also checks for empty()
-        Mark(l->continuing());
-      }
-      if (l->has_continuing()) {
-        if (!BlockScope(l->continuing(), BlockInfo::Type::kLoopContinuing,
-                        [&] { return Statements(l->continuing()->list()); })) {
-          return false;
-        }
-      }
-
-      return true;
-    });
+    return LoopStatement(l);
   }
   if (auto* r = stmt->As<ast::ReturnStatement>()) {
     return Return(r);
@@ -1369,7 +1363,11 @@
   for (auto* sel : stmt->selectors()) {
     Mark(sel);
   }
-  return BlockScope(stmt->body(), BlockInfo::Type::kSwitchCase,
+  sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
+      stmt->body(), current_statement_, sem::BlockStatement::Type::kSwitchCase);
+  builder_->Sem().Add(stmt->body(), sem_block);
+  ScopedAssignment<sem::Statement*> sa(current_statement_, sem_block);
+  return BlockScope(stmt->body(),
                     [&] { return Statements(stmt->body()->list()); });
 }
 
@@ -1388,17 +1386,21 @@
   }
 
   Mark(stmt->body());
-  if (!BlockStatement(stmt->body())) {
-    return false;
+  {
+    sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
+        stmt->body(), current_statement_, sem::BlockStatement::Type::kGeneric);
+    builder_->Sem().Add(stmt->body(), sem_block);
+    ScopedAssignment<sem::Statement*> sa(current_statement_, sem_block);
+    if (!BlockScope(stmt->body(),
+                    [&] { return Statements(stmt->body()->list()); })) {
+      return false;
+    }
   }
 
   for (auto* else_stmt : stmt->else_statements()) {
     Mark(else_stmt);
-    // Else statements are a bit unusual - they're owned by the if-statement,
-    // not a BlockStatement.
-    constexpr ast::BlockStatement* no_block_statement = nullptr;
     auto* sem_else_stmt =
-        builder_->create<sem::Statement>(else_stmt, no_block_statement);
+        builder_->create<sem::Statement>(else_stmt, current_statement_);
     builder_->Sem().Add(else_stmt, sem_else_stmt);
     ScopedAssignment<sem::Statement*> sa(current_statement_, sem_else_stmt);
     if (auto* cond = else_stmt->condition()) {
@@ -1417,13 +1419,56 @@
       }
     }
     Mark(else_stmt->body());
-    if (!BlockStatement(else_stmt->body())) {
-      return false;
+    {
+      sem::BlockStatement* sem_block = builder_->create<sem::BlockStatement>(
+          else_stmt->body(), current_statement_,
+          sem::BlockStatement::Type::kGeneric);
+      builder_->Sem().Add(else_stmt->body(), sem_block);
+      ScopedAssignment<sem::Statement*> sa_else_body(current_statement_,
+                                                     sem_block);
+      if (!BlockScope(else_stmt->body(),
+                      [&] { return Statements(else_stmt->body()->list()); })) {
+        return false;
+      }
     }
   }
   return true;
 }
 
+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_block_body = builder_->create<sem::BlockStatement>(
+      stmt->body(), current_statement_, sem::BlockStatement::Type::kLoop);
+  builder_->Sem().Add(stmt->body(), sem_block_body);
+  ScopedAssignment<sem::Statement*> body_sa(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::BlockStatement>(
+          stmt->continuing(), current_statement_,
+          sem::BlockStatement::Type::kLoopContinuing);
+      builder_->Sem().Add(stmt->continuing(), sem_block_continuing);
+      ScopedAssignment<sem::Statement*> continuing_sa(current_statement_,
+                                                      sem_block_continuing);
+      if (!BlockScope(stmt->continuing(),
+                      [&] { return Statements(stmt->continuing()->list()); })) {
+        return false;
+      }
+    }
+    return true;
+  });
+}
+
 bool Resolver::Expressions(const ast::ExpressionList& list) {
   for (auto* expr : list) {
     Mark(expr);
@@ -1778,11 +1823,11 @@
       // refer to a variable that is bypassed by a continue statement in the
       // loop's body block.
       if (auto* continuing_block = current_block_->FindFirstParent(
-              BlockInfo::Type::kLoopContinuing)) {
+              sem::BlockStatement::Type::kLoopContinuing)) {
         auto* loop_block =
-            continuing_block->FindFirstParent(BlockInfo::Type::kLoop);
-        if (loop_block->first_continue != size_t(~0)) {
-          auto& decls = loop_block->decls;
+            continuing_block->FindFirstParent(sem::BlockStatement::Type::kLoop);
+        if (loop_block->FirstContinue() != size_t(~0)) {
+          auto& decls = loop_block->Decls();
           // If our identifier is in loop_block->decls, make sure its index is
           // less than first_continue
           auto iter = std::find_if(
@@ -1791,7 +1836,7 @@
           if (iter != decls.end()) {
             auto var_decl_index =
                 static_cast<size_t>(std::distance(decls.begin(), iter));
-            if (var_decl_index >= loop_block->first_continue) {
+            if (var_decl_index >= loop_block->FirstContinue()) {
               diagnostics_.add_error(
                   "continue statement bypasses declaration of '" +
                       builder_->Symbols().NameFor(symbol) +
@@ -2227,7 +2272,7 @@
   }
 
   variable_stack_.set(var->symbol(), info);
-  current_block_->decls.push_back(var);
+  current_block_->AddDecl(var);
 
   if (!ValidateVariable(info)) {
     return false;
@@ -2868,6 +2913,11 @@
   }
   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);
+    ScopedAssignment<sem::Statement*> sa(current_statement_, sem_statement);
     if (!CaseStatement(case_stmt)) {
       return false;
     }
@@ -2992,11 +3042,15 @@
 }
 
 template <typename F>
-bool Resolver::BlockScope(const ast::BlockStatement* block,
-                          BlockInfo::Type type,
-                          F&& callback) {
-  BlockInfo block_info(block, type, current_block_);
-  ScopedAssignment<BlockInfo*> sa(current_block_, &block_info);
+bool Resolver::BlockScope(const ast::BlockStatement* block, F&& callback) {
+  auto* sem_block = builder_->Sem().Get<sem::BlockStatement>(block);
+  if (!sem_block) {
+    TINT_ICE(diagnostics_) << "Resolver::BlockScope() called on a block for "
+                              "which semantic information is not available";
+    return false;
+  }
+  ScopedAssignment<sem::BlockStatement*> sa(
+      current_block_, const_cast<sem::BlockStatement*>(sem_block));
   variable_stack_.push_scope();
   bool result = callback();
   variable_stack_.pop_scope();
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 8218140..3b8b05e 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -25,6 +25,7 @@
 #include "src/program_builder.h"
 #include "src/scope_stack.h"
 #include "src/sem/binding_point.h"
+#include "src/sem/block_statement.h"
 #include "src/sem/struct.h"
 #include "src/utils/unique_vector.h"
 
@@ -40,6 +41,7 @@
 class ConstructorExpression;
 class Function;
 class IdentifierExpression;
+class LoopStatement;
 class MemberAccessorExpression;
 class ReturnStatement;
 class SwitchStatement;
@@ -137,40 +139,6 @@
     sem::Statement* statement;
   };
 
-  /// Structure holding semantic information about a block (i.e. scope), such as
-  /// parent block and variables declared in the block.
-  /// Used to validate variable scoping rules.
-  struct BlockInfo {
-    enum class Type { kGeneric, kLoop, kLoopContinuing, kSwitchCase };
-
-    BlockInfo(const ast::BlockStatement* block, Type type, BlockInfo* parent);
-    ~BlockInfo();
-
-    template <typename Pred>
-    BlockInfo* FindFirstParent(Pred&& pred) {
-      BlockInfo* curr = this;
-      while (curr && !pred(curr)) {
-        curr = curr->parent;
-      }
-      return curr;
-    }
-
-    BlockInfo* FindFirstParent(BlockInfo::Type ty) {
-      return FindFirstParent(
-          [ty](auto* block_info) { return block_info->type == ty; });
-    }
-
-    ast::BlockStatement const* const block;
-    Type const type;
-    BlockInfo* const parent;
-    std::vector<const ast::Variable*> decls;
-
-    // first_continue is set to the index of the first variable in decls
-    // declared after the first continue statement in a loop block, if any.
-    constexpr static size_t kNoContinue = size_t(~0);
-    size_t first_continue = kNoContinue;
-  };
-
   /// Resolves the program, without creating final the semantic nodes.
   /// @returns true on success, false on error
   bool ResolveInternal();
@@ -200,7 +168,6 @@
   bool Assignment(ast::AssignmentStatement* a);
   bool Binary(ast::BinaryExpression*);
   bool Bitcast(ast::BitcastExpression*);
-  bool BlockStatement(const ast::BlockStatement*);
   bool Call(ast::CallExpression*);
   bool CaseStatement(ast::CaseStatement*);
   bool Constructor(ast::ConstructorExpression*);
@@ -211,6 +178,7 @@
   bool Identifier(ast::IdentifierExpression*);
   bool IfStatement(ast::IfStatement*);
   bool IntrinsicCall(ast::CallExpression*, sem::IntrinsicType);
+  bool LoopStatement(ast::LoopStatement*);
   bool MemberAccessor(ast::MemberAccessorExpression*);
   bool Parameter(ast::Variable* param);
   bool Return(ast::ReturnStatement* ret);
@@ -317,13 +285,11 @@
                typ::Type type,
                const std::string& type_name);
 
-  /// Constructs a new BlockInfo 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.
+  /// 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.
   template <typename F>
-  bool BlockScope(const ast::BlockStatement* block,
-                  BlockInfo::Type type,
-                  F&& callback);
+  bool BlockScope(const ast::BlockStatement* block, F&& callback);
 
   /// Returns a human-readable string representation of the vector type name
   /// with the given parameters.
@@ -340,7 +306,7 @@
   ProgramBuilder* const builder_;
   diag::List& diagnostics_;
   std::unique_ptr<IntrinsicTable> const intrinsic_table_;
-  BlockInfo* current_block_ = nullptr;
+  sem::BlockStatement* current_block_ = nullptr;
   ScopeStack<VariableInfo*> variable_stack_;
   std::unordered_map<Symbol, FunctionInfo*> symbol_to_function_;
   std::unordered_map<const ast::Function*, FunctionInfo*> function_to_info_;
diff --git a/src/resolver/resolver_test_helper.h b/src/resolver/resolver_test_helper.h
index 4675256..a9dbb36 100644
--- a/src/resolver/resolver_test_helper.h
+++ b/src/resolver/resolver_test_helper.h
@@ -56,7 +56,7 @@
   /// if the statement is not owned by a BlockStatement.
   const ast::BlockStatement* BlockOf(ast::Statement* stmt) {
     auto* sem_stmt = Sem().Get(stmt);
-    return sem_stmt ? sem_stmt->Block() : nullptr;
+    return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
   }
 
   /// Returns the BlockStatement that holds the given expression.
@@ -65,7 +65,7 @@
   /// expression is not indirectly owned by a BlockStatement.
   const ast::BlockStatement* BlockOf(ast::Expression* expr) {
     auto* sem_stmt = Sem().Get(expr)->Stmt();
-    return sem_stmt ? sem_stmt->Block() : nullptr;
+    return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
   }
 
   /// Returns the semantic variable for the given identifier expression.
diff --git a/src/sem/block_statement.cc b/src/sem/block_statement.cc
new file mode 100644
index 0000000..8d63f0d
--- /dev/null
+++ b/src/sem/block_statement.cc
@@ -0,0 +1,51 @@
+// 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/block_statement.h"
+
+#include "src/ast/block_statement.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::sem::BlockStatement);
+
+namespace tint {
+namespace sem {
+
+BlockStatement::BlockStatement(const ast::BlockStatement* declaration,
+                               const Statement* parent,
+                               Type type)
+    : Base(declaration, parent), type_(type) {}
+
+BlockStatement::~BlockStatement() = default;
+
+const BlockStatement* BlockStatement::FindFirstParent(
+    BlockStatement::Type ty) const {
+  return FindFirstParent(
+      [ty](auto* block_info) { return block_info->type_ == ty; });
+}
+
+const ast::BlockStatement* BlockStatement::Declaration() const {
+  return Base::Declaration()->As<ast::BlockStatement>();
+}
+
+void BlockStatement::SetFirstContinue(size_t first_continue) {
+  TINT_ASSERT(type_ == Type::kLoop);
+  first_continue_ = first_continue;
+}
+
+void BlockStatement::AddDecl(ast::Variable* var) {
+  decls_.push_back(var);
+}
+
+}  // namespace sem
+}  // namespace tint
diff --git a/src/sem/block_statement.h b/src/sem/block_statement.h
new file mode 100644
index 0000000..5e0f481
--- /dev/null
+++ b/src/sem/block_statement.h
@@ -0,0 +1,105 @@
+// 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_BLOCK_STATEMENT_H_
+#define SRC_SEM_BLOCK_STATEMENT_H_
+
+#include <vector>
+
+#include "src/debug.h"
+#include "src/sem/statement.h"
+
+namespace tint {
+
+// Forward declarations
+namespace ast {
+class BlockStatement;
+class Variable;
+}  // namespace ast
+
+namespace sem {
+
+/// Holds semantic information about a block, such as parent block and variables
+/// declared in the block.
+class BlockStatement : public Castable<BlockStatement, Statement> {
+ public:
+  enum class Type { kGeneric, kLoop, kLoopContinuing, kSwitchCase };
+
+  /// Constructor
+  /// @param declaration the AST node for this block statement
+  /// @param parent the owning statement
+  /// @param type the type of block this is
+  BlockStatement(const ast::BlockStatement* declaration,
+                 const Statement* parent,
+                 Type type);
+
+  /// Destructor
+  ~BlockStatement() override;
+
+  /// @returns the AST block statement associated with this semantic block
+  /// 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 closest enclosing block that matches the given type, which
+  ///          may be the block itself, or nullptr if no match is found
+  /// @param ty the type of block to be searched for
+  const BlockStatement* FindFirstParent(BlockStatement::Type ty) const;
+
+  /// @returns the declarations associated with this block
+  const std::vector<const ast::Variable*>& Decls() const { return decls_; }
+
+  /// Requires that this is a loop block.
+  /// @returns the index of the first variable declared after the first continue
+  ///          statement
+  size_t FirstContinue() const {
+    TINT_ASSERT(type_ == Type::kLoop);
+    return first_continue_;
+  }
+
+  /// Requires that this is a loop block.
+  /// Allows the resolver to set the index of the first variable declared after
+  /// the first continue statement.
+  /// @param first_continue index of the relevant variable
+  void SetFirstContinue(size_t first_continue);
+
+  /// Allows the resolver to associate a declaration with this block.
+  /// @param var a variable declaration to be added to the block
+  void AddDecl(ast::Variable* var);
+
+ private:
+  Type const type_;
+  std::vector<const ast::Variable*> decls_;
+
+  // first_continue is set to the index of the first variable in decls
+  // declared after the first continue statement in a loop block, if any.
+  constexpr static size_t kNoContinue = size_t(~0);
+  size_t first_continue_ = kNoContinue;
+};
+
+}  // namespace sem
+}  // namespace tint
+
+#endif  // SRC_SEM_BLOCK_STATEMENT_H_
diff --git a/src/sem/statement.cc b/src/sem/statement.cc
index 32c88b1..e8620c8 100644
--- a/src/sem/statement.cc
+++ b/src/sem/statement.cc
@@ -15,7 +15,10 @@
 #include <algorithm>
 
 #include "src/ast/block_statement.h"
+#include "src/ast/loop_statement.h"
+#include "src/ast/statement.h"
 #include "src/debug.h"
+#include "src/sem/block_statement.h"
 #include "src/sem/statement.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::sem::Statement);
@@ -23,17 +26,44 @@
 namespace tint {
 namespace sem {
 
-Statement::Statement(const ast::Statement* declaration,
-                     const ast::BlockStatement* block)
-    : declaration_(declaration), block_(block) {
+Statement::Statement(const ast::Statement* declaration, const Statement* parent)
+    : declaration_(declaration), parent_(parent) {
 #ifndef NDEBUG
-  if (block) {
-    auto& stmts = block->statements();
-    TINT_ASSERT(std::find(stmts.begin(), stmts.end(), declaration) !=
-                stmts.end());
+  if (parent_) {
+    auto* block = Block();
+    if (parent_ == block) {
+      // The parent of this statement is a block. We thus expect the statement
+      // to be an element of the block. There is one exception: a loop's
+      // continuing block has the loop's body as its parent, but the continuing
+      // block is not a statement in the body, so we rule out that case.
+      auto& stmts = block->Declaration()->statements();
+      if (std::find(stmts.begin(), stmts.end(), declaration) == stmts.end()) {
+        bool statement_is_continuing_for_loop = false;
+        if (parent_->parent_ != nullptr) {
+          if (auto* loop =
+                  parent_->parent_->Declaration()->As<ast::LoopStatement>()) {
+            if (loop->has_continuing() && Declaration() == loop->continuing()) {
+              statement_is_continuing_for_loop = true;
+            }
+          }
+        }
+        TINT_ASSERT(statement_is_continuing_for_loop);
+      }
+    }
   }
 #endif  //  NDEBUG
 }
 
+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;
+}
+
 }  // namespace sem
 }  // namespace tint
diff --git a/src/sem/statement.h b/src/sem/statement.h
index 251690c..6add2ae 100644
--- a/src/sem/statement.h
+++ b/src/sem/statement.h
@@ -21,30 +21,33 @@
 
 // Forward declarations
 namespace ast {
-class BlockStatement;
 class Statement;
 }  // namespace ast
 
 namespace sem {
 
+class BlockStatement;
+
 /// 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 block the owning AST block statement
-  Statement(const ast::Statement* declaration,
-            const ast::BlockStatement* block);
+  /// @param parent the owning statement
+  Statement(const ast::Statement* declaration, const Statement* parent);
 
   /// @return the AST node for this statement
   const ast::Statement* Declaration() const { return declaration_; }
 
-  /// @return the owning AST block statement for this statement
-  const ast::BlockStatement* Block() const { return block_; }
+  /// @return the statement that encloses this statement
+  const Statement* Parent() const { return parent_; }
+
+  /// @return the closest enclosing block for this statement
+  const BlockStatement* Block() const;
 
  private:
   ast::Statement const* const declaration_;
-  ast::BlockStatement const* const block_;
+  Statement const* const parent_;
 };
 
 }  // namespace sem
diff --git a/src/transform/calculate_array_length.cc b/src/transform/calculate_array_length.cc
index fbaef80..96efb9d 100644
--- a/src/transform/calculate_array_length.cc
+++ b/src/transform/calculate_array_length.cc
@@ -19,6 +19,7 @@
 
 #include "src/ast/call_statement.h"
 #include "src/program_builder.h"
+#include "src/sem/block_statement.h"
 #include "src/sem/call.h"
 #include "src/sem/statement.h"
 #include "src/sem/struct.h"
@@ -155,12 +156,7 @@
           }
 
           // Find the current statement block
-          auto* block = call->Stmt()->Block();
-          if (!block) {
-            TINT_ICE(ctx.dst->Diagnostics())
-                << "arrayLength() statement is outside a BlockStatement";
-            break;
-          }
+          auto* block = call->Stmt()->Block()->Declaration();
 
           // If the storage_buffer_expr is resolves to a variable (typically
           // true) then key the array_length from the variable. If not, key off
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
index 2810781..8da983f 100644
--- a/src/transform/canonicalize_entry_point_io.cc
+++ b/src/transform/canonicalize_entry_point_io.cc
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "src/program_builder.h"
+#include "src/sem/block_statement.h"
 #include "src/sem/function.h"
 #include "src/sem/statement.h"
 #include "src/sem/struct.h"
@@ -257,7 +258,8 @@
             auto* ty = CreateASTTypeFor(&ctx, ret_type);
             auto* temp_var =
                 ctx.dst->Decl(ctx.dst->Const(temp, ty, new_ret_value()));
-            ctx.InsertBefore(ret_sem->Block()->statements(), ret, temp_var);
+            ctx.InsertBefore(ret_sem->Block()->Declaration()->statements(), ret,
+                             temp_var);
             new_ret_value = [&ctx, temp] { return ctx.dst->Expr(temp); };
           }
 
diff --git a/src/transform/hlsl.cc b/src/transform/hlsl.cc
index af5e0ed..65ed1ca 100644
--- a/src/transform/hlsl.cc
+++ b/src/transform/hlsl.cc
@@ -19,6 +19,7 @@
 #include "src/ast/stage_decoration.h"
 #include "src/ast/variable_decl_statement.h"
 #include "src/program_builder.h"
+#include "src/sem/block_statement.h"
 #include "src/sem/expression.h"
 #include "src/sem/statement.h"
 #include "src/sem/variable.h"
@@ -113,8 +114,8 @@
         auto* dst_ident = ctx.dst->Expr(dst_symbol);
 
         // Insert the constant before the usage
-        ctx.InsertBefore(src_sem_stmt->Block()->statements(), src_stmt,
-                         dst_var_decl);
+        ctx.InsertBefore(src_sem_stmt->Block()->Declaration()->statements(),
+                         src_stmt, dst_var_decl);
         // Replace the inlined initializer with a reference to the constant
         ctx.Replace(src_init, dst_ident);
       }
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index d671362..b5db367 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -21,6 +21,7 @@
 #include "src/ast/return_statement.h"
 #include "src/ast/stage_decoration.h"
 #include "src/program_builder.h"
+#include "src/sem/block_statement.h"
 #include "src/sem/function.h"
 #include "src/sem/statement.h"
 #include "src/sem/struct.h"
@@ -188,7 +189,7 @@
       for (auto* ret : func->ReturnStatements()) {
         auto* ret_sem = ctx.src->Sem().Get(ret);
         auto* call = ctx.dst->Call(return_func_symbol, ctx.Clone(ret->value()));
-        ctx.InsertBefore(ret_sem->Block()->statements(), ret,
+        ctx.InsertBefore(ret_sem->Block()->Declaration()->statements(), ret,
                          ctx.dst->create<ast::CallStatement>(call));
         ctx.Replace(ret, ctx.dst->Return());
       }