diff --git a/doc/next/6-stdlib/99-minor/text/template/66107.md b/doc/next/6-stdlib/99-minor/text/template/66107.md index 109c96e0214..03e1a1f21c8 100644 --- a/doc/next/6-stdlib/99-minor/text/template/66107.md +++ b/doc/next/6-stdlib/99-minor/text/template/66107.md @@ -1 +1 @@ -Templates now support range-over-func. +Templates now support range-over-func and range-over-int. diff --git a/src/text/template/doc.go b/src/text/template/doc.go index 847f96b7250..7b63bb76ae4 100644 --- a/src/text/template/doc.go +++ b/src/text/template/doc.go @@ -99,7 +99,7 @@ data, defined in detail in the corresponding sections that follow. {{range pipeline}} T1 {{end}} The value of the pipeline must be an array, slice, map, iter.Seq, - iter.Seq2 or channel. + iter.Seq2, integer 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 @@ -108,7 +108,7 @@ data, defined in detail in the corresponding sections that follow. {{range pipeline}} T1 {{else}} T0 {{end}} The value of the pipeline must be an array, slice, map, iter.Seq, - iter.Seq2 or channel. + iter.Seq2, integer 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. diff --git a/src/text/template/exec.go b/src/text/template/exec.go index 96d2f50ef87..a70d383566d 100644 --- a/src/text/template/exec.go +++ b/src/text/template/exec.go @@ -395,6 +395,22 @@ func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) { s.walk(elem, r.List) } switch val.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if len(r.Pipe.Decl) > 1 { + s.errorf("can't use %s to iterate over more than one variable", val) + break + } + run := false + for v := range val.Seq() { + run = true + // Pass element as second value, as we do for channels. + oneIteration(reflect.Value{}, v) + } + if !run { + break + } + return case reflect.Array, reflect.Slice: if val.Len() == 0 { break diff --git a/src/text/template/exec_test.go b/src/text/template/exec_test.go index b84e278c12b..cca53f4d723 100644 --- a/src/text/template/exec_test.go +++ b/src/text/template/exec_test.go @@ -613,6 +613,19 @@ var execTests = []execTest{ {"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}, + {"range int8", rangeTestInt, rangeTestData[int8](), int8(5), true}, + {"range int16", rangeTestInt, rangeTestData[int16](), int16(5), true}, + {"range int32", rangeTestInt, rangeTestData[int32](), int32(5), true}, + {"range int64", rangeTestInt, rangeTestData[int64](), int64(5), true}, + {"range int", rangeTestInt, rangeTestData[int](), int(5), true}, + {"range uint8", rangeTestInt, rangeTestData[uint8](), uint8(5), true}, + {"range uint16", rangeTestInt, rangeTestData[uint16](), uint16(5), true}, + {"range uint32", rangeTestInt, rangeTestData[uint32](), uint32(5), true}, + {"range uint64", rangeTestInt, rangeTestData[uint64](), uint64(5), true}, + {"range uint", rangeTestInt, rangeTestData[uint](), uint(5), true}, + {"range uintptr", rangeTestInt, rangeTestData[uintptr](), uintptr(5), true}, + {"range uintptr(0)", `{{range $v := .}}{{print $v}}{{else}}empty{{end}}`, "empty", uintptr(0), true}, + {"range 5", `{{range $v := 5}}{{printf "%T%d" $v $v}}{{end}}`, rangeTestData[int](), nil, true}, // Cute examples. {"or as if true", `{{or .SI "slice is empty"}}`, "[3 4 5]", tVal, true}, @@ -737,6 +750,17 @@ func fVal2(i int) iter.Seq2[int, int] { } } +const rangeTestInt = `{{range $v := .}}{{printf "%T%d" $v $v}}{{end}}` + +func rangeTestData[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr]() string { + I := T(5) + var buf strings.Builder + for i := T(0); i < I; i++ { + fmt.Fprintf(&buf, "%T%d", i, i) + } + return buf.String() +} + func zeroArgs() string { return "zeroArgs" }