From 5ef7be9e7c5c4f793aef47cbbce05303e5db7f75 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 16 May 2019 15:14:39 -0700 Subject: [PATCH] go/types: more work on type-checking stand-alone contracts Does not handle contract embedding yet. --- src/go/ast/ast.go | 2 +- src/go/types/contracts.go | 106 ++++++++++++++++++++++------ src/go/types/predicates.go | 6 +- src/go/types/subst.go | 3 + src/go/types/testdata/contracts.go2 | 23 ++++++ src/go/types/type.go | 20 ++++-- 6 files changed, 130 insertions(+), 30 deletions(-) diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 881ff3af30..b55b02ca4c 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -464,7 +464,7 @@ type ( type Constraint struct { Param *Ident // constrained type parameter; or nil (for embedded constraints) - MName *Ident // method name; or nil + MName *Ident // method name; or nil (for embedded contracts or type constraints) Type Expr // embedded constraint (CallExpr), constraint type, or method type (*FuncType) } diff --git a/src/go/types/contracts.go b/src/go/types/contracts.go index 815411d7dd..5c1c1620a0 100644 --- a/src/go/types/contracts.go +++ b/src/go/types/contracts.go @@ -9,80 +9,142 @@ package types import ( "go/ast" "go/token" + "sort" ) // TODO(gri) Handling a contract like a type is problematic because it // won't exclude a contract where we only permit a type. Investigate. -func (check *Checker) contractType(ctyp *Contract, e *ast.ContractType) { +func (check *Checker) contractType(contr *Contract, e *ast.ContractType) { scope := NewScope(check.scope, token.NoPos, token.NoPos, "contract type parameters") check.scope = scope defer check.closeScope() check.recordScope(e, scope) // collect type parameters - var tparams []*TypeName for index, name := range e.TParams { tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil) NewTypeParam(tpar, index) // assigns type to tpar as a side-effect check.declare(scope, name, tpar, scope.pos) - tparams = append(tparams, tpar) + contr.TParams = append(contr.TParams, tpar) + } + + addMethod := func(tpar *TypeName, m *Func) { + cs := contr.insert(tpar) + iface := cs.Iface + if iface == nil { + iface = new(Interface) + cs.Iface = iface + } + iface.methods = append(iface.methods, m) + } + + addType := func(tpar *TypeName, typ Type) { + cs := contr.insert(tpar) + // TODO(gri) should we complain about duplicate types? + cs.Types = append(cs.Types, typ) } - ctyp.TParams = tparams // collect constraints for _, c := range e.Constraints { if c.Param != nil { // If a type name is present, it must be one of the contract's type parameters. - tpar := scope.Lookup(c.Param.Name) - if tpar == nil { - check.errorf(c.Param.Pos(), "%s not declared by contract", c.Param.Name) - continue // TODO(gri) should try fall through - } - if c.Type == nil { - check.invalidAST(c.Param.Pos(), "missing method or type constraint") + pos := c.Param.Pos() + obj := scope.Lookup(c.Param.Name) + if obj == nil { + check.errorf(pos, "%s not declared by contract", c.Param.Name) continue } + if c.Type == nil { + check.invalidAST(pos, "missing method or type constraint") + continue + } + tpar := obj.(*TypeName) // scope holds only *TypeNames typ := check.typ(c.Type) if c.MName != nil { // If a method name is present, it must be unique for the respective // type parameter, and c.Type is a method signature (guaranteed by AST). sig, _ := typ.(*Signature) if sig == nil { - check.invalidAST(c.Type.Pos(), "invalid method type") + check.invalidAST(c.Type.Pos(), "invalid method type %s", typ) } - // TODO(gri) what requirements do we have for sig.scope, sig.recv? + // add receiver to signture (TODO(gri) do we need this? what's the "correct" receiver?) + assert(sig.recv == nil) + recvTyp := tpar.typ + sig.recv = NewVar(pos, check.pkg, "", recvTyp) + // make a method + m := NewFunc(c.MName.Pos(), check.pkg, c.MName.Name, sig) + addMethod(tpar, m) } else { // no method name => we have a type constraint var why string - if !check.constraint(typ, &why) { + if !check.typeConstraint(typ, &why) { check.errorf(c.Type.Pos(), "invalid type constraint %s (%s)", typ, why) continue } + addType(tpar, typ) } } else { // no type name => we have an embedded contract - panic("embedded contracts unimplemented") + // A correct AST will have no method name and a type that is an *ast.CallExpr in this case. + if c.MName != nil { + check.invalidAST(c.MName.Pos(), "no method (%s) expected with embedded contract declaration", c.MName.Name) + } + // TODO(gri) we can probably get away w/o checking this (even if the AST is broken) + econtr, _ := c.Type.(*ast.CallExpr) + if econtr == nil { + check.invalidAST(c.Type.Pos(), "invalid embedded contract %s", econtr) + } + // etyp := check.typ(c.Type) + // _ = etyp + // TODO(gri) complete this + check.errorf(c.Type.Pos(), "%s: contract embedding not yet implemented", c.Type) + } + } + + // cleanup/complete interfaces + // TODO(gri) should check for duplicate entries in first pass (no need for this extra pass) + for tpar, cs := range contr.CMap { + iface := cs.Iface + if iface == nil { + cs.Iface = &emptyInterface + } else { + var mset objset + for _, m := range iface.methods { + if m0 := mset.insert(m); m0 != nil { + // A method with the same name exists already. + // Complain if the signatures are different + // but leave it in the method set. + // TODO(gri) should we remove it from the set? + // TODO(gri) factor out this functionality + if !Identical(m0.Type(), m.Type()) { + check.errorf(m.Pos(), "method %s already declared with different signature for %s", m.name, tpar.name) + check.reportAltDecl(m0) + } + } + } + sort.Sort(byUniqueMethodName(iface.methods)) + iface.Complete() } } } -func (check *Checker) constraint(typ Type, why *string) bool { +func (check *Checker) typeConstraint(typ Type, why *string) bool { switch t := typ.(type) { case *Basic: // ok case *Array: - return check.constraint(t.elem, why) + return check.typeConstraint(t.elem, why) case *Slice: - return check.constraint(t.elem, why) + return check.typeConstraint(t.elem, why) case *Struct: for _, f := range t.fields { - if !check.constraint(f.typ, why) { + if !check.typeConstraint(f.typ, why) { return false } } case *Pointer: - return check.constraint(t.base, why) + return check.typeConstraint(t.base, why) case *Tuple: panic("tuple type checking unimplemented") case *Signature: @@ -90,9 +152,9 @@ func (check *Checker) constraint(typ Type, why *string) bool { case *Interface: panic("interface type checking unimplemented") case *Map: - return check.constraint(t.key, why) && check.constraint(t.elem, why) + return check.typeConstraint(t.key, why) && check.typeConstraint(t.elem, why) case *Chan: - return check.constraint(t.elem, why) + return check.typeConstraint(t.elem, why) case *Named: *why = check.sprintf("%s is not a type literal", t) return false diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index a3c94bc456..0c7bfa2369 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -308,15 +308,15 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair, tparams } case *TypeParam: - if tparams == nil { - unreachable() - } if y, ok := y.(*TypeParam); ok { // TODO(gri) do we need to look at type names here? // - consider type-checking a generic function calling another generic function // - what about self-recursive calls? return x.index == y.index } + if tparams == nil { + return false + } if x := tparams[x.index]; x != nil { return check.identical0(x, y, cmpTags, p, tparams) } diff --git a/src/go/types/subst.go b/src/go/types/subst.go index c584e100f6..9766772ea0 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -98,6 +98,9 @@ func (s *subster) typ(typ Type) (res Type) { return targ } + case *Contract: + panic("subst not implemented for contracts") + default: panic("unimplemented") } diff --git a/src/go/types/testdata/contracts.go2 b/src/go/types/testdata/contracts.go2 index e13f8cba50..930ba5ca23 100644 --- a/src/go/types/testdata/contracts.go2 +++ b/src/go/types/testdata/contracts.go2 @@ -16,11 +16,28 @@ type _ contract(){} type _ contract(A, B, A /* ERROR A redeclared */ ){} // method constraints +// Methods may be declaration multiple times as long as they have matching signatures. +// TODO(gri) The "correct" way of doing this is perhaps to allow multiple declarations +// only they appear though embedding (where they are harder to avoid), but not in general. contract _(A) { A } /* ERROR expected type */ contract _(A) { A m(); A add(A) int } contract _(A) { B /* ERROR B not declared by contract */ m() } +contract _(A) { A m(); A m() } // double declaration with same signature is ok +contract _(A) { + A m() + A m /* ERROR already declared */ () int +} +contract _(A, B) { + A m(x int) B + B m(x int) A + A m(x int) B // double declaration with same signature is ok + B m /* ERROR already declared */ (x int) B // double declaration with different signature is not ok +} // type constraints +// Types may be declared multiple times. +// TODO(gri) The "correct" way of doing this is perhaps to allow multiple declarations +// only they appear though embedding (where they are harder to avoid), but not in general. contract _(A) { A A } contract _(A) { A B /* ERROR undeclared name: B */ } contract _(A) { A int } @@ -29,3 +46,9 @@ contract _(A) { A []B /* ERROR undeclared name: B */ } contract C(A) { A [ /* ERROR invalid type constraint */ ]C } contract _(A) { A struct { f int } } contract _(A, B) { A B } + +// embedded contracts +contract E() {} +contract _() { + E /* ERROR embedding not yet implemented */ () +} diff --git a/src/go/types/type.go b/src/go/types/type.go index 0e57e835d0..373918ed5a 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -499,13 +499,25 @@ func (t *Named) AddMethod(m *Func) { // A Contract represents a contract. type Contract struct { - TParams []*TypeName // TODO(gri) should this be a TypeParam? + TParams []*TypeName + CMap map[*TypeName]*Constraint // lazily allocated (possibly nil) } -func NewContract(tparams []*TypeName) *Contract { - return &Contract{ - TParams: tparams, +func (ct *Contract) insert(tpar *TypeName) *Constraint { + cs := ct.CMap[tpar] + if cs == nil { + cs = new(Constraint) + if ct.CMap == nil { + ct.CMap = make(map[*TypeName]*Constraint) + } + ct.CMap[tpar] = cs } + return cs +} + +type Constraint struct { + Iface *Interface // methods associated with the type parameter; or empty interface + Types []Type // types associated with the type parameter; not canonicalized for now } // A TypeParam represents a type parameter type.