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>
--- /dev/null
+// 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
+}
import (
"cmd/compile/internal/syntax"
"go/constant"
- "internal/buildcfg"
. "internal/types/errors"
"slices"
)
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
}
}
}
}
-
-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
-}
"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
--- /dev/null
+// 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
+}
"go/ast"
"go/constant"
"go/token"
- "internal/buildcfg"
. "internal/types/errors"
"slices"
)
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
-}