Skip to content

Commit 5c3a6db

Browse files
committed
fix(for_range_channel): ranging over channels
Signed-off-by: Christian Stewart <[email protected]>
1 parent bc4befb commit 5c3a6db

File tree

5 files changed

+121
-0
lines changed

5 files changed

+121
-0
lines changed

compiler/stmt-range.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"fmt"
55
"go/ast"
66
"go/types"
7+
"strings"
8+
9+
"go/token"
710

811
"github.com/pkg/errors"
912
)
@@ -85,6 +88,10 @@ func (c *GoToTSCompiler) WriteStmtRange(exp *ast.RangeStmt) error {
8588
return c.writeInterfaceIteratorRange(exp)
8689
}
8790

91+
if _, ok := underlying.(*types.Chan); ok {
92+
return c.writeChannelRange(exp)
93+
}
94+
8895
return errors.Errorf("unsupported range loop type: %T for expression %v", underlying, exp)
8996
}
9097

@@ -418,3 +425,72 @@ func (c *GoToTSCompiler) writeInterfaceIteratorRange(exp *ast.RangeStmt) error {
418425
c.tsw.WriteLine("})()")
419426
return nil
420427
}
428+
429+
func (c *GoToTSCompiler) writeChannelRange(exp *ast.RangeStmt) error {
430+
if exp.Value != nil {
431+
return fmt.Errorf("channel range does not support two iteration variables")
432+
}
433+
434+
c.tsw.WriteLiterally("for (;;) {")
435+
c.tsw.Indent(1)
436+
c.tsw.WriteLine("")
437+
438+
valueIsBlank := exp.Key == nil
439+
valueName := "_"
440+
if !valueIsBlank {
441+
if valIdent, ok := exp.Key.(*ast.Ident); ok {
442+
if valIdent.Name != "_" {
443+
valueName = valIdent.Name
444+
} else {
445+
valueIsBlank = true
446+
}
447+
} else {
448+
return fmt.Errorf("unsupported iteration variable in channel range: %T", exp.Key)
449+
}
450+
}
451+
452+
keyword := "const "
453+
okVarName := "_ok"
454+
if exp.Tok != token.DEFINE {
455+
keyword = ""
456+
c.tsw.WriteLiterally("let ")
457+
c.tsw.WriteLiterally(okVarName)
458+
c.tsw.WriteLine("")
459+
}
460+
461+
patternParts := []string{}
462+
if !valueIsBlank {
463+
patternParts = append(patternParts, fmt.Sprintf("value: %s", valueName))
464+
}
465+
patternParts = append(patternParts, fmt.Sprintf("ok: %s", okVarName))
466+
destructuringPattern := fmt.Sprintf("{ %s }", strings.Join(patternParts, ", "))
467+
468+
c.tsw.WriteLiterally(keyword)
469+
if keyword == "" {
470+
c.tsw.WriteLiterally(";(")
471+
}
472+
c.tsw.WriteLiterally(destructuringPattern)
473+
c.tsw.WriteLiterally(" = await $.chanRecvWithOk(")
474+
if err := c.WriteValueExpr(exp.X); err != nil {
475+
return err
476+
}
477+
c.tsw.WriteLiterally(")")
478+
if keyword == "" {
479+
c.tsw.WriteLiterally(")")
480+
}
481+
c.tsw.WriteLine("")
482+
483+
c.tsw.WriteLiterally("if (!")
484+
c.tsw.WriteLiterally(okVarName)
485+
c.tsw.WriteLiterally(") break")
486+
c.tsw.WriteLine("")
487+
488+
if err := c.WriteStmtBlock(exp.Body, false); err != nil {
489+
return err
490+
}
491+
492+
c.tsw.Indent(-1)
493+
c.tsw.WriteLine("}")
494+
495+
return nil
496+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
0
2+
1

compliance/tests/for_range_channel/for_range_channel.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,14 @@ func main() {
88
for x := range c {
99
println(x)
1010
}
11+
12+
// test with = instead of := within the for range
13+
c = make(chan int, 1)
14+
c <- 1
15+
close(c)
16+
17+
var y int
18+
for y = range c {
19+
println(y)
20+
}
1121
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Generated file based on for_range_channel.go
2+
// Updated when compliance tests are re-run, DO NOT EDIT!
3+
4+
import * as $ from "@goscript/builtin/index.js";
5+
6+
export async function main(): Promise<void> {
7+
let c = $.makeChannel<number>(1, 0, 'both')
8+
await $.chanSend(c, 0)
9+
c.close()
10+
11+
for (;;) {
12+
const { value: x, ok: _ok } = await $.chanRecvWithOk(c)
13+
if (!_ok) break
14+
{
15+
console.log(x)
16+
}
17+
}
18+
19+
// test with = instead of := within the for range
20+
c = $.makeChannel<number>(1, 0, 'both')
21+
await $.chanSend(c, 1)
22+
c.close()
23+
24+
let y: number = 0
25+
for (;;) {
26+
let _ok
27+
;({ value: y, ok: _ok } = await $.chanRecvWithOk(c))
28+
if (!_ok) break
29+
{
30+
console.log(y)
31+
}
32+
}
33+
}
34+

compliance/tests/for_range_channel/index.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)