go/types, types2: factor out shared for-range checking code into range.go

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>
This commit is contained in:
Robert Griesemer 2025-03-06 09:58:51 -08:00 committed by Gopher Robot
parent 232dfd226b
commit 8a7742e78c
5 changed files with 520 additions and 496 deletions

View File

@ -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
}

View 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
}

View 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

250
src/go/types/range.go Normal file
View File

@ -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
}

View 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
}