From 845bc01fc5d04b8d4e42dd7795525099bc575ff2 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 7 Jul 2024 17:16:26 +0800 Subject: [PATCH 01/14] text/template: support range-over-func Fixes #66107 Change-Id: If3e503efca200aa86463c9d27d06986b03c0d638 --- src/text/template/exec.go | 33 +++++++++++++++++++++++++++++++++ src/text/template/exec_test.go | 21 +++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 5b35b3e5a85b17..500d5b09b59201 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -394,6 +394,25 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { }() s.walk(elem, r.List) } + + rangeone := func(elem reflect.Value) { + if len(r.Pipe.Decl) > 0 { + s.setVar(r.Pipe.Decl[0].Ident[0], elem) + } + if len(r.Pipe.Decl) > 1 { + s.errorf("can't use %v iterate over two variables", val) + return + } + defer s.pop(mark) + defer func() { + // Consume panic(walkContinue) + if r := recover(); r != nil && r != walkContinue { + panic(r) + } + }() + s.walk(elem, r.List) + } + switch val.Kind() { case reflect.Array, reflect.Slice: if val.Len() == 0 { @@ -434,6 +453,20 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { return case reflect.Invalid: break // An invalid value is likely a nil map, etc. and acts like an empty map. + case reflect.Func: + if val.Type().CanSeq() { + for v := range val.Seq() { + rangeone(v) + } + return + } + if val.Type().CanSeq2() { + for i, v := range val.Seq2() { + oneIteration(reflect.ValueOf(i), v) + } + return + } + fallthrough default: s.errorf("range can't iterate over %v", val) } diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 9903e17d0ef2c8..4250ac091e23a1 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -601,6 +601,27 @@ var execTests = []execTest{ {"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true}, {"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true}, {"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true}, + {"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", func(yield func(int) bool) { + for i := range 2 { + if !yield(i) { + break + } + } + }, true}, + {"range iter.Seq[int]", `{{range $i, $c := .}}{{$c}}{{end}}`, "", func(yield func(int) bool) { + for i := range 2 { + if !yield(i) { + break + } + } + }, false}, + {"range iter.Seq[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", func(yield func(int, int) bool) { + for i := range 2 { + if !yield(i, i+1) { + break + } + } + }, true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, From e5c75eadd9ce99a721db4f2cc5cb2579036e0550 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 7 Jul 2024 17:22:45 +0800 Subject: [PATCH 02/14] doc Change-Id: If16c21bd933e22f55afe85f6aaf0891cd9a40ef9 --- src/text/template/doc.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/text/template/doc.go b/src/text/template/doc.go index 12f6fe0d1c9cf2..847f96b72508f3 100644 --- a/src/text/template/doc.go +++ b/src/text/template/doc.go @@ -98,7 +98,8 @@ data, defined in detail in the corresponding sections that follow. {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}} {{range pipeline}} T1 {{end}} - The value of the pipeline must be an array, slice, map, or channel. + The value of the pipeline must be an array, slice, map, iter.Seq, + iter.Seq2 or channel. If the value of the pipeline has length zero, nothing is output; otherwise, dot is set to the successive elements of the array, slice, or map and T1 is executed. If the value is a map and the @@ -106,7 +107,8 @@ data, defined in detail in the corresponding sections that follow. visited in sorted key order. {{range pipeline}} T1 {{else}} T0 {{end}} - The value of the pipeline must be an array, slice, map, or channel. + The value of the pipeline must be an array, slice, map, iter.Seq, + iter.Seq2 or channel. If the value of the pipeline has length zero, dot is unaffected and T0 is executed; otherwise, dot is set to the successive elements of the array, slice, or map and T1 is executed. From 797456b38557f01c394439aa5682213f4ea82a85 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Mon, 8 Jul 2024 20:26:12 +0800 Subject: [PATCH 03/14] doc Change-Id: Id67b97c88504a8c6ef9cb6702217f3dd170eca2a --- doc/next/6-stdlib/99-minor/text/template/66107.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/next/6-stdlib/99-minor/text/template/66107.md diff --git a/doc/next/6-stdlib/99-minor/text/template/66107.md b/doc/next/6-stdlib/99-minor/text/template/66107.md new file mode 100644 index 00000000000000..109c96e0214649 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/text/template/66107.md @@ -0,0 +1 @@ +Templates now support range-over-func. From 2a119ee74047f809fc7fe55a24e4bfd9fc61a9d5 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 8 Sep 2024 12:31:32 +0800 Subject: [PATCH 04/14] new Change-Id: I219e28bb3dd32f91573803481dcda2828453987b --- src/text/template/exec.go | 30 +++++++++--------------------- src/text/template/exec_test.go | 9 ++++++++- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 500d5b09b59201..38a73a2b03c047 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -360,12 +360,13 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { val, _ := indirect(s.evalPipeline(dot, r.Pipe)) // mark top of stack before any variables in the body are pushed. mark := s.mark() + var rangefunc = false oneIteration := func(index, elem reflect.Value) { if len(r.Pipe.Decl) > 0 { - if r.Pipe.IsAssign { + if r.Pipe.IsAssign || rangefunc { // With two variables, index comes first. // With one, we use the element. - if len(r.Pipe.Decl) > 1 { + if len(r.Pipe.Decl) > 1 || rangefunc { s.setVar(r.Pipe.Decl[0].Ident[0], index) } else { s.setVar(r.Pipe.Decl[0].Ident[0], elem) @@ -395,24 +396,6 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { s.walk(elem, r.List) } - rangeone := func(elem reflect.Value) { - if len(r.Pipe.Decl) > 0 { - s.setVar(r.Pipe.Decl[0].Ident[0], elem) - } - if len(r.Pipe.Decl) > 1 { - s.errorf("can't use %v iterate over two variables", val) - return - } - defer s.pop(mark) - defer func() { - // Consume panic(walkContinue) - if r := recover(); r != nil && r != walkContinue { - panic(r) - } - }() - s.walk(elem, r.List) - } - switch val.Kind() { case reflect.Array, reflect.Slice: if val.Len() == 0 { @@ -455,8 +438,13 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { break // An invalid value is likely a nil map, etc. and acts like an empty map. case reflect.Func: if val.Type().CanSeq() { + if len(r.Pipe.Decl) > 1 { + s.errorf("range can't iterate over %v", val) + return + } + rangefunc = true for v := range val.Seq() { - rangeone(v) + oneIteration(v, reflect.Value{}) } return } diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 4250ac091e23a1..7be23e08e9cbac 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -608,7 +608,14 @@ var execTests = []execTest{ } } }, true}, - {"range iter.Seq[int]", `{{range $i, $c := .}}{{$c}}{{end}}`, "", func(yield func(int) bool) { + {"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", func(yield func(int) bool) { + for i := range 2 { + if !yield(i) { + break + } + } + }, true}, + {"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", func(yield func(int) bool) { for i := range 2 { if !yield(i) { break From 3d9e2e7956d0b85b5f1319b3df8565bd3fe3387e Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 8 Sep 2024 13:05:34 +0800 Subject: [PATCH 05/14] new Change-Id: I1775584dd31aa431585f268b4d6c28497a82614a --- src/text/template/exec.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 38a73a2b03c047..50353389bef177 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -395,7 +395,6 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { }() s.walk(elem, r.List) } - switch val.Kind() { case reflect.Array, reflect.Slice: if val.Len() == 0 { @@ -439,7 +438,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { case reflect.Func: if val.Type().CanSeq() { if len(r.Pipe.Decl) > 1 { - s.errorf("range can't iterate over %v", val) + s.errorf("can't use %s iterate over more than one variable", val) return } rangefunc = true From 9e25220c34d96abee6affdb2faeb8ef30f2917ee Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 8 Sep 2024 13:11:05 +0800 Subject: [PATCH 06/14] new Change-Id: Ief805237a218fbdd4ecf6607b9e5d0c050e436e0 --- src/text/template/exec_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 7be23e08e9cbac..61b89cc3328e92 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -622,7 +622,7 @@ var execTests = []execTest{ } } }, false}, - {"range iter.Seq[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", func(yield func(int, int) bool) { + {"range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", func(yield func(int, int) bool) { for i := range 2 { if !yield(i, i+1) { break From d052cf47990076d6a6c5a6ff53f0a60a2cac5069 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Sun, 8 Sep 2024 13:20:10 +0800 Subject: [PATCH 07/14] new Change-Id: I01fe954cea45e20c01866b7d23218fa9d04d6608 --- src/text/template/exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 50353389bef177..87690aea020103 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -449,7 +449,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } if val.Type().CanSeq2() { for i, v := range val.Seq2() { - oneIteration(reflect.ValueOf(i), v) + oneIteration(i, v) } return } From b9e4de5a0ba7c0dcc8721337ee6d767bbc5d52e9 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Mon, 9 Sep 2024 17:10:54 +0800 Subject: [PATCH 08/14] new Change-Id: I27c5bc1d169b6af1651d94b5fbece7208c0dd7ca --- src/text/template/exec.go | 18 ++++++++---------- src/text/template/exec_test.go | 7 +++++++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 87690aea020103..a21fea434f6113 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -360,11 +360,10 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { val, _ := indirect(s.evalPipeline(dot, r.Pipe)) // mark top of stack before any variables in the body are pushed. mark := s.mark() - var rangefunc = false - oneIteration := func(index, elem reflect.Value) { + oneIteration := func(index, elem reflect.Value, rangefunc bool) { if len(r.Pipe.Decl) > 0 { if r.Pipe.IsAssign || rangefunc { - // With two variables, index comes first. + // With two variables, index comes first, except for range over a function. // With one, we use the element. if len(r.Pipe.Decl) > 1 || rangefunc { s.setVar(r.Pipe.Decl[0].Ident[0], index) @@ -378,7 +377,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } } if len(r.Pipe.Decl) > 1 { - if r.Pipe.IsAssign { + if r.Pipe.IsAssign || rangefunc { s.setVar(r.Pipe.Decl[1].Ident[0], elem) } else { // Set next var (lexically the first if there @@ -401,7 +400,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { break } for i := 0; i < val.Len(); i++ { - oneIteration(reflect.ValueOf(i), val.Index(i)) + oneIteration(reflect.ValueOf(i), val.Index(i), false) } return case reflect.Map: @@ -410,7 +409,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } om := fmtsort.Sort(val) for _, m := range om { - oneIteration(m.Key, m.Value) + oneIteration(m.Key, m.Value, false) } return case reflect.Chan: @@ -427,7 +426,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { if !ok { break } - oneIteration(reflect.ValueOf(i), elem) + oneIteration(reflect.ValueOf(i), elem, false) } if i == 0 { break @@ -441,15 +440,14 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { s.errorf("can't use %s iterate over more than one variable", val) return } - rangefunc = true for v := range val.Seq() { - oneIteration(v, reflect.Value{}) + oneIteration(v, reflect.Value{}, true) } return } if val.Type().CanSeq2() { for i, v := range val.Seq2() { - oneIteration(i, v) + oneIteration(i, v, true) } return } diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 61b89cc3328e92..527d14cae20a81 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -629,6 +629,13 @@ var execTests = []execTest{ } } }, true}, + {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", func(yield func(int, int) bool) { + for i := range 2 { + if !yield(i, i+1) { + break + } + } + }, true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, From ef2002ed0a714f4cee19f8f4c9f09e621a6c475d Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Wed, 11 Sep 2024 17:28:23 +0800 Subject: [PATCH 09/14] new Change-Id: Iea634f19dcabba0cef596e89c4fc8f92d4e09bc6 --- src/text/template/exec.go | 5 +-- src/text/template/exec_test.go | 56 +++++++++++++--------------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index a21fea434f6113..cc0e4c674d010c 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -363,8 +363,9 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { oneIteration := func(index, elem reflect.Value, rangefunc bool) { if len(r.Pipe.Decl) > 0 { if r.Pipe.IsAssign || rangefunc { - // With two variables, index comes first, except for range over a function. - // With one, we use the element. + // With two variables index comes first in all cases. + // With one variable, we use the element for most cases, + // but not for range over a function. if len(r.Pipe.Decl) > 1 || rangefunc { s.setVar(r.Pipe.Decl[0].Ident[0], index) } else { diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 527d14cae20a81..0b14c608253450 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -601,41 +601,11 @@ var execTests = []execTest{ {"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true}, {"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true}, {"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true}, - {"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", func(yield func(int) bool) { - for i := range 2 { - if !yield(i) { - break - } - } - }, true}, - {"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", func(yield func(int) bool) { - for i := range 2 { - if !yield(i) { - break - } - } - }, true}, - {"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", func(yield func(int) bool) { - for i := range 2 { - if !yield(i) { - break - } - } - }, false}, - {"range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", func(yield func(int, int) bool) { - for i := range 2 { - if !yield(i, i+1) { - break - } - } - }, true}, - {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", func(yield func(int, int) bool) { - for i := range 2 { - if !yield(i, i+1) { - break - } - } - }, true}, + {"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal1, true}, + {"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1, true}, + {"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", fVal1, false}, + {"range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, + {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2, true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, @@ -740,6 +710,22 @@ var execTests = []execTest{ {"issue60801", "{{$k := 0}}{{$v := 0}}{{range $k, $v = .AI}}{{$k}}={{$v}} {{end}}", "0=3 1=4 2=5 ", tVal, true}, } +func fVal1(yield func(int) bool) { + for i := range 2 { + if !yield(i) { + break + } + } +} + +func fVal2(yield func(int, int) bool) { + for i := range 2 { + if !yield(i, i+1) { + break + } + } +} + func zeroArgs() string { return "zeroArgs" } From da1a7acd66636b85f9e25e936c344919d784b6fc Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Mon, 16 Sep 2024 22:54:43 +0800 Subject: [PATCH 10/14] new Change-Id: I1042b276871d077286d002f4fe0debd3fe1c4de8 --- src/text/template/exec_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 0b14c608253450..21d45de7059695 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -604,8 +604,10 @@ var execTests = []execTest{ {"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal1, true}, {"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1, true}, {"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", fVal1, false}, - {"range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, + {"i, c := range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, + {"i, c = range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2, true}, + {"i := range iter.Seq2[int,int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal2, true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, From 8417f2dc687a0e2b2d228167f8a58adf232aba81 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 17 Sep 2024 10:14:24 +0800 Subject: [PATCH 11/14] new Change-Id: I81f582be4bb63576198eaedb09d7deb61ab00b31 --- src/text/template/exec_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 21d45de7059695..3522c3e0f737e4 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -608,6 +608,8 @@ var execTests = []execTest{ {"i, c = range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2, true}, {"i := range iter.Seq2[int,int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal2, true}, + {"i,c,x range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{$x := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, + {"i,x range iter.Seq[int]", `{{$i := 0}}{{$x := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1, true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, From fd4a8c205a9f4c84abdfac0198d303fd7a8701d4 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Tue, 17 Sep 2024 11:06:47 +0800 Subject: [PATCH 12/14] new Change-Id: Ibd5e3b279b74a7b20bf207eed5cceaf200791714 --- src/text/template/exec.go | 12 +++++++++- src/text/template/exec_test.go | 41 ++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index cc0e4c674d010c..12e4f468f2cd55 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -439,17 +439,27 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { if val.Type().CanSeq() { if len(r.Pipe.Decl) > 1 { s.errorf("can't use %s iterate over more than one variable", val) - return + break } + run := false for v := range val.Seq() { + run = true oneIteration(v, reflect.Value{}, true) } + if !run { + break + } return } if val.Type().CanSeq2() { + run := false for i, v := range val.Seq2() { + run = true oneIteration(i, v, true) } + if !run { + break + } return } fallthrough diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index 3522c3e0f737e4..b84e278c12bd2e 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -10,6 +10,7 @@ import ( "flag" "fmt" "io" + "iter" "reflect" "strings" "sync" @@ -601,15 +602,17 @@ var execTests = []execTest{ {"declare in range", "{{range $x := .PSI}}<{{$foo:=$x}}{{$x}}>{{end}}", "<21><22><23>", tVal, true}, {"range count", `{{range $i, $x := count 5}}[{{$i}}]{{$x}}{{end}}`, "[0]a[1]b[2]c[3]d[4]e", tVal, true}, {"range nil count", `{{range $i, $x := count 0}}{{else}}empty{{end}}`, "empty", tVal, true}, - {"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal1, true}, - {"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1, true}, - {"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", fVal1, false}, - {"i, c := range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, - {"i, c = range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, - {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2, true}, - {"i := range iter.Seq2[int,int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal2, true}, - {"i,c,x range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{$x := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2, true}, - {"i,x range iter.Seq[int]", `{{$i := 0}}{{$x := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1, true}, + {"range iter.Seq[int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal1(2), true}, + {"i = range iter.Seq[int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1(2), true}, + {"range iter.Seq[int] over two var", `{{range $i, $c := .}}{{$c}}{{end}}`, "", fVal1(2), false}, + {"i, c := range iter.Seq2[int,int]", `{{range $i, $c := .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true}, + {"i, c = range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true}, + {"i = range iter.Seq2[int,int]", `{{$i := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal2(2), true}, + {"i := range iter.Seq2[int,int]", `{{range $i := .}}{{$i}}{{end}}`, "01", fVal2(2), true}, + {"i,c,x range iter.Seq2[int,int]", `{{$i := 0}}{{$c := 0}}{{$x := 0}}{{range $i, $c = .}}{{$i}}{{$c}}{{end}}`, "0112", fVal2(2), true}, + {"i,x range iter.Seq[int]", `{{$i := 0}}{{$x := 0}}{{range $i = .}}{{$i}}{{end}}`, "01", fVal1(2), true}, + {"range iter.Seq[int] else", `{{range $i := .}}{{$i}}{{else}}empty{{end}}`, "empty", fVal1(0), true}, + {"range iter.Seq2[int,int] else", `{{range $i := .}}{{$i}}{{else}}empty{{end}}`, "empty", fVal2(0), true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, @@ -714,18 +717,22 @@ var execTests = []execTest{ {"issue60801", "{{$k := 0}}{{$v := 0}}{{range $k, $v = .AI}}{{$k}}={{$v}} {{end}}", "0=3 1=4 2=5 ", tVal, true}, } -func fVal1(yield func(int) bool) { - for i := range 2 { - if !yield(i) { - break +func fVal1(i int) iter.Seq[int] { + return func(yield func(int) bool) { + for v := range i { + if !yield(v) { + break + } } } } -func fVal2(yield func(int, int) bool) { - for i := range 2 { - if !yield(i, i+1) { - break +func fVal2(i int) iter.Seq2[int, int] { + return func(yield func(int, int) bool) { + for v := range i { + if !yield(v, v+1) { + break + } } } } From 64703793953cd31d1e81ea54ef4e0556eb43fc86 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Thu, 19 Sep 2024 19:27:47 +0800 Subject: [PATCH 13/14] new Change-Id: I8ed2b28225874104f9e78f66d3f2f4018bac81af --- src/text/template/exec.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 12e4f468f2cd55..9f55527bb434b6 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -360,13 +360,13 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { val, _ := indirect(s.evalPipeline(dot, r.Pipe)) // mark top of stack before any variables in the body are pushed. mark := s.mark() - oneIteration := func(index, elem reflect.Value, rangefunc bool) { + oneIteration := func(index, elem reflect.Value) { if len(r.Pipe.Decl) > 0 { - if r.Pipe.IsAssign || rangefunc { + if r.Pipe.IsAssign { // With two variables index comes first in all cases. // With one variable, we use the element for most cases, // but not for range over a function. - if len(r.Pipe.Decl) > 1 || rangefunc { + if len(r.Pipe.Decl) > 1 { s.setVar(r.Pipe.Decl[0].Ident[0], index) } else { s.setVar(r.Pipe.Decl[0].Ident[0], elem) @@ -378,7 +378,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } } if len(r.Pipe.Decl) > 1 { - if r.Pipe.IsAssign || rangefunc { + if r.Pipe.IsAssign { s.setVar(r.Pipe.Decl[1].Ident[0], elem) } else { // Set next var (lexically the first if there @@ -401,7 +401,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { break } for i := 0; i < val.Len(); i++ { - oneIteration(reflect.ValueOf(i), val.Index(i), false) + oneIteration(reflect.ValueOf(i), val.Index(i)) } return case reflect.Map: @@ -410,7 +410,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { } om := fmtsort.Sort(val) for _, m := range om { - oneIteration(m.Key, m.Value, false) + oneIteration(m.Key, m.Value) } return case reflect.Chan: @@ -427,7 +427,7 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { if !ok { break } - oneIteration(reflect.ValueOf(i), elem, false) + oneIteration(reflect.ValueOf(i), elem) } if i == 0 { break @@ -444,7 +444,9 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { run := false for v := range val.Seq() { run = true - oneIteration(v, reflect.Value{}, true) + // Pass element as second value, + // as we do for channels. + oneIteration(reflect.Value{}, v) } if !run { break @@ -455,7 +457,14 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { run := false for i, v := range val.Seq2() { run = true - oneIteration(i, v, true) + if len(r.Pipe.Decl) > 1 { + oneIteration(i, v) + } else { + // If there is only one range variable, + // oneIteration will use the + // second value. + oneIteration(reflect.Value{}, i) + } } if !run { break From 5ebf615db5889a04738c555c651e07c1fd287748 Mon Sep 17 00:00:00 2001 From: qiulaidongfeng <2645477756@qq.com> Date: Thu, 19 Sep 2024 21:40:15 +0800 Subject: [PATCH 14/14] new Change-Id: I23ea3f7a59567f00dfb8b58d4bd067230fe94e13 --- src/text/template/exec.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 9f55527bb434b6..96d2f50ef87ffc 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -363,9 +363,8 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { oneIteration := func(index, elem reflect.Value) { if len(r.Pipe.Decl) > 0 { if r.Pipe.IsAssign { - // With two variables index comes first in all cases. - // With one variable, we use the element for most cases, - // but not for range over a function. + // With two variables, index comes first. + // With one, we use the element. if len(r.Pipe.Decl) > 1 { s.setVar(r.Pipe.Decl[0].Ident[0], index) } else {