]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile/internal/types2: add support for language version checking
authorRobert Griesemer <gri@golang.org>
Wed, 3 Feb 2021 22:56:13 +0000 (14:56 -0800)
committerRobert Griesemer <gri@golang.org>
Thu, 4 Feb 2021 22:20:33 +0000 (22:20 +0000)
Add the Config.Lang field which may be set to a Go version string,
such as "go1.12". This is a string rather than explicit semantic
version numbers (such as {1, 12}) for API robustness; a string
is more flexible should we need more or different information.

Add -lang flag to types2 package for use with (manual) testing
when running "go test -run Check$ -lang=... -files=...".

While changing flags, look for comma-separated (rather than space-
separated) files when providing the -file flag.

Check that numeric constant literals, signed shift counts are
accepted according to the selected language version.

Type alias declarations and overlapping embedded interfaces are
not yet checked.

Updates #31793.

Change-Id: I9ff238ed38a88f377eb2267dc3e8816b89a40635
Reviewed-on: https://go-review.googlesource.com/c/go/+/289509
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
src/cmd/compile/internal/types2/api.go
src/cmd/compile/internal/types2/check.go
src/cmd/compile/internal/types2/check_test.go
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/stdlib_test.go
src/cmd/compile/internal/types2/testdata/go1_12.src [new file with mode: 0644]
src/cmd/compile/internal/types2/version.go [new file with mode: 0644]

index b29c0802edf8bdb905649e3f103397bd78b1fa84..30f0430ff18afb8e7551243269945f525051ff37 100644 (file)
@@ -99,6 +99,13 @@ type ImporterFrom interface {
 // A Config specifies the configuration for type checking.
 // The zero value for Config is a ready-to-use default configuration.
 type Config struct {
+       // GoVersion describes the accepted Go language version. The string
+       // must follow the format "go%d.%d" (e.g. "go1.12") or ist must be
+       // empty; an empty string indicates the latest language version.
+       // If the format is invalid, invoking the type checker will cause a
+       // panic.
+       GoVersion string
+
        // If IgnoreFuncBodies is set, function bodies are not
        // type-checked.
        IgnoreFuncBodies bool
index e2c6c4f60612626b44f1f36fb895908fd3df66b0..95fb4e1076cf8a01009b2a96cc5760781bacfd31 100644 (file)
@@ -88,12 +88,13 @@ type Checker struct {
        conf *Config
        pkg  *Package
        *Info
-       nextId uint64                      // unique Id for type parameters (first valid Id is 1)
-       objMap map[Object]*declInfo        // maps package-level objects and (non-interface) methods to declaration info
-       impMap map[importKey]*Package      // maps (import path, source directory) to (complete or fake) package
-       posMap map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions
-       typMap map[string]*Named           // maps an instantiated named type hash to a *Named type
-       pkgCnt map[string]int              // counts number of imported packages with a given name (for better error messages)
+       version version                     // accepted language version
+       nextId  uint64                      // unique Id for type parameters (first valid Id is 1)
+       objMap  map[Object]*declInfo        // maps package-level objects and (non-interface) methods to declaration info
+       impMap  map[importKey]*Package      // maps (import path, source directory) to (complete or fake) package
+       posMap  map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions
+       typMap  map[string]*Named           // maps an instantiated named type hash to a *Named type
+       pkgCnt  map[string]int              // counts number of imported packages with a given name (for better error messages)
 
        // information collected during type-checking of a set of package files
        // (initialized by Files, valid only for the duration of check.Files;
@@ -182,16 +183,22 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
                info = new(Info)
        }
 
+       version, err := parseGoVersion(conf.GoVersion)
+       if err != nil {
+               panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
+       }
+
        return &Checker{
-               conf:   conf,
-               pkg:    pkg,
-               Info:   info,
-               nextId: 1,
-               objMap: make(map[Object]*declInfo),
-               impMap: make(map[importKey]*Package),
-               posMap: make(map[*Interface][]syntax.Pos),
-               typMap: make(map[string]*Named),
-               pkgCnt: make(map[string]int),
+               conf:    conf,
+               pkg:     pkg,
+               Info:    info,
+               version: version,
+               nextId:  1,
+               objMap:  make(map[Object]*declInfo),
+               impMap:  make(map[importKey]*Package),
+               posMap:  make(map[*Interface][]syntax.Pos),
+               typMap:  make(map[string]*Named),
+               pkgCnt:  make(map[string]int),
        }
 }
 
index b03b074b6d5ac8ce53994db4c9b2a748141a7e44..9c1d278520dfcd41cb353adf33ddd231b5da2e89 100644 (file)
@@ -44,7 +44,8 @@ import (
 var (
        haltOnError = flag.Bool("halt", false, "halt on error")
        listErrors  = flag.Bool("errlist", false, "list errors")
-       testFiles   = flag.String("files", "", "space-separated list of test files")
+       testFiles   = flag.String("files", "", "comma-separated list of test files")
+       goVersion   = flag.String("lang", "", "Go language version (e.g. \"go1.12\"")
 )
 
 func parseFiles(t *testing.T, filenames []string, mode syntax.Mode) ([]*syntax.File, []error) {
@@ -83,7 +84,21 @@ func delta(x, y uint) uint {
        }
 }
 
-func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
+// goVersionRx matches a Go version string using '_', e.g. "go1_12".
+var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*_(0|[1-9][0-9]*)$`)
+
+// asGoVersion returns a regular Go language version string
+// if s is a Go version string using '_' rather than '.' to
+// separate the major and minor version numbers (e.g. "go1_12").
+// Otherwise it returns the empty string.
+func asGoVersion(s string) string {
+       if goVersionRx.MatchString(s) {
+               return strings.Replace(s, "_", ".", 1)
+       }
+       return ""
+}
+
+func checkFiles(t *testing.T, sources []string, goVersion string, colDelta uint, trace bool) {
        if len(sources) == 0 {
                t.Fatal("no source files")
        }
@@ -100,6 +115,11 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
                pkgName = files[0].PkgName.Value
        }
 
+       // if no Go version is given, consider the package name
+       if goVersion == "" {
+               goVersion = asGoVersion(pkgName)
+       }
+
        if *listErrors && len(errlist) > 0 {
                t.Errorf("--- %s:", pkgName)
                for _, err := range errlist {
@@ -109,6 +129,7 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
 
        // typecheck and collect typechecker errors
        var conf Config
+       conf.GoVersion = goVersion
        conf.AcceptMethodTypeParams = true
        conf.InferFromConstraints = true
        // special case for importC.src
@@ -220,13 +241,14 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
 }
 
 // TestCheck is for manual testing of selected input files, provided with -files.
+// The accepted Go language version can be controlled with the -lang flag.
 func TestCheck(t *testing.T) {
        if *testFiles == "" {
                return
        }
        testenv.MustHaveGoBuild(t)
        DefPredeclaredTestFuncs()
-       checkFiles(t, strings.Split(*testFiles, " "), 0, testing.Verbose())
+       checkFiles(t, strings.Split(*testFiles, ","), *goVersion, 0, testing.Verbose())
 }
 
 func TestTestdata(t *testing.T)  { DefPredeclaredTestFuncs(); testDir(t, 75, "testdata") } // TODO(gri) narrow column tolerance
@@ -263,7 +285,7 @@ func testDir(t *testing.T, colDelta uint, dir string) {
                                        fmt.Printf("\t%s\n", files[i])
                                }
                        }
-                       checkFiles(t, files, colDelta, false)
+                       checkFiles(t, files, "", colDelta, false)
                        continue
                }
 
@@ -271,6 +293,6 @@ func testDir(t *testing.T, colDelta uint, dir string) {
                if testing.Verbose() {
                        fmt.Printf("%3d %s\n", count, path)
                }
-               checkFiles(t, []string{path}, colDelta, false)
+               checkFiles(t, []string{path}, "", colDelta, false)
        }
 }
index 679495d3f3d349e6ac4db760ad464a944ac3ded5..9889e3113dd4d35507107fd3e09c27da80811b7b 100644 (file)
@@ -816,6 +816,10 @@ func (check *Checker) shift(x, y *operand, e syntax.Expr, op syntax.Operator) {
                check.invalidOpf(y, "shift count %s must be integer", y)
                x.mode = invalid
                return
+       } else if !isUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) {
+               check.invalidOpf(y, "signed shift count %s requires go1.13 or later", y)
+               x.mode = invalid
+               return
        }
 
        if x.mode == constant_ {
@@ -1185,6 +1189,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
                }
                switch e.Kind {
                case syntax.IntLit, syntax.FloatLit, syntax.ImagLit:
+                       check.langCompat(e)
                        // The max. mantissa precision for untyped numeric values
                        // is 512 bits, or 4048 bits for each of the two integer
                        // parts of a fraction for floating-point numbers that are
index a146619d7e35a0cf076c609d420851485b12814c..2949e230193593189d0dc2206066281a3487ba9c 100644 (file)
@@ -110,6 +110,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
                // get per-file instructions
                expectErrors := false
                filename := filepath.Join(path, f.Name())
+               goVersion := ""
                if comment := firstComment(filename); comment != "" {
                        fields := strings.Fields(comment)
                        switch fields[0] {
@@ -119,13 +120,17 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
                                expectErrors = true
                                for _, arg := range fields[1:] {
                                        if arg == "-0" || arg == "-+" || arg == "-std" {
-                                               // Marked explicitly as not expected errors (-0),
+                                               // Marked explicitly as not expecting errors (-0),
                                                // or marked as compiling runtime/stdlib, which is only done
                                                // to trigger runtime/stdlib-only error output.
                                                // In both cases, the code should typecheck.
                                                expectErrors = false
                                                break
                                        }
+                                       const prefix = "-lang="
+                                       if strings.HasPrefix(arg, prefix) {
+                                               goVersion = arg[len(prefix):]
+                                       }
                                }
                        }
                }
@@ -136,7 +141,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
                }
                file, err := syntax.ParseFile(filename, nil, nil, 0)
                if err == nil {
-                       conf := Config{Importer: stdLibImporter}
+                       conf := Config{GoVersion: goVersion, Importer: stdLibImporter}
                        _, err = conf.Check(filename, []*syntax.File{file}, nil)
                }
 
@@ -187,7 +192,6 @@ func TestStdFixed(t *testing.T) {
                "issue22200b.go", // go/types does not have constraints on stack size
                "issue25507.go",  // go/types does not have constraints on stack size
                "issue20780.go",  // go/types does not have constraints on stack size
-               "issue31747.go",  // go/types does not have constraints on language level (-lang=go1.12) (see #31793)
                "issue34329.go",  // go/types does not have constraints on language level (-lang=go1.13) (see #31793)
                "issue42058a.go", // go/types does not have constraints on channel element size
                "issue42058b.go", // go/types does not have constraints on channel element size
diff --git a/src/cmd/compile/internal/types2/testdata/go1_12.src b/src/cmd/compile/internal/types2/testdata/go1_12.src
new file mode 100644 (file)
index 0000000..75a602b
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright 2021 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.
+
+// Check Go language version-specific errors.
+
+package go1_12 // go1.12
+
+// numeric literals
+const (
+       _ = 1_000 // ERROR "underscores in numeric literals requires go1.13 or later"
+       _ = 0b111 // ERROR "binary literals requires go1.13 or later"
+       _ = 0o567 // ERROR "0o/0O-style octal literals requires go1.13 or later"
+       _ = 0xabc // ok
+       _ = 0x0p1 // ERROR "hexadecimal floating-point literals requires go1.13 or later"
+
+       _ = 0B111 // ERROR "binary"
+       _ = 0O567 // ERROR "octal"
+       _ = 0Xabc // ok
+       _ = 0X0P1 // ERROR "hexadecimal floating-point"
+
+       _ = 1_000i // ERROR "underscores"
+       _ = 0b111i // ERROR "binary"
+       _ = 0o567i // ERROR "octal"
+       _ = 0xabci // ERROR "hexadecimal floating-point"
+       _ = 0x0p1i // ERROR "hexadecimal floating-point"
+)
+
+// signed shift counts
+var (
+       s int
+       _ = 1 << s // ERROR "invalid operation: signed shift count s \(variable of type int\) requires go1.13 or later"
+       _ = 1 >> s // ERROR "signed shift count"
+)
diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go
new file mode 100644 (file)
index 0000000..cb497f0
--- /dev/null
@@ -0,0 +1,81 @@
+// Copyright 2021 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 types2
+
+import (
+       "cmd/compile/internal/syntax"
+       "fmt"
+       "regexp"
+       "strconv"
+       "strings"
+)
+
+// langCompat reports an error if the representation of a numeric
+// literal is not compatible with the current language version.
+func (check *Checker) langCompat(lit *syntax.BasicLit) {
+       s := lit.Value
+       if len(s) <= 2 || check.allowVersion(check.pkg, 1, 13) {
+               return
+       }
+       // len(s) > 2
+       if strings.Contains(s, "_") {
+               check.errorf(lit, "underscores in numeric literals requires go1.13 or later")
+               return
+       }
+       if s[0] != '0' {
+               return
+       }
+       radix := s[1]
+       if radix == 'b' || radix == 'B' {
+               check.errorf(lit, "binary literals requires go1.13 or later")
+               return
+       }
+       if radix == 'o' || radix == 'O' {
+               check.errorf(lit, "0o/0O-style octal literals requires go1.13 or later")
+               return
+       }
+       if lit.Kind != syntax.IntLit && (radix == 'x' || radix == 'X') {
+               check.errorf(lit, "hexadecimal floating-point literals requires go1.13 or later")
+       }
+}
+
+// allowVersion reports whether the given package
+// is allowed to use version major.minor.
+func (check *Checker) allowVersion(pkg *Package, major, minor int) bool {
+       // We assume that imported packages have all been checked,
+       // so we only have to check for the local package.
+       if pkg != check.pkg {
+               return true
+       }
+       ma, mi := check.version.major, check.version.minor
+       return ma == 0 && mi == 0 || ma > major || ma == major && mi >= minor
+}
+
+type version struct {
+       major, minor int
+}
+
+// parseGoVersion parses a Go version string (such as "go1.12")
+// and returns the version, or an error. If s is the empty
+// string, the version is 0.0.
+func parseGoVersion(s string) (v version, err error) {
+       if s == "" {
+               return
+       }
+       matches := goVersionRx.FindStringSubmatch(s)
+       if matches == nil {
+               err = fmt.Errorf(`should be something like "go1.12"`)
+               return
+       }
+       v.major, err = strconv.Atoi(matches[1])
+       if err != nil {
+               return
+       }
+       v.minor, err = strconv.Atoi(matches[2])
+       return
+}
+
+// goVersionRx matches a Go version string, e.g. "go1.12".
+var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)