go/types: interfaces as type bounds starting to work

With various loose ends.

Change-Id: Idbcb4affc585f5bb14caa2045943a64a78d05d09
This commit is contained in:
Robert Griesemer 2019-11-19 17:10:04 -08:00
parent 51d599efa2
commit c47bad9a28
10 changed files with 110 additions and 29 deletions

View File

@ -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
}

View File

@ -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"},
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
*/

View File

@ -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 &copy
}
}
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 {

View File

@ -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() {}

View File

@ -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.

View File

@ -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:

View File

@ -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