}
}
}
+
+// TestTooNew ensures that "too new" errors are emitted when the file
+// or module is tagged with a newer version of Go than this go/types.
+func TestTooNew(t *testing.T) {
+ for _, test := range []struct {
+ goVersion string // package's Go version (as if derived from go.mod file)
+ fileVersion string // file's Go version (becomes a build tag)
+ wantErr string // expected substring of concatenation of all errors
+ }{
+ {"go1.98", "", "package requires newer Go version go1.98"},
+ {"", "go1.99", "p:2:9: file requires newer Go version go1.99"},
+ {"go1.98", "go1.99", "package requires newer Go version go1.98"}, // (two
+ {"go1.98", "go1.99", "file requires newer Go version go1.99"}, // errors)
+ } {
+ var src string
+ if test.fileVersion != "" {
+ src = "//go:build " + test.fileVersion + "\n"
+ }
+ src += "package p; func f()"
+
+ var errs []error
+ conf := Config{
+ GoVersion: test.goVersion,
+ Error: func(err error) { errs = append(errs, err) },
+ }
+ info := &Info{Defs: make(map[*syntax.Name]Object)}
+ typecheck(src, &conf, info)
+ got := fmt.Sprint(errs)
+ if !strings.Contains(got, test.wantErr) {
+ t.Errorf("%q: unexpected error: got %q, want substring %q",
+ src, got, test.wantErr)
+ }
+
+ // Assert that declarations were type checked nonetheless.
+ var gotObjs []string
+ for id, obj := range info.Defs {
+ if obj != nil {
+ objStr := strings.ReplaceAll(fmt.Sprintf("%s:%T", id.Value, obj), "types2", "types")
+ gotObjs = append(gotObjs, objStr)
+ }
+ }
+ wantObjs := "f:*types.Func"
+ if !strings.Contains(fmt.Sprint(gotObjs), wantObjs) {
+ t.Errorf("%q: got %s, want substring %q",
+ src, gotObjs, wantObjs)
+ }
+ }
+}
import (
"cmd/compile/internal/syntax"
- "errors"
"fmt"
"go/constant"
"internal/godebug"
check.versions = versions
pkgVersionOk := check.version.isValid()
+ if pkgVersionOk && len(files) > 0 && check.version.cmp(go_current) > 0 {
+ check.errorf(files[0], TooNew, "package requires newer Go version %v (application built with %v)",
+ check.version, go_current)
+ }
downgradeOk := check.version.cmp(go1_21) >= 0
// determine Go version for each file
// (This version string may contain dot-release numbers as in go1.20.1,
// unlike file versions which are Go language versions only, if valid.)
v := check.conf.GoVersion
- // use the file version, if applicable
- // (file versions are either the empty string or of the form go1.dd)
- if pkgVersionOk {
- fileVersion := asGoVersion(file.GoVersion)
- if fileVersion.isValid() {
+
+ fileVersion := asGoVersion(file.GoVersion)
+ if fileVersion.isValid() {
+ // use the file version, if applicable
+ // (file versions are either the empty string or of the form go1.dd)
+ if pkgVersionOk {
cmp := fileVersion.cmp(check.version)
// Go 1.21 introduced the feature of setting the go.mod
// go line to an early version of Go and allowing //go:build lines
v = file.GoVersion
}
}
+
+ // Report a specific error for each tagged file that's too new.
+ // (Normally the build system will have filtered files by version,
+ // but clients can present arbitrary files to the type checker.)
+ if fileVersion.cmp(go_current) > 0 {
+ // Use position of 'package [p]' for types/types2 consistency.
+ // (Ideally we would use the //build tag itself.)
+ check.errorf(file.PkgName, TooNew, "file requires newer Go version %v", fileVersion)
+ }
}
versions[base(file.Pos())] = v // base(file.Pos()) may be nil for tests
}
}
// Files checks the provided files as part of the checker's package.
-func (check *Checker) Files(files []*syntax.File) error { return check.checkFiles(files) }
-
-var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together")
-
-func (check *Checker) checkFiles(files []*syntax.File) (err error) {
+func (check *Checker) Files(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
return nil
}
- // Note: NewChecker doesn't return an error, so we need to check the version here.
- if check.version.cmp(go_current) > 0 {
- return fmt.Errorf("package requires newer Go version %v", check.version)
- }
- if check.conf.FakeImportC && check.conf.go115UsesCgo {
- return errBadCgo
- }
+ // Avoid early returns here! Nearly all errors can be
+ // localized to a piece of syntax and needn't prevent
+ // type-checking of the rest of the package.
defer check.handleBailout(&err)
+ check.checkFiles(files)
+ return
+}
+// checkFiles type-checks the specified files. Errors are reported as
+// a side effect, not by returning early, to ensure that well-formed
+// syntax is properly type annotated even in a package containing
+// errors.
+func (check *Checker) checkFiles(files []*syntax.File) {
print := func(msg string) {
if check.conf.Trace {
fmt.Println()
check.ctxt = nil
// TODO(gri) There's more memory we should release at this point.
-
- return
}
// processDelayed processes all delayed actions pushed after top.
// no package yet => import it
if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) {
+ if check.conf.FakeImportC && check.conf.go115UsesCgo {
+ check.error(pos, BadImportPath, "cannot use FakeImportC and go115UsesCgo together")
+ }
imp = NewPackage("C", "C")
imp.fake = true // package scope is not populated
imp.cgo = check.conf.go115UsesCgo
}
}
}
+
+// TestTooNew ensures that "too new" errors are emitted when the file
+// or module is tagged with a newer version of Go than this go/types.
+func TestTooNew(t *testing.T) {
+ for _, test := range []struct {
+ goVersion string // package's Go version (as if derived from go.mod file)
+ fileVersion string // file's Go version (becomes a build tag)
+ wantErr string // expected substring of concatenation of all errors
+ }{
+ {"go1.98", "", "package requires newer Go version go1.98"},
+ {"", "go1.99", "p:2:9: file requires newer Go version go1.99"},
+ {"go1.98", "go1.99", "package requires newer Go version go1.98"}, // (two
+ {"go1.98", "go1.99", "file requires newer Go version go1.99"}, // errors)
+ } {
+ var src string
+ if test.fileVersion != "" {
+ src = "//go:build " + test.fileVersion + "\n"
+ }
+ src += "package p; func f()"
+
+ var errs []error
+ conf := Config{
+ GoVersion: test.goVersion,
+ Error: func(err error) { errs = append(errs, err) },
+ }
+ info := &Info{Defs: make(map[*ast.Ident]Object)}
+ typecheck(src, &conf, info)
+ got := fmt.Sprint(errs)
+ if !strings.Contains(got, test.wantErr) {
+ t.Errorf("%q: unexpected error: got %q, want substring %q",
+ src, got, test.wantErr)
+ }
+
+ // Assert that declarations were type checked nonetheless.
+ var gotObjs []string
+ for id, obj := range info.Defs {
+ if obj != nil {
+ objStr := strings.ReplaceAll(fmt.Sprintf("%s:%T", id.Name, obj), "types2", "types")
+ gotObjs = append(gotObjs, objStr)
+ }
+ }
+ wantObjs := "f:*types.Func"
+ if !strings.Contains(fmt.Sprint(gotObjs), wantObjs) {
+ t.Errorf("%q: got %s, want substring %q",
+ src, gotObjs, wantObjs)
+ }
+ }
+}
package types
import (
- "errors"
"fmt"
"go/ast"
"go/constant"
check.versions = versions
pkgVersionOk := check.version.isValid()
+ if pkgVersionOk && len(files) > 0 && check.version.cmp(go_current) > 0 {
+ check.errorf(files[0], TooNew, "package requires newer Go version %v (application built with %v)",
+ check.version, go_current)
+ }
downgradeOk := check.version.cmp(go1_21) >= 0
// determine Go version for each file
// (This version string may contain dot-release numbers as in go1.20.1,
// unlike file versions which are Go language versions only, if valid.)
v := check.conf.GoVersion
- // use the file version, if applicable
- // (file versions are either the empty string or of the form go1.dd)
- if pkgVersionOk {
- fileVersion := asGoVersion(file.GoVersion)
- if fileVersion.isValid() {
+
+ fileVersion := asGoVersion(file.GoVersion)
+ if fileVersion.isValid() {
+ // use the file version, if applicable
+ // (file versions are either the empty string or of the form go1.dd)
+ if pkgVersionOk {
cmp := fileVersion.cmp(check.version)
// Go 1.21 introduced the feature of setting the go.mod
// go line to an early version of Go and allowing //go:build lines
v = file.GoVersion
}
}
+
+ // Report a specific error for each tagged file that's too new.
+ // (Normally the build system will have filtered files by version,
+ // but clients can present arbitrary files to the type checker.)
+ if fileVersion.cmp(go_current) > 0 {
+ // Use position of 'package [p]' for types/types2 consistency.
+ // (Ideally we would use the //build tag itself.)
+ check.errorf(file.Name, TooNew, "file requires newer Go version %v (application built with %v)", fileVersion, go_current)
+ }
}
versions[file] = v
}
}
// Files checks the provided files as part of the checker's package.
-func (check *Checker) Files(files []*ast.File) error { return check.checkFiles(files) }
-
-var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together")
-
-func (check *Checker) checkFiles(files []*ast.File) (err error) {
+func (check *Checker) Files(files []*ast.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
return nil
}
- // Note: NewChecker doesn't return an error, so we need to check the version here.
- if check.version.cmp(go_current) > 0 {
- return fmt.Errorf("package requires newer Go version %v", check.version)
- }
- if check.conf.FakeImportC && check.conf.go115UsesCgo {
- return errBadCgo
- }
+ // Avoid early returns here! Nearly all errors can be
+ // localized to a piece of syntax and needn't prevent
+ // type-checking of the rest of the package.
defer check.handleBailout(&err)
+ check.checkFiles(files)
+ return
+}
+// checkFiles type-checks the specified files. Errors are reported as
+// a side effect, not by returning early, to ensure that well-formed
+// syntax is properly type annotated even in a package containing
+// errors.
+func (check *Checker) checkFiles(files []*ast.File) {
print := func(msg string) {
if check.conf._Trace {
fmt.Println()
check.ctxt = nil
// TODO(rFindley) There's more memory we should release at this point.
-
- return
}
// processDelayed processes all delayed actions pushed after top.
// no package yet => import it
if path == "C" && (check.conf.FakeImportC || check.conf.go115UsesCgo) {
+ if check.conf.FakeImportC && check.conf.go115UsesCgo {
+ check.error(at, BadImportPath, "cannot use FakeImportC and go115UsesCgo together")
+ }
imp = NewPackage("C", "C")
imp.fake = true // package scope is not populated
imp.cgo = check.conf.go115UsesCgo
_ = x[InvalidClear-148]
_ = x[TypeTooLarge-149]
_ = x[InvalidMinMaxOperand-150]
+ _ = x[TooNew-151]
}
const (
_Code_name_2 = "InvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDot"
_Code_name_3 = "InvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDecl"
_Code_name_4 = "InvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString"
- _Code_name_5 = "InvalidClearTypeTooLargeInvalidMinMaxOperand"
+ _Code_name_5 = "InvalidClearTypeTooLargeInvalidMinMaxOperandTooNew"
)
var (
_Code_index_2 = [...]uint16{0, 15, 22, 33, 56, 71, 83, 94, 109, 123, 138, 153, 166, 175, 189, 204, 215, 230, 239, 255, 275, 293, 312, 324, 343, 362, 378, 395, 414, 428, 439, 454, 467, 482, 498, 512, 528, 543, 560, 578, 593, 603, 613, 630, 652, 666, 680, 700, 718, 738, 756}
_Code_index_3 = [...]uint16{0, 16, 31, 44, 54, 66, 77, 91, 104, 115, 125, 140, 151, 162, 175, 191, 208, 232, 249, 264, 274, 283, 296, 312, 328, 339, 354}
_Code_index_4 = [...]uint16{0, 14, 30, 44, 61, 81, 94, 110, 124, 141, 158, 175, 190, 204, 218, 229, 241, 254, 271, 284, 295, 308, 320, 329, 336, 348, 364, 382, 400, 415, 432, 451, 465, 485, 497, 521, 544, 562, 584, 603}
- _Code_index_5 = [...]uint8{0, 12, 24, 44}
+ _Code_index_5 = [...]uint8{0, 12, 24, 44, 50}
)
func (i Code) String() string {
case 108 <= i && i <= 146:
i -= 108
return _Code_name_4[_Code_index_4[i]:_Code_index_4[i+1]]
- case 148 <= i && i <= 150:
+ case 148 <= i && i <= 151:
i -= 148
return _Code_name_5[_Code_index_5[i]:_Code_index_5[i+1]]
default:
package errors
-//go:generate stringer -type Code codes.go
+//go:generate go run golang.org/x/tools/cmd/stringer@latest -type Code codes.go
type Code int
// var s, t []byte
// var _ = max(s, t)
InvalidMinMaxOperand
+
+ // TooNew indicates that, through build tags or a go.mod file,
+ // a source file requires a version of Go that is newer than
+ // the logic of the type checker. As a consequence, the type
+ // checker may produce spurious errors or fail to report real
+ // errors. The solution is to rebuild the application with a
+ // newer Go release.
+ TooNew
)