mirror of https://github.com/golang/go.git
go/types, types2: allow range-over-func to omit iteration variables
For #65236. Change-Id: I63e57c1d8e9765979e9e58b45948008964b32384 Reviewed-on: https://go-review.googlesource.com/c/go/+/592176 Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
ca5ba146da
commit
e15cc7ab82
|
|
@ -857,7 +857,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
|
||||||
var key, val Type
|
var key, val Type
|
||||||
if x.mode != invalid {
|
if x.mode != invalid {
|
||||||
// Ranging over a type parameter is permitted if it has a core type.
|
// Ranging over a type parameter is permitted if it has a core type.
|
||||||
k, v, cause, isFunc, ok := rangeKeyVal(x.typ, func(v goVersion) bool {
|
k, v, cause, ok := rangeKeyVal(x.typ, func(v goVersion) bool {
|
||||||
return check.allowVersion(x.expr, v)
|
return check.allowVersion(x.expr, v)
|
||||||
})
|
})
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -871,17 +871,6 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
|
||||||
check.softErrorf(sValue, InvalidIterVar, "range over %s permits only one iteration variable", &x)
|
check.softErrorf(sValue, InvalidIterVar, "range over %s permits only one iteration variable", &x)
|
||||||
case sExtra != nil:
|
case sExtra != nil:
|
||||||
check.softErrorf(sExtra, InvalidIterVar, "range clause permits at most two iteration variables")
|
check.softErrorf(sExtra, InvalidIterVar, "range clause permits at most two iteration variables")
|
||||||
case isFunc && ((k == nil) != (sKey == nil) || (v == nil) != (sValue == nil)):
|
|
||||||
var count string
|
|
||||||
switch {
|
|
||||||
case k == nil:
|
|
||||||
count = "no iteration variables"
|
|
||||||
case v == nil:
|
|
||||||
count = "one iteration variable"
|
|
||||||
default:
|
|
||||||
count = "two iteration variables"
|
|
||||||
}
|
|
||||||
check.softErrorf(&x, InvalidIterVar, "range over %s must have %s", &x, count)
|
|
||||||
}
|
}
|
||||||
key, val = k, v
|
key, val = k, v
|
||||||
}
|
}
|
||||||
|
|
@ -1001,7 +990,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
|
||||||
// RangeKeyVal returns the key and value types for a range over typ.
|
// RangeKeyVal returns the key and value types for a range over typ.
|
||||||
// Exported for use by the compiler (does not exist in go/types).
|
// Exported for use by the compiler (does not exist in go/types).
|
||||||
func RangeKeyVal(typ Type) (Type, Type) {
|
func RangeKeyVal(typ Type) (Type, Type) {
|
||||||
key, val, _, _, _ := rangeKeyVal(typ, nil)
|
key, val, _, _ := rangeKeyVal(typ, nil)
|
||||||
return key, val
|
return key, val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1010,9 +999,9 @@ func RangeKeyVal(typ Type) (Type, Type) {
|
||||||
// If allowVersion != nil, it is used to check the required language version.
|
// If allowVersion != nil, it is used to check the required language version.
|
||||||
// If the range clause is not permitted, rangeKeyVal returns ok = false.
|
// If the range clause is not permitted, rangeKeyVal returns ok = false.
|
||||||
// When ok = false, rangeKeyVal may also return a reason in cause.
|
// When ok = false, rangeKeyVal may also return a reason in cause.
|
||||||
func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, isFunc, ok bool) {
|
func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
|
||||||
bad := func(cause string) (Type, Type, string, bool, bool) {
|
bad := func(cause string) (Type, Type, string, bool) {
|
||||||
return Typ[Invalid], Typ[Invalid], cause, false, false
|
return Typ[Invalid], Typ[Invalid], cause, false
|
||||||
}
|
}
|
||||||
toSig := func(t Type) *Signature {
|
toSig := func(t Type) *Signature {
|
||||||
sig, _ := coreType(t).(*Signature)
|
sig, _ := coreType(t).(*Signature)
|
||||||
|
|
@ -1025,25 +1014,25 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
|
||||||
return bad("no core type")
|
return bad("no core type")
|
||||||
case *Basic:
|
case *Basic:
|
||||||
if isString(typ) {
|
if isString(typ) {
|
||||||
return Typ[Int], universeRune, "", false, true // use 'rune' name
|
return Typ[Int], universeRune, "", true // use 'rune' name
|
||||||
}
|
}
|
||||||
if isInteger(typ) {
|
if isInteger(typ) {
|
||||||
if allowVersion != nil && !allowVersion(go1_22) {
|
if allowVersion != nil && !allowVersion(go1_22) {
|
||||||
return bad("requires go1.22 or later")
|
return bad("requires go1.22 or later")
|
||||||
}
|
}
|
||||||
return orig, nil, "", false, true
|
return orig, nil, "", true
|
||||||
}
|
}
|
||||||
case *Array:
|
case *Array:
|
||||||
return Typ[Int], typ.elem, "", false, true
|
return Typ[Int], typ.elem, "", true
|
||||||
case *Slice:
|
case *Slice:
|
||||||
return Typ[Int], typ.elem, "", false, true
|
return Typ[Int], typ.elem, "", true
|
||||||
case *Map:
|
case *Map:
|
||||||
return typ.key, typ.elem, "", false, true
|
return typ.key, typ.elem, "", true
|
||||||
case *Chan:
|
case *Chan:
|
||||||
if typ.dir == SendOnly {
|
if typ.dir == SendOnly {
|
||||||
return bad("receive from send-only channel")
|
return bad("receive from send-only channel")
|
||||||
}
|
}
|
||||||
return typ.elem, nil, "", false, true
|
return typ.elem, nil, "", true
|
||||||
case *Signature:
|
case *Signature:
|
||||||
if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
|
if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
|
||||||
return bad("requires go1.23 or later")
|
return bad("requires go1.23 or later")
|
||||||
|
|
@ -1071,7 +1060,7 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
|
||||||
if cb.Params().Len() >= 2 {
|
if cb.Params().Len() >= 2 {
|
||||||
val = cb.Params().At(1).Type()
|
val = cb.Params().At(1).Type()
|
||||||
}
|
}
|
||||||
return key, val, "", true, true
|
return key, val, "", true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -857,7 +857,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
|
||||||
var key, val Type
|
var key, val Type
|
||||||
if x.mode != invalid {
|
if x.mode != invalid {
|
||||||
// Ranging over a type parameter is permitted if it has a core type.
|
// Ranging over a type parameter is permitted if it has a core type.
|
||||||
k, v, cause, isFunc, ok := rangeKeyVal(x.typ, func(v goVersion) bool {
|
k, v, cause, ok := rangeKeyVal(x.typ, func(v goVersion) bool {
|
||||||
return check.allowVersion(x.expr, v)
|
return check.allowVersion(x.expr, v)
|
||||||
})
|
})
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -871,17 +871,6 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
|
||||||
check.softErrorf(sValue, InvalidIterVar, "range over %s permits only one iteration variable", &x)
|
check.softErrorf(sValue, InvalidIterVar, "range over %s permits only one iteration variable", &x)
|
||||||
case sExtra != nil:
|
case sExtra != nil:
|
||||||
check.softErrorf(sExtra, InvalidIterVar, "range clause permits at most two iteration variables")
|
check.softErrorf(sExtra, InvalidIterVar, "range clause permits at most two iteration variables")
|
||||||
case isFunc && ((k == nil) != (sKey == nil) || (v == nil) != (sValue == nil)):
|
|
||||||
var count string
|
|
||||||
switch {
|
|
||||||
case k == nil:
|
|
||||||
count = "no iteration variables"
|
|
||||||
case v == nil:
|
|
||||||
count = "one iteration variable"
|
|
||||||
default:
|
|
||||||
count = "two iteration variables"
|
|
||||||
}
|
|
||||||
check.softErrorf(&x, InvalidIterVar, "range over %s must have %s", &x, count)
|
|
||||||
}
|
}
|
||||||
key, val = k, v
|
key, val = k, v
|
||||||
}
|
}
|
||||||
|
|
@ -1003,9 +992,9 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
|
||||||
// If allowVersion != nil, it is used to check the required language version.
|
// If allowVersion != nil, it is used to check the required language version.
|
||||||
// If the range clause is not permitted, rangeKeyVal returns ok = false.
|
// If the range clause is not permitted, rangeKeyVal returns ok = false.
|
||||||
// When ok = false, rangeKeyVal may also return a reason in cause.
|
// When ok = false, rangeKeyVal may also return a reason in cause.
|
||||||
func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, isFunc, ok bool) {
|
func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) {
|
||||||
bad := func(cause string) (Type, Type, string, bool, bool) {
|
bad := func(cause string) (Type, Type, string, bool) {
|
||||||
return Typ[Invalid], Typ[Invalid], cause, false, false
|
return Typ[Invalid], Typ[Invalid], cause, false
|
||||||
}
|
}
|
||||||
toSig := func(t Type) *Signature {
|
toSig := func(t Type) *Signature {
|
||||||
sig, _ := coreType(t).(*Signature)
|
sig, _ := coreType(t).(*Signature)
|
||||||
|
|
@ -1018,26 +1007,25 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
|
||||||
return bad("no core type")
|
return bad("no core type")
|
||||||
case *Basic:
|
case *Basic:
|
||||||
if isString(typ) {
|
if isString(typ) {
|
||||||
return Typ[Int], universeRune, "", false, true // use 'rune' name
|
return Typ[Int], universeRune, "", true // use 'rune' name
|
||||||
}
|
}
|
||||||
if isInteger(typ) {
|
if isInteger(typ) {
|
||||||
// untyped numeric constants may be representable as integer values
|
|
||||||
if allowVersion != nil && !allowVersion(go1_22) {
|
if allowVersion != nil && !allowVersion(go1_22) {
|
||||||
return bad("requires go1.22 or later")
|
return bad("requires go1.22 or later")
|
||||||
}
|
}
|
||||||
return orig, nil, "", false, true
|
return orig, nil, "", true
|
||||||
}
|
}
|
||||||
case *Array:
|
case *Array:
|
||||||
return Typ[Int], typ.elem, "", false, true
|
return Typ[Int], typ.elem, "", true
|
||||||
case *Slice:
|
case *Slice:
|
||||||
return Typ[Int], typ.elem, "", false, true
|
return Typ[Int], typ.elem, "", true
|
||||||
case *Map:
|
case *Map:
|
||||||
return typ.key, typ.elem, "", false, true
|
return typ.key, typ.elem, "", true
|
||||||
case *Chan:
|
case *Chan:
|
||||||
if typ.dir == SendOnly {
|
if typ.dir == SendOnly {
|
||||||
return bad("receive from send-only channel")
|
return bad("receive from send-only channel")
|
||||||
}
|
}
|
||||||
return typ.elem, nil, "", false, true
|
return typ.elem, nil, "", true
|
||||||
case *Signature:
|
case *Signature:
|
||||||
if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
|
if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
|
||||||
return bad("requires go1.23 or later")
|
return bad("requires go1.23 or later")
|
||||||
|
|
@ -1065,7 +1053,7 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
|
||||||
if cb.Params().Len() >= 2 {
|
if cb.Params().Len() >= 2 {
|
||||||
val = cb.Params().At(1).Type()
|
val = cb.Params().At(1).Type()
|
||||||
}
|
}
|
||||||
return key, val, "", true, true
|
return key, val, "", true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ func f7(func(int) MyBool) {}
|
||||||
func f8(func(MyInt, MyString) MyBool) {}
|
func f8(func(MyInt, MyString) MyBool) {}
|
||||||
|
|
||||||
func test() {
|
func test() {
|
||||||
// TODO: Would be nice to 'for range T.M' and 'for range (*T).PM' directly,
|
// TODO: Would be nice to test 'for range T.M' and 'for range (*T).PM' directly,
|
||||||
// but there is no gofmt-friendly way to write the error pattern in the right place.
|
// but there is no gofmt-friendly way to write the error pattern in the right place.
|
||||||
m1 := T.M
|
m1 := T.M
|
||||||
for range m1 /* ERROR "cannot range over m1 (variable of type func(T)): func must be func(yield func(...) bool): argument is not func" */ {
|
for range m1 /* ERROR "cannot range over m1 (variable of type func(T)): func must be func(yield func(...) bool): argument is not func" */ {
|
||||||
|
|
@ -36,7 +36,7 @@ func test() {
|
||||||
}
|
}
|
||||||
for range f2 /* ERROR "cannot range over f2 (value of type func(func())): func must be func(yield func(...) bool): yield func does not return bool" */ {
|
for range f2 /* ERROR "cannot range over f2 (value of type func(func())): func must be func(yield func(...) bool): yield func does not return bool" */ {
|
||||||
}
|
}
|
||||||
for range f4 /* ERROR "range over f4 (value of type func(func(int) bool)) must have one iteration variable" */ {
|
for range f4 {
|
||||||
}
|
}
|
||||||
for _ = range f4 {
|
for _ = range f4 {
|
||||||
}
|
}
|
||||||
|
|
@ -153,3 +153,33 @@ func _[T ~func(func(int) bool)](x T) {
|
||||||
for _ = range x { // ok
|
for _ = range x { // ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go.dev/issue/65236
|
||||||
|
|
||||||
|
func seq0(func() bool) {}
|
||||||
|
func seq1(func(int) bool) {}
|
||||||
|
func seq2(func(int, int) bool) {}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
for range seq0 {
|
||||||
|
}
|
||||||
|
for _ /* ERROR "range over seq0 (value of type func(func() bool)) permits no iteration variables" */ = range seq0 {
|
||||||
|
}
|
||||||
|
|
||||||
|
for range seq1 {
|
||||||
|
}
|
||||||
|
for _ = range seq1 {
|
||||||
|
}
|
||||||
|
for _, _ /* ERROR "range over seq1 (value of type func(func(int) bool)) permits only one iteration variable" */ = range seq1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
for range seq2 {
|
||||||
|
}
|
||||||
|
for _ = range seq2 {
|
||||||
|
}
|
||||||
|
for _, _ = range seq2 {
|
||||||
|
}
|
||||||
|
// Note: go/types reports a parser error in this case, hence the different error messages.
|
||||||
|
for _, _, _ /* ERRORx "(range clause permits at most two iteration variables|expected at most 2 expressions)" */ = range seq2 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue