go/types: more work on type-checking stand-alone contracts

Does not handle contract embedding yet.
This commit is contained in:
Robert Griesemer 2019-05-16 15:14:39 -07:00
parent 48d479685e
commit 5ef7be9e7c
6 changed files with 130 additions and 30 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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