From 13c9cda93599eb67d61cd978b12d2faf84a639f7 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 13 Dec 2023 14:15:53 -0800 Subject: [PATCH] go/types, types2: report error for range over int if Go version < 1.22 Fixes #64704. Change-Id: Ied3af46ab534343cdafba5ee27680b9c6ef3d37a Reviewed-on: https://go-review.googlesource.com/c/go/+/549459 Auto-Submit: Robert Griesemer Reviewed-by: Robert Griesemer Run-TryBot: Robert Griesemer Reviewed-by: Alan Donovan TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/stmt.go | 20 +++++++++++++------ src/cmd/compile/internal/types2/version.go | 1 + src/go/types/stmt.go | 17 +++++++++++----- src/go/types/version.go | 1 + .../types/testdata/fixedbugs/issue64704.go | 12 +++++++++++ 5 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 src/internal/types/testdata/fixedbugs/issue64704.go diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 7956bf3033674..a07bc9370a609 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -861,7 +861,9 @@ 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) + k, v, cause, isFunc, ok := rangeKeyVal(x.typ, func(v goVersion) bool { + return check.allowVersion(check.pkg, x.expr, v) + }) switch { case !ok && cause != "": check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause) @@ -964,16 +966,18 @@ 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) + key, val, _, _, _ := rangeKeyVal(typ, nil) return key, val } // rangeKeyVal returns the key and value type produced by a range clause -// over an expression of type typ. 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) (key, val Type, cause string, isFunc, ok bool) { +// over an expression of type typ. +// 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 } @@ -991,6 +995,9 @@ func rangeKeyVal(typ Type) (key, val Type, cause string, isFunc, ok bool) { return Typ[Int], universeRune, "", false, 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 } case *Array: @@ -1005,6 +1012,7 @@ func rangeKeyVal(typ Type) (key, val Type, cause string, isFunc, ok bool) { } return typ.elem, nil, "", false, true case *Signature: + // TODO(gri) when this becomes enabled permanently, add version check if !buildcfg.Experiment.RangeFunc { break } diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go index 12c86ef9fe621..5aa3c803b5499 100644 --- a/src/cmd/compile/internal/types2/version.go +++ b/src/cmd/compile/internal/types2/version.go @@ -43,6 +43,7 @@ var ( go1_18 = asGoVersion("go1.18") go1_20 = asGoVersion("go1.20") go1_21 = asGoVersion("go1.21") + go1_22 = asGoVersion("go1.22") // current (deployed) Go version go_current = asGoVersion(fmt.Sprintf("go1.%d", goversion.Version)) diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index 288d74b95a4fa..35c485827d23f 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -852,7 +852,9 @@ 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) + k, v, cause, isFunc, ok := rangeKeyVal(x.typ, func(v goVersion) bool { + return check.allowVersion(check.pkg, x.expr, v) + }) switch { case !ok && cause != "": check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause) @@ -955,10 +957,11 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) { } // rangeKeyVal returns the key and value type produced by a range clause -// over an expression of type typ. 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) (key, val Type, cause string, isFunc, ok bool) { +// over an expression of type typ. +// 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 } @@ -976,6 +979,9 @@ func rangeKeyVal(typ Type) (key, val Type, cause string, isFunc, ok bool) { return Typ[Int], universeRune, "", false, 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 } case *Array: @@ -990,6 +996,7 @@ func rangeKeyVal(typ Type) (key, val Type, cause string, isFunc, ok bool) { } return typ.elem, nil, "", false, true case *Signature: + // TODO(gri) when this becomes enabled permanently, add version check if !buildcfg.Experiment.RangeFunc { break } diff --git a/src/go/types/version.go b/src/go/types/version.go index cfbab0f2a8b95..f2466edc1fa1c 100644 --- a/src/go/types/version.go +++ b/src/go/types/version.go @@ -44,6 +44,7 @@ var ( go1_18 = asGoVersion("go1.18") go1_20 = asGoVersion("go1.20") go1_21 = asGoVersion("go1.21") + go1_22 = asGoVersion("go1.22") // current (deployed) Go version go_current = asGoVersion(fmt.Sprintf("go1.%d", goversion.Version)) diff --git a/src/internal/types/testdata/fixedbugs/issue64704.go b/src/internal/types/testdata/fixedbugs/issue64704.go new file mode 100644 index 0000000000000..c8e9056cdd525 --- /dev/null +++ b/src/internal/types/testdata/fixedbugs/issue64704.go @@ -0,0 +1,12 @@ +// -lang=go1.21 + +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func _() { + for range 10 /* ERROR "cannot range over 10 (untyped int constant): requires go1.22 or later" */ { + } +}