diff --git a/src/go/types/NOTES b/src/go/types/NOTES index 18a2c876fa..5b2f77b718 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -4,6 +4,7 @@ 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) @@ -16,7 +17,6 @@ KNOWN ISSUES - 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 -- using a contract and providing type arguments currently leads to an error (e.g. func f(type T C(T)) (x T) ... ) - leaving away unused receiver type parameters leads to an error; e.g.: "type S(type T) struct{}; func (S) _()" - using _ for an unused receiver type parameter leads to an error and crash; e.g.: "type T(type P) int; func (_ T(_)) m()" @@ -28,7 +28,9 @@ OPEN QUESTIONS - confirm that it's ok to use inference in missingMethod to compare parameterized methods - now that we allow parenthesized embedded interfaces, should we allow parenthesized embedded fields? (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?) + ---------------------------------------------------------------------------------------------------- DESIGN/IMPLEMENTATION diff --git a/src/go/types/contracts.go b/src/go/types/contracts.go index 4ffc9ac771..5b50cc352f 100644 --- a/src/go/types/contracts.go +++ b/src/go/types/contracts.go @@ -120,59 +120,25 @@ func (check *Checker) contractDecl(obj *Contract, cdecl *ast.ContractSpec) { // 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 { - // 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 - // contract arguments must match the embedded contract's parameters - 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 - } + if eobj, targs, valid := check.unpackContractExpr(econtr); eobj != nil { + // we have a (possibly invalid) contract expression + if !valid { + continue + } - // 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 { - // 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) - } - } - if len(targs) < n { - continue // some arguments were incorrect - } + // 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) + } - // 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 - } + // 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 } diff --git a/src/go/types/decl.go b/src/go/types/decl.go index f8e2bf2d7b..235779a527 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -637,20 +637,21 @@ 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 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 obj, targs, valid := check.unpackContractExpr(f.Type); 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 + check.errorf(f.Type.Pos(), "contract instantiation not yet implemented") + } else { + // obj denotes a valid uninstantiated contract => + // use the declared type parameters as "arguments" 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 // Use contract's matching type parameter bound and // instantiate it with the actual type parameters // (== targs) present. @@ -662,8 +663,8 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam bound := obj.Bounds[i] setBoundAt(index+i, check.instantiate(name.Pos(), bound, targs, nil)) } - goto next } + goto next } // otherwise, bound must be an interface @@ -682,11 +683,18 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam return } -func (check *Checker) unpackContractExpr(x ast.Expr) (obj *Contract, targs []Type) { +// unpackContractExpr 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. +// 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) { // permit any parenthesized expression x = unparen(x) - // a call expression might be an instantiated contract => unpack arguments + // a call expression might be an instantiated contract => unpack var call *ast.CallExpr if call, _ = x.(*ast.CallExpr); call != nil { x = call.Fun @@ -699,38 +707,34 @@ func (check *Checker) unpackContractExpr(x ast.Expr) (obj *Contract, targs []Typ if obj.typ == nil { check.objDecl(obj, nil) if obj.typ == Typ[Invalid] { - goto Error // we have a contract but it's broken + check.use(call.Args...) + return // we have a contract but it's broken } } if call != nil { // collect type arguments if len(call.Args) != len(obj.TParams) { check.errorf(call.Pos(), "%d type parameters but contract expects %d", len(call.Args), len(obj.TParams)) - goto Error + check.use(call.Args...) + return } for _, arg := range call.Args { - if targ := check.typ(arg); targ != Typ[Invalid] { + // for now, contract type arguments must be type parameters + targ := check.typ(arg) + if _, ok := targ.(*TypeParam); ok { targs = append(targs, targ) + } else if targ != Typ[Invalid] { + check.errorf(arg.Pos(), "%s is not a type parameter", arg) } } if len(targs) != len(call.Args) { - // some arguments were invalid - obj.typ = Typ[Invalid] - return + return // some arguments are invalid } } } } - return - -Error: - if call != nil { - check.use(call.Args...) - } - if obj != nil { - obj.typ = Typ[Invalid] - } + valid = true return } diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index 97242bb0d7..a7779ea8d3 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -31,4 +31,4 @@ contract C(T) { T op(T) T } -func f(type T ((C)))(x, y T) T //{ return x.op(y) } +func f(type T C /* ERROR not yet implemented */ (T))(x, y T) T //{ return x.op(y) }