Skip to content

Handle default case in the middle of the switch statement#27

Closed
lucic71 wants to merge 38 commits intoCpp2Rust:masterfrom
lucic71:default-case-in-the-middle
Closed

Handle default case in the middle of the switch statement#27
lucic71 wants to merge 38 commits intoCpp2Rust:masterfrom
lucic71:default-case-in-the-middle

Conversation

@lucic71
Copy link
Copy Markdown
Contributor

@lucic71 lucic71 commented Apr 23, 2026

This PR adds support for handling code like:

int default_middle(int x) {
  int r = 0;
  switch (x) {
  case 1:
    r = 1;
    break;
  default: // default case in the middle of the switch statement
    r = 99;
    break;
  case 2:
    r = 2;
    break;
  }
  return r;
}

When translating this to rust, the default arm must be moved at the end of the match statement, otherwise all arms after the default one get ignored, making the following assertion fail assert(default_middle(2) == 2):

pub unsafe fn default_middle_4(mut x: i32) -> i32 {
    let mut r: i32 = 0;
    'switch: {
        let __match_cond = x;
        match __match_cond {
            v if v == 1 => {
                r = 1;
                break 'switch;
            }
            v if v == 2 => {
                r = 2;
                break 'switch;
            }
            _ => { // default branch moved at the end
                r = 99;
                break 'switch;
            }
        }
    };
    return r;
}

To implement this, VisitSwitchStmt iterates over all top level cases and defers the conversion of default arm after all non-default arms are converted.

lucic71 added 30 commits April 22, 2026 16:46
Rename visited_cases to visited_switch_cases_ and move it in class
scope. VisitSwitchStmt always resets the set of visited switch cases.

The old implementation was buggy because it saved reused SwitchCase
pointers across translation units, making contain() return true for
switches declared in other TUs.
C++ allows:

  switch (x) {
  default:
    ...
  case 1:
    ...
  }

In rust, default needs to be always on the last position, otherwise,
all values of x, even 1, will hit the default arm. Hence, the above C++
exmaple becomes:

  match x {
    1 => ...
    _ => ...
  }

As such, the algorithm for translating the cases becomes:

  for (case: GetTopLevelSwitchCases()) {
    if (ChainContainsDefault(case)) {
      defer the conversion for the end
    }
    Convert(case)
    Convert(GetSwitchArmBody(case))
  }
  Convert(deferred default)

ChainContainsDefault traverses the stacked case statements starting from
a top level case. For example:

  case 2:
  case 3:
  default:
    ...

is deferred for the end of the match arms and is translated as:

  _ => {}

i.e., drop the case 2, case 3 from the output, only convert as if it was
only default.
@lucic71 lucic71 closed this Apr 23, 2026
@lucic71 lucic71 reopened this Apr 24, 2026
@lucic71 lucic71 closed this Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant