go/types, types2: report error for floating-point iteration variable

While at it, slightly improve documentation and code.
Also, add additional test cases for #66561.

Updates #66561.
Fixes #67027.

Change-Id: I682b0e9227e065d6bbd199871c2e1ecff13edc66
Reviewed-on: https://go-review.googlesource.com/c/go/+/580937
Reviewed-by: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2024-04-22 15:52:59 -07:00 committed by Gopher Robot
parent a523152ea1
commit 26ed0528c7
4 changed files with 110 additions and 30 deletions

View File

@ -898,7 +898,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
lhs := [2]Expr{sKey, sValue} // sKey, sValue may be nil
rhs := [2]Type{key, val} // key, val may be nil
constIntRange := x.mode == constant_ && isInteger(x.typ)
rangeOverInt := isInteger(x.typ)
if isDef {
// short variable declaration
@ -933,14 +933,15 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
continue
}
// initialize lhs variable
if constIntRange {
if rangeOverInt {
assert(i == 0) // at most one iteration variable (rhs[1] == nil for rangeOverInt)
check.initVar(obj, &x, "range clause")
} else {
x.mode = value
x.expr = lhs // we don't have a better rhs expression to use here
x.typ = typ
check.initVar(obj, &x, "assignment") // error is on variable, use "assignment" not "range clause"
var y operand
y.mode = value
y.expr = lhs // we don't have a better rhs expression to use here
y.typ = typ
check.initVar(obj, &y, "assignment") // error is on variable, use "assignment" not "range clause"
}
assert(obj.typ != nil)
}
@ -967,21 +968,30 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
continue
}
if constIntRange {
if rangeOverInt {
assert(i == 0) // at most one iteration variable (rhs[1] == nil for rangeOverInt)
check.assignVar(lhs, nil, &x, "range clause")
// If the assignment succeeded, if x was untyped before, it now
// has a type inferred via the assignment. It must be an integer.
// (go.dev/issues/67027)
if x.mode != invalid && !isInteger(x.typ) {
check.softErrorf(lhs, InvalidRangeExpr, "cannot use iteration variable of type %s", x.typ)
}
} else {
x.mode = value
x.expr = lhs // we don't have a better rhs expression to use here
x.typ = typ
check.assignVar(lhs, nil, &x, "assignment") // error is on variable, use "assignment" not "range clause"
var y operand
y.mode = value
y.expr = lhs // we don't have a better rhs expression to use here
y.typ = typ
check.assignVar(lhs, nil, &y, "assignment") // error is on variable, use "assignment" not "range clause"
}
}
} else if constIntRange {
} else if rangeOverInt {
// If we don't have any iteration variables, we still need to
// check that a (possibly untyped) integer range expression x
// is valid.
// We do this by checking the assignment _ = x. This ensures
// that an untyped x can be converted to a value of type int.
// that an untyped x can be converted to a value of its default
// type (rune or int).
check.assignment(&x, nil, "range clause")
}

View File

@ -898,7 +898,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
lhs := [2]Expr{sKey, sValue} // sKey, sValue may be nil
rhs := [2]Type{key, val} // key, val may be nil
constIntRange := x.mode == constant_ && isInteger(x.typ)
rangeOverInt := isInteger(x.typ)
if isDef {
// short variable declaration
@ -933,14 +933,15 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
continue
}
// initialize lhs variable
if constIntRange {
if rangeOverInt {
assert(i == 0) // at most one iteration variable (rhs[1] == nil for rangeOverInt)
check.initVar(obj, &x, "range clause")
} else {
x.mode = value
x.expr = lhs // we don't have a better rhs expression to use here
x.typ = typ
check.initVar(obj, &x, "assignment") // error is on variable, use "assignment" not "range clause"
var y operand
y.mode = value
y.expr = lhs // we don't have a better rhs expression to use here
y.typ = typ
check.initVar(obj, &y, "assignment") // error is on variable, use "assignment" not "range clause"
}
assert(obj.typ != nil)
}
@ -967,21 +968,30 @@ func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
continue
}
if constIntRange {
if rangeOverInt {
assert(i == 0) // at most one iteration variable (rhs[1] == nil for rangeOverInt)
check.assignVar(lhs, nil, &x, "range clause")
// If the assignment succeeded, if x was untyped before, it now
// has a type inferred via the assignment. It must be an integer.
// (go.dev/issues/67027)
if x.mode != invalid && !isInteger(x.typ) {
check.softErrorf(lhs, InvalidRangeExpr, "cannot use iteration variable of type %s", x.typ)
}
} else {
x.mode = value
x.expr = lhs // we don't have a better rhs expression to use here
x.typ = typ
check.assignVar(lhs, nil, &x, "assignment") // error is on variable, use "assignment" not "range clause"
var y operand
y.mode = value
y.expr = lhs // we don't have a better rhs expression to use here
y.typ = typ
check.assignVar(lhs, nil, &y, "assignment") // error is on variable, use "assignment" not "range clause"
}
}
} else if constIntRange {
} else if rangeOverInt {
// If we don't have any iteration variables, we still need to
// check that a (possibly untyped) integer range expression x
// is valid.
// We do this by checking the assignment _ = x. This ensures
// that an untyped x can be converted to a value of type int.
// that an untyped x can be converted to a value of its default
// type (rune or int).
check.assignment(&x, nil, "range clause")
}
@ -1011,6 +1021,7 @@ func rangeKeyVal(typ Type, allowVersion func(goVersion) bool) (key, val Type, ca
return Typ[Int], universeRune, "", false, 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")
}

View File

@ -953,10 +953,10 @@ func issue10148() {
for y /* ERROR "declared and not used" */ := range "" {
_ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1
}
for range 1.5 /* ERROR "cannot range over 1.5" */ {
for range 1.5 /* ERROR "cannot range over 1.5 (untyped float constant)" */ {
_ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1
}
for y := range 1.5 /* ERROR "cannot range over 1.5" */ {
for y := range 1.5 /* ERROR "cannot range over 1.5 (untyped float constant)" */ {
_ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1
}
}

View File

@ -129,3 +129,62 @@ func issue65133() {
for u8 = range 256 /* ERROR "cannot use 256 (untyped int constant) as uint8 value in range clause (overflows)" */ {
}
}
func issue64471() {
for i := range 'a' {
var _ *rune = &i // ensure i has type rune
}
}
func issue66561() {
for range 10.0 /* ERROR "cannot range over 10.0 (untyped float constant 10)" */ {
}
for range 1e3 /* ERROR "cannot range over 1e3 (untyped float constant 1000)" */ {
}
for range 1 /* ERROR "cannot range over 1 + 0i (untyped complex constant (1 + 0i))" */ + 0i {
}
for range 1.1 /* ERROR "cannot range over 1.1 (untyped float constant)" */ {
}
for range 1i /* ERROR "cannot range over 1i (untyped complex constant (0 + 1i))" */ {
}
for i := range 10.0 /* ERROR "cannot range over 10.0 (untyped float constant 10)" */ {
_ = i
}
for i := range 1e3 /* ERROR "cannot range over 1e3 (untyped float constant 1000)" */ {
_ = i
}
for i := range 1 /* ERROR "cannot range over 1 + 0i (untyped complex constant (1 + 0i))" */ + 0i {
_ = i
}
for i := range 1.1 /* ERROR "cannot range over 1.1 (untyped float constant)" */ {
_ = i
}
for i := range 1i /* ERROR "cannot range over 1i (untyped complex constant (0 + 1i))" */ {
_ = i
}
var j float64
_ = j
for j /* ERROR "cannot use iteration variable of type float64" */ = range 1 {
}
for j = range 1.1 /* ERROR "cannot range over 1.1 (untyped float constant)" */ {
}
for j = range 10.0 /* ERROR "cannot range over 10.0 (untyped float constant 10)" */ {
}
// There shouldn't be assignment errors if there are more iteration variables than permitted.
var i int
_ = i
for i, j /* ERROR "range over 10 (untyped int constant) permits only one iteration variable" */ = range 10 {
}
}
func issue67027() {
var i float64
_ = i
for i /* ERROR "cannot use iteration variable of type float64" */ = range 10 {
}
}