Skip to content

Commit b81efb7

Browse files
committed
[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 <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent a7e9b4b commit b81efb7

File tree

8 files changed

+186
-23
lines changed

8 files changed

+186
-23
lines changed

src/go/build/deps_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ var depsRules = `
285285
math/big, go/token
286286
< go/constant;
287287
288-
container/heap, go/constant, go/parser
288+
container/heap, go/constant, go/parser, regexp
289289
< go/types;
290290
291291
FMT

src/go/types/api.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ type ImporterFrom interface {
101101
// A Config specifies the configuration for type checking.
102102
// The zero value for Config is a ready-to-use default configuration.
103103
type Config struct {
104+
// GoVersion describes the accepted Go language version. The string
105+
// must follow the format "go%d.%d" (e.g. "go1.12") or it must be
106+
// empty; an empty string indicates the latest language version.
107+
// If the format is invalid, invoking the type checker will cause a
108+
// panic.
109+
GoVersion string
110+
104111
// If IgnoreFuncBodies is set, function bodies are not
105112
// type-checked.
106113
IgnoreFuncBodies bool

src/go/types/check.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package types
88

99
import (
1010
"errors"
11+
"fmt"
1112
"go/ast"
1213
"go/constant"
1314
"go/token"
@@ -84,10 +85,11 @@ type Checker struct {
8485
fset *token.FileSet
8586
pkg *Package
8687
*Info
87-
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
88-
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
89-
posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
90-
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
88+
version version // accepted language version
89+
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
90+
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
91+
posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
92+
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
9193

9294
// information collected during type-checking of a set of package files
9395
// (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
176178
info = new(Info)
177179
}
178180

181+
version, err := parseGoVersion(conf.GoVersion)
182+
if err != nil {
183+
panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
184+
}
185+
179186
return &Checker{
180-
conf: conf,
181-
fset: fset,
182-
pkg: pkg,
183-
Info: info,
184-
objMap: make(map[Object]*declInfo),
185-
impMap: make(map[importKey]*Package),
186-
posMap: make(map[*Interface][]token.Pos),
187-
pkgCnt: make(map[string]int),
187+
conf: conf,
188+
fset: fset,
189+
pkg: pkg,
190+
Info: info,
191+
version: version,
192+
objMap: make(map[Object]*declInfo),
193+
impMap: make(map[importKey]*Package),
194+
posMap: make(map[*Interface][]token.Pos),
195+
pkgCnt: make(map[string]int),
188196
}
189197
}
190198

src/go/types/check_test.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ import (
4747
var (
4848
haltOnError = flag.Bool("halt", false, "halt on error")
4949
listErrors = flag.Bool("errlist", false, "list errors")
50-
testFiles = flag.String("files", "", "space-separated list of test files")
50+
testFiles = flag.String("files", "", "comma-separated list of test files")
51+
goVersion = flag.String("lang", "", "Go language version (e.g. \"go1.12\"")
5152
)
5253

5354
var fset = token.NewFileSet()
@@ -188,7 +189,21 @@ func eliminate(t *testing.T, errmap map[string][]string, errlist []error) {
188189
}
189190
}
190191

191-
func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
192+
// goVersionRx matches a Go version string using '_', e.g. "go1_12".
193+
var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*_(0|[1-9][0-9]*)$`)
194+
195+
// asGoVersion returns a regular Go language version string
196+
// if s is a Go version string using '_' rather than '.' to
197+
// separate the major and minor version numbers (e.g. "go1_12").
198+
// Otherwise it returns the empty string.
199+
func asGoVersion(s string) string {
200+
if goVersionRx.MatchString(s) {
201+
return strings.Replace(s, "_", ".", 1)
202+
}
203+
return ""
204+
}
205+
206+
func checkFiles(t *testing.T, goVersion string, filenames []string, srcs [][]byte) {
192207
if len(filenames) == 0 {
193208
t.Fatal("no source files")
194209
}
@@ -201,6 +216,11 @@ func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
201216
pkgName = files[0].Name.Name
202217
}
203218

219+
// if no Go version is given, consider the package name
220+
if goVersion == "" {
221+
goVersion = asGoVersion(pkgName)
222+
}
223+
204224
if *listErrors && len(errlist) > 0 {
205225
t.Errorf("--- %s:", pkgName)
206226
for _, err := range errlist {
@@ -210,6 +230,7 @@ func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
210230

211231
// typecheck and collect typechecker errors
212232
var conf Config
233+
conf.GoVersion = goVersion
213234

214235
// special case for importC.src
215236
if len(filenames) == 1 {
@@ -267,19 +288,20 @@ func checkFiles(t *testing.T, filenames []string, srcs [][]byte) {
267288
}
268289

269290
// TestCheck is for manual testing of selected input files, provided with -files.
291+
// The accepted Go language version can be controlled with the -lang flag.
270292
func TestCheck(t *testing.T) {
271293
if *testFiles == "" {
272294
return
273295
}
274296
testenv.MustHaveGoBuild(t)
275297
DefPredeclaredTestFuncs()
276-
testPkg(t, strings.Split(*testFiles, " "))
298+
testPkg(t, strings.Split(*testFiles, ","), *goVersion)
277299
}
278300

279301
func TestLongConstants(t *testing.T) {
280302
format := "package longconst\n\nconst _ = %s\nconst _ = %s // ERROR excessively long constant"
281303
src := fmt.Sprintf(format, strings.Repeat("1", 9999), strings.Repeat("1", 10001))
282-
checkFiles(t, []string{"longconst.go"}, [][]byte{[]byte(src)})
304+
checkFiles(t, "", []string{"longconst.go"}, [][]byte{[]byte(src)})
283305
}
284306

285307
func TestTestdata(t *testing.T) { DefPredeclaredTestFuncs(); testDir(t, "testdata") }
@@ -312,12 +334,12 @@ func testDir(t *testing.T, dir string) {
312334
filenames = []string{path}
313335
}
314336
t.Run(filepath.Base(path), func(t *testing.T) {
315-
testPkg(t, filenames)
337+
testPkg(t, filenames, "")
316338
})
317339
}
318340
}
319341

320-
func testPkg(t *testing.T, filenames []string) {
342+
func testPkg(t *testing.T, filenames []string, goVersion string) {
321343
srcs := make([][]byte, len(filenames))
322344
for i, filename := range filenames {
323345
src, err := os.ReadFile(filename)
@@ -326,5 +348,5 @@ func testPkg(t *testing.T, filenames []string) {
326348
}
327349
srcs[i] = src
328350
}
329-
checkFiles(t, filenames, srcs)
351+
checkFiles(t, goVersion, filenames, srcs)
330352
}

src/go/types/expr.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,10 @@ func (check *Checker) shift(x, y *operand, e ast.Expr, op token.Token) {
772772
check.invalidOp(y, _InvalidShiftCount, "shift count %s must be integer", y)
773773
x.mode = invalid
774774
return
775+
} else if !isUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) {
776+
check.invalidOp(y, _InvalidShiftCount, "signed shift count %s requires go1.13 or later", y)
777+
x.mode = invalid
778+
return
775779
}
776780

777781
var yval constant.Value
@@ -1152,6 +1156,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
11521156
case *ast.BasicLit:
11531157
switch e.Kind {
11541158
case token.INT, token.FLOAT, token.IMAG:
1159+
check.langCompat(e)
11551160
// The max. mantissa precision for untyped numeric values
11561161
// is 512 bits, or 4048 bits for each of the two integer
11571162
// parts of a fraction for floating-point numbers that are

src/go/types/stdlib_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
106106
// get per-file instructions
107107
expectErrors := false
108108
filename := filepath.Join(path, f.Name())
109+
goVersion := ""
109110
if comment := firstComment(filename); comment != "" {
110111
fields := strings.Fields(comment)
111112
switch fields[0] {
@@ -115,21 +116,25 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
115116
expectErrors = true
116117
for _, arg := range fields[1:] {
117118
if arg == "-0" || arg == "-+" || arg == "-std" {
118-
// Marked explicitly as not expected errors (-0),
119+
// Marked explicitly as not expecting errors (-0),
119120
// or marked as compiling runtime/stdlib, which is only done
120121
// to trigger runtime/stdlib-only error output.
121122
// In both cases, the code should typecheck.
122123
expectErrors = false
123124
break
124125
}
126+
const prefix = "-lang="
127+
if strings.HasPrefix(arg, prefix) {
128+
goVersion = arg[len(prefix):]
129+
}
125130
}
126131
}
127132
}
128133

129134
// parse and type-check file
130135
file, err := parser.ParseFile(fset, filename, nil, 0)
131136
if err == nil {
132-
conf := Config{Importer: stdLibImporter}
137+
conf := Config{GoVersion: goVersion, Importer: stdLibImporter}
133138
_, err = conf.Check(filename, fset, []*ast.File{file}, nil)
134139
}
135140

@@ -180,7 +185,6 @@ func TestStdFixed(t *testing.T) {
180185
"issue22200b.go", // go/types does not have constraints on stack size
181186
"issue25507.go", // go/types does not have constraints on stack size
182187
"issue20780.go", // go/types does not have constraints on stack size
183-
"issue31747.go", // go/types does not have constraints on language level (-lang=go1.12) (see #31793)
184188
"issue34329.go", // go/types does not have constraints on language level (-lang=go1.13) (see #31793)
185189
"bug251.go", // issue #34333 which was exposed with fix for #34151
186190
"issue42058a.go", // go/types does not have constraints on channel element size

src/go/types/testdata/go1_12.src

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Check Go language version-specific errors.
6+
7+
package go1_12 // go1.12
8+
9+
// numeric literals
10+
const (
11+
_ = 1_000 // ERROR "underscores in numeric literals requires go1.13 or later"
12+
_ = 0b111 // ERROR "binary literals requires go1.13 or later"
13+
_ = 0o567 // ERROR "0o/0O-style octal literals requires go1.13 or later"
14+
_ = 0xabc // ok
15+
_ = 0x0p1 // ERROR "hexadecimal floating-point literals requires go1.13 or later"
16+
17+
_ = 0B111 // ERROR "binary"
18+
_ = 0O567 // ERROR "octal"
19+
_ = 0Xabc // ok
20+
_ = 0X0P1 // ERROR "hexadecimal floating-point"
21+
22+
_ = 1_000i // ERROR "underscores"
23+
_ = 0b111i // ERROR "binary"
24+
_ = 0o567i // ERROR "octal"
25+
_ = 0xabci // ERROR "hexadecimal floating-point"
26+
_ = 0x0p1i // ERROR "hexadecimal floating-point"
27+
)
28+
29+
// signed shift counts
30+
var (
31+
s int
32+
_ = 1 << s // ERROR "invalid operation: signed shift count s \(variable of type int\) requires go1.13 or later"
33+
_ = 1 >> s // ERROR "signed shift count"
34+
)
35+

src/go/types/version.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package types
6+
7+
import (
8+
"fmt"
9+
"go/ast"
10+
"go/token"
11+
"regexp"
12+
"strconv"
13+
"strings"
14+
)
15+
16+
// langCompat reports an error if the representation of a numeric
17+
// literal is not compatible with the current language version.
18+
func (check *Checker) langCompat(lit *ast.BasicLit) {
19+
s := lit.Value
20+
if len(s) <= 2 || check.allowVersion(check.pkg, 1, 13) {
21+
return
22+
}
23+
// len(s) > 2
24+
if strings.Contains(s, "_") {
25+
check.errorf(lit, _InvalidLit, "underscores in numeric literals requires go1.13 or later")
26+
return
27+
}
28+
if s[0] != '0' {
29+
return
30+
}
31+
radix := s[1]
32+
if radix == 'b' || radix == 'B' {
33+
check.errorf(lit, _InvalidLit, "binary literals requires go1.13 or later")
34+
return
35+
}
36+
if radix == 'o' || radix == 'O' {
37+
check.errorf(lit, _InvalidLit, "0o/0O-style octal literals requires go1.13 or later")
38+
return
39+
}
40+
if lit.Kind != token.INT && (radix == 'x' || radix == 'X') {
41+
check.errorf(lit, _InvalidLit, "hexadecimal floating-point literals requires go1.13 or later")
42+
}
43+
}
44+
45+
// allowVersion reports whether the given package
46+
// is allowed to use version major.minor.
47+
func (check *Checker) allowVersion(pkg *Package, major, minor int) bool {
48+
// We assume that imported packages have all been checked,
49+
// so we only have to check for the local package.
50+
if pkg != check.pkg {
51+
return true
52+
}
53+
ma, mi := check.version.major, check.version.minor
54+
return ma == 0 && mi == 0 || ma > major || ma == major && mi >= minor
55+
}
56+
57+
type version struct {
58+
major, minor int
59+
}
60+
61+
// parseGoVersion parses a Go version string (such as "go1.12")
62+
// and returns the version, or an error. If s is the empty
63+
// string, the version is 0.0.
64+
func parseGoVersion(s string) (v version, err error) {
65+
if s == "" {
66+
return
67+
}
68+
matches := goVersionRx.FindStringSubmatch(s)
69+
if matches == nil {
70+
err = fmt.Errorf(`should be something like "go1.12"`)
71+
return
72+
}
73+
v.major, err = strconv.Atoi(matches[1])
74+
if err != nil {
75+
return
76+
}
77+
v.minor, err = strconv.Atoi(matches[2])
78+
return
79+
}
80+
81+
// goVersionRx matches a Go version string, e.g. "go1.12".
82+
var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)

0 commit comments

Comments
 (0)