Skip to content

Commit cfbd2e7

Browse files
qiulaidongfenggopherbot
authored andcommitted
text/template: support range-over-func
For #66107 Change-Id: I2fcd04bebe80346dbd244ab7ea09cbe6010b9d8e GitHub-Last-Rev: 5ebf615 GitHub-Pull-Request: #68329 Reviewed-on: https://go-review.googlesource.com/c/go/+/596956 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Carlos Amedee <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]>
1 parent 0081f17 commit cfbd2e7

File tree

4 files changed

+74
-2
lines changed

4 files changed

+74
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Templates now support range-over-func.

src/text/template/doc.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,17 @@ data, defined in detail in the corresponding sections that follow.
9898
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
9999
100100
{{range pipeline}} T1 {{end}}
101-
The value of the pipeline must be an array, slice, map, or channel.
101+
The value of the pipeline must be an array, slice, map, iter.Seq,
102+
iter.Seq2 or channel.
102103
If the value of the pipeline has length zero, nothing is output;
103104
otherwise, dot is set to the successive elements of the array,
104105
slice, or map and T1 is executed. If the value is a map and the
105106
keys are of basic type with a defined order, the elements will be
106107
visited in sorted key order.
107108
108109
{{range pipeline}} T1 {{else}} T0 {{end}}
109-
The value of the pipeline must be an array, slice, map, or channel.
110+
The value of the pipeline must be an array, slice, map, iter.Seq,
111+
iter.Seq2 or channel.
110112
If the value of the pipeline has length zero, dot is unaffected and
111113
T0 is executed; otherwise, dot is set to the successive elements
112114
of the array, slice, or map and T1 is executed.

src/text/template/exec.go

+37
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,43 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
434434
return
435435
case reflect.Invalid:
436436
break // An invalid value is likely a nil map, etc. and acts like an empty map.
437+
case reflect.Func:
438+
if val.Type().CanSeq() {
439+
if len(r.Pipe.Decl) > 1 {
440+
s.errorf("can't use %s iterate over more than one variable", val)
441+
break
442+
}
443+
run := false
444+
for v := range val.Seq() {
445+
run = true
446+
// Pass element as second value,
447+
// as we do for channels.
448+
oneIteration(reflect.Value{}, v)
449+
}
450+
if !run {
451+
break
452+
}
453+
return
454+
}
455+
if val.Type().CanSeq2() {
456+
run := false
457+
for i, v := range val.Seq2() {
458+
run = true
459+
if len(r.Pipe.Decl) > 1 {
460+
oneIteration(i, v)
461+
} else {
462+
// If there is only one range variable,
463+
// oneIteration will use the
464+
// second value.
465+
oneIteration(reflect.Value{}, i)
466+
}
467+
}
468+
if !run {
469+
break
470+
}
471+
return
472+
}
473+
fallthrough
437474
default:
438475
s.errorf("range can't iterate over %v", val)
439476
}

src/text/template/exec_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"flag"
1111
"fmt"
1212
"io"
13+
"iter"
1314
"reflect"
1415
"strings"
1516
"sync"
@@ -601,6 +602,17 @@ var execTests = []execTest{
601602
{"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true},
602603
{"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true},
603604
{"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true},
605+
{"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal1(2), true},
606+
{"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1(2), true},
607+
{"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", fVal1(2), false},
608+
{"i, c := range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true},
609+
{"i, c = range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true},
610+
{"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2(2), true},
611+
{"i := range iter.Seq2[int,int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal2(2), true},
612+
{"i,c,x range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{$x := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true},
613+
{"i,x range iter.Seq[int]", `{{$i := 0}}{{$x := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1(2), true},
614+
{"range iter.Seq[int] else", `{{range $i := .}}{{$i}}{{else}}empty{{end}}`, "empty", fVal1(0), true},
615+
{"range iter.Seq2[int,int] else", `{{range $i := .}}{{$i}}{{else}}empty{{end}}`, "empty", fVal2(0), true},
604616

605617
// Cute examples.
606618
{"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true},
@@ -705,6 +717,26 @@ var execTests = []execTest{
705717
{"issue60801", "{{$k := 0}}{{$v := 0}}{{range $k, $v = .AI}}{{$k}}={{$v}} {{end}}", "0=3 1=4 2=5 ", tVal, true},
706718
}
707719

720+
func fVal1(i int) iter.Seq[int] {
721+
return func(yield func(int) bool) {
722+
for v := range i {
723+
if !yield(v) {
724+
break
725+
}
726+
}
727+
}
728+
}
729+
730+
func fVal2(i int) iter.Seq2[int, int] {
731+
return func(yield func(int, int) bool) {
732+
for v := range i {
733+
if !yield(v, v+1) {
734+
break
735+
}
736+
}
737+
}
738+
}
739+
708740
func zeroArgs() string {
709741
return "zeroArgs"
710742
}

0 commit comments

Comments
 (0)