diff --git a/src/go/types/index.go b/src/go/types/index.go index 769626dcc2..036752c734 100644 --- a/src/go/types/index.go +++ b/src/go/types/index.go @@ -15,18 +15,18 @@ import ( // If e is a valid function instantiation, indexExpr returns true. // In that case x represents the uninstantiated function value and // it is the caller's responsibility to instantiate the function. -func (check *Checker) indexExpr(x *operand, expr *typeparams.IndexExpr) (isFuncInst bool) { - check.exprOrType(x, expr.X) +func (check *Checker) indexExpr(x *operand, e *typeparams.IndexExpr) (isFuncInst bool) { + check.exprOrType(x, e.X) switch x.mode { case invalid: - check.use(expr.Indices...) + check.use(e.Indices...) return false case typexpr: // type instantiation x.mode = invalid - x.typ = check.varType(expr.Orig) + x.typ = check.varType(e.Orig) if x.typ != Typ[Invalid] { x.mode = typexpr } @@ -41,7 +41,7 @@ func (check *Checker) indexExpr(x *operand, expr *typeparams.IndexExpr) (isFuncI 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 @@ -77,10 +77,10 @@ func (check *Checker) indexExpr(x *operand, expr *typeparams.IndexExpr) (isFuncI x.typ = typ.elem case *Map: - index := check.singleIndex(expr) + index := check.singleIndex(e) if index == nil { x.mode = invalid - return + return false } var key operand check.expr(&key, index) @@ -88,88 +88,81 @@ func (check *Checker) indexExpr(x *operand, expr *typeparams.IndexExpr) (isFuncI // ok to continue even if indexing failed - map element type is known x.mode = mapindex x.typ = typ.elem - x.expr = expr.Orig - return + x.expr = e.Orig + 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 - } - 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) { + if !isString(t) { return false } - tkey = t.key - e = t.elem - nmaps++ - case *TypeParam: - check.errorf(x, 0, "type of %s contains a type parameter - cannot index (implementation restriction)", x) - case *instance: - panic("unimplemented") - } - if e == nil || telem != nil && !Identical(e, telem) { + elem = universeByte + case *Array: + elem = t.elem + alen = t.len + case *Pointer: + a, _ := under(t.base).(*Array) + if a == nil { + return false + } + elem = a.elem + alen = a.len + case *Slice: + elem = t.elem + case *Map: + key = t.key + elem = t.elem + default: 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 }) { - // If there are maps, the index expression must be assignable - // to the map key type (as for simple map index expressions). - if nmaps > 0 { - index := check.singleIndex(expr) + // 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 = expr.Orig - 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, expr *typeparams.IndexExpr) (isFuncI if !valid { check.invalidOp(x, _NonIndexableOperand, "cannot index %s", x) x.mode = invalid - return + return false } - index := check.singleIndex(expr) + 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) diff --git a/src/go/types/testdata/check/typeparams.go2 b/src/go/types/testdata/check/typeparams.go2 index 5b4361d279..b832e6b760 100644 --- a/src/go/types/testdata/check/typeparams.go2 +++ b/src/go/types/testdata/check/typeparams.go2 @@ -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 diff --git a/src/go/types/testdata/fixedbugs/issue45635.go2 b/src/go/types/testdata/fixedbugs/issue45635.go2 index c6784e12fd..fc50797b17 100644 --- a/src/go/types/testdata/fixedbugs/issue45635.go2 +++ b/src/go/types/testdata/fixedbugs/issue45635.go2 @@ -13,7 +13,7 @@ type N[T any] struct{} var _ N [] // ERROR expected type argument list 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(rFindley) Fix the duplicate error below. - _ = t[i, j /* ERROR "more than one index" */ /* ERROR "more than one index" */ ] + _ = t[i, j /* ERROR "more than one index" */ ] } diff --git a/src/go/types/typeparam.go b/src/go/types/typeparam.go index e134508855..92b048f247 100644 --- a/src/go/types/typeparam.go +++ b/src/go/types/typeparam.go @@ -70,3 +70,10 @@ func (t *TypeParam) Bound() *Interface { 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) +} diff --git a/src/go/types/typeset.go b/src/go/types/typeset.go index e979e90e6f..3fe48892fe 100644 --- a/src/go/types/typeset.go +++ b/src/go/types/typeset.go @@ -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 diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index d234d86e61..aef5e2013b 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -165,7 +165,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('~') diff --git a/src/go/types/union.go b/src/go/types/union.go index 690b734d76..7c69ec7b10 100644 --- a/src/go/types/union.go +++ b/src/go/types/union.go @@ -60,7 +60,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