Repository: gfx-rs/naga (Rust)

Issue:
[wgsl-in] Remove fallthrough statement
Spec PR: https://github.com/gpuweb/gpuweb/pull/3315
wgsl switch spec grammar allows `case default`
C.f. https://www.w3.org/TR/WGSL/#switch-statement:

> switch_statement :
> | [switch](https://www.w3.org/TR/WGSL/#syntax-switch) [expression](https://www.w3.org/TR/WGSL/#syntax-expression) [brace_left](https://www.w3.org/TR/WGSL/#syntax-brace_left) [switch_body](https://www.w3.org/TR/WGSL/#syntax-switch_body) + [brace_right](https://www.w3.org/TR/WGSL/#syntax-brace_right)
> 
> switch_body :
> | [case_clause](https://www.w3.org/TR/WGSL/#syntax-case_clause)
> 
> | [default_alone_clause](https://www.w3.org/TR/WGSL/#syntax-default_alone_clause)
> 
> case_clause :
> | [case](https://www.w3.org/TR/WGSL/#syntax-case) [case_selectors](https://www.w3.org/TR/WGSL/#syntax-case_selectors) [colon](https://www.w3.org/TR/WGSL/#syntax-colon) ? [compound_statement](https://www.w3.org/TR/WGSL/#syntax-compound_statement)
> 
> default_alone_clause :
> | [default](https://www.w3.org/TR/WGSL/#syntax-default) [colon](https://www.w3.org/TR/WGSL/#syntax-colon) ? [compound_statement](https://www.w3.org/TR/WGSL/#syntax-compound_statement)
> 
> case_selectors :
> | [case_selector](https://www.w3.org/TR/WGSL/#syntax-case_selector) ( [comma](https://www.w3.org/TR/WGSL/#syntax-comma) [case_selector](https://www.w3.org/TR/WGSL/#syntax-case_selector) ) * [comma](https://www.w3.org/TR/WGSL/#syntax-comma) ?
> 
> case_selector :
> | [default](https://www.w3.org/TR/WGSL/#syntax-default)
> 
> | [expression](https://www.w3.org/TR/WGSL/#syntax-expression)

the body is a list of `[switch_body]`s, which can be either `case [case_selectors] ...` or `default ...`, but `[case_selector]` can also be `default`, meaning, if I'm reading this correctly, should allow both:

```wgsl
switch x {
  default { foo }
  case 1 { bar }
}
```

and

```wgsl
switch x {
  case default { foo }
  case 1 { bar }
}
```

but the latter on naga (0.10.0) gives something like:

```
Shader 'box.wgsl' parsing error: expected unsigned/signed integer literal, found 'default'
   ┌─ wgsl:60:12
   │
60 │       case default { uv.x = 0.0; }
   │            ^^^^^^^ expected unsigned/signed integer literal

    expected unsigned/signed integer literal, found 'default'
```


Discussion:
We also need to allow `default` to be used with other case selectors (which was changed by that same gpuweb PR).





Patch:
diff --git a/src/back/glsl/mod.rs b/src/back/glsl/mod.rs
index 3698b79781..72a5d46f77 100644
--- a/src/back/glsl/mod.rs
+++ b/src/back/glsl/mod.rs
@@ -1807,23 +1807,29 @@ impl<'a, W: Write> Writer<'a, W> {
                 for case in cases {
                     match case.value {
                         crate::SwitchValue::Integer(value) => {
-                            writeln!(self.out, "{}case {}{}:", l2, value, type_postfix)?
+                            write!(self.out, "{}case {}{}:", l2, value, type_postfix)?
                         }
-                        crate::SwitchValue::Default => writeln!(self.out, "{}default:", l2)?,
+                        crate::SwitchValue::Default => write!(self.out, "{}default:", l2)?,
+                    }
+
+                    let write_block_braces = !(case.fall_through && case.body.is_empty());
+                    if write_block_braces {
+                        writeln!(self.out, " {{")?;
+                    } else {
+                        writeln!(self.out)?;
                     }
 
                     for sta in case.body.iter() {
                         self.write_stmt(sta, ctx, l2.next())?;
                     }
 
-                    // Write fallthrough comment if the case is fallthrough,
-                    // otherwise write a break, if the case is not already
-                    // broken out of at the end of its body.
-                    if case.fall_through {
-                        writeln!(self.out, "{}/* fallthrough */", l2.next())?;
-                    } else if case.body.last().map_or(true, |s| !s.is_terminator()) {
+                    if !case.fall_through && case.body.last().map_or(true, |s| !s.is_terminator()) {
                         writeln!(self.out, "{}break;", l2.next())?;
                     }
+
+                    if write_block_braces {
+                        writeln!(self.out, "{}}}", l2)?;
+                    }
                 }
 
                 writeln!(self.out, "{}}}", level)?
diff --git a/src/back/hlsl/writer.rs b/src/back/hlsl/writer.rs
index c0f2442309..e1325e5abf 100644
--- a/src/back/hlsl/writer.rs
+++ b/src/back/hlsl/writer.rs
@@ -1825,18 +1825,47 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
 
                 for (i, case) in cases.iter().enumerate() {
                     match case.value {
-                        crate::SwitchValue::Integer(value) => writeln!(
+                        crate::SwitchValue::Integer(value) => write!(
                             self.out,
-                            "{}case {}{}: {{",
+                            "{}case {}{}:",
                             indent_level_1, value, type_postfix
                         )?,
                         crate::SwitchValue::Default => {
-                            writeln!(self.out, "{}default: {{", indent_level_1)?
+                            write!(self.out, "{}default:", indent_level_1)?
                         }
                     }
 
-                    // FXC doesn't support fallthrough so we duplicate the body of the following case blocks
-                    if case.fall_through {
+                    // The new block is not only stylistic, it plays a role here:
+                    // We might end up having to write the same case body
+                    // multiple times due to FXC not supporting fallthrough.
+                    // Therefore, some `Expression`s written by `Statement::Emit`
+                    // will end up having the same name (`_expr<handle_index>`).
+                    // So we need to put each case in its own scope.
+                    let write_block_braces = !(case.fall_through && case.body.is_empty());
+                    if write_block_braces {
+                        writeln!(self.out, " {{")?;
+                    } else {
+                        writeln!(self.out)?;
+                    }
+
+                    // Although FXC does support a series of case clauses before
+                    // a block[^yes], it does not support fallthrough from a
+                    // non-empty case block to the next[^no]. If this case has a
+                    // non-empty body with a fallthrough, emulate that by
+                    // duplicating the bodies of all the cases it would fall
+                    // into as extensions of this case's own body. This makes
+                    // the HLSL output potentially quadratic in the size of the
+                    // Naga IR.
+                    //
+                    // [^yes]: ```hlsl
+                    // case 1:
+                    // case 2: do_stuff()
+                    // ```
+                    // [^no]: ```hlsl
+                    // case 1: do_this();
+                    // case 2: do_that();
+                    // ```
+                    if case.fall_through && !case.body.is_empty() {
                         let curr_len = i + 1;
                         let end_case_idx = curr_len
                             + cases
@@ -1861,12 +1890,16 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> {
                         for sta in case.body.iter() {
                             self.write_stmt(module, sta, func_ctx, indent_level_2)?;
                         }
-                        if case.body.last().map_or(true, |s| !s.is_terminator()) {
+                        if !case.fall_through
+                            && case.body.last().map_or(true, |s| !s.is_terminator())
+                        {
                             writeln!(self.out, "{}break;", indent_level_2)?;
                         }
                     }
 
-                    writeln!(self.out, "{}}}", indent_level_1)?;
+                    if write_block_braces {
+                        writeln!(self.out, "{}}}", indent_level_1)?;
+                    }
                 }
 
                 writeln!(self.out, "{}}}", level)?
diff --git a/src/back/msl/writer.rs b/src/back/msl/writer.rs
index 7617ca9a6d..7cf6ebed62 100644
--- a/src/back/msl/writer.rs
+++ b/src/back/msl/writer.rs
@@ -2546,19 +2546,30 @@ impl<W: Write> Writer<W> {
                     for case in cases.iter() {
                         match case.value {
                             crate::SwitchValue::Integer(value) => {
-                                writeln!(self.out, "{}case {}{}: {{", lcase, value, type_postfix)?;
+                                write!(self.out, "{}case {}{}:", lcase, value, type_postfix)?;
                             }
                             crate::SwitchValue::Default => {
-                                writeln!(self.out, "{}default: {{", lcase)?;
+                                write!(self.out, "{}default:", lcase)?;
                             }
                         }
+
+                        let write_block_braces = !(case.fall_through && case.body.is_empty());
+                        if write_block_braces {
+                            writeln!(self.out, " {{")?;
+                        } else {
+                            writeln!(self.out)?;
+                        }
+
                         self.put_block(lcase.next(), &case.body, context)?;
                         if !case.fall_through
                             && case.body.last().map_or(true, |s| !s.is_terminator())
                         {
                             writeln!(self.out, "{}break;", lcase.next())?;
                         }
-                        writeln!(self.out, "{}}}", lcase)?;
+
+                        if write_block_braces {
+                            writeln!(self.out, "{}}}", lcase)?;
+                        }
                     }
                     writeln!(self.out, "{}}}", level)?;
                 }
diff --git a/src/back/spv/block.rs b/src/back/spv/block.rs
index 10fd5d72aa..9bf0b8c895 100644
--- a/src/back/spv/block.rs
+++ b/src/back/spv/block.rs
@@ -1680,32 +1680,37 @@ impl<'w> BlockContext<'w> {
                         spirv::SelectionControl::NONE,
                     ));
 
-                    let default_id = self.gen_id();
+                    let mut default_id = None;
+                    // id of previous empty fall-through case
+                    let mut last_id = None;
 
-                    let mut reached_default = false;
                     let mut raw_cases = Vec::with_capacity(cases.len());
                     let mut case_ids = Vec::with_capacity(cases.len());
                     for case in cases.iter() {
+                        // take id of previous empty fall-through case or generate a new one
+                        let label_id = last_id.take().unwrap_or_else(|| self.gen_id());
+
+                        if case.fall_through && case.body.is_empty() {
+                            last_id = Some(label_id);
+                        }
+
+                        case_ids.push(label_id);
+
                         match case.value {
                             crate::SwitchValue::Integer(value) => {
-                                let label_id = self.gen_id();
-                                // No cases should be added after the default case is encountered
-                                // since the default case catches all
-                                if !reached_default {
-                                    raw_cases.push(super::instructions::Case {
-                                        value: value as Word,
-                                        label_id,
-                                    });
-                                }
-                                case_ids.push(label_id);
+                                raw_cases.push(super::instructions::Case {
+                                    value: value as Word,
+                                    label_id,
+                                });
                             }
                             crate::SwitchValue::Default => {
-                                case_ids.push(default_id);
-                                reached_default = true;
+                                default_id = Some(label_id);
                             }
                         }
                     }
 
+                    let default_id = default_id.unwrap();
+
                     self.function.consume(
                         block,
                         Instruction::switch(selector_id, default_id, &raw_cases),
@@ -1716,7 +1721,12 @@ impl<'w> BlockContext<'w> {
                         ..loop_context
                     };
 
-                    for (i, (case, label_id)) in cases.iter().zip(case_ids.iter()).enumerate() {
+                    for (i, (case, label_id)) in cases
+                        .iter()
+                        .zip(case_ids.iter())
+                        .filter(|&(case, _)| !(case.fall_through && case.body.is_empty()))
+                        .enumerate()
+                    {
                         let case_finish_id = if case.fall_through {
                             case_ids[i + 1]
                         } else {
@@ -1732,17 +1742,6 @@ impl<'w> BlockContext<'w> {
                         )?;
                     }
 
-                    // If no default was encountered write a empty block to satisfy the presence of
-                    // a block the default label
-                    if !reached_default {
-                        self.write_block(
-                            default_id,
-                            &[],
-                            BlockExit::Branch { target: merge_id },
-                            inner_context,
-                        )?;
-                    }
-
                     block = Block::new(merge_id);
                 }
                 crate::Statement::Loop {
diff --git a/src/back/wgsl/writer.rs b/src/back/wgsl/writer.rs
index 817fa78b0a..34dfba76a1 100644
--- a/src/back/wgsl/writer.rs
+++ b/src/back/wgsl/writer.rs
@@ -880,25 +880,47 @@ impl<W: Write> Writer<W> {
                 };
 
                 let l2 = level.next();
-                if !cases.is_empty() {
-                    for case in cases {
-                        match case.value {
-                            crate::SwitchValue::Integer(value) => {
-                                writeln!(self.out, "{}case {}{}: {{", l2, value, type_postfix)?;
+                let mut new_case = true;
+                for case in cases {
+                    if case.fall_through && !case.body.is_empty() {
+                        // TODO: we could do the same workaround as we did for the HLSL backend
+                        return Err(Error::Unimplemented(
+                            "fall-through switch case block".into(),
+                        ));
+                    }
+
+                    match case.value {
+                        crate::SwitchValue::Integer(value) => {
+                            if new_case {
+                                write!(self.out, "{}case ", l2)?;
                             }
-                            crate::SwitchValue::Default => {
-                                writeln!(self.out, "{}default: {{", l2)?;
+                            write!(self.out, "{}{}", value, type_postfix)?;
+                        }
+                        crate::SwitchValue::Default => {
+                            if new_case {
+                                if case.fall_through {
+                                    write!(self.out, "{}case ", l2)?;
+                                } else {
+                                    write!(self.out, "{}", l2)?;
+                                }
                             }
+                            write!(self.out, "default")?;
                         }
+                    }
 
-                        for sta in case.body.iter() {
-                            self.write_stmt(module, sta, func_ctx, l2.next())?;
-                        }
+                    new_case = !case.fall_through;
 
-                        if case.fall_through {
-                            writeln!(self.out, "{}fallthrough;", l2.next())?;
-                        }
+                    if case.fall_through {
+                        write!(self.out, ", ")?;
+                    } else {
+                        writeln!(self.out, ": {{")?;
+                    }
+
+                    for sta in case.body.iter() {
+                        self.write_stmt(module, sta, func_ctx, l2.next())?;
+                    }
 
+                    if !case.fall_through {
                         writeln!(self.out, "{}}}", l2)?;
                     }
                 }
diff --git a/src/front/wgsl/mod.rs b/src/front/wgsl/mod.rs
index 2873e6c73c..4e2733f69e 100644
--- a/src/front/wgsl/mod.rs
+++ b/src/front/wgsl/mod.rs
@@ -1513,13 +1513,20 @@ impl Parser {
         lexer.span_from(initial)
     }
 
-    fn parse_switch_value<'a>(lexer: &mut Lexer<'a>, uint: bool) -> Result<i32, Error<'a>> {
-        let token_span = lexer.next();
-        match token_span.0 {
-            Token::Number(Ok(Number::U32(num))) if uint => Ok(num as i32),
-            Token::Number(Ok(Number::I32(num))) if !uint => Ok(num),
-            Token::Number(Err(e)) => Err(Error::BadNumber(token_span.1, e)),
-            _ => Err(Error::Unexpected(token_span.1, ExpectedToken::Integer)),
+    fn parse_switch_value<'a>(
+        lexer: &mut Lexer<'a>,
+        uint: bool,
+    ) -> Result<crate::SwitchValue, Error<'a>> {
+        match lexer.next() {
+            (Token::Word("default"), _) => Ok(crate::SwitchValue::Default),
+            (Token::Number(Ok(Number::U32(num))), _) if uint => {
+                Ok(crate::SwitchValue::Integer(num as i32))
+            }
+            (Token::Number(Ok(Number::I32(num))), _) if !uint => {
+                Ok(crate::SwitchValue::Integer(num))
+            }
+            (Token::Number(Err(e)), span) => Err(Error::BadNumber(span, e)),
+            (_, span) => Err(Error::Unexpected(span, ExpectedToken::Integer)),
         }
     }
 
@@ -3576,34 +3583,6 @@ impl Parser {
         Ok(())
     }
 
-    fn parse_switch_case_body<'a, 'out>(
-        &mut self,
-        lexer: &mut Lexer<'a>,
-        mut context: StatementContext<'a, '_, 'out>,
-    ) -> Result<(bool, crate::Block), Error<'a>> {
-        let mut body = crate::Block::new();
-        // Push a new lexical scope for the switch case body
-        context.symbol_table.push_scope();
-
-        lexer.expect(Token::Paren('{'))?;
-        let fall_through = loop {
-            // default statements
-            if lexer.skip(Token::Word("fallthrough")) {
-                lexer.expect(Token::Separator(';'))?;
-                lexer.expect(Token::Paren('}'))?;
-                break true;
-            }
-            if lexer.skip(Token::Paren('}')) {
-                break false;
-            }
-            self.parse_statement(lexer, context.reborrow(), &mut body, false)?;
-        };
-        // Pop the switch case body lexical scope
-        context.symbol_table.pop_scope();
-
-        Ok((fall_through, body))
-    }
-
     fn parse_statement<'a, 'out>(
         &mut self,
         lexer: &mut Lexer<'a>,
@@ -3619,26 +3598,9 @@ impl Parser {
                 return Ok(());
             }
             (Token::Paren('{'), _) => {
-                self.push_rule_span(Rule::Block, lexer);
-                // Push a new lexical scope for the block statement
-                context.symbol_table.push_scope();
-
-                let _ = lexer.next();
-                let mut statements = crate::Block::new();
-                while !lexer.skip(Token::Paren('}')) {
-                    self.parse_statement(
-                        lexer,
-                        context.reborrow(),
-                        &mut statements,
-                        is_uniform_control_flow,
-                    )?;
-                }
-                // Pop the block statement lexical scope
-                context.symbol_table.pop_scope();
-
-                self.pop_rule_span(lexer);
-                let span = NagaSpan::from(self.pop_rule_span(lexer));
-                block.push(crate::Statement::Block(statements), span);
+                let body = self.parse_block(lexer, context, is_uniform_control_flow)?;
+                let span = self.pop_rule_span(lexer);
+                block.push(crate::Statement::Block(body), NagaSpan::from(span));
                 return Ok(());
             }
             (Token::Word(word), _) => {
@@ -3945,37 +3907,34 @@ impl Parser {
                                             break value;
                                         }
                                         cases.push(crate::SwitchCase {
-                                            value: crate::SwitchValue::Integer(value),
+                                            value,
                                             body: crate::Block::new(),
                                             fall_through: true,
                                         });
                                     };
 
-                                    let (fall_through, body) =
-                                        self.parse_switch_case_body(lexer, context.reborrow())?;
-
+                                    let body =
+                                        self.parse_block(lexer, context.reborrow(), false)?;
                                     cases.push(crate::SwitchCase {
-                                        value: crate::SwitchValue::Integer(value),
+                                        value,
                                         body,
-                                        fall_through,
+                                        fall_through: false,
                                     });
                                 }
                                 (Token::Word("default"), _) => {
                                     lexer.skip(Token::Separator(':'));
-                                    let (fall_through, body) =
-                                        self.parse_switch_case_body(lexer, context.reborrow())?;
+
+                                    let body =
+                                        self.parse_block(lexer, context.reborrow(), false)?;
                                     cases.push(crate::SwitchCase {
                                         value: crate::SwitchValue::Default,
                                         body,
-                                        fall_through,
+                                        fall_through: false,
                                     });
                                 }
                                 (Token::Paren('}'), _) => break,
-                                other => {
-                                    return Err(Error::Unexpected(
-                                        other.1,
-                                        ExpectedToken::SwitchItem,
-                                    ))
+                                (_, span) => {
+                                    return Err(Error::Unexpected(span, ExpectedToken::SwitchItem))
                                 }
                             }
                         }
@@ -3993,7 +3952,6 @@ impl Parser {
                                 lexer,
                                 context.as_expression(&mut body, &mut emitter),
                             )?;
-                            lexer.expect(Token::Paren('{'))?;
                             body.extend(emitter.finish(context.expressions));
                             Ok(condition)
                         })?;
@@ -4007,14 +3965,8 @@ impl Parser {
                             },
                             NagaSpan::from(span),
                         );
-                        // Push a lexical scope for the while loop body
-                        context.symbol_table.push_scope();
 
-                        while !lexer.skip(Token::Paren('}')) {
-                            self.parse_statement(lexer, context.reborrow(), &mut body, false)?;
-                        }
-                        // Pop the while loop body lexical scope
-                        context.symbol_table.pop_scope();
+                        body.extend_block(self.parse_block(lexer, context.reborrow(), false)?);
 
                         Some(crate::Statement::Loop {
                             body,
@@ -4093,11 +4045,9 @@ impl Parser {
                             }
                             lexer.expect(Token::Paren(')'))?;
                         }
-                        lexer.expect(Token::Paren('{'))?;
 
-                        while !lexer.skip(Token::Paren('}')) {
-                            self.parse_statement(lexer, context.reborrow(), &mut body, false)?;
-                        }
+                        body.extend_block(self.parse_block(lexer, context.reborrow(), false)?);
+
                         // Pop the for loop lexical scope
                         context.symbol_table.pop_scope();
 
@@ -4306,6 +4256,7 @@ impl Parser {
         })
     }
 
+    /// compound_statement
     fn parse_block<'a>(
         &mut self,
         lexer: &mut Lexer<'a>,
@@ -4326,7 +4277,7 @@ impl Parser {
                 is_uniform_control_flow,
             )?;
         }
-        //Pop the block lexical scope
+        // Pop the block lexical scope
         context.symbol_table.pop_scope();
 
         self.pop_rule_span(lexer);
diff --git a/src/lib.rs b/src/lib.rs
index e122d1224c..abd6cb858b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1466,6 +1466,22 @@ pub enum Statement {
         reject: Block,
     },
     /// Conditionally executes one of multiple blocks, based on the value of the selector.
+    ///
+    /// Each case must have a distinct [`value`], exactly one of which must be
+    /// [`Default`]. The `Default` may appear at any position, and covers all
+    /// values not explicitly appearing in other cases. A `Default` appearing in
+    /// the midst of the list of cases does not shadow the cases that follow.
+    ///
+    /// Some backend languages don't support fallthrough (HLSL due to FXC,
+    /// WGSL), and may translate fallthrough cases in the IR by duplicating
+    /// code. However, all backend languages do support cases selected by
+    /// multiple values, like `case 1: case 2: case 3: { ... }`. This is
+    /// represented in the IR as a series of fallthrough cases with empty
+    /// bodies, except for the last.
+    ///
+    /// [`value`]: SwitchCase::value
+    /// [`body`]: SwitchCase::body
+    /// [`Default`]: SwitchValue::Default
     Switch {
         selector: Handle<Expression>, //int
         cases: Vec<SwitchCase>,


Test patch:
diff --git a/src/front/wgsl/tests.rs b/src/front/wgsl/tests.rs
index 33fc541acb..6aaa505d4d 100644
--- a/src/front/wgsl/tests.rs
+++ b/src/front/wgsl/tests.rs
@@ -249,8 +249,7 @@ fn parse_switch() {
             var pos: f32;
             switch (3) {
                 case 0, 1: { pos = 0.0; }
-                case 2: { pos = 1.0; fallthrough; }
-                case 3: {}
+                case 2: { pos = 1.0; }
                 default: { pos = 3.0; }
             }
         }
@@ -267,8 +266,7 @@ fn parse_switch_optional_colon_in_case() {
             var pos: f32;
             switch (3) {
                 case 0, 1 { pos = 0.0; }
-                case 2 { pos = 1.0; fallthrough; }
-                case 3 {}
+                case 2 { pos = 1.0; }
                 default { pos = 3.0; }
             }
         }
@@ -277,6 +275,23 @@ fn parse_switch_optional_colon_in_case() {
     .unwrap();
 }
 
+#[test]
+fn parse_switch_default_in_case() {
+    parse_str(
+        "
+        fn main() {
+            var pos: f32;
+            switch (3) {
+                case 0, 1: { pos = 0.0; }
+                case 2: {}
+                case default, 3: { pos = 3.0; }
+            }
+        }
+    ",
+    )
+    .unwrap();
+}
+
 #[test]
 fn parse_parentheses_switch() {
     parse_str(
diff --git a/tests/in/control-flow.wgsl b/tests/in/control-flow.wgsl
index 787742d71d..5a0ef1cbbf 100644
--- a/tests/in/control-flow.wgsl
+++ b/tests/in/control-flow.wgsl
@@ -22,15 +22,13 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
         case 2: {
             pos = 1;
         }
-        case 3: {
+        case 3, 4: {
             pos = 2;
-            fallthrough;
         }
-        case 4: {
+        case 5: {
             pos = 3;
-            fallthrough;
         }
-        default: {
+        case default, 6: {
             pos = 4;
         }
     }
@@ -54,7 +52,6 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
         }
         case 3: {
             pos = 2;
-            fallthrough;
         }
         case 4: {}
         default: {
diff --git a/tests/out/glsl/control-flow.main.Compute.glsl b/tests/out/glsl/control-flow.main.Compute.glsl
index 5446e36098..b877f9cb69 100644
--- a/tests/out/glsl/control-flow.main.Compute.glsl
+++ b/tests/out/glsl/control-flow.main.Compute.glsl
@@ -8,17 +8,20 @@ layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
 
 void switch_default_break(int i) {
     switch(i) {
-        default:
+        default: {
             break;
+        }
     }
 }
 
 void switch_case_break() {
     switch(0) {
-        case 0:
+        case 0: {
             break;
-        default:
+        }
+        default: {
             break;
+        }
     }
     return;
 }
@@ -26,10 +29,12 @@ void switch_case_break() {
 void loop_switch_continue(int x) {
     while(true) {
         switch(x) {
-            case 1:
+            case 1: {
                 continue;
-            default:
+            }
+            default: {
                 break;
+            }
         }
     }
     return;
@@ -43,50 +48,65 @@ void main() {
     memoryBarrierShared();
     barrier();
     switch(1) {
-        default:
+        default: {
             pos = 1;
             break;
+        }
     }
     int _e4 = pos;
     switch(_e4) {
-        case 1:
+        case 1: {
             pos = 0;
             break;
-        case 2:
+        }
+        case 2: {
             pos = 1;
             break;
+        }
         case 3:
+        case 4: {
             pos = 2;
-            /* fallthrough */
-        case 4:
+            break;
+        }
+        case 5: {
             pos = 3;
-            /* fallthrough */
+            break;
+        }
         default:
+        case 6: {
             pos = 4;
             break;
+        }
     }
     switch(0u) {
-        case 0u:
+        case 0u: {
             break;
-        default:
+        }
+        default: {
             break;
+        }
     }
     int _e11 = pos;
     switch(_e11) {
-        case 1:
+        case 1: {
             pos = 0;
             break;
-        case 2:
+        }
+        case 2: {
             pos = 1;
             return;
-        case 3:
+        }
+        case 3: {
             pos = 2;
-            /* fallthrough */
-        case 4:
             return;
-        default:
+        }
+        case 4: {
+            return;
+        }
+        default: {
             pos = 3;
             return;
+        }
     }
 }
 
diff --git a/tests/out/hlsl/control-flow.hlsl b/tests/out/hlsl/control-flow.hlsl
index 4b879ef20c..7f7a4b6e5c 100644
--- a/tests/out/hlsl/control-flow.hlsl
+++ b/tests/out/hlsl/control-flow.hlsl
@@ -59,28 +59,17 @@ void main(uint3 global_id : SV_DispatchThreadID)
             pos = 1;
             break;
         }
-        case 3: {
-            {
-                pos = 2;
-            }
-            {
-                pos = 3;
-            }
-            {
-                pos = 4;
-            }
+        case 3:
+        case 4: {
+            pos = 2;
             break;
         }
-        case 4: {
-            {
-                pos = 3;
-            }
-            {
-                pos = 4;
-            }
+        case 5: {
+            pos = 3;
             break;
         }
-        default: {
+        default:
+        case 6: {
             pos = 4;
             break;
         }
@@ -104,12 +93,8 @@ void main(uint3 global_id : SV_DispatchThreadID)
             return;
         }
         case 3: {
-            {
-                pos = 2;
-            }
-            {
-                return;
-            }
+            pos = 2;
+            return;
         }
         case 4: {
             return;
diff --git a/tests/out/msl/control-flow.msl b/tests/out/msl/control-flow.msl
index 4a0b805663..be396e23a8 100644
--- a/tests/out/msl/control-flow.msl
+++ b/tests/out/msl/control-flow.msl
@@ -68,13 +68,17 @@ kernel void main_(
             pos = 1;
             break;
         }
-        case 3: {
+        case 3:
+        case 4: {
             pos = 2;
+            break;
         }
-        case 4: {
+        case 5: {
             pos = 3;
+            break;
         }
-        default: {
+        default:
+        case 6: {
             pos = 4;
             break;
         }
@@ -99,6 +103,7 @@ kernel void main_(
         }
         case 3: {
             pos = 2;
+            return;
         }
         case 4: {
             return;
diff --git a/tests/out/spv/control-flow.spvasm b/tests/out/spv/control-flow.spvasm
index dfd1b145a2..e1885e7159 100644
--- a/tests/out/spv/control-flow.spvasm
+++ b/tests/out/spv/control-flow.spvasm
@@ -45,11 +45,11 @@ OpFunctionEnd
 OpBranch %22
 %22 = OpLabel
 OpSelectionMerge %23 None
-OpSwitch %5 %24 0 %25
-%25 = OpLabel
-OpBranch %23
+OpSwitch %5 %25 0 %24
 %24 = OpLabel
 OpBranch %23
+%25 = OpLabel
+OpBranch %23
 %23 = OpLabel
 OpReturn
 OpFunctionEnd
@@ -64,10 +64,10 @@ OpLoopMerge %31 %33 None
 OpBranch %32
 %32 = OpLabel
 OpSelectionMerge %34 None
-OpSwitch %27 %35 1 %36
-%36 = OpLabel
-OpBranch %33
+OpSwitch %27 %36 1 %35
 %35 = OpLabel
+OpBranch %33
+%36 = OpLabel
 OpBranch %34
 %34 = OpLabel
 OpBranch %33
@@ -92,45 +92,45 @@ OpBranch %50
 %50 = OpLabel
 %52 = OpLoad  %4  %37
 OpSelectionMerge %53 None
-OpSwitch %52 %54 1 %55 2 %56 3 %57 4 %58
-%55 = OpLabel
+OpSwitch %52 %58 1 %54 2 %55 3 %56 4 %56 5 %57 6 %58
+%54 = OpLabel
 OpStore %37 %5
 OpBranch %53
-%56 = OpLabel
+%55 = OpLabel
 OpStore %37 %3
 OpBranch %53
-%57 = OpLabel
+%56 = OpLabel
 OpStore %37 %6
-OpBranch %58
-%58 = OpLabel
+OpBranch %53
+%57 = OpLabel
 OpStore %37 %7
-OpBranch %54
-%54 = OpLabel
+OpBranch %53
+%58 = OpLabel
 OpStore %37 %8
 OpBranch %53
 %53 = OpLabel
 OpSelectionMerge %59 None
-OpSwitch %9 %60 0 %61
-%61 = OpLabel
-OpBranch %59
+OpSwitch %9 %61 0 %60
 %60 = OpLabel
 OpBranch %59
+%61 = OpLabel
+OpBranch %59
 %59 = OpLabel
 %62 = OpLoad  %4  %37
 OpSelectionMerge %63 None
-OpSwitch %62 %64 1 %65 2 %66 3 %67 4 %68
-%65 = OpLabel
+OpSwitch %62 %68 1 %64 2 %65 3 %66 4 %67
+%64 = OpLabel
 OpStore %37 %5
 OpBranch %63
-%66 = OpLabel
+%65 = OpLabel
 OpStore %37 %3
 OpReturn
-%67 = OpLabel
+%66 = OpLabel
 OpStore %37 %6
-OpBranch %68
-%68 = OpLabel
 OpReturn
-%64 = OpLabel
+%67 = OpLabel
+OpReturn
+%68 = OpLabel
 OpStore %37 %7
 OpReturn
 %63 = OpLabel
diff --git a/tests/out/wgsl/control-flow.wgsl b/tests/out/wgsl/control-flow.wgsl
index ce3911f2b1..2872406d76 100644
--- a/tests/out/wgsl/control-flow.wgsl
+++ b/tests/out/wgsl/control-flow.wgsl
@@ -50,15 +50,13 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
         case 2: {
             pos = 1;
         }
-        case 3: {
+        case 3, 4: {
             pos = 2;
-            fallthrough;
         }
-        case 4: {
+        case 5: {
             pos = 3;
-            fallthrough;
         }
-        default: {
+        case default, 6: {
             pos = 4;
         }
     }
@@ -80,7 +78,7 @@ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
         }
         case 3: {
             pos = 2;
-            fallthrough;
+            return;
         }
         case 4: {
             return;
diff --git a/tests/wgsl-errors.rs b/tests/wgsl-errors.rs
index dd3e3dda6c..594d92354d 100644
--- a/tests/wgsl-errors.rs
+++ b/tests/wgsl-errors.rs
@@ -1370,28 +1370,6 @@ fn select() {
     }
 }
 
-#[test]
-fn last_case_falltrough() {
-    check_validation! {
-        "
-        fn test_falltrough() {
-          switch(0) {
-            default: {}
-            case 0: {
-              fallthrough;
-            }
-          }
-        }
-        ":
-        Err(
-            naga::valid::ValidationError::Function {
-                source: naga::valid::FunctionError::LastCaseFallTrough,
-                ..
-            },
-        )
-    }
-}
-
 #[test]
 fn missing_default_case() {
     check_validation! {