Remove infrastructure for fallthrough

This Cl removes the internal infrastructor and backend code
generation for the fallthrough statement.

Bug: tint:1644
Change-Id: I2a1de7d527865e5a7221074f4e0fb106599f4c57
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/109005
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index fbbfe5a..36fd66b 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -247,8 +247,6 @@
     "ast/f16.h",
     "ast/f32.cc",
     "ast/f32.h",
-    "ast/fallthrough_statement.cc",
-    "ast/fallthrough_statement.h",
     "ast/float_literal_expression.cc",
     "ast/float_literal_expression.h",
     "ast/for_loop_statement.cc",
@@ -1047,7 +1045,6 @@
       "ast/external_texture_test.cc",
       "ast/f16_test.cc",
       "ast/f32_test.cc",
-      "ast/fallthrough_statement_test.cc",
       "ast/float_literal_expression_test.cc",
       "ast/for_loop_statement_test.cc",
       "ast/function_test.cc",
@@ -1159,8 +1156,8 @@
       "resolver/resolver_test.cc",
       "resolver/resolver_test_helper.cc",
       "resolver/resolver_test_helper.h",
-      "resolver/side_effects_test.cc",
       "resolver/root_identifier_test.cc",
+      "resolver/side_effects_test.cc",
       "resolver/static_assert_test.cc",
       "resolver/struct_address_space_use_test.cc",
       "resolver/struct_layout_test.cc",
@@ -1480,7 +1477,6 @@
       "writer/wgsl/generator_impl_continue_test.cc",
       "writer/wgsl/generator_impl_discard_test.cc",
       "writer/wgsl/generator_impl_enable_test.cc",
-      "writer/wgsl/generator_impl_fallthrough_test.cc",
       "writer/wgsl/generator_impl_function_test.cc",
       "writer/wgsl/generator_impl_global_decl_test.cc",
       "writer/wgsl/generator_impl_identifier_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index cbf23db..978986e 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -139,8 +139,6 @@
   ast/f16.h
   ast/f32.cc
   ast/f32.h
-  ast/fallthrough_statement.cc
-  ast/fallthrough_statement.h
   ast/float_literal_expression.cc
   ast/float_literal_expression.h
   ast/for_loop_statement.cc
@@ -786,7 +784,6 @@
     ast/external_texture_test.cc
     ast/f16_test.cc
     ast/f32_test.cc
-    ast/fallthrough_statement_test.cc
     ast/float_literal_expression_test.cc
     ast/for_loop_statement_test.cc
     ast/function_test.cc
@@ -1150,7 +1147,6 @@
       writer/wgsl/generator_impl_continue_test.cc
       writer/wgsl/generator_impl_discard_test.cc
       writer/wgsl/generator_impl_enable_test.cc
-      writer/wgsl/generator_impl_fallthrough_test.cc
       writer/wgsl/generator_impl_function_test.cc
       writer/wgsl/generator_impl_global_decl_test.cc
       writer/wgsl/generator_impl_identifier_test.cc
diff --git a/src/tint/ast/fallthrough_statement.cc b/src/tint/ast/fallthrough_statement.cc
deleted file mode 100644
index 0d30ae3..0000000
--- a/src/tint/ast/fallthrough_statement.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2020 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/tint/ast/fallthrough_statement.h"
-
-#include "src/tint/program_builder.h"
-
-TINT_INSTANTIATE_TYPEINFO(tint::ast::FallthroughStatement);
-
-namespace tint::ast {
-
-FallthroughStatement::FallthroughStatement(ProgramID pid, NodeID nid, const Source& src)
-    : Base(pid, nid, src) {}
-
-FallthroughStatement::FallthroughStatement(FallthroughStatement&&) = default;
-
-FallthroughStatement::~FallthroughStatement() = default;
-
-const FallthroughStatement* FallthroughStatement::Clone(CloneContext* ctx) const {
-    // Clone arguments outside of create() call to have deterministic ordering
-    auto src = ctx->Clone(source);
-    return ctx->dst->create<FallthroughStatement>(src);
-}
-
-}  // namespace tint::ast
diff --git a/src/tint/ast/fallthrough_statement.h b/src/tint/ast/fallthrough_statement.h
deleted file mode 100644
index da2fd3d..0000000
--- a/src/tint/ast/fallthrough_statement.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2020 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_TINT_AST_FALLTHROUGH_STATEMENT_H_
-#define SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_
-
-#include "src/tint/ast/statement.h"
-
-namespace tint::ast {
-
-/// An fallthrough statement
-class FallthroughStatement final : public Castable<FallthroughStatement, Statement> {
-  public:
-    /// Constructor
-    /// @param pid the identifier of the program that owns this node
-    /// @param nid the unique node identifier
-    /// @param src the source of this node
-    FallthroughStatement(ProgramID pid, NodeID nid, const Source& src);
-    /// Move constructor
-    FallthroughStatement(FallthroughStatement&&);
-    ~FallthroughStatement() override;
-
-    /// Clones this node and all transitive child nodes using the `CloneContext`
-    /// `ctx`.
-    /// @param ctx the clone context
-    /// @return the newly cloned node
-    const FallthroughStatement* Clone(CloneContext* ctx) const override;
-};
-
-}  // namespace tint::ast
-
-#endif  // SRC_TINT_AST_FALLTHROUGH_STATEMENT_H_
diff --git a/src/tint/ast/fallthrough_statement_test.cc b/src/tint/ast/fallthrough_statement_test.cc
deleted file mode 100644
index f823e91..0000000
--- a/src/tint/ast/fallthrough_statement_test.cc
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2020 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/tint/ast/fallthrough_statement.h"
-
-#include "src/tint/ast/test_helper.h"
-
-namespace tint::ast {
-namespace {
-
-using FallthroughStatementTest = TestHelper;
-
-TEST_F(FallthroughStatementTest, Creation) {
-    auto* stmt = create<FallthroughStatement>();
-    EXPECT_EQ(stmt->source.range.begin.line, 0u);
-    EXPECT_EQ(stmt->source.range.begin.column, 0u);
-    EXPECT_EQ(stmt->source.range.end.line, 0u);
-    EXPECT_EQ(stmt->source.range.end.column, 0u);
-}
-
-TEST_F(FallthroughStatementTest, Creation_WithSource) {
-    auto* stmt = create<FallthroughStatement>(Source{Source::Location{20, 2}});
-    auto src = stmt->source;
-    EXPECT_EQ(src.range.begin.line, 20u);
-    EXPECT_EQ(src.range.begin.column, 2u);
-}
-
-TEST_F(FallthroughStatementTest, IsFallthrough) {
-    auto* stmt = create<FallthroughStatement>();
-    EXPECT_TRUE(stmt->Is<FallthroughStatement>());
-}
-
-}  // namespace
-}  // namespace tint::ast
diff --git a/src/tint/ast/statement.cc b/src/tint/ast/statement.cc
index 6acfff3..7b9ca5d 100644
--- a/src/tint/ast/statement.cc
+++ b/src/tint/ast/statement.cc
@@ -19,7 +19,6 @@
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/continue_statement.h"
 #include "src/tint/ast/discard_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/if_statement.h"
 #include "src/tint/ast/loop_statement.h"
 #include "src/tint/ast/return_statement.h"
@@ -58,9 +57,6 @@
     if (Is<DiscardStatement>()) {
         return "discard statement";
     }
-    if (Is<FallthroughStatement>()) {
-        return "fallthrough statement";
-    }
     if (Is<IfStatement>()) {
         return "if statement";
     }
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 6bbcf6d..423559d 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -19,7 +19,6 @@
 #include "src/tint/ast/break_if_statement.h"
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/continue_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/function.h"
 #include "src/tint/ast/if_statement.h"
@@ -210,7 +209,6 @@
         //        [&](const ast::CallStatement* c) { },
         [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
         //        [&](const ast::DiscardStatement* d) { },
-        [&](const ast::FallthroughStatement*) { return EmitFallthrough(); },
         [&](const ast::IfStatement* i) { return EmitIf(i); },
         [&](const ast::LoopStatement* l) { return EmitLoop(l); },
         [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
@@ -415,25 +413,12 @@
     {
         FlowStackScope scope(this, switch_node);
 
-        // TODO(crbug.com/tint/1644): This can be simplifed when fallthrough is removed, a single
-        // loop can be used to iterate each body statement and emit for the case. Two loops are
-        // needed in order to have the target for a fallthrough.
         for (const auto* c : stmt->body) {
-            builder_.CreateCase(switch_node, c->selectors);
-        }
-
-        for (size_t i = 0; i < stmt->body.Length(); ++i) {
-            current_flow_block_ = switch_node->cases[i].start_target;
-            if (i < (stmt->body.Length() - 1)) {
-                fallthrough_target_ = switch_node->cases[i + 1].start_target;
-            }
-
-            if (!EmitStatement(stmt->body[i]->body)) {
+            current_flow_block_ = builder_.CreateCase(switch_node, c->selectors);
+            if (!EmitStatement(c->body)) {
                 return false;
             }
             BranchToIfNeeded(switch_node->merge_target);
-
-            fallthrough_target_ = nullptr;
         }
     }
     current_flow_block_ = nullptr;
@@ -514,10 +499,4 @@
     return true;
 }
 
-bool BuilderImpl::EmitFallthrough() {
-    TINT_ASSERT(IR, fallthrough_target_ != nullptr);
-    BranchTo(fallthrough_target_);
-    return true;
-}
-
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index d6c4bc4..8f24dc6 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -134,10 +134,6 @@
     /// @returns true if successful, false otherwise.
     bool EmitBreakIf(const ast::BreakIfStatement* stmt);
 
-    /// Emits a fallthrough statement
-    /// @returns true if successful, false otherwise
-    bool EmitFallthrough();
-
     /// Retrieve the IR Flow node for a given AST node.
     /// @param n the node to lookup
     /// @returns the FlowNode for the given ast::Node or nullptr if it doesn't exist.
@@ -166,9 +162,6 @@
     Block* current_flow_block_ = nullptr;
     Function* current_function_ = nullptr;
 
-    // TODO(crbug.com/tint/1644): Remove this when fallthrough is removed.
-    Block* fallthrough_target_ = nullptr;
-
     /// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node.
     /// Used for testing purposes.
     std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_;
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 1ee66e5..14f01ba 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -1313,64 +1313,5 @@
     EXPECT_EQ(flow->merge_target->branch_target, nullptr);
 }
 
-// TODO(crbug.com/tint/1644): Remove when fallthrough is removed.
-TEST_F(IRBuilderImplTest, Switch_Fallthrough) {
-    // func -> switch -> case 1
-    //                -> case 2
-    //                -> default
-    //
-    //   [case 1] -> switch merge
-    //   [case 2] -> default
-    //   [default] -> switch merge
-    //   [switch merge] -> func end
-    //
-    auto* ast_switch =
-        Switch(1_i, utils::Vector{Case(utils::Vector{CaseSelector(0_i)}, Block()),
-                                  Case(utils::Vector{CaseSelector(1_i)}, Block(Fallthrough())),
-                                  DefaultCase(Block())});
-
-    WrapInFunction(ast_switch);
-    auto& b = Build();
-
-    auto r = b.Build();
-    ASSERT_TRUE(r) << b.error();
-    auto m = r.Move();
-
-    auto* ir_switch = b.FlowNodeForAstNode(ast_switch);
-    ASSERT_NE(ir_switch, nullptr);
-    ASSERT_TRUE(ir_switch->Is<ir::Switch>());
-
-    auto* flow = ir_switch->As<ir::Switch>();
-    ASSERT_NE(flow->merge_target, nullptr);
-    ASSERT_EQ(3u, flow->cases.Length());
-
-    ASSERT_EQ(1u, m.functions.Length());
-    auto* func = m.functions[0];
-
-    ASSERT_EQ(1u, flow->cases[0].selectors.Length());
-    ASSERT_TRUE(flow->cases[0].selectors[0]->expr->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(0_i, flow->cases[0].selectors[0]->expr->As<ast::IntLiteralExpression>()->value);
-
-    ASSERT_EQ(1u, flow->cases[1].selectors.Length());
-    ASSERT_TRUE(flow->cases[1].selectors[0]->expr->Is<ast::IntLiteralExpression>());
-    EXPECT_EQ(1_i, flow->cases[1].selectors[0]->expr->As<ast::IntLiteralExpression>()->value);
-
-    ASSERT_EQ(1u, flow->cases[2].selectors.Length());
-    EXPECT_TRUE(flow->cases[2].selectors[0]->IsDefault());
-
-    EXPECT_EQ(1u, flow->inbound_branches.Length());
-    EXPECT_EQ(1u, flow->cases[0].start_target->inbound_branches.Length());
-    EXPECT_EQ(1u, flow->cases[1].start_target->inbound_branches.Length());
-    EXPECT_EQ(2u, flow->cases[2].start_target->inbound_branches.Length());
-    EXPECT_EQ(2u, flow->merge_target->inbound_branches.Length());
-    EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
-
-    EXPECT_EQ(func->start_target->branch_target, ir_switch);
-    EXPECT_EQ(flow->cases[0].start_target->branch_target, flow->merge_target);
-    EXPECT_EQ(flow->cases[1].start_target->branch_target, flow->cases[2].start_target);
-    EXPECT_EQ(flow->cases[2].start_target->branch_target, flow->merge_target);
-    EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
-}
-
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 3d1f40e..d6d1f00 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -47,7 +47,6 @@
 #include "src/tint/ast/external_texture.h"
 #include "src/tint/ast/f16.h"
 #include "src/tint/ast/f32.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/float_literal_expression.h"
 #include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/i32.h"
@@ -2932,17 +2931,6 @@
     /// @returns the selector pointer
     const ast::CaseSelector* DefaultCaseSelector() { return create<ast::CaseSelector>(nullptr); }
 
-    /// Creates an ast::FallthroughStatement
-    /// @param source the source information
-    /// @returns the fallthrough statement pointer
-    const ast::FallthroughStatement* Fallthrough(const Source& source) {
-        return create<ast::FallthroughStatement>(source);
-    }
-
-    /// Creates an ast::FallthroughStatement
-    /// @returns the fallthrough statement pointer
-    const ast::FallthroughStatement* Fallthrough() { return create<ast::FallthroughStatement>(); }
-
     /// Creates an ast::BuiltinAttribute
     /// @param source the source information
     /// @param builtin the builtin value
diff --git a/src/tint/resolver/control_block_validation_test.cc b/src/tint/resolver/control_block_validation_test.cc
index 8cbf506..2104321 100644
--- a/src/tint/resolver/control_block_validation_test.cc
+++ b/src/tint/resolver/control_block_validation_test.cc
@@ -14,7 +14,6 @@
 
 #include "src/tint/ast/break_statement.h"
 #include "src/tint/ast/continue_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/switch_statement.h"
 #include "src/tint/resolver/resolver_test_helper.h"
 
@@ -378,24 +377,6 @@
               "12:34 note: previous case declared here");
 }
 
-TEST_F(ResolverControlBlockValidationTest, LastClauseLastStatementIsFallthrough_Fail) {
-    // var a : i32 = 2;
-    // switch (a) {
-    //   default: { fallthrough; }
-    // }
-    auto* var = Var("a", ty.i32(), Expr(2_i));
-    auto* fallthrough = create<ast::FallthroughStatement>(Source{{12, 34}});
-    auto* block = Block(Decl(var),   //
-                        Switch("a",  //
-                               DefaultCase(Block(fallthrough))));
-    WrapInFunction(block);
-
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "12:34 error: a fallthrough statement must not be used in the last "
-              "switch case");
-}
-
 TEST_F(ResolverControlBlockValidationTest, SwitchCase_Pass) {
     // var a : i32 = 2;
     // switch (a) {
diff --git a/src/tint/resolver/dependency_graph.cc b/src/tint/resolver/dependency_graph.cc
index 4dbf1c0..1fda54f 100644
--- a/src/tint/resolver/dependency_graph.cc
+++ b/src/tint/resolver/dependency_graph.cc
@@ -35,7 +35,6 @@
 #include "src/tint/ast/external_texture.h"
 #include "src/tint/ast/f16.h"
 #include "src/tint/ast/f32.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/i32.h"
 #include "src/tint/ast/id_attribute.h"
@@ -320,7 +319,7 @@
             [&](const ast::StaticAssert* assertion) { TraverseExpression(assertion->condition); },
             [&](Default) {
                 if (!stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
-                                   ast::DiscardStatement, ast::FallthroughStatement>()) {
+                                   ast::DiscardStatement>()) {
                     UnhandledNode(diagnostics_, stmt);
                 }
             });
diff --git a/src/tint/resolver/dependency_graph_test.cc b/src/tint/resolver/dependency_graph_test.cc
index c02a561..2cc4a3a 100644
--- a/src/tint/resolver/dependency_graph_test.cc
+++ b/src/tint/resolver/dependency_graph_test.cc
@@ -1273,8 +1273,6 @@
              Switch(V,                                  //
                     Case(CaseSelector(1_i),             //
                          Block(Assign(V, V))),          //
-                    Case(CaseSelector(2_i),             //
-                         Block(Fallthrough())),         //
                     DefaultCase(Block(Assign(V, V)))),  //
              Return(V),                                 //
              Break(),                                   //
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index e3b2d63..3dc054c 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -31,7 +31,6 @@
 #include "src/tint/ast/depth_texture.h"
 #include "src/tint/ast/disable_validation_attribute.h"
 #include "src/tint/ast/discard_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/if_statement.h"
@@ -1217,7 +1216,6 @@
         [&](const ast::CompoundAssignmentStatement* c) { return CompoundAssignmentStatement(c); },
         [&](const ast::ContinueStatement* c) { return ContinueStatement(c); },
         [&](const ast::DiscardStatement* d) { return DiscardStatement(d); },
-        [&](const ast::FallthroughStatement* f) { return FallthroughStatement(f); },
         [&](const ast::IncrementDecrementStatement* i) { return IncrementDecrementStatement(i); },
         [&](const ast::ReturnStatement* r) { return ReturnStatement(r); },
         [&](const ast::VariableDeclStatement* v) { return VariableDeclStatement(v); },
@@ -3146,7 +3144,7 @@
         if (behaviors.Contains(sem::Behavior::kBreak)) {
             behaviors.Add(sem::Behavior::kNext);
         }
-        behaviors.Remove(sem::Behavior::kBreak, sem::Behavior::kFallthrough);
+        behaviors.Remove(sem::Behavior::kBreak);
 
         return validator_.SwitchStatement(stmt);
     });
@@ -3308,16 +3306,6 @@
     });
 }
 
-sem::Statement* Resolver::FallthroughStatement(const ast::FallthroughStatement* stmt) {
-    auto* sem =
-        builder_->create<sem::Statement>(stmt, current_compound_statement_, current_function_);
-    return StatementScope(stmt, sem, [&] {
-        sem->Behaviors() = sem::Behavior::kFallthrough;
-
-        return validator_.FallthroughStatement(sem);
-    });
-}
-
 sem::Statement* Resolver::IncrementDecrementStatement(
     const ast::IncrementDecrementStatement* stmt) {
     auto* sem =
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index bfb95b3..38e624c 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -212,7 +212,6 @@
     sem::Statement* CompoundAssignmentStatement(const ast::CompoundAssignmentStatement*);
     sem::Statement* ContinueStatement(const ast::ContinueStatement*);
     sem::Statement* DiscardStatement(const ast::DiscardStatement*);
-    sem::Statement* FallthroughStatement(const ast::FallthroughStatement*);
     sem::ForLoopStatement* ForLoopStatement(const ast::ForLoopStatement*);
     sem::WhileStatement* WhileStatement(const ast::WhileStatement*);
     sem::GlobalVariable* GlobalVariable(const ast::Variable*);
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 8c4f478..afdbdfb 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -477,10 +477,9 @@
                 }
 
                 // Propagate all variables assignments to the containing scope if the behavior is
-                // either 'Next' or 'Fallthrough'.
+                // 'Next'.
                 auto& behaviors = sem_.Get(b)->Behaviors();
-                if (behaviors.Contains(sem::Behavior::kNext) ||
-                    behaviors.Contains(sem::Behavior::kFallthrough)) {
+                if (behaviors.Contains(sem::Behavior::kNext)) {
                     for (auto var : scoped_assignments) {
                         current_function_->variables.Set(var.key, var.value);
                     }
@@ -613,8 +612,6 @@
 
             [&](const ast::DiscardStatement*) { return cf; },
 
-            [&](const ast::FallthroughStatement*) { return cf; },
-
             [&](const ast::ForLoopStatement* f) {
                 auto* sem_loop = sem_.Get(f);
                 auto* cfx = CreateNode("loop_start");
@@ -937,46 +934,35 @@
                 info.type = "switch";
 
                 auto* cf_n = v;
-                bool previous_case_has_fallthrough = false;
                 for (auto* c : s->body) {
                     auto* sem_case = sem_.Get(c);
 
-                    if (previous_case_has_fallthrough) {
-                        cf_n = ProcessStatement(cf_n, c->body);
-                    } else {
-                        current_function_->variables.Push();
-                        cf_n = ProcessStatement(v, c->body);
-                    }
+                    current_function_->variables.Push();
+                    cf_n = ProcessStatement(v, c->body);
 
                     if (cf_end) {
                         cf_end->AddEdge(cf_n);
                     }
 
-                    bool has_fallthrough =
-                        sem_case->Behaviors().Contains(sem::Behavior::kFallthrough);
-                    if (!has_fallthrough) {
-                        if (sem_case->Behaviors().Contains(sem::Behavior::kNext)) {
-                            // Propagate variable values to the switch exit nodes.
-                            for (auto* var : current_function_->local_var_decls) {
-                                // Skip variables that were declared inside the switch.
-                                if (auto* lv = var->As<sem::LocalVariable>();
-                                    lv && lv->Statement()->FindFirstParent(
-                                              [&](auto* st) { return st == sem_switch; })) {
-                                    continue;
-                                }
-
-                                // Add an edge from the variable exit node to its new value.
-                                auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
-                                    auto name =
-                                        builder_->Symbols().NameFor(var->Declaration()->symbol);
-                                    return CreateNode(name + "_value_" + info.type + "_exit");
-                                });
-                                exit_node->AddEdge(current_function_->variables.Get(var));
+                    if (sem_case->Behaviors().Contains(sem::Behavior::kNext)) {
+                        // Propagate variable values to the switch exit nodes.
+                        for (auto* var : current_function_->local_var_decls) {
+                            // Skip variables that were declared inside the switch.
+                            if (auto* lv = var->As<sem::LocalVariable>();
+                                lv && lv->Statement()->FindFirstParent(
+                                          [&](auto* st) { return st == sem_switch; })) {
+                                continue;
                             }
+
+                            // Add an edge from the variable exit node to its new value.
+                            auto* exit_node = info.var_exit_nodes.GetOrCreate(var, [&]() {
+                                auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
+                                return CreateNode(name + "_value_" + info.type + "_exit");
+                            });
+                            exit_node->AddEdge(current_function_->variables.Get(var));
                         }
-                        current_function_->variables.Pop();
                     }
-                    previous_case_has_fallthrough = has_fallthrough;
+                    current_function_->variables.Pop();
                 }
 
                 // Update nodes for any variables assigned in the switch statement.
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 42bee0d..df720a1 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -28,7 +28,6 @@
 #include "src/tint/ast/depth_texture.h"
 #include "src/tint/ast/disable_validation_attribute.h"
 #include "src/tint/ast/discard_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/if_statement.h"
@@ -1538,26 +1537,6 @@
     return true;
 }
 
-bool Validator::FallthroughStatement(const sem::Statement* stmt) const {
-    if (auto* block = As<sem::BlockStatement>(stmt->Parent())) {
-        if (auto* c = As<sem::CaseStatement>(block->Parent())) {
-            if (block->Declaration()->Last() == stmt->Declaration()) {
-                if (auto* s = As<sem::SwitchStatement>(c->Parent())) {
-                    if (c->Declaration() != s->Declaration()->body.Back()) {
-                        return true;
-                    }
-                    AddError("a fallthrough statement must not be used in the last switch case",
-                             stmt->Declaration()->source);
-                    return false;
-                }
-            }
-        }
-    }
-    AddError("fallthrough must only be used as the last statement of a case block",
-             stmt->Declaration()->source);
-    return false;
-}
-
 bool Validator::LoopStatement(const sem::LoopStatement* stmt) const {
     if (stmt->Behaviors().Empty()) {
         AddError("loop does not exit", stmt->Declaration()->source.Begin());
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 577b688..500d056 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -223,11 +223,6 @@
     /// @returns true on success, false otherwise
     bool WhileStatement(const sem::WhileStatement* stmt) const;
 
-    /// Validates a fallthrough statement
-    /// @param stmt the fallthrough to validate
-    /// @returns true on success, false otherwise
-    bool FallthroughStatement(const sem::Statement* stmt) const;
-
     /// Validates a function
     /// @param func the function to validate
     /// @param stage the current pipeline stage
diff --git a/src/tint/sem/behavior.cc b/src/tint/sem/behavior.cc
index 4746104..670cc1d 100644
--- a/src/tint/sem/behavior.cc
+++ b/src/tint/sem/behavior.cc
@@ -24,8 +24,6 @@
             return out << "Break";
         case Behavior::kContinue:
             return out << "Continue";
-        case Behavior::kFallthrough:
-            return out << "Fallthrough";
         case Behavior::kNext:
             return out << "Next";
     }
diff --git a/src/tint/sem/behavior.h b/src/tint/sem/behavior.h
index 2557252..011ca72 100644
--- a/src/tint/sem/behavior.h
+++ b/src/tint/sem/behavior.h
@@ -25,7 +25,6 @@
     kReturn,
     kBreak,
     kContinue,
-    kFallthrough,
     kNext,
 };
 
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 290629e..e716b1d 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -22,7 +22,6 @@
 #include <vector>
 
 #include "src/tint/ast/call_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/internal_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
@@ -104,8 +103,8 @@
 
 const char kTempNamePrefix[] = "tint_tmp";
 
-bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) {
-    return IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmts->Last());
+bool last_is_break(const ast::BlockStatement* stmts) {
+    return IsAnyOf<ast::BreakStatement>(stmts->Last());
 }
 
 const char* convert_texel_format_to_glsl(const ast::TexelFormat format) {
@@ -1821,7 +1820,7 @@
         if (!EmitStatements(stmt->body->statements)) {
             return false;
         }
-        if (!last_is_break_or_fallthrough(stmt->body)) {
+        if (!last_is_break(stmt->body)) {
             line() << "break;";
         }
     }
@@ -2748,10 +2747,6 @@
         },
         [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
         [&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
-        [&](const ast::FallthroughStatement*) {
-            line() << "/* fallthrough */";
-            return true;
-        },
         [&](const ast::IfStatement* i) { return EmitIf(i); },
         [&](const ast::LoopStatement* l) { return EmitLoop(l); },
         [&](const ast::ForLoopStatement* l) { return EmitForLoop(l); },
diff --git a/src/tint/writer/glsl/generator_impl_case_test.cc b/src/tint/writer/glsl/generator_impl_case_test.cc
index 4f1c6f3..f12defd 100644
--- a/src/tint/writer/glsl/generator_impl_case_test.cc
+++ b/src/tint/writer/glsl/generator_impl_case_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/writer/glsl/test_helper.h"
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -53,22 +52,6 @@
 )");
 }
 
-TEST_F(GlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
-    auto* s = Switch(1_i, Case(CaseSelector(5_i), Block(create<ast::FallthroughStatement>())),
-                     DefaultCase());
-    WrapInFunction(s);
-
-    GeneratorImpl& gen = Build();
-
-    gen.increment_indent();
-
-    ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-    EXPECT_EQ(gen.result(), R"(  case 5: {
-    /* fallthrough */
-  }
-)");
-}
-
 TEST_F(GlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
     auto* s = Switch(1_i,
                      Case(
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 4c2ac48..9cfe5d7 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -23,7 +23,6 @@
 #include <vector>
 
 #include "src/tint/ast/call_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/internal_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
@@ -2506,18 +2505,7 @@
         return false;
     }
 
-    // Inline all fallthrough case statements. FXC cannot handle fallthroughs.
-    while (tint::Is<ast::FallthroughStatement>(stmt->body->Last())) {
-        case_idx++;
-        stmt = s->body[case_idx];
-        // Generate each fallthrough case statement in a new block. This is done to
-        // prevent symbol collision of variables declared in these cases statements.
-        if (!EmitBlock(stmt->body)) {
-            return false;
-        }
-    }
-
-    if (!tint::IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmt->body->Last())) {
+    if (!tint::IsAnyOf<ast::BreakStatement>(stmt->body->Last())) {
         line() << "break;";
     }
 
@@ -3534,10 +3522,6 @@
         [&](const ast::DiscardStatement* d) {  //
             return EmitDiscard(d);
         },
-        [&](const ast::FallthroughStatement*) {  //
-            line() << "/* fallthrough */";
-            return true;
-        },
         [&](const ast::IfStatement* i) {  //
             return EmitIf(i);
         },
diff --git a/src/tint/writer/hlsl/generator_impl_case_test.cc b/src/tint/writer/hlsl/generator_impl_case_test.cc
index bfcbcaf..2352bbc 100644
--- a/src/tint/writer/hlsl/generator_impl_case_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_case_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/writer/hlsl/test_helper.h"
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -53,28 +52,6 @@
 )");
 }
 
-TEST_F(HlslGeneratorImplTest_Case, Emit_Case_WithFallthrough) {
-    auto* s = Switch(1_i,                                                                  //
-                     Case(CaseSelector(4_i), Block(create<ast::FallthroughStatement>())),  //
-                     Case(CaseSelector(5_i), Block(create<ast::ReturnStatement>())),       //
-                     DefaultCase());
-    WrapInFunction(s);
-
-    GeneratorImpl& gen = Build();
-
-    gen.increment_indent();
-
-    ASSERT_TRUE(gen.EmitCase(s, 0)) << gen.error();
-    EXPECT_EQ(gen.result(), R"(  case 4: {
-    /* fallthrough */
-    {
-      return;
-    }
-    break;
-  }
-)");
-}
-
 TEST_F(HlslGeneratorImplTest_Case, Emit_Case_MultipleSelectors) {
     auto* s = Switch(1_i,
                      Case(utils::Vector{CaseSelector(5_i), CaseSelector(6_i)},
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 1342ac9..534742e 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -25,7 +25,6 @@
 #include "src/tint/ast/bool_literal_expression.h"
 #include "src/tint/ast/call_statement.h"
 #include "src/tint/ast/disable_validation_attribute.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/float_literal_expression.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/interpolate_attribute.h"
@@ -85,8 +84,8 @@
 namespace tint::writer::msl {
 namespace {
 
-bool last_is_break_or_fallthrough(const ast::BlockStatement* stmts) {
-    return IsAnyOf<ast::BreakStatement, ast::FallthroughStatement>(stmts->Last());
+bool last_is_break(const ast::BlockStatement* stmts) {
+    return IsAnyOf<ast::BreakStatement>(stmts->Last());
 }
 
 void PrintF32(std::ostream& out, float value) {
@@ -1587,7 +1586,7 @@
             }
         }
 
-        if (!last_is_break_or_fallthrough(stmt->body)) {
+        if (!last_is_break(stmt->body)) {
             line() << "break;";
         }
     }
@@ -2426,10 +2425,6 @@
         [&](const ast::DiscardStatement* d) {  //
             return EmitDiscard(d);
         },
-        [&](const ast::FallthroughStatement*) {  //
-            line() << "/* fallthrough */";
-            return true;
-        },
         [&](const ast::IfStatement* i) {  //
             return EmitIf(i);
         },
diff --git a/src/tint/writer/msl/generator_impl_case_test.cc b/src/tint/writer/msl/generator_impl_case_test.cc
index 8aae4fe..c218000 100644
--- a/src/tint/writer/msl/generator_impl_case_test.cc
+++ b/src/tint/writer/msl/generator_impl_case_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/writer/msl/test_helper.h"
 
 using namespace tint::number_suffixes;  // NOLINT
@@ -53,22 +52,6 @@
 )");
 }
 
-TEST_F(MslGeneratorImplTest, Emit_Case_WithFallthrough) {
-    auto* s = Switch(1_i, Case(CaseSelector(5_i), Block(create<ast::FallthroughStatement>())),
-                     DefaultCase());
-    WrapInFunction(s);
-
-    GeneratorImpl& gen = Build();
-
-    gen.increment_indent();
-
-    ASSERT_TRUE(gen.EmitCase(s->body[0])) << gen.error();
-    EXPECT_EQ(gen.result(), R"(  case 5: {
-    /* fallthrough */
-  }
-)");
-}
-
 TEST_F(MslGeneratorImplTest, Emit_Case_MultipleSelectors) {
     auto* s = Switch(1_i,
                      Case(
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 6342f71..3c28de3 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -19,7 +19,6 @@
 
 #include "spirv/unified1/GLSL.std.450.h"
 #include "src/tint/ast/call_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/id_attribute.h"
 #include "src/tint/ast/internal_attribute.h"
 #include "src/tint/ast/traverse_expressions.h"
@@ -86,10 +85,6 @@
     return model;
 }
 
-bool LastIsFallthrough(const ast::BlockStatement* stmts) {
-    return !stmts->Empty() && stmts->Last()->Is<ast::FallthroughStatement>();
-}
-
 /// Returns the matrix type that is `type` or that is wrapped by
 /// one or more levels of an arrays inside of `type`.
 /// @param type the given type, which must not be null
@@ -3515,9 +3510,7 @@
     bool generated_default = false;
     auto& body = stmt->body;
     // We output the case statements in order they were entered in the original
-    // source. Each fallthrough goes to the next case entry, so is a forward
-    // branch, otherwise the branch is to the merge block which comes after
-    // the switch statement.
+    // source. The branch is to the merge block which comes after the switch statement.
     for (uint32_t i = 0; i < body.Length(); i++) {
         auto* item = body[i];
 
@@ -3531,17 +3524,7 @@
         if (!GenerateBlockStatement(item->body)) {
             return false;
         }
-
-        if (LastIsFallthrough(item->body)) {
-            if (i == (body.Length() - 1)) {
-                // This case is caught by Resolver validation
-                TINT_UNREACHABLE(Writer, builder_.Diagnostics());
-                return false;
-            }
-            if (!push_function_inst(spv::Op::OpBranch, {Operand(case_ids[i + 1])})) {
-                return false;
-            }
-        } else if (InsideBasicBlock()) {
+        if (InsideBasicBlock()) {
             if (!push_function_inst(spv::Op::OpBranch, {Operand(merge_block_id)})) {
                 return false;
             }
@@ -3671,10 +3654,6 @@
         [&](const ast::CallStatement* c) { return GenerateCallExpression(c->expr) != 0; },
         [&](const ast::ContinueStatement* c) { return GenerateContinueStatement(c); },
         [&](const ast::DiscardStatement* d) { return GenerateDiscardStatement(d); },
-        [&](const ast::FallthroughStatement*) {
-            // Do nothing here, the fallthrough gets handled by the switch code.
-            return true;
-        },
         [&](const ast::IfStatement* i) { return GenerateIfStatement(i); },
         [&](const ast::LoopStatement* l) { return GenerateLoopStatement(l); },
         [&](const ast::ReturnStatement* r) { return GenerateReturnStatement(r); },
diff --git a/src/tint/writer/spirv/builder_static_assert_test.cc b/src/tint/writer/spirv/builder_static_assert_test.cc
index c6d6a51..6e5092d 100644
--- a/src/tint/writer/spirv/builder_static_assert_test.cc
+++ b/src/tint/writer/spirv/builder_static_assert_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/writer/spirv/spv_dump.h"
 #include "src/tint/writer/spirv/test_helper.h"
 
diff --git a/src/tint/writer/spirv/builder_switch_test.cc b/src/tint/writer/spirv/builder_switch_test.cc
index c59c640..6cf8fad 100644
--- a/src/tint/writer/spirv/builder_switch_test.cc
+++ b/src/tint/writer/spirv/builder_switch_test.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/writer/spirv/spv_dump.h"
 #include "src/tint/writer/spirv/test_helper.h"
 
@@ -328,69 +327,6 @@
 )");
 }
 
-TEST_F(BuilderTest, Switch_CaseWithFallthrough) {
-    // switch(a) {
-    //   case 1i:
-    //      v = 1i;
-    //      fallthrough;
-    //   case 2i:
-    //      v = 2i;
-    //   default: {}
-    //      v = 3i;
-    //  }
-
-    auto* v = GlobalVar("v", ty.i32(), ast::AddressSpace::kPrivate);
-    auto* a = GlobalVar("a", ty.i32(), ast::AddressSpace::kPrivate);
-
-    auto* func = Func("a_func", utils::Empty, ty.void_(),
-                      utils::Vector{
-                          Switch(Expr("a"),                                     //
-                                 Case(CaseSelector(1_i),                        //
-                                      Block(Assign("v", 1_i), Fallthrough())),  //
-                                 Case(CaseSelector(2_i),                        //
-                                      Block(Assign("v", 2_i))),                 //
-                                 DefaultCase(Block(Assign("v", 3_i)))),
-                      });
-
-    spirv::Builder& b = Build();
-
-    ASSERT_TRUE(b.GenerateGlobalVariable(v)) << b.error();
-    ASSERT_TRUE(b.GenerateGlobalVariable(a)) << b.error();
-    ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
-
-    EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
-OpName %5 "a"
-OpName %8 "a_func"
-%3 = OpTypeInt 32 1
-%2 = OpTypePointer Private %3
-%4 = OpConstantNull %3
-%1 = OpVariable %2 Private %4
-%5 = OpVariable %2 Private %4
-%7 = OpTypeVoid
-%6 = OpTypeFunction %7
-%15 = OpConstant %3 1
-%16 = OpConstant %3 2
-%17 = OpConstant %3 3
-%8 = OpFunction %7 None %6
-%9 = OpLabel
-%11 = OpLoad %3 %5
-OpSelectionMerge %10 None
-OpSwitch %11 %12 1 %13 2 %14
-%13 = OpLabel
-OpStore %1 %15
-OpBranch %14
-%14 = OpLabel
-OpStore %1 %16
-OpBranch %10
-%12 = OpLabel
-OpStore %1 %17
-OpBranch %10
-%10 = OpLabel
-OpReturn
-OpFunctionEnd
-)");
-}
-
 TEST_F(BuilderTest, Switch_WithNestedBreak) {
     // switch (a) {
     //   case 1:
@@ -460,7 +396,7 @@
     //     return 1i;
     //   }
     //   case 2i: {
-    //     fallthrough;
+    //     return 1i;
     //   }
     //   default: {
     //     return 3i;
@@ -469,9 +405,9 @@
 
     auto* fn = Func("f", utils::Empty, ty.i32(),
                     utils::Vector{
-                        Switch(1_i,                                            //
-                               Case(CaseSelector(1_i), Block(Return(1_i))),    //
-                               Case(CaseSelector(2_i), Block(Fallthrough())),  //
+                        Switch(1_i,                                          //
+                               Case(CaseSelector(1_i), Block(Return(1_i))),  //
+                               Case(CaseSelector(2_i), Block(Return(1_i))),  //
                                DefaultCase(Block(Return(3_i)))),
                     });
 
@@ -491,7 +427,7 @@
 %8 = OpLabel
 OpReturnValue %6
 %9 = OpLabel
-OpBranch %7
+OpReturnValue %6
 %7 = OpLabel
 OpReturnValue %10
 %5 = OpLabel
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index bf7ebd7..6327c6c 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -970,7 +970,6 @@
         [&](const ast::CompoundAssignmentStatement* c) { return EmitCompoundAssign(c); },
         [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
         [&](const ast::DiscardStatement* d) { return EmitDiscard(d); },
-        [&](const ast::FallthroughStatement* f) { return EmitFallthrough(f); },
         [&](const ast::IfStatement* i) { return EmitIf(i); },
         [&](const ast::IncrementDecrementStatement* l) { return EmitIncrementDecrement(l); },
         [&](const ast::LoopStatement* l) { return EmitLoop(l); },
@@ -1093,11 +1092,6 @@
     return true;
 }
 
-bool GeneratorImpl::EmitFallthrough(const ast::FallthroughStatement*) {
-    line() << "fallthrough;";
-    return true;
-}
-
 bool GeneratorImpl::EmitIf(const ast::IfStatement* stmt) {
     {
         auto out = line();
diff --git a/src/tint/writer/wgsl/generator_impl.h b/src/tint/writer/wgsl/generator_impl.h
index 2b7f1c9..7c76cbd 100644
--- a/src/tint/writer/wgsl/generator_impl.h
+++ b/src/tint/writer/wgsl/generator_impl.h
@@ -25,7 +25,6 @@
 #include "src/tint/ast/compound_assignment_statement.h"
 #include "src/tint/ast/continue_statement.h"
 #include "src/tint/ast/discard_statement.h"
-#include "src/tint/ast/fallthrough_statement.h"
 #include "src/tint/ast/for_loop_statement.h"
 #include "src/tint/ast/if_statement.h"
 #include "src/tint/ast/index_accessor_expression.h"
@@ -124,10 +123,6 @@
     /// @param expr the expression
     /// @returns true if the expression was emitted
     bool EmitExpression(std::ostream& out, const ast::Expression* expr);
-    /// Handles generating a fallthrough statement
-    /// @param stmt the fallthrough statement
-    /// @returns true if the statement was successfully emitted
-    bool EmitFallthrough(const ast::FallthroughStatement* stmt);
     /// Handles generating a function
     /// @param func the function to generate
     /// @returns true if the function was emitted
diff --git a/src/tint/writer/wgsl/generator_impl_fallthrough_test.cc b/src/tint/writer/wgsl/generator_impl_fallthrough_test.cc
deleted file mode 100644
index 093fba1..0000000
--- a/src/tint/writer/wgsl/generator_impl_fallthrough_test.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 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/tint/writer/wgsl/test_helper.h"
-
-using namespace tint::number_suffixes;  // NOLINT
-
-namespace tint::writer::wgsl {
-namespace {
-
-using WgslGeneratorImplTest = TestHelper;
-
-TEST_F(WgslGeneratorImplTest, Emit_Fallthrough) {
-    auto* f = create<ast::FallthroughStatement>();
-    WrapInFunction(Switch(1_i,                                //
-                          Case(CaseSelector(1_i), Block(f)),  //
-                          DefaultCase()));
-
-    GeneratorImpl& gen = Build();
-
-    gen.increment_indent();
-
-    ASSERT_TRUE(gen.EmitStatement(f)) << gen.error();
-    EXPECT_EQ(gen.result(), "  fallthrough;\n");
-}
-
-}  // namespace
-}  // namespace tint::writer::wgsl