From 2aeeba683624592ddde2c5e8f3677e25b16f6310 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 11 Dec 2019 16:04:34 -0800 Subject: [PATCH] go/types: set receiver type bounds for methods Enable a few more tests that now work correctly. Change-Id: I7efe91660c2896d4d8279b86831aa7de2ae7c0ad --- src/go/types/decl.go | 38 ++++++++++++++++++++++++----- src/go/types/examples/contracts.go2 | 8 +++--- src/go/types/resolver.go | 2 +- src/go/types/subst.go | 25 +++++++++++++++++-- src/go/types/typexpr.go | 2 +- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 349e8ef0d8..54854f82d5 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -730,15 +730,41 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) { fdecl := decl.fdecl if fdecl.IsMethod() { - _, _, tparams := check.unpackRecv(fdecl.Recv.List[0].Type, true) + _, rname, tparams := check.unpackRecv(fdecl.Recv.List[0].Type, true) if len(tparams) > 0 { - // TODO(gri) need to provide contract - // (check that number of parameters match is done when type-checking the receiver expression) + // declare the method's receiver type parameters check.openScope(fdecl, "receiver type parameters") defer check.closeScope() - // TODO(gri) can we use (an adjusted version of) collectTypeParams here? - for i, name := range tparams { - obj.tparams = append(obj.tparams, check.declareTypeParam(name, i, nil)) + // collect and declare the type parameters + var list []Type // list of corresponding *TypeParams + for index, name := range tparams { + tpar := check.declareTypeParam(name, index, nil) + obj.tparams = append(obj.tparams, tpar) + list = append(list, tpar.typ) + } + // determine receiver type to get its type parameters + // and the respective type parameter bounds + var recvTParams []*TypeName + if rname != nil { + // recv should be a Named type (otherwise an error is reported elsewhere) + if recv, _ := check.genericType(rname).(*Named); recv != nil { + recvTParams = recv.obj.tparams + } + } + // provide type parameter bounds + // - only do this if we have the right number (otherwise an error is reported elsewhere) + if len(list) == len(recvTParams) { + assert(len(list) == len(obj.tparams)) + for index, tname := range obj.tparams { + bound := recvTParams[index].typ.(*TypeParam).bound + // bound is (possibly) parameterized in the context of the + // receiver type declaration. Substitute parameters for the + // current context. + if bound != nil { + bound = check.subst(tname.pos, bound, recvTParams, list) + tname.typ.(*TypeParam).bound = bound + } + } } } } else if fdecl.TParams != nil { diff --git a/src/go/types/examples/contracts.go2 b/src/go/types/examples/contracts.go2 index ef3b6b2c01..d48d441e40 100644 --- a/src/go/types/examples/contracts.go2 +++ b/src/go/types/examples/contracts.go2 @@ -43,17 +43,17 @@ contract G(Node, Edge) { type Graph (type Node, Edge G) struct { /* ... */ } -func New (type Node, Edge G) (nodes []Node) *Graph(Node, Edge) { panic("unimplemented") } +func New (type Node, Edge G) (nodes []Node) *Graph(Node, Edge) -// func (g *Graph(N, E)) ShortestPath(from, to N) []E { panic("unimplemented") } +func (g *Graph(N, E)) ShortestPath(from, to N) []E // Same Graph using interface bounds instead of a contract. type AltGraph (type Node NodeFace(Edge), Edge EdgeFace(Node)) struct { } -func AltNew (type Node NodeFace(Edge), Edge EdgeFace(Node)) (nodes []Node) *AltGraph(Node, Edge) { panic("unimplemented") } +func AltNew (type Node NodeFace(Edge), Edge EdgeFace(Node)) (nodes []Node) *AltGraph(Node, Edge) -// func (g *AltGraph(N, E)) ShortestPath(from, to N) []E { panic("unimplemented") } +func (g *AltGraph(N, E)) ShortestPath(from, to N) []E type NodeFace(type Edge) interface { Edges() []Edge diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index 1189f72276..6aec2e571b 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -501,7 +501,7 @@ func (check *Checker) collectObjects() { // rtyp is a pointer receiver, rname is the receiver type name, and tparams are its // type parameters, if any. The type parameters are only unpacked if unpackParams is // set. If rname is nil, the receiver is unusable (i.e., the source has a bug which we -// cannot easily work aound with). +// cannot easily work around). func (check *Checker) unpackRecv(rtyp ast.Expr, unpackParams bool) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) { L: // unpack receiver type for { diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 1258c32a2b..40da37154a 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -76,6 +76,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist // determine type parameter bound iface := tpar.Interface() + // TODO(gri) document/explain why the substitution below is correct //check.dump(">>> %s: iface before: %s", pos, iface) bound := check.subst(pos, tpar.bound, tparams, targs).Underlying() switch b := bound.(type) { @@ -102,6 +103,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist if targ, _ := targ.Underlying().(*TypeParam); targ != nil { for _, t := range targ.Interface().types { if !includesType(t, iface) { + // TODO(gri) match this error message with the one below (or vice versa) check.softErrorf(pos, "%s does not satisfy %s (missing type %s)", targ, tpar.bound, t) break } @@ -123,6 +125,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist } // includesType reports whether iface includes typ +// TODO(gri) make this a method of *Interface func includesType(typ Type, iface *Interface) bool { for _, t := range iface.types { if Identical(typ.Underlying(), t) { @@ -139,13 +142,28 @@ func (check *Checker) subst(pos token.Pos, typ Type, tpars []*TypeName, targs [] if len(tpars) == 0 { return typ } - subst := subster{pos, check, make(map[Type]Type), tpars, targs} + + // common cases + switch t := typ.(type) { + case *Basic: + return typ // nothing to do + case *TypeParam: + for i, tpar := range tpars { + if tpar.typ == t { + return targs[i] + } + } + return typ + } + + // general case + subst := subster{check, pos, make(map[Type]Type), tpars, targs} return subst.typ(typ) } type subster struct { - pos token.Pos check *Checker + pos token.Pos cache map[Type]Type tpars []*TypeName targs []Type @@ -303,6 +321,9 @@ func (subst *subster) typ(typ Type) Type { } } + case *Contract: + panic("unimplemented") + default: panic("unimplemented") } diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 4909d77041..2085abdbb9 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -137,7 +137,7 @@ func (check *Checker) definedType(e ast.Expr, def *Named) Type { return typ } -// generic us like typ bit the type must be an (uninstantiated) generic type. +// generic is like typ but the type must be an (uninstantiated) generic type. func (check *Checker) genericType(e ast.Expr) Type { typ := check.typInternal(e, nil) assert(isTyped(typ))