Skip to content

Commit ffa1ceb

Browse files
authored
Merge pull request #593 from goplus/xgopilot/claude/fix-forrange-scope-1771217831
fix: emit pre-range statements to outer scope in ForRange
2 parents 2210ad3 + b174b81 commit ffa1ceb

File tree

2 files changed

+58
-5
lines changed

2 files changed

+58
-5
lines changed

stmt.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,12 @@ func (p *forStmt) End(cb *CodeBuilder, src ast.Node) {
493493
//
494494
// end
495495
type forRangeStmt struct {
496-
names []string
497-
stmt *ast.RangeStmt
498-
x *internal.Elem
499-
old codeBlockCtx
500-
kvt []types.Type
496+
names []string
497+
stmt *ast.RangeStmt
498+
x *internal.Elem
499+
old codeBlockCtx
500+
kvt []types.Type
501+
preStmts []ast.Stmt // statements emitted during range expression compilation
501502

502503
enumName string // XGo_Enum or Gop_Enum
503504

@@ -506,6 +507,10 @@ type forRangeStmt struct {
506507
}
507508

508509
func (p *forRangeStmt) RangeAssignThen(cb *CodeBuilder, pos token.Pos) {
510+
// Extract statements emitted during range expression compilation (e.g., auto-generated
511+
// type assertions like `_autoGo_1, _ := doc.(map[string]any)`). These were emitted to
512+
// the for-range block scope but belong in the outer scope before the for statement.
513+
p.preStmts = cb.clearBlockStmt()
509514
if names := p.names; names != nil { // for k, v := range XXX {
510515
var val ast.Expr
511516
switch len(names) {
@@ -785,6 +790,11 @@ func (p *forRangeStmt) End(cb *CodeBuilder, src ast.Node) {
785790
}
786791
stmts, flows := cb.endBlockStmt(&p.old)
787792
cb.current.flows |= (flows &^ (flowFlagBreak | flowFlagContinue))
793+
// Emit pre-range statements (e.g., type assertions) to the outer scope
794+
// before the for-range statement.
795+
for _, s := range p.preStmts {
796+
cb.emitStmt(s)
797+
}
788798
if n := p.udt; n == 0 {
789799
if p.enumName != "" {
790800
p.stmt.X = &ast.CallExpr{

xgo_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,49 @@ func bar(v *foo.Foo6) {
10831083
`)
10841084
}
10851085

1086+
// Test that pre-range statements are emitted to outer scope
1087+
// Regression test for: goplus/xgo#2629
1088+
func TestForRangePreStmts(t *testing.T) {
1089+
pkg := newMainPackage()
1090+
// Create a function that takes an interface{} parameter
1091+
v := pkg.NewParam(token.NoPos, "data", types.NewInterfaceType(nil, nil).Complete())
1092+
1093+
pkg.NewFunc(nil, "bar", types.NewTuple(v), nil, false).BodyStart(pkg).
1094+
// Start for-range and push the block scope
1095+
ForRange("k", "v").
1096+
// This simulates the scenario where type assertion statements are emitted
1097+
// during range expression compilation. We'll manually emit a statement
1098+
// to the current block (which is the for-range block scope) before
1099+
// calling RangeAssignThen.
1100+
DefineVarStart(token.NoPos, "_autoGo_1", "_").
1101+
Val(ctxRef(pkg, "data")).TypeAssert(types.NewMap(
1102+
types.Typ[types.String],
1103+
types.NewInterfaceType(nil, nil).Complete(),
1104+
), 2).
1105+
EndInit(1).
1106+
// Now call RangeAssignThen which should extract the DefineVar statement
1107+
// and save it as a pre-statement
1108+
VarVal("_autoGo_1").
1109+
RangeAssignThen(token.NoPos).
1110+
Val(pkg.Import("fmt").Ref("Println")).Val(ctxRef(pkg, "k")).Val(ctxRef(pkg, "v")).Call(2).EndStmt().
1111+
End().
1112+
End()
1113+
1114+
// The expected output should have the type assertion BEFORE the for-range loop,
1115+
// not inside it
1116+
domTest(t, pkg, `package main
1117+
1118+
import "fmt"
1119+
1120+
func bar(data interface{}) {
1121+
_autoGo_1, _ := data.(map[string]interface{})
1122+
for k, v := range _autoGo_1 {
1123+
fmt.Println(k, v)
1124+
}
1125+
}
1126+
`)
1127+
}
1128+
10861129
// ----------------------------------------------------------------------------
10871130

10881131
func TestStaticMethod(t *testing.T) {

0 commit comments

Comments
 (0)