]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: factor out shared for-range checking code into range.go
authorRobert Griesemer <gri@golang.org>
Thu, 6 Mar 2025 17:58:51 +0000 (09:58 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 6 Mar 2025 21:44:34 +0000 (13:44 -0800)
For go/types, generate its range.go file from the corresponding types2 file.

Change-Id: Iaff3ecbf1c536143c92f7b50e2461140469f9280
Reviewed-on: https://go-review.googlesource.com/c/go/+/655536
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: 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>
src/cmd/compile/internal/types2/range.go [new file with mode: 0644]
src/cmd/compile/internal/types2/stmt.go
src/go/types/generate_test.go
src/go/types/range.go [new file with mode: 0644]
src/go/types/stmt.go

diff --git a/src/cmd/compile/internal/types2/range.go b/src/cmd/compile/internal/types2/range.go
new file mode 100644 (file)
index 0000000..86626ce
--- /dev/null
@@ -0,0 +1,247 @@
+// Copyright 2025 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.
+
+// This file implements typechecking of range statements.
+
+package types2
+
+import (
+       "cmd/compile/internal/syntax"
+       "internal/buildcfg"
+       . "internal/types/errors"
+)
+
+// rangeStmt type-checks a range statement of form
+//
+//     for sKey, sValue = range rangeVar { ... }
+//
+// where sKey, sValue, sExtra may be nil. isDef indicates whether these
+// variables are assigned to only (=) or whether there is a short variable
+// declaration (:=). If the latter and there are no variables, an error is
+// reported at noNewVarPos.
+func (check *Checker) rangeStmt(inner stmtContext, rangeStmt *syntax.ForStmt, noNewVarPos poser, sKey, sValue, sExtra, rangeVar syntax.Expr, isDef bool) {
+       // check expression to iterate over
+       var x operand
+       check.expr(nil, &x, rangeVar)
+
+       // determine key/value types
+       var key, val Type
+       if x.mode != invalid {
+               k, v, cause, ok := rangeKeyVal(check, x.typ, func(v goVersion) bool {
+                       return check.allowVersion(v)
+               })
+               switch {
+               case !ok && cause != "":
+                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
+               case !ok:
+                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
+               case k == nil && sKey != nil:
+                       check.softErrorf(sKey, InvalidIterVar, "range over %s permits no iteration variables", &x)
+               case v == nil && sValue != nil:
+                       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")
+               }
+               key, val = k, v
+       }
+
+       // Open the for-statement block scope now, after the range clause.
+       // Iteration variables declared with := need to go in this scope (was go.dev/issue/51437).
+       check.openScope(rangeStmt, "range")
+       defer check.closeScope()
+
+       // check assignment to/declaration of iteration variables
+       // (irregular assignment, cannot easily map to existing assignment checks)
+
+       // lhs expressions and initialization value (rhs) types
+       lhs := [2]syntax.Expr{sKey, sValue} // sKey, sValue may be nil
+       rhs := [2]Type{key, val}            // key, val may be nil
+
+       rangeOverInt := isInteger(x.typ)
+
+       if isDef {
+               // short variable declaration
+               var vars []*Var
+               for i, lhs := range lhs {
+                       if lhs == nil {
+                               continue
+                       }
+
+                       // determine lhs variable
+                       var obj *Var
+                       if ident, _ := lhs.(*syntax.Name); ident != nil {
+                               // declare new variable
+                               name := ident.Value
+                               obj = newVar(LocalVar, ident.Pos(), check.pkg, name, nil)
+                               check.recordDef(ident, obj)
+                               // _ variables don't count as new variables
+                               if name != "_" {
+                                       vars = append(vars, obj)
+                               }
+                       } else {
+                               check.errorf(lhs, InvalidSyntaxTree, "cannot declare %s", lhs)
+                               obj = newVar(LocalVar, lhs.Pos(), check.pkg, "_", nil) // dummy variable
+                       }
+                       assert(obj.typ == nil)
+
+                       // initialize lhs iteration variable, if any
+                       typ := rhs[i]
+                       if typ == nil || typ == Typ[Invalid] {
+                               // typ == Typ[Invalid] can happen if allowVersion fails.
+                               obj.typ = Typ[Invalid]
+                               check.usedVars[obj] = true // don't complain about unused variable
+                               continue
+                       }
+
+                       if rangeOverInt {
+                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] for rangeOverInt)
+                               check.initVar(obj, &x, "range clause")
+                       } else {
+                               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)
+               }
+
+               // declare variables
+               if len(vars) > 0 {
+                       scopePos := rangeStmt.Body.Pos()
+                       for _, obj := range vars {
+                               check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
+                       }
+               } else {
+                       check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
+               }
+       } else if sKey != nil /* lhs[0] != nil */ {
+               // ordinary assignment
+               for i, lhs := range lhs {
+                       if lhs == nil {
+                               continue
+                       }
+
+                       // assign to lhs iteration variable, if any
+                       typ := rhs[i]
+                       if typ == nil || typ == Typ[Invalid] {
+                               continue
+                       }
+
+                       if rangeOverInt {
+                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] 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 {
+                               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 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 its default
+               // type (rune or int).
+               check.assignment(&x, nil, "range clause")
+       }
+
+       check.stmt(inner, rangeStmt.Body)
+}
+
+// rangeKeyVal returns the key and value type produced by a range clause
+// over an expression of type orig.
+// 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.
+// The check parameter is only used in case of an error; it may be nil.
+func rangeKeyVal(check *Checker, orig 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
+       }
+
+       rtyp, err := commonUnder(orig, func(t, u Type) *typeError {
+               // A channel must permit receive operations.
+               if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
+                       return typeErrorf("receive from send-only channel %s", t)
+               }
+               return nil
+       })
+       if rtyp == nil {
+               return bad(err.format(check))
+       }
+
+       switch typ := arrayPtrDeref(rtyp).(type) {
+       case *Basic:
+               if isString(typ) {
+                       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, "", true
+               }
+       case *Array:
+               return Typ[Int], typ.elem, "", true
+       case *Slice:
+               return Typ[Int], typ.elem, "", true
+       case *Map:
+               return typ.key, typ.elem, "", true
+       case *Chan:
+               assert(typ.dir != SendOnly)
+               return typ.elem, nil, "", true
+       case *Signature:
+               if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
+                       return bad("requires go1.23 or later")
+               }
+               // check iterator arity
+               switch {
+               case typ.Params().Len() != 1:
+                       return bad("func must be func(yield func(...) bool): wrong argument count")
+               case typ.Results().Len() != 0:
+                       return bad("func must be func(yield func(...) bool): unexpected results")
+               }
+               assert(typ.Recv() == nil)
+               // check iterator argument type
+               u, err := commonUnder(typ.Params().At(0).Type(), nil)
+               cb, _ := u.(*Signature)
+               switch {
+               case cb == nil:
+                       if err != nil {
+                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
+                       } else {
+                               return bad("func must be func(yield func(...) bool): argument is not func")
+                       }
+               case cb.Params().Len() > 2:
+                       return bad("func must be func(yield func(...) bool): yield func has too many parameters")
+               case cb.Results().Len() != 1 || !Identical(cb.Results().At(0).Type(), universeBool):
+                       // see go.dev/issues/71131, go.dev/issues/71164
+                       if cb.Results().Len() == 1 && isBoolean(cb.Results().At(0).Type()) {
+                               return bad("func must be func(yield func(...) bool): yield func returns user-defined boolean, not bool")
+                       } else {
+                               return bad("func must be func(yield func(...) bool): yield func does not return bool")
+                       }
+               }
+               assert(cb.Recv() == nil)
+               // determine key and value types, if any
+               if cb.Params().Len() >= 1 {
+                       key = cb.Params().At(0).Type()
+               }
+               if cb.Params().Len() >= 2 {
+                       val = cb.Params().At(1).Type()
+               }
+               return key, val, "", true
+       }
+       return
+}
index 79cc0150d4af9dd4dae3282b784a53c48958247b..efe9c99d87625f4808ce98de703e2bc756f8b641 100644 (file)
@@ -9,7 +9,6 @@ package types2
 import (
        "cmd/compile/internal/syntax"
        "go/constant"
-       "internal/buildcfg"
        . "internal/types/errors"
        "slices"
 )
@@ -668,7 +667,23 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) {
                inner |= breakOk | continueOk
 
                if rclause, _ := s.Init.(*syntax.RangeClause); rclause != nil {
-                       check.rangeStmt(inner, s, rclause)
+                       // extract sKey, sValue, s.Extra from the range clause
+                       sKey := rclause.Lhs            // possibly nil
+                       var sValue, sExtra syntax.Expr // possibly nil
+                       if p, _ := sKey.(*syntax.ListExpr); p != nil {
+                               if len(p.ElemList) < 2 {
+                                       check.error(s, InvalidSyntaxTree, "invalid lhs in range clause")
+                                       return
+                               }
+                               // len(p.ElemList) >= 2
+                               sKey = p.ElemList[0]
+                               sValue = p.ElemList[1]
+                               if len(p.ElemList) > 2 {
+                                       // delay error reporting until we know more
+                                       sExtra = p.ElemList[2]
+                               }
+                       }
+                       check.rangeStmt(inner, s, s, sKey, sValue, sExtra, rclause.X, rclause.Def)
                        break
                }
 
@@ -825,257 +840,3 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
                }
        }
 }
-
-func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *syntax.RangeClause) {
-       // Convert syntax form to local variables.
-       type Expr = syntax.Expr
-       type identType = syntax.Name
-       identName := func(n *identType) string { return n.Value }
-       sKey := rclause.Lhs // possibly nil
-       var sValue, sExtra syntax.Expr
-       if p, _ := sKey.(*syntax.ListExpr); p != nil {
-               if len(p.ElemList) < 2 {
-                       check.error(s, InvalidSyntaxTree, "invalid lhs in range clause")
-                       return
-               }
-               // len(p.ElemList) >= 2
-               sKey = p.ElemList[0]
-               sValue = p.ElemList[1]
-               if len(p.ElemList) > 2 {
-                       // delay error reporting until we know more
-                       sExtra = p.ElemList[2]
-               }
-       }
-       isDef := rclause.Def
-       rangeVar := rclause.X
-       noNewVarPos := s
-
-       // Do not use rclause anymore.
-       rclause = nil
-
-       // Everything from here on is shared between cmd/compile/internal/types2 and go/types.
-
-       // check expression to iterate over
-       var x operand
-       check.expr(nil, &x, rangeVar)
-
-       // determine key/value types
-       var key, val Type
-       if x.mode != invalid {
-               k, v, cause, ok := rangeKeyVal(check, x.typ, func(v goVersion) bool {
-                       return check.allowVersion(v)
-               })
-               switch {
-               case !ok && cause != "":
-                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
-               case !ok:
-                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
-               case k == nil && sKey != nil:
-                       check.softErrorf(sKey, InvalidIterVar, "range over %s permits no iteration variables", &x)
-               case v == nil && sValue != nil:
-                       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")
-               }
-               key, val = k, v
-       }
-
-       // Open the for-statement block scope now, after the range clause.
-       // Iteration variables declared with := need to go in this scope (was go.dev/issue/51437).
-       check.openScope(s, "range")
-       defer check.closeScope()
-
-       // check assignment to/declaration of iteration variables
-       // (irregular assignment, cannot easily map to existing assignment checks)
-
-       // lhs expressions and initialization value (rhs) types
-       lhs := [2]Expr{sKey, sValue} // sKey, sValue may be nil
-       rhs := [2]Type{key, val}     // key, val may be nil
-
-       rangeOverInt := isInteger(x.typ)
-
-       if isDef {
-               // short variable declaration
-               var vars []*Var
-               for i, lhs := range lhs {
-                       if lhs == nil {
-                               continue
-                       }
-
-                       // determine lhs variable
-                       var obj *Var
-                       if ident, _ := lhs.(*identType); ident != nil {
-                               // declare new variable
-                               name := identName(ident)
-                               obj = newVar(LocalVar, ident.Pos(), check.pkg, name, nil)
-                               check.recordDef(ident, obj)
-                               // _ variables don't count as new variables
-                               if name != "_" {
-                                       vars = append(vars, obj)
-                               }
-                       } else {
-                               check.errorf(lhs, InvalidSyntaxTree, "cannot declare %s", lhs)
-                               obj = newVar(LocalVar, lhs.Pos(), check.pkg, "_", nil) // dummy variable
-                       }
-                       assert(obj.typ == nil)
-
-                       // initialize lhs iteration variable, if any
-                       typ := rhs[i]
-                       if typ == nil || typ == Typ[Invalid] {
-                               // typ == Typ[Invalid] can happen if allowVersion fails.
-                               obj.typ = Typ[Invalid]
-                               check.usedVars[obj] = true // don't complain about unused variable
-                               continue
-                       }
-
-                       if rangeOverInt {
-                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] for rangeOverInt)
-                               check.initVar(obj, &x, "range clause")
-                       } else {
-                               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)
-               }
-
-               // declare variables
-               if len(vars) > 0 {
-                       scopePos := s.Body.Pos()
-                       for _, obj := range vars {
-                               check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
-                       }
-               } else {
-                       check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
-               }
-       } else if sKey != nil /* lhs[0] != nil */ {
-               // ordinary assignment
-               for i, lhs := range lhs {
-                       if lhs == nil {
-                               continue
-                       }
-
-                       // assign to lhs iteration variable, if any
-                       typ := rhs[i]
-                       if typ == nil || typ == Typ[Invalid] {
-                               continue
-                       }
-
-                       if rangeOverInt {
-                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] 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 {
-                               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 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 its default
-               // type (rune or int).
-               check.assignment(&x, nil, "range clause")
-       }
-
-       check.stmt(inner, s.Body)
-}
-
-// rangeKeyVal returns the key and value type produced by a range clause
-// over an expression of type orig.
-// 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.
-// The check parameter is only used in case of an error; it may be nil.
-func rangeKeyVal(check *Checker, orig 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
-       }
-
-       rtyp, err := commonUnder(orig, func(t, u Type) *typeError {
-               // A channel must permit receive operations.
-               if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
-                       return typeErrorf("receive from send-only channel %s", t)
-               }
-               return nil
-       })
-       if rtyp == nil {
-               return bad(err.format(check))
-       }
-
-       switch typ := arrayPtrDeref(rtyp).(type) {
-       case *Basic:
-               if isString(typ) {
-                       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, "", true
-               }
-       case *Array:
-               return Typ[Int], typ.elem, "", true
-       case *Slice:
-               return Typ[Int], typ.elem, "", true
-       case *Map:
-               return typ.key, typ.elem, "", true
-       case *Chan:
-               assert(typ.dir != SendOnly)
-               return typ.elem, nil, "", true
-       case *Signature:
-               if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
-                       return bad("requires go1.23 or later")
-               }
-               // check iterator arity
-               switch {
-               case typ.Params().Len() != 1:
-                       return bad("func must be func(yield func(...) bool): wrong argument count")
-               case typ.Results().Len() != 0:
-                       return bad("func must be func(yield func(...) bool): unexpected results")
-               }
-               assert(typ.Recv() == nil)
-               // check iterator argument type
-               u, err := commonUnder(typ.Params().At(0).Type(), nil)
-               cb, _ := u.(*Signature)
-               switch {
-               case cb == nil:
-                       if err != nil {
-                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
-                       } else {
-                               return bad("func must be func(yield func(...) bool): argument is not func")
-                       }
-               case cb.Params().Len() > 2:
-                       return bad("func must be func(yield func(...) bool): yield func has too many parameters")
-               case cb.Results().Len() != 1 || !Identical(cb.Results().At(0).Type(), universeBool):
-                       // see go.dev/issues/71131, go.dev/issues/71164
-                       if cb.Results().Len() == 1 && isBoolean(cb.Results().At(0).Type()) {
-                               return bad("func must be func(yield func(...) bool): yield func returns user-defined boolean, not bool")
-                       } else {
-                               return bad("func must be func(yield func(...) bool): yield func does not return bool")
-                       }
-               }
-               assert(cb.Recv() == nil)
-               // determine key and value types, if any
-               if cb.Params().Len() >= 1 {
-                       key = cb.Params().At(0).Type()
-               }
-               if cb.Params().Len() >= 2 {
-                       val = cb.Params().At(1).Type()
-               }
-               return key, val, "", true
-       }
-       return
-}
index 62e17a957a5c84de0b19fb90189a5d185f973bb2..e5e0874d17f9072789592d2fb852d59a3a0b988c 100644 (file)
@@ -171,6 +171,11 @@ var filemap = map[string]action{
        "package.go":    nil,
        "pointer.go":    nil,
        "predicates.go": nil,
+       "range.go": func(f *ast.File) {
+               renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
+               renameSelectorExprs(f, "syntax.Name->ast.Ident", "syntax.ForStmt->ast.RangeStmt", "ident.Value->ident.Name") // must happen before renaming identifiers
+               renameIdents(f, "syntax->ast", "poser->positioner")
+       },
        "recording.go": func(f *ast.File) {
                renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
                renameSelectorExprs(f, "syntax.Name->ast.Ident") // must happen before renaming identifiers
diff --git a/src/go/types/range.go b/src/go/types/range.go
new file mode 100644 (file)
index 0000000..5c80463
--- /dev/null
@@ -0,0 +1,250 @@
+// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
+// Source: ../../cmd/compile/internal/types2/range.go
+
+// Copyright 2025 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.
+
+// This file implements typechecking of range statements.
+
+package types
+
+import (
+       "go/ast"
+       "internal/buildcfg"
+       . "internal/types/errors"
+)
+
+// rangeStmt type-checks a range statement of form
+//
+//     for sKey, sValue = range rangeVar { ... }
+//
+// where sKey, sValue, sExtra may be nil. isDef indicates whether these
+// variables are assigned to only (=) or whether there is a short variable
+// declaration (:=). If the latter and there are no variables, an error is
+// reported at noNewVarPos.
+func (check *Checker) rangeStmt(inner stmtContext, rangeStmt *ast.RangeStmt, noNewVarPos positioner, sKey, sValue, sExtra, rangeVar ast.Expr, isDef bool) {
+       // check expression to iterate over
+       var x operand
+       check.expr(nil, &x, rangeVar)
+
+       // determine key/value types
+       var key, val Type
+       if x.mode != invalid {
+               k, v, cause, ok := rangeKeyVal(check, x.typ, func(v goVersion) bool {
+                       return check.allowVersion(v)
+               })
+               switch {
+               case !ok && cause != "":
+                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
+               case !ok:
+                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
+               case k == nil && sKey != nil:
+                       check.softErrorf(sKey, InvalidIterVar, "range over %s permits no iteration variables", &x)
+               case v == nil && sValue != nil:
+                       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")
+               }
+               key, val = k, v
+       }
+
+       // Open the for-statement block scope now, after the range clause.
+       // Iteration variables declared with := need to go in this scope (was go.dev/issue/51437).
+       check.openScope(rangeStmt, "range")
+       defer check.closeScope()
+
+       // check assignment to/declaration of iteration variables
+       // (irregular assignment, cannot easily map to existing assignment checks)
+
+       // lhs expressions and initialization value (rhs) types
+       lhs := [2]ast.Expr{sKey, sValue} // sKey, sValue may be nil
+       rhs := [2]Type{key, val}         // key, val may be nil
+
+       rangeOverInt := isInteger(x.typ)
+
+       if isDef {
+               // short variable declaration
+               var vars []*Var
+               for i, lhs := range lhs {
+                       if lhs == nil {
+                               continue
+                       }
+
+                       // determine lhs variable
+                       var obj *Var
+                       if ident, _ := lhs.(*ast.Ident); ident != nil {
+                               // declare new variable
+                               name := ident.Name
+                               obj = newVar(LocalVar, ident.Pos(), check.pkg, name, nil)
+                               check.recordDef(ident, obj)
+                               // _ variables don't count as new variables
+                               if name != "_" {
+                                       vars = append(vars, obj)
+                               }
+                       } else {
+                               check.errorf(lhs, InvalidSyntaxTree, "cannot declare %s", lhs)
+                               obj = newVar(LocalVar, lhs.Pos(), check.pkg, "_", nil) // dummy variable
+                       }
+                       assert(obj.typ == nil)
+
+                       // initialize lhs iteration variable, if any
+                       typ := rhs[i]
+                       if typ == nil || typ == Typ[Invalid] {
+                               // typ == Typ[Invalid] can happen if allowVersion fails.
+                               obj.typ = Typ[Invalid]
+                               check.usedVars[obj] = true // don't complain about unused variable
+                               continue
+                       }
+
+                       if rangeOverInt {
+                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] for rangeOverInt)
+                               check.initVar(obj, &x, "range clause")
+                       } else {
+                               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)
+               }
+
+               // declare variables
+               if len(vars) > 0 {
+                       scopePos := rangeStmt.Body.Pos()
+                       for _, obj := range vars {
+                               check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
+                       }
+               } else {
+                       check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
+               }
+       } else if sKey != nil /* lhs[0] != nil */ {
+               // ordinary assignment
+               for i, lhs := range lhs {
+                       if lhs == nil {
+                               continue
+                       }
+
+                       // assign to lhs iteration variable, if any
+                       typ := rhs[i]
+                       if typ == nil || typ == Typ[Invalid] {
+                               continue
+                       }
+
+                       if rangeOverInt {
+                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] 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 {
+                               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 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 its default
+               // type (rune or int).
+               check.assignment(&x, nil, "range clause")
+       }
+
+       check.stmt(inner, rangeStmt.Body)
+}
+
+// rangeKeyVal returns the key and value type produced by a range clause
+// over an expression of type orig.
+// 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.
+// The check parameter is only used in case of an error; it may be nil.
+func rangeKeyVal(check *Checker, orig 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
+       }
+
+       rtyp, err := commonUnder(orig, func(t, u Type) *typeError {
+               // A channel must permit receive operations.
+               if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
+                       return typeErrorf("receive from send-only channel %s", t)
+               }
+               return nil
+       })
+       if rtyp == nil {
+               return bad(err.format(check))
+       }
+
+       switch typ := arrayPtrDeref(rtyp).(type) {
+       case *Basic:
+               if isString(typ) {
+                       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, "", true
+               }
+       case *Array:
+               return Typ[Int], typ.elem, "", true
+       case *Slice:
+               return Typ[Int], typ.elem, "", true
+       case *Map:
+               return typ.key, typ.elem, "", true
+       case *Chan:
+               assert(typ.dir != SendOnly)
+               return typ.elem, nil, "", true
+       case *Signature:
+               if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
+                       return bad("requires go1.23 or later")
+               }
+               // check iterator arity
+               switch {
+               case typ.Params().Len() != 1:
+                       return bad("func must be func(yield func(...) bool): wrong argument count")
+               case typ.Results().Len() != 0:
+                       return bad("func must be func(yield func(...) bool): unexpected results")
+               }
+               assert(typ.Recv() == nil)
+               // check iterator argument type
+               u, err := commonUnder(typ.Params().At(0).Type(), nil)
+               cb, _ := u.(*Signature)
+               switch {
+               case cb == nil:
+                       if err != nil {
+                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
+                       } else {
+                               return bad("func must be func(yield func(...) bool): argument is not func")
+                       }
+               case cb.Params().Len() > 2:
+                       return bad("func must be func(yield func(...) bool): yield func has too many parameters")
+               case cb.Results().Len() != 1 || !Identical(cb.Results().At(0).Type(), universeBool):
+                       // see go.dev/issues/71131, go.dev/issues/71164
+                       if cb.Results().Len() == 1 && isBoolean(cb.Results().At(0).Type()) {
+                               return bad("func must be func(yield func(...) bool): yield func returns user-defined boolean, not bool")
+                       } else {
+                               return bad("func must be func(yield func(...) bool): yield func does not return bool")
+                       }
+               }
+               assert(cb.Recv() == nil)
+               // determine key and value types, if any
+               if cb.Params().Len() >= 1 {
+                       key = cb.Params().At(0).Type()
+               }
+               if cb.Params().Len() >= 2 {
+                       val = cb.Params().At(1).Type()
+               }
+               return key, val, "", true
+       }
+       return
+}
index 68f31fef652aed2eebc9cd693ccc6023391fecb6..398075eaf3730095ce3f3c58fe869d296edc094d 100644 (file)
@@ -10,7 +10,6 @@ import (
        "go/ast"
        "go/constant"
        "go/token"
-       "internal/buildcfg"
        . "internal/types/errors"
        "slices"
 )
@@ -856,247 +855,9 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
 
        case *ast.RangeStmt:
                inner |= breakOk | continueOk
-               check.rangeStmt(inner, s)
+               check.rangeStmt(inner, s, inNode(s, s.TokPos), s.Key, s.Value, nil, s.X, s.Tok == token.DEFINE)
 
        default:
                check.error(s, InvalidSyntaxTree, "invalid statement")
        }
 }
-
-func (check *Checker) rangeStmt(inner stmtContext, s *ast.RangeStmt) {
-       // Convert go/ast form to local variables.
-       type Expr = ast.Expr
-       type identType = ast.Ident
-       identName := func(n *identType) string { return n.Name }
-       sKey, sValue := s.Key, s.Value
-       var sExtra ast.Expr = nil // (used only in types2 fork)
-       isDef := s.Tok == token.DEFINE
-       rangeVar := s.X
-       noNewVarPos := inNode(s, s.TokPos)
-
-       // Everything from here on is shared between cmd/compile/internal/types2 and go/types.
-
-       // check expression to iterate over
-       var x operand
-       check.expr(nil, &x, rangeVar)
-
-       // determine key/value types
-       var key, val Type
-       if x.mode != invalid {
-               k, v, cause, ok := rangeKeyVal(check, x.typ, func(v goVersion) bool {
-                       return check.allowVersion(v)
-               })
-               switch {
-               case !ok && cause != "":
-                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
-               case !ok:
-                       check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
-               case k == nil && sKey != nil:
-                       check.softErrorf(sKey, InvalidIterVar, "range over %s permits no iteration variables", &x)
-               case v == nil && sValue != nil:
-                       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")
-               }
-               key, val = k, v
-       }
-
-       // Open the for-statement block scope now, after the range clause.
-       // Iteration variables declared with := need to go in this scope (was go.dev/issue/51437).
-       check.openScope(s, "range")
-       defer check.closeScope()
-
-       // check assignment to/declaration of iteration variables
-       // (irregular assignment, cannot easily map to existing assignment checks)
-
-       // lhs expressions and initialization value (rhs) types
-       lhs := [2]Expr{sKey, sValue} // sKey, sValue may be nil
-       rhs := [2]Type{key, val}     // key, val may be nil
-
-       rangeOverInt := isInteger(x.typ)
-
-       if isDef {
-               // short variable declaration
-               var vars []*Var
-               for i, lhs := range lhs {
-                       if lhs == nil {
-                               continue
-                       }
-
-                       // determine lhs variable
-                       var obj *Var
-                       if ident, _ := lhs.(*identType); ident != nil {
-                               // declare new variable
-                               name := identName(ident)
-                               obj = newVar(LocalVar, ident.Pos(), check.pkg, name, nil)
-                               check.recordDef(ident, obj)
-                               // _ variables don't count as new variables
-                               if name != "_" {
-                                       vars = append(vars, obj)
-                               }
-                       } else {
-                               check.errorf(lhs, InvalidSyntaxTree, "cannot declare %s", lhs)
-                               obj = newVar(LocalVar, lhs.Pos(), check.pkg, "_", nil) // dummy variable
-                       }
-                       assert(obj.typ == nil)
-
-                       // initialize lhs iteration variable, if any
-                       typ := rhs[i]
-                       if typ == nil || typ == Typ[Invalid] {
-                               // typ == Typ[Invalid] can happen if allowVersion fails.
-                               obj.typ = Typ[Invalid]
-                               check.usedVars[obj] = true // don't complain about unused variable
-                               continue
-                       }
-
-                       if rangeOverInt {
-                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] for rangeOverInt)
-                               check.initVar(obj, &x, "range clause")
-                       } else {
-                               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)
-               }
-
-               // declare variables
-               if len(vars) > 0 {
-                       scopePos := s.Body.Pos()
-                       for _, obj := range vars {
-                               check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
-                       }
-               } else {
-                       check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
-               }
-       } else if sKey != nil /* lhs[0] != nil */ {
-               // ordinary assignment
-               for i, lhs := range lhs {
-                       if lhs == nil {
-                               continue
-                       }
-
-                       // assign to lhs iteration variable, if any
-                       typ := rhs[i]
-                       if typ == nil || typ == Typ[Invalid] {
-                               continue
-                       }
-
-                       if rangeOverInt {
-                               assert(i == 0) // at most one iteration variable (rhs[1] == nil or Typ[Invalid] 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 {
-                               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 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 its default
-               // type (rune or int).
-               check.assignment(&x, nil, "range clause")
-       }
-
-       check.stmt(inner, s.Body)
-}
-
-// rangeKeyVal returns the key and value type produced by a range clause
-// over an expression of type orig.
-// 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.
-// The check parameter is only used in case of an error; it may be nil.
-func rangeKeyVal(check *Checker, orig 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
-       }
-
-       rtyp, err := commonUnder(orig, func(t, u Type) *typeError {
-               // A channel must permit receive operations.
-               if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
-                       return typeErrorf("receive from send-only channel %s", t)
-               }
-               return nil
-       })
-       if rtyp == nil {
-               return bad(err.format(check))
-       }
-
-       switch typ := arrayPtrDeref(rtyp).(type) {
-       case *Basic:
-               if isString(typ) {
-                       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, "", true
-               }
-       case *Array:
-               return Typ[Int], typ.elem, "", true
-       case *Slice:
-               return Typ[Int], typ.elem, "", true
-       case *Map:
-               return typ.key, typ.elem, "", true
-       case *Chan:
-               assert(typ.dir != SendOnly)
-               return typ.elem, nil, "", true
-       case *Signature:
-               if !buildcfg.Experiment.RangeFunc && allowVersion != nil && !allowVersion(go1_23) {
-                       return bad("requires go1.23 or later")
-               }
-               // check iterator arity
-               switch {
-               case typ.Params().Len() != 1:
-                       return bad("func must be func(yield func(...) bool): wrong argument count")
-               case typ.Results().Len() != 0:
-                       return bad("func must be func(yield func(...) bool): unexpected results")
-               }
-               assert(typ.Recv() == nil)
-               // check iterator argument type
-               u, err := commonUnder(typ.Params().At(0).Type(), nil)
-               cb, _ := u.(*Signature)
-               switch {
-               case cb == nil:
-                       if err != nil {
-                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
-                       } else {
-                               return bad("func must be func(yield func(...) bool): argument is not func")
-                       }
-               case cb.Params().Len() > 2:
-                       return bad("func must be func(yield func(...) bool): yield func has too many parameters")
-               case cb.Results().Len() != 1 || !Identical(cb.Results().At(0).Type(), universeBool):
-                       // see go.dev/issues/71131, go.dev/issues/71164
-                       if cb.Results().Len() == 1 && isBoolean(cb.Results().At(0).Type()) {
-                               return bad("func must be func(yield func(...) bool): yield func returns user-defined boolean, not bool")
-                       } else {
-                               return bad("func must be func(yield func(...) bool): yield func does not return bool")
-                       }
-               }
-               assert(cb.Recv() == nil)
-               // determine key and value types, if any
-               if cb.Params().Len() >= 1 {
-                       key = cb.Params().At(0).Type()
-               }
-               if cb.Params().Len() >= 2 {
-                       val = cb.Params().At(1).Type()
-               }
-               return key, val, "", true
-       }
-       return
-}