Skip to content

Commit 1ff2fda

Browse files
committed
[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 <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 370e9f5 commit 1ff2fda

File tree

7 files changed

+183
-23
lines changed

7 files changed

+183
-23
lines changed

src/cmd/compile/internal/types2/api.go

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

src/cmd/compile/internal/types2/check.go

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,13 @@ type Checker struct {
8888
conf *Config
8989
pkg *Package
9090
*Info
91-
nextId uint64 // unique Id for type parameters (first valid Id is 1)
92-
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
93-
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
94-
posMap map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions
95-
typMap map[string]*Named // maps an instantiated named type hash to a *Named type
96-
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
91+
version version // accepted language version
92+
nextId uint64 // unique Id for type parameters (first valid Id is 1)
93+
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
94+
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
95+
posMap map[*Interface][]syntax.Pos // maps interface types to lists of embedded interface positions
96+
typMap map[string]*Named // maps an instantiated named type hash to a *Named type
97+
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
9798

9899
// information collected during type-checking of a set of package files
99100
// (initialized by Files, valid only for the duration of check.Files;
@@ -182,16 +183,22 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
182183
info = new(Info)
183184
}
184185

186+
version, err := parseGoVersion(conf.GoVersion)
187+
if err != nil {
188+
panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
189+
}
190+
185191
return &Checker{
186-
conf: conf,
187-
pkg: pkg,
188-
Info: info,
189-
nextId: 1,
190-
objMap: make(map[Object]*declInfo),
191-
impMap: make(map[importKey]*Package),
192-
posMap: make(map[*Interface][]syntax.Pos),
193-
typMap: make(map[string]*Named),
194-
pkgCnt: make(map[string]int),
192+
conf: conf,
193+
pkg: pkg,
194+
Info: info,
195+
version: version,
196+
nextId: 1,
197+
objMap: make(map[Object]*declInfo),
198+
impMap: make(map[importKey]*Package),
199+
posMap: make(map[*Interface][]syntax.Pos),
200+
typMap: make(map[string]*Named),
201+
pkgCnt: make(map[string]int),
195202
}
196203
}
197204

src/cmd/compile/internal/types2/check_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ import (
4444
var (
4545
haltOnError = flag.Bool("halt", false, "halt on error")
4646
listErrors = flag.Bool("errlist", false, "list errors")
47-
testFiles = flag.String("files", "", "space-separated list of test files")
47+
testFiles = flag.String("files", "", "comma-separated list of test files")
48+
goVersion = flag.String("lang", "", "Go language version (e.g. \"go1.12\"")
4849
)
4950

5051
func parseFiles(t *testing.T, filenames []string, mode syntax.Mode) ([]*syntax.File, []error) {
@@ -83,7 +84,21 @@ func delta(x, y uint) uint {
8384
}
8485
}
8586

86-
func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
87+
// goVersionRx matches a Go version string using '_', e.g. "go1_12".
88+
var goVersionRx = regexp.MustCompile(`^go[1-9][0-9]*_(0|[1-9][0-9]*)$`)
89+
90+
// asGoVersion returns a regular Go language version string
91+
// if s is a Go version string using '_' rather than '.' to
92+
// separate the major and minor version numbers (e.g. "go1_12").
93+
// Otherwise it returns the empty string.
94+
func asGoVersion(s string) string {
95+
if goVersionRx.MatchString(s) {
96+
return strings.Replace(s, "_", ".", 1)
97+
}
98+
return ""
99+
}
100+
101+
func checkFiles(t *testing.T, sources []string, goVersion string, colDelta uint, trace bool) {
87102
if len(sources) == 0 {
88103
t.Fatal("no source files")
89104
}
@@ -100,6 +115,11 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
100115
pkgName = files[0].PkgName.Value
101116
}
102117

118+
// if no Go version is given, consider the package name
119+
if goVersion == "" {
120+
goVersion = asGoVersion(pkgName)
121+
}
122+
103123
if *listErrors && len(errlist) > 0 {
104124
t.Errorf("--- %s:", pkgName)
105125
for _, err := range errlist {
@@ -109,6 +129,7 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
109129

110130
// typecheck and collect typechecker errors
111131
var conf Config
132+
conf.GoVersion = goVersion
112133
conf.AcceptMethodTypeParams = true
113134
conf.InferFromConstraints = true
114135
// special case for importC.src
@@ -220,13 +241,14 @@ func checkFiles(t *testing.T, sources []string, colDelta uint, trace bool) {
220241
}
221242

222243
// TestCheck is for manual testing of selected input files, provided with -files.
244+
// The accepted Go language version can be controlled with the -lang flag.
223245
func TestCheck(t *testing.T) {
224246
if *testFiles == "" {
225247
return
226248
}
227249
testenv.MustHaveGoBuild(t)
228250
DefPredeclaredTestFuncs()
229-
checkFiles(t, strings.Split(*testFiles, " "), 0, testing.Verbose())
251+
checkFiles(t, strings.Split(*testFiles, ","), *goVersion, 0, testing.Verbose())
230252
}
231253

232254
func TestTestdata(t *testing.T) { DefPredeclaredTestFuncs(); testDir(t, 75, "testdata") } // TODO(gri) narrow column tolerance
@@ -263,14 +285,14 @@ func testDir(t *testing.T, colDelta uint, dir string) {
263285
fmt.Printf("\t%s\n", files[i])
264286
}
265287
}
266-
checkFiles(t, files, colDelta, false)
288+
checkFiles(t, files, "", colDelta, false)
267289
continue
268290
}
269291

270292
// otherwise, fi is a stand-alone file
271293
if testing.Verbose() {
272294
fmt.Printf("%3d %s\n", count, path)
273295
}
274-
checkFiles(t, []string{path}, colDelta, false)
296+
checkFiles(t, []string{path}, "", colDelta, false)
275297
}
276298
}

src/cmd/compile/internal/types2/expr.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,10 @@ func (check *Checker) shift(x, y *operand, e syntax.Expr, op syntax.Operator) {
816816
check.invalidOpf(y, "shift count %s must be integer", y)
817817
x.mode = invalid
818818
return
819+
} else if !isUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) {
820+
check.invalidOpf(y, "signed shift count %s requires go1.13 or later", y)
821+
x.mode = invalid
822+
return
819823
}
820824

821825
if x.mode == constant_ {
@@ -1185,6 +1189,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
11851189
}
11861190
switch e.Kind {
11871191
case syntax.IntLit, syntax.FloatLit, syntax.ImagLit:
1192+
check.langCompat(e)
11881193
// The max. mantissa precision for untyped numeric values
11891194
// is 512 bits, or 4048 bits for each of the two integer
11901195
// parts of a fraction for floating-point numbers that are

src/cmd/compile/internal/types2/stdlib_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
110110
// get per-file instructions
111111
expectErrors := false
112112
filename := filepath.Join(path, f.Name())
113+
goVersion := ""
113114
if comment := firstComment(filename); comment != "" {
114115
fields := strings.Fields(comment)
115116
switch fields[0] {
@@ -119,13 +120,17 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
119120
expectErrors = true
120121
for _, arg := range fields[1:] {
121122
if arg == "-0" || arg == "-+" || arg == "-std" {
122-
// Marked explicitly as not expected errors (-0),
123+
// Marked explicitly as not expecting errors (-0),
123124
// or marked as compiling runtime/stdlib, which is only done
124125
// to trigger runtime/stdlib-only error output.
125126
// In both cases, the code should typecheck.
126127
expectErrors = false
127128
break
128129
}
130+
const prefix = "-lang="
131+
if strings.HasPrefix(arg, prefix) {
132+
goVersion = arg[len(prefix):]
133+
}
129134
}
130135
}
131136
}
@@ -136,7 +141,7 @@ func testTestDir(t *testing.T, path string, ignore ...string) {
136141
}
137142
file, err := syntax.ParseFile(filename, nil, nil, 0)
138143
if err == nil {
139-
conf := Config{Importer: stdLibImporter}
144+
conf := Config{GoVersion: goVersion, Importer: stdLibImporter}
140145
_, err = conf.Check(filename, []*syntax.File{file}, nil)
141146
}
142147

@@ -187,7 +192,6 @@ func TestStdFixed(t *testing.T) {
187192
"issue22200b.go", // go/types does not have constraints on stack size
188193
"issue25507.go", // go/types does not have constraints on stack size
189194
"issue20780.go", // go/types does not have constraints on stack size
190-
"issue31747.go", // go/types does not have constraints on language level (-lang=go1.12) (see #31793)
191195
"issue34329.go", // go/types does not have constraints on language level (-lang=go1.13) (see #31793)
192196
"issue42058a.go", // go/types does not have constraints on channel element size
193197
"issue42058b.go", // go/types does not have constraints on channel element size
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 types2
6+
7+
import (
8+
"cmd/compile/internal/syntax"
9+
"fmt"
10+
"regexp"
11+
"strconv"
12+
"strings"
13+
)
14+
15+
// langCompat reports an error if the representation of a numeric
16+
// literal is not compatible with the current language version.
17+
func (check *Checker) langCompat(lit *syntax.BasicLit) {
18+
s := lit.Value
19+
if len(s) <= 2 || check.allowVersion(check.pkg, 1, 13) {
20+
return
21+
}
22+
// len(s) > 2
23+
if strings.Contains(s, "_") {
24+
check.errorf(lit, "underscores in numeric literals requires go1.13 or later")
25+
return
26+
}
27+
if s[0] != '0' {
28+
return
29+
}
30+
radix := s[1]
31+
if radix == 'b' || radix == 'B' {
32+
check.errorf(lit, "binary literals requires go1.13 or later")
33+
return
34+
}
35+
if radix == 'o' || radix == 'O' {
36+
check.errorf(lit, "0o/0O-style octal literals requires go1.13 or later")
37+
return
38+
}
39+
if lit.Kind != syntax.IntLit && (radix == 'x' || radix == 'X') {
40+
check.errorf(lit, "hexadecimal floating-point literals requires go1.13 or later")
41+
}
42+
}
43+
44+
// allowVersion reports whether the given package
45+
// is allowed to use version major.minor.
46+
func (check *Checker) allowVersion(pkg *Package, major, minor int) bool {
47+
// We assume that imported packages have all been checked,
48+
// so we only have to check for the local package.
49+
if pkg != check.pkg {
50+
return true
51+
}
52+
ma, mi := check.version.major, check.version.minor
53+
return ma == 0 && mi == 0 || ma > major || ma == major && mi >= minor
54+
}
55+
56+
type version struct {
57+
major, minor int
58+
}
59+
60+
// parseGoVersion parses a Go version string (such as "go1.12")
61+
// and returns the version, or an error. If s is the empty
62+
// string, the version is 0.0.
63+
func parseGoVersion(s string) (v version, err error) {
64+
if s == "" {
65+
return
66+
}
67+
matches := goVersionRx.FindStringSubmatch(s)
68+
if matches == nil {
69+
err = fmt.Errorf(`should be something like "go1.12"`)
70+
return
71+
}
72+
v.major, err = strconv.Atoi(matches[1])
73+
if err != nil {
74+
return
75+
}
76+
v.minor, err = strconv.Atoi(matches[2])
77+
return
78+
}
79+
80+
// goVersionRx matches a Go version string, e.g. "go1.12".
81+
var goVersionRx = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`)

0 commit comments

Comments
 (0)