diff --git a/src/go/types/contracts.go b/src/go/types/contracts.go new file mode 100644 index 0000000000..815411d7dd --- /dev/null +++ b/src/go/types/contracts.go @@ -0,0 +1,108 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements type-checking of contracts. + +package types + +import ( + "go/ast" + "go/token" +) + +// 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) { + 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) + } + 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") + continue + } + 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") + } + // TODO(gri) what requirements do we have for sig.scope, sig.recv? + } else { + // no method name => we have a type constraint + var why string + if !check.constraint(typ, &why) { + check.errorf(c.Type.Pos(), "invalid type constraint %s (%s)", typ, why) + continue + } + } + } else { + // no type name => we have an embedded contract + panic("embedded contracts unimplemented") + } + } +} + +func (check *Checker) constraint(typ Type, why *string) bool { + switch t := typ.(type) { + case *Basic: + // ok + case *Array: + return check.constraint(t.elem, why) + case *Slice: + return check.constraint(t.elem, why) + case *Struct: + for _, f := range t.fields { + if !check.constraint(f.typ, why) { + return false + } + } + case *Pointer: + return check.constraint(t.base, why) + case *Tuple: + panic("tuple type checking unimplemented") + case *Signature: + panic("signature type checking unimplemented") + case *Interface: + panic("interface type checking unimplemented") + case *Map: + return check.constraint(t.key, why) && check.constraint(t.elem, why) + case *Chan: + return check.constraint(t.elem, why) + case *Named: + *why = check.sprintf("%s is not a type literal", t) + return false + case *Contract: + *why = check.sprintf("%s is not a type", t) + return false + case *TypeParam: + // ok + default: + unreachable() + } + return true +} diff --git a/src/go/types/testdata/contracts.src b/src/go/types/testdata/contracts.src index c432ed0b83..e13f8cba50 100644 --- a/src/go/types/testdata/contracts.src +++ b/src/go/types/testdata/contracts.src @@ -11,14 +11,21 @@ contract _(A, B, C){} contract _(A, B, A /* ERROR A redeclared */ ){} // For now we also allow this form of contract declaration. +// TODO(gri) probably not a good idea. Disallow a local level for sure. type _ contract(){} type _ contract(A, B, A /* ERROR A redeclared */ ){} // method constraints contract _(A) { A } /* ERROR expected type */ -contract _(A) { A m() } +contract _(A) { A m(); A add(A) int } contract _(A) { B /* ERROR B not declared by contract */ m() } // type constraints contract _(A) { A A } contract _(A) { A B /* ERROR undeclared name: B */ } +contract _(A) { A int } +contract _(A) { A []int } +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 } diff --git a/src/go/types/type.go b/src/go/types/type.go index 4b17446fb7..0e57e835d0 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -499,7 +499,7 @@ func (t *Named) AddMethod(m *Func) { // A Contract represents a contract. type Contract struct { - TParams []*TypeName + TParams []*TypeName // TODO(gri) should this be a TypeParam? } func NewContract(tparams []*TypeName) *Contract { diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index f0ed1b62f4..2bb42dd856 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -801,57 +801,3 @@ func embeddedFieldIdent(e ast.Expr) *ast.Ident { } return nil // invalid embedded field } - -func (check *Checker) contractType(ctyp *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) - } - 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") - continue - } - typ := check.typ(c.Type) - if c.MName != nil { - // If a method name is present, it must be unique, and c.Type - // must must be a method signature (guaranteed by AST). - sig, _ := typ.(*Signature) - if sig == nil { - check.invalidAST(c.Type.Pos(), "invalid method type") - } - // TODO(gri) what requirements do we have for sig.scope, sig.recv? - } else { - // no method name => we have a type constraint - check.typeConstraint(typ) - } - } else { - // no type name => we have an embedded contract - panic("embedded contracts unimplemented") - } - } -} - -func (check *Checker) typeConstraint(typ Type) { - // TODO(gri) verify that we have a valid type constraint - // - determine exact rules -}