From e15cc7ab82235c8036c0090c5617d4044e7bd419 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 12 Jun 2024 13:19:25 -0700 Subject: [PATCH] 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 Reviewed-by: Robert Findley LUCI-TryBot-Result: Go LUCI Auto-Submit: Robert Griesemer --- src/cmd/compile/internal/types2/stmt.go | 35 ++++++++--------------- src/go/types/stmt.go | 34 +++++++--------------- src/internal/types/testdata/spec/range.go | 34 ++++++++++++++++++++-- 3 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 656f0e2eb2..f9e17aa616 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -857,7 +857,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s var key, val Type if x.mode != invalid { // 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) }) 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) case sExtra != nil: 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 } @@ -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. // Exported for use by the compiler (does not exist in go/types). func RangeKeyVal(typ Type) (Type, Type) { - key, val, _, _, _ := rangeKeyVal(typ, nil) + key, val, _, _ := rangeKeyVal(typ, nil) 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 the range clause is not permitted, rangeKeyVal returns ok = false. // 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) { - bad := func(cause string) (Type, Type, string, bool, bool) { - return Typ[Invalid], Typ[Invalid], cause, false, false +func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) { + bad := func(cause string) (Type, Type, string, bool) { + return Typ[Invalid], Typ[Invalid], cause, false } toSig := func(t Type) *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") case *Basic: if isString(typ) { - return Typ[Int], universeRune, "", false, true // use 'rune' name + return Typ[Int], universeRune, "", true // use 'rune' name } if isInteger(typ) { if allowVersion != nil && !allowVersion(go1_22) { return bad("requires go1.22 or later") } - return orig, nil, "", false, true + return orig, nil, "", true } case *Array: - return Typ[Int], typ.elem, "", false, true + return Typ[Int], typ.elem, "", true case *Slice: - return Typ[Int], typ.elem, "", false, true + return Typ[Int], typ.elem, "", true case *Map: - return typ.key, typ.elem, "", false, true + return typ.key, typ.elem, "", true case *Chan: if typ.dir == SendOnly { return bad("receive from send-only channel") } - return typ.elem, nil, "", false, true + return typ.elem, nil, "", true case *Signature: if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) { 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 { val = cb.Params().At(1).Type() } - return key, val, "", true, true + return key, val, "", true } return } diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index f9a733fc3a..f5cceb8e5f 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -857,7 +857,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) { var key, val Type if x.mode != invalid { // 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) }) 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) case sExtra != nil: 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 } @@ -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 the range clause is not permitted, rangeKeyVal returns ok = false. // 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) { - bad := func(cause string) (Type, Type, string, bool, bool) { - return Typ[Invalid], Typ[Invalid], cause, false, false +func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, cause string, ok bool) { + bad := func(cause string) (Type, Type, string, bool) { + return Typ[Invalid], Typ[Invalid], cause, false } toSig := func(t Type) *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") case *Basic: if isString(typ) { - return Typ[Int], universeRune, "", false, true // use 'rune' name + return Typ[Int], universeRune, "", true // use 'rune' name } if isInteger(typ) { - // untyped numeric constants may be representable as integer values if allowVersion != nil && !allowVersion(go1_22) { return bad("requires go1.22 or later") } - return orig, nil, "", false, true + return orig, nil, "", true } case *Array: - return Typ[Int], typ.elem, "", false, true + return Typ[Int], typ.elem, "", true case *Slice: - return Typ[Int], typ.elem, "", false, true + return Typ[Int], typ.elem, "", true case *Map: - return typ.key, typ.elem, "", false, true + return typ.key, typ.elem, "", true case *Chan: if typ.dir == SendOnly { return bad("receive from send-only channel") } - return typ.elem, nil, "", false, true + return typ.elem, nil, "", true case *Signature: if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) { 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 { val = cb.Params().At(1).Type() } - return key, val, "", true, true + return key, val, "", true } return } diff --git a/src/internal/types/testdata/spec/range.go b/src/internal/types/testdata/spec/range.go index 07bd6b6769..9e32256fb7 100644 --- a/src/internal/types/testdata/spec/range.go +++ b/src/internal/types/testdata/spec/range.go @@ -24,7 +24,7 @@ func f7(func(int) MyBool) {} func f8(func(MyInt, MyString) MyBool) {} 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. 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" */ { @@ -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 f4 /* ERROR "range over f4 (value of type func(func(int) bool)) must have one iteration variable" */ { + for range f4 { } for _ = range f4 { } @@ -153,3 +153,33 @@ func _[T ~func(func(int) bool)](x T) { 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 { + } +} -- 2.48.1