go/types: factor out contract expression handling

Change-Id: I499cce2abd6cf0647ea6c0aa5f93bbd513020518
This commit is contained in:
Robert Griesemer 2020-01-09 12:56:32 -08:00
parent 312a8ea57c
commit 3272b2b60b
4 changed files with 54 additions and 82 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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) }