[dev.typeparams] cmd/compile/internal/types2: clean up index expr implementation for type parameters

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>
This commit is contained in:
Robert Griesemer 2021-07-02 15:41:28 -07:00
parent 47547d8508
commit 03ec8de24b
7 changed files with 98 additions and 79 deletions

View file

@ -41,7 +41,7 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
// ordinary index expression // ordinary index expression
valid := false valid := false
length := int64(-1) // valid if >= 0 length := int64(-1) // valid if >= 0
switch typ := optype(x.typ).(type) { switch typ := under(x.typ).(type) {
case *Basic: case *Basic:
if isString(typ) { if isString(typ) {
valid = true valid = true
@ -80,7 +80,7 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
index := check.singleIndex(e) index := check.singleIndex(e)
if index == nil { if index == nil {
x.mode = invalid x.mode = invalid
return return false
} }
var key operand var key operand
check.expr(&key, index) check.expr(&key, index)
@ -89,87 +89,80 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
x.mode = mapindex x.mode = mapindex
x.typ = typ.elem x.typ = typ.elem
x.expr = e x.expr = e
return return false
case *Union: case *TypeParam:
// A union type can be indexed if all of the union's terms // TODO(gri) report detailed failure cause for better error messages
// support indexing and have the same index and element var tkey, telem Type // tkey != nil if we have maps
// type. Special rules apply for maps in the union type. if typ.underIs(func(u Type) bool {
var tkey, telem Type // key is for map types only var key, elem Type
nmaps := 0 // number of map types in union type alen := int64(-1) // valid if >= 0
if typ.underIs(func(t Type) bool { switch t := u.(type) {
var e Type
switch t := t.(type) {
case *Basic: case *Basic:
if isString(t) { if !isString(t) {
e = universeByte
}
case *Array:
e = t.elem
case *Pointer:
if t := asArray(t.base); t != nil {
e = t.elem
}
case *Slice:
e = 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) {
return false return false
} }
tkey = t.key elem = universeByte
e = t.elem case *Array:
nmaps++ elem = t.elem
case *TypeParam: alen = t.len
check.errorf(x, "type of %s contains a type parameter - cannot index (implementation restriction)", x) case *Pointer:
case *instance: a, _ := under(t.base).(*Array)
unimplemented() if a == nil {
} return false
if e == nil || telem != nil && !Identical(e, telem) { }
elem = a.elem
alen = a.len
case *Slice:
elem = t.elem
case *Map:
key = t.key
elem = t.elem
default:
return false return false
} }
telem = e 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
}
// 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
}
}
return true return true
}) { }) {
// If there are maps, the index expression must be assignable // For maps, the index expression must be assignable to the map key type.
// to the map key type (as for simple map index expressions). if tkey != nil {
if nmaps > 0 {
index := check.singleIndex(e) index := check.singleIndex(e)
if index == nil { if index == nil {
x.mode = invalid x.mode = invalid
return return false
} }
var key operand var key operand
check.expr(&key, index) check.expr(&key, index)
check.assignment(&key, tkey, "map index") check.assignment(&key, tkey, "map index")
// ok to continue even if indexing failed - map element type is known // ok to continue even if indexing failed - map element type is known
x.mode = mapindex
// If there are only maps, we are done. x.typ = telem
if nmaps == typ.NumTerms() { x.expr = e
x.mode = mapindex return false
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
} }
// no maps
valid = true
x.mode = variable
x.typ = telem x.typ = telem
} }
} }
@ -177,13 +170,13 @@ func (check *Checker) indexExpr(x *operand, e *syntax.IndexExpr) (isFuncInst boo
if !valid { if !valid {
check.errorf(x, invalidOp+"cannot index %s", x) check.errorf(x, invalidOp+"cannot index %s", x)
x.mode = invalid x.mode = invalid
return return false
} }
index := check.singleIndex(e) index := check.singleIndex(e)
if index == nil { if index == nil {
x.mode = invalid x.mode = invalid
return return false
} }
// In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0) // In pathological (invalid) cases (e.g.: type T1 [][[]T1{}[0][0]]T0)

View 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{ ~int }] (x T, i int) { _ = x /* ERROR "cannot index" */ [i] }
func _[T interface{ ~string }] (x T, i int) { _ = x[i] } func _[T interface{ ~string }] (x T, i int) { _ = x[i] }
func _[T interface{ ~[]int }] (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{ ~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{ ~[]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] } 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) // 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[i] } 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{ ~[]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{ ~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{ ~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{ ~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{ ~[]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 // slicing
// TODO(gri) implement this // TODO(gri) implement this

View file

@ -13,7 +13,7 @@ type N[T any] struct{}
var _ N[] /* ERROR expecting type */ var _ N[] /* ERROR expecting type */
type I interface { type I interface {
~map[int]int | ~[]int ~[]int
} }
func _[T I](i, j 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 */ ] _ = s[i, j /* ERROR more than one index */ ]
var t T var t T
// TODO(gri) fix multiple error below _ = t[i, j /* ERROR more than one index */ ]
_ = t[i, j /* ERROR more than one index */ /* ERROR more than one index */ ]
} }

View file

@ -78,3 +78,10 @@ func (t *TypeParam) SetBound(bound Type) {
func (t *TypeParam) Underlying() Type { return t } func (t *TypeParam) Underlying() Type { return t }
func (t *TypeParam) String() string { return TypeString(t, nil) } 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)
}

View file

@ -75,6 +75,21 @@ func (s *TypeSet) String() string {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Implementation // 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. // topTypeSet may be used as type set for the empty interface.
var topTypeSet TypeSet var topTypeSet TypeSet

View file

@ -164,7 +164,7 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
} }
for i, e := range t.types { for i, e := range t.types {
if i > 0 { if i > 0 {
buf.WriteString("|") buf.WriteByte('|')
} }
if t.tilde[i] { if t.tilde[i] {
buf.WriteByte('~') buf.WriteByte('~')

View file

@ -57,7 +57,7 @@ func (u *Union) is(f func(Type, bool) bool) bool {
return true 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 { func (u *Union) underIs(f func(Type) bool) bool {
if u.IsEmpty() { if u.IsEmpty() {
return false return false