]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types: record Config.GoVersion for reporting in Package.GoVersion method
authorRuss Cox <rsc@golang.org>
Wed, 5 Jul 2023 16:08:51 +0000 (12:08 -0400)
committerRuss Cox <rsc@golang.org>
Thu, 6 Jul 2023 13:09:19 +0000 (13:09 +0000)
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 <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
api/go1.21.txt
src/cmd/compile/internal/types2/package.go
src/cmd/compile/internal/types2/sizeof_test.go
src/cmd/compile/internal/types2/version.go
src/go/build/deps_test.go
src/go/types/check.go
src/go/types/package.go
src/go/types/sizeof_test.go
src/go/types/version.go
src/go/types/version_test.go [new file with mode: 0644]

index 6435d10914cfd9f82fb7c9e096e3415e80131d2b..c8ca3df2e65945a54810ce57eb0d5c4a6838b1b9 100644 (file)
@@ -174,6 +174,7 @@ pkg go/build, type Package struct, Directives []Directive #56986
 pkg go/build, type Package struct, TestDirectives []Directive #56986
 pkg go/build, type Package struct, XTestDirectives []Directive #56986
 pkg go/token, method (*File) Lines() []int #57708
+pkg go/types, method (*Package) GoVersion() string #61175
 pkg html/template, const ErrJSTemplate = 12 #59584
 pkg html/template, const ErrJSTemplate ErrorCode #59584
 pkg io/fs, func FormatDirEntry(DirEntry) string #54451
index 61670f67181ec71fdccb6f94a45ffa01028bd257..e08099d81f078b9f44cdea7c3f1dbe3a10671a0f 100644 (file)
@@ -10,13 +10,14 @@ import (
 
 // A Package describes a Go package.
 type Package struct {
-       path     string
-       name     string
-       scope    *Scope
-       imports  []*Package
-       complete bool
-       fake     bool // scope lookup errors are silently dropped if package is fake (internal use only)
-       cgo      bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go
+       path      string
+       name      string
+       scope     *Scope
+       imports   []*Package
+       complete  bool
+       fake      bool   // scope lookup errors are silently dropped if package is fake (internal use only)
+       cgo       bool   // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go
+       goVersion string // minimum Go version required for package (by Config.GoVersion, typically from go.mod)
 }
 
 // NewPackage returns a new Package for the given package path and name.
@@ -35,6 +36,12 @@ func (pkg *Package) Name() string { return pkg.name }
 // SetName sets the package name.
 func (pkg *Package) SetName(name string) { pkg.name = name }
 
+// GoVersion returns the minimum Go version required by this package.
+// If the minimum version is unknown, GoVersion returns the empty string.
+// Individual source files may specify a different minimum Go version,
+// as reported in the [go/ast.File.GoVersion] field.
+func (pkg *Package) GoVersion() string { return pkg.goVersion }
+
 // Scope returns the (complete or incomplete) package scope
 // holding the objects declared at package level (TypeNames,
 // Consts, Vars, and Funcs).
index af82b3fa7add821c952abca0347f94c9358327bb..740dbc92762aff8b6419660ca2c29d27eba3411b 100644 (file)
@@ -47,7 +47,7 @@ func TestSizeof(t *testing.T) {
 
                // Misc
                {Scope{}, 60, 104},
-               {Package{}, 36, 72},
+               {Package{}, 44, 88},
                {_TypeSet{}, 28, 56},
        }
 
index 7d01b829a95e05342fb6fdf4c784f4b74a153681..e525f164705eb01b9b885aa1836a17b41de32d16 100644 (file)
@@ -6,7 +6,6 @@ package types2
 
 import (
        "cmd/compile/internal/syntax"
-       "errors"
        "fmt"
        "strings"
 )
@@ -44,23 +43,24 @@ var (
        go1_21 = version{1, 21}
 )
 
-var errVersionSyntax = errors.New("invalid Go version syntax")
-
 // 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) {
+       bad := func() (version, error) {
+               return version{}, fmt.Errorf("invalid Go version syntax %q", s)
+       }
        if s == "" {
                return
        }
        if !strings.HasPrefix(s, "go") {
-               return version{}, errVersionSyntax
+               return bad()
        }
        s = s[len("go"):]
        i := 0
        for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
                if i >= 10 || i == 0 && s[i] == '0' {
-                       return version{}, errVersionSyntax
+                       return bad()
                }
                v.major = 10*v.major + int(s[i]) - '0'
        }
@@ -68,7 +68,7 @@ func parseGoVersion(s string) (v version, err error) {
                return
        }
        if i == 0 || s[i] != '.' {
-               return version{}, errVersionSyntax
+               return bad()
        }
        s = s[i+1:]
        if s == "0" {
@@ -81,14 +81,15 @@ func parseGoVersion(s string) (v version, err error) {
        i = 0
        for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
                if i >= 10 || i == 0 && s[i] == '0' {
-                       return version{}, errVersionSyntax
+                       return bad()
                }
                v.minor = 10*v.minor + int(s[i]) - '0'
        }
-       if i > 0 && i == len(s) {
-               return
-       }
-       return version{}, errVersionSyntax
+       // Accept any suffix after the minor number.
+       // We are only looking for the language version (major.minor)
+       // but want to accept any valid Go version, like go1.21.0
+       // and go1.21rc2.
+       return
 }
 
 // langCompat reports an error if the representation of a numeric
index be8ac30f9daf9f444f5ea2f3c5b01e12fad8000a..2f335068b8a62fea40cc699681d333c983ab243b 100644 (file)
@@ -286,7 +286,7 @@ var depsRules = `
        math/big, go/token
        < go/constant;
 
-       container/heap, go/constant, go/parser, internal/types/errors
+       container/heap, go/constant, go/parser, internal/goversion, internal/types/errors
        < go/types;
 
        # The vast majority of standard library packages should not be resorting to regexp.
index 5381b5db68765c1f01a4b1a4ec2e7ce3ea316483..591de5f329c0b7cfed42d5b8b63d687e46cfb1fc 100644 (file)
@@ -12,6 +12,7 @@ import (
        "go/ast"
        "go/constant"
        "go/token"
+       "internal/goversion"
        . "internal/types/errors"
 )
 
@@ -98,11 +99,12 @@ type Checker struct {
        fset *token.FileSet
        pkg  *Package
        *Info
-       version version                // accepted language version
-       nextID  uint64                 // unique Id for type parameters (first valid Id is 1)
-       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
-       valids  instanceLookup         // valid *Named (incl. instantiated) types per the validType check
+       version    version                // accepted language version
+       versionErr error                  // version error, delayed from NewChecker
+       nextID     uint64                 // unique Id for type parameters (first valid Id is 1)
+       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
+       valids     instanceLookup         // valid *Named (incl. instantiated) types per the validType check
 
        // pkgPathMap maps package names to the set of distinct import paths we've
        // 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
                info = new(Info)
        }
 
-       version, err := parseGoVersion(conf.GoVersion)
-       if err != nil {
-               panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err))
+       version, versionErr := parseGoVersion(conf.GoVersion)
+       if pkg != nil {
+               pkg.goVersion = conf.GoVersion
        }
 
        return &Checker{
-               conf:    conf,
-               ctxt:    conf.Context,
-               fset:    fset,
-               pkg:     pkg,
-               Info:    info,
-               version: version,
-               objMap:  make(map[Object]*declInfo),
-               impMap:  make(map[importKey]*Package),
+               conf:       conf,
+               ctxt:       conf.Context,
+               fset:       fset,
+               pkg:        pkg,
+               Info:       info,
+               version:    version,
+               versionErr: versionErr,
+               objMap:     make(map[Object]*declInfo),
+               impMap:     make(map[importKey]*Package),
        }
 }
 
@@ -342,6 +345,12 @@ func (check *Checker) Files(files []*ast.File) error { return check.checkFiles(f
 var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together")
 
 func (check *Checker) checkFiles(files []*ast.File) (err error) {
+       if check.versionErr != nil {
+               return check.versionErr
+       }
+       if check.version.after(version{1, goversion.Version}) {
+               return fmt.Errorf("package requires newer Go version %v", check.version)
+       }
        if check.conf.FakeImportC && check.conf.go115UsesCgo {
                return errBadCgo
        }
index 7aa62fb7a3b715dca8ec60e4d719bc22ddfd4fd1..0f52d5f4893d185c53c59997fed722a438721106 100644 (file)
@@ -12,13 +12,14 @@ import (
 
 // A Package describes a Go package.
 type Package struct {
-       path     string
-       name     string
-       scope    *Scope
-       imports  []*Package
-       complete bool
-       fake     bool // scope lookup errors are silently dropped if package is fake (internal use only)
-       cgo      bool // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go
+       path      string
+       name      string
+       scope     *Scope
+       imports   []*Package
+       complete  bool
+       fake      bool   // scope lookup errors are silently dropped if package is fake (internal use only)
+       cgo       bool   // uses of this package will be rewritten into uses of declarations from _cgo_gotypes.go
+       goVersion string // minimum Go version required for package (by Config.GoVersion, typically from go.mod)
 }
 
 // NewPackage returns a new Package for the given package path and name.
@@ -37,6 +38,12 @@ func (pkg *Package) Name() string { return pkg.name }
 // SetName sets the package name.
 func (pkg *Package) SetName(name string) { pkg.name = name }
 
+// GoVersion returns the minimum Go version required by this package.
+// If the minimum version is unknown, GoVersion returns the empty string.
+// Individual source files may specify a different minimum Go version,
+// as reported in the [go/ast.File.GoVersion] field.
+func (pkg *Package) GoVersion() string { return pkg.goVersion }
+
 // Scope returns the (complete or incomplete) package scope
 // holding the objects declared at package level (TypeNames,
 // Consts, Vars, and Funcs).
index f17a1781f574483e7ed3d71ff9f9765c957d6019..9e5b5f8b20398a243416e5a6cb84e4bce9c2022c 100644 (file)
@@ -46,7 +46,7 @@ func TestSizeof(t *testing.T) {
 
                // Misc
                {Scope{}, 44, 88},
-               {Package{}, 36, 72},
+               {Package{}, 44, 88},
                {_TypeSet{}, 28, 56},
        }
        for _, test := range tests {
index 07a42a79eed24bdd257c005776fa597b6af856d7..108d9b34a062aefcfb5774556aaf760bde7ad175 100644 (file)
@@ -5,7 +5,6 @@
 package types
 
 import (
-       "errors"
        "fmt"
        "go/ast"
        "go/token"
@@ -45,23 +44,24 @@ var (
        go1_21 = version{1, 21}
 )
 
-var errVersionSyntax = errors.New("invalid Go version syntax")
-
 // 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) {
+       bad := func() (version, error) {
+               return version{}, fmt.Errorf("invalid Go version syntax %q", s)
+       }
        if s == "" {
                return
        }
        if !strings.HasPrefix(s, "go") {
-               return version{}, errVersionSyntax
+               return bad()
        }
        s = s[len("go"):]
        i := 0
        for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
                if i >= 10 || i == 0 && s[i] == '0' {
-                       return version{}, errVersionSyntax
+                       return bad()
                }
                v.major = 10*v.major + int(s[i]) - '0'
        }
@@ -69,7 +69,7 @@ func parseGoVersion(s string) (v version, err error) {
                return
        }
        if i == 0 || s[i] != '.' {
-               return version{}, errVersionSyntax
+               return bad()
        }
        s = s[i+1:]
        if s == "0" {
@@ -82,14 +82,15 @@ func parseGoVersion(s string) (v version, err error) {
        i = 0
        for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
                if i >= 10 || i == 0 && s[i] == '0' {
-                       return version{}, errVersionSyntax
+                       return bad()
                }
                v.minor = 10*v.minor + int(s[i]) - '0'
        }
-       if i > 0 && i == len(s) {
-               return
-       }
-       return version{}, errVersionSyntax
+       // Accept any suffix after the minor number.
+       // We are only looking for the language version (major.minor)
+       // but want to accept any valid Go version, like go1.21.0
+       // and go1.21rc2.
+       return
 }
 
 // langCompat reports an error if the representation of a numeric
diff --git a/src/go/types/version_test.go b/src/go/types/version_test.go
new file mode 100644 (file)
index 0000000..dc9becf
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2023 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 "testing"
+
+var parseGoVersionTests = []struct {
+       in  string
+       out version
+}{
+       {"go1.21", version{1, 21}},
+       {"go1.21.0", version{1, 21}},
+       {"go1.21rc2", version{1, 21}},
+}
+
+func TestParseGoVersion(t *testing.T) {
+       for _, tt := range parseGoVersionTests {
+               if out, err := parseGoVersion(tt.in); out != tt.out || err != nil {
+                       t.Errorf("parseGoVersion(%q) = %v, %v, want %v, nil", tt.in, out, err, tt.out)
+               }
+       }
+}