From 26ed0528c76d10883e0d24ca8b7bb87e62dc4487 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Mon, 22 Apr 2024 15:52:59 -0700 Subject: [PATCH] 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Auto-Submit: Robert Griesemer --- src/cmd/compile/internal/types2/stmt.go | 38 +++++++----- src/go/types/stmt.go | 39 +++++++----- src/internal/types/testdata/check/stmt0.go | 4 +- src/internal/types/testdata/spec/range_int.go | 59 +++++++++++++++++++ 4 files changed, 110 insertions(+), 30 deletions(-) diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 1984777008..655d072171 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -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") } diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index bfb51fd2e5..258ad1d327 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -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") } diff --git a/src/internal/types/testdata/check/stmt0.go b/src/internal/types/testdata/check/stmt0.go index a6c47cb483..ea161279c6 100644 --- a/src/internal/types/testdata/check/stmt0.go +++ b/src/internal/types/testdata/check/stmt0.go @@ -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 } } diff --git a/src/internal/types/testdata/spec/range_int.go b/src/internal/types/testdata/spec/range_int.go index 7f722e2d99..766736cc15 100644 --- a/src/internal/types/testdata/spec/range_int.go +++ b/src/internal/types/testdata/spec/range_int.go @@ -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 { + } +} -- 2.48.1