go/types, types2: don't panic when instantiating generic alias with wrong number of type arguments

The existing code assumed the type argument count check in
Checker.instance couldn't fail for generic alias types
(matching the code for generic signatures), but it actually
can.

Adjust the code accordingly and document that the result of
Checker.instance may be invalid.

Review all call sites of Checker.instance and make sure we
handle the failure case, or document the code accordingly
(in the case of generic signatures).

When reporting an type argument count error, use the alias
name rather than the alias string representation to match
the error we get for a non-alias type.

While at it, update the manual.go template for ease of use.

Fixes #71198.

Change-Id: I6d19ec6418440e9b49574a2d7dd9825e0af6c2fc
Reviewed-on: https://go-review.googlesource.com/c/go/+/641857
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@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 2025-01-09 15:01:03 -08:00 committed by Gopher Robot
parent c53307c3fd
commit 17ed215958
9 changed files with 56 additions and 14 deletions

View file

@ -142,6 +142,9 @@ func (check *Checker) instantiateSignature(pos syntax.Pos, expr syntax.Expr, typ
}() }()
} }
// For signatures, Checker.instance will always succeed because the type argument
// count is correct at this point (see assertion above); hence the type assertion
// to *Signature will always succeed.
inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature) inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature)
assert(inst.TypeParams().Len() == 0) // signature is not generic anymore assert(inst.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(expr, targs, inst) check.recordInstance(expr, targs, inst)

View file

@ -74,7 +74,8 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e
// instance instantiates the given original (generic) function or type with the // instance instantiates the given original (generic) function or type with the
// provided type arguments and returns the resulting instance. If an identical // provided type arguments and returns the resulting instance. If an identical
// instance exists already in the given contexts, it returns that instance, // instance exists already in the given contexts, it returns that instance,
// otherwise it creates a new one. // otherwise it creates a new one. If there is an error (such as wrong number
// of type arguments), the result is Typ[Invalid].
// //
// If expanding is non-nil, it is the Named instance type currently being // If expanding is non-nil, it is the Named instance type currently being
// expanded. If ctxt is non-nil, it is the context associated with the current // expanded. If ctxt is non-nil, it is the context associated with the current
@ -133,9 +134,13 @@ func (check *Checker) instance(pos syntax.Pos, orig genericType, targs []Type, e
assert(expanding == nil) // Alias instances cannot be reached from Named types assert(expanding == nil) // Alias instances cannot be reached from Named types
} }
// verify type parameter count (see go.dev/issue/71198 for a test case)
tparams := orig.TypeParams() tparams := orig.TypeParams()
// TODO(gri) investigate if this is needed (type argument and parameter count seem to be correct here) if !check.validateTArgLen(pos, orig.obj.Name(), tparams.Len(), len(targs)) {
if !check.validateTArgLen(pos, orig.String(), tparams.Len(), len(targs)) { // TODO(gri) Consider returning a valid alias instance with invalid
// underlying (aliased) type to match behavior of *Named
// types. Then this function will never return an invalid
// result.
return Typ[Invalid] return Typ[Invalid]
} }
if tparams.Len() == 0 { if tparams.Len() == 0 {

View file

@ -1,4 +1,4 @@
// Copyright 2024 The Go Authors. All rights reserved. // Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View file

@ -475,9 +475,14 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *
} }
// create instance // create instance
// The instance is not generic anymore as it has type arguments, but it still // The instance is not generic anymore as it has type arguments, but unless
// satisfies the genericType interface because it has type parameters, too. // instantiation failed, it still satisfies the genericType interface because
inst := check.instance(x.Pos(), gtyp, targs, nil, check.context()).(genericType) // it has type parameters, too.
ityp := check.instance(x.Pos(), gtyp, targs, nil, check.context())
inst, _ := ityp.(genericType)
if inst == nil {
return Typ[Invalid]
}
// For Named types, orig.tparams may not be set up, so we need to do expansion later. // For Named types, orig.tparams may not be set up, so we need to do expansion later.
check.later(func() { check.later(func() {

View file

@ -143,6 +143,9 @@ func (check *Checker) instantiateSignature(pos token.Pos, expr ast.Expr, typ *Si
}() }()
} }
// For signatures, Checker.instance will always succeed because the type argument
// count is correct at this point (see assertion above); hence the type assertion
// to *Signature will always succeed.
inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature) inst := check.instance(pos, typ, targs, nil, check.context()).(*Signature)
assert(inst.TypeParams().Len() == 0) // signature is not generic anymore assert(inst.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(expr, targs, inst) check.recordInstance(expr, targs, inst)

View file

@ -77,7 +77,8 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e
// instance instantiates the given original (generic) function or type with the // instance instantiates the given original (generic) function or type with the
// provided type arguments and returns the resulting instance. If an identical // provided type arguments and returns the resulting instance. If an identical
// instance exists already in the given contexts, it returns that instance, // instance exists already in the given contexts, it returns that instance,
// otherwise it creates a new one. // otherwise it creates a new one. If there is an error (such as wrong number
// of type arguments), the result is Typ[Invalid].
// //
// If expanding is non-nil, it is the Named instance type currently being // If expanding is non-nil, it is the Named instance type currently being
// expanded. If ctxt is non-nil, it is the context associated with the current // expanded. If ctxt is non-nil, it is the context associated with the current
@ -136,9 +137,13 @@ func (check *Checker) instance(pos token.Pos, orig genericType, targs []Type, ex
assert(expanding == nil) // Alias instances cannot be reached from Named types assert(expanding == nil) // Alias instances cannot be reached from Named types
} }
// verify type parameter count (see go.dev/issue/71198 for a test case)
tparams := orig.TypeParams() tparams := orig.TypeParams()
// TODO(gri) investigate if this is needed (type argument and parameter count seem to be correct here) if !check.validateTArgLen(pos, orig.obj.Name(), tparams.Len(), len(targs)) {
if !check.validateTArgLen(pos, orig.String(), tparams.Len(), len(targs)) { // TODO(gri) Consider returning a valid alias instance with invalid
// underlying (aliased) type to match behavior of *Named
// types. Then this function will never return an invalid
// result.
return Typ[Invalid] return Typ[Invalid]
} }
if tparams.Len() == 0 { if tparams.Len() == 0 {

View file

@ -1,4 +1,4 @@
// Copyright 2024 The Go Authors. All rights reserved. // Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.

View file

@ -471,9 +471,14 @@ func (check *Checker) instantiatedType(ix *indexedExpr, def *TypeName) (res Type
} }
// create instance // create instance
// The instance is not generic anymore as it has type arguments, but it still // The instance is not generic anymore as it has type arguments, but unless
// satisfies the genericType interface because it has type parameters, too. // instantiation failed, it still satisfies the genericType interface because
inst := check.instance(ix.Pos(), gtyp, targs, nil, check.context()).(genericType) // it has type parameters, too.
ityp := check.instance(ix.Pos(), gtyp, targs, nil, check.context())
inst, _ := ityp.(genericType)
if inst == nil {
return Typ[Invalid]
}
// For Named types, orig.tparams may not be set up, so we need to do expansion later. // For Named types, orig.tparams may not be set up, so we need to do expansion later.
check.later(func() { check.later(func() {

View file

@ -0,0 +1,16 @@
// -gotypesalias=1
// Copyright 2025 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
type A[_ any] = any
// This must not panic; also the error message must match the style for non-alias types, below.
func _[_ A /* ERROR "too many type arguments for type A: have 2, want 1" */ [int, string]]() {}
type T[_ any] any
func _[_ T /* ERROR "too many type arguments for type T: have 2, want 1" */ [int, string]]() {}