"fmt"
"go/constant"
"internal/godebug"
- "internal/goversion"
. "internal/types/errors"
)
ctxt *Context // context for de-duplicating instances
pkg *Package
*Info
- version version // accepted language version
- posVers map[*syntax.PosBase]version // maps file PosBases to versions (may be nil)
- 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 goVersion // 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
// 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
// (initialized by Files, valid only for the duration of check.Files;
// maps and lists are allocated on demand)
files []*syntax.File // list of package files
+ versions map[*syntax.PosBase]string // maps file bases to version strings (each file has an entry)
imports []*PkgName // list of imported packages
dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through
recvTParamMap map[*syntax.Name]*TypeParam // maps blank receiver type parameters to their type
ctxt: conf.Context,
pkg: pkg,
Info: info,
+ version: asGoVersion(conf.GoVersion),
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
}
}
}
+ // reuse Info.FileVersions if provided
+ versions := check.Info.FileVersions
+ if versions == nil {
+ versions = make(map[*syntax.PosBase]string)
+ }
+ check.versions = versions
+
+ pkgVersionOk := check.version.isValid()
+ downgradeOk := check.version.cmp(go1_21) >= 0
+
+ // determine Go version for each file
for _, file := range check.files {
- fbase := base(file.Pos()) // fbase may be nil for tests
- check.recordFileVersion(fbase, check.conf.GoVersion) // record package version (possibly zero version)
- v, _ := parseGoVersion(file.GoVersion)
- if v.major > 0 {
- if v.equal(check.version) {
- continue
- }
- // Go 1.21 introduced the feature of setting the go.mod
- // go line to an early version of Go and allowing //go:build lines
- // to “upgrade” the Go version in a given file.
- // We can do that backwards compatibly.
- // Go 1.21 also introduced the feature of allowing //go:build lines
- // to “downgrade” the Go version in a given file.
- // That can't be done compatibly in general, since before the
- // build lines were ignored and code got the module's Go version.
- // To work around this, downgrades are only allowed when the
- // module's Go version is Go 1.21 or later.
- // If there is no check.version, then we don't really know what Go version to apply.
- // Legacy tools may do this, and they historically have accepted everything.
- // Preserve that behavior by ignoring //go:build constraints entirely in that case.
- if (v.before(check.version) && check.version.before(go1_21)) || check.version.equal(go0_0) {
- continue
- }
- if check.posVers == nil {
- check.posVers = make(map[*syntax.PosBase]version)
+ // use unaltered Config.GoVersion by default
+ // (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() {
+ 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
+ // to “upgrade” (cmp > 0) the Go version in a given file.
+ // We can do that backwards compatibly.
+ //
+ // Go 1.21 also introduced the feature of allowing //go:build lines
+ // to “downgrade” (cmp < 0) the Go version in a given file.
+ // That can't be done compatibly in general, since before the
+ // build lines were ignored and code got the module's Go version.
+ // To work around this, downgrades are only allowed when the
+ // module's Go version is Go 1.21 or later.
+ //
+ // If there is no valid check.version, then we don't really know what
+ // Go version to apply.
+ // Legacy tools may do this, and they historically have accepted everything.
+ // Preserve that behavior by ignoring //go:build constraints entirely in that
+ // case (!pkgVersionOk).
+ if cmp > 0 || cmp < 0 && downgradeOk {
+ v = file.GoVersion
+ }
}
- check.posVers[fbase] = v
- check.recordFileVersion(fbase, file.GoVersion) // overwrite package version
}
+ versions[base(file.Pos())] = v // base(file.Pos()) may be nil for tests
}
}
return nil
}
- // Note: parseGoVersion and the subsequent checks should happen once,
- // when we create a new Checker, not for each batch of files.
- // We can't change it at this point because NewChecker doesn't
- // return an error.
- check.version, err = parseGoVersion(check.conf.GoVersion)
- if err != nil {
- return err
- }
- if check.version.after(version{1, goversion.Version}) {
+ // 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 {
m[node] = scope
}
}
-
-func (check *Checker) recordFileVersion(fbase *syntax.PosBase, version string) {
- if m := check.FileVersions; m != nil {
- m[fbase] = version
- }
-}