design: type parameter: add rule for composite types in type lists
Also clarify rules for type assertions to generic types and generic
types as cases in a type switch.
Change-Id: I27bc3c843ba5a57acb7c583c854ff8acd2c043d3
Reviewed-on: https://go-review.googlesource.com/c/proposal/+/272706
Trust: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/design/go2draft-type-parameters.md b/design/go2draft-type-parameters.md
index 3b2b71f..297b6c2 100644
--- a/design/go2draft-type-parameters.md
+++ b/design/go2draft-type-parameters.md
@@ -2,7 +2,7 @@
Ian Lance Taylor\
Robert Griesemer\
-September 21, 2020
+November 25, 2020
## Abstract
@@ -1846,6 +1846,69 @@
}
```
+For composite types (string, pointer, array, slice, struct, function,
+map, channel) we impose an additional restriction: an operation may
+only be used if the operator accepts identical input types (if any)
+and produces identical result types for all of the types listed in the
+type list.
+To be clear, this additional restriction is only imposed when a
+composite type appears in a type list.
+It does not apply when a composite type is formed from a type
+parameter outside of a type list, as in `var v []T` for some type
+parameter `T`.
+
+```
+// structField is a type constraint with a list of structs that all
+// have a field named x.
+type structField interface {
+ type struct { a int; x int },
+ struct { b int; x float64 },
+ struct { c int; x uint64 }
+}
+
+// This function is INVALID.
+func IncrementX[T structField](p *T) {
+ v := p.x // INVALID: type of p.x is not the same for all types in list
+ v++
+ p.x = v
+}
+
+// sliceOrMap is a type constraint for a slice or a map.
+type sliceOrMap interface {
+ type []int, map[int]int
+}
+
+// Entry returns the i'th entry in a slice or the value of a map
+// at key i. This is valid as the result of the operator is always int.
+func Entry[T sliceOrMap](c T, i int) int {
+ // This is either a slice index operation or a map key lookup.
+ // Either way, the index and result types are type int.
+ return c[i]
+}
+
+// sliceOrFloatMap is a type constraint for a slice or a map.
+type sliceOrFloatMap interface {
+ type []int, map[float64]int
+}
+
+// This function is INVALID.
+// In this example the input type of the index operation is either
+// int (for a slice) or float64 (for a map), so the operation is
+// not permitted.
+func FloatEntry[T sliceOrFloatMap](c T) int {
+ return c[1.0] // INVALID: input type is either int or float64.
+}
+```
+
+Imposing this restriction makes it easier to reason about the type of
+some operation in a generic function.
+It avoids introducing the notion of a value whose type is the union of
+a set of types found by applying some operation to each element of a
+type list.
+
+(Note: with more understanding of how people want to write code, it
+may be possible to relax this restriction in the future.)
+
#### Type parameters in type lists
A type literal in a constraint can refer to type parameters of the
@@ -1954,37 +2017,6 @@
}
```
-#### Notes on composite types in type lists
-
-Type lists may include composite types.
-
-```
-type structField interface {
- type struct { a int; x int },
- struct { b int; x float64 },
- struct { c int; x uint64 }
-}
-
-func IncrementX[T structField](p *T) {
- v := p.x
- v++
- p.x = v
-}
-```
-
-This constraint on the type parameter of `IncrementX` is such that
-every valid type argument is a struct with a field `x` of some numeric
-type.
-Therefore, `IncrementX` is a valid function.
-The type of `v` is a type based on a type parameter, with an implicit
-constraint of `interface { type int, float64, uint64 }`.
-
-Admittedly this can get fairly complex, and there may be details here
-that we don't fully understand.
-In general, though, an operation (other than a method call) is
-permitted if it would be permitted for each separate type in the type
-list.
-
#### Type lists in embedded constraints
When a constraint embeds another constraint, the type list of the
@@ -3831,6 +3863,53 @@
}
```
+### Generic types as type switch cases
+
+A generic type may be used as the type in a type assertion or as a
+case in a type switch.
+
+Here are some trivial examples:
+
+```
+func Assertion[T any](v interface{}) (T, bool) {
+ t, ok := v.(T)
+ return t, ok
+}
+
+func Switch[T any](v interface{}) (T, bool) {
+ switch v := v.(type) {
+ case T:
+ return v, true
+ default:
+ var zero T
+ return zero, false
+ }
+}
+```
+
+In a type switch, it's OK if a generic type turns out to duplicate
+some other case in the type switch.
+The first matching case is chosen.
+
+```
+func Switch2[T any](v interface{}) int {
+ switch v.(type) {
+ case T:
+ return 0
+ case string:
+ return 1
+ default:
+ return 2
+ }
+}
+
+// S2a will be set to 0.
+var S2a = Switch2[string]("a string")
+
+// S2b will be set to 1.
+var S2b = Switch2[int]("another string")
+```
+
### Type inference for composite literals
This is a feature we are not suggesting now, but could consider for