From 4efa3ddb8716f4e7b6eb83eb914ff09587311d5d Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 24 Apr 2020 17:21:55 -0700 Subject: [PATCH] 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 --- src/go/types/NOTES | 5 ++++ src/go/types/decl.go | 35 +++++++++++++++++++++++++++- src/go/types/examples/types.go2 | 36 ++++++++++++++++++++++++++++- src/go/types/testdata/contracts.go2 | 6 ++++- src/go/types/testdata/tmp.go2 | 14 +++++++++-- src/go/types/typexpr.go | 9 ++++++++ 6 files changed, 100 insertions(+), 5 deletions(-) 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