go/types: make type bound instantiation optional if bound has one type parameter

If a type bound expects one type parameter, permit omitting explicit
instantiation of the type bound in a type parameter list. In that case,
the type bound is instantiated with each respective type parameter
and becomes that type bound for that type parameter.

For example, given:

	type Bound(type T) interface { ... }

the type parameter list:

	(type P, Q Bound)

is syntactic sugar for:

	(type P Bound(P), Q Bound(Q))

This is a generalization of the former change:

https://team-review.git.corp.google.com/c/golang/go2-dev/+/728411

Passes all.bash.

Change-Id: I0191dcf3fbccf6e777f01dfa8ccef5aba0310213
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go2-dev/+/751267
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2020-05-19 17:44:39 -07:00
parent c64ef0ed90
commit 8df9c7a8fa
3 changed files with 50 additions and 30 deletions

View File

@ -680,42 +680,50 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
// TODO(gri) We should try to delay the IsInterface check
// as it may expand a possibly incomplete type.
if bound := check.anyType(f.Type); IsInterface(bound) {
// If we have exactly one type parameter and the type bound expects exactly
// one type argument, permit leaving away the type argument for the type
// bound. This allows us to write (type T B(T)) as (type T B) instead.
// If the type bound expects exactly one type argument, permit leaving
// it away and use the corresponding type parameter as implicit argument.
// This allows us to write (type p b(p), q b(q), r b(r)) as (type p, q, r b).
if isGeneric(bound) {
base := bound.(*Named) // only a *Named type can be generic
if len(f.Names) != 1 || len(base.tparams) != 1 {
// TODO(gri) make this error message better
if len(base.tparams) != 1 {
check.errorf(f.Type.Pos(), "cannot use generic type %s without instantiation (more than one type parameter)", bound)
bound = Typ[Invalid]
goto next
}
// We have and expect exactly one type parameter.
// "Manually" instantiate the bound with the parameter.
// We have exactly one type parameter.
// "Manually" instantiate the bound with each type
// parameter the bound applies to.
// TODO(gri) this code (in more general form) is also in
// checker.typInternal for the *ast.CallExpr case. Factor?
typ := new(instance)
typ.check = check
typ.pos = f.Type.Pos()
typ.base = base
typ.targs = []Type{tparams[index].typ}
typ.poslist = []token.Pos{f.Names[0].Pos()}
// make sure we check instantiation works at least once
// and that the resulting type is valid
check.atEnd(func() {
t := typ.expand()
check.validType(t, nil)
})
// update bound and recorded type
bound = typ
check.recordTypeAndValue(f.Type, typexpr, typ, nil)
for i, name := range f.Names {
typ := new(instance)
typ.check = check
typ.pos = f.Type.Pos()
typ.base = base
typ.targs = []Type{tparams[index+i].typ}
typ.poslist = []token.Pos{name.Pos()}
// Make sure we check instantiation works at least once
// and that the resulting type is valid.
check.atEnd(func() {
check.validType(typ.expand(), nil)
})
// Set bound for each type parameter and record type.
setBoundAt(index+i, typ)
// We don't have a mechanism to record multiple different types
// for a single type bound expression. Just do it for the first
// type parameter for now.
// TODO(gri) investigate what we need to do here
if i == 0 {
check.recordTypeAndValue(f.Type, typexpr, typ, nil)
}
}
goto next
}
// Otherwise, set the bound for each type parameter.
for i, _ := range f.Names {
setBoundAt(index+i, bound)
}
} else if bound != Typ[Invalid] {
check.errorf(f.Type.Pos(), "%s is not an interface or contract", bound)
check.errorf(f.Type.Pos(), "%s is not an interface", bound)
}
next:
@ -843,7 +851,7 @@ func (check *Checker) declareTypeParams(tparams []*TypeName, names []*ast.Ident)
nstr := name.Name
if len(nstr) > 0 && nstr[0] == '*' {
nstr = nstr[1:]
check.errorf(name.Pos(), "warning: *-designation recognized but currently ignored")
check.errorf(name.Pos(), `pointer designation not yet supported ("*" is ignored)`)
}
tpar := NewTypeName(name.Pos(), check.pkg, nstr, nil)
check.NewTypeParam(tpar, len(tparams), &emptyInterface) // assigns type to tpar as a side-effect

View File

@ -202,9 +202,9 @@ func _(type T interface{ m(); type int })() {
}
// As a special case, an explicit type parameter may be omitted
// from a type parameter bound if there is exactly one type
// parameter to which the type bound applies, and the type bound
// expects exactly one type argument.
// from a type parameter bound if the type bound expects exactly
// one type argument. In that case, the type argument is the
// respective type paramater to which the type bound applies.
type Adder(type T) interface {
Add(T) T
}
@ -229,7 +229,19 @@ func _(type T1 B1)()
func _(type T1 B2 /* ERROR cannot use generic type .* without instantiation */ )()
func _(type T1, T2 B0)()
func _(type T1, T2 B1 /* ERROR cannot use generic type .* without instantiation */ )()
func _(type T1, T2 B1)()
func _(type T1, T2 B2 /* ERROR cannot use generic type .* without instantiation */ )()
func _(type T1 B0, T2 B1)() // here B1 applies to T2
// When the type argument is left away, the type bound is
// instantiated for each type parameter with that type
// parameter.
func _(type A, B Adder, C Adder(A))() {
var a A // A's type bound is Adder(A)
a = a.Add(a)
var b B // B's type bound is Adder(B)
b = b.Add(b)
var c C // C's type bound is Adder(A)
a = c.Add(a)
}

View File

@ -722,7 +722,7 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
case "underlying type":
types = append(types, f.Type)
if firstUnderlying {
check.errorf(name.Pos(), `"underlying" designation recognized but currently ignored`)
check.errorf(name.Pos(), `"underlying" designation not yet supported ("underlying" is ignored)`)
firstUnderlying = false
}
continue