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