go/types: fix bug with generic function instantiation

Once a generic function is instantiated, it's not generic anymore.

Also: Added various additional test cases.

Change-Id: Ic2304b6c252cfdf41e526825dda64b8a77023d47
This commit is contained in:
Robert Griesemer 2019-12-19 14:34:29 -08:00
parent edb963e7e9
commit ac0c4f1846
6 changed files with 83 additions and 81 deletions

View File

@ -4,13 +4,15 @@ TODO
- interface embedding doesn't take care of literal type constraints yet
(need an allTypes list, like we have an allMethods list?)
- type assertions on/against parameterized types
- consolidate Signature.tparams and Signature.mtparams?
- can we always set Named.targs?
- use []*TypeParam for tparams in subst
OPEN ISSUES
- using a contract and enumerating type arguments currently leads to an error (e.g. func f(type T C(T)) (x T) ... )
- contracts slip through in places where only types are permitted
- parameterized interface methods (of type bounds) need to be customized (subst) for context
DESIGN/IMPLEMENTATION DECISIONS
DESIGN/IMPLEMENTATION
- 12/4/2019: do not allow parenthesized generic uninstantiated types (unless instantiated implicitly)
In other words: generic types must always be instantiated before they can be used in any form
More generally: Only permit type instantiation T(x) in type context, when the type is a named type.
@ -19,3 +21,11 @@ DESIGN/IMPLEMENTATION DECISIONS
- 12/12/2019: represent type bounds always as (possibly unnamed) interfaces
(contracts are user syntactic sugar)
- 12/19/2019: Type parameters don't act like type aliases. For instance:
func f(type T1, T2)(x T1) T2 { return x }
is not valid, no matter how T1 and T2 are instantiated (but if T1 and T2 were type aliases with
both of them having type int, the return x would be valid). In fact, the type parameters act more
like named types. With their underlying type being the interface by which they are bound.

View File

@ -106,7 +106,9 @@ func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind {
}
// instantiate function signature
x.typ = check.instantiate(x.pos(), sig, targs, poslist)
sig = check.instantiate(x.pos(), sig, targs, poslist).(*Signature)
sig.tparams = nil // signature is not generic anymore
x.typ = sig
x.mode = value
x.expr = e
return expression
@ -260,7 +262,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, args []*oper
ddd := call.Ellipsis.IsValid()
// set up parameters
params := sig.params
sig_params := sig.params
if sig.variadic {
if ddd {
// variadic_func(a, b, c...)
@ -282,7 +284,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, args []*oper
for len(vars) < nargs {
vars = append(vars, NewParam(last.pos, last.pkg, last.name, typ))
}
params = NewTuple(vars...)
sig_params = NewTuple(vars...)
npars = nargs
} else {
// nargs < npars-1
@ -312,19 +314,20 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, args []*oper
if len(sig.tparams) > 0 {
// TODO(gri) provide position information for targs so we can feed
// it to the instantiate call for better error reporting
targs := check.infer(call.Rparen, sig.tparams, params, args)
targs := check.infer(call.Rparen, sig.tparams, sig_params, args)
if targs == nil {
return
}
rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature)
params = check.subst(call.Pos(), params, sig.tparams, targs).(*Tuple)
rsig.tparams = nil // signature is not generic anymore
sig_params = check.subst(call.Pos(), sig_params, sig.tparams, targs).(*Tuple)
// TODO(gri) Optimization: We don't need to check arguments
// from which we inferred parameter types.
}
// check arguments
for i, a := range args {
check.assignment(a, params.vars[i].typ, "argument")
check.assignment(a, sig_params.vars[i].typ, "argument")
}
return

View File

@ -230,7 +230,9 @@ func (subst *subster) typ(typ Type) Type {
results := subst.tuple(t.results)
if recv != t.recv || params != t.params || results != t.results {
copy := *t
copy.tparams = nil // TODO(gri) is this correct? (another indication that perhaps tparams belong to the function decl)
// 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
copy.recv = recv
copy.params = params
copy.results = results

View File

@ -3,75 +3,3 @@
// license that can be found in the LICENSE file.
package p
func f(type P interface{ m() P })(x P)
type T(type P) P
func (_ T(P)) m() T(P)
func _(type Q)(x Q) {
f(T(Q))(T(Q)(x))
}
// TODO(gri) Once the code above works, check the code below.
/*
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs(T))()
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() OrderedAbs(T)
func OrderedAbsDifference(type T)() {
AbsDifference(OrderedAbs(T))()
}
*/
/*
package p
type T(type _ interface { a() }, _ interface{}) struct{}
type A(type P) struct{ x P }
func (_ A(P)) a() {}
var _ T(A(int), int)
*/
/*
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs(T))(x T)
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)(x T) {
AbsDifference(OrderedAbs(T)(x))
}
*/
// TODO(gri) Once the code above works, check the code below.
/*
type NumericAbs(type T) interface {
Abs() T
}
func AbsDifference(type T NumericAbs(T))()
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() T
func OrderedAbsDifference(type T)() {
AbsDifference(OrderedAbs(T))()
}
*/

View File

@ -78,3 +78,41 @@ func Values (type T) (r Receiver(T)) T {
func (it Iterator(K)) Next() K {
return Values(Pair(K))(it.r).key
}
// A more complex test case testing type bounds (extracted from linalg.go2 and reduced to essence)
contract NumericAbs(T) {
T Abs() T
}
func AbsDifference(type T NumericAbs)(x T)
type OrderedAbs(type T) T
func (a OrderedAbs(T)) Abs() OrderedAbs(T)
func OrderedAbsDifference(type T)(x T) {
AbsDifference(OrderedAbs(T)(x))
}
// same code, reduced to essence
func g(type P interface{ m() P })(x P)
type T4(type P) P
func (_ T4(P)) m() T4(P)
func _(type Q)(x Q) {
g(T4(Q)(x))
}
// Another test case that caused problems in the past
type T5(type _ interface { a() }, _ interface{}) struct{}
type A(type P) struct{ x P }
func (_ A(P)) a() {}
var _ T5(A(int), int)

View File

@ -151,6 +151,27 @@ func _(type P)() {
f10(S2(P, int, P){}, S2(P, float32, bool){})
}
// corner case for type inference
// (was bug: after instanting f11, the type-checker didn't mark f11 as non-generic)
func f11(type T)()
func _() {
f11(int)()
}
// the previous example was extracted from
func f12(type T interface{m() T})()
type A(type T) T
func (a A(T)) m() A(T)
func _(type T)() {
f12(A(T))()
}
// method expressions
func (_ S1(P)) m()