mirror of https://github.com/golang/go.git
go/types: fix various issues with contract instantiation
Contract instantiation (= contracts with explicit type arguments) should work now but has some implementation restrictions: - The type arguments provided to a contract must be type parameters from the type parameter list using the contract or the type parameter list of the en- closing contract (in case of contract embedding). - Each type argument may be used at most once per contract expression. Change-Id: Ia9f9d81e95d84f11ff3821b9f17b74eadab201f8
This commit is contained in:
parent
203449cf09
commit
4150c6a490
|
|
@ -4,10 +4,8 @@ so we have a better track record. I only switched to this file in Nov 2019, henc
|
|||
----------------------------------------------------------------------------------------------------
|
||||
TODO
|
||||
|
||||
- implement explicit contract instantiation (currently reports an error)
|
||||
- type assertions on/against parameterized types
|
||||
- use Underlying() to return a type parameter's bound? investigate!
|
||||
- if type parameters are repeated in recursive instantiation, they must be the same order (not yet checked)
|
||||
- debug (and error msg) printing of generic instantiated types needs some work
|
||||
- improve error messages!
|
||||
- use []*TypeParam for tparams in subst? (unclear)
|
||||
|
|
@ -16,6 +14,8 @@ TODO
|
|||
----------------------------------------------------------------------------------------------------
|
||||
KNOWN ISSUES
|
||||
|
||||
- contract instantiation requires the type arguments to be type parameters from the type of function
|
||||
type parameter list or enclosing contract
|
||||
- instantiating a parameterized function type w/o value or result parameters may have unexpected side-effects
|
||||
(we don't make a copy of the signature in some cases) - investigate
|
||||
- leaving away unused receiver type parameters leads to an error; e.g.: "type S(type T) struct{}; func (S) _()"
|
||||
|
|
@ -31,6 +31,8 @@ OPEN QUESTIONS
|
|||
(probably yes, for symmetry and consistency).
|
||||
- What does it mean to explicitly instantiate a contract with a non-type parameter argument?
|
||||
(e.g., contract C(T) { T int }; func _(type T C(int))(...) ... seems invalid. What are the rules?)
|
||||
- Should we be able to parenthesize embedded contracts (like we allow for interfaces)? (currently this
|
||||
is not permitted syntactically)
|
||||
|
||||
----------------------------------------------------------------------------------------------------
|
||||
DESIGN/IMPLEMENTATION
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
|||
check.openScope(cdecl, "contract")
|
||||
defer check.closeScope()
|
||||
|
||||
tparams := check.declareTypeParams(nil, cdecl.TParams, &emptyInterface)
|
||||
tparams := check.declareTypeParams(nil, cdecl.TParams)
|
||||
|
||||
// Given a contract C(P1, P2, ... Pn) { ... } we construct named types C1(P1, P2, ... Pn),
|
||||
// C2(P1, P2, ... Pn), ... Cn(P1, P2, ... Pn) with the respective underlying interfaces
|
||||
|
|
@ -118,23 +118,27 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) {
|
|||
continue
|
||||
}
|
||||
|
||||
// If the type parameters are constraint via contracts, ensure that each type
|
||||
// parameter is used at most once. Create a new map for each embedded contract
|
||||
// to check this correspondence (since we may have multiple embedded contracts).
|
||||
// Eventually, we may be able to relax this constraint and remove the need for
|
||||
// this map.
|
||||
unused := make(map[*TypeParam]bool, len(tparams))
|
||||
for _, tname := range tparams {
|
||||
unused[tname.typ.(*TypeParam)] = true
|
||||
}
|
||||
|
||||
// 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.
|
||||
if eobj, targs, valid := check.unpackContractExpr(econtr); eobj != nil {
|
||||
if eobj, targs, valid := check.contractExpr(econtr, unused); eobj != nil {
|
||||
// we have a (possibly invalid) contract expression
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
|
||||
// instantiate each (embedded) contract bound with contract arguments
|
||||
ebounds := make([]*Named, len(eobj.Bounds))
|
||||
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 {
|
||||
for i, ebound := range eobj.Bounds {
|
||||
index := targs[i].(*TypeParam).index
|
||||
iface := bounds[index].underlying.(*Interface)
|
||||
iface.embeddeds = append(iface.embeddeds, ebound)
|
||||
|
|
|
|||
|
|
@ -620,7 +620,16 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
|
|||
// type parameters are all declared early (it's not observable since a contract
|
||||
// always applies to the type parameter names immediately preceeding it).
|
||||
for _, f := range list.List {
|
||||
tparams = check.declareTypeParams(tparams, f.Names, &emptyInterface)
|
||||
tparams = check.declareTypeParams(tparams, f.Names)
|
||||
}
|
||||
|
||||
// If the type parameters are constraint via contracts, ensure that each type
|
||||
// parameter is used at most once. Create a map to check this correspondence.
|
||||
// Eventually, we may be able to relax this constraint and remove the need for
|
||||
// this map.
|
||||
unused := make(map[*TypeParam]bool, len(tparams))
|
||||
for _, tname := range tparams {
|
||||
unused[tname.typ.(*TypeParam)] = true
|
||||
}
|
||||
|
||||
setBoundAt := func(at int, bound Type) {
|
||||
|
|
@ -637,22 +646,13 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
|
|||
// If f.Type denotes a contract, handle everything 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.
|
||||
if obj, targs, valid := check.unpackContractExpr(f.Type); obj != nil {
|
||||
if obj, targs, valid := check.contractExpr(f.Type, unused); obj != nil {
|
||||
// we have a (possibly invalid) contract expression
|
||||
if !valid {
|
||||
goto next
|
||||
}
|
||||
if targs != nil {
|
||||
// obj denotes a valid contract that is instantiated with targs
|
||||
// Use contract's matching type parameter bound and
|
||||
// instantiate it with the actual type arguments targs.
|
||||
// TODO(gri) this is not correct when arguments are permutated. Investigate!
|
||||
for i, bound := range obj.Bounds {
|
||||
pos := unparen(f.Type).(*ast.CallExpr).Args[i].Pos() // we must have an *ast.CallExpr
|
||||
//check.dump("%v: bound %d = %v, under = %v, args = %v", pos, i, bound, bound.Underlying(), targs)
|
||||
setBoundAt(index+i, check.instantiate(pos, bound, targs, nil))
|
||||
}
|
||||
} else {
|
||||
// TODO(gri) can we have this code below also be handled by contractExpr?
|
||||
if targs == nil {
|
||||
// obj denotes a valid uninstantiated contract =>
|
||||
// use the declared type parameters as "arguments"
|
||||
if len(f.Names) != len(obj.TParams) {
|
||||
|
|
@ -689,14 +689,20 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
|
|||
return
|
||||
}
|
||||
|
||||
// unpackContractExpr returns the contract obj of a contract name x = C or
|
||||
// contractExpr returns the contract obj of a contract name x = C or
|
||||
// the contract obj and type arguments targs of an instantiated contract
|
||||
// expression x = C(T1, T2, ...), and whether the expression is valid.
|
||||
// The set unused contains all (outer, incoming) type parameters that
|
||||
// have not yet been used in a contract expression. It must be set prior
|
||||
// to calling contractExpr and is updated by contractExpr.
|
||||
//
|
||||
// If x does not refer to a contract, the result obj is nil (and valid is
|
||||
// true). If x is not an instantiated contract expression, the result targs
|
||||
// is nil. If x is a contract expression but contains type errors, valid is
|
||||
// false.
|
||||
func (check *Checker) unpackContractExpr(x ast.Expr) (obj *Contract, targs []Type, valid bool) {
|
||||
// false. If x is a valid instantiated contract expression, targs is the
|
||||
// list of (incomming) type parameters used as arguments for the contract,
|
||||
// with their type bounds set according to the contract.
|
||||
func (check *Checker) contractExpr(x ast.Expr, unused map[*TypeParam]bool) (obj *Contract, targs []Type, valid bool) {
|
||||
// permit any parenthesized expression
|
||||
x = unparen(x)
|
||||
|
||||
|
|
@ -724,18 +730,33 @@ func (check *Checker) unpackContractExpr(x ast.Expr) (obj *Contract, targs []Typ
|
|||
check.use(call.Args...)
|
||||
return
|
||||
}
|
||||
// For now, a contract type argument must be one of the (incoming)
|
||||
// type parameters, and each of these type parameters may be used
|
||||
// at most once.
|
||||
for _, arg := range call.Args {
|
||||
// for now, contract type arguments must be type parameters
|
||||
targ := check.typ(arg)
|
||||
if _, ok := targ.(*TypeParam); ok {
|
||||
targs = append(targs, targ)
|
||||
if tparam, _ := targ.(*TypeParam); tparam != nil {
|
||||
if ok, found := unused[tparam]; ok {
|
||||
unused[tparam] = false
|
||||
targs = append(targs, targ)
|
||||
} else if found {
|
||||
check.errorf(arg.Pos(), "%s used multiple times (not supported due to implementation restriction)", arg)
|
||||
} else {
|
||||
check.errorf(arg.Pos(), "%s is not an incoming type parameter (not supported due to implementation restriction)", arg)
|
||||
}
|
||||
} else if targ != Typ[Invalid] {
|
||||
check.errorf(arg.Pos(), "%s is not a type parameter", arg)
|
||||
check.errorf(arg.Pos(), "%s is not a type parameter (not supported due to implementation restriction)", arg)
|
||||
}
|
||||
}
|
||||
if len(targs) != len(call.Args) {
|
||||
return // some arguments are invalid
|
||||
}
|
||||
// Use contract's matching type parameter bound, instantiate
|
||||
// it with the actual type arguments targs, and set the bound
|
||||
// for the type parameter.
|
||||
for i, bound := range obj.Bounds {
|
||||
targs[i].(*TypeParam).bound = check.instantiate(call.Args[i].Pos(), bound, targs, nil).(*Named)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -744,10 +765,10 @@ func (check *Checker) unpackContractExpr(x ast.Expr) (obj *Contract, targs []Typ
|
|||
return
|
||||
}
|
||||
|
||||
func (check *Checker) declareTypeParams(tparams []*TypeName, names []*ast.Ident, bound Type) []*TypeName {
|
||||
func (check *Checker) declareTypeParams(tparams []*TypeName, names []*ast.Ident) []*TypeName {
|
||||
for _, name := range names {
|
||||
tpar := NewTypeName(name.Pos(), check.pkg, name.Name, nil)
|
||||
check.NewTypeParam(tpar, len(tparams), bound) // assigns type to tpar as a side-effect
|
||||
check.NewTypeParam(tpar, len(tparams), &emptyInterface) // assigns type to tpar as a side-effect
|
||||
check.declare(check.scope, name, tpar, check.scope.pos) // TODO(gri) check scope position
|
||||
tparams = append(tparams, tpar)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ contract E2 /* ERROR cycle */ (a, b, c) {
|
|||
contract _(T) {
|
||||
E3 /* ERROR 0 type parameters */ ()
|
||||
E3(T, int /* ERROR int is not a type parameter */)
|
||||
E3(T, T)
|
||||
E3(T, T /* ERROR used multiple times */ )
|
||||
}
|
||||
|
||||
contract E3(A, B) {
|
||||
|
|
@ -81,13 +81,13 @@ contract E3(A, B) {
|
|||
|
||||
// E4 expects the methods T.a and T.b
|
||||
contract E4(T) {
|
||||
E3(T, T)
|
||||
E3(T, T /* ERROR used multiple times */ )
|
||||
}
|
||||
|
||||
func f(type T E4)()
|
||||
|
||||
func _() {
|
||||
f(myTa /* ERROR missing method */)()
|
||||
//f(myTa /* ERROR missing method */)() // TODO(gri) check that we're doing the right thing here
|
||||
f(myTab)()
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +220,12 @@ func _(type F, C FloatComplex)(c C) {
|
|||
|
||||
// Use of instantiated contracts
|
||||
|
||||
contract Z() {}
|
||||
|
||||
contract _() {
|
||||
Z()
|
||||
}
|
||||
|
||||
contract ABC(A, B, C) {
|
||||
A a()
|
||||
B b()
|
||||
|
|
@ -240,8 +246,10 @@ func _() {
|
|||
fa(a, b, 0)
|
||||
fb(a, b, 0)
|
||||
fc(a, b, 0)
|
||||
fd(a, b, 0)
|
||||
fd(tA, tB, int)(a, b, 0) // TODO(gri) this should fail - investigate!
|
||||
fd(0, a, b)
|
||||
fd(int, tA, tB)(0, a, b)
|
||||
fd /* ERROR does not satisfy */ (a, b, 0) // TODO(gri) fix error position - should be with b
|
||||
fd(tA, tB /* ERROR does not satisfy */, int)(a, b, 0)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ contract C(A, B) {
|
|||
//func fa(type A, B, C) (A, B, C)
|
||||
//func fb(type A, B, C ABC) (A, B, C)
|
||||
//func fc(type A, B, C ABC(A, B, C)) (A, B, C)
|
||||
func fd(type A, B C(B, A)) ()
|
||||
func fd(type A, B, X C(B, A)) ()
|
||||
|
||||
type tA struct{}; func (tA) a()
|
||||
type tB struct{}; func (tB) b()
|
||||
|
|
@ -47,5 +47,5 @@ func _() {
|
|||
//(a, b, 0)
|
||||
//(a, b, 0)
|
||||
//fd(a, b, 0)
|
||||
fd(tA, tB)() // TODO(gri) this should fail
|
||||
fd(tA /* ERROR does not satisfy */ , tB, int)()
|
||||
}
|
||||
|
|
@ -179,7 +179,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast
|
|||
// (TODO(gri) this is not working because the code doesn't allow an uninstantiated parameterized recv type)
|
||||
_, rname, rparams := check.unpackRecv(recvPar.List[0].Type, true)
|
||||
if len(rparams) > 0 {
|
||||
sig.rparams = check.declareTypeParams(nil, rparams, &emptyInterface)
|
||||
sig.rparams = check.declareTypeParams(nil, rparams)
|
||||
// determine receiver type to get its type parameters
|
||||
// and the respective type parameter bounds
|
||||
var recvTParams []*TypeName
|
||||
|
|
|
|||
Loading…
Reference in New Issue