Skip to content

Commit b490bdc

Browse files
committed
go/types: record Config.GoVersion for reporting in Package.GoVersion method
Clients of go/types, such as analyzers, may need to know which specific Go version a package is written for. Record that information in the Package and expose it using the new GoVersion method. Update parseGoVersion to handle the new Go versions that may be passed around starting in Go 1.21.0: versions like "go1.21.0" and "go1.21rc2". This is not strictly necessary today, but it adds some valuable future-proofing. While we are here, change NewChecker from panicking on invalid version to saving an error for returning later from Files. Go versions are now likely to be coming from a variety of sources, not just hard-coded in calls to NewChecker, making a panic inappropriate. For #61174. Fixes #61175. Change-Id: Ibe41fe207c1b6e71064b1fe448ac55776089c541 Reviewed-on: https://go-review.googlesource.com/c/go/+/507975 Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 36ea4f9 commit b490bdc

File tree

10 files changed

+105
-55
lines changed

10 files changed

+105
-55
lines changed

api/go1.21.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ pkg go/build, type Package struct, Directives []Directive #56986
174174
pkg go/build, type Package struct, TestDirectives []Directive #56986
175175
pkg go/build, type Package struct, XTestDirectives []Directive #56986
176176
pkg go/token, method (*File) Lines() []int #57708
177+
pkg go/types, method (*Package) GoVersion() string #61175
177178
pkg html/template, const ErrJSTemplate = 12 #59584
178179
pkg html/template, const ErrJSTemplate ErrorCode #59584
179180
pkg io/fs, func FormatDirEntry(DirEntry) string #54451

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import (
1010

1111
// A Package describes a Go package.
1212
type Package struct {
13-
path string
14-
name string
15-
scope *Scope
16-
imports []*Package
17-
complete bool
18-
fake bool // scope lookup errors are silently dropped if package is fake (internal use only)
19-
cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go
13+
path string
14+
name string
15+
scope *Scope
16+
imports []*Package
17+
complete bool
18+
fake bool // scope lookup errors are silently dropped if package is fake (internal use only)
19+
cgo bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go
20+
goVersion string // minimum Go version required for package (by Config.GoVersion, typically from go.mod)
2021
}
2122

2223
// NewPackage returns a new Package for the given package path and name.
@@ -35,6 +36,12 @@ func (pkg *Package) Name() string { return pkg.name }
3536
// SetName sets the package name.
3637
func (pkg *Package) SetName(name string) { pkg.name = name }
3738

39+
// GoVersion returns the minimum Go version required by this package.
40+
// If the minimum version is unknown, GoVersion returns the empty string.
41+
// Individual source files may specify a different minimum Go version,
42+
// as reported in the [go/ast.File.GoVersion] field.
43+
func (pkg *Package) GoVersion() string { return pkg.goVersion }
44+
3845
// Scope returns the (complete or incomplete) package scope
3946
// holding the objects declared at package level (TypeNames,
4047
// Consts, Vars, and Funcs).

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestSizeof(t *testing.T) {
4747

4848
// Misc
4949
{Scope{}, 60, 104},
50-
{Package{}, 36, 72},
50+
{Package{}, 44, 88},
5151
{_TypeSet{}, 28, 56},
5252
}
5353

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package types2
66

77
import (
88
"cmd/compile/internal/syntax"
9-
"errors"
109
"fmt"
1110
"strings"
1211
)
@@ -44,31 +43,32 @@ var (
4443
go1_21 = version{1, 21}
4544
)
4645

47-
var errVersionSyntax = errors.New("invalid Go version syntax")
48-
4946
// parseGoVersion parses a Go version string (such as "go1.12")
5047
// and returns the version, or an error. If s is the empty
5148
// string, the version is 0.0.
5249
func parseGoVersion(s string) (v version, err error) {
50+
bad := func() (version, error) {
51+
return version{}, fmt.Errorf("invalid Go version syntax %q", s)
52+
}
5353
if s == "" {
5454
return
5555
}
5656
if !strings.HasPrefix(s, "go") {
57-
return version{}, errVersionSyntax
57+
return bad()
5858
}
5959
s = s[len("go"):]
6060
i := 0
6161
for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
6262
if i >= 10 || i == 0 && s[i] == '0' {
63-
return version{}, errVersionSyntax
63+
return bad()
6464
}
6565
v.major = 10*v.major + int(s[i]) - '0'
6666
}
6767
if i > 0 && i == len(s) {
6868
return
6969
}
7070
if i == 0 || s[i] != '.' {
71-
return version{}, errVersionSyntax
71+
return bad()
7272
}
7373
s = s[i+1:]
7474
if s == "0" {
@@ -81,14 +81,15 @@ func parseGoVersion(s string) (v version, err error) {
8181
i = 0
8282
for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
8383
if i >= 10 || i == 0 && s[i] == '0' {
84-
return version{}, errVersionSyntax
84+
return bad()
8585
}
8686
v.minor = 10*v.minor + int(s[i]) - '0'
8787
}
88-
if i > 0 && i == len(s) {
89-
return
90-
}
91-
return version{}, errVersionSyntax
88+
// Accept any suffix after the minor number.
89+
// We are only looking for the language version (major.minor)
90+
// but want to accept any valid Go version, like go1.21.0
91+
// and go1.21rc2.
92+
return
9293
}
9394

9495
// langCompat reports an error if the representation of a numeric

src/go/build/deps_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ var depsRules = `
286286
math/big, go/token
287287
< go/constant;
288288
289-
container/heap, go/constant, go/parser, internal/types/errors
289+
container/heap, go/constant, go/parser, internal/goversion, internal/types/errors
290290
< go/types;
291291
292292
# The vast majority of standard library packages should not be resorting to regexp.

src/go/types/check.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"go/ast"
1313
"go/constant"
1414
"go/token"
15+
"internal/goversion"
1516
. "internal/types/errors"
1617
)
1718

@@ -98,11 +99,12 @@ type Checker struct {
9899
fset *token.FileSet
99100
pkg *Package
100101
*Info
101-
version version // accepted language version
102-
nextID uint64 // unique Id for type parameters (first valid Id is 1)
103-
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
104-
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
105-
valids instanceLookup // valid *Named (incl. instantiated) types per the validType check
102+
version version // accepted language version
103+
versionErr error // version error, delayed from NewChecker
104+
nextID uint64 // unique Id for type parameters (first valid Id is 1)
105+
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
106+
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
107+
valids instanceLookup // valid *Named (incl. instantiated) types per the validType check
106108

107109
// pkgPathMap maps package names to the set of distinct import paths we've
108110
// seen for that name, anywhere in the import graph. It is used for
@@ -233,20 +235,21 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
233235
info = new(Info)
234236
}
235237

236-
version, err := parseGoVersion(conf.GoVersion)
237-
if err != nil {
238-
panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
238+
version, versionErr := parseGoVersion(conf.GoVersion)
239+
if pkg != nil {
240+
pkg.goVersion = conf.GoVersion
239241
}
240242

241243
return &Checker{
242-
conf: conf,
243-
ctxt: conf.Context,
244-
fset: fset,
245-
pkg: pkg,
246-
Info: info,
247-
version: version,
248-
objMap: make(map[Object]*declInfo),
249-
impMap: make(map[importKey]*Package),
244+
conf: conf,
245+
ctxt: conf.Context,
246+
fset: fset,
247+
pkg: pkg,
248+
Info: info,
249+
version: version,
250+
versionErr: versionErr,
251+
objMap: make(map[Object]*declInfo),
252+
impMap: make(map[importKey]*Package),
250253
}
251254
}
252255

@@ -342,6 +345,12 @@ func (check *Checker) Files(files []*ast.File) error { return check.checkFiles(f
342345
var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together")
343346

344347
func (check *Checker) checkFiles(files []*ast.File) (err error) {
348+
if check.versionErr != nil {
349+
return check.versionErr
350+
}
351+
if check.version.after(version{1, goversion.Version}) {
352+
return fmt.Errorf("package requires newer Go version %v", check.version)
353+
}
345354
if check.conf.FakeImportC && check.conf.go115UsesCgo {
346355
return errBadCgo
347356
}

src/go/types/package.go

Lines changed: 14 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/go/types/sizeof_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestSizeof(t *testing.T) {
4646

4747
// Misc
4848
{Scope{}, 44, 88},
49-
{Package{}, 36, 72},
49+
{Package{}, 44, 88},
5050
{_TypeSet{}, 28, 56},
5151
}
5252
for _, test := range tests {

src/go/types/version.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package types
66

77
import (
8-
"errors"
98
"fmt"
109
"go/ast"
1110
"go/token"
@@ -45,31 +44,32 @@ var (
4544
go1_21 = version{1, 21}
4645
)
4746

48-
var errVersionSyntax = errors.New("invalid Go version syntax")
49-
5047
// parseGoVersion parses a Go version string (such as "go1.12")
5148
// and returns the version, or an error. If s is the empty
5249
// string, the version is 0.0.
5350
func parseGoVersion(s string) (v version, err error) {
51+
bad := func() (version, error) {
52+
return version{}, fmt.Errorf("invalid Go version syntax %q", s)
53+
}
5454
if s == "" {
5555
return
5656
}
5757
if !strings.HasPrefix(s, "go") {
58-
return version{}, errVersionSyntax
58+
return bad()
5959
}
6060
s = s[len("go"):]
6161
i := 0
6262
for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
6363
if i >= 10 || i == 0 && s[i] == '0' {
64-
return version{}, errVersionSyntax
64+
return bad()
6565
}
6666
v.major = 10*v.major + int(s[i]) - '0'
6767
}
6868
if i > 0 && i == len(s) {
6969
return
7070
}
7171
if i == 0 || s[i] != '.' {
72-
return version{}, errVersionSyntax
72+
return bad()
7373
}
7474
s = s[i+1:]
7575
if s == "0" {
@@ -82,14 +82,15 @@ func parseGoVersion(s string) (v version, err error) {
8282
i = 0
8383
for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
8484
if i >= 10 || i == 0 && s[i] == '0' {
85-
return version{}, errVersionSyntax
85+
return bad()
8686
}
8787
v.minor = 10*v.minor + int(s[i]) - '0'
8888
}
89-
if i > 0 && i == len(s) {
90-
return
91-
}
92-
return version{}, errVersionSyntax
89+
// Accept any suffix after the minor number.
90+
// We are only looking for the language version (major.minor)
91+
// but want to accept any valid Go version, like go1.21.0
92+
// and go1.21rc2.
93+
return
9394
}
9495

9596
// langCompat reports an error if the representation of a numeric

src/go/types/version_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2023 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 "testing"
8+
9+
var parseGoVersionTests = []struct {
10+
in string
11+
out version
12+
}{
13+
{"go1.21", version{1, 21}},
14+
{"go1.21.0", version{1, 21}},
15+
{"go1.21rc2", version{1, 21}},
16+
}
17+
18+
func TestParseGoVersion(t *testing.T) {
19+
for _, tt := range parseGoVersionTests {
20+
if out, err := parseGoVersion(tt.in); out != tt.out || err != nil {
21+
t.Errorf("parseGoVersion(%q) = %v, %v, want %v, nil", tt.in, out, err, tt.out)
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)