go: cmd/compile: field reference not working for type parameter with structural constraint
What version of Go are you using (go version)?
$ go version go version go1.18beta1 darwin/amd64
Does this issue reproduce with the latest release?
na. This is only currently possible in 1.18beta1.
What operating system and processor architecture are you using (go env)?
go env Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/Users/akutz/Library/Caches/go-build" GOENV="/Users/akutz/Library/Application Support/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/akutz/Projects/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="/Users/akutz/Projects/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/Users/akutz/.go/active" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/Users/akutz/.go/active/pkg/tool/darwin_amd64" GOVCS="" GOVERSION="go1.18beta1" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="/Users/akutz/Projects/controller-runtime/go.mod" GOWORK="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/0c/ffr4wl4d37924s44ctztx_ym00bgms/T/go-build1780269713=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
Over the holidays I started digging into Golang generics, and after much consideration, I decided I wanted to create a constraint like so:
type Condition[T ~string, S ~string] interface {
~struct{
Type T
Status string
Severity S
LastTransitionTime int64
Reason string
Message string
}
}
It is my understanding the above constraint should be valid based upon the following example from the type parameter proposal (from the section Composite Types in Constraints):
// structField is a type constraint whose type set consists of some
// struct types that all have a field named x.
type structField interface {
struct { a int; x int } |
struct { b int; x float64 } |
struct { c int; x uint64 }
}
// This function is INVALID.
func IncrementX[T structField](p *T) {
v := p.x // INVALID: type of p.x is not the same for all types in set
v++
p.x = v
}
While the above example shows the function is INVALID, it also implies that such a constraint would be valid if not for the mixed-type of x. However, when I tried to use this in practice, I received the following error:
c.Type undefined (interface Condition has no method Type)
I was able to replicate this in the Go 1.18 playgound with the following example:
package main
import (
"fmt"
)
type Cat struct {
Name string
}
type Dog struct {
Name string
}
type Person struct {
Name string
}
type Named interface {
~struct{ Name string }
}
func SayName[T Named](t T) {
fmt.Println(t.Name)
}
func main() {
SayName(Person{Name: "Andrew"})
}
The above program produces the following error:
./prog.go:24:16: t.Name undefined (interface Named has no method Name)
Go build failed.
What did you expect to see?
I expected the program to build and print the line Andrew.
What did you see instead?
The aforementioned compile error:
./prog.go:24:16: t.Name undefined (interface Named has no method Name)
Go build failed.
It would be incredibly useful for this constraint to work as I understand it from the proposal. Perhaps:
- I misunderstood the proposal
- The proposal is not up-to-date with the implementation
- The implementation is incomplete in 1.18beta1
Regardless, the reason I would like this to work is as follows:
- Using the above example, imagine
C Condition[T, S] - Now imagine a value of
c C - If I want to set the
Typefield onc, I can just doc.Type = ... - But without the above constraint, I would have to change to something like the following:
type Condition[T ~string, S ~string] interface { SetType(type3 T) } - The problem with the above is that if I have a struct that implements the interface
Condition[T, S], that means the struct must have a function likefunc (c *MyCondition) SetType(type3 MyConditionType). - The receiver for the setter function is necessarily by address
- Which means it is not
MyConditionthat satisfies the constraintCondition[T, S], but*MyCondition - This greatly complicates things, and it would be much nicer to constrain based on the
~struct {...}and to use those fields directly
Thanks!
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 26 (10 by maintainers)
Commits related to this issue
- go/types, types2: remove unused code in lookupFieldOrMethod The underlying type of a type parameter is an interface, so we don't need a special case for type parameters anymore. Simply share the (ide... — committed to golang/go by griesemer 2 years ago
- go/types, types2: implement field access for struct structural constraints This change implements field the access p.f where the type of p is a type parameter with a structural constraint that is a s... — committed to golang/go by griesemer 2 years ago
- test/typeparam: adjust test preamble (fix longtests) For #50417. Change-Id: Ic55727c454ec342354f7fbffd22aa350e0d392c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/376174 Trust: Robert Grie... — committed to golang/go by griesemer 2 years ago
- cmd/compile: support field access for typeparam with structural constraint In the compiler, we need to distinguish field and method access on a type param. For field access, we avoid the dictionary a... — committed to golang/go by danscales 2 years ago
- go/types, types2: remove unused code in lookupFieldOrMethod The underlying type of a type parameter is an interface, so we don't need a special case for type parameters anymore. Simply share the (ide... — committed to jproberts/go by griesemer 2 years ago
- go/types, types2: implement field access for struct structural constraints This change implements field the access p.f where the type of p is a type parameter with a structural constraint that is a s... — committed to jproberts/go by griesemer 2 years ago
- test/typeparam: adjust test preamble (fix longtests) For #50417. Change-Id: Ic55727c454ec342354f7fbffd22aa350e0d392c2 Reviewed-on: https://go-review.googlesource.com/c/go/+/376174 Trust: Robert Grie... — committed to jproberts/go by griesemer 2 years ago
- cmd/compile: support field access for typeparam with structural constraint In the compiler, we need to distinguish field and method access on a type param. For field access, we avoid the dictionary a... — committed to jproberts/go by danscales 2 years ago
Change https://golang.org/cl/375795 mentions this issue:
cmd/compile/internal/types2: implement field access for struct structural constraintsOK, I closed this issue, since the original example and several other examples are now fixed. @akutz I filed your latest test case as a new issue #50690 (which has lower priority - it’s quite an unusual case).
The necessary CL has not been submitted yet as of now. This is not working at tip (only type-checking works). cc: @danscales
@akutz It is working in the CL most recently posted. I see
Andrewwhen running withgotip download 376194 && gotip run x.goAs Ian and I mention in https://github.com/golang/go/issues/50233#issuecomment-1005261663 and following , reading a field of a type param that has a structural constraint does not work. The example given is not a read of a type param with a structural constraint, but a read of a non-generic type (which has been returned by a generic function call).
Thanks @danscales! I strongly prefer option 3 from #50233 (support setting
x.b1) as it can avoid constraints using interfaces with mutating functions (i.e. setters), which necessarily have to be defined on an address, not the struct itself. This results in the situation wherenew(T)ends up being**Tinstead of just*T. A structural constraint would also enablevar t Tto define a new instance of T, where such behavior would not work for constraints expressed with interfaces.See issue #50233 as well . We currently allow struct initialization of interface types with a single struct type, but not reading/writing of individual fields in expressions/assignments, etc. Still to be decided whether we should support both or not support both for Go 1.18, but we will aim to become consistent. We should be deciding soon.
A smaller repro: https://go.dev/play/p/bJ1QGu1MU38?v=gotip
I’m working on a book about generics in Go (self-promotion klaxon), and one of my beta readers sent me more or less exactly this as a bug report. When I looked at it, I was confused too. If the constraint is precisely that T is a struct with an
intfieldx, then shouldn’t we be able to set thexfield on a T parameter? It certainly does seem that we should. Indeed, if we can’t reference a struct field in a generic function constrained on that struct, that rather limits the usefulness of such functions! And, by extension, constraints involving struct types.I need to explain in the book why this apparently intuitive idea doesn’t work (yet); how do you think I should frame that explanation? Should I say something like “this doesn’t work due to a known compiler bug #50417”? Or is it rather a misunderstanding of the type parameters proposal which I can clarify? (Perhaps the spec should clarify it.) Or is it something like “this definitely should work, but it is difficult to implement, so we haven’t done it yet—but we’re working on it.”?