mirror of
https://github.com/golang/go
synced 2024-11-11 19:41:36 -07:00
[dev.regabi] go/types: add support for language version checking
This is a port of CL 289509 to go/types. It differs from that CL in codes added to errors, to fit the new factoring of check_test.go, and to allow go/types to import regexp in deps_test.go For #31793 Change-Id: Ia9e4c7f5aac1493001189184227c2ebc79a76e77 Reviewed-on: https://go-review.googlesource.com/c/go/+/291317 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
a7e9b4b948
commit
b81efb7ec4
@ -285,7 +285,7 @@ var depsRules = `
|
||||
math/big, go/token
|
||||
< go/constant;
|
||||
|
||||
container/heap, go/constant, go/parser
|
||||
container/heap, go/constant, go/parser, regexp
|
||||
< go/types;
|
||||
|
||||
FMT
|
||||
|
@ -101,6 +101,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 it 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
|
||||
|
@ -8,6 +8,7 @@ package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
@ -84,10 +85,11 @@ type Checker struct {
|
||||
fset *token.FileSet
|
||||
pkg *Package
|
||||
*Info
|
||||
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][]token.Pos // maps interface types to lists of embedded interface positions
|
||||
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
|
||||
version version // accepted language version
|
||||
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][]token.Pos // maps interface types to lists of embedded interface positions
|
||||
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;
|
||||
@ -176,15 +178,21 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
|
||||
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,
|
||||
fset: fset,
|
||||
pkg: pkg,
|
||||
Info: info,
|
||||
objMap: make(map[Object]*declInfo),
|
||||
impMap: make(map[importKey]*Package),
|
||||
posMap: make(map[*Interface][]token.Pos),
|
||||
pkgCnt: make(map[string]int),
|
||||
conf: conf,
|
||||
fset: fset,
|
||||
pkg: pkg,
|
||||
Info: info,
|
||||
version: version,
|
||||
objMap: make(map[Object]*declInfo),
|
||||
impMap: make(map[importKey]*Package),
|
||||
posMap: make(map[*Interface][]token.Pos),
|
||||
pkgCnt: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,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\"")
|
||||
)
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
@ -188,7 +189,21 @@ func eliminate(t *testing.T, errmap map[string][]string, errlist []error) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
|
||||
// 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, goVersion string, filenames []string, srcs [][]byte) {
|
||||
if len(filenames) == 0 {
|
||||
t.Fatal("no source files")
|
||||
}
|
||||
@ -201,6 +216,11 @@ func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
|
||||
pkgName = files[0].Name.Name
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -210,6 +230,7 @@ func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
|
||||
|
||||
// typecheck and collect typechecker errors
|
||||
var conf Config
|
||||
conf.GoVersion = goVersion
|
||||
|
||||
// special case for importC.src
|
||||
if len(filenames) == 1 {
|
||||
@ -267,19 +288,20 @@ func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
|
||||
}
|
||||
|
||||
// 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()
|
||||
testPkg(t, strings.Split(*testFiles, " "))
|
||||
testPkg(t, strings.Split(*testFiles, ","), *goVersion)
|
||||
}
|
||||
|
||||
func TestLongConstants(t *testing.T) {
|
||||
format := "package longconst\n\nconst _ = %s\nconst _ = %s // ERROR excessively long constant"
|
||||
src := fmt.Sprintf(format, strings.Repeat("1", 9999), strings.Repeat("1", 10001))
|
||||
checkFiles(t, []string{"longconst.go"}, [][]byte{[]byte(src)})
|
||||
checkFiles(t, "", []string{"longconst.go"}, [][]byte{[]byte(src)})
|
||||
}
|
||||
|
||||
func TestTestdata(t *testing.T) { DefPredeclaredTestFuncs(); testDir(t, "testdata") }
|
||||
@ -312,12 +334,12 @@ func testDir(t *testing.T, dir string) {
|
||||
filenames = []string{path}
|
||||
}
|
||||
t.Run(filepath.Base(path), func(t *testing.T) {
|
||||
testPkg(t, filenames)
|
||||
testPkg(t, filenames, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testPkg(t *testing.T, filenames []string) {
|
||||
func testPkg(t *testing.T, filenames []string, goVersion string) {
|
||||
srcs := make([][]byte, len(filenames))
|
||||
for i, filename := range filenames {
|
||||
src, err := os.ReadFile(filename)
|
||||
@ -326,5 +348,5 @@ func testPkg(t *testing.T, filenames []string) {
|
||||
}
|
||||
srcs[i] = src
|
||||
}
|
||||
checkFiles(t, filenames, srcs)
|
||||
checkFiles(t, goVersion, filenames, srcs)
|
||||
}
|
||||
|
@ -772,6 +772,10 @@ func (check *Checker) shift(x, y *operand, e ast.Expr, op token.Token) {
|
||||
check.invalidOp(y, _InvalidShiftCount, "shift count %s must be integer", y)
|
||||
x.mode = invalid
|
||||
return
|
||||
} else if !isUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) {
|
||||
check.invalidOp(y, _InvalidShiftCount, "signed shift count %s requires go1.13 or later", y)
|
||||
x.mode = invalid
|
||||
return
|
||||
}
|
||||
|
||||
var yval constant.Value
|
||||
@ -1152,6 +1156,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
|
||||
case *ast.BasicLit:
|
||||
switch e.Kind {
|
||||
case token.INT, token.FLOAT, token.IMAG:
|
||||
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
|
||||
|
@ -106,6 +106,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] {
|
||||
@ -115,13 +116,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):]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,7 +134,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
|
||||
// parse and type-check file
|
||||
file, err := parser.ParseFile(fset, filename, nil, 0)
|
||||
if err == nil {
|
||||
conf := Config{Importer: stdLibImporter}
|
||||
conf := Config{GoVersion: goVersion, Importer: stdLibImporter}
|
||||
_, err = conf.Check(filename, fset, []*ast.File{file}, nil)
|
||||
}
|
||||
|
||||
@ -180,7 +185,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)
|
||||
"bug251.go", // issue #34333 which was exposed with fix for #34151
|
||||
"issue42058a.go", // go/types does not have constraints on channel element size
|
||||
|
35
src/go/types/testdata/go1_12.src
vendored
Normal file
35
src/go/types/testdata/go1_12.src
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// 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"
|
||||
)
|
||||
|
82
src/go/types/version.go
Normal file
82
src/go/types/version.go
Normal file
@ -0,0 +1,82 @@
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"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 *ast.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, _InvalidLit, "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, _InvalidLit, "binary literals requires go1.13 or later")
|
||||
return
|
||||
}
|
||||
if radix == 'o' || radix == 'O' {
|
||||
check.errorf(lit, _InvalidLit, "0o/0O-style octal literals requires go1.13 or later")
|
||||
return
|
||||
}
|
||||
if lit.Kind != token.INT && (radix == 'x' || radix == 'X') {
|
||||
check.errorf(lit, _InvalidLit, "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]*)$`)
|
Loading…
Reference in New Issue
Block a user