diff options
Diffstat (limited to 'src/go/types')
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 = © - } - // 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 = © + } + 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() |