From 8a7742e78c8f32fcfe4d7cbcfa2f423c8ac08f29 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 6 Mar 2025 09:58:51 -0800 Subject: [PATCH] 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 Auto-Submit: Robert Griesemer LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/range.go | 247 ++++++++++++++++++++ src/cmd/compile/internal/types2/stmt.go | 273 ++--------------------- src/go/types/generate_test.go | 5 + src/go/types/range.go | 250 +++++++++++++++++++++ src/go/types/stmt.go | 241 +------------------- 5 files changed, 520 insertions(+), 496 deletions(-) create mode 100644 src/cmd/compile/internal/types2/range.go create mode 100644 src/go/types/range.go diff --git a/src/cmd/compile/internal/types2/range.go b/src/cmd/compile/internal/types2/range.go new file mode 100644 index 0000000000..86626ceaa8 --- /dev/null +++ b/src/cmd/compile/internal/types2/range.go @@ -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 +} diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 79cc0150d4..efe9c99d87 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -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 -} diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go index 62e17a957a..e5e0874d17 100644 --- a/src/go/types/generate_test.go +++ b/src/go/types/generate_test.go @@ -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 diff --git a/src/go/types/range.go b/src/go/types/range.go new file mode 100644 index 0000000000..5c80463aba --- /dev/null +++ b/src/go/types/range.go @@ -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 +} diff --git a/src/go/types/stmt.go b/src/go/types/stmt.go index 68f31fef65..398075eaf3 100644 --- a/src/go/types/stmt.go +++ b/src/go/types/stmt.go @@ -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 -} -- 2.50.0