Skip to content

text/template: index/element switched for iter.Seq2 in range #73282

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bep opened this issue Apr 9, 2025 · 10 comments
Closed

text/template: index/element switched for iter.Seq2 in range #73282

bep opened this issue Apr 9, 2025 · 10 comments
Labels
BugReport Issues describing a possible bug in the Go implementation. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Unfortunate

Comments

@bep
Copy link
Contributor

bep commented Apr 9, 2025

Go version

go1.24.2 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/bep/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/bep/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/_g/j3j21hts4fn7__h04w2x8gb40000gn/T/go-build279183488=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/bep/dev/go/bep/temp/go.mod'
GOMODCACHE='/Users/bep/dev/gomod_cache'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/bep/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/bep/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

package main

import (
	"bytes"
	"fmt"
	"iter"
	"log"
	"slices"
	"strings"
	"text/template"
)

type MyStrings []string

func (c MyStrings) Backward() iter.Seq2[int, string] {
	return slices.Backward(c)
}

func main() {
	data := map[string]interface{}{
		"abc": MyStrings{"a", "b", "c"},
	}
	tpl := `
Range: {{ range .abc }}{{ . }}{{ end }}
Range $i, $e: {{ range $i, $e := .abc }}{{ $e }}{{ end }}
Range $i, $e dot: {{ range $i, $e := .abc }}{{ . }}{{ end }}
Range $e dot: {{ range $e := .abc }}{{ . }}{{ end }}
Backward: {{ range .abc.Backward }}{{ . }}{{ end }}
Backward $i, $e: {{ range  $i, $e := .abc.Backward }}{{ $e }}{{ end }}
Backward $i, $e dot: {{ range  $i, $e := .abc.Backward }}{{ . }}{{ end }}
Backward $e dot: {{ range $e := .abc.Backward }}{{ . }}{{ end }}
`

	var buf bytes.Buffer
	tmpl, err := template.New("").Parse(tpl)
	if err != nil {
		log.Fatal(err)
	}

	if err := tmpl.Execute(&buf, data); err != nil {
		log.Fatal(err)
	}
	result := strings.TrimSpace(buf.String())
	fmt.Println(result)
}

What did you see happen?

Range: abc
Range $i, $e: abc
Range $i, $e dot: abc
Range $e dot: abc
Backward: 210
Backward $i, $e: cba
Backward $i, $e dot: cba
Backward $e dot: 210

What did you expect to see?

Range: abc
Range $i, $e: abc
Range $i, $e dot: abc
Range $e dot: abc
Backward: cba
Backward $i, $e: cba
Backward $i, $e dot: cba
Backward $e dot: cba
@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Apr 9, 2025
@qiulaidongfeng
Copy link
Member

Existing implementation conforms to the specification
See https://go.dev/ref/spec#For_statements

Range expression                                       1st value                2nd value

function, 2 values  f  func(func(K, V) bool)           key      k  K            v          V

@bep
Copy link
Contributor Author

bep commented Apr 9, 2025

@qiulaidongfeng this issue is about the range func in Go Templates. If you apply the Go Language spec to Go Templates, you will find many discrepancies.

Some additional arguments to my initial post:

  • A range over a map/slice or a range over a basic iterator of that same map/slice should behave the same. This is how it works for regular Go, and anything else would be a big surprise.
  • This difference in behaviour requires that the end user knows/cares about the difference between a slice/map and an iterator, which becomes especially problematic in a more dynamically typed scripting language.
  • It also means that it's not possible to replace slices/maps with a struct without breaking the API, which is incredibly unfortunate.

@dmitshur
Copy link
Member

dmitshur commented Apr 9, 2025

CC @robpike, @mvdan.

@dmitshur dmitshur added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Apr 9, 2025
@qiulaidongfeng
Copy link
Member

See https://go.dev/doc/go1.24#minor_library_changes

Templates now support range-over-func and range-over-int.

not to support other new semantics.
I think the description in the go specification is the definition of range-over-func.

By the way, it has been released by go1.24, so, according to https://go.dev/doc/go1compat, for backward compatibility, we should not change.

@seankhliao
Copy link
Member

I don't think this is something we can change at this point.

Plus, while it convention to return (index, value) when working with slices, that may not always be true when working with iterators in general. For example, fallible iterators may choose to return (value, error), and it would be decidedly less useful if the single value iteration returned just the errors.

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale May 24, 2025
@bep
Copy link
Contributor Author

bep commented May 24, 2025

I don't think this is something we can change at this point.

Of course you can change this. You may not want to change this. But if you ask 100 developers about this, 99 will say that this is a bug.

@dmitshur not sure what this "closed as not planned" comes from. In Hugo, I suspect the biggest consumer/producer of Go templates in the wild, we have a "soft fork" of the Go template execution code, and I can certainly maintain this patch if needed (and I really need it), but that means that there will eventually be confusion, and I'm pretty sure the issues/questions will end up on this issue tracker and not mine.

@dmitshur
Copy link
Member

Since it's not already mentioned here, the proposal for this was #66107, and the accepted plan there was:

The proposal is to allow range over func and int the same as in Go.

The current implementation seems to match the plan in the accepted proposal, right?

It's not possible to change the current behavior without also breaking programs that have been written targetting the Go 1.24 behavior. If it should be changed anyway, one possible path is to change it in a future version of Go by gating it on the go directive. That would need a new proposal rather than a regular issue.

I'm not the owner of this package and I haven't looked closely at the details and previous discussion, but at a glance it looks like both directions (staying closer to Go semantics vs staying closer to text/template semantics) have trade-offs, such that making a change would help in some ways and make other things worse. It's worth opening a proposal if there is information that hasn't already been considered during discussion of proposal #66107 or in this issue.

@bep
Copy link
Contributor Author

bep commented May 24, 2025

@dmitshur I have read the proposal several times, and 1. The proposal does not say anything about what the "dot" should be for iter.Seq2. There's a discussion about it with no conclusion (or the closest is this: #66107 (comment) -- but that does not seem to be enforced (no errors in the example above).

Oh, well, I understand that I'm late to the party here and that this is a tough sell, but I will repeat and say that it's incredibly unfortunate that you have created new range syntax that's not compatible with how it works with slices and maps.

@robpike
Copy link
Contributor

robpike commented May 24, 2025

This might just need to be labeled as Unfortunate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Unfortunate
Projects
None yet
Development

No branches or pull requests

6 participants