mirror of
https://github.com/golang/go
synced 2024-11-26 19:31:17 -07:00
[dev.typeparams] cmd/compile/internal/types2: add support for language version checking
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>
This commit is contained in:
parent
370e9f5843
commit
1ff2fdaaf1
@ -99,6 +99,13 @@ type ImporterFrom interface {
|
|||||||
// A Config specifies the configuration for type checking.
|
// A Config specifies the configuration for type checking.
|
||||||
// The zero value for Config is a ready-to-use default configuration.
|
// The zero value for Config is a ready-to-use default configuration.
|
||||||
type Config struct {
|
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
|
// If IgnoreFuncBodies is set, function bodies are not
|
||||||
// type-checked.
|
// type-checked.
|
||||||
IgnoreFuncBodies bool
|
IgnoreFuncBodies bool
|
||||||
|
@ -88,12 +88,13 @@ type Checker struct {
|
|||||||
conf *Config
|
conf *Config
|
||||||
pkg *Package
|
pkg *Package
|
||||||
*Info
|
*Info
|
||||||
nextId uint64 // unique Id for type parameters (first valid Id is 1)
|
version version // accepted language version
|
||||||
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
|
nextId uint64 // unique Id for type parameters (first valid Id is 1)
|
||||||
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
|
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
|
||||||
posMap map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions
|
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
|
||||||
typMap map[string]*Named // maps an instantiated named type hash to a *Named type
|
posMap map[*Interface][]syntax.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)
|
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
|
// information collected during type-checking of a set of package files
|
||||||
// (initialized by Files, valid only for the duration of check.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)
|
info = new(Info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version, err := parseGoVersion(conf.GoVersion)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
|
||||||
|
}
|
||||||
|
|
||||||
return &Checker{
|
return &Checker{
|
||||||
conf: conf,
|
conf: conf,
|
||||||
pkg: pkg,
|
pkg: pkg,
|
||||||
Info: info,
|
Info: info,
|
||||||
nextId: 1,
|
version: version,
|
||||||
objMap: make(map[Object]*declInfo),
|
nextId: 1,
|
||||||
impMap: make(map[importKey]*Package),
|
objMap: make(map[Object]*declInfo),
|
||||||
posMap: make(map[*Interface][]syntax.Pos),
|
impMap: make(map[importKey]*Package),
|
||||||
typMap: make(map[string]*Named),
|
posMap: make(map[*Interface][]syntax.Pos),
|
||||||
pkgCnt: make(map[string]int),
|
typMap: make(map[string]*Named),
|
||||||
|
pkgCnt: make(map[string]int),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
haltOnError = flag.Bool("halt", false, "halt on error")
|
haltOnError = flag.Bool("halt", false, "halt on error")
|
||||||
listErrors = flag.Bool("errlist", false, "list errors")
|
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) {
|
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 {
|
if len(sources) == 0 {
|
||||||
t.Fatal("no source files")
|
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
|
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 {
|
if *listErrors && len(errlist) > 0 {
|
||||||
t.Errorf("--- %s:", pkgName)
|
t.Errorf("--- %s:", pkgName)
|
||||||
for _, err := range errlist {
|
for _, err := range errlist {
|
||||||
@ -109,6 +129,7 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
|
|||||||
|
|
||||||
// typecheck and collect typechecker errors
|
// typecheck and collect typechecker errors
|
||||||
var conf Config
|
var conf Config
|
||||||
|
conf.GoVersion = goVersion
|
||||||
conf.AcceptMethodTypeParams = true
|
conf.AcceptMethodTypeParams = true
|
||||||
conf.InferFromConstraints = true
|
conf.InferFromConstraints = true
|
||||||
// special case for importC.src
|
// 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.
|
// 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) {
|
func TestCheck(t *testing.T) {
|
||||||
if *testFiles == "" {
|
if *testFiles == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
testenv.MustHaveGoBuild(t)
|
testenv.MustHaveGoBuild(t)
|
||||||
DefPredeclaredTestFuncs()
|
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
|
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])
|
fmt.Printf("\t%s\n", files[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkFiles(t, files, colDelta, false)
|
checkFiles(t, files, "", colDelta, false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,6 +293,6 @@ func testDir(t *testing.T, colDelta uint, dir string) {
|
|||||||
if testing.Verbose() {
|
if testing.Verbose() {
|
||||||
fmt.Printf("%3d %s\n", count, path)
|
fmt.Printf("%3d %s\n", count, path)
|
||||||
}
|
}
|
||||||
checkFiles(t, []string{path}, colDelta, false)
|
checkFiles(t, []string{path}, "", colDelta, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
check.invalidOpf(y, "shift count %s must be integer", y)
|
||||||
x.mode = invalid
|
x.mode = invalid
|
||||||
return
|
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_ {
|
if x.mode == constant_ {
|
||||||
@ -1185,6 +1189,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
|
|||||||
}
|
}
|
||||||
switch e.Kind {
|
switch e.Kind {
|
||||||
case syntax.IntLit, syntax.FloatLit, syntax.ImagLit:
|
case syntax.IntLit, syntax.FloatLit, syntax.ImagLit:
|
||||||
|
check.langCompat(e)
|
||||||
// The max. mantissa precision for untyped numeric values
|
// The max. mantissa precision for untyped numeric values
|
||||||
// is 512 bits, or 4048 bits for each of the two integer
|
// is 512 bits, or 4048 bits for each of the two integer
|
||||||
// parts of a fraction for floating-point numbers that are
|
// parts of a fraction for floating-point numbers that are
|
||||||
|
@ -110,6 +110,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
|
|||||||
// get per-file instructions
|
// get per-file instructions
|
||||||
expectErrors := false
|
expectErrors := false
|
||||||
filename := filepath.Join(path, f.Name())
|
filename := filepath.Join(path, f.Name())
|
||||||
|
goVersion := ""
|
||||||
if comment := firstComment(filename); comment != "" {
|
if comment := firstComment(filename); comment != "" {
|
||||||
fields := strings.Fields(comment)
|
fields := strings.Fields(comment)
|
||||||
switch fields[0] {
|
switch fields[0] {
|
||||||
@ -119,13 +120,17 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
|
|||||||
expectErrors = true
|
expectErrors = true
|
||||||
for _, arg := range fields[1:] {
|
for _, arg := range fields[1:] {
|
||||||
if arg == "-0" || arg == "-+" || arg == "-std" {
|
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
|
// or marked as compiling runtime/stdlib, which is only done
|
||||||
// to trigger runtime/stdlib-only error output.
|
// to trigger runtime/stdlib-only error output.
|
||||||
// In both cases, the code should typecheck.
|
// In both cases, the code should typecheck.
|
||||||
expectErrors = false
|
expectErrors = false
|
||||||
break
|
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)
|
file, err := syntax.ParseFile(filename, nil, nil, 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
conf := Config{Importer: stdLibImporter}
|
conf := Config{GoVersion: goVersion, Importer: stdLibImporter}
|
||||||
_, err = conf.Check(filename, []*syntax.File{file}, nil)
|
_, 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
|
"issue22200b.go", // go/types does not have constraints on stack size
|
||||||
"issue25507.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
|
"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)
|
"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
|
"issue42058a.go", // go/types does not have constraints on channel element size
|
||||||
"issue42058b.go", // go/types does not have constraints on channel element size
|
"issue42058b.go", // go/types does not have constraints on channel element size
|
||||||
|
34
src/cmd/compile/internal/types2/testdata/go1_12.src
vendored
Normal file
34
src/cmd/compile/internal/types2/testdata/go1_12.src
vendored
Normal file
@ -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"
|
||||||
|
)
|
81
src/cmd/compile/internal/types2/version.go
Normal file
81
src/cmd/compile/internal/types2/version.go
Normal file
@ -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]*)$`)
|
Loading…
Reference in New Issue
Block a user