cmd/compile, go/types: typechecking of range over int, func

Add type-checking logic for range over integers and functions,
behind GOEXPERIMENT=range.

For proposal #61405 (but behind a GOEXPERIMENT).
For #61717.

Change-Id: Ibf78cf381798b450dbe05eb922df82af2b009403
Reviewed-on: https://go-review.googlesource.com/c/go/+/510537
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Russ Cox 2023-06-14 09:52:44 -04:00 committed by Gopher Robot
parent fd54185a8d
commit 8b727f856e
16 changed files with 537 additions and 180 deletions

View file

@ -34,11 +34,13 @@ import (
"cmd/compile/internal/syntax" "cmd/compile/internal/syntax"
"flag" "flag"
"fmt" "fmt"
"internal/buildcfg"
"internal/testenv" "internal/testenv"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -123,12 +125,23 @@ func testFiles(t *testing.T, filenames []string, srcs [][]byte, colDelta uint, m
} }
var conf Config var conf Config
var goexperiment string
flags := flag.NewFlagSet("", flag.PanicOnError) flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "") flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.StringVar(&goexperiment, "goexperiment", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "") flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
if err := parseFlags(srcs[0], flags); err != nil { if err := parseFlags(srcs[0], flags); err != nil {
t.Fatal(err) t.Fatal(err)
} }
exp, err := buildcfg.ParseGOEXPERIMENT(runtime.GOOS, runtime.GOARCH, goexperiment)
if err != nil {
t.Fatal(err)
}
old := buildcfg.Experiment
defer func() {
buildcfg.Experiment = old
}()
buildcfg.Experiment = *exp
files, errlist := parseFiles(t, filenames, srcs, 0) files, errlist := parseFiles(t, filenames, srcs, 0)
@ -355,6 +368,12 @@ func TestIssue47243_TypedRHS(t *testing.T) {
} }
func TestCheck(t *testing.T) { func TestCheck(t *testing.T) {
old := buildcfg.Experiment.Range
defer func() {
buildcfg.Experiment.Range = old
}()
buildcfg.Experiment.Range = true
DefPredeclaredTestFuncs() DefPredeclaredTestFuncs()
testDirFiles(t, "../../../../internal/types/testdata/check", 50, false) // TODO(gri) narrow column tolerance testDirFiles(t, "../../../../internal/types/testdata/check", 50, false) // TODO(gri) narrow column tolerance
} }

View file

@ -233,6 +233,9 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
filename := filepath.Join(path, f.Name()) filename := filepath.Join(path, f.Name())
goVersion := "" goVersion := ""
if comment := firstComment(filename); comment != "" { if comment := firstComment(filename); comment != "" {
if strings.Contains(comment, "-goexperiment") {
continue // ignore this file
}
fields := strings.Fields(comment) fields := strings.Fields(comment)
switch fields[0] { switch fields[0] {
case "skip", "compiledir": case "skip", "compiledir":

View file

@ -9,6 +9,7 @@ package types2
import ( import (
"cmd/compile/internal/syntax" "cmd/compile/internal/syntax"
"go/constant" "go/constant"
"internal/buildcfg"
. "internal/types/errors" . "internal/types/errors"
"sort" "sort"
) )
@ -828,7 +829,10 @@ func (check *Checker) typeSwitchStmt(inner stmtContext, s *syntax.SwitchStmt, gu
} }
func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *syntax.RangeClause) { func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *syntax.RangeClause) {
// determine lhs, if any // 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 sKey := rclause.Lhs // possibly nil
var sValue, sExtra syntax.Expr var sValue, sExtra syntax.Expr
if p, _ := sKey.(*syntax.ListExpr); p != nil { if p, _ := sKey.(*syntax.ListExpr); p != nil {
@ -844,43 +848,48 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
sExtra = p.ElemList[2] 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 // check expression to iterate over
var x operand var x operand
check.expr(nil, &x, rclause.X) check.expr(nil, &x, rangeVar)
// determine key/value types // determine key/value types
var key, val Type var key, val Type
if x.mode != invalid { if x.mode != invalid {
// Ranging over a type parameter is permitted if it has a core type. // Ranging over a type parameter is permitted if it has a core type.
var cause string k, v, cause, isFunc, ok := rangeKeyVal(x.typ)
u := coreType(x.typ) switch {
if t, _ := u.(*Chan); t != nil { case !ok && cause != "":
if sValue != nil { check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
check.softErrorf(sValue, InvalidIterVar, "range over %s permits only one iteration variable", &x) case !ok:
// ok to continue
}
if t.dir == SendOnly {
cause = "receive from send-only channel"
}
} else {
if sExtra != nil {
check.softErrorf(sExtra, InvalidIterVar, "range clause permits at most two iteration variables")
// ok to continue
}
if u == nil {
cause = check.sprintf("%s has no core type", x.typ)
}
}
key, val = rangeKeyVal(u)
if key == nil || cause != "" {
if cause == "" {
check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x) check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
} else { case k == nil && sKey != nil:
check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s (%s)", &x, cause) 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")
case isFunc && ((k == nil) != (sKey == nil) || (v == nil) != (sValue == nil)):
var count string
switch {
case k == nil:
count = "no iteration variables"
case v == nil:
count = "one iteration variable"
default:
count = "two iteration variables"
} }
// ok to continue check.softErrorf(&x, InvalidIterVar, "range over %s must have %s", &x, count)
} }
key, val = k, v
} }
// Open the for-statement block scope now, after the range clause. // Open the for-statement block scope now, after the range clause.
@ -892,10 +901,10 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
// (irregular assignment, cannot easily map to existing assignment checks) // (irregular assignment, cannot easily map to existing assignment checks)
// lhs expressions and initialization value (rhs) types // lhs expressions and initialization value (rhs) types
lhs := [2]syntax.Expr{sKey, sValue} lhs := [2]expr{sKey, sValue}
rhs := [2]Type{key, val} // key, val may be nil rhs := [2]Type{key, val} // key, val may be nil
if rclause.Def { if isDef {
// short variable declaration // short variable declaration
var vars []*Var var vars []*Var
for i, lhs := range lhs { for i, lhs := range lhs {
@ -905,9 +914,9 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
// determine lhs variable // determine lhs variable
var obj *Var var obj *Var
if ident, _ := lhs.(*syntax.Name); ident != nil { if ident, _ := lhs.(*identType); ident != nil {
// declare new variable // declare new variable
name := ident.Value name := identName(ident)
obj = NewVar(ident.Pos(), check.pkg, name, nil) obj = NewVar(ident.Pos(), check.pkg, name, nil)
check.recordDef(ident, obj) check.recordDef(ident, obj)
// _ variables don't count as new variables // _ variables don't count as new variables
@ -938,7 +947,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
} }
} else { } else {
check.error(s, NoNewVar, "no new variables on left side of :=") check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
} }
} else { } else {
// ordinary assignment // ordinary assignment
@ -959,22 +968,68 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
} }
// rangeKeyVal returns the key and value type produced by a range clause // rangeKeyVal returns the key and value type produced by a range clause
// over an expression of type typ. If the range clause is not permitted // over an expression of type typ. If the range clause is not permitted,
// the results are nil. // rangeKeyVal returns ok = false. When ok = false, rangeKeyVal may also
func rangeKeyVal(typ Type) (key, val Type) { // return a reason in cause.
switch typ := arrayPtrDeref(typ).(type) { func rangeKeyVal(typ Type) (key, val Type, cause string, isFunc, ok bool) {
bad := func(cause string) (Type, Type, string, bool, bool) {
return Typ[Invalid], Typ[Invalid], cause, false, false
}
toSig := func(t Type) *Signature {
sig, _ := coreType(t).(*Signature)
return sig
}
orig := typ
switch typ := arrayPtrDeref(coreType(typ)).(type) {
case nil:
return bad("no core type")
case *Basic: case *Basic:
if isString(typ) { if isString(typ) {
return Typ[Int], universeRune // use 'rune' name return Typ[Int], universeRune, "", false, true // use 'rune' name
}
if buildcfg.Experiment.Range && isInteger(typ) {
return orig, nil, "", false, true
} }
case *Array: case *Array:
return Typ[Int], typ.elem return Typ[Int], typ.elem, "", false, true
case *Slice: case *Slice:
return Typ[Int], typ.elem return Typ[Int], typ.elem, "", false, true
case *Map: case *Map:
return typ.key, typ.elem return typ.key, typ.elem, "", false, true
case *Chan: case *Chan:
return typ.elem, Typ[Invalid] if typ.dir == SendOnly {
return bad("receive from send-only channel")
}
return typ.elem, nil, "", false, true
case *Signature:
if !buildcfg.Experiment.Range {
break
}
assert(typ.Recv() == nil)
switch {
case typ.Params().Len() != 1:
return bad("func must be func(yield func(...) bool): wrong argument count")
case toSig(typ.Params().At(0).Type()) == nil:
return bad("func must be func(yield func(...) bool): argument is not func")
case typ.Results().Len() != 0:
return bad("func must be func(yield func(...) bool): unexpected results")
}
cb := toSig(typ.Params().At(0).Type())
assert(cb.Recv() == nil)
switch {
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 || !isBoolean(cb.Results().At(0).Type()):
return bad("func must be func(yield func(...) bool): yield func does not return bool")
}
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, true
} }
return return
} }

View file

@ -288,7 +288,10 @@ var depsRules = `
math/big, go/token math/big, go/token
< go/constant; < go/constant;
container/heap, go/constant, go/parser, internal/goversion, internal/types/errors FMT, internal/goexperiment
< internal/buildcfg;
container/heap, go/constant, go/parser, internal/buildcfg, internal/goversion, internal/types/errors
< go/types; < go/types;
# The vast majority of standard library packages should not be resorting to regexp. # The vast majority of standard library packages should not be resorting to regexp.
@ -299,9 +302,6 @@ var depsRules = `
go/doc/comment, go/parser, internal/lazyregexp, text/template go/doc/comment, go/parser, internal/lazyregexp, text/template
< go/doc; < go/doc;
FMT, internal/goexperiment
< internal/buildcfg;
go/build/constraint, go/doc, go/parser, internal/buildcfg, internal/goroot, internal/goversion, internal/platform go/build/constraint, go/doc, go/parser, internal/buildcfg, internal/goroot, internal/goversion, internal/platform
< go/build; < go/build;

View file

@ -38,12 +38,14 @@ import (
"go/parser" "go/parser"
"go/scanner" "go/scanner"
"go/token" "go/token"
"internal/buildcfg"
"internal/testenv" "internal/testenv"
"internal/types/errors" "internal/types/errors"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -134,12 +136,23 @@ func testFiles(t *testing.T, filenames []string, srcs [][]byte, manual bool, opt
} }
var conf Config var conf Config
var goexperiment string
flags := flag.NewFlagSet("", flag.PanicOnError) flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "") flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.StringVar(&goexperiment, "goexperiment", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "") flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
if err := parseFlags(srcs[0], flags); err != nil { if err := parseFlags(srcs[0], flags); err != nil {
t.Fatal(err) t.Fatal(err)
} }
exp, err := buildcfg.ParseGOEXPERIMENT(runtime.GOOS, runtime.GOARCH, goexperiment)
if err != nil {
t.Fatal(err)
}
old := buildcfg.Experiment
defer func() {
buildcfg.Experiment = old
}()
buildcfg.Experiment = *exp
files, errlist := parseFiles(t, filenames, srcs, parser.AllErrors) files, errlist := parseFiles(t, filenames, srcs, parser.AllErrors)
@ -383,6 +396,12 @@ func TestIssue47243_TypedRHS(t *testing.T) {
} }
func TestCheck(t *testing.T) { func TestCheck(t *testing.T) {
old := buildcfg.Experiment.Range
defer func() {
buildcfg.Experiment.Range = old
}()
buildcfg.Experiment.Range = true
DefPredeclaredTestFuncs() DefPredeclaredTestFuncs()
testDirFiles(t, "../../internal/types/testdata/check", false) testDirFiles(t, "../../internal/types/testdata/check", false)
} }

View file

@ -237,6 +237,9 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
filename := filepath.Join(path, f.Name()) filename := filepath.Join(path, f.Name())
goVersion := "" goVersion := ""
if comment := firstComment(filename); comment != "" { if comment := firstComment(filename); comment != "" {
if strings.Contains(comment, "-goexperiment") {
continue // ignore this file
}
fields := strings.Fields(comment) fields := strings.Fields(comment)
switch fields[0] { switch fields[0] {
case "skip", "compiledir": case "skip", "compiledir":

View file

@ -10,6 +10,7 @@ import (
"go/ast" "go/ast"
"go/constant" "go/constant"
"go/token" "go/token"
"internal/buildcfg"
. "internal/types/errors" . "internal/types/errors"
"sort" "sort"
) )
@ -827,38 +828,59 @@ 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)
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
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 // check expression to iterate over
var x operand var x operand
check.expr(nil, &x, s.X) check.expr(nil, &x, rangeVar)
// determine key/value types // determine key/value types
var key, val Type var key, val Type
if x.mode != invalid { if x.mode != invalid {
// Ranging over a type parameter is permitted if it has a core type. // Ranging over a type parameter is permitted if it has a core type.
var cause string k, v, cause, isFunc, ok := rangeKeyVal(x.typ)
u := coreType(x.typ) switch {
switch t := u.(type) { case !ok && cause != "":
case nil: check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s: %s", &x, cause)
cause = check.sprintf("%s has no core type", x.typ) case !ok:
case *Chan:
if s.Value != nil {
check.softErrorf(s.Value, InvalidIterVar, "range over %s permits only one iteration variable", &x)
// ok to continue
}
if t.dir == SendOnly {
cause = "receive from send-only channel"
}
}
key, val = rangeKeyVal(u)
if key == nil || cause != "" {
if cause == "" {
check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x) check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s", &x)
} else { case k == nil && sKey != nil:
check.softErrorf(&x, InvalidRangeExpr, "cannot range over %s (%s)", &x, cause) 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")
case isFunc && ((k == nil) != (sKey == nil) || (v == nil) != (sValue == nil)):
var count string
switch {
case k == nil:
count = "no iteration variables"
case v == nil:
count = "one iteration variable"
default:
count = "two iteration variables"
} }
// ok to continue check.softErrorf(&x, InvalidIterVar, "range over %s must have %s", &x, count)
} }
key, val = k, v
} }
// Open the for-statement block scope now, after the range clause. // Open the for-statement block scope now, after the range clause.
@ -870,10 +892,10 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
// (irregular assignment, cannot easily map to existing assignment checks) // (irregular assignment, cannot easily map to existing assignment checks)
// lhs expressions and initialization value (rhs) types // lhs expressions and initialization value (rhs) types
lhs := [2]ast.Expr{s.Key, s.Value} lhs := [2]expr{sKey, sValue}
rhs := [2]Type{key, val} // key, val may be nil rhs := [2]Type{key, val} // key, val may be nil
if s.Tok == token.DEFINE { if isDef {
// short variable declaration // short variable declaration
var vars []*Var var vars []*Var
for i, lhs := range lhs { for i, lhs := range lhs {
@ -883,9 +905,9 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
// determine lhs variable // determine lhs variable
var obj *Var var obj *Var
if ident, _ := lhs.(*ast.Ident); ident != nil { if ident, _ := lhs.(*identType); ident != nil {
// declare new variable // declare new variable
name := ident.Name name := identName(ident)
obj = NewVar(ident.Pos(), check.pkg, name, nil) obj = NewVar(ident.Pos(), check.pkg, name, nil)
check.recordDef(ident, obj) check.recordDef(ident, obj)
// _ variables don't count as new variables // _ variables don't count as new variables
@ -916,7 +938,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
} }
} else { } else {
check.error(inNode(s, s.TokPos), NoNewVar, "no new variables on left side of :=") check.error(noNewVarPos, NoNewVar, "no new variables on left side of :=")
} }
} else { } else {
// ordinary assignment // ordinary assignment
@ -934,29 +956,71 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
} }
check.stmt(inner, s.Body) check.stmt(inner, s.Body)
default:
check.error(s, InvalidSyntaxTree, "invalid statement")
}
} }
// rangeKeyVal returns the key and value type produced by a range clause // rangeKeyVal returns the key and value type produced by a range clause
// over an expression of type typ. If the range clause is not permitted // over an expression of type typ. If the range clause is not permitted,
// the results are nil. // rangeKeyVal returns ok = false. When ok = false, rangeKeyVal may also
func rangeKeyVal(typ Type) (key, val Type) { // return a reason in cause.
switch typ := arrayPtrDeref(typ).(type) { func rangeKeyVal(typ Type) (key, val Type, cause string, isFunc, ok bool) {
bad := func(cause string) (Type, Type, string, bool, bool) {
return Typ[Invalid], Typ[Invalid], cause, false, false
}
toSig := func(t Type) *Signature {
sig, _ := coreType(t).(*Signature)
return sig
}
orig := typ
switch typ := arrayPtrDeref(coreType(typ)).(type) {
case nil:
return bad("no core type")
case *Basic: case *Basic:
if isString(typ) { if isString(typ) {
return Typ[Int], universeRune // use 'rune' name return Typ[Int], universeRune, "", false, true // use 'rune' name
}
if buildcfg.Experiment.Range && isInteger(typ) {
return orig, nil, "", false, true
} }
case *Array: case *Array:
return Typ[Int], typ.elem return Typ[Int], typ.elem, "", false, true
case *Slice: case *Slice:
return Typ[Int], typ.elem return Typ[Int], typ.elem, "", false, true
case *Map: case *Map:
return typ.key, typ.elem return typ.key, typ.elem, "", false, true
case *Chan: case *Chan:
return typ.elem, Typ[Invalid] if typ.dir == SendOnly {
return bad("receive from send-only channel")
}
return typ.elem, nil, "", false, true
case *Signature:
if !buildcfg.Experiment.Range {
break
}
assert(typ.Recv() == nil)
switch {
case typ.Params().Len() != 1:
return bad("func must be func(yield func(...) bool): wrong argument count")
case toSig(typ.Params().At(0).Type()) == nil:
return bad("func must be func(yield func(...) bool): argument is not func")
case typ.Results().Len() != 0:
return bad("func must be func(yield func(...) bool): unexpected results")
}
cb := toSig(typ.Params().At(0).Type())
assert(cb.Recv() == nil)
switch {
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 || !isBoolean(cb.Results().At(0).Type()):
return bad("func must be func(yield func(...) bool): yield func does not return bool")
}
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, true
} }
return return
} }

View file

@ -1,7 +1,3 @@
// Copyright 2023 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.
// Code generated by mkconsts.go. DO NOT EDIT. // Code generated by mkconsts.go. DO NOT EDIT.
//go:build !goexperiment.newinliner //go:build !goexperiment.newinliner

View file

@ -1,7 +1,3 @@
// Copyright 2023 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.
// Code generated by mkconsts.go. DO NOT EDIT. // Code generated by mkconsts.go. DO NOT EDIT.
//go:build goexperiment.newinliner //go:build goexperiment.newinliner

View file

@ -0,0 +1,9 @@
// Code generated by mkconsts.go. DO NOT EDIT.
//go:build !goexperiment.range
// +build !goexperiment.range
package goexperiment
const Range = false
const RangeInt = 0

View file

@ -0,0 +1,9 @@
// Code generated by mkconsts.go. DO NOT EDIT.
//go:build goexperiment.range
// +build goexperiment.range
package goexperiment
const Range = true
const RangeInt = 1

View file

@ -113,4 +113,7 @@ type Flags struct {
// NewInliner enables a new+improved version of the function // NewInliner enables a new+improved version of the function
// inlining phase within the Go compiler. // inlining phase within the Go compiler.
NewInliner bool NewInliner bool
// Range enables range over int and func.
Range bool
} }

View file

@ -1004,12 +1004,12 @@ const (
// } // }
InvalidIterVar InvalidIterVar
// InvalidRangeExpr occurs when the type of a range expression is not array, // InvalidRangeExpr occurs when the type of a range expression is not
// slice, string, map, or channel. // a valid type for use with a range loop.
// //
// Example: // Example:
// func f(i int) { // func f(f float64) {
// for j := range i { // for j := range f {
// println(j) // println(j)
// } // }
// } // }

View file

@ -805,7 +805,6 @@ func fors1() {
func rangeloops1() { func rangeloops1() {
var ( var (
x int
a [10]float32 a [10]float32
b []string b []string
p *[10]complex128 p *[10]complex128
@ -815,11 +814,12 @@ func rangeloops1() {
c chan int c chan int
sc chan<- int sc chan<- int
rc <-chan int rc <-chan int
xs struct{}
) )
for range x /* ERROR "cannot range over" */ {} for range xs /* ERROR "cannot range over" */ {}
for _ = range x /* ERROR "cannot range over" */ {} for _ = range xs /* ERROR "cannot range over" */ {}
for i := range x /* ERROR "cannot range over" */ {} for i := range xs /* ERROR "cannot range over" */ { _ = i }
for range a {} for range a {}
for i := range a { for i := range a {
@ -953,10 +953,10 @@ func issue10148() {
for y /* ERROR "declared and not used" */ := range "" { for y /* ERROR "declared and not used" */ := range "" {
_ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1 _ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1
} }
for range 1 /* ERROR "cannot range over 1" */ { for range 1.5 /* ERROR "cannot range over 1.5" */ {
_ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1 _ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1
} }
for y := range 1 /* ERROR "cannot range over 1" */ { for y := range 1.5 /* ERROR "cannot range over 1.5" */ {
_ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1 _ = "" /* ERROR "mismatched types untyped string and untyped int" */ + 1
} }
} }

View file

@ -0,0 +1,157 @@
// -goexperiment=range
// Copyright 2023 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.
package p
type MyInt int32
type MyBool bool
type MyString string
type MyFunc1 func(func(int) bool)
type MyFunc2 func(int) bool
type MyFunc3 func(MyFunc2)
type T struct{}
func (*T) PM() {}
func (T) M() {}
func f1() {}
func f2(func()) {}
func f4(func(int) bool) {}
func f5(func(int, string) bool) {}
func f7(func(int) MyBool) {}
func f8(func(MyInt, MyString) MyBool) {}
func test() {
// TODO: Would be nice to 'for range T.M' and 'for range (*T).PM' directly,
// but there is no gofmt-friendly way to write the error pattern in the right place.
m1 := T.M
for range m1 /* ERROR "cannot range over m1 (variable of type func(T)): func must be func(yield func(...) bool): argument is not func" */ {
}
m2 := (*T).PM
for range m2 /* ERROR "cannot range over m2 (variable of type func(*T)): func must be func(yield func(...) bool): argument is not func" */ {
}
for range f1 /* ERROR "cannot range over f1 (value of type func()): func must be func(yield func(...) bool): wrong argument count" */ {
}
for range f2 /* ERROR "cannot range over f2 (value of type func(func())): func must be func(yield func(...) bool): yield func does not return bool" */ {
}
for range f4 /* ERROR "range over f4 (value of type func(func(int) bool)) must have one iteration variable" */ {
}
for _ = range f4 {
}
for _, _ = range f5 {
}
for _ = range f7 {
}
for _, _ = range f8 {
}
for range 1 {
}
for range uint8(1) {
}
for range int64(1) {
}
for range MyInt(1) {
}
for range 'x' {
}
for range 1.0 /* ERROR "cannot range over 1.0 (untyped float constant 1)" */ {
}
for _ = range MyFunc1(nil) {
}
for _ = range MyFunc3(nil) {
}
for _ = range (func(MyFunc2))(nil) {
}
var i int
var s string
var mi MyInt
var ms MyString
for i := range f4 {
_ = i
}
for i = range f4 {
_ = i
}
for i, s := range f5 {
_, _ = i, s
}
for i, s = range f5 {
_, _ = i, s
}
for i, _ := range f5 {
_ = i
}
for i, _ = range f5 {
_ = i
}
for i := range f7 {
_ = i
}
for i = range f7 {
_ = i
}
for mi, _ := range f8 {
_ = mi
}
for mi, _ = range f8 {
_ = mi
}
for mi, ms := range f8 {
_, _ = mi, ms
}
for i /* ERROR "cannot use i (value of type MyInt) as int value in assignment" */, s /* ERROR "cannot use s (value of type MyString) as string value in assignment" */ = range f8 {
_, _ = mi, ms
}
for mi, ms := range f8 {
i, s = mi /* ERROR "cannot use mi (variable of type MyInt) as int value in assignment" */, ms /* ERROR "cannot use ms (variable of type MyString) as string value in assignment" */
}
for mi, ms = range f8 {
_, _ = mi, ms
}
for i := range 10 {
_ = i
}
for i = range 10 {
_ = i
}
for i, j /* ERROR "range over 10 (untyped int constant) permits only one iteration variable" */ := range 10 {
_, _ = i, j
}
for mi := range MyInt(10) {
_ = mi
}
for mi = range MyInt(10) {
_ = mi
}
}
func _[T int | string](x T) {
for range x /* ERROR "cannot range over x (variable of type T constrained by int | string): no core type" */ {
}
}
func _[T int | int64](x T) {
for range x /* ERROR "cannot range over x (variable of type T constrained by int | int64): no core type" */ {
}
}
func _[T ~int](x T) {
for range x { // ok
}
}
func _[T any](x func(func(T) bool)) {
for _ = range x { // ok
}
}
func _[T ~func(func(int) bool)](x T) {
for _ = range x { // ok
}
}

24
test/range2.go Normal file
View file

@ -0,0 +1,24 @@
// errorcheck -goexperiment range
// Copyright 2023 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.
// See ../internal/types/testdata/spec/range.go for most tests.
// The ones in this file cannot be expressed in that framework
// due to conflicts between that framework's error location pickiness
// and gofmt's comment location pickiness.
package p
type T struct{}
func (*T) PM() {}
func (T) M() {}
func test() {
for range T.M { // ERROR "cannot range over T.M \(value of type func\(T\)\): func must be func\(yield func\(...\) bool\): argument is not func"
}
for range (*T).PM { // ERROR "cannot range over \(\*T\).PM \(value of type func\(\*T\)\): func must be func\(yield func\(...\) bool\): argument is not func"
}
}