mirror of https://github.com/golang/go.git
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:
parent
232dfd226b
commit
8a7742e78c
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,6 @@ package types2
|
||||||
import (
|
import (
|
||||||
"cmd/compile/internal/syntax"
|
"cmd/compile/internal/syntax"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
"internal/buildcfg"
|
|
||||||
. "internal/types/errors"
|
. "internal/types/errors"
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
@ -668,7 +667,23 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) {
|
||||||
inner |= breakOk | continueOk
|
inner |= breakOk | continueOk
|
||||||
|
|
||||||
if rclause, _ := s.Init.(*syntax.RangeClause); rclause != nil {
|
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
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,11 @@ var filemap = map[string]action{
|
||||||
"package.go": nil,
|
"package.go": nil,
|
||||||
"pointer.go": nil,
|
"pointer.go": nil,
|
||||||
"predicates.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) {
|
"recording.go": func(f *ast.File) {
|
||||||
renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
|
renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
|
||||||
renameSelectorExprs(f, "syntax.Name->ast.Ident") // must happen before renaming identifiers
|
renameSelectorExprs(f, "syntax.Name->ast.Ident") // must happen before renaming identifiers
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
"go/token"
|
"go/token"
|
||||||
"internal/buildcfg"
|
|
||||||
. "internal/types/errors"
|
. "internal/types/errors"
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
@ -856,247 +855,9 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
||||||
|
|
||||||
case *ast.RangeStmt:
|
case *ast.RangeStmt:
|
||||||
inner |= breakOk | continueOk
|
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:
|
default:
|
||||||
check.error(s, InvalidSyntaxTree, "invalid statement")
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue