cmd/compile, go/*: allow generic methods starting with Go 1.27

This only changes the type checkers and tests.
Backend compiler changes are not included.

For #77273.

Change-Id: I8cf0b6fddf6afd6b08b06ba6fdf9c726af4bea8d
Reviewed-on: https://go-review.googlesource.com/c/go/+/746820
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer
2026-02-18 14:41:50 -08:00
committed by Gopher Robot
parent d4ace6b007
commit e84da0405b
9 changed files with 164 additions and 32 deletions

View File

@@ -415,22 +415,27 @@ func (check *Checker) collectObjects() {
case *syntax.FuncDecl:
name := s.Name.Value
obj := NewFunc(s.Name.Pos(), pkg, name, nil)
hasTParamError := false // avoid duplicate type parameter errors
obj := NewFunc(s.Name.Pos(), pkg, name, nil) // signature set later
var tparam0 *syntax.Field
if len(s.TParamList) > 0 {
tparam0 = s.TParamList[0]
}
if s.Recv == nil {
// regular function
if name == "init" || name == "main" && pkg.name == "main" {
// init and main functions must not declare type and ordinary parameters or results
code := InvalidInitDecl
if name == "main" {
code = InvalidMainDecl
}
if len(s.TParamList) != 0 {
check.softErrorf(s.TParamList[0], code, "func %s must have no type parameters", name)
hasTParamError = true
if tparam0 != nil {
check.softErrorf(tparam0, code, "func %s must have no type parameters", name)
}
if t := s.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 {
check.softErrorf(s.Name, code, "func %s must have no arguments and no return values", name)
}
} else {
_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_18, "type parameter")
}
// don't declare init functions in the package scope - they are invisible
if name == "init" {
@@ -452,14 +457,9 @@ func (check *Checker) collectObjects() {
if recv, _ := base.(*syntax.Name); recv != nil && name != "_" {
methods = append(methods, methodInfo{obj, ptr, recv})
}
// methods cannot have type parameters for now
if len(s.TParamList) != 0 {
check.softErrorf(s.TParamList[0], InvalidMethodTypeParams, "method %s must have no type parameters", name)
hasTParamError = true
}
_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_27, "generic method")
check.recordDef(s.Name, obj)
}
_ = len(s.TParamList) != 0 && !hasTParamError && check.verifyVersionf(s.TParamList[0], go1_18, "type parameter")
info := &declInfo{file: fileScope, version: check.version, fdecl: s}
// Methods are not package-level objects but we still track them in the
// object map so that we can handle them like regular functions (if the

View File

@@ -44,6 +44,7 @@ var (
go1_22 = asGoVersion("go1.22")
go1_23 = asGoVersion("go1.23")
go1_26 = asGoVersion("go1.26")
go1_27 = asGoVersion("go1.27")
// current (deployed) Go version
go_current = asGoVersion(fmt.Sprintf("go1.%d", goversion.Version))

View File

@@ -400,7 +400,10 @@ func (check *Checker) collectObjects() {
case funcDecl:
name := d.decl.Name.Name
obj := NewFunc(d.decl.Name.Pos(), pkg, name, nil) // signature set later
hasTParamError := false // avoid duplicate type parameter errors
var tparam0 *ast.Field
if d.decl.Type.TypeParams.NumFields() > 0 {
tparam0 = d.decl.Type.TypeParams.List[0]
}
if d.decl.Recv.NumFields() == 0 {
// regular function
if d.decl.Recv != nil {
@@ -408,18 +411,20 @@ func (check *Checker) collectObjects() {
// treat as function
}
if name == "init" || (name == "main" && check.pkg.name == "main") {
// init and main functions must not declare type and ordinary parameters or results
code := InvalidInitDecl
if name == "main" {
code = InvalidMainDecl
}
if d.decl.Type.TypeParams.NumFields() != 0 {
check.softErrorf(d.decl.Type.TypeParams.List[0], code, "func %s must have no type parameters", name)
hasTParamError = true
if tparam0 != nil {
check.softErrorf(tparam0, code, "func %s must have no type parameters", name)
}
if t := d.decl.Type; t.Params.NumFields() != 0 || t.Results != nil {
// TODO(rFindley) Should this be a hard error?
check.softErrorf(d.decl.Name, code, "func %s must have no arguments and no return values", name)
}
} else {
_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_18, "type parameter")
}
if name == "init" {
// don't declare init functions in the package scope - they are invisible
@@ -433,12 +438,6 @@ func (check *Checker) collectObjects() {
}
} else {
// method
// TODO(rFindley) earlier versions of this code checked that methods
// have no type parameters, but this is checked later
// when type checking the function type. Confirm that
// we don't need to check tparams here.
ptr, base, _ := check.unpackRecv(d.decl.Recv.List[0].Type, false)
// (Methods with invalid receiver cannot be associated to a type, and
// methods with blank _ names are never found; no need to collect any
@@ -446,14 +445,9 @@ func (check *Checker) collectObjects() {
if recv, _ := base.(*ast.Ident); recv != nil && name != "_" {
methods = append(methods, methodInfo{obj, ptr, recv})
}
// methods cannot have type parameters for now
if d.decl.Type.TypeParams.NumFields() != 0 {
check.softErrorf(d.decl.Type.TypeParams.List[0], InvalidMethodTypeParams, "method %s must have no type parameters", name)
hasTParamError = true
}
_ = tparam0 != nil && check.verifyVersionf(tparam0, go1_27, "generic method")
check.recordDef(d.decl.Name, obj)
}
_ = d.decl.Type.TypeParams.NumFields() != 0 && !hasTParamError && check.verifyVersionf(d.decl.Type.TypeParams.List[0], go1_18, "type parameter")
info := &declInfo{file: fileScope, version: check.version, fdecl: d.decl}
// Methods are not package-level objects but we still track them in the
// object map so that we can handle them like regular functions (if the

View File

@@ -44,6 +44,7 @@ var (
go1_22 = asGoVersion("go1.22")
go1_23 = asGoVersion("go1.23")
go1_26 = asGoVersion("go1.26")
go1_27 = asGoVersion("go1.27")
// current (deployed) Go version
go_current = asGoVersion(fmt.Sprintf("go1.%d", goversion.Version))

View File

@@ -326,11 +326,13 @@ func init() {}
func init[_ /* ERROR "func init must have no type parameters" */ any]() {}
func init[P /* ERROR "func init must have no type parameters" */ any]() {}
// type parameters on concrete methods are permitted
type T struct {}
func (T) m1() {}
func (T) m2[_ /* ERROR "method m2 must have no type parameters" */ any]() {}
func (T) m3[P /* ERROR "method m3 must have no type parameters" */ any]() {}
func (T) m2[_ any]() {}
func (T) m3[P any]() {}
// type inference across parameterized types

View File

@@ -0,0 +1,27 @@
// -lang=go1.26
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
// The parser does not accept type parameters for interface methods.
// In the past, type checking the code below led to a crash (#50427).
type T interface{ m[ /* ERROR "must have no type parameters" */ P any]() }
func _(t T) {
var _ interface{ m[ /* ERROR "must have no type parameters" */ P any](); n() } = t /* ERROR "does not implement" */
}
// Type parameters on concrete methods are not permitted before Go 1.27.
type S struct{}
func (S) m[P /* ERROR "generic method requires go1.27 or later" */ any]() {}
func _(s S) {
var _ interface{ m[ /* ERROR "must have no type parameters" */ P any](); n() } = s /* ERROR "does not implement" */
}

View File

@@ -4,7 +4,7 @@
package p
// The parser no longer parses type parameters for methods.
// The parser does not accept type parameters for interface methods.
// In the past, type checking the code below led to a crash (#50427).
type T interface{ m[ /* ERROR "must have no type parameters" */ P any]() }
@@ -13,9 +13,11 @@ func _(t T) {
var _ interface{ m[ /* ERROR "must have no type parameters" */ P any](); n() } = t /* ERROR "does not implement" */
}
// Type parameters on concrete methods are permitted as of Go 1.27.
type S struct{}
func (S) m[P /* ERROR "must have no type parameters" */ any]() {}
func (S) m[P any]() {}
func _(s S) {
var _ interface{ m[ /* ERROR "must have no type parameters" */ P any](); n() } = s /* ERROR "does not implement" */

View File

@@ -0,0 +1,105 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
// Non-interface methods may declare type parameters.
type T struct{}
func (T) m[P any](x P) P { return x }
func _() {
// A generic method must be instantiated before it is called.
var x T
var _ int = x.m[int](1) // explicit instantiation
var _ int = x.m(2) // instantiation via type inference
var _ int = x /* ERROR "cannot use x.m(3.14) (value of type float64)" */ .m(3.14)
// Receivers of generic method calls may be complex expressions:
// Instantiation must work not just on simple operands.
var a [10]T
_ = a[0].m[int] // explicit instantiation
_ = a[1].m(2.72) // instantiation via type inference
var m map[string][]struct{ T }
_ = m["foo"][0].T.m[float32]
_ = m["foo"][0].T.m(2.72)
_ = m["foo"][0].m[float32] // method promotion with explicit instantiation
_ = m["foo"][0].m(2.72) // method promotion with instantiation via type inference
// A generic method expression may be assigned to a function after instantiation.
var _ func(T, int) int = T.m[int] // explicit instantiation
var _ func(T, int) int = T.m // instantiation via type inference
// A generic method value may be assigned to a function after instantiation.
var _ func(int) int = x.m[int] // explicit instantiation
var _ func(int) int = x.m // instantiation via type inference
}
// Generic methods may be added to generic types.
type G[F any] struct {
f F
}
// The constraint for the method parameter may refer to the receiver type parameter.
func (g G[F]) m[H interface{ convert(F) H }]() (r H) {
return r.convert(g.f)
}
// But the usual restrictions for type terms still apply.
func (G[F]) m2[P F /* ERROR "cannot use a type parameter as constraint" */ ]() {}
func (G[F]) m3[P *F]() {} // this is ok
// Generic methods don't satisfy interfaces.
type I[P any] interface {
m(P) P
}
var _ I[int] = T /* ERROR "(wrong type for method m)\n\t\thave m[P any](P) P\n\t\twant m(int) int" */ {}
// A method declaring type parameters is generic even if it doesn't use the type parameters in its signature.
type U struct {}
func (U) m[_ any](x int) int { return x }
var _ I[int] = U /* ERROR "wrong type for method m)\n\t\thave m[_ any](int) int\n\t\twant m(int) int" */ {}
type J interface {
m()
}
type V struct {}
func (V) m[_ any]() {}
var _ J = V /* ERROR "wrong type for method m)\n\t\thave m[_ any]()\n\t\twant m()" */ {}
// Test case from parser smoke test.
type List[E any] []E
func (l List[E]) Map[F any](m func(E) F) (r List[F]) {
for _, x := range l {
r = append(r, m(x))
}
return
}
func _() {
l := List[string]{"foo", "foobar", "42"}
r := l.Map(func(s string) int { return len(s)})
_ = r
}
func _[E, F any](l List[E]) List[F] {
var f func(List[E], func(E) F) List[F] = List[E].Map // method expression & type inference
return f(l, func(E) F { var f F; return f })
}
func _[E, F any](l List[E]) List[F] {
var f func(func(E) F) List[F] = l.Map // method value & type inference
return f(func(E) F { var f F; return f })
}

View File

@@ -8,7 +8,7 @@ package p
type S struct{}
func (S) _[_ any]() {} // ERROR "method _ must have no type parameters"
func (S) _[_ any]() {}
type _ interface {
m[_ any]() // ERROR "interface method must have no type parameters"