mirror of https://github.com/golang/go.git
go/types: implement contract embedding
Also: - updated README - rebased on master Change-Id: Ieafcdd94460e02964350abc98651d0348d6521f9
This commit is contained in:
parent
b6a8a0a86f
commit
d4338964b3
|
|
@ -5,7 +5,6 @@ TODO
|
|||
- use Underlying() to return a type parameter's bound? investigate!
|
||||
- better error message when declaring a contract local to a function (parser gets out of sync)
|
||||
- if type parameters are repeated in recursive instantiation, they must be the same order (not yet checked)
|
||||
- implement contract embedding
|
||||
- interface embedding doesn't take care of literal type constraints yet
|
||||
(need an allTypes list, like we have an allMethods list?)
|
||||
- type assertions on/against parameterized types
|
||||
|
|
@ -55,3 +54,5 @@ DESIGN/IMPLEMENTATION
|
|||
the function block. The scope of type parameters starts at the 'type' keyword; the scope of ordinary
|
||||
parameters starts with the (opening '{' of the) function body. Both scopes end with the closing '}'
|
||||
of the function body (i.e., the end of the function block).
|
||||
|
||||
- 1/2/2020: Implementation decision: contracts can only be declared at the package level.
|
||||
|
|
|
|||
|
|
@ -74,19 +74,18 @@ contracts are syntactic sugar that may improve readability.
|
|||
|
||||
KNOWN ISSUES
|
||||
|
||||
- gofmt works only partly with parameterized code
|
||||
- importing of packages exporting generic code is not implemented
|
||||
(and likely won't be implemented in this prototype)
|
||||
(and won't be implemented in this prototype)
|
||||
- error messages are reasonable but expect them to be significantly
|
||||
better in a real implementation (the subscript numbers on type
|
||||
parameters are there to visually identify different parameters
|
||||
with the same name)
|
||||
- contract embedding is not implemented yet
|
||||
- embedding of interfaces ignores (the notationally new) type
|
||||
lists in interfaces
|
||||
- various type-specific operations (such as indexing, sending a
|
||||
message, etc.) on expressions of a generic type don't work
|
||||
yet (but are relatively easy to implement, going forward)
|
||||
- gofmt works only partly with parameterized code
|
||||
|
||||
See also the NOTES file for a more up-to-date documentation of the
|
||||
current state and issues.
|
||||
|
|
|
|||
|
|
@ -11,18 +11,9 @@ import (
|
|||
"go/token"
|
||||
)
|
||||
|
||||
type contractType struct{}
|
||||
|
||||
func (contractType) String() string { return "<dummy contract type>" }
|
||||
func (contractType) Underlying() Type { panic("unreachable") }
|
||||
|
||||
func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
||||
assert(obj.typ == nil)
|
||||
|
||||
// contracts don't have types, but we need to set a type to
|
||||
// detect recursive declarations and satisfy various assertions
|
||||
obj.typ = new(contractType)
|
||||
|
||||
check.openScope(cdecl, "contract")
|
||||
defer check.closeScope()
|
||||
|
||||
|
|
@ -47,7 +38,7 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
|||
pos := c.Param.Pos()
|
||||
tobj := check.scope.Lookup(c.Param.Name)
|
||||
if tobj == nil {
|
||||
check.errorf(pos, "%s not declared by contract", c.Param.Name)
|
||||
check.errorf(pos, "%s is not a type parameter declared by the contract", c.Param.Name)
|
||||
continue
|
||||
}
|
||||
if c.Types == nil {
|
||||
|
|
@ -76,8 +67,7 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
|||
}
|
||||
}
|
||||
|
||||
tpar := tobj.(*TypeName)
|
||||
ifaceName := bounds[tpar.typ.(*TypeParam).index]
|
||||
ifaceName := bounds[tobj.(*TypeName).typ.(*TypeParam).index]
|
||||
iface := ifaceName.underlying.(*Interface)
|
||||
switch nmethods {
|
||||
case 0:
|
||||
|
|
@ -125,42 +115,68 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
|||
econtr, _ := c.Types[0].(*ast.CallExpr)
|
||||
if econtr == nil {
|
||||
check.invalidAST(c.Types[0].Pos(), "invalid embedded contract %s", econtr)
|
||||
continue
|
||||
}
|
||||
// Handle contract lookup so we don't need to set up a special contract mode
|
||||
|
||||
// Handle contract lookup here so we don't need to set up a special contract mode
|
||||
// for operands just to carry its information through in form of some contract Type.
|
||||
// TODO(gri) this code is also in collectTypeParams (decl.go) - factor out!
|
||||
if ident, ok := unparen(econtr.Fun).(*ast.Ident); ok {
|
||||
if eobj, _ := check.lookup(ident.Name).(*Contract); eobj != nil {
|
||||
// TODO(gri) must set up contract if not yet done!
|
||||
// set up contract if not yet done
|
||||
if eobj.typ == nil {
|
||||
check.objDecl(eobj, nil)
|
||||
if eobj.typ == Typ[Invalid] {
|
||||
continue // don't use contract
|
||||
}
|
||||
}
|
||||
// eobj is a valid contract
|
||||
// TODO(gri) look for contract cycles!
|
||||
// contract arguments must match the embedded contract's parameters
|
||||
if len(econtr.Args) != len(eobj.TParams) {
|
||||
check.errorf(c.Types[0].Pos(), "%d type parameters but contract expects %d", len(econtr.Args), len(eobj.TParams))
|
||||
n := len(econtr.Args)
|
||||
if n != len(eobj.TParams) {
|
||||
check.errorf(c.Types[0].Pos(), "%d type parameters but contract expects %d", n, len(eobj.TParams))
|
||||
continue
|
||||
}
|
||||
// contract arguments must be type parameters
|
||||
// For now, they must be from the enclosing contract.
|
||||
// TODO(gri) can we allow any type parameter?
|
||||
targs := make([]Type, len(econtr.Args))
|
||||
for i, arg := range econtr.Args {
|
||||
|
||||
// contract arguments must be type parameters from the enclosing contract
|
||||
var targs []Type
|
||||
for _, arg := range econtr.Args {
|
||||
targ := check.typ(arg)
|
||||
if parg, _ := targ.(*TypeParam); parg != nil {
|
||||
// TODO(gri) check that targ is a parameter from the enclosing contract
|
||||
targs[i] = targ
|
||||
} else {
|
||||
// Contract declarations are only permitted at the package level,
|
||||
// thus the only type parameters visible inside a contract are
|
||||
// type parameters from the enclosing contract.
|
||||
// The assertion below cannot fail unless we change the premise
|
||||
// and permit contract declarations inside parameterized functions.
|
||||
assert(tparams[parg.index] == parg.obj)
|
||||
targs = append(targs, targ)
|
||||
} else if targ != Typ[Invalid] {
|
||||
check.errorf(arg.Pos(), "%s is not a type parameter", arg)
|
||||
}
|
||||
}
|
||||
// TODO(gri) - implement the steps below
|
||||
// - for each eobj type parameter, determine its (interface) bound
|
||||
// - substitute that type parameter with the actual type argument in that interface
|
||||
// - add the interface as am embedded interface to the bound matching the actual type argument
|
||||
// - tests! (incl. overlapping methods, etc.)
|
||||
check.errorf(c.Types[0].Pos(), "%s: contract embedding not yet implemented", c.Types[0])
|
||||
continue
|
||||
if len(targs) < n {
|
||||
continue // some arguments were incorrect
|
||||
}
|
||||
|
||||
// instantiate each (embedded) contract bound with contract arguments
|
||||
assert(n == len(eobj.Bounds))
|
||||
ebounds := make([]*Named, n)
|
||||
for i, ebound := range eobj.Bounds {
|
||||
ebounds[i] = check.instantiate(econtr.Args[i].Pos(), ebound, targs, nil).(*Named)
|
||||
}
|
||||
|
||||
// add the instantiated bounds as embedded interfaces to the respective
|
||||
// embedding (outer) contract bound
|
||||
for i, ebound := range ebounds {
|
||||
index := targs[i].(*TypeParam).index
|
||||
iface := bounds[index].underlying.(*Interface)
|
||||
iface.embeddeds = append(iface.embeddeds, ebound)
|
||||
check.posMap[iface] = append(check.posMap[iface], econtr.Pos()) // satisfy completeInterface requirements
|
||||
}
|
||||
}
|
||||
continue // success
|
||||
}
|
||||
|
||||
check.errorf(c.Types[0].Pos(), "%s is not a contract", c.Types[0])
|
||||
}
|
||||
}
|
||||
|
|
@ -170,10 +186,18 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
|||
check.completeInterface(cdecl.Pos(), bound.underlying.(*Interface))
|
||||
}
|
||||
|
||||
obj.typ = new(contractType) // mark contract as fully set up
|
||||
obj.TParams = tparams
|
||||
obj.Bounds = bounds
|
||||
}
|
||||
|
||||
// Contracts don't have types, but we need to set a type to
|
||||
// detect recursive declarations and satisfy assertions.
|
||||
type contractType struct{}
|
||||
|
||||
func (contractType) String() string { return "<dummy contract type>" }
|
||||
func (contractType) Underlying() Type { panic("unreachable") }
|
||||
|
||||
func (check *Checker) collectTypeConstraints(pos token.Pos, list []Type, types []ast.Expr) []Type {
|
||||
for _, texpr := range types {
|
||||
if texpr == nil {
|
||||
|
|
|
|||
|
|
@ -158,6 +158,12 @@ func (check *Checker) objDecl(obj Object, def *Named) {
|
|||
// initialize a variable with the function.
|
||||
}
|
||||
|
||||
case *Contract:
|
||||
// TODO(gri) is there anything else we need to do here?
|
||||
if check.cycle(obj) {
|
||||
obj.typ = Typ[Invalid]
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
|
@ -249,6 +255,8 @@ func (check *Checker) cycle(obj Object) (isCycle bool) {
|
|||
}
|
||||
case *Func:
|
||||
// ignored for now
|
||||
case *Contract:
|
||||
// TODO(gri) what do we need to do here, if anything?
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
|
@ -631,12 +639,18 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
|
|||
// its information through in form of some contract Type.
|
||||
if ident, ok := unparen(f.Type).(*ast.Ident); ok {
|
||||
if obj, _ := check.lookup(ident.Name).(*Contract); obj != nil {
|
||||
// set up contract if not yet done
|
||||
if obj.typ == nil {
|
||||
check.objDecl(obj, nil)
|
||||
if obj.typ == Typ[Invalid] {
|
||||
goto next // don't use contract
|
||||
}
|
||||
}
|
||||
if len(f.Names) != len(obj.TParams) {
|
||||
check.errorf(f.Type.Pos(), "%d type parameters but contract expects %d", len(f.Names), len(obj.TParams))
|
||||
goto next
|
||||
}
|
||||
// obj is a valid contract
|
||||
// TODO(gri) must set up contract if not yet done!
|
||||
// Use contract's matching type parameter bound and
|
||||
// instantiate it with the actual type parameters
|
||||
// (== targs) present.
|
||||
|
|
@ -645,7 +659,7 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
|
|||
targs[i] = tparam.typ
|
||||
}
|
||||
for i, name := range f.Names {
|
||||
bound := obj.boundsAt(i)
|
||||
bound := obj.Bounds[i]
|
||||
setBoundAt(index+i, check.instantiate(name.Pos(), bound, targs, nil))
|
||||
}
|
||||
goto next
|
||||
|
|
|
|||
|
|
@ -332,12 +332,6 @@ func NewContract(pos token.Pos, pkg *Package, name string) *Contract {
|
|||
return &Contract{object{nil, pos, pkg, name, nil, 0, white, token.NoPos}, nil, nil}
|
||||
}
|
||||
|
||||
// boundsAt returns the (named) interface for the respective
|
||||
// contract type parameter with the given index.
|
||||
func (c *Contract) boundsAt(index int) *Named {
|
||||
return c.Bounds[index]
|
||||
}
|
||||
|
||||
// A Label represents a declared label.
|
||||
// Labels don't have a type.
|
||||
type Label struct {
|
||||
|
|
|
|||
|
|
@ -228,13 +228,11 @@ func (subst *subster) typ(typ Type) Type {
|
|||
}
|
||||
|
||||
case *Interface:
|
||||
// for now ignore embeddeds
|
||||
// TODO(gri) decide what to do
|
||||
assert(len(t.embeddeds) == 0)
|
||||
methods, mcopied := subst.funcList(t.methods)
|
||||
embeddeds, ecopied := subst.typeList(t.embeddeds)
|
||||
types, tcopied := subst.typeList(t.types)
|
||||
if mcopied || tcopied {
|
||||
iface := &Interface{methods: methods, types: types}
|
||||
if mcopied || ecopied || tcopied {
|
||||
iface := &Interface{methods: methods, embeddeds: embeddeds, types: types}
|
||||
iface.Complete()
|
||||
return iface
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ contract _(A, B, A /* ERROR A redeclared */ ){}
|
|||
// method constraints
|
||||
contract _(A) { A } /* ERROR expected type */ // TODO(gri) investigate this error! looks wrong
|
||||
contract _(A) { A m(); A add(A) int }
|
||||
contract _(A) { B /* ERROR B not declared by contract */ m() }
|
||||
contract _(A) { B /* ERROR B is not a type parameter declared by the contract */ m() }
|
||||
contract _(A) { A m(); A m /* ERROR duplicate method */ () }
|
||||
contract _(A) {
|
||||
A m()
|
||||
|
|
@ -45,17 +45,59 @@ contract C(A) { A C /* ERROR use of contract */ }
|
|||
contract _(A) { A struct { f int } }
|
||||
contract _(A, B) { A B }
|
||||
|
||||
// embedded contracts
|
||||
contract E() {}
|
||||
contract _() {
|
||||
// E /* ERROR embedding not yet implemented */
|
||||
}
|
||||
|
||||
// contract use in wrong places
|
||||
const _ E /* ERROR use of contract */ = 0
|
||||
type _ E /* ERROR use of contract */
|
||||
var _ E /* ERROR use of contract */
|
||||
var _ = E /* ERROR use of contract */
|
||||
contract E() {}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// embedded contracts
|
||||
|
||||
contract E0() {}
|
||||
contract _() {
|
||||
E0()
|
||||
}
|
||||
|
||||
contract E1 /* ERROR cycle */ () {
|
||||
E1()
|
||||
}
|
||||
|
||||
contract E2 /* ERROR cycle */ (a, b, c) {
|
||||
E2(a, b) // no further errors here because of incorrect parameter count
|
||||
}
|
||||
|
||||
contract _(T) {
|
||||
E3 /* ERROR 0 type parameters */ ()
|
||||
E3(T, int /* ERROR int is not a type parameter */)
|
||||
E3(T, T)
|
||||
}
|
||||
|
||||
contract E3(A, B) {
|
||||
A a()
|
||||
B b()
|
||||
}
|
||||
|
||||
// E4 expects the methods T.a and T.b
|
||||
contract E4(T) {
|
||||
E3(T, T)
|
||||
}
|
||||
|
||||
func f(type T E4)()
|
||||
|
||||
func _() {
|
||||
f(myTa /* ERROR missing method */)()
|
||||
f(myTab)()
|
||||
}
|
||||
|
||||
type myTa struct{}
|
||||
|
||||
func (myTa) a()
|
||||
|
||||
type myTab struct{ myTa }
|
||||
|
||||
func (myTab) b()
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Contract satisfaction
|
||||
|
|
|
|||
|
|
@ -28,12 +28,7 @@ func DotProduct(type T Numeric)(s1, s2 []T) T {
|
|||
|
||||
// NumericAbs matches numeric types with an Abs method.
|
||||
contract NumericAbs(T) {
|
||||
// TODO(gri) implement contract embedding
|
||||
// Numeric(T)
|
||||
T int; T int8; T int16; T int32; T int64
|
||||
T uint; T uint8; T uint16; T uint32; T uint64; T uintptr
|
||||
T float32; T float64
|
||||
T complex64; T complex128
|
||||
Numeric(T)
|
||||
|
||||
T Abs() T
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,28 @@ contract C1(a, b, c) {
|
|||
}
|
||||
|
||||
contract C2(a, b) {
|
||||
C1 /* ERROR contract embedding */ (a, b, a)
|
||||
C1(a, b, b)
|
||||
}
|
||||
|
||||
func f(type T, _ C2)(x T)
|
||||
|
||||
type X struct{}
|
||||
|
||||
func (X) a()
|
||||
//func (X) b()
|
||||
//func (X) c()
|
||||
|
||||
func _() {
|
||||
var x X
|
||||
f(X, int)(x)
|
||||
}
|
||||
|
||||
contract C3(x) {
|
||||
C4(x)
|
||||
}
|
||||
|
||||
contract C4(x) {
|
||||
// C3(x)
|
||||
}
|
||||
|
||||
contract C(A) { A C /* ERROR use of contract */ }
|
||||
|
|
@ -37,6 +37,13 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool)
|
|||
}
|
||||
check.recordUse(e, obj)
|
||||
|
||||
// If we have a contract, don't bother type-checking it and avoid a
|
||||
// possible cycle error in favor of the more informative error below.
|
||||
if obj, _ := obj.(*Contract); obj != nil {
|
||||
check.errorf(e.Pos(), "use of contract %s not in type parameter declaration", obj.name)
|
||||
return
|
||||
}
|
||||
|
||||
// Type-check the object.
|
||||
// Only call Checker.objDecl if the object doesn't have a type yet
|
||||
// (in which case we must actually determine it) or the object is a
|
||||
|
|
@ -99,9 +106,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool)
|
|||
x.mode = variable
|
||||
|
||||
case *Contract:
|
||||
// TODO(gri) need better error message here
|
||||
check.errorf(e.Pos(), "use of contract %s not in type parameter declaration", obj.name)
|
||||
return
|
||||
unreachable() // handled earlier
|
||||
|
||||
case *Func:
|
||||
check.addDeclDep(obj)
|
||||
|
|
|
|||
Loading…
Reference in New Issue