go/types: temporary fix for subtle signature instantiation bug

A signature that's instantiated but doesn't have any incoming
or result (value) parameters doesn't get a copy automatically.
This leads to bugs because the instantiated signature doesn't
lose its type parameters when it should.

Make a copy outside for now, this fixes some (but not all cases)
and added test cases.

Also, factored out printing of type parameters in type printing.

Change-Id: I0ec3a4226c7473cddfb16704a2218992dd411593
This commit is contained in:
Robert Griesemer 2019-12-20 22:15:53 -08:00
parent d45f7ef80d
commit 53a8d66b5b
7 changed files with 86 additions and 28 deletions

View File

@ -11,6 +11,8 @@ TODO
- use []*TypeParam for tparams in subst? (unclear)
OPEN 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 enumerating type arguments currently leads to an error (e.g. func f(type T C(T)) (x T) ... )
DESIGN/IMPLEMENTATION
@ -39,3 +41,6 @@ DESIGN/IMPLEMENTATION
- 12/20/2019: Decided to start moving type parameters to types (from TypeName to Named), need to do the
same for Func. This make more sense as in general any type (conceptually even literal types) could
have type parameters. It's a property of the type, not the type name. It also simplified the code.
- 12/20/2019: Type parameters may be part of type lists in contracts/interfaces. It just falls out
naturally. Added test cases.

View File

@ -106,9 +106,17 @@ func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind {
}
// instantiate function signature
sig = check.instantiate(x.pos(), sig, targs, poslist).(*Signature)
sig.tparams = nil // signature is not generic anymore
x.typ = sig
res := check.instantiate(x.pos(), sig, targs, poslist).(*Signature)
// TODO(gri) The code below should not be necessary. The subst function
// does not correctly create a copy for signatures that have no value
// parameters but are instantiated. Documented bug.
// Look also into the situation for methods.
if res == sig {
copy := *sig
res = &copy
}
res.tparams = nil // signature is not generic anymore
x.typ = res
x.mode = value
x.expr = e
return expression

View File

@ -13,14 +13,14 @@ import (
type contractType struct{}
func (contractType) String() string { return "<contract type>" }
func (contractType) String() string { return "<dummy contract type>" }
func (contractType) Underlying() Type { panic("unreachable") }
func (check *Checker) contractDecl(contr *Contract, e *ast.ContractSpec) {
assert(contr.typ == nil)
// contracts don't have types, but we need to set a type to
// detect recursive declrations and satisfy various assertions
// detect recursive declarations and satisfy various assertions
contr.typ = new(contractType)
check.openScope(e, "contract")
@ -224,8 +224,7 @@ func (check *Checker) typeConstraint(typ Type, why *string) bool {
*why = check.sprintf("%s is not a type literal", t)
return false
case *TypeParam:
// TODO(gri) should this be ok? need a good use case
// ok for now
// ok, e.g.: func f (type T interface { type T }) ()
default:
unreachable()
}

View File

@ -24,7 +24,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
if res != nil {
under = res.Underlying()
}
check.trace(pos, "=> %s %s", res, under)
check.trace(pos, "=> %s (under = %s)", res, under)
}()
}
@ -32,11 +32,11 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
// TODO(gri) What is better here: work with TypeParams, or work with TypeNames?
var tparams []*TypeName
switch typ := typ.(type) {
switch t := typ.(type) {
case *Named:
tparams = typ.tparams
tparams = t.tparams
case *Signature:
tparams = typ.tparams
tparams = t.tparams
default:
check.dump(">>> trying to instantiate %s", typ)
unreachable() // only defined types and (defined) functions can be generic
@ -220,16 +220,20 @@ func (subst *subster) typ(typ Type) Type {
return subst.tuple(t)
case *Signature:
// TODO(gri) BUG: If we instantiate this signature but it has no value params, we don't get a copy!
// We need to look at the actual type parameters of the signature as well.
// TODO(gri) rethink the recv situation with respect to methods on parameterized types
//recv := s.var_(t.recv) // not strictly needed (receivers cannot be parameterized) (?)
recv := t.recv
params := subst.tuple(t.params)
results := subst.tuple(t.results)
if recv != t.recv || params != t.params || results != t.results {
// TODO(gri) what do we need to do with t.scope, if anything?
copy := *t
// note: we leave (copy.)tparams alone - if a caller instantiated a function
// via instantiate (calling subst) it is the caller's responsibility
// to nil out this field
// TODO(gri) if we instantiate this signature, we need to set
// tparams to nil (the signature may be a field type of a struct)
// otherwise the signature remains parameterized which would be
// wrong. Investigate the correct approach.
copy.recv = recv
copy.params = params
copy.results = results

View File

@ -185,6 +185,30 @@ func adderSum(type T Adder(T))(data []T) T {
func _(type T Adder /* ERROR cannot use generic type Adder */)(data []T) T
// --------------------------------------------------------------------------------------
// Type lists may contain type parameters... :-)
contract G1(T) { T T }
contract G2(T, T2) { T T, int; T2 T, int }
func g1 (type T G1) ()
func g2 (type T, T2 G2) ()
func h1 (type T interface{ type T }) ()
func h2 (type T, T2 interface{ type T, int }) ()
func _() {
g1(int)()
g1(string)()
g2(int, int)()
g2(int, string /* ERROR string does not satisfy */ )()
h1(int)()
h1(string)()
h2(int, int)()
h2(int, string /* ERROR string does not satisfy */ )()
}
// --------------------------------------------------------------------------------------
// Instantiations require bounds to be satisfied

View File

@ -3,3 +3,13 @@
// license that can be found in the LICENSE file.
package p
func f1 (type T interface{ type T }) ()
func f2 (type T, T2 interface{ type T, int }) ()
func _() {
f1(int)()
f1(string)()
f2(int, int)()
f2(int, string /* ERROR string does not satisfy */ )()
}

View File

@ -260,20 +260,7 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
case *Named:
writeTypeName(buf, t.obj, qf)
if t.tparams != nil {
buf.WriteString("(type ")
for i, p := range t.tparams {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(p.name)
if ptyp, _ := p.typ.(*TypeParam); ptyp != nil && ptyp.bound != nil {
buf.WriteByte(' ')
writeType(buf, ptyp.bound, qf, visited)
// TODO(gri) if this is a generic type bound, we should print
// the type parameters
}
}
buf.WriteByte(')')
writeTParamList(buf, t.tparams, qf, visited)
}
case *TypeParam:
@ -300,6 +287,23 @@ func writeTypeList(buf *bytes.Buffer, list []Type, qf Qualifier, visited []Type)
}
}
func writeTParamList(buf *bytes.Buffer, list []*TypeName, qf Qualifier, visited []Type) {
buf.WriteString("(type ")
for i, p := range list {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(p.name)
if ptyp, _ := p.typ.(*TypeParam); ptyp != nil && ptyp.bound != nil {
buf.WriteByte(' ')
writeType(buf, ptyp.bound, qf, visited)
// TODO(gri) if this is a generic type bound, we should print
// the type parameters
}
}
buf.WriteByte(')')
}
func writeTypeName(buf *bytes.Buffer, obj *TypeName, qf Qualifier) {
s := "<Named w/o object>"
if obj != nil {
@ -361,6 +365,10 @@ func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) {
}
func writeSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier, visited []Type) {
if sig.tparams != nil {
writeTParamList(buf, sig.tparams, qf, visited)
}
writeTuple(buf, sig.params, sig.variadic, qf, visited)
n := sig.results.Len()