diff --git a/compiler/lowering.go b/compiler/lowering.go index 4e10c45fa..be0e078a7 100644 --- a/compiler/lowering.go +++ b/compiler/lowering.go @@ -9275,10 +9275,11 @@ const taglessSwitchValue = "(true as boolean)" func (o *LoweringOwner) lowerSwitchStmt(ctx lowerFileContext, stmt *ast.SwitchStmt) ([]loweredStmt, []Diagnostic) { var diagnostics []Diagnostic var init []loweredStmt + var prelude []loweredStmt if stmt.Init != nil { - lowered, initDiagnostics := o.lowerStmt(ctx, stmt.Init) + var initDiagnostics []Diagnostic + ctx, prelude, init, initDiagnostics = o.lowerSwitchInit(ctx, stmt.Init) diagnostics = append(diagnostics, initDiagnostics...) - init = append(init, lowered...) } value := taglessSwitchValue @@ -9361,7 +9362,25 @@ func (o *LoweringOwner) lowerSwitchStmt(ctx lowerFileContext, stmt *ast.SwitchSt return []loweredStmt{lowered}, diagnostics } init = append(init, lowered) - return []loweredStmt{{children: init}}, diagnostics + return append(prelude, loweredStmt{children: init}), diagnostics +} + +// lowerSwitchInit lowers the init statement of a switch or type switch, as +// lowerStmt does for an if init. A short declaration that redeclares a name +// its initializer reads, as in switch pos, err := f(pos), saves the outer +// value in prelude, to emit before the block of the switch. It returns the +// context for the rest of the switch, in which the new names are visible. +func (o *LoweringOwner) lowerSwitchInit( + ctx lowerFileContext, + init ast.Stmt, +) (lowerFileContext, []loweredStmt, []loweredStmt, []Diagnostic) { + initCtx := ctx + var prelude []loweredStmt + if stmtCtx, nextCtx, aliasPrelude, ok := o.lowerShortDeclStatementContext(ctx, init); ok { + initCtx, ctx, prelude = stmtCtx, nextCtx, aliasPrelude + } + lowered, diagnostics := o.lowerStmt(initCtx, init) + return ctx, prelude, lowered, diagnostics } // splitSwitchFallthrough returns the statements of a case body without its @@ -9429,8 +9448,11 @@ func constantComparableType(ctx lowerFileContext, expr ast.Expr) string { func (o *LoweringOwner) lowerTypeSwitchStmt(ctx lowerFileContext, stmt *ast.TypeSwitchStmt) ([]loweredStmt, []Diagnostic) { var lowered []loweredStmt var diagnostics []Diagnostic + var prelude []loweredStmt if stmt.Init != nil { - init, initDiagnostics := o.lowerStmt(ctx, stmt.Init) + var init []loweredStmt + var initDiagnostics []Diagnostic + ctx, prelude, init, initDiagnostics = o.lowerSwitchInit(ctx, stmt.Init) diagnostics = append(diagnostics, initDiagnostics...) lowered = append(lowered, init...) } @@ -9500,7 +9522,7 @@ func (o *LoweringOwner) lowerTypeSwitchStmt(ctx lowerFileContext, stmt *ast.Type switchIR.site = o.lowerTypeSwitchSite(ctx, switchIR) } lowered = append(lowered, loweredStmt{typeSwitch: switchIR}) - return lowered, diagnostics + return append(prelude, lowered...), diagnostics } // lowerTypeSwitchSite returns the hoisted runtime site of a type switch whose diff --git a/tests/tests/shadow_define_init/expected.log b/tests/tests/shadow_define_init/expected.log new file mode 100644 index 000000000..d97064f99 --- /dev/null +++ b/tests/tests/shadow_define_init/expected.log @@ -0,0 +1,6 @@ +switchInit: 2 +ifInit: 2 +block: 2 +single: 4 +forInit: 3 +typeSwitchInit: 5 diff --git a/tests/tests/shadow_define_init/index.ts b/tests/tests/shadow_define_init/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/tests/tests/shadow_define_init/shadow_define_init.go b/tests/tests/shadow_define_init/shadow_define_init.go new file mode 100644 index 000000000..138bba888 --- /dev/null +++ b/tests/tests/shadow_define_init/shadow_define_init.go @@ -0,0 +1,69 @@ +package main + +func next(n int) (int, error) { + return n + 1, nil +} + +// switchInit redeclares pos in a switch init from the outer pos. +func switchInit(pos int) int { + switch pos, err := next(pos); err { + case nil: + return pos + } + return -1 +} + +// ifInit redeclares pos in an if init from the outer pos. +func ifInit(pos int) int { + if pos, err := next(pos); err == nil { + return pos + } + return -1 +} + +// block redeclares pos in a nested block from the outer pos. +func block(pos int) int { + { + pos, err := next(pos) + if err == nil { + return pos + } + } + return -1 +} + +// single redeclares pos with a single value in a switch init. +func single(pos int) int { + switch pos := pos * 2; pos { + case 4: + return pos + } + return -1 +} + +// forInit redeclares i in a for init from the outer i. +func forInit(i int) int { + sum := 0 + for i := i; i < 3; i++ { + sum += i + } + return sum +} + +// typeSwitchInit redeclares v in a type switch init from the outer v. +func typeSwitchInit(v any) int { + switch v, ok := v.(int); any(ok).(type) { + case bool: + return v + } + return -1 +} + +func main() { + println("switchInit:", switchInit(1)) + println("ifInit:", ifInit(1)) + println("block:", block(1)) + println("single:", single(2)) + println("forInit:", forInit(1)) + println("typeSwitchInit:", typeSwitchInit(5)) +} diff --git a/tests/tests/shadow_define_init/shadow_define_init.gs.ts b/tests/tests/shadow_define_init/shadow_define_init.gs.ts new file mode 100644 index 000000000..b89c72bd2 --- /dev/null +++ b/tests/tests/shadow_define_init/shadow_define_init.gs.ts @@ -0,0 +1,114 @@ +// Generated file based on shadow_define_init.go +// Updated when compliance tests are re-run, DO NOT EDIT! + +import * as $ from "@goscript/builtin/index.js" + +export function next(n: number): [number, $.GoError] { + return [n + 1, null] +} + +export function switchInit(pos: number): number { + let __goscriptShadow0 = pos + { + let __goscriptTuple0: any = next(__goscriptShadow0) + let __goscriptShadow1: number = __goscriptTuple0[0] + let err: $.GoError = __goscriptTuple0[1] + let __goscriptSwitch0 = err + switch ((true as boolean)) { + case __goscriptSwitch0 == null: + { + return __goscriptShadow1 + break + } + } + } + return -1 +} + +export function ifInit(pos: number): number { + let __goscriptShadow2 = pos + { + let __goscriptTuple1: any = next(__goscriptShadow2) + let __goscriptShadow3: number = __goscriptTuple1[0] + let err: $.GoError = __goscriptTuple1[1] + if (err == null) { + return __goscriptShadow3 + } + } + return -1 +} + +export function block(pos: number): number { + { + let __goscriptShadow4 = pos + let __goscriptTuple2: any = next(__goscriptShadow4) + let __goscriptShadow5: number = __goscriptTuple2[0] + let err: $.GoError = __goscriptTuple2[1] + if (err == null) { + return __goscriptShadow5 + } + } + return -1 +} + +export function single(pos: number): number { + let __goscriptShadow6 = pos + { + let __goscriptShadow7 = __goscriptShadow6 * 2 + switch (__goscriptShadow7) { + case 4: + { + return __goscriptShadow7 + break + } + } + } + return -1 +} + +export function forInit(i: number): number { + let sum = 0 + { + let __goscriptShadow8 = i + for (let __goscriptShadow9 = __goscriptShadow8; __goscriptShadow9 < 3; __goscriptShadow9++) { + sum += __goscriptShadow9 + } + } + return sum +} + +export function typeSwitchInit(v: any): number { + let __goscriptShadow10 = v + let __goscriptTuple3: any = $.typeAssertTuple(__goscriptShadow10, /* @__PURE__ */ $.basicType("int")) + let __goscriptShadow11: number = __goscriptTuple3[0] + let ok: boolean = __goscriptTuple3[1] + { + const __goscriptTypeSwitchValue = ok + switch ($.typeSwitchCase(__goscriptTypeSwitchValue, __goscriptStatic0())) { + case 0: + { + return __goscriptShadow11 + } + break + } + } + return -1 +} + +export async function main(): globalThis.Promise { + await $.println("switchInit:", switchInit(1)) + await $.println("ifInit:", ifInit(1)) + await $.println("block:", block(1)) + await $.println("single:", single(2)) + await $.println("forInit:", forInit(1)) + await $.println("typeSwitchInit:", typeSwitchInit($.basicInterfaceValue(5, "int"))) +} + +var __goscriptStatic0$value: any +function __goscriptStatic0(): any { + return __goscriptStatic0$value ??= ($.typeSwitchSite([/* @__PURE__ */ $.basicType("bool")])) +} + +if ($.isMainScript(import.meta)) { + await main() +} diff --git a/tests/tests/shadow_define_init/tsconfig.json b/tests/tests/shadow_define_init/tsconfig.json new file mode 100644 index 000000000..4cbc5f6eb --- /dev/null +++ b/tests/tests/shadow_define_init/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "paths": { + "*": [ + "./*" + ], + "@go/github.com/s4wave/goscript/*": [ + "../../../*" + ], + "@goscript/*": [ + "../../../tests/tests/shadow_define_init/run/output/@goscript/*", + "../../../gs/*", + "../../../tests/deps/*" + ], + "@goscript/github.com/s4wave/goscript/tests/tests/shadow_define_init/*": [ + "./*" + ] + } + }, + "extends": "../../../tests/tsconfig.base.json", + "include": [ + "index.ts", + "shadow_define_init.gs.ts" + ] +}