mirror of
https://github.com/golang/go
synced 2024-09-04 15:34:21 +00:00
[dev.typeparams] go/types: move instantiation code to instantiate.go (cleanup)
This is a port of CL 333589 to go/types. Change-Id: Ib9fb1b09e10c400e62d20b55ff0558f3a92fc0eb Reviewed-on: https://go-review.googlesource.com/c/go/+/335072 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
41ff0aac13
commit
4a72be87b3
|
@ -2,6 +2,9 @@
|
||||||
// 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.
|
||||||
|
|
||||||
|
// This file implements instantiation of generic types
|
||||||
|
// through substitution of type parameters by type arguments.
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,6 +12,106 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Instantiate instantiates the type typ with the given type arguments
|
||||||
|
// targs. To check type constraint satisfaction, verify must be set.
|
||||||
|
// pos and posList correspond to the instantiation and type argument
|
||||||
|
// positions respectively; posList may be nil or shorter than the number
|
||||||
|
// of type arguments provided.
|
||||||
|
// typ must be a *Named or a *Signature type, and its number of type
|
||||||
|
// parameters must match the number of provided type arguments.
|
||||||
|
// The receiver (check) may be nil if and only if verify is not set.
|
||||||
|
// The result is a new, instantiated (not generic) type of the same kind
|
||||||
|
// (either a *Named or a *Signature).
|
||||||
|
// Any methods attached to a *Named are simply copied; they are not
|
||||||
|
// instantiated.
|
||||||
|
func (check *Checker) Instantiate(pos token.Pos, typ Type, targs []Type, posList []token.Pos, verify bool) (res Type) {
|
||||||
|
if verify && check == nil {
|
||||||
|
panic("cannot have nil receiver if verify is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if check != nil && trace {
|
||||||
|
check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs))
|
||||||
|
check.indent++
|
||||||
|
defer func() {
|
||||||
|
check.indent--
|
||||||
|
var under Type
|
||||||
|
if res != nil {
|
||||||
|
// Calling under() here may lead to endless instantiations.
|
||||||
|
// Test case: type T[P any] T[P]
|
||||||
|
// TODO(gri) investigate if that's a bug or to be expected.
|
||||||
|
under = res.Underlying()
|
||||||
|
}
|
||||||
|
check.trace(pos, "=> %s (under = %s)", res, under)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(len(posList) <= len(targs))
|
||||||
|
|
||||||
|
// TODO(gri) What is better here: work with TypeParams, or work with TypeNames?
|
||||||
|
var tparams []*TypeName
|
||||||
|
switch t := typ.(type) {
|
||||||
|
case *Named:
|
||||||
|
tparams = t.TParams()
|
||||||
|
case *Signature:
|
||||||
|
tparams = t.tparams
|
||||||
|
defer func() {
|
||||||
|
// If we had an unexpected failure somewhere don't panic below when
|
||||||
|
// asserting res.(*Signature). Check for *Signature in case Typ[Invalid]
|
||||||
|
// is returned.
|
||||||
|
if _, ok := res.(*Signature); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the signature doesn't use its type parameters, subst
|
||||||
|
// will not make a copy. In that case, make a copy now (so
|
||||||
|
// we can set tparams to nil w/o causing side-effects).
|
||||||
|
if t == res {
|
||||||
|
copy := *t
|
||||||
|
res = ©
|
||||||
|
}
|
||||||
|
// After instantiating a generic signature, it is not generic
|
||||||
|
// anymore; we need to set tparams to nil.
|
||||||
|
res.(*Signature).tparams = nil
|
||||||
|
}()
|
||||||
|
default:
|
||||||
|
// only types and functions can be generic
|
||||||
|
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
// the number of supplied types must match the number of type parameters
|
||||||
|
if len(targs) != len(tparams) {
|
||||||
|
// TODO(gri) provide better error message
|
||||||
|
if check != nil {
|
||||||
|
check.errorf(atPos(pos), _Todo, "got %d arguments but %d type parameters", len(targs), len(tparams))
|
||||||
|
return Typ[Invalid]
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("%v: got %d arguments but %d type parameters", pos, len(targs), len(tparams)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tparams) == 0 {
|
||||||
|
return typ // nothing to do (minor optimization)
|
||||||
|
}
|
||||||
|
|
||||||
|
smap := makeSubstMap(tparams, targs)
|
||||||
|
|
||||||
|
// check bounds
|
||||||
|
if verify {
|
||||||
|
for i, tname := range tparams {
|
||||||
|
// best position for error reporting
|
||||||
|
pos := pos
|
||||||
|
if i < len(posList) {
|
||||||
|
pos = posList[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop checking bounds after the first failure
|
||||||
|
if !check.satisfies(pos, targs[i], tname.typ.(*TypeParam), smap) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return check.subst(pos, typ, smap)
|
||||||
|
}
|
||||||
|
|
||||||
// InstantiateLazy is like Instantiate, but avoids actually
|
// InstantiateLazy is like Instantiate, but avoids actually
|
||||||
// instantiating the type until needed.
|
// instantiating the type until needed.
|
||||||
func (check *Checker) InstantiateLazy(pos token.Pos, typ Type, targs []Type, verify bool) (res Type) {
|
func (check *Checker) InstantiateLazy(pos token.Pos, typ Type, targs []Type, verify bool) (res Type) {
|
||||||
|
@ -25,3 +128,85 @@ func (check *Checker) InstantiateLazy(pos token.Pos, typ Type, targs []Type, ver
|
||||||
verify: verify,
|
verify: verify,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// satisfies reports whether the type argument targ satisfies the constraint of type parameter
|
||||||
|
// parameter tpar (after any of its type parameters have been substituted through smap).
|
||||||
|
// A suitable error is reported if the result is false.
|
||||||
|
// TODO(gri) This should be a method of interfaces or type sets.
|
||||||
|
func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *TypeParam, smap *substMap) bool {
|
||||||
|
iface := tpar.Bound()
|
||||||
|
if iface.Empty() {
|
||||||
|
return true // no type bound
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type parameter bound is parameterized with the same type parameters
|
||||||
|
// as the instantiated type; before we can use it for bounds checking we
|
||||||
|
// need to instantiate it with the type arguments with which we instantiate
|
||||||
|
// the parameterized type.
|
||||||
|
iface = check.subst(pos, iface, smap).(*Interface)
|
||||||
|
|
||||||
|
// targ must implement iface (methods)
|
||||||
|
// - check only if we have methods
|
||||||
|
if iface.NumMethods() > 0 {
|
||||||
|
// If the type argument is a pointer to a type parameter, the type argument's
|
||||||
|
// method set is empty.
|
||||||
|
// TODO(gri) is this what we want? (spec question)
|
||||||
|
if base, isPtr := deref(targ); isPtr && asTypeParam(base) != nil {
|
||||||
|
check.errorf(atPos(pos), 0, "%s has no methods", targ)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if m, wrong := check.missingMethod(targ, iface, true); m != nil {
|
||||||
|
// TODO(gri) needs to print updated name to avoid major confusion in error message!
|
||||||
|
// (print warning for now)
|
||||||
|
// Old warning:
|
||||||
|
// check.softErrorf(pos, "%s does not satisfy %s (warning: name not updated) = %s (missing method %s)", targ, tpar.bound, iface, m)
|
||||||
|
if m.name == "==" {
|
||||||
|
// We don't want to report "missing method ==".
|
||||||
|
check.softErrorf(atPos(pos), 0, "%s does not satisfy comparable", targ)
|
||||||
|
} else if wrong != nil {
|
||||||
|
// TODO(gri) This can still report uninstantiated types which makes the error message
|
||||||
|
// more difficult to read then necessary.
|
||||||
|
// TODO(rFindley) should this use parentheses rather than ':' for qualification?
|
||||||
|
check.softErrorf(atPos(pos), _Todo,
|
||||||
|
"%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s",
|
||||||
|
targ, tpar.bound, wrong, m,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
check.softErrorf(atPos(pos), 0, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m.name)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// targ's underlying type must also be one of the interface types listed, if any
|
||||||
|
if iface.typeSet().types == nil {
|
||||||
|
return true // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
// If targ is itself a type parameter, each of its possible types, but at least one, must be in the
|
||||||
|
// list of iface types (i.e., the targ type list must be a non-empty subset of the iface types).
|
||||||
|
if targ := asTypeParam(targ); targ != nil {
|
||||||
|
targBound := targ.Bound()
|
||||||
|
if targBound.typeSet().types == nil {
|
||||||
|
check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return iface.is(func(typ Type, tilde bool) bool {
|
||||||
|
// TODO(gri) incorporate tilde information!
|
||||||
|
if !iface.isSatisfiedBy(typ) {
|
||||||
|
// TODO(gri) match this error message with the one below (or vice versa)
|
||||||
|
check.softErrorf(atPos(pos), 0, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, typ, iface.typeSet().types)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, targ's type or underlying type must also be one of the interface types listed, if any.
|
||||||
|
if !iface.isSatisfiedBy(targ) {
|
||||||
|
check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.typeSet().types)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
// 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.
|
||||||
|
|
||||||
// This file implements instantiation of generic types
|
// This file implements type parameter substitution.
|
||||||
// through substitution of type parameters by actual
|
|
||||||
// types.
|
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
@ -56,188 +54,6 @@ func (m *substMap) lookup(tpar *TypeParam) Type {
|
||||||
return tpar
|
return tpar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate instantiates the type typ with the given type arguments
|
|
||||||
// targs. To check type constraint satisfaction, verify must be set.
|
|
||||||
// pos and posList correspond to the instantiation and type argument
|
|
||||||
// positions respectively; posList may be nil or shorter than the number
|
|
||||||
// of type arguments provided.
|
|
||||||
// typ must be a *Named or a *Signature type, and its number of type
|
|
||||||
// parameters must match the number of provided type arguments.
|
|
||||||
// The receiver (check) may be nil if and only if verify is not set.
|
|
||||||
// The result is a new, instantiated (not generic) type of the same kind
|
|
||||||
// (either a *Named or a *Signature).
|
|
||||||
// Any methods attached to a *Named are simply copied; they are not
|
|
||||||
// instantiated.
|
|
||||||
func (check *Checker) Instantiate(pos token.Pos, typ Type, targs []Type, posList []token.Pos, verify bool) (res Type) {
|
|
||||||
if verify && check == nil {
|
|
||||||
panic("cannot have nil receiver if verify is set")
|
|
||||||
}
|
|
||||||
|
|
||||||
if check != nil && trace {
|
|
||||||
check.trace(pos, "-- instantiating %s with %s", typ, typeListString(targs))
|
|
||||||
check.indent++
|
|
||||||
defer func() {
|
|
||||||
check.indent--
|
|
||||||
var under Type
|
|
||||||
if res != nil {
|
|
||||||
// Calling under() here may lead to endless instantiations.
|
|
||||||
// Test case: type T[P any] T[P]
|
|
||||||
// TODO(gri) investigate if that's a bug or to be expected.
|
|
||||||
under = res.Underlying()
|
|
||||||
}
|
|
||||||
check.trace(pos, "=> %s (under = %s)", res, under)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(len(posList) <= len(targs))
|
|
||||||
|
|
||||||
// TODO(gri) What is better here: work with TypeParams, or work with TypeNames?
|
|
||||||
var tparams []*TypeName
|
|
||||||
switch t := typ.(type) {
|
|
||||||
case *Named:
|
|
||||||
tparams = t.TParams()
|
|
||||||
case *Signature:
|
|
||||||
tparams = t.tparams
|
|
||||||
defer func() {
|
|
||||||
// If we had an unexpected failure somewhere don't panic below when
|
|
||||||
// asserting res.(*Signature). Check for *Signature in case Typ[Invalid]
|
|
||||||
// is returned.
|
|
||||||
if _, ok := res.(*Signature); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// If the signature doesn't use its type parameters, subst
|
|
||||||
// will not make a copy. In that case, make a copy now (so
|
|
||||||
// we can set tparams to nil w/o causing side-effects).
|
|
||||||
if t == res {
|
|
||||||
copy := *t
|
|
||||||
res = ©
|
|
||||||
}
|
|
||||||
// After instantiating a generic signature, it is not generic
|
|
||||||
// anymore; we need to set tparams to nil.
|
|
||||||
res.(*Signature).tparams = nil
|
|
||||||
}()
|
|
||||||
default:
|
|
||||||
// only types and functions can be generic
|
|
||||||
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
|
|
||||||
}
|
|
||||||
|
|
||||||
// the number of supplied types must match the number of type parameters
|
|
||||||
if len(targs) != len(tparams) {
|
|
||||||
// TODO(gri) provide better error message
|
|
||||||
if check != nil {
|
|
||||||
check.errorf(atPos(pos), _Todo, "got %d arguments but %d type parameters", len(targs), len(tparams))
|
|
||||||
return Typ[Invalid]
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("%v: got %d arguments but %d type parameters", pos, len(targs), len(tparams)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tparams) == 0 {
|
|
||||||
return typ // nothing to do (minor optimization)
|
|
||||||
}
|
|
||||||
|
|
||||||
smap := makeSubstMap(tparams, targs)
|
|
||||||
|
|
||||||
// check bounds
|
|
||||||
if verify {
|
|
||||||
for i, tname := range tparams {
|
|
||||||
// best position for error reporting
|
|
||||||
pos := pos
|
|
||||||
if i < len(posList) {
|
|
||||||
pos = posList[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop checking bounds after the first failure
|
|
||||||
if !check.satisfies(pos, targs[i], tname.typ.(*TypeParam), smap) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return check.subst(pos, typ, smap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// satisfies reports whether the type argument targ satisfies the constraint of type parameter
|
|
||||||
// parameter tpar (after any of its type parameters have been substituted through smap).
|
|
||||||
// A suitable error is reported if the result is false.
|
|
||||||
// TODO(gri) This should be a method of interfaces or type sets.
|
|
||||||
func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *TypeParam, smap *substMap) bool {
|
|
||||||
iface := tpar.Bound()
|
|
||||||
if iface.Empty() {
|
|
||||||
return true // no type bound
|
|
||||||
}
|
|
||||||
|
|
||||||
// The type parameter bound is parameterized with the same type parameters
|
|
||||||
// as the instantiated type; before we can use it for bounds checking we
|
|
||||||
// need to instantiate it with the type arguments with which we instantiate
|
|
||||||
// the parameterized type.
|
|
||||||
iface = check.subst(pos, iface, smap).(*Interface)
|
|
||||||
|
|
||||||
// targ must implement iface (methods)
|
|
||||||
// - check only if we have methods
|
|
||||||
if iface.NumMethods() > 0 {
|
|
||||||
// If the type argument is a pointer to a type parameter, the type argument's
|
|
||||||
// method set is empty.
|
|
||||||
// TODO(gri) is this what we want? (spec question)
|
|
||||||
if base, isPtr := deref(targ); isPtr && asTypeParam(base) != nil {
|
|
||||||
check.errorf(atPos(pos), 0, "%s has no methods", targ)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if m, wrong := check.missingMethod(targ, iface, true); m != nil {
|
|
||||||
// TODO(gri) needs to print updated name to avoid major confusion in error message!
|
|
||||||
// (print warning for now)
|
|
||||||
// Old warning:
|
|
||||||
// check.softErrorf(pos, "%s does not satisfy %s (warning: name not updated) = %s (missing method %s)", targ, tpar.bound, iface, m)
|
|
||||||
if m.name == "==" {
|
|
||||||
// We don't want to report "missing method ==".
|
|
||||||
check.softErrorf(atPos(pos), 0, "%s does not satisfy comparable", targ)
|
|
||||||
} else if wrong != nil {
|
|
||||||
// TODO(gri) This can still report uninstantiated types which makes the error message
|
|
||||||
// more difficult to read then necessary.
|
|
||||||
// TODO(rFindley) should this use parentheses rather than ':' for qualification?
|
|
||||||
check.softErrorf(atPos(pos), _Todo,
|
|
||||||
"%s does not satisfy %s: wrong method signature\n\tgot %s\n\twant %s",
|
|
||||||
targ, tpar.bound, wrong, m,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
check.softErrorf(atPos(pos), 0, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m.name)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// targ's underlying type must also be one of the interface types listed, if any
|
|
||||||
if iface.typeSet().types == nil {
|
|
||||||
return true // nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
// If targ is itself a type parameter, each of its possible types, but at least one, must be in the
|
|
||||||
// list of iface types (i.e., the targ type list must be a non-empty subset of the iface types).
|
|
||||||
if targ := asTypeParam(targ); targ != nil {
|
|
||||||
targBound := targ.Bound()
|
|
||||||
if targBound.typeSet().types == nil {
|
|
||||||
check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return iface.is(func(typ Type, tilde bool) bool {
|
|
||||||
// TODO(gri) incorporate tilde information!
|
|
||||||
if !iface.isSatisfiedBy(typ) {
|
|
||||||
// TODO(gri) match this error message with the one below (or vice versa)
|
|
||||||
check.softErrorf(atPos(pos), 0, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, typ, iface.typeSet().types)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, targ's type or underlying type must also be one of the interface types listed, if any.
|
|
||||||
if !iface.isSatisfiedBy(targ) {
|
|
||||||
check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.typeSet().types)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// subst returns the type typ with its type parameters tpars replaced by
|
// subst returns the type typ with its type parameters tpars replaced by
|
||||||
// the corresponding type arguments targs, recursively.
|
// the corresponding type arguments targs, recursively.
|
||||||
// subst is functional in the sense that it doesn't modify the incoming
|
// subst is functional in the sense that it doesn't modify the incoming
|
||||||
|
|
Loading…
Reference in a new issue