mirror of https://github.com/golang/go.git
go/types: re-enable initial contract type checking
- collect contract methods in respective interfaces - basic checking on contract type constraints Contracts are not yet tested against or used to type check function bodies. Change-Id: I13b00c44524e599f92f1ba5b4b5d6734e2bf22e1
This commit is contained in:
parent
628e3a2c6f
commit
590e4ea3f0
|
|
@ -474,7 +474,7 @@ type (
|
||||||
type Constraint struct {
|
type Constraint struct {
|
||||||
Param *Ident // constrained type parameter; or nil (for embedded contracts)
|
Param *Ident // constrained type parameter; or nil (for embedded contracts)
|
||||||
MNames []*Ident // list of method names; or nil (for embedded contracts or type constraints)
|
MNames []*Ident // list of method names; or nil (for embedded contracts or type constraints)
|
||||||
Types []Expr // embedded constraint (single *CallExpr), list of types, or list of method types (*FuncType)
|
Types []Expr // embedded contract (single *CallExpr), list of types, or list of method types (*FuncType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pos and End implementations for expression/type nodes.
|
// Pos and End implementations for expression/type nodes.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -16,80 +15,121 @@ import (
|
||||||
// won't exclude a contract where we only permit a type. Investigate.
|
// won't exclude a contract where we only permit a type. Investigate.
|
||||||
|
|
||||||
func (check *Checker) contractType(contr *Contract, e *ast.ContractType) {
|
func (check *Checker) contractType(contr *Contract, e *ast.ContractType) {
|
||||||
scope := NewScope(check.scope, token.NoPos, token.NoPos, "contract type parameters")
|
pos := e.Lbrace
|
||||||
|
if len(e.TParams) > 0 {
|
||||||
|
pos = e.TParams[0].Pos()
|
||||||
|
}
|
||||||
|
scope := NewScope(check.scope, pos, e.Rbrace, "contract type parameters")
|
||||||
check.scope = scope
|
check.scope = scope
|
||||||
defer check.closeScope()
|
defer check.closeScope()
|
||||||
check.recordScope(e, scope)
|
check.recordScope(e, scope)
|
||||||
|
|
||||||
// collect type parameters
|
// collect type parameters
|
||||||
|
tparams := make([]*TypeName, len(e.TParams))
|
||||||
for index, name := range e.TParams {
|
for index, name := range e.TParams {
|
||||||
tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
|
tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
|
||||||
NewTypeParam(tpar, index) // assigns type to tpar as a side-effect
|
NewTypeParam(tpar, index) // assigns type to tpar as a side-effect
|
||||||
check.declare(scope, name, tpar, scope.pos)
|
check.declare(scope, name, tpar, scope.pos)
|
||||||
contr.TParams = append(contr.TParams, tpar)
|
tparams[index] = tpar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// each type parameter's constraints are represented by an interface
|
||||||
|
ifaces := make(map[*TypeName]*Interface)
|
||||||
|
|
||||||
addMethod := func(tpar *TypeName, m *Func) {
|
addMethod := func(tpar *TypeName, m *Func) {
|
||||||
cs := contr.insert(tpar)
|
iface := ifaces[tpar]
|
||||||
iface := cs.Iface
|
|
||||||
if iface == nil {
|
if iface == nil {
|
||||||
iface = new(Interface)
|
iface = new(Interface)
|
||||||
cs.Iface = iface
|
ifaces[tpar] = iface
|
||||||
}
|
}
|
||||||
iface.methods = append(iface.methods, m)
|
iface.methods = append(iface.methods, m)
|
||||||
}
|
}
|
||||||
_ = addMethod
|
|
||||||
|
|
||||||
addType := func(tpar *TypeName, typ Type) {
|
|
||||||
cs := contr.insert(tpar)
|
|
||||||
// TODO(gri) should we complain about duplicate types?
|
|
||||||
cs.Types = append(cs.Types, typ)
|
|
||||||
}
|
|
||||||
_ = addType
|
|
||||||
|
|
||||||
// collect constraints
|
// collect constraints
|
||||||
for _, c := range e.Constraints {
|
for _, c := range e.Constraints {
|
||||||
if c.Param != nil {
|
if c.Param != nil {
|
||||||
// TODO(gri) update this code
|
// If a type name is present, it must be one of the contract's type parameters.
|
||||||
/*
|
pos := c.Param.Pos()
|
||||||
// If a type name is present, it must be one of the contract's type parameters.
|
obj := scope.Lookup(c.Param.Name)
|
||||||
pos := c.Param.Pos()
|
if obj == nil {
|
||||||
obj := scope.Lookup(c.Param.Name)
|
check.errorf(pos, "%s not declared by contract", c.Param.Name)
|
||||||
if obj == nil {
|
continue
|
||||||
check.errorf(pos, "%s not declared by contract", c.Param.Name)
|
}
|
||||||
continue
|
if c.Types == nil {
|
||||||
}
|
check.invalidAST(pos, "missing method or type constraint")
|
||||||
if c.Type == nil {
|
continue
|
||||||
check.invalidAST(pos, "missing method or type constraint")
|
}
|
||||||
continue
|
|
||||||
}
|
// For now we only allow a single method or a list of types,
|
||||||
tpar := obj.(*TypeName) // scope holds only *TypeNames
|
// and not multiple methods or a mix of methods and types.
|
||||||
typ := check.typ(c.Type)
|
nmethods := 0 // must be 0 or 1 or we have an error
|
||||||
if c.MName != nil {
|
for i, mname := range c.MNames {
|
||||||
// If a method name is present, it must be unique for the respective
|
if mname != nil {
|
||||||
// type parameter, and c.Type is a method signature (guaranteed by AST).
|
nmethods++
|
||||||
sig, _ := typ.(*Signature)
|
if nmethods > 1 {
|
||||||
if sig == nil {
|
check.errorf(mname.Pos(), "cannot have more than one method")
|
||||||
check.invalidAST(c.Type.Pos(), "invalid method type %s", typ)
|
break
|
||||||
}
|
}
|
||||||
// add receiver to signture (TODO(gri) do we need this? what's the "correct" receiver?)
|
} else if nmethods > 0 {
|
||||||
assert(sig.recv == nil)
|
nmethods = 2 // mark as invalid
|
||||||
recvTyp := tpar.typ
|
pos := pos // fallback position in case we don't have a type
|
||||||
sig.recv = NewVar(pos, check.pkg, "", recvTyp)
|
if i < len(c.Types) && c.Types[i] != nil {
|
||||||
// make a method
|
pos = c.Types[i].Pos()
|
||||||
m := NewFunc(c.MName.Pos(), check.pkg, c.MName.Name, sig)
|
}
|
||||||
addMethod(tpar, m)
|
check.errorf(pos, "cannot mix types and methods")
|
||||||
} else {
|
break
|
||||||
// no method name => we have a type constraint
|
}
|
||||||
var why string
|
}
|
||||||
if !check.typeConstraint(typ, &why) {
|
|
||||||
check.errorf(c.Type.Pos(), "invalid type constraint %s (%s)", typ, why)
|
tpar := obj.(*TypeName)
|
||||||
|
switch nmethods {
|
||||||
|
case 0:
|
||||||
|
// type constraints
|
||||||
|
for _, texpr := range c.Types {
|
||||||
|
if texpr == nil {
|
||||||
|
check.invalidAST(pos, "missing type constraint")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
addType(tpar, typ)
|
typ := check.typ(texpr)
|
||||||
|
// A type constraint may be a predeclared type or a
|
||||||
|
// composite type composed only of predeclared types.
|
||||||
|
// TODO(gri) should we keep this restriction?
|
||||||
|
var why string
|
||||||
|
if !check.typeConstraint(typ, &why) {
|
||||||
|
check.errorf(texpr.Pos(), "invalid type constraint %s (%s)", typ, why)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO(gri) add type
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
} else { // c.Param == nil
|
case 1:
|
||||||
|
// method constraint
|
||||||
|
if nmethods != len(c.Types) {
|
||||||
|
check.invalidAST(pos, "number of method names and signatures doesn't match")
|
||||||
|
}
|
||||||
|
// If a method is present, it must be unique for the respective type
|
||||||
|
// parameter, and c.Types[0] is a method signature (guaranteed by AST).
|
||||||
|
typ := check.typ(c.Types[0])
|
||||||
|
sig, _ := typ.(*Signature)
|
||||||
|
if sig == nil {
|
||||||
|
check.invalidAST(c.Types[0].Pos(), "invalid method type %s", typ)
|
||||||
|
}
|
||||||
|
// add receiver to signature
|
||||||
|
// (TODO(gri) verify that this matches what we do elsewhere, e.g., in NewInterfaceType)
|
||||||
|
assert(sig.recv == nil)
|
||||||
|
recvTyp := tpar.typ
|
||||||
|
sig.recv = NewVar(pos, check.pkg, "", recvTyp)
|
||||||
|
// add the method
|
||||||
|
mname := c.MNames[0]
|
||||||
|
m := NewFunc(mname.Pos(), check.pkg, mname.Name, sig)
|
||||||
|
addMethod(tpar, m)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// ignore (error was reported earlier)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
// no type name => we have an embedded contract
|
// no type name => we have an embedded contract
|
||||||
// A correct AST will have no method name and a single type that is an *ast.CallExpr in this case.
|
// A correct AST will have no method name and a single type that is an *ast.CallExpr in this case.
|
||||||
if len(c.MNames) != 0 {
|
if len(c.MNames) != 0 {
|
||||||
|
|
@ -113,30 +153,26 @@ func (check *Checker) contractType(contr *Contract, e *ast.ContractType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup/complete interfaces
|
// cleanup/complete interfaces
|
||||||
// TODO(gri) should check for duplicate entries in first pass (no need for this extra pass)
|
// TODO(gri) should check for duplicate entries in first pass (=> no need for this extra pass)
|
||||||
for tpar, cs := range contr.CMap {
|
for tpar, iface := range ifaces {
|
||||||
iface := cs.Iface
|
|
||||||
if iface == nil {
|
if iface == nil {
|
||||||
cs.Iface = &emptyInterface
|
ifaces[tpar] = &emptyInterface
|
||||||
} else {
|
} else {
|
||||||
var mset objset
|
var mset objset
|
||||||
for _, m := range iface.methods {
|
for _, m := range iface.methods {
|
||||||
if m0 := mset.insert(m); m0 != nil {
|
if m0 := mset.insert(m); m0 != nil {
|
||||||
// A method with the same name exists already.
|
// A method with the same name exists already.
|
||||||
// Complain if the signatures are different
|
check.errorf(m.Pos(), "method %s already declared", m.name)
|
||||||
// but leave it in the method set.
|
check.reportAltDecl(m0)
|
||||||
// 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))
|
sort.Sort(byUniqueMethodName(iface.methods))
|
||||||
iface.Complete()
|
iface.Complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contr.TParams = tparams
|
||||||
|
contr.IFaces = ifaces
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gri) does this simply check for the absence of defined types?
|
// TODO(gri) does this simply check for the absence of defined types?
|
||||||
|
|
|
||||||
|
|
@ -16,22 +16,25 @@ type _ contract(){}
|
||||||
type _ contract(A, B, A /* ERROR A redeclared */ ){}
|
type _ contract(A, B, A /* ERROR A redeclared */ ){}
|
||||||
|
|
||||||
// method constraints
|
// 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 } /* ERROR expected type */
|
||||||
contract _(A) { A m(); A add(A) int }
|
contract _(A) { A m(); A add(A) int }
|
||||||
//contract _(A) { B /* ERROR B not declared by contract */ m() }
|
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 */ () }
|
||||||
contract _(A) {
|
contract _(A) {
|
||||||
A m()
|
A m()
|
||||||
// A m /* ERROR already declared */ () int
|
A m /* ERROR already declared */ () int
|
||||||
}
|
}
|
||||||
contract _(A, B) {
|
contract _(A, B) {
|
||||||
A m(x int) B
|
A m(x int) B
|
||||||
B m(x int) A
|
B m(x int) A
|
||||||
A m(x int) B // double declaration with same signature is ok
|
A m /* ERROR already declared */ (x int) B
|
||||||
// B m /* ERROR already declared */ (x int) B // double declaration with different signature is not ok
|
B m /* ERROR already declared */ (x int) B
|
||||||
|
}
|
||||||
|
|
||||||
|
contract _(A) {
|
||||||
|
A m1(), m2 /* ERROR cannot have more than one method */ ()
|
||||||
|
A m3(), int /* ERROR cannot mix types and methods */
|
||||||
|
A int, float32, m4(), string /* ERROR cannot mix types and methods */
|
||||||
}
|
}
|
||||||
|
|
||||||
// type constraints
|
// type constraints
|
||||||
|
|
@ -39,16 +42,35 @@ contract _(A, B) {
|
||||||
// TODO(gri) The "correct" way of doing this is perhaps to allow multiple declarations
|
// TODO(gri) The "correct" way of doing this is perhaps to allow multiple declarations
|
||||||
// only when they appear though embedding (where they are harder to avoid), but not in general.
|
// only when they appear though embedding (where they are harder to avoid), but not in general.
|
||||||
contract _(A) { A A }
|
contract _(A) { A A }
|
||||||
//contract _(A) { A B /* ERROR undeclared name: B */ }
|
contract _(A) { A B /* ERROR undeclared name: B */ }
|
||||||
contract _(A) { A int }
|
contract _(A) { A int }
|
||||||
contract _(A) { A []int }
|
contract _(A) { A []int }
|
||||||
//contract _(A) { A []B /* ERROR undeclared name: B */ }
|
contract _(A) { A []B /* ERROR undeclared name: B */ }
|
||||||
//contract C(A) { A [ /* ERROR invalid type constraint */ ]C }
|
contract C(A) { A [ /* ERROR invalid type constraint */ ]C }
|
||||||
contract _(A) { A struct { f int } }
|
contract _(A) { A struct { f int } }
|
||||||
contract _(A, B) { A B }
|
contract _(A, B) { A B }
|
||||||
|
|
||||||
// embedded contracts
|
// embedded contracts
|
||||||
contract E() {}
|
contract E() {}
|
||||||
contract _() {
|
contract _() {
|
||||||
//E /* ERROR embedding not yet implemented */ ()
|
// E /* ERROR embedding not yet implemented */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
// Contract implementation
|
||||||
|
|
||||||
|
contract Stringer(T) {
|
||||||
|
T String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type List(type T Stringer) struct{
|
||||||
|
data T
|
||||||
|
link *List(T)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ List(MyData)
|
||||||
|
var _ List(int) // TODO(gri) should get an error here: int doesn't implement the Stringer contract
|
||||||
|
|
||||||
|
type MyData string
|
||||||
|
|
||||||
|
func (s MyData) String() string { return string(s) }
|
||||||
|
|
|
||||||
|
|
@ -510,24 +510,7 @@ type Parameterized struct {
|
||||||
// A Contract represents a contract.
|
// A Contract represents a contract.
|
||||||
type Contract struct {
|
type Contract struct {
|
||||||
TParams []*TypeName
|
TParams []*TypeName
|
||||||
CMap map[*TypeName]*Constraint // lazily allocated (possibly nil)
|
IFaces map[*TypeName]*Interface
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
// A TypeParam represents a type parameter type.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue