From 65b2edc906ab9826eacc3f8f0bd02229c103341d Mon Sep 17 00:00:00 2001 From: CharlesKWON Date: Sat, 18 Apr 2026 17:11:47 +0900 Subject: [PATCH] =?UTF-8?q?fix(gengo):=20SWITCH=20edge=20cases=20=E2=80=94?= =?UTF-8?q?=20empty=20body,=20OTHERWISE-only,=20EXIT=20semantics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three SWITCH codegen bugs surfaced by harbour-core/tests/switch.prg: 1. Empty SWITCH (`SWITCH x ENDSWITCH`) — legal Harbour, produced by conditional-compile files like switch.prg:13. Previous code emitted `_sw := t.Pop2()` followed by `}` with no matching `{`, closing the enclosing procedure body and producing "syntax error: non-declaration statement outside function body". 2. OTHERWISE-only (no CASE arms) — emitted `} else {` with no opening if, same "unexpected keyword else" category. 3. `EXIT` inside a CASE should break out of the SWITCH — but Five lowers SWITCH to an if/else-if chain, so the generated `break` had nowhere to land ("break is not in a loop, switch, or select"). Fix all three by wrapping every SWITCH in a one-iteration `for` loop. `break` inside a case targets the wrapper, matching Harbour semantics. Empty / OTHERWISE-only bodies still emit valid Go because the for-loop provides the scope boundary regardless of whether any if-chain opened. A trailing `break` keeps the loop one-shot. Also: - `_ = _sw` silences unused-var for empty SWITCH. - Conditionally emit the if-chain closing `}` only when at least one CASE ran. All 15 SWITCH blocks in harbour-core/tests/switch.prg now build and run to completion. FiveSql2 43/43, Harbour compat 56/56, Go test ALL PASS. Co-Authored-By: Claude Opus 4.7 (1M context) --- compiler/gengo/gengo.go | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index be36dfe..311e60f 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -2077,8 +2077,17 @@ func hasLoopStmt(s ast.Stmt) bool { func (g *Generator) emitSwitch(s *ast.SwitchStmt, locals localMap) { + // Wrap the whole thing in a one-iteration `for` so: + // 1. `_sw` stays scoped to the switch. + // 2. `EXIT` inside a CASE emits `break` and targets this loop, + // matching Harbour SWITCH semantics (EXIT terminates SWITCH). + // 3. Empty SWITCH (`SWITCH x ENDSWITCH`, common in conditional- + // compile test files) stays valid Go. + g.writeln("for {") + g.indent++ g.emitExpr(s.Expr) g.writeln("_sw := t.Pop2()") + g.writeln("_ = _sw") // silence unused-var warning when no cases reference it first := true for _, c := range s.Cases { if first { @@ -2096,13 +2105,28 @@ func (g *Generator) emitSwitch(s *ast.SwitchStmt, locals localMap) { g.indent-- } if len(s.Otherwise) > 0 { - g.writeln("} else {") - g.indent++ - for _, stmt := range s.Otherwise { - g.emitStmt(stmt, locals) + if first { + // No CASE arms — emit the OTHERWISE body as-is, no if/else. + for _, stmt := range s.Otherwise { + g.emitStmt(stmt, locals) + } + } else { + g.writeln("} else {") + g.indent++ + for _, stmt := range s.Otherwise { + g.emitStmt(stmt, locals) + } + g.indent-- + g.writeln("}") } - g.indent-- + } else if !first { + // Had CASE arms, no OTHERWISE — close the if/else-if chain. + g.writeln("}") } + // Always break out of our one-iteration `for` wrapper, regardless + // of which (or no) case ran. + g.writeln("break") + g.indent-- g.writeln("}") }