aboutsummaryrefslogtreecommitdiff
path: root/src/go/types
diff options
context:
space:
mode:
Diffstat (limited to 'src/go/types')
-rw-r--r--src/go/types/api.go5
-rw-r--r--src/go/types/api_test.go43
-rw-r--r--src/go/types/call.go50
-rw-r--r--src/go/types/check.go7
-rw-r--r--src/go/types/decl.go67
-rw-r--r--src/go/types/environment.go7
-rw-r--r--src/go/types/errors.go2
-rw-r--r--src/go/types/errors_test.go1
-rw-r--r--src/go/types/expr.go2
-rw-r--r--src/go/types/exprstring.go2
-rw-r--r--src/go/types/infer.go125
-rw-r--r--src/go/types/instantiate.go8
-rw-r--r--src/go/types/instantiate_test.go82
-rw-r--r--src/go/types/lookup.go28
-rw-r--r--src/go/types/named.go189
-rw-r--r--src/go/types/object.go21
-rw-r--r--src/go/types/predicates.go12
-rw-r--r--src/go/types/resolver.go2
-rw-r--r--src/go/types/signature.go24
-rw-r--r--src/go/types/sizeof_test.go4
-rw-r--r--src/go/types/struct.go2
-rw-r--r--src/go/types/subst.go69
-rw-r--r--src/go/types/testdata/fixedbugs/issue43527.go216
-rw-r--r--src/go/types/testdata/fixedbugs/issue45550.go210
-rw-r--r--src/go/types/testdata/fixedbugs/issue47796.go233
-rw-r--r--src/go/types/testdata/fixedbugs/issue47887.go228
-rw-r--r--src/go/types/testdata/fixedbugs/issue48136.go236
-rw-r--r--src/go/types/type.go2
-rw-r--r--src/go/types/typeparam.go2
-rw-r--r--src/go/types/typestring.go10
-rw-r--r--src/go/types/typexpr.go12
-rw-r--r--src/go/types/unify.go3
32 files changed, 630 insertions, 274 deletions
diff --git a/src/go/types/api.go b/src/go/types/api.go
index 5beeff530c..ebc3a01266 100644
--- a/src/go/types/api.go
+++ b/src/go/types/api.go
@@ -115,6 +115,11 @@ type ImporterFrom interface {
// A Config specifies the configuration for type checking.
// The zero value for Config is a ready-to-use default configuration.
type Config struct {
+ // Environment is the environment used for resolving global
+ // identifiers. If nil, the type checker will initialize this
+ // field with a newly created environment.
+ Environment *Environment
+
// GoVersion describes the accepted Go language version. The string
// must follow the format "go%d.%d" (e.g. "go1.12") or it must be
// empty; an empty string indicates the latest language version.
diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go
index 49c054bd7d..d4f9bb65c9 100644
--- a/src/go/types/api_test.go
+++ b/src/go/types/api_test.go
@@ -152,6 +152,7 @@ func TestValuesInfo(t *testing.T) {
{`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `(0 + 0i)`},
{`package g0; const (a = len([iota]int{}); b; c); const _ = c`, `c`, `int`, `2`}, // issue #22341
+ {`package g1; var(j int32; s int; n = 1.0<<s == j)`, `1.0`, `int32`, `1`}, // issue #48422
}
for _, test := range tests {
@@ -1621,6 +1622,48 @@ func TestIdentical_issue15173(t *testing.T) {
}
}
+func TestIdenticalUnions(t *testing.T) {
+ tname := NewTypeName(token.NoPos, nil, "myInt", nil)
+ myInt := NewNamed(tname, Typ[Int], nil)
+ tmap := map[string]*Term{
+ "int": NewTerm(false, Typ[Int]),
+ "~int": NewTerm(true, Typ[Int]),
+ "string": NewTerm(false, Typ[String]),
+ "~string": NewTerm(true, Typ[String]),
+ "myInt": NewTerm(false, myInt),
+ }
+ makeUnion := func(s string) *Union {
+ parts := strings.Split(s, "|")
+ var terms []*Term
+ for _, p := range parts {
+ term := tmap[p]
+ if term == nil {
+ t.Fatalf("missing term %q", p)
+ }
+ terms = append(terms, term)
+ }
+ return NewUnion(terms)
+ }
+ for _, test := range []struct {
+ x, y string
+ want bool
+ }{
+ // These tests are just sanity checks. The tests for type sets and
+ // interfaces provide much more test coverage.
+ {"int|~int", "~int", true},
+ {"myInt|~int", "~int", true},
+ {"int|string", "string|int", true},
+ {"int|int|string", "string|int", true},
+ {"myInt|string", "int|string", false},
+ } {
+ x := makeUnion(test.x)
+ y := makeUnion(test.y)
+ if got := Identical(x, y); got != test.want {
+ t.Errorf("Identical(%v, %v) = %t", test.x, test.y, got)
+ }
+ }
+}
+
func TestIssue15305(t *testing.T) {
const src = "package p; func f() int16; var _ = f(undef)"
fset := token.NewFileSet()
diff --git a/src/go/types/call.go b/src/go/types/call.go
index 39cd67c5f3..4d14e31730 100644
--- a/src/go/types/call.go
+++ b/src/go/types/call.go
@@ -337,7 +337,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type
if sig.TypeParams().Len() > 0 {
if !check.allowVersion(check.pkg, 1, 18) {
switch call.Fun.(type) {
- case *ast.IndexExpr, *ast.MultiIndexExpr:
+ case *ast.IndexExpr, *ast.IndexListExpr:
ix := typeparams.UnpackIndexExpr(call.Fun)
check.softErrorf(inNode(call.Fun, ix.Lbrack), _Todo, "function instantiation requires go1.18 or later")
default:
@@ -532,54 +532,6 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
// methods may not have a fully set up signature yet
if m, _ := obj.(*Func); m != nil {
check.objDecl(m, nil)
- // If m has a parameterized receiver type, infer the type arguments from
- // the actual receiver provided and then substitute the type parameters in
- // the signature accordingly.
- // TODO(gri) factor this code out
- sig := m.typ.(*Signature)
- if sig.RParams().Len() > 0 {
- // For inference to work, we must use the receiver type
- // matching the receiver in the actual method declaration.
- // If the method is embedded, the matching receiver is the
- // embedded struct or interface that declared the method.
- // Traverse the embedding to find that type (issue #44688).
- recv := x.typ
- for i := 0; i < len(index)-1; i++ {
- // The embedded type is either a struct or a pointer to
- // a struct except for the last one (which we don't need).
- recv = asStruct(derefStructPtr(recv)).Field(index[i]).typ
- }
-
- // The method may have a pointer receiver, but the actually provided receiver
- // may be a (hopefully addressable) non-pointer value, or vice versa. Here we
- // only care about inferring receiver type parameters; to make the inference
- // work, match up pointer-ness of receiver and argument.
- if ptrRecv := isPointer(sig.recv.typ); ptrRecv != isPointer(recv) {
- if ptrRecv {
- recv = NewPointer(recv)
- } else {
- recv = recv.(*Pointer).base
- }
- }
- // Disable reporting of errors during inference below. If we're unable to infer
- // the receiver type arguments here, the receiver must be be otherwise invalid
- // and an error has been reported elsewhere.
- arg := operand{mode: variable, expr: x.expr, typ: recv}
- targs := check.infer(m, sig.RParams().list(), nil, NewTuple(sig.recv), []*operand{&arg}, false /* no error reporting */)
- if targs == nil {
- // We may reach here if there were other errors (see issue #40056).
- goto Error
- }
- // Don't modify m. Instead - for now - make a copy of m and use that instead.
- // (If we modify m, some tests will fail; possibly because the m is in use.)
- // TODO(gri) investigate and provide a correct explanation here
- copy := *m
- copy.typ = check.subst(e.Pos(), m.typ, makeSubstMap(sig.RParams().list(), targs), nil)
- obj = &copy
- }
- // TODO(gri) we also need to do substitution for parameterized interface methods
- // (this breaks code in testdata/linalg.go2 at the moment)
- // 12/20/2019: Is this TODO still correct?
}
if x.mode == typexpr {
diff --git a/src/go/types/check.go b/src/go/types/check.go
index 0383a58c64..63f4cbd4a0 100644
--- a/src/go/types/check.go
+++ b/src/go/types/check.go
@@ -89,7 +89,6 @@ type Checker struct {
nextID uint64 // unique Id for type parameters (first valid Id is 1)
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
- env *Environment // for deduplicating identical instances
// pkgPathMap maps package names to the set of distinct import paths we've
// seen for that name, anywhere in the import graph. It is used for
@@ -174,6 +173,11 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
conf = new(Config)
}
+ // make sure we have an environment
+ if conf.Environment == nil {
+ conf.Environment = NewEnvironment()
+ }
+
// make sure we have an info struct
if info == nil {
info = new(Info)
@@ -192,7 +196,6 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
version: version,
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
- env: NewEnvironment(),
}
}
diff --git a/src/go/types/decl.go b/src/go/types/decl.go
index d132d30b9d..0fdcfa8023 100644
--- a/src/go/types/decl.go
+++ b/src/go/types/decl.go
@@ -65,6 +65,12 @@ func (check *Checker) objDecl(obj Object, def *Named) {
}()
}
+ // Funcs with m.instRecv set have not yet be completed. Complete them now
+ // so that they have a type when objDecl exits.
+ if m, _ := obj.(*Func); m != nil && m.instRecv != nil {
+ check.completeMethod(check.conf.Environment, m)
+ }
+
// Checking the declaration of obj means inferring its type
// (and possibly its value, for constants).
// An object's type (and thus the object) may be in one of
@@ -316,7 +322,7 @@ func (check *Checker) validType(typ Type, path []Object) typeInfo {
}
case *Named:
- t.expand(check.env)
+ t.resolve(check.conf.Environment)
// don't touch the type if it is from a different package or the Universe scope
// (doing so would lead to a race condition - was issue #35049)
if t.obj.pkg != check.pkg {
@@ -615,7 +621,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) {
if tdecl.TypeParams != nil {
check.openScope(tdecl, "type parameters")
defer check.closeScope()
- named.tparams = check.collectTypeParams(tdecl.TypeParams)
+ check.collectTypeParams(&named.tparams, tdecl.TypeParams)
}
// determine underlying type of named
@@ -647,7 +653,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) {
}
}
-func (check *Checker) collectTypeParams(list *ast.FieldList) *TypeParamList {
+func (check *Checker) collectTypeParams(dst **TypeParamList, list *ast.FieldList) {
var tparams []*TypeParam
// Declare type parameters up-front, with empty interface as type bound.
// The scope of type parameters starts at the beginning of the type parameter
@@ -656,13 +662,28 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) *TypeParamList {
tparams = check.declareTypeParams(tparams, f.Names)
}
+ // Set the type parameters before collecting the type constraints because
+ // the parameterized type may be used by the constraints (issue #47887).
+ // Example: type T[P T[P]] interface{}
+ *dst = bindTParams(tparams)
+
index := 0
var bound Type
+ var bounds []Type
+ var posns []positioner // bound positions
for _, f := range list.List {
if f.Type == nil {
goto next
}
- bound = check.boundType(f.Type)
+ // The predeclared identifier "any" is visible only as a type bound in a type parameter list.
+ // If we allow "any" for general use, this if-statement can be removed (issue #33232).
+ if name, _ := unparen(f.Type).(*ast.Ident); name != nil && name.Name == "any" && check.lookup("any") == universeAny {
+ bound = universeAny.Type()
+ } else {
+ bound = check.typ(f.Type)
+ }
+ bounds = append(bounds, bound)
+ posns = append(posns, f.Type)
for i := range f.Names {
tparams[index+i].bound = bound
}
@@ -671,13 +692,26 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) *TypeParamList {
index += len(f.Names)
}
- return bindTParams(tparams)
+ check.later(func() {
+ for i, bound := range bounds {
+ u := under(bound)
+ if _, ok := u.(*Interface); !ok && u != Typ[Invalid] {
+ check.errorf(posns[i], _Todo, "%s is not an interface", bound)
+ }
+ }
+ })
}
func (check *Checker) declareTypeParams(tparams []*TypeParam, names []*ast.Ident) []*TypeParam {
+ // Use Typ[Invalid] for the type constraint to ensure that a type
+ // is present even if the actual constraint has not been assigned
+ // yet.
+ // TODO(gri) Need to systematically review all uses of type parameter
+ // constraints to make sure we don't rely on them if they
+ // are not properly set yet.
for _, name := range names {
tname := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
- tpar := check.newTypeParam(tname, &emptyInterface) // assigns type to tpar as a side-effect
+ tpar := check.newTypeParam(tname, Typ[Invalid]) // assigns type to tpar as a side-effect
check.declare(check.scope, name, tname, check.scope.pos) // TODO(gri) check scope position
tparams = append(tparams, tpar)
}
@@ -689,25 +723,6 @@ func (check *Checker) declareTypeParams(tparams []*TypeParam, names []*ast.Ident
return tparams
}
-// boundType type-checks the type expression e and returns its type, or Typ[Invalid].
-// The type must be an interface, including the predeclared type "any".
-func (check *Checker) boundType(e ast.Expr) Type {
- // The predeclared identifier "any" is visible only as a type bound in a type parameter list.
- // If we allow "any" for general use, this if-statement can be removed (issue #33232).
- if name, _ := unparen(e).(*ast.Ident); name != nil && name.Name == "any" && check.lookup("any") == universeAny {
- return universeAny.Type()
- }
-
- bound := check.typ(e)
- check.later(func() {
- u := under(bound)
- if _, ok := u.(*Interface); !ok && u != Typ[Invalid] {
- check.errorf(e, _Todo, "%s is not an interface", bound)
- }
- })
- return bound
-}
-
func (check *Checker) collectMethods(obj *TypeName) {
// get associated methods
// (Checker.collectObjects only collects methods with non-blank names;
@@ -764,7 +779,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
}
if base != nil {
- base.load() // TODO(mdempsky): Probably unnecessary.
+ base.resolve(nil) // TODO(mdempsky): Probably unnecessary.
base.methods = append(base.methods, m)
}
}
diff --git a/src/go/types/environment.go b/src/go/types/environment.go
index 93383efe1a..61fc3c5348 100644
--- a/src/go/types/environment.go
+++ b/src/go/types/environment.go
@@ -50,13 +50,6 @@ func (env *Environment) typeHash(typ Type, targs []Type) string {
h.typ(typ)
}
- if debug {
- // there should be no instance markers in type hashes
- for _, b := range buf.Bytes() {
- assert(b != instanceMarker)
- }
- }
-
return buf.String()
}
diff --git a/src/go/types/errors.go b/src/go/types/errors.go
index 933de93d85..2d48fe14da 100644
--- a/src/go/types/errors.go
+++ b/src/go/types/errors.go
@@ -265,7 +265,7 @@ func stripAnnotations(s string) string {
var b strings.Builder
for _, r := range s {
// strip #'s and subscript digits
- if r != instanceMarker && !('₀' <= r && r < '₀'+10) { // '₀' == U+2080
+ if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
b.WriteRune(r)
}
}
diff --git a/src/go/types/errors_test.go b/src/go/types/errors_test.go
index fdbe07cae0..942a9fdd4c 100644
--- a/src/go/types/errors_test.go
+++ b/src/go/types/errors_test.go
@@ -15,7 +15,6 @@ func TestStripAnnotations(t *testing.T) {
{"foo", "foo"},
{"foo₀", "foo"},
{"foo(T₀)", "foo(T)"},
- {"#foo(T₀)", "foo(T)"},
} {
got := stripAnnotations(test.in)
if got != test.want {
diff --git a/src/go/types/expr.go b/src/go/types/expr.go
index 5ca4edebcb..007205a9fb 100644
--- a/src/go/types/expr.go
+++ b/src/go/types/expr.go
@@ -1392,7 +1392,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case *ast.SelectorExpr:
check.selector(x, e)
- case *ast.IndexExpr, *ast.MultiIndexExpr:
+ case *ast.IndexExpr, *ast.IndexListExpr:
ix := typeparams.UnpackIndexExpr(e)
if check.indexExpr(x, ix) {
check.funcInst(x, ix)
diff --git a/src/go/types/exprstring.go b/src/go/types/exprstring.go
index aee8a5ba5f..06e7a9dcb4 100644
--- a/src/go/types/exprstring.go
+++ b/src/go/types/exprstring.go
@@ -67,7 +67,7 @@ func WriteExpr(buf *bytes.Buffer, x ast.Expr) {
buf.WriteByte('.')
buf.WriteString(x.Sel.Name)
- case *ast.IndexExpr, *ast.MultiIndexExpr:
+ case *ast.IndexExpr, *ast.IndexListExpr:
ix := typeparams.UnpackIndexExpr(x)
WriteExpr(buf, ix.X)
buf.WriteByte('[')
diff --git a/src/go/types/infer.go b/src/go/types/infer.go
index e6417545e9..18c5119177 100644
--- a/src/go/types/infer.go
+++ b/src/go/types/infer.go
@@ -8,6 +8,7 @@
package types
import (
+ "fmt"
"go/token"
"strings"
)
@@ -27,6 +28,7 @@ import (
//
// Constraint type inference is used after each step to expand the set of type arguments.
//
+// TODO(rfindley): remove the report parameter: is no longer needed.
func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand, report bool) (result []Type) {
if debug {
defer func() {
@@ -404,6 +406,34 @@ func (check *Checker) inferB(tparams []*TypeParam, targs []Type, report bool) (t
}
}
+ // The data structure of each (provided or inferred) type represents a graph, where
+ // each node corresponds to a type and each (directed) vertice points to a component
+ // type. The substitution process described above repeatedly replaces type parameter
+ // nodes in these graphs with the graphs of the types the type parameters stand for,
+ // which creates a new (possibly bigger) graph for each type.
+ // The substitution process will not stop if the replacement graph for a type parameter
+ // also contains that type parameter.
+ // For instance, for [A interface{ *A }], without any type argument provided for A,
+ // unification produces the type list [*A]. Substituting A in *A with the value for
+ // A will lead to infinite expansion by producing [**A], [****A], [********A], etc.,
+ // because the graph A -> *A has a cycle through A.
+ // Generally, cycles may occur across multiple type parameters and inferred types
+ // (for instance, consider [P interface{ *Q }, Q interface{ func(P) }]).
+ // We eliminate cycles by walking the graphs for all type parameters. If a cycle
+ // through a type parameter is detected, cycleFinder nils out the respectice type
+ // which kills the cycle; this also means that the respective type could not be
+ // inferred.
+ //
+ // TODO(gri) If useful, we could report the respective cycle as an error. We don't
+ // do this now because type inference will fail anyway, and furthermore,
+ // constraints with cycles of this kind cannot currently be satisfied by
+ // any user-suplied type. But should that change, reporting an error
+ // would be wrong.
+ w := cycleFinder{tparams, types, make(map[Type]bool)}
+ for _, t := range tparams {
+ w.typ(t) // t != nil
+ }
+
// dirty tracks the indices of all types that may still contain type parameters.
// We know that nil type entries and entries corresponding to provided (non-nil)
// type arguments are clean, so exclude them from the start.
@@ -452,3 +482,98 @@ func (check *Checker) inferB(tparams []*TypeParam, targs []Type, report bool) (t
return
}
+
+type cycleFinder struct {
+ tparams []*TypeParam
+ types []Type
+ seen map[Type]bool
+}
+
+func (w *cycleFinder) typ(typ Type) {
+ if w.seen[typ] {
+ // We have seen typ before. If it is one of the type parameters
+ // in tparams, iterative substitution will lead to infinite expansion.
+ // Nil out the corresponding type which effectively kills the cycle.
+ if tpar, _ := typ.(*TypeParam); tpar != nil {
+ if i := tparamIndex(w.tparams, tpar); i >= 0 {
+ // cycle through tpar
+ w.types[i] = nil
+ }
+ }
+ // If we don't have one of our type parameters, the cycle is due
+ // to an ordinary recursive type and we can just stop walking it.
+ return
+ }
+ w.seen[typ] = true
+ defer delete(w.seen, typ)
+
+ switch t := typ.(type) {
+ case *Basic, *top:
+ // nothing to do
+
+ case *Array:
+ w.typ(t.elem)
+
+ case *Slice:
+ w.typ(t.elem)
+
+ case *Struct:
+ w.varList(t.fields)
+
+ case *Pointer:
+ w.typ(t.base)
+
+ // case *Tuple:
+ // This case should not occur because tuples only appear
+ // in signatures where they are handled explicitly.
+
+ case *Signature:
+ // There are no "method types" so we should never see a recv.
+ assert(t.recv == nil)
+ if t.params != nil {
+ w.varList(t.params.vars)
+ }
+ if t.results != nil {
+ w.varList(t.results.vars)
+ }
+
+ case *Union:
+ for _, t := range t.terms {
+ w.typ(t.typ)
+ }
+
+ case *Interface:
+ for _, m := range t.methods {
+ w.typ(m.typ)
+ }
+ for _, t := range t.embeddeds {
+ w.typ(t)
+ }
+
+ case *Map:
+ w.typ(t.key)
+ w.typ(t.elem)
+
+ case *Chan:
+ w.typ(t.elem)
+
+ case *Named:
+ for _, tpar := range t.TypeArgs().list() {
+ w.typ(tpar)
+ }
+
+ case *TypeParam:
+ if i := tparamIndex(w.tparams, t); i >= 0 && w.types[i] != nil {
+ w.typ(w.types[i])
+ }
+
+ default:
+ panic(fmt.Sprintf("unexpected %T", typ))
+ }
+}
+
+func (w *cycleFinder) varList(list []*Var) {
+ for _, v := range list {
+ w.typ(v.typ)
+ }
+}
diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go
index 040877829c..b178d1eb3f 100644
--- a/src/go/types/instantiate.go
+++ b/src/go/types/instantiate.go
@@ -71,7 +71,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, posList
}()
}
- inst := check.instance(pos, typ, targs, check.env)
+ inst := check.instance(pos, typ, targs, check.conf.Environment)
assert(len(posList) <= len(targs))
check.later(func() {
@@ -116,9 +116,11 @@ func (check *Checker) instance(pos token.Pos, typ Type, targs []Type, env *Envir
}
}
tname := NewTypeName(pos, t.obj.pkg, t.obj.name, nil)
- named := check.newNamed(tname, t, nil, nil, nil) // methods and tparams are set when named is loaded
+ named := check.newNamed(tname, t, nil, nil, nil) // methods and tparams are set when named is resolved
named.targs = NewTypeList(targs)
- named.instPos = &pos
+ named.resolver = func(env *Environment, n *Named) (*TypeParamList, Type, []*Func) {
+ return expandNamed(env, n, pos)
+ }
if env != nil {
// It's possible that we've lost a race to add named to the environment.
// In this case, use whichever instance is recorded in the environment.
diff --git a/src/go/types/instantiate_test.go b/src/go/types/instantiate_test.go
index 0b09bfebe3..0c66acb875 100644
--- a/src/go/types/instantiate_test.go
+++ b/src/go/types/instantiate_test.go
@@ -6,6 +6,7 @@ package types_test
import (
. "go/types"
+ "strings"
"testing"
)
@@ -70,3 +71,84 @@ func TestInstantiateNonEquality(t *testing.T) {
t.Errorf("instance from pkg1 (%s) is identical to instance from pkg2 (%s)", res1, res2)
}
}
+
+func TestMethodInstantiation(t *testing.T) {
+ const prefix = genericPkg + `p
+
+type T[P any] struct{}
+
+var X T[int]
+
+`
+ tests := []struct {
+ decl string
+ want string
+ }{
+ {"func (r T[P]) m() P", "func (T[int]).m() int"},
+ {"func (r T[P]) m(P)", "func (T[int]).m(int)"},
+ {"func (r T[P]) m() func() P", "func (T[int]).m() func() int"},
+ {"func (r T[P]) m() T[P]", "func (T[int]).m() T[int]"},
+ {"func (r T[P]) m(T[P])", "func (T[int]).m(T[int])"},
+ {"func (r T[P]) m(T[P], P, string)", "func (T[int]).m(T[int], int, string)"},
+ {"func (r T[P]) m(T[P], T[string], T[int])", "func (T[int]).m(T[int], T[string], T[int])"},
+ }
+
+ for _, test := range tests {
+ src := prefix + test.decl
+ pkg, err := pkgFor(".", src, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ typ := pkg.Scope().Lookup("X").Type().(*Named)
+ obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m")
+ m, _ := obj.(*Func)
+ if m == nil {
+ t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
+ }
+ if got := ObjectString(m, RelativeTo(pkg)); got != test.want {
+ t.Errorf("instantiated %q, want %q", got, test.want)
+ }
+ }
+}
+
+func TestImmutableSignatures(t *testing.T) {
+ const src = genericPkg + `p
+
+type T[P any] struct{}
+
+func (T[P]) m() {}
+
+var _ T[int]
+`
+ pkg, err := pkgFor(".", src, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ typ := pkg.Scope().Lookup("T").Type().(*Named)
+ obj, _, _ := LookupFieldOrMethod(typ, false, pkg, "m")
+ if obj == nil {
+ t.Fatalf(`LookupFieldOrMethod(%s, "m") = %v, want func m`, typ, obj)
+ }
+
+ // Verify that the original method is not mutated by instantiating T (this
+ // bug manifested when subst did not return a new signature).
+ want := "func (T[P]).m()"
+ if got := stripAnnotations(ObjectString(obj, RelativeTo(pkg))); got != want {
+ t.Errorf("instantiated %q, want %q", got, want)
+ }
+}
+
+// Copied from errors.go.
+func stripAnnotations(s string) string {
+ var b strings.Builder
+ for _, r := range s {
+ // strip #'s and subscript digits
+ if r < '₀' || '₀'+10 <= r { // '₀' == U+2080
+ b.WriteRune(r)
+ }
+ }
+ if b.Len() < len(s) {
+ return b.String()
+ }
+ return s
+}
diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go
index f5bdd31a6f..a270159499 100644
--- a/src/go/types/lookup.go
+++ b/src/go/types/lookup.go
@@ -6,8 +6,6 @@
package types
-import "go/token"
-
// Internal use of LookupFieldOrMethod: If the obj result is a method
// associated with a concrete (non-interface) type, the method's signature
// may not be fully set up. Call Checker.objDecl(obj, nil) before accessing
@@ -124,7 +122,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
seen[named] = true
// look for a matching attached method
- named.load()
+ named.resolve(nil)
if i, m := lookupMethod(named.methods, pkg, name); m != nil {
// potential match
// caution: method may not have a proper signature yet
@@ -342,8 +340,6 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
}
// A concrete type implements T if it implements all methods of T.
- Vd, _ := deref(V)
- Vn := asNamed(Vd)
for _, m := range T.typeSet().methods {
// TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)?
obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name)
@@ -378,33 +374,13 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
panic("method with type parameters")
}
- // If V is a (instantiated) generic type, its methods are still
- // parameterized using the original (declaration) receiver type
- // parameters (subst simply copies the existing method list, it
- // does not instantiate the methods).
- // In order to compare the signatures, substitute the receiver
- // type parameters of ftyp with V's instantiation type arguments.
- // This lazily instantiates the signature of method f.
- if Vn != nil && Vn.TypeParams().Len() > 0 {
- // Be careful: The number of type arguments may not match
- // the number of receiver parameters. If so, an error was
- // reported earlier but the length discrepancy is still
- // here. Exit early in this case to prevent an assertion
- // failure in makeSubstMap.
- // TODO(gri) Can we avoid this check by fixing the lengths?
- if len(ftyp.RParams().list()) != Vn.targs.Len() {
- return
- }
- ftyp = check.subst(token.NoPos, ftyp, makeSubstMap(ftyp.RParams().list(), Vn.targs.list()), nil).(*Signature)
- }
-
// If the methods have type parameters we don't care whether they
// are the same or not, as long as they match up. Use unification
// to see if they can be made to match.
// TODO(gri) is this always correct? what about type bounds?
// (Alternative is to rename/subst type parameters and compare.)
u := newUnifier(true)
- u.x.init(ftyp.RParams().list())
+ u.x.init(ftyp.RecvTypeParams().list())
if !u.unify(ftyp, mtyp) {
return m, f
}
diff --git a/src/go/types/named.go b/src/go/types/named.go
index 51c4a236da..302e43174e 100644
--- a/src/go/types/named.go
+++ b/src/go/types/named.go
@@ -17,13 +17,13 @@ type Named struct {
orig *Named // original, uninstantiated type
fromRHS Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting)
underlying Type // possibly a *Named during setup; never a *Named once set up completely
- instPos *token.Pos // position information for lazy instantiation, or nil
tparams *TypeParamList // type parameters, or nil
targs *TypeList // type arguments (after instantiation), or nil
methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
- resolve func(*Named) ([]*TypeParam, Type, []*Func)
- once sync.Once
+ // resolver may be provided to lazily resolve type parameters, underlying, and methods.
+ resolver func(*Environment, *Named) (tparams *TypeParamList, underlying Type, methods []*Func)
+ once sync.Once // ensures that tparams, underlying, and methods are resolved before accessing
}
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
@@ -36,43 +36,22 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods)
}
-func (t *Named) load() *Named {
- // If t is an instantiated type, it derives its methods and tparams from its
- // base type. Since we expect type parameters and methods to be set after a
- // call to load, we must load the base and copy here.
- //
- // underlying is set when t is expanded.
- //
- // By convention, a type instance is loaded iff its tparams are set.
- if t.targs.Len() > 0 && t.tparams == nil {
- t.orig.load()
- t.tparams = t.orig.tparams
- t.methods = t.orig.methods
- }
- if t.resolve == nil {
+func (t *Named) resolve(env *Environment) *Named {
+ if t.resolver == nil {
return t
}
t.once.Do(func() {
- // TODO(mdempsky): Since we're passing t to resolve anyway
+ // TODO(mdempsky): Since we're passing t to the resolver anyway
// (necessary because types2 expects the receiver type for methods
// on defined interface types to be the Named rather than the
// underlying Interface), maybe it should just handle calling
// SetTypeParams, SetUnderlying, and AddMethod instead? Those
- // methods would need to support reentrant calls though. It would
+ // methods would need to support reentrant calls though. It would
// also make the API more future-proof towards further extensions
// (like SetTypeParams).
-
- tparams, underlying, methods := t.resolve(t)
-
- switch underlying.(type) {
- case nil, *Named:
- panic("invalid underlying type")
- }
-
- t.tparams = bindTParams(tparams)
- t.underlying = underlying
- t.methods = methods
+ t.tparams, t.underlying, t.methods = t.resolver(env, t)
+ t.fromRHS = t.underlying // for cycle detection
})
return t
}
@@ -112,28 +91,28 @@ func (t *Named) Obj() *TypeName {
return t.orig.obj // for non-instances this is the same as t.obj
}
-// _Orig returns the original generic type an instantiated type is derived from.
-// If t is not an instantiated type, the result is t.
-func (t *Named) _Orig() *Named { return t.orig }
+// Origin returns the parameterized type from which the named type t is
+// instantiated. If t is not an instantiated type, the result is t.
+func (t *Named) Origin() *Named { return t.orig }
// TODO(gri) Come up with a better representation and API to distinguish
// between parameterized instantiated and non-instantiated types.
// TypeParams returns the type parameters of the named type t, or nil.
// The result is non-nil for an (originally) parameterized type even if it is instantiated.
-func (t *Named) TypeParams() *TypeParamList { return t.load().tparams }
+func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams }
// SetTypeParams sets the type parameters of the named type t.
-func (t *Named) SetTypeParams(tparams []*TypeParam) { t.load().tparams = bindTParams(tparams) }
+func (t *Named) SetTypeParams(tparams []*TypeParam) { t.resolve(nil).tparams = bindTParams(tparams) }
// TypeArgs returns the type arguments used to instantiate the named type t.
func (t *Named) TypeArgs() *TypeList { return t.targs }
// NumMethods returns the number of explicit methods whose receiver is named type t.
-func (t *Named) NumMethods() int { return len(t.load().methods) }
+func (t *Named) NumMethods() int { return len(t.resolve(nil).methods) }
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
-func (t *Named) Method(i int) *Func { return t.load().methods[i] }
+func (t *Named) Method(i int) *Func { return t.resolve(nil).methods[i] }
// SetUnderlying sets the underlying type and marks t as complete.
func (t *Named) SetUnderlying(underlying Type) {
@@ -143,18 +122,18 @@ func (t *Named) SetUnderlying(underlying Type) {
if _, ok := underlying.(*Named); ok {
panic("underlying type must not be *Named")
}
- t.load().underlying = underlying
+ t.resolve(nil).underlying = underlying
}
// AddMethod adds method m unless it is already in the method list.
func (t *Named) AddMethod(m *Func) {
- t.load()
+ t.resolve(nil)
if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 {
t.methods = append(t.methods, m)
}
}
-func (t *Named) Underlying() Type { return t.load().expand(nil).underlying }
+func (t *Named) Underlying() Type { return t.resolve(nil).underlying }
func (t *Named) String() string { return TypeString(t, nil) }
// ----------------------------------------------------------------------------
@@ -240,43 +219,105 @@ func (n *Named) setUnderlying(typ Type) {
}
}
-// expand ensures that the underlying type of n is instantiated.
+// bestEnv returns the best available environment. In order of preference:
+// - the given env, if non-nil
+// - the Checker env, if check is non-nil
+// - a new environment
+func (check *Checker) bestEnv(env *Environment) *Environment {
+ if env != nil {
+ return env
+ }
+ if check != nil {
+ assert(check.conf.Environment != nil)
+ return check.conf.Environment
+ }
+ return NewEnvironment()
+}
+
+// expandNamed ensures that the underlying type of n is instantiated.
// The underlying type will be Typ[Invalid] if there was an error.
-func (n *Named) expand(env *Environment) *Named {
- if n.instPos != nil {
- // n must be loaded before instantiation, in order to have accurate
- // tparams. This is done implicitly by the call to n.TypeParams, but making
- // it explicit is harmless: load is idempotent.
- n.load()
- var u Type
- if n.check.validateTArgLen(*n.instPos, n.tparams.Len(), n.targs.Len()) {
- // TODO(rfindley): handling an optional Checker and Environment here (and
- // in subst) feels overly complicated. Can we simplify?
- if env == nil {
- if n.check != nil {
- env = n.check.env
- } else {
- // If we're instantiating lazily, we might be outside the scope of a
- // type-checking pass. In that case we won't have a pre-existing
- // environment, but don't want to create a duplicate of the current
- // instance in the process of expansion.
- env = NewEnvironment()
- }
- h := env.typeHash(n.orig, n.targs.list())
- // add the instance to the environment to avoid infinite recursion.
- // addInstance may return a different, existing instance, but we
- // shouldn't return that instance from expand.
- env.typeForHash(h, n)
+func expandNamed(env *Environment, n *Named, instPos token.Pos) (tparams *TypeParamList, underlying Type, methods []*Func) {
+ n.orig.resolve(env)
+
+ check := n.check
+
+ if check.validateTArgLen(instPos, n.orig.tparams.Len(), n.targs.Len()) {
+ // We must always have an env, to avoid infinite recursion.
+ env = check.bestEnv(env)
+ h := env.typeHash(n.orig, n.targs.list())
+ // ensure that an instance is recorded for h to avoid infinite recursion.
+ env.typeForHash(h, n)
+
+ smap := makeSubstMap(n.orig.tparams.list(), n.targs.list())
+ underlying = n.check.subst(instPos, n.orig.underlying, smap, env)
+
+ for i := 0; i < n.orig.NumMethods(); i++ {
+ origm := n.orig.Method(i)
+
+ // During type checking origm may not have a fully set up type, so defer
+ // instantiation of its signature until later.
+ m := NewFunc(origm.pos, origm.pkg, origm.name, nil)
+ m.hasPtrRecv = origm.hasPtrRecv
+ // Setting instRecv here allows us to complete later (we need the
+ // instRecv to get targs and the original method).
+ m.instRecv = n
+
+ methods = append(methods, m)
+ }
+ } else {
+ underlying = Typ[Invalid]
+ }
+
+ // Methods should not escape the type checker API without being completed. If
+ // we're in the context of a type checking pass, we need to defer this until
+ // later (not all methods may have types).
+ completeMethods := func() {
+ for _, m := range methods {
+ if m.instRecv != nil {
+ check.completeMethod(env, m)
}
- u = n.check.subst(*n.instPos, n.orig.underlying, makeSubstMap(n.TypeParams().list(), n.targs.list()), env)
- } else {
- u = Typ[Invalid]
}
- n.underlying = u
- n.fromRHS = u
- n.instPos = nil
}
- return n
+ if check != nil {
+ check.later(completeMethods)
+ } else {
+ completeMethods()
+ }
+
+ return n.orig.tparams, underlying, methods
+}
+
+func (check *Checker) completeMethod(env *Environment, m *Func) {
+ assert(m.instRecv != nil)
+ rtyp := m.instRecv
+ m.instRecv = nil
+ m.setColor(black)
+
+ assert(rtyp.TypeArgs().Len() > 0)
+
+ // Look up the original method.
+ _, orig := lookupMethod(rtyp.orig.methods, rtyp.obj.pkg, m.name)
+ assert(orig != nil)
+ if check != nil {
+ check.objDecl(orig, nil)
+ }
+ origSig := orig.typ.(*Signature)
+ if origSig.RecvTypeParams().Len() != rtyp.targs.Len() {
+ m.typ = origSig // or new(Signature), but we can't use Typ[Invalid]: Funcs must have Signature type
+ return // error reported elsewhere
+ }
+
+ smap := makeSubstMap(origSig.RecvTypeParams().list(), rtyp.targs.list())
+ sig := check.subst(orig.pos, origSig, smap, env).(*Signature)
+ if sig == origSig {
+ // No substitution occurred, but we still need to create a copy to hold the
+ // instantiated receiver.
+ copy := *origSig
+ sig = &copy
+ }
+ sig.recv = NewParam(origSig.recv.pos, origSig.recv.pkg, origSig.recv.name, rtyp)
+
+ m.typ = sig
}
// safeUnderlying returns the underlying of typ without expanding instances, to
@@ -285,7 +326,7 @@ func (n *Named) expand(env *Environment) *Named {
// TODO(rfindley): eliminate this function or give it a better name.
func safeUnderlying(typ Type) Type {
if t, _ := typ.(*Named); t != nil {
- return t.load().underlying
+ return t.resolve(nil).underlying
}
return typ.Underlying()
}
diff --git a/src/go/types/object.go b/src/go/types/object.go
index b25fffdf5c..454b714458 100644
--- a/src/go/types/object.go
+++ b/src/go/types/object.go
@@ -232,9 +232,21 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
// _NewTypeNameLazy returns a new defined type like NewTypeName, but it
// lazily calls resolve to finish constructing the Named object.
-func _NewTypeNameLazy(pos token.Pos, pkg *Package, name string, resolve func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName {
+func _NewTypeNameLazy(pos token.Pos, pkg *Package, name string, load func(named *Named) (tparams []*TypeParam, underlying Type, methods []*Func)) *TypeName {
obj := NewTypeName(pos, pkg, name, nil)
- NewNamed(obj, nil, nil).resolve = resolve
+
+ resolve := func(_ *Environment, t *Named) (*TypeParamList, Type, []*Func) {
+ tparams, underlying, methods := load(t)
+
+ switch underlying.(type) {
+ case nil, *Named:
+ panic(fmt.Sprintf("invalid underlying type %T", t.underlying))
+ }
+
+ return bindTParams(tparams), underlying, methods
+ }
+
+ NewNamed(obj, nil, nil).resolver = resolve
return obj
}
@@ -305,7 +317,8 @@ func (*Var) isDependency() {} // a variable may be a dependency of an initializa
// An abstract method may belong to many interfaces due to embedding.
type Func struct {
object
- hasPtrRecv bool // only valid for methods that don't have a type yet
+ instRecv *Named // if non-nil, the receiver type for an incomplete instance method
+ hasPtrRecv bool // only valid for methods that don't have a type yet
}
// NewFunc returns a new function with the given signature, representing
@@ -316,7 +329,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
if sig != nil {
typ = sig
}
- return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, false}
+ return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), token.NoPos}, nil, false}
}
// FullName returns the package- or receiver-type-qualified name of
diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go
index 73d240241e..a5d4be9bcc 100644
--- a/src/go/types/predicates.go
+++ b/src/go/types/predicates.go
@@ -6,6 +6,8 @@
package types
+import "go/token"
+
// isNamed reports whether typ has a name.
// isNamed may be called with types that are not fully set up.
func isNamed(typ Type) bool {
@@ -225,6 +227,13 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
identical(x.results, y.results, cmpTags, p)
}
+ case *Union:
+ if y, _ := y.(*Union); y != nil {
+ xset := computeUnionTypeSet(nil, token.NoPos, x)
+ yset := computeUnionTypeSet(nil, token.NoPos, y)
+ return xset.terms.equal(yset.terms)
+ }
+
case *Interface:
// Two interface types are identical if they describe the same type sets.
// With the existing implementation restriction, this simplifies to:
@@ -302,9 +311,6 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
// Two named types are identical if their type names originate
// in the same type declaration.
if y, ok := y.(*Named); ok {
- x.expand(nil)
- y.expand(nil)
-
xargs := x.TypeArgs().list()
yargs := y.TypeArgs().list()
diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go
index b04a673ab7..486c09220b 100644
--- a/src/go/types/resolver.go
+++ b/src/go/types/resolver.go
@@ -513,7 +513,7 @@ L: // unpack receiver type
// unpack type parameters, if any
switch rtyp.(type) {
- case *ast.IndexExpr, *ast.MultiIndexExpr:
+ case *ast.IndexExpr, *ast.IndexListExpr:
ix := typeparams.UnpackIndexExpr(rtyp)
rtyp = ix.X
if unpackParams {
diff --git a/src/go/types/signature.go b/src/go/types/signature.go
index 0561947901..bf6c775b89 100644
--- a/src/go/types/signature.go
+++ b/src/go/types/signature.go
@@ -61,11 +61,11 @@ func (s *Signature) TypeParams() *TypeParamList { return s.tparams }
// SetTypeParams sets the type parameters of signature s.
func (s *Signature) SetTypeParams(tparams []*TypeParam) { s.tparams = bindTParams(tparams) }
-// RParams returns the receiver type parameters of signature s, or nil.
-func (s *Signature) RParams() *TypeParamList { return s.rparams }
+// RecvTypeParams returns the receiver type parameters of signature s, or nil.
+func (s *Signature) RecvTypeParams() *TypeParamList { return s.rparams }
-// SetRParams sets the receiver type params of signature s.
-func (s *Signature) SetRParams(rparams []*TypeParam) { s.rparams = bindTParams(rparams) }
+// SetRecvTypeParams sets the receiver type params of signature s.
+func (s *Signature) SetRecvTypeParams(rparams []*TypeParam) { s.rparams = bindTParams(rparams) }
// Params returns the parameters of signature s, or nil.
func (s *Signature) Params() *Tuple { return s.params }
@@ -133,14 +133,14 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
}
// provide type parameter bounds
// - only do this if we have the right number (otherwise an error is reported elsewhere)
- if sig.RParams().Len() == len(recvTParams) {
+ if sig.RecvTypeParams().Len() == len(recvTParams) {
// We have a list of *TypeNames but we need a list of Types.
- list := make([]Type, sig.RParams().Len())
- for i, t := range sig.RParams().list() {
+ list := make([]Type, sig.RecvTypeParams().Len())
+ for i, t := range sig.RecvTypeParams().list() {
list[i] = t
}
smap := makeSubstMap(recvTParams, list)
- for i, tpar := range sig.RParams().list() {
+ for i, tpar := range sig.RecvTypeParams().list() {
bound := recvTParams[i].bound
// bound is (possibly) parameterized in the context of the
// receiver type declaration. Substitute parameters for the
@@ -152,7 +152,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
}
if ftyp.TypeParams != nil {
- sig.tparams = check.collectTypeParams(ftyp.TypeParams)
+ check.collectTypeParams(&sig.tparams, ftyp.TypeParams)
// Always type-check method type parameters but complain that they are not allowed.
// (A separate check is needed when type-checking interface method signatures because
// they don't have a receiver specification.)
@@ -200,10 +200,10 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
var err string
switch T := rtyp.(type) {
case *Named:
- T.expand(nil)
+ T.resolve(check.conf.Environment)
// The receiver type may be an instantiated type referred to
// by an alias (which cannot have receiver parameters for now).
- if T.TypeArgs() != nil && sig.RParams() == nil {
+ if T.TypeArgs() != nil && sig.RecvTypeParams() == nil {
check.errorf(atPos(recv.pos), _Todo, "cannot define methods on instantiated type %s", recv.typ)
break
}
@@ -326,7 +326,7 @@ func isubst(x ast.Expr, smap map[*ast.Ident]*ast.Ident) ast.Expr {
new.X = X
return &new
}
- case *ast.IndexExpr, *ast.MultiIndexExpr:
+ case *ast.IndexExpr, *ast.IndexListExpr:
ix := typeparams.UnpackIndexExpr(x)
var newIndexes []ast.Expr
for i, index := range ix.Indices {
diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go
index f64f732884..0e3c0064a0 100644
--- a/src/go/types/sizeof_test.go
+++ b/src/go/types/sizeof_test.go
@@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) {
{Interface{}, 44, 88},
{Map{}, 16, 32},
{Chan{}, 12, 24},
- {Named{}, 72, 136},
+ {Named{}, 68, 128},
{TypeParam{}, 28, 48},
{term{}, 12, 24},
{top{}, 0, 0},
@@ -40,7 +40,7 @@ func TestSizeof(t *testing.T) {
{Const{}, 48, 88},
{TypeName{}, 40, 72},
{Var{}, 44, 80},
- {Func{}, 44, 80},
+ {Func{}, 48, 88},
{Label{}, 44, 80},
{Builtin{}, 44, 80},
{Nil{}, 40, 72},
diff --git a/src/go/types/struct.go b/src/go/types/struct.go
index f6e6f2a5e6..24a2435ff7 100644
--- a/src/go/types/struct.go
+++ b/src/go/types/struct.go
@@ -176,7 +176,7 @@ func embeddedFieldIdent(e ast.Expr) *ast.Ident {
return e.Sel
case *ast.IndexExpr:
return embeddedFieldIdent(e.X)
- case *ast.MultiIndexExpr:
+ case *ast.IndexListExpr:
return embeddedFieldIdent(e.X)
}
return nil // invalid embedded field
diff --git a/src/go/types/subst.go b/src/go/types/subst.go
index 4f9d76d598..16aafd622e 100644
--- a/src/go/types/subst.go
+++ b/src/go/types/subst.go
@@ -8,9 +8,6 @@ package types
import "go/token"
-// TODO(rFindley) decide error codes for the errors in this file, and check
-// if error spans can be improved
-
type substMap map[*TypeParam]Type
// makeSubstMap creates a new substitution map mapping tpars[i] to targs[i].
@@ -55,25 +52,12 @@ func (check *Checker) subst(pos token.Pos, typ Type, smap substMap, env *Environ
}
// general case
- var subst subster
- subst.pos = pos
- subst.smap = smap
-
- if check != nil {
- subst.check = check
- if env == nil {
- env = check.env
- }
+ subst := subster{
+ pos: pos,
+ smap: smap,
+ check: check,
+ env: check.bestEnv(env),
}
- if env == nil {
- // If we don't have a *Checker and its global type map,
- // use a local version. Besides avoiding duplicate work,
- // the type map prevents infinite recursive substitution
- // for recursive types (example: type T[P any] *T[P]).
- env = NewEnvironment()
- }
- subst.env = env
-
return subst.typ(typ)
}
@@ -128,8 +112,7 @@ func (subst *subster) typ(typ Type) Type {
if recv != t.recv || params != t.params || results != t.results {
return &Signature{
rparams: t.rparams,
- // TODO(rFindley) why can't we nil out tparams here, rather than in
- // instantiate above?
+ // TODO(rFindley) why can't we nil out tparams here, rather than in instantiate?
tparams: t.tparams,
scope: t.scope,
recv: recv,
@@ -182,13 +165,19 @@ func (subst *subster) typ(typ Type) Type {
}
}
- if t.TypeParams().Len() == 0 {
+ // subst is called by expandNamed, so in this function we need to be
+ // careful not to call any methods that would cause t to be expanded: doing
+ // so would result in deadlock.
+ //
+ // So we call t.orig.TypeParams() rather than t.TypeParams() here and
+ // below.
+ if t.orig.TypeParams().Len() == 0 {
dump(">>> %s is not parameterized", t)
return t // type is not parameterized
}
var newTArgs []Type
- assert(t.targs.Len() == t.TypeParams().Len())
+ assert(t.targs.Len() == t.orig.TypeParams().Len())
// already instantiated
dump(">>> %s already instantiated", t)
@@ -201,7 +190,7 @@ func (subst *subster) typ(typ Type) Type {
if new_targ != targ {
dump(">>> substituted %d targ %s => %s", i, targ, new_targ)
if newTArgs == nil {
- newTArgs = make([]Type, t.TypeParams().Len())
+ newTArgs = make([]Type, t.orig.TypeParams().Len())
copy(newTArgs, t.targs.list())
}
newTArgs[i] = new_targ
@@ -221,27 +210,19 @@ func (subst *subster) typ(typ Type) Type {
return named
}
- // Create a new named type and populate the environment to avoid endless
+ t.orig.resolve(subst.env)
+ // Create a new instance and populate the environment to avoid endless
// recursion. The position used here is irrelevant because validation only
// occurs on t (we don't call validType on named), but we use subst.pos to
// help with debugging.
- tname := NewTypeName(subst.pos, t.obj.pkg, t.obj.name, nil)
- t.load()
- // It's ok to provide a nil *Checker because the newly created type
- // doesn't need to be (lazily) expanded; it's expanded below.
- named := (*Checker)(nil).newNamed(tname, t.orig, nil, t.tparams, t.methods) // t is loaded, so tparams and methods are available
- named.targs = NewTypeList(newTArgs)
- subst.env.typeForHash(h, named)
- t.expand(subst.env) // must happen after env update to avoid infinite recursion
-
- // do the substitution
- dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, newTArgs)
- named.underlying = subst.typOrNil(t.underlying)
- dump(">>> underlying: %v", named.underlying)
- assert(named.underlying != nil)
- named.fromRHS = named.underlying // for consistency, though no cycle detection is necessary
-
- return named
+ t.orig.resolve(subst.env)
+ return subst.check.instance(subst.pos, t.orig, newTArgs, subst.env)
+
+ // Note that if we were to expose substitution more generally (not just in
+ // the context of a declaration), we'd have to substitute in
+ // named.underlying as well.
+ //
+ // But this is unnecessary for now.
case *TypeParam:
return subst.smap.lookup(t)
diff --git a/src/go/types/testdata/fixedbugs/issue43527.go2 b/src/go/types/testdata/fixedbugs/issue43527.go2
new file mode 100644
index 0000000000..e4bcee51fe
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue43527.go2
@@ -0,0 +1,16 @@
+// Copyright 2021 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
+
+const L = 10
+
+type (
+ _ [L]struct{}
+ _ [A /* ERROR undeclared name A for array length */ ]struct{}
+ _ [B /* ERROR not an expression */ ]struct{}
+ _[A any] struct{}
+
+ B int
+)
diff --git a/src/go/types/testdata/fixedbugs/issue45550.go2 b/src/go/types/testdata/fixedbugs/issue45550.go2
new file mode 100644
index 0000000000..c3e9e34b87
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue45550.go2
@@ -0,0 +1,10 @@
+// Copyright 2021 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 Builder[T interface{ struct{ Builder[T] } }] struct{}
+type myBuilder struct {
+ Builder[myBuilder /* ERROR myBuilder does not satisfy */]
+}
diff --git a/src/go/types/testdata/fixedbugs/issue47796.go2 b/src/go/types/testdata/fixedbugs/issue47796.go2
new file mode 100644
index 0000000000..9c10683e22
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue47796.go2
@@ -0,0 +1,33 @@
+// Copyright 2021 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
+
+// parameterized types with self-recursive constraints
+type (
+ T1[P T1[P]] interface{}
+ T2[P, Q T2[P, Q]] interface{}
+ T3[P T2[P, Q], Q interface{ ~string }] interface{}
+
+ T4a[P T4a[P]] interface{ ~int }
+ T4b[P T4b[int]] interface{ ~int }
+ T4c[P T4c[string /* ERROR string does not satisfy T4c\[string\] */]] interface{ ~int }
+
+ // mutually recursive constraints
+ T5[P T6[P]] interface{ int }
+ T6[P T5[P]] interface{ int }
+)
+
+// verify that constraints are checked as expected
+var (
+ _ T1[int]
+ _ T2[int, string]
+ _ T3[int, string]
+)
+
+// test case from issue
+
+type Eq[a Eq[a]] interface {
+ Equal(that a) bool
+}
diff --git a/src/go/types/testdata/fixedbugs/issue47887.go2 b/src/go/types/testdata/fixedbugs/issue47887.go2
new file mode 100644
index 0000000000..4c4fc2fda8
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue47887.go2
@@ -0,0 +1,28 @@
+// Copyright 2021 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 Fooer[t any] interface {
+ foo(Barer[t])
+}
+type Barer[t any] interface {
+ bar(Bazer[t])
+}
+type Bazer[t any] interface {
+ Fooer[t]
+ baz(t)
+}
+
+type Int int
+
+func (n Int) baz(int) {}
+func (n Int) foo(b Barer[int]) { b.bar(n) }
+
+type F[t any] interface { f(G[t]) }
+type G[t any] interface { g(H[t]) }
+type H[t any] interface { F[t] }
+
+type T struct{}
+func (n T) f(b G[T]) { b.g(n) }
diff --git a/src/go/types/testdata/fixedbugs/issue48136.go2 b/src/go/types/testdata/fixedbugs/issue48136.go2
new file mode 100644
index 0000000000..b87f84ae64
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue48136.go2
@@ -0,0 +1,36 @@
+// Copyright 2021 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
+
+func f1[P interface{ *P }]() {}
+func f2[P interface{ func(P) }]() {}
+func f3[P, Q interface{ func(Q) P }]() {}
+func f4[P interface{ *Q }, Q interface{ func(P) }]() {}
+func f5[P interface{ func(P) }]() {}
+func f6[P interface { *Tree[P] }, Q any ]() {}
+
+func _() {
+ f1 /* ERROR cannot infer P */ ()
+ f2 /* ERROR cannot infer P */ ()
+ f3 /* ERROR cannot infer P */ ()
+ f4 /* ERROR cannot infer P */ ()
+ f5 /* ERROR cannot infer P */ ()
+ f6 /* ERROR cannot infer P */ ()
+}
+
+type Tree[P any] struct {
+ left, right *Tree[P]
+ data P
+}
+
+// test case from issue
+
+func foo[Src interface { func() Src }]() Src {
+ return foo[Src]
+}
+
+func _() {
+ foo /* ERROR cannot infer Src */ ()
+}
diff --git a/src/go/types/type.go b/src/go/types/type.go
index b9634cf6f6..31149cfd36 100644
--- a/src/go/types/type.go
+++ b/src/go/types/type.go
@@ -114,7 +114,7 @@ func asInterface(t Type) *Interface {
func asNamed(t Type) *Named {
e, _ := t.(*Named)
if e != nil {
- e.expand(nil)
+ e.resolve(nil)
}
return e
}
diff --git a/src/go/types/typeparam.go b/src/go/types/typeparam.go
index a0f2a3acd0..150ad079a8 100644
--- a/src/go/types/typeparam.go
+++ b/src/go/types/typeparam.go
@@ -32,7 +32,7 @@ type TypeParam struct {
// or Signature type by calling SetTypeParams. Setting a type parameter on more
// than one type will result in a panic.
//
-// The bound argument can be nil, and set later via SetConstraint.
+// The constraint argument can be nil, and set later via SetConstraint.
func NewTypeParam(obj *TypeName, constraint Type) *TypeParam {
return (*Checker)(nil).newTypeParam(obj, constraint)
}
diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go
index 7e971c0325..eadc50a754 100644
--- a/src/go/types/typestring.go
+++ b/src/go/types/typestring.go
@@ -65,9 +65,6 @@ func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) {
newTypeWriter(buf, qf).signature(sig)
}
-// instanceMarker is the prefix for an instantiated type in unexpanded form.
-const instanceMarker = '#'
-
type typeWriter struct {
buf *bytes.Buffer
seen map[Type]bool
@@ -226,13 +223,6 @@ func (w *typeWriter) typ(typ Type) {
}
case *Named:
- // Instance markers indicate unexpanded instantiated
- // types. Write them to aid debugging, but don't write
- // them when we need an instance hash: whether a type
- // is fully expanded or not doesn't matter for identity.
- if w.env == nil && t.instPos != nil {
- w.byte(instanceMarker)
- }
w.typePrefix(t)
w.typeName(t.obj)
if t.targs != nil {
diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go
index af56297144..0143f53009 100644
--- a/src/go/types/typexpr.go
+++ b/src/go/types/typexpr.go
@@ -258,7 +258,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) {
check.errorf(&x, _NotAType, "%s is not a type", &x)
}
- case *ast.IndexExpr, *ast.MultiIndexExpr:
+ case *ast.IndexExpr, *ast.IndexListExpr:
ix := typeparams.UnpackIndexExpr(e)
if !check.allowVersion(check.pkg, 1, 18) {
check.softErrorf(inNode(e, ix.Lbrack), _Todo, "type instantiation requires go1.18 or later")
@@ -412,6 +412,14 @@ func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.Expr, def *Named
// and returns the constant length >= 0, or a value < 0
// to indicate an error (and thus an unknown length).
func (check *Checker) arrayLength(e ast.Expr) int64 {
+ // If e is an undeclared identifier, the array declaration might be an
+ // attempt at a parameterized type declaration with missing constraint.
+ // Provide a better error message than just "undeclared name: X".
+ if name, _ := e.(*ast.Ident); name != nil && check.lookup(name.Name) == nil {
+ check.errorf(name, _InvalidArrayLen, "undeclared name %s for array length", name.Name)
+ return -1
+ }
+
var x operand
check.expr(&x, e)
if x.mode != constant_ {
@@ -420,6 +428,7 @@ func (check *Checker) arrayLength(e ast.Expr) int64 {
}
return -1
}
+
if isUntyped(x.typ) || isInteger(x.typ) {
if val := constant.ToInt(x.val); val.Kind() == constant.Int {
if representableConst(val, check, Typ[Int], nil) {
@@ -431,6 +440,7 @@ func (check *Checker) arrayLength(e ast.Expr) int64 {
}
}
}
+
check.errorf(&x, _InvalidArrayLen, "array length %s must be integer", &x)
return -1
}
diff --git a/src/go/types/unify.go b/src/go/types/unify.go
index ed769aafe8..6d10f71a90 100644
--- a/src/go/types/unify.go
+++ b/src/go/types/unify.go
@@ -425,9 +425,6 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
case *Named:
if y, ok := y.(*Named); ok {
- x.expand(nil)
- y.expand(nil)
-
xargs := x.targs.list()
yargs := y.targs.list()