From c47bad9a287f4bacc50dc08b8c7062f2e4623b28 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 19 Nov 2019 17:10:04 -0800 Subject: [PATCH] go/types: interfaces as type bounds starting to work With various loose ends. Change-Id: Idbcb4affc585f5bb14caa2045943a64a78d05d09 --- src/go/types/builtins.go | 2 +- src/go/types/check_test.go | 2 +- src/go/types/contracts.go | 4 ++- src/go/types/decl.go | 46 ++++++++++++++++++++++------- src/go/types/examples/contracts.go2 | 6 ++-- src/go/types/subst.go | 40 ++++++++++++++++++++++++- src/go/types/testdata/contracts.go2 | 2 +- src/go/types/type.go | 18 +++++++---- src/go/types/typestring.go | 2 +- src/go/types/typexpr.go | 17 ++++++++--- 10 files changed, 110 insertions(+), 29 deletions(-) diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index a81bdc70b4..39813edf5c 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -684,7 +684,7 @@ func applyTypeFunc(f func(Type) Type, x Type) Type { TParams: []*TypeName{tpar}, IFaces: map[*TypeName]*Interface{tpar: iface}, } - resTyp.contr = contr + resTyp.bound = contr return resTyp } diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index ec28ae0e40..857b60ab8b 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -113,7 +113,7 @@ var tests = [][]string{ {"testdata/map2.go2"}, // Go 2 prototype examples - // {"examples/contracts.go2"}, // TODO(gri) enable + {"examples/contracts.go2"}, {"examples/functions.go2"}, {"examples/types.go2"}, } diff --git a/src/go/types/contracts.go b/src/go/types/contracts.go index e2bee0e106..54cdc61b65 100644 --- a/src/go/types/contracts.go +++ b/src/go/types/contracts.go @@ -247,7 +247,9 @@ func (check *Checker) satisfyContract(contr *Contract, targs []Type) bool { // with the respective type arguments. // TODO(gri) fix this if IsParameterized(iface) { - panic("unimplemented") + // check.dump("BEFORE iface(%s) => %s (%s)", targ, iface, fmt.Sprintf("%p", iface)) + iface = check.subst(iface, contr.TParams, targs).(*Interface) + // check.dump("AFTER iface(%s) => %s (%s)", targ, iface, fmt.Sprintf("%p", iface)) } // use interface type of type parameter, if any // targ must implement iface diff --git a/src/go/types/decl.go b/src/go/types/decl.go index b27a88eeea..30ad852e79 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -599,34 +599,58 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { } func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeName) { + // Declare type parameters up-front. + // If we use interfaces as type bounds, the scope of type parameters starts at + // the beginning of the type parameter list (so we can have mutually recursive + // parameterized interfaces). If we use contracts, it doesn't matter that the + // type parameters are all declared early (it's not observable). + index := 0 for _, f := range list.List { - var contr *Contract + for _, name := range f.Names { + tparams = append(tparams, check.declareTypeParam(name, index, nil)) + index++ + } + } + + index = 0 + for _, f := range list.List { + var bound Type if f.Type != nil { typ := check.typ(f.Type) if typ != Typ[Invalid] { - if contr, _ = typ.Underlying().(*Contract); contr != nil { - if len(f.Names) != len(contr.TParams) { + switch b := typ.Underlying().(type) { + case *Interface: + bound = b + case *Contract: + if len(f.Names) != len(b.TParams) { // TODO(gri) improve error message - check.errorf(f.Type.Pos(), "%d type parameters but contract expects %d", len(f.Names), len(contr.TParams)) - contr = nil // cannot use this contract + check.errorf(f.Type.Pos(), "%d type parameters but contract expects %d", len(f.Names), len(b.TParams)) + break // cannot use this contract } - } else { - check.errorf(f.Type.Pos(), "%s is not a contract", typ) + bound = b + default: + check.errorf(f.Type.Pos(), "%s is not an interface or contract", typ) } } } - for _, name := range f.Names { - tparams = append(tparams, check.declareTypeParam(name, len(tparams), contr)) + // set the type parameter's bound + if bound != nil { + for _, name := range f.Names { + tname := tparams[index] + assert(name.Name == tname.name) + tname.typ.(*TypeParam).bound = bound + index++ + } } } return tparams } -func (check *Checker) declareTypeParam(name *ast.Ident, index int, contr *Contract) *TypeName { +func (check *Checker) declareTypeParam(name *ast.Ident, index int, bound Type) *TypeName { tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) - NewTypeParam(tpar, index, contr) // assigns type to tpar as a side-effect + NewTypeParam(tpar, index, bound) // assigns type to tpar as a side-effect check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position return tpar } diff --git a/src/go/types/examples/contracts.go2 b/src/go/types/examples/contracts.go2 index e71c1d08d3..b3b0c6ea7a 100644 --- a/src/go/types/examples/contracts.go2 +++ b/src/go/types/examples/contracts.go2 @@ -45,13 +45,14 @@ type Graph (type Node, Edge G) struct { /* ... */ } func New (type Node, Edge G) (nodes []Node) *Graph(Node, Edge) { panic("unimplemented") } -func (g *Graph(Node, Edge)) ShortestPath(from, to Node) []Edge { panic("unimplemented") } +// func (g *Graph(N, E)) ShortestPath(from, to N) []E { panic("unimplemented") } // 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") } + type NodeFace(type Edge) interface { Edges() []Edge } @@ -59,4 +60,3 @@ type NodeFace(type Edge) interface { type EdgeFace(type Node) interface { Nodes() (from, to Node) } -*/ \ No newline at end of file diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 4783b7dd6b..2f9e5a6fcd 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -106,7 +106,15 @@ func (s *subster) typ(typ Type) (res Type) { } case *Interface: - panic("subst not implemented for interfaces") + // for now ignore embeddeds and types + // TODO(gri) decide what to do + assert(len(t.embeddeds) == 0) + assert(len(t.types) == 0) + if methods, copied := s.funcList(t.methods); copied { + iface := &Interface{methods: methods} + iface.Complete() + return iface + } case *Map: key := s.typ(t.key) @@ -222,6 +230,36 @@ func (s *subster) varList(in []*Var) (out []*Var, copied bool) { return } +func (s *subster) func_(f *Func) *Func { + assert(len(f.tparams) == 0) + if f != nil { + if typ := s.typ(f.typ); typ != f.typ { + copy := *f + copy.typ = typ + return © + } + } + return f +} + +func (s *subster) funcList(in []*Func) (out []*Func, copied bool) { + out = in + for i, f := range in { + if g := s.func_(f); g != f { + if !copied { + // first function that got substituted => allocate new out slice + // and copy all functions + new := make([]*Func, len(in)) + copy(new, out) + out = new + copied = true + } + out[i] = g + } + } + return +} + func typeListString(targs []Type) string { var buf bytes.Buffer for i, arg := range targs { diff --git a/src/go/types/testdata/contracts.go2 b/src/go/types/testdata/contracts.go2 index e83db783fe..d79f8cc550 100644 --- a/src/go/types/testdata/contracts.go2 +++ b/src/go/types/testdata/contracts.go2 @@ -60,7 +60,7 @@ contract _() { // Contract implementation // Type parameter type must be a contract. -type _(type T int /* ERROR not a contract */ ) struct{} +type _(type T int /* ERROR not an interface or contract */ ) struct{} // The number of type parameters must match the number of contract parameters. contract C0() {} diff --git a/src/go/types/type.go b/src/go/types/type.go index 6883c37b8d..78736aa41c 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -250,7 +250,6 @@ type Interface struct { allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) - // for contracts types []Type // for contracts } @@ -547,12 +546,12 @@ func (c *Contract) ifaceAt(index int) *Interface { type TypeParam struct { obj *TypeName index int - contr *Contract // nil if no contract + bound Type // either an *Interface or a *Contract } // NewTypeParam returns a new TypeParam. -func NewTypeParam(obj *TypeName, index int, contr *Contract) *TypeParam { - typ := &TypeParam{obj, index, contr} +func NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam { + typ := &TypeParam{obj, index, bound} if obj.typ == nil { obj.typ = typ } @@ -564,7 +563,16 @@ func NewTypeParam(obj *TypeName, index int, contr *Contract) *TypeParam { // the result is the empty interface. // TODO(gri) should this be Underlying instead? func (t *TypeParam) Interface() *Interface { - return t.contr.ifaceAt(t.index) + switch b := t.bound.(type) { + case nil: + return &emptyInterface + case *Interface: + return b + case *Contract: + return b.ifaceAt(t.index) + default: + panic("type parameter bound must be an interface or contract") + } } // Implementations for Type methods. diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 4a300dfc93..27febdf78f 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -269,7 +269,7 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { } buf.WriteString(p.name) } - buf.WriteString("){}") + buf.WriteString("){...}") // TODO write contract body case *TypeParam: diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 15ee13aca4..5d2e2a1ce2 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -495,10 +495,19 @@ func (check *Checker) parameterizedType(typ *Parameterized, e *ast.CallExpr) boo // lends itself more easily to a design where we just use interfaces // rather than contracts. assert(len(tname.tparams) > 0) - contr := tname.tparams[0].typ.(*TypeParam).contr - if !check.satisfyContract(contr, args) { - // TODO(gri) need to put in some work for really good error messages here - check.errorf(e.Pos(), "contract for %s is not satisfied", tname) + bound := tname.tparams[0].typ.(*TypeParam).bound // TODO(gri) This is incorrect (index 0) in general. FIX THIS. + switch b := bound.(type) { + case nil: + // nothing to do (no bound) + case *Interface: + panic("unimplemented") + case *Contract: + if !check.satisfyContract(b, args) { + // TODO(gri) need to put in some work for really good error messages here + check.errorf(e.Pos(), "contract for %s is not satisfied", tname) + } + default: + unreachable() } // complete parameterized type