blob: 5c80463aba0db3b25d275aa53b46ebfe304bd93d [file] [log] [blame] [edit]
// 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
}