go/types: ensure a TypeParam always has a non-nil type bound

Plus minor cleanups.

Change-Id: I2508a576ad56d55fb188aa78c861afd0d032cf80
This commit is contained in:
Robert Griesemer 2020-01-13 11:22:09 -08:00
parent 3272b2b60b
commit 5d80216f87
8 changed files with 22 additions and 19 deletions

View File

@ -11,6 +11,7 @@ TODO
- debug (and error msg) printing of generic instantiated types needs some work
- improve error messages!
- use []*TypeParam for tparams in subst? (unclear)
- should we use nil instead of &emptyInterface for no type bounds (as an optimization)?
----------------------------------------------------------------------------------------------------
KNOWN ISSUES
@ -87,4 +88,5 @@ DESIGN/IMPLEMENTATION
- 1/7/2020: The current implementation permits empty type parameter lists as in: "func f(type)(x int)"
but we cannot call such a function as "f()(1)"; the empty type argument list causes problems.
We should either disallow empty type parameter lists or document this well.
Document that we allow empty type parameter list declarations, but not empty actual type parameter
lists. (We could allow them for types, but that doesn't seem consistent and probably is confusing).

View File

@ -695,7 +695,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
// construct a suitable new type parameter
tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter>", nil)
ptyp := check.NewTypeParam(tpar, 0, nil) // assigns type to tpar as a side-effect
ptyp := check.NewTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect
ptyp.bound = &Interface{types: resTypes, allMethods: markComplete, allTypes: resTypes}
return ptyp

View File

@ -441,7 +441,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
x.typ = exp.typ
x.id = exp.id
default:
check.dump("unexpected object %v", exp)
check.dump("%v: unexpected object %v", e.Sel.Pos(), exp)
unreachable()
}
x.expr = e

View File

@ -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, nil)
tparams := check.declareTypeParams(nil, cdecl.TParams, &emptyInterface)
// 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

View File

@ -644,7 +644,7 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
}
if targs != nil {
// obj denotes a valid contract that is instantiated with targs
check.errorf(f.Type.Pos(), "contract instantiation not yet implemented")
check.errorf(f.Type.Pos(), "explicit contract instantiation not yet implemented")
} else {
// obj denotes a valid uninstantiated contract =>
// use the declared type parameters as "arguments"

View File

@ -38,7 +38,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
case *Signature:
tparams = t.tparams
default:
check.dump(">>> trying to instantiate %s", typ)
check.dump("%v: cannot instantiate %v", pos, typ)
unreachable() // only defined types and (defined) functions can be generic
}
@ -50,29 +50,26 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
return Typ[Invalid]
}
// TODO(gri) should we check for len(tparams) == 0?
if len(tparams) == 0 {
return typ // nothing to do (minor optimization)
}
// check bounds
for i, tname := range tparams {
targ := targs[i]
tpar := tname.typ.(*TypeParam)
// TODO(gri) decide if we want to keep this or standardize on the empty interface
// (keeping it may make sense because it's a common scenario and it may
// be more efficient to check)
if tpar.bound == nil {
continue // no type bound (optimization)
iface := tpar.Interface()
if iface.Empty() {
continue // no type bound
}
targ := targs[i]
// best position for error reporting
pos := pos
if i < len(poslist) {
pos = poslist[i]
}
// determine type parameter bound
iface := tpar.Interface()
// The type parameter bound is parameterized with the same type parameters
// as the instantiated type; before we can use it for bounds checking we
// need to instantiate it with the type arguments with which we instantiate

View File

@ -359,7 +359,10 @@ func (t *Interface) Method(i int) *Func { t.assertCompleteness(); return t.allMe
// Empty reports whether t is the empty interface.
// The interface must have been completed.
func (t *Interface) Empty() bool { t.assertCompleteness(); return len(t.allMethods) == 0 }
func (t *Interface) Empty() bool {
t.assertCompleteness()
return len(t.allMethods) == 0 && len(t.allTypes) == 0
}
// includes reports whether the interface t includes the type typ.
func (t *Interface) includes(typ Type) bool {
@ -537,6 +540,7 @@ type TypeParam struct {
// NewTypeParam returns a new TypeParam.
func (check *Checker) NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam {
assert(bound != nil)
typ := &TypeParam{check.nextId, obj, index, bound}
check.nextId++
if obj.typ == nil {

View File

@ -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, nil)
sig.rparams = check.declareTypeParams(nil, rparams, &emptyInterface)
// determine receiver type to get its type parameters
// and the respective type parameter bounds
var recvTParams []*TypeName