From 13893bbf9e40a41bd66e8a8b9f5b1de8c9d29ef1 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 9 Dec 2019 14:08:26 -0800 Subject: [PATCH] go/types: type instantiation to verify type bounds Change-Id: Ia4670a0d6bbabf4a1ac71410c7bfccc99ba61c9d --- src/go/types/NOTES | 13 ------ src/go/types/subst.go | 71 +++++++++++++++++++++++++---- src/go/types/testdata/contracts.go2 | 14 ++++++ src/go/types/type.go | 2 +- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/src/go/types/NOTES b/src/go/types/NOTES index 1b02386a8b..39620f4031 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -9,19 +9,6 @@ TODO OPEN ISSUES -- type constraints are not verified in some cases: - -package p - -type B interface{type int} - -func f(type P B)(x P) {} - -func _() { - f(string)("foo") // this should be invalid - f("foo") // this should be invalid -} - - incorrect constraint errors: package p diff --git a/src/go/types/subst.go b/src/go/types/subst.go index dce998f2c7..3eac9ba021 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -28,24 +28,79 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type) (res Ty }() } - // TODO(gri) need to do a satisfy bounds check here - + // TODO(gri) What is better here: work with TypeParams, or work with TypeNames? + var tparams []*TypeName switch typ := typ.(type) { case *Named: - // TODO(gri) What is better here: work with TypeParams, or work with TypeNames? - return check.subst(pos, typ, typ.obj.tparams, targs) - + tparams = typ.obj.tparams case *Signature: - return check.subst(pos, typ, typ.tparams, targs) + tparams = typ.tparams + default: + check.dump(">>> trying to instantiate %s", typ) + unreachable() // only defined types and (defined) functions can be generic + } - panic("unreachable") // only defined types and (defined) functions can be generic + // check bounds + for i, tname := range tparams { + tpar := tname.typ.(*TypeParam) + if tpar.bound == nil { + continue // no bound + } + + // determine type parameter bound + var iface *Interface + switch bound := tpar.bound.Underlying().(type) { + case *Interface: + iface = bound + case *Contract: + iface = bound.ifaceAt(i) + default: + unreachable() + } + + // use interface type of type parameter, if any + // targ must implement iface + targ := targs[i] + if m, _ := check.missingMethod(targ, iface, true); m != nil { + check.softErrorf(pos, "%s does not satisfy %s (missing method %s)", targ, tpar.bound, m) + break + } + + // targ's underlying type must also be one of the interface types listed, if any + if len(iface.types) > 0 { + utyp := targ.Underlying() + + // TODO(gri) Cannot handle a type argument that is itself parameterized for now + switch utyp.(type) { + case *Interface, *Contract: + panic("unimplemented") + } + + // TODO(gri) factor this out as sep. function? + ok := false + for _, t := range iface.types { + // if we find one matching type, we're ok + if Identical(utyp, t) { + ok = true + break + } + } + + if !ok { + check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface) + break + } + } + } + + return check.subst(pos, typ, tparams, targs) } // subst returns the type typ with its type parameters tparams replaced by // the corresponding type arguments targs, recursively. func (check *Checker) subst(pos token.Pos, typ Type, tpars []*TypeName, targs []Type) Type { - assert(len(tpars) == len(targs)) + //assert(len(tpars) == len(targs)) // bounds may have only some type parameters if len(tpars) == 0 { return typ } diff --git a/src/go/types/testdata/contracts.go2 b/src/go/types/testdata/contracts.go2 index ab11a6297b..81a55650aa 100644 --- a/src/go/types/testdata/contracts.go2 +++ b/src/go/types/testdata/contracts.go2 @@ -183,3 +183,17 @@ func adderSum(type T Adder(T))(data []T) T { } func _(type T Adder /* ERROR cannot use generic type Adder */)(data []T) T + +// -------------------------------------------------------------------------------------- +// Instantiations require bounds to be satisfied + +type B1 interface{type int} + +func f1(type P B1)(x P) {} + +func _() { + f1 /* ERROR string not found */ (string)("foo") + f1 /* ERROR string not found */ ("foo") + f1 /* ERROR float64 not found */ (1.2) + f1(42) +} diff --git a/src/go/types/type.go b/src/go/types/type.go index 86dadb4a8a..c8b91028a0 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -540,7 +540,7 @@ type TypeParam struct { id uint64 // unique id (TODO should this be with the object? all objects?) obj *TypeName index int // parameter index - bound Type // either an *Interface or a *Contract + bound Type // either an *Interface or a *Contract // TODO(gri) make this *Interface or nil only? } // NewTypeParam returns a new TypeParam.