mirror of https://github.com/golang/go.git
go/types: factor out contract expression handling
Change-Id: I499cce2abd6cf0647ea6c0aa5f93bbd513020518
This commit is contained in:
parent
312a8ea57c
commit
3272b2b60b
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
Loading…
Reference in New Issue