Skip to content

Switch on String: forward default into each length bucket#60

Open
Pign wants to merge 1 commit into
SomeRanDev:mainfrom
Pign:fix/switch-on-string-default
Open

Switch on String: forward default into each length bucket#60
Pign wants to merge 1 commit into
SomeRanDev:mainfrom
Pign:fix/switch-on-string-default

Conversation

@Pign
Copy link
Copy Markdown

@Pign Pign commented May 1, 2026

Summary

compileSwitchOptimizedForStrings (in src/cxxcompiler/subcompilers/Expressions.hx) buckets cases by String.length and emits a switch(__temp.size()) { case N: { /* if-cascade */ } }. It calls compileSwitchAsIfs per bucket with null for the default expression, so the user's default: only ends up attached to the outer size-switch via compileDefaultCase(edef). That fires when the input's length matches no bucket, but not when the length matches a bucket and the value matches no case inside it. Such inputs fall through the inner if-cascade with no statement executed — silently breaking statement-position string switches and tripping -Werror=return-type on value-position ones.

Repro

switch (ch) {           // ch is a 1-char String
    case "H": ...
    case "W": ...
    case "d": ...
    case "e": ...
    default:  return 0;  // never fires for unknown 1-char inputs
}

The generated C++ has case 1: { if (__temp == "H") { ... } else if (__temp == "W") ... } with no terminating else, so an input like "X" exits via break;, the size-switch finishes, and the function returns nothing.

(Caught while writing a small bitmap-font helper in a real Haxe→C++ project; the -Werror=return-type from gcc/clang made it surface immediately.)

Fix

Pass edef into the bucketed compileSwitchAsIfs so each cascade ends with else { /* user default */ }. The outer-default emission is kept — it still covers unmatched-length inputs.

Test plan

  • Two regression tests added in test/unit_testing/tests/Switch/:
    • statement-position switch (matches the bucket length, falls through to default)
    • value-position switch (must produce a value for unmatched same-length inputs, otherwise the C++ output trips -Werror=return-type)
  • Each is wrapped in its own helper function because the codegen declares auto __temp = ...; at function scope; calling two string switches from the same function would currently collide on __temp. That's a separate, pre-existing issue and isn't in scope here.
  • All 46 existing unit tests still pass (haxe Test.hxml).
  • Generated C++ for the new tests builds clean under -Werror.

Reviewed in our fork at Pign#1 before opening here.

`compileSwitchOptimizedForStrings` partitions cases by `String.length`
and emits a `switch(__temp.size()) { case N: { /* if-cascade */ } }`,
calling `compileSwitchAsIfs` per bucket with `null` for the default
expression. The user's `default:` is then only attached to the outer
size-switch (`compileDefaultCase(edef)`), which fires when the input's
length matches no bucket — but not when the length matches a bucket
and the value matches no case inside it. That input falls through the
inner if-cascade with no statement executed, silently breaking
statement-position string switches and tripping `-Werror=return-type`
on value-position ones.

Pass `edef` into the bucketed `compileSwitchAsIfs` so each cascade
ends with `else { /* user default */ }`. The outer-default emission
stays — it still handles unmatched-length inputs.

Repro: a switch on a String value with two same-length cases ("A",
"B") and a default; pass any other length-1 string and the default
never runs. Added two regression tests (statement-position and
value-position) under `test/unit_testing/tests/Switch/`.
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