mirror of https://github.com/golang/go.git
go/types: make type bound instantiation optional if there's only one type parameter
This simplifies the use of parameterized interfaces as type bounds.
For example, given:
type Bound(type T) interface { ... }
we can use this interface as a type bound with or without instantiation
in a type parameter list:
func _(type T Bound(T)) ... // with explicit instantiation
func _(type T Bound) ... // instantiation left away
In the latter example, Bound is syntactic sugar for Bound(T).
In general, in a type parameter list, instantiation of an interface
type bound is optional iff the the type bound applies to exactly one
type parameter and the type bound expects exactly one type argument.
Change-Id: I0e820a0e214427cce6397069c1d5268494096812
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go2-dev/+/728411
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
7c99cab23e
commit
4efa3ddb87
|
|
@ -155,3 +155,8 @@ DESIGN/IMPLEMENTATION
|
|||
method signature, and 3) a type parameter followed by a type list. This is what the type
|
||||
checker currently supports and the printer can print. (The parser still accepts a list of
|
||||
method signatures or types, freely mixed.)
|
||||
|
||||
- 4/24/2020: Permit omission of explicit type bound instantiation if the type bound is an
|
||||
interface that applies to exactly one type parameter and the type bound expects exactly
|
||||
one type argument. This makes parameterized interface type bounds easier to use in a common
|
||||
case.
|
||||
|
|
|
|||
|
|
@ -672,7 +672,40 @@ func (check *Checker) collectTypeParams(list *ast.FieldList) (tparams []*TypeNam
|
|||
}
|
||||
|
||||
// otherwise, bound must be an interface
|
||||
if bound := expand(check.typ(f.Type)); IsInterface(bound) {
|
||||
// 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 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
|
||||
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.
|
||||
// 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, _ := range f.Names {
|
||||
setBoundAt(index+i, bound)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,8 @@ func _() {
|
|||
xint = xbool // ERROR assignment
|
||||
}
|
||||
|
||||
// Generic types cannot be used without instantiation.
|
||||
// Generic types cannot be used without instantiation
|
||||
// (except in special cases in type parameter lists).
|
||||
var _ T // ERROR cannot use generic type T
|
||||
|
||||
// In type context, parameterized (generic) types cannot use instantiation syntax.
|
||||
|
|
@ -199,3 +200,36 @@ func _(type T interface{ m(); type int })() {
|
|||
y.m()
|
||||
_ = y < 0
|
||||
}
|
||||
|
||||
// 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.
|
||||
type Adder(type T) interface {
|
||||
Add(T) T
|
||||
}
|
||||
|
||||
// We don't need to explicitly instantiate the Adder bound
|
||||
// if we have exactly one type parameter.
|
||||
func Sum(type T Adder)(list []T) T {
|
||||
var sum T
|
||||
for _, x := range list {
|
||||
sum = sum.Add(x)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// Valid and invalid variations.
|
||||
type B0 interface {}
|
||||
type B1(type _) interface{}
|
||||
type B2(type _, _) interface{}
|
||||
|
||||
func _(type T1 B0)()
|
||||
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 B2 /* ERROR cannot use generic type .* without instantiation */ )()
|
||||
|
||||
func _(type T1 B0, T2 B1)() // here B1 applies to T2
|
||||
|
|
|
|||
|
|
@ -272,7 +272,11 @@ func adderSum(type T Adder(T))(data []T) T {
|
|||
return s
|
||||
}
|
||||
|
||||
func _(type T Adder /* ERROR cannot use generic type Adder */)(data []T) T
|
||||
// If we have exactly one type parameter and a type bound
|
||||
// expecting exactly one type argument, we can leave the
|
||||
// type argument away with the type bound as a form of
|
||||
// syntactic sugar.
|
||||
func _(type T Adder)(data []T) T
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Type lists may contain type parameters... :-)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,15 @@
|
|||
package p
|
||||
|
||||
type T(type P) P
|
||||
type Adder(type T) interface {
|
||||
Add(T) T
|
||||
}
|
||||
|
||||
const _ T(int) = 0
|
||||
// We don't need to explicitly instantiate the Adder bound
|
||||
// if we have exactly one type parameter.
|
||||
func Sum(type T Adder)(list []T) T {
|
||||
var sum T
|
||||
for _, x := range list {
|
||||
sum = sum.Add(x)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,6 +133,15 @@ func (check *Checker) typ(e ast.Expr) Type {
|
|||
return check.definedType(e, nil)
|
||||
}
|
||||
|
||||
// anyType type-checks the type expression e and returns its type, or Typ[Invalid].
|
||||
// The type may be generic or instantiated.
|
||||
func (check *Checker) anyType(e ast.Expr) Type {
|
||||
typ := check.typInternal(e, nil)
|
||||
assert(isTyped(typ))
|
||||
check.recordTypeAndValue(e, typexpr, typ, nil)
|
||||
return typ
|
||||
}
|
||||
|
||||
// definedType is like typ but also accepts a type name def.
|
||||
// If def != nil, e is the type specification for the defined type def, declared
|
||||
// in a type declaration, and def.underlying will be set to the type of e before
|
||||
|
|
|
|||
Loading…
Reference in New Issue