]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile/internal/types2: clean up index expr implementation...
authorRobert Griesemer <gri@golang.org>
Fri, 2 Jul 2021 22:41:28 +0000 (15:41 -0700)
committerRobert Griesemer <gri@golang.org>
Wed, 7 Jul 2021 23:42:12 +0000 (23:42 +0000)
This makes the implementation match the intended spec behavior:

Given an index expression a[x] where a is a type parameter, the
index expression is valid if the constraint for a satisfies the
following criteria:

- Either all types in the constraint type set are maps, or none
  of them are.

- If the (type set) types are maps, they must all have the same
  key type. (This may be too strict, perhaps it's sufficient to
  ensure assignability, but we can always relax that later.)

- All (type set) types must have the same element types.

- If there are any arrays, a constant index must be in range for
  the shortest array.

Change-Id: I8c094c11e6fc9496c293871ccf93e3814c881e6f
Reviewed-on: https://go-review.googlesource.com/c/go/+/332553
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
src/cmd/compile/internal/types2/index.go
src/cmd/compile/internal/types2/testdata/check/typeparams.go2
src/cmd/compile/internal/types2/testdata/fixedbugs/issue45635.go2
src/cmd/compile/internal/types2/typeparam.go
src/cmd/compile/internal/types2/typeset.go
src/cmd/compile/internal/types2/typestring.go
src/cmd/compile/internal/types2/union.go

index 47e0853a3b0ee77bd8ea4fddbab7876e3172a24b..5a4dcb47417028dfb78128cb4a1e436d3ff17f39 100644 (file)
@@ -41,7 +41,7 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
        // ordinary index expression
        valid := false
        length := int64(-1) // valid if >= 0
-       switch typ := optype(x.typ).(type) {
+       switch typ := under(x.typ).(type) {
        case *Basic:
                if isString(typ) {
                        valid = true
@@ -80,7 +80,7 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
                index := check.singleIndex(e)
                if index == nil {
                        x.mode = invalid
-                       return
+                       return false
                }
                var key operand
                check.expr(&key, index)
@@ -89,87 +89,80 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
                x.mode = mapindex
                x.typ = typ.elem
                x.expr = e
-               return
+               return false
 
-       case *Union:
-               // A union type can be indexed if all of the union's terms
-               // support indexing and have the same index and element
-               // type. Special rules apply for maps in the union type.
-               var tkey, telem Type // key is for map types only
-               nmaps := 0           // number of map types in union type
-               if typ.underIs(func(t Type) bool {
-                       var e Type
-                       switch t := t.(type) {
+       case *TypeParam:
+               // TODO(gri) report detailed failure cause for better error messages
+               var tkey, telem Type // tkey != nil if we have maps
+               if typ.underIs(func(u Type) bool {
+                       var key, elem Type
+                       alen := int64(-1) // valid if >= 0
+                       switch t := u.(type) {
                        case *Basic:
-                               if isString(t) {
-                                       e = universeByte
+                               if !isString(t) {
+                                       return false
                                }
+                               elem = universeByte
                        case *Array:
-                               e = t.elem
+                               elem = t.elem
+                               alen = t.len
                        case *Pointer:
-                               if t := asArray(t.base); t != nil {
-                                       e = t.elem
+                               a, _ := under(t.base).(*Array)
+                               if a == nil {
+                                       return false
                                }
+                               elem = a.elem
+                               alen = a.len
                        case *Slice:
-                               e = t.elem
+                               elem = t.elem
                        case *Map:
-                               // If there are multiple maps in the union type,
-                               // they must have identical key types.
-                               // TODO(gri) We may be able to relax this rule
-                               // but it becomes complicated very quickly.
-                               if tkey != nil && !Identical(t.key, tkey) {
+                               key = t.key
+                               elem = t.elem
+                       default:
+                               return false
+                       }
+                       assert(elem != nil)
+                       if telem == nil {
+                               // first type
+                               tkey, telem = key, elem
+                               length = alen
+                       } else {
+                               // all map keys must be identical (incl. all nil)
+                               if !Identical(key, tkey) {
                                        return false
                                }
-                               tkey = t.key
-                               e = t.elem
-                               nmaps++
-                       case *TypeParam:
-                               check.errorf(x, "type of %s contains a type parameter - cannot index (implementation restriction)", x)
-                       case *instance:
-                               unimplemented()
-                       }
-                       if e == nil || telem != nil && !Identical(e, telem) {
-                               return false
+                               // all element types must be identical
+                               if !Identical(elem, telem) {
+                                       return false
+                               }
+                               tkey, telem = key, elem
+                               // track the minimal length for arrays
+                               if alen >= 0 && alen < length {
+                                       length = alen
+                               }
                        }
-                       telem = e
                        return true
                }) {
-                       // If there are maps, the index expression must be assignable
-                       // to the map key type (as for simple map index expressions).
-                       if nmaps > 0 {
+                       // For maps, the index expression must be assignable to the map key type.
+                       if tkey != nil {
                                index := check.singleIndex(e)
                                if index == nil {
                                        x.mode = invalid
-                                       return
+                                       return false
                                }
                                var key operand
                                check.expr(&key, index)
                                check.assignment(&key, tkey, "map index")
                                // ok to continue even if indexing failed - map element type is known
-
-                               // If there are only maps, we are done.
-                               if nmaps == typ.NumTerms() {
-                                       x.mode = mapindex
-                                       x.typ = telem
-                                       x.expr = e
-                                       return
-                               }
-
-                               // Otherwise we have mix of maps and other types. For
-                               // now we require that the map key be an integer type.
-                               // TODO(gri) This is probably not good enough.
-                               valid = isInteger(tkey)
-                               // avoid 2nd indexing error if indexing failed above
-                               if !valid && key.mode == invalid {
-                                       x.mode = invalid
-                                       return
-                               }
-                               x.mode = value // map index expressions are not addressable
-                       } else {
-                               // no maps
-                               valid = true
-                               x.mode = variable
+                               x.mode = mapindex
+                               x.typ = telem
+                               x.expr = e
+                               return false
                        }
+
+                       // no maps
+                       valid = true
+                       x.mode = variable
                        x.typ = telem
                }
        }
@@ -177,13 +170,13 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
        if !valid {
                check.errorf(x, invalidOp+"cannot index %s", x)
                x.mode = invalid
-               return
+               return false
        }
 
        index := check.singleIndex(e)
        if index == nil {
                x.mode = invalid
-               return
+               return false
        }
 
        // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0)
index 4074ef17ea2463bb2dcf907e319f4ea4f3d5649d..123567682a3aa3a9fc1a7ec6127937e44e68b9cb 100644 (file)
@@ -98,18 +98,23 @@ func _[T any] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] }
 func _[T interface{ ~int }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] }
 func _[T interface{ ~string }] (x T, i int) { _ = x[i] }
 func _[T interface{ ~[]int }] (x T, i int) { _ = x[i] }
-func _[T interface{ ~[10]int | ~*[20]int | ~map[int]int }] (x T, i int) { _ = x[i] }
+func _[T interface{ ~[10]int | ~*[20]int | ~map[int]int }] (x T, i int) { _ = x /* ERROR cannot index */ [i] } // map and non-map types
 func _[T interface{ ~string | ~[]byte }] (x T, i int) { _ = x[i] }
 func _[T interface{ ~[]int | ~[1]rune }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] }
 func _[T interface{ ~string | ~[]rune }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] }
 
-// indexing with various combinations of map types in type lists (see issue #42616)
-func _[T interface{ ~[]E | ~map[int]E }, E any](x T, i int) { _ = x[i] }
+// indexing with various combinations of map types in type sets (see issue #42616)
+func _[T interface{ ~[]E | ~map[int]E }, E any](x T, i int) { _ = x /* ERROR cannot index */ [i] } // map and non-map types
 func _[T interface{ ~[]E }, E any](x T, i int) { _ = &x[i] }
 func _[T interface{ ~map[int]E }, E any](x T, i int) { _, _ = x[i] } // comma-ok permitted
-func _[T interface{ ~[]E | ~map[int]E }, E any](x T, i int) { _ = &x /* ERROR cannot take address */ [i] }
-func _[T interface{ ~[]E | ~map[int]E | ~map[uint]E }, E any](x T, i int) { _ = x /* ERROR cannot index */ [i] } // different map element types
-func _[T interface{ ~[]E | ~map[string]E }, E any](x T, i int) { _ = x[i /* ERROR cannot use i */ ] }
+func _[T interface{ ~map[int]E }, E any](x T, i int) { _ = &x /* ERROR cannot take address */ [i] }
+func _[T interface{ ~map[int]E | ~map[uint]E }, E any](x T, i int) { _ = x /* ERROR cannot index */ [i] } // different map element types
+func _[T interface{ ~[]E | ~map[string]E }, E any](x T, i int) { _ = x /* ERROR cannot index */ [i] } // map and non-map types
+
+// indexing with various combinations of array and other types in type sets
+func _[T interface{ [10]int }](x T, i int) { _ = x[i]; _ = x[9]; _ = x[10 /* ERROR out of bounds */ ] }
+func _[T interface{ [10]byte | string }](x T, i int) { _ = x[i]; _ = x[9]; _ = x[10 /* ERROR out of bounds */ ] }
+func _[T interface{ [10]int | *[20]int | []int }](x T, i int) { _ = x[i]; _ = x[9]; _ = x[10 /* ERROR out of bounds */ ] }
 
 // slicing
 // TODO(gri) implement this
index e9b57ae8f1048a9406c3da2a5684a115d7b7ad51..29379591057a6148a38cc0ff6ede246c527ee79e 100644 (file)
@@ -13,7 +13,7 @@ type N[T any] struct{}
 var _ N[] /* ERROR expecting type */
 
 type I interface {
-       ~map[int]int | ~[]int
+       ~[]int
 }
 
 func _[T I](i, j int) {
@@ -27,6 +27,5 @@ func _[T I](i, j int) {
        _ = s[i, j /* ERROR more than one index */ ]
 
        var t T
-       // TODO(gri) fix multiple error below
-       _ = t[i, j /* ERROR more than one index */ /* ERROR more than one index */ ]
+       _ = t[i, j /* ERROR more than one index */ ]
 }
index 5c7440591242df1b89d3ec5fde974d85f4c890e6..b73b4edf79f25dcdf4664f5c171a147cd99c61bb 100644 (file)
@@ -78,3 +78,10 @@ func (t *TypeParam) SetBound(bound Type) {
 
 func (t *TypeParam) Underlying() Type { return t }
 func (t *TypeParam) String() string   { return TypeString(t, nil) }
+
+// ----------------------------------------------------------------------------
+// Implementation
+
+func (t *TypeParam) underIs(f func(Type) bool) bool {
+       return t.Bound().typeSet().underIs(f)
+}
index 265221501f26dc13655aec6017bb9e7a9e74b029..6ff85639748b17724aa9ee2bc2c8c0f0736e9944 100644 (file)
@@ -75,6 +75,21 @@ func (s *TypeSet) String() string {
 // ----------------------------------------------------------------------------
 // Implementation
 
+// underIs reports whether f returned true for the underlying types of the
+// enumerable types in the type set s. If the type set comprises all types
+// f is called once with the top type; if the type set is empty, the result
+// is false.
+func (s *TypeSet) underIs(f func(Type) bool) bool {
+       switch t := s.types.(type) {
+       case nil:
+               return f(theTop)
+       default:
+               return f(t)
+       case *Union:
+               return t.underIs(f)
+       }
+}
+
 // topTypeSet may be used as type set for the empty interface.
 var topTypeSet TypeSet
 
index 4925252b3946b3037731b277e16ddc7976eb4e0a..f63a23c98ce8772852883daf98a0ecaaf6ab2ab5 100644 (file)
@@ -164,7 +164,7 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
                }
                for i, e := range t.types {
                        if i > 0 {
-                               buf.WriteString("|")
+                               buf.WriteByte('|')
                        }
                        if t.tilde[i] {
                                buf.WriteByte('~')
index 30570b5e802a2214bc4a0771b72527d7cf279b03..e5e851c1d267d7aae91a3e185338f4a8f706cd9e 100644 (file)
@@ -57,7 +57,7 @@ func (u *Union) is(f func(Type, bool) bool) bool {
        return true
 }
 
-// is reports whether f returned true for the underlying types of all terms of u.
+// underIs reports whether f returned true for the underlying types of all terms of u.
 func (u *Union) underIs(f func(Type) bool) bool {
        if u.IsEmpty() {
                return false