mirror of
https://github.com/golang/go
synced 2024-11-02 11:50:30 +00:00
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:
parent
fd54185a8d
commit
8b727f856e
16 changed files with 537 additions and 180 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
9
src/internal/goexperiment/exp_range_off.go
Normal file
9
src/internal/goexperiment/exp_range_off.go
Normal 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
|
9
src/internal/goexperiment/exp_range_on.go
Normal file
9
src/internal/goexperiment/exp_range_on.go
Normal 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
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
12
src/internal/types/testdata/check/stmt0.go
vendored
12
src/internal/types/testdata/check/stmt0.go
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
157
src/internal/types/testdata/spec/range.go
vendored
Normal file
157
src/internal/types/testdata/spec/range.go
vendored
Normal 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
24
test/range2.go
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue