diff --git a/src/go/types/NOTES b/src/go/types/NOTES index 14f343b1d9..61811edb5b 100644 --- a/src/go/types/NOTES +++ b/src/go/types/NOTES @@ -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. diff --git a/src/go/types/decl.go b/src/go/types/decl.go index fe5a3db18d..17e7133787 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -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) } diff --git a/src/go/types/examples/types.go2 b/src/go/types/examples/types.go2 index 43c5ee6cb7..99eb6101b6 100644 --- a/src/go/types/examples/types.go2 +++ b/src/go/types/examples/types.go2 @@ -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 diff --git a/src/go/types/testdata/contracts.go2 b/src/go/types/testdata/contracts.go2 index 9f401f88b1..f24d405f66 100644 --- a/src/go/types/testdata/contracts.go2 +++ b/src/go/types/testdata/contracts.go2 @@ -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... :-) diff --git a/src/go/types/testdata/tmp.go2 b/src/go/types/testdata/tmp.go2 index 303b2d3adb..776f869cf0 100644 --- a/src/go/types/testdata/tmp.go2 +++ b/src/go/types/testdata/tmp.go2 @@ -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 +} diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 4b5ff11ede..542c73722e 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -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