diff options
Diffstat (limited to 'src/cmd/compile/internal/types2')
-rw-r--r-- | src/cmd/compile/internal/types2/check.go | 38 | ||||
-rw-r--r-- | src/cmd/compile/internal/types2/issues_test.go | 81 | ||||
-rw-r--r-- | src/cmd/compile/internal/types2/package.go | 21 | ||||
-rw-r--r-- | src/cmd/compile/internal/types2/sizeof_test.go | 2 | ||||
-rw-r--r-- | src/cmd/compile/internal/types2/version.go | 23 | ||||
-rw-r--r-- | src/cmd/compile/internal/types2/version_test.go | 24 |
6 files changed, 134 insertions, 55 deletions
diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index b2a9eb0dbc..0a2a49062b 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "go/constant" + "internal/goversion" . "internal/types/errors" ) @@ -231,19 +232,19 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker { info = new(Info) } - version, err := parseGoVersion(conf.GoVersion) - if err != nil { - panic(fmt.Sprintf("invalid Go version %q (%v)", conf.GoVersion, err)) - } + // Note: clients may call NewChecker with the Unsafe package, which is + // globally shared and must not be mutated. Therefore NewChecker must not + // mutate *pkg. + // + // (previously, pkg.goVersion was mutated here: go.dev/issue/61212) return &Checker{ - conf: conf, - ctxt: conf.Context, - pkg: pkg, - Info: info, - version: version, - objMap: make(map[Object]*declInfo), - impMap: make(map[importKey]*Package), + conf: conf, + ctxt: conf.Context, + pkg: pkg, + Info: info, + objMap: make(map[Object]*declInfo), + impMap: make(map[importKey]*Package), } } @@ -333,6 +334,20 @@ func (check *Checker) Files(files []*syntax.File) error { return check.checkFile var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together") func (check *Checker) checkFiles(files []*syntax.File) (err error) { + if check.pkg == Unsafe { + // Defensive handling for Unsafe, which cannot be type checked, and must + // not be mutated. See https://go.dev/issue/61212 for an example of where + // Unsafe is passed to NewChecker. + return nil + } + + check.version, err = parseGoVersion(check.conf.GoVersion) + if err != nil { + return err + } + 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 } @@ -377,6 +392,7 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) { check.monomorph() } + check.pkg.goVersion = check.conf.GoVersion check.pkg.complete = true // no longer needed - release memory diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go index 8bd42a5271..5e0ae213dc 100644 --- a/src/cmd/compile/internal/types2/issues_test.go +++ b/src/cmd/compile/internal/types2/issues_test.go @@ -497,14 +497,14 @@ func TestIssue43088(t *testing.T) { // _ T2 // } // } - n1 := NewTypeName(syntax.Pos{}, nil, "T1", nil) + n1 := NewTypeName(nopos, nil, "T1", nil) T1 := NewNamed(n1, nil, nil) - n2 := NewTypeName(syntax.Pos{}, nil, "T2", nil) + n2 := NewTypeName(nopos, nil, "T2", nil) T2 := NewNamed(n2, nil, nil) - s1 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", T2, false)}, nil) + s1 := NewStruct([]*Var{NewField(nopos, nil, "_", T2, false)}, nil) T1.SetUnderlying(s1) - s2 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", T2, false)}, nil) - s3 := NewStruct([]*Var{NewField(syntax.Pos{}, nil, "_", s2, false)}, nil) + s2 := NewStruct([]*Var{NewField(nopos, nil, "_", T2, false)}, nil) + s3 := NewStruct([]*Var{NewField(nopos, nil, "_", s2, false)}, nil) T2.SetUnderlying(s3) // These calls must terminate (no endless recursion). @@ -535,38 +535,69 @@ func TestIssue44515(t *testing.T) { } func TestIssue43124(t *testing.T) { - testenv.MustHaveGoBuild(t) + // TODO(rFindley) move this to testdata by enhancing support for importing. + + testenv.MustHaveGoBuild(t) // The go command is needed for the importer to determine the locations of stdlib .a files. // All involved packages have the same name (template). Error messages should // disambiguate between text/template and html/template by printing the full // path. const ( asrc = `package a; import "text/template"; func F(template.Template) {}; func G(int) {}` - bsrc = `package b; import ("a"; "html/template"); func _() { a.F(template.Template{}) }` - csrc = `package c; import ("a"; "html/template"); func _() { a.G(template.Template{}) }` - ) + bsrc = ` +package b - a := mustTypecheck(asrc, nil, nil) - conf := Config{Importer: importHelper{pkg: a, fallback: defaultImporter()}} +import ( + "a" + "html/template" +) +func _() { // Packages should be fully qualified when there is ambiguity within the // error string itself. - _, err := typecheck(bsrc, &conf, nil) - if err == nil { - t.Fatal("package b had no errors") - } - if !strings.Contains(err.Error(), "text/template") || !strings.Contains(err.Error(), "html/template") { - t.Errorf("type checking error for b does not disambiguate package template: %q", err) - } + a.F(template /* ERRORx "cannot use.*html/template.* as .*text/template" */ .Template{}) +} +` + csrc = ` +package c - // ...and also when there is any ambiguity in reachable packages. - _, err = typecheck(csrc, &conf, nil) - if err == nil { - t.Fatal("package c had no errors") - } - if !strings.Contains(err.Error(), "html/template") { - t.Errorf("type checking error for c does not disambiguate package template: %q", err) +import ( + "a" + "fmt" + "html/template" +) + +// go.dev/issue/46905: make sure template is not the first package qualified. +var _ fmt.Stringer = 1 // ERRORx "cannot use 1.*as fmt\\.Stringer" + +// Packages should be fully qualified when there is ambiguity in reachable +// packages. In this case both a (and for that matter html/template) import +// text/template. +func _() { a.G(template /* ERRORx "cannot use .*html/template.*Template" */ .Template{}) } +` + + tsrc = ` +package template + +import "text/template" + +type T int + +// Verify that the current package name also causes disambiguation. +var _ T = template /* ERRORx "cannot use.*text/template.* as T value" */.Template{} +` + ) + + a := mustTypecheck(asrc, nil, nil) + imp := importHelper{pkg: a, fallback: defaultImporter()} + + withImporter := func(cfg *Config) { + cfg.Importer = imp } + + testFiles(t, []string{"b.go"}, [][]byte{[]byte(bsrc)}, 0, false, withImporter) + testFiles(t, []string{"c.go"}, [][]byte{[]byte(csrc)}, 0, false, withImporter) + testFiles(t, []string{"t.go"}, [][]byte{[]byte(tsrc)}, 0, false, withImporter) } func TestIssue50646(t *testing.T) { diff --git a/src/cmd/compile/internal/types2/package.go b/src/cmd/compile/internal/types2/package.go index 61670f6718..e08099d81f 100644 --- a/src/cmd/compile/internal/types2/package.go +++ b/src/cmd/compile/internal/types2/package.go @@ -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). diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index af82b3fa7a..740dbc9276 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -47,7 +47,7 @@ func TestSizeof(t *testing.T) { // Misc {Scope{}, 60, 104}, - {Package{}, 36, 72}, + {Package{}, 44, 88}, {_TypeSet{}, 28, 56}, } diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go index 7d01b829a9..e525f16470 100644 --- a/src/cmd/compile/internal/types2/version.go +++ b/src/cmd/compile/internal/types2/version.go @@ -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 diff --git a/src/cmd/compile/internal/types2/version_test.go b/src/cmd/compile/internal/types2/version_test.go new file mode 100644 index 0000000000..651758e1b0 --- /dev/null +++ b/src/cmd/compile/internal/types2/version_test.go @@ -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 types2 + +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) + } + } +} |