X-Git-Url: http://www.git.cypherpunks.ru/?a=blobdiff_plain;f=src%2Fgo%2Ftypes%2Fversion.go;h=cfbab0f2a8b954ae722862f21e6214f94aac4768;hb=56c91c05020408fbe18a8c511fc005c365f30d58;hp=0f4d064b7444f2a9fe468ead4522ce0f045ab45d;hpb=3073f3f9411737de2232e6f6d634c118b53aed22;p=gostls13.git diff --git a/src/go/types/version.go b/src/go/types/version.go index 0f4d064b74..cfbab0f2a8 100644 --- a/src/go/types/version.go +++ b/src/go/types/version.go @@ -8,90 +8,46 @@ import ( "fmt" "go/ast" "go/token" + "go/version" + "internal/goversion" "strings" ) -// A version represents a released Go version. -type version struct { - major, minor int -} - -func (v version) String() string { - return fmt.Sprintf("go%d.%d", v.major, v.minor) -} +// A goVersion is a Go language version string of the form "go1.%d" +// where d is the minor version number. goVersion strings don't +// contain release numbers ("go1.20.1" is not a valid goVersion). +type goVersion string -func (v version) equal(u version) bool { - return v.major == u.major && v.minor == u.minor +// asGoVersion returns v as a goVersion (e.g., "go1.20.1" becomes "go1.20"). +// If v is not a valid Go version, the result is the empty string. +func asGoVersion(v string) goVersion { + return goVersion(version.Lang(v)) } -func (v version) before(u version) bool { - return v.major < u.major || v.major == u.major && v.minor < u.minor +// isValid reports whether v is a valid Go version. +func (v goVersion) isValid() bool { + return v != "" } -func (v version) after(u version) bool { - return v.major > u.major || v.major == u.major && v.minor > u.minor +// cmp returns -1, 0, or +1 depending on whether x < y, x == y, or x > y, +// interpreted as Go versions. +func (x goVersion) cmp(y goVersion) int { + return version.Compare(string(x), string(y)) } -// Go versions that introduced language changes. var ( - go0_0 = version{0, 0} // no version specified - go1_9 = version{1, 9} - go1_13 = version{1, 13} - go1_14 = version{1, 14} - go1_17 = version{1, 17} - go1_18 = version{1, 18} - go1_20 = version{1, 20} - go1_21 = version{1, 21} -) + // Go versions that introduced language changes + go1_9 = asGoVersion("go1.9") + go1_13 = asGoVersion("go1.13") + go1_14 = asGoVersion("go1.14") + go1_17 = asGoVersion("go1.17") + go1_18 = asGoVersion("go1.18") + go1_20 = asGoVersion("go1.20") + go1_21 = asGoVersion("go1.21") -// 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 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 bad() - } - v.major = 10*v.major + int(s[i]) - '0' - } - if i > 0 && i == len(s) { - return - } - if i == 0 || s[i] != '.' { - return bad() - } - s = s[i+1:] - if s == "0" { - // We really should not accept "go1.0", - // but we didn't reject it from the start - // and there are now programs that use it. - // So accept it. - return - } - i = 0 - for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { - if i >= 10 || i == 0 && s[i] == '0' { - return bad() - } - v.minor = 10*v.minor + int(s[i]) - '0' - } - // 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 -} + // current (deployed) Go version + go_current = asGoVersion(fmt.Sprintf("go1.%d", goversion.Version)) +) // langCompat reports an error if the representation of a numeric // literal is not compatible with the current language version. @@ -122,35 +78,54 @@ func (check *Checker) langCompat(lit *ast.BasicLit) { } } -// allowVersion reports whether the given package -// is allowed to use version major.minor. -func (check *Checker) allowVersion(pkg *Package, at positioner, v version) bool { +// allowVersion reports whether the given package is allowed to use version v. +func (check *Checker) allowVersion(pkg *Package, at positioner, v goVersion) bool { // We assume that imported packages have all been checked, // so we only have to check for the local package. if pkg != check.pkg { return true } - // If the source file declares its Go version and at references a valid - // position, use that to decide. - if pos := at.Pos(); pos.IsValid() && check.posVers != nil { - fileStart := check.fset.File(pos).Pos(0) - if src, ok := check.posVers[fileStart]; ok && src.major >= 1 { - return !src.before(v) - } - } - - // Otherwise fall back to the version in the checker. - return check.version.equal(go0_0) || !check.version.before(v) + // If no explicit file version is specified, + // fileVersion corresponds to the module version. + var fileVersion goVersion + if pos := at.Pos(); pos.IsValid() { + // We need version.Lang below because file versions + // can be (unaltered) Config.GoVersion strings that + // may contain dot-release information. + fileVersion = asGoVersion(check.versions[check.fileFor(pos)]) + } + return !fileVersion.isValid() || fileVersion.cmp(v) >= 0 } // verifyVersionf is like allowVersion but also accepts a format string and arguments // which are used to report a version error if allowVersion returns false. It uses the // current package. -func (check *Checker) verifyVersionf(at positioner, v version, format string, args ...interface{}) bool { +func (check *Checker) verifyVersionf(at positioner, v goVersion, format string, args ...interface{}) bool { if !check.allowVersion(check.pkg, at, v) { check.versionErrorf(at, v, format, args...) return false } return true } + +// TODO(gri) Consider a more direct (position-independent) mechanism +// to identify which file we're in so that version checks +// work correctly in the absence of correct position info. + +// fileFor returns the *ast.File which contains the position pos. +// If there are no files, the result is nil. +// The position must be valid. +func (check *Checker) fileFor(pos token.Pos) *ast.File { + assert(pos.IsValid()) + // Eval and CheckExpr tests may not have any source files. + if len(check.files) == 0 { + return nil + } + for _, file := range check.files { + if file.FileStart <= pos && pos < file.FileEnd { + return file + } + } + panic(check.sprintf("file not found for pos = %d (%s)", int(pos), check.fset.Position(pos))) +}