Fix issue with break inside switch inside continuing.

Tint is currently too restrictive in testing for a break statement
inside of a continuing block. In the case where the break is inside a
switch it is permissible, as it breaks the switch, not the loop.

This CL updates the validation routines to allow a switch to hold a
break statement inside a continuing block.

Bug: tint:2144
Change-Id: I82850cb736d1c2433fd98f2a2cd9bfd325d698d7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/175080
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/wgsl/resolver/validation_test.cc b/src/tint/lang/wgsl/resolver/validation_test.cc
index 6bc6fc2..ba5da4a 100644
--- a/src/tint/lang/wgsl/resolver/validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/validation_test.cc
@@ -994,6 +994,22 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_F(ResolverValidationTest, Stmt_BreakInSwitchInContinuing) {
+    // loop {
+    //   continuing {
+    //     switch(1) {
+    //       default:
+    //         break;
+    //     }
+    //   }
+    // }
+
+    auto* cont = Block(Switch(1_i, DefaultCase(Block(Break()))));
+
+    WrapInFunction(Loop(Block(Break()), cont));
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
 TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueInContinuing) {
     auto* cont = Block(                           // continuing {
         If(true, Block(                           //   if(true) {
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index 8e54a74..3cf40c0 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -266,11 +266,15 @@
 }
 
 const ast::Statement* Validator::ClosestContinuing(bool stop_at_loop,
+                                                   bool stop_at_switch,
                                                    sem::Statement* current_statement) const {
     for (const auto* s = current_statement; s != nullptr; s = s->Parent()) {
         if (stop_at_loop && s->Is<sem::LoopStatement>()) {
             break;
         }
+        if (stop_at_switch && s->Is<sem::SwitchStatement>()) {
+            break;
+        }
         if (s->Is<sem::LoopContinuingBlockStatement>()) {
             return s->Declaration();
         }
@@ -1583,7 +1587,8 @@
         AddError("break statement must be in a loop or switch case", stmt->Declaration()->source);
         return false;
     }
-    if (ClosestContinuing(/*stop_at_loop*/ true, current_statement) != nullptr) {
+    if (ClosestContinuing(/*stop_at_loop*/ true, /* stop_at_switch */ true, current_statement) !=
+        nullptr) {
         AddError(
             "`break` must not be used to exit from a continuing block. Use `break-if` instead.",
             stmt->Declaration()->source);
@@ -1594,7 +1599,8 @@
 
 bool Validator::ContinueStatement(const sem::Statement* stmt,
                                   sem::Statement* current_statement) const {
-    if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true, current_statement)) {
+    if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ true, /* stop_at_switch */ false,
+                                             current_statement)) {
         AddError("continuing blocks must not contain a continue statement",
                  stmt->Declaration()->source);
         if (continuing != stmt->Declaration() && continuing != stmt->Parent()->Declaration()) {
@@ -2463,7 +2469,8 @@
     }
 
     auto* sem = sem_.Get(ret);
-    if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false, current_statement)) {
+    if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false, /* stop_at_switch */ false,
+                                             current_statement)) {
         AddError("continuing blocks must not contain a return statement", ret->source);
         if (continuing != sem->Declaration() && continuing != sem->Parent()->Declaration()) {
             AddNote("see continuing block here", continuing->source);
diff --git a/src/tint/lang/wgsl/resolver/validator.h b/src/tint/lang/wgsl/resolver/validator.h
index 92ccf7c..fc42824 100644
--- a/src/tint/lang/wgsl/resolver/validator.h
+++ b/src/tint/lang/wgsl/resolver/validator.h
@@ -579,8 +579,11 @@
     /// (transitively) owns the current statement.
     /// @param stop_at_loop if true then the function will return nullptr if a
     /// loop or for-loop was found before the continuing.
+    /// @param stop_at_switch if true then the function will return nullptr if a switch was found
+    /// before continuing.
     /// @param current_statement the current statement being resolved
     const ast::Statement* ClosestContinuing(bool stop_at_loop,
+                                            bool stop_at_switch,
                                             sem::Statement* current_statement) const;
 
     /// Returns a human-readable string representation of the vector type name