aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/types2
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/types2')
-rw-r--r--src/cmd/compile/internal/types2/check.go38
-rw-r--r--src/cmd/compile/internal/types2/issues_test.go81
-rw-r--r--src/cmd/compile/internal/types2/package.go21
-rw-r--r--src/cmd/compile/internal/types2/sizeof_test.go2
-rw-r--r--src/cmd/compile/internal/types2/version.go23
-rw-r--r--src/cmd/compile/internal/types2/version_test.go24
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)
+ }
+ }
+}