Repository: goplus/gop
Pull request #478 (2020-07-18 23:46:12)

continue and break in range flow without label
```go
for k, v <- [1, 2, 3] {
	if k < 2 {
		continue
	}
	if k > 2 {
		break
	}
	println(k, v)
}
```

JessonChan: Don't know why travis-ci can’t be triggered
codecov-commenter: # [Codecov](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=h1) Report
> Merging [#478](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=desc) into [master](https://codecov.io/gh/goplus/gop/commit/63a00830455f280264800485a156e0cbe758baba&el=desc) will **increase** coverage by `0.03%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/goplus/gop/pull/478/graphs/tree.svg?width=650&height=150&src=pr&token=4qln9Wk3Pn)](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #478      +/-   ##
==========================================
+ Coverage   88.84%   88.88%   +0.03%     
==========================================
  Files          37       37              
  Lines        9547     9578      +31     
==========================================
+ Hits         8482     8513      +31     
  Misses        786      786              
  Partials      279      279              
```


| [Impacted Files](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cl/compile.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-Y2wvY29tcGlsZS5nbw==) | `75.66% <ø> (ø)` | |
| [exec/bytecode/code.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb2RlLmdv) | `100.00% <ø> (ø)` | |
| [cl/expr.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-Y2wvZXhwci5nbw==) | `83.17% <100.00%> (+0.09%)` | :arrow_up: |
| [cl/stmt.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-Y2wvc3RtdC5nbw==) | `93.19% <100.00%> (+0.12%)` | :arrow_up: |
| [exec/bytecode/context.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb250ZXh0Lmdv) | `91.37% <100.00%> (+0.22%)` | :arrow_up: |
| [exec/bytecode/goinstr.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9nb2luc3RyLmdv) | `94.60% <100.00%> (+0.12%)` | :arrow_up: |
| [exec/golang/func.go](https://codecov.io/gh/goplus/gop/pull/478/diff?src=pr&el=tree#diff-ZXhlYy9nb2xhbmcvZnVuYy5nbw==) | `94.44% <100.00%> (+0.47%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=footer). Last update [63a0083...ef63487](https://codecov.io/gh/goplus/gop/pull/478?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index 3b07e4a8..8f2560a1 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -230,31 +230,37 @@ func (fc *funcCtx) nextFlow(post, done exec.Label, name string) {
 	}
 }
 
-func (fc *funcCtx) getBreakLabel(labelName string) exec.Label {
+func (fc *funcCtx) getBreakLabel(labelName string) (label exec.Label, rangeFor bool) {
 	if fc.currentFlow == nil {
-		return nil
+		return nil, false
+	}
+	if fc.currentFlow.postLabel == nil && fc.currentFlow.doneLabel == nil {
+		return nil, true
 	}
 	for i := fc.currentFlow; i != nil; i = i.parent {
 		if i.doneLabel != nil {
 			if labelName == "" || i.name == labelName {
-				return i.doneLabel
+				return i.doneLabel, false
 			}
 		}
 	}
-	return nil
+	return nil, false
 }
-func (fc *funcCtx) getContinueLabel(labelName string) exec.Label {
+func (fc *funcCtx) getContinueLabel(labelName string) (label exec.Label, rangeFor bool) {
 	if fc.currentFlow == nil {
-		return nil
+		return nil, false
+	}
+	if fc.currentFlow.postLabel == nil && fc.currentFlow.doneLabel == nil {
+		return nil, true
 	}
 	for i := fc.currentFlow; i != nil; i = i.parent {
 		if i.postLabel != nil {
 			if labelName == "" || i.name == labelName {
-				return i.postLabel
+				return i.postLabel, false
 			}
 		}
 	}
-	return nil
+	return nil, false
 }
 
 func (fc *funcCtx) checkLabels() {
diff --git a/cl/expr.go b/cl/expr.go
index 4eec31f9..d1880279 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -356,6 +356,10 @@ func compileForPhrase(parent *blockCtx, f ast.ForPhrase, noExecCtx bool) (*block
 		varVal = ctx.insertVar(f.Value.Name, typVal, true).v
 	}
 	return ctx, func(exprElt func()) {
+		ctx.nextFlow(nil, nil, "")
+		defer func() {
+			ctx.currentFlow = ctx.currentFlow.parent
+		}()
 		exprX()
 		out := ctx.out
 		c := ctx.NewForPhrase(typData)
diff --git a/cl/stmt.go b/cl/stmt.go
index ba55bdf6..7b242c3b 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -191,22 +191,30 @@ func compileBranchStmt(ctx *blockCtx, v *ast.BranchStmt) {
 		if v.Label != nil {
 			labelName = v.Label.Name
 		}
-		label := ctx.getBreakLabel(labelName)
+		label, rangeFor := ctx.getBreakLabel(labelName)
 		if label != nil {
 			ctx.out.Jmp(label)
 			return
 		}
+		if rangeFor {
+			ctx.out.Return(exec.BreakAsReturn)
+			return
+		}
 		log.Panicln("break statement out of for/switch/select statements")
 	case token.CONTINUE:
 		var labelName string
 		if v.Label != nil {
 			labelName = v.Label.Name
 		}
-		label := ctx.getContinueLabel(labelName)
+		label, rangeFor := ctx.getContinueLabel(labelName)
 		if label != nil {
 			ctx.out.Jmp(label)
 			return
 		}
+		if rangeFor {
+			ctx.out.Return(exec.ContinueAsReturn)
+			return
+		}
 		log.Panicln("continue statement out of for statements")
 	}
 }
diff --git a/exec.spec/interface.go b/exec.spec/interface.go
index ab42c12f..ae4695bf 100644
--- a/exec.spec/interface.go
+++ b/exec.spec/interface.go
@@ -54,6 +54,12 @@ func (p Reserved) Push(b Builder, val interface{}) {
 	b.ReservedAsPush(p, val)
 }
 
+// BreakAsReturn
+const BreakAsReturn = -2
+
+// ContinueAsReturn
+const ContinueAsReturn = -3
+
 // ForPhrase represents a for range phrase.
 type ForPhrase interface {
 }
diff --git a/exec/bytecode/code.go b/exec/bytecode/code.go
index a994f19f..82a0e037 100644
--- a/exec/bytecode/code.go
+++ b/exec/bytecode/code.go
@@ -123,6 +123,8 @@ const (
 	iPushNil        = (opPushValSpec << bitsOpShift) | 2
 	iPushUnresolved = (opInvalid << bitsOpShift)
 	iReturn         = (opReturn << bitsOpShift) | (0xffffffff & bitsOperand)
+	iBreak          = (opReturn << bitsOpShift) | (0xfffffffe & bitsOperand)
+	iContinue       = (opReturn << bitsOpShift) | (0xfffffffd & bitsOperand)
 )
 
 const (
diff --git a/exec/bytecode/context.go b/exec/bytecode/context.go
index 325ce9e1..d120f9a8 100644
--- a/exec/bytecode/context.go
+++ b/exec/bytecode/context.go
@@ -204,7 +204,10 @@ func (ctx *Context) Exec(ip, ipEnd int) {
 		case opCallGoFuncv:
 			execGoFuncv(i, ctx)
 		case opReturn:
-			if i != iReturn {
+			ctx.ip = int(i)
+			if i == iBreak || i == iContinue || i == iReturn {
+				ctx.ip = int(i)
+			} else {
 				ctx.ip = ipReturnN
 			}
 			goto finished
diff --git a/exec/bytecode/goinstr.go b/exec/bytecode/goinstr.go
index 44b49039..f93b67bc 100644
--- a/exec/bytecode/goinstr.go
+++ b/exec/bytecode/goinstr.go
@@ -61,6 +61,7 @@ func (c *ForPhrase) execListRange(data reflect.Value, ctx *Context) {
 	var key, val = c.Key, c.Value
 	var blockScope = c.block != nil
 	var old savedScopeCtx
+Loop:
 	for i := 0; i < n; i++ {
 		if key != nil {
 			ctx.setVar(key.idx, i)
@@ -83,6 +84,14 @@ func (c *ForPhrase) execListRange(data reflect.Value, ctx *Context) {
 		if blockScope {
 			ctx.restoreScope(old)
 		}
+		switch ctx.ip {
+		case iReturn, ipReturnN:
+			return
+		case iBreak:
+			break Loop
+		case iContinue:
+			continue
+		}
 	}
 	ctx.ip = ipEnd
 }
@@ -93,6 +102,7 @@ func (c *ForPhrase) execMapRange(data reflect.Value, ctx *Context) {
 	var key, val = c.Key, c.Value
 	var blockScope = c.block != nil
 	var old savedScopeCtx
+Loop:
 	for iter.Next() {
 		if key != nil {
 			ctx.setVar(key.idx, iter.Key().Interface())
@@ -115,6 +125,14 @@ func (c *ForPhrase) execMapRange(data reflect.Value, ctx *Context) {
 		if blockScope {
 			ctx.restoreScope(old)
 		}
+		switch ctx.ip {
+		case iReturn, ipReturnN:
+			return
+		case iBreak:
+			break Loop
+		case iContinue:
+			continue
+		}
 	}
 	ctx.ip = ipEnd
 }
diff --git a/exec/golang/func.go b/exec/golang/func.go
index 65809261..548a6eef 100644
--- a/exec/golang/func.go
+++ b/exec/golang/func.go
@@ -173,16 +173,29 @@ func (p *Builder) DefineFunc(fun exec.FuncInfo) *Builder {
 // Return instr
 func (p *Builder) Return(n int32) *Builder {
 	var results []ast.Expr
-	if n > 0 {
-		arity := int(n)
-		args := p.rhs.GetArgs(arity)
-		results = make([]ast.Expr, n)
-		for i, arg := range args {
-			results[i] = arg.(ast.Expr)
+	var stmt ast.Stmt
+	switch n {
+	case exec.BreakAsReturn:
+		stmt = &ast.BranchStmt{
+			Tok: token.BREAK,
 		}
-		p.rhs.PopN(arity)
+	case exec.ContinueAsReturn:
+		stmt = &ast.BranchStmt{
+			Tok: token.CONTINUE,
+		}
+	default:
+		if n > 0 {
+			arity := int(n)
+			args := p.rhs.GetArgs(arity)
+			results = make([]ast.Expr, n)
+			for i, arg := range args {
+				results[i] = arg.(ast.Expr)
+			}
+			p.rhs.PopN(arity)
+		}
+		stmt = &ast.ReturnStmt{Results: results}
 	}
-	p.rhs.Push(&ast.ReturnStmt{Results: results})
+	p.rhs.Push(stmt)
 	return p
 }
 


Test patch:
diff --git a/cl/stmt_test.go b/cl/stmt_test.go
index cc7576ae..14257fc1 100644
--- a/cl/stmt_test.go
+++ b/cl/stmt_test.go
@@ -915,3 +915,242 @@ var testGotoLabelClauses = map[string]testData{
 func TestGotoLabelStmt(t *testing.T) {
 	testScripts(t, "TestGotoLabelStmt", testGotoLabelClauses)
 }
+
+// -----------------------------------------------------------------------------
+
+var testRangeStmtWithBranchClauses = map[string]testData{
+	"range_with_continue": {clause: `
+					arr := [1,3,5,7]
+					sum := 0
+					for i,_:=range arr {
+						if arr[i]<5{
+							continue
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "12\n"},
+	"range_with_break": {clause: `
+					arr := [1,3,5,7]
+					sum := 0
+					for i,_:=range arr {
+						if arr[i]>5{
+							break
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "9\n"},
+
+	"range_with_continue_break": {clause: `
+					arr := [1,3,5,7]
+					sum := 0
+					for i,_:=range arr {
+						if arr[i]>5{
+							break
+						}
+						for j:=0;j<len(arr);j++{
+							if arr[j]<5{
+								continue
+							}
+							sum+=arr[j]
+						}
+					}
+					println(sum)
+					`, want: "36\n"},
+	"range_with_return_value": {clause: `
+					func foo() int{
+						arr := [1,2,3,4]
+						sum := 0
+						for i, _ <- arr {
+							if arr[i]>0{
+								return 1
+							}
+							sum+=arr[i]
+						}
+						return 0
+					}
+					println(foo())
+					`, want: "1\n"},
+	"range_with_only_return": {clause: `
+					func foo() {
+						arr := [1,2,3,4]
+						sum := 0
+						for i, _ <- arr {
+							if arr[i]>0{
+								println("1")
+								return 
+							}
+							sum+=arr[i]
+						}
+						println("0")
+						return 
+					}
+					foo()
+					`, want: "1\n"},
+}
+
+func TestRangeStmtWithBranch(t *testing.T) {
+	testScripts(t, "TestRangeStmtWithBranch", testRangeStmtWithBranchClauses)
+}
+
+// -----------------------------------------------------------------------------
+
+var testForPhraseWithBranchClauses = map[string]testData{
+	"for_phrase_with_continue": {clause: `
+					arr := [1,3,5,7]
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]<5{
+							continue
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "12\n"},
+	"for_phrase_with_break": {clause: `
+					arr := [1,3,5,7]
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]>5{
+							break
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "9\n"},
+	"for_phrase_with_continue_break": {clause: `
+					arr := [1,3,5,7]
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]>5{
+							break
+						}
+						for j:=0;j<len(arr);j++{
+							if arr[j]<5{
+								continue
+							}
+							sum+=arr[j]
+						}
+					}
+					println(sum)
+					`, want: "36\n"},
+	"for_phrase_with_return_value": {clause: `
+					func foo() int{
+						arr := [1,2,3,4]
+						sum := 0
+						for i, _ <- arr {
+							if arr[i]>0{
+								return 1
+							}
+							sum+=arr[i]
+						}
+						return 0
+					}
+					println(foo())
+					`, want: "1\n"},
+	"for_phrase_with_only_return": {clause: `
+					func foo() {
+						arr := [1,2,3,4]
+						sum := 0
+						for i, _ <- arr {
+							if arr[i]>0{
+								println("1")
+								return 
+							}
+							sum+=arr[i]
+						}
+						println("0")
+						return 
+					}
+					foo()
+					`, want: "1\n"},
+}
+
+func TestForPhraseWithBranch(t *testing.T) {
+	testScripts(t, "TestForPhraseWithBranch", testForPhraseWithBranchClauses)
+}
+
+// -----------------------------------------------------------------------------
+
+var testRangeMapWithBranchClauses = map[string]testData{
+	"map_for_with_continue": {clause: `
+					arr := {1:1,2:3,3:5,4:7}
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]<5{
+							continue
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "12\n"},
+	"map_for_with_break": {clause: `
+					arr := {1:1,2:3,3:5,4:7}
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]>0{
+							break
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "0\n"},
+
+	"map_for_range_continue": {clause: `
+					arr := {1:1,2:3,3:5,4:7}
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]<5{
+							continue
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "12\n"},
+	"map_for_range_break": {clause: `
+					arr := {1:1,2:3,3:5,4:7}
+					sum := 0
+					for i, _ <- arr {
+						if arr[i]>0{
+							break
+						}
+						sum+=arr[i]
+					}
+					println(sum)
+					`, want: "0\n"},
+	"map_for_range_return_value": {clause: `
+					func foo() int{
+						arr := {1:1,2:3,3:5,4:7}
+						sum := 0
+						for i, _ <- arr {
+							if arr[i]>0{
+								return 1
+							}
+							sum+=arr[i]
+						}
+						return 0
+					}
+					println(foo())
+					`, want: "1\n"},
+	"map_for_range_only_return": {clause: `
+					func foo() {
+						arr := {1:1,2:3,3:5,4:7}
+						sum := 0
+						for i, _ <- arr {
+							if arr[i]>0{
+								println("1")
+								return 
+							}
+							sum+=arr[i]
+						}
+						println("0")
+						return 
+					}
+					foo()
+					`, want: "1\n"},
+}
+
+func TestMapForPhraseWithBranch(t *testing.T) {
+	testScripts(t, "TestMapForPhraseWithBranch", testRangeMapWithBranchClauses)
+}
diff --git a/exec/golang/testdata/12.flow/flow.gop b/exec/golang/testdata/12.flow/flow.gop
index 231192f8..20408d1d 100644
--- a/exec/golang/testdata/12.flow/flow.gop
+++ b/exec/golang/testdata/12.flow/flow.gop
@@ -52,6 +52,26 @@ default:
 	println(y)
 }
 
+for k, v := range [k for k, _ <- make([]int, 20)] {
+	if k < 10 {
+		continue
+	}
+	if k > 15 {
+		break
+	}
+	println(k, v)
+}
+
+for k, v <- {k: k for k, _ <- make([]int, 20)} {
+	if k < 10 {
+		continue
+	}
+	if k > 15 {
+		println("map reach break", v > 15)
+		break
+	}
+}
+
 println("fallthrough with tag")
 switch v {
 case "Hello":
@@ -128,3 +148,5 @@ L3:
 			println(x)
 		}
 	}
+	println(forFoo1())
+	fooFoo2()
diff --git a/exec/golang/testdata/12.flow/return_flow.gop b/exec/golang/testdata/12.flow/return_flow.gop
new file mode 100644
index 00000000..af196aef
--- /dev/null
+++ b/exec/golang/testdata/12.flow/return_flow.gop
@@ -0,0 +1,25 @@
+func fooFoo2() {
+	arr := {1: 1, 2: 3, 3: 5, 4: 7}
+	sum := 0
+	for i, _ <- arr {
+		if arr[i] > 0 {
+			println("1")
+			return
+		}
+		sum += arr[i]
+	}
+	println("0")
+	return
+}
+
+func forFoo1() int {
+	arr := {1: 1, 2: 3, 3: 5, 4: 7}
+	sum := 0
+	for i, _ <- arr {
+		if arr[i] > 0 {
+			return 1
+		}
+		sum += arr[i]
+	}
+	return 0
+}