From 804d786a30c664df63ef1e0c95d44772063afae1 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 13 Mar 2023 21:06:49 -0400 Subject: [PATCH] go/types, cmd/compile/internal/types2: use per-file Go version For #57001, compilers and others tools will need to understand that a different Go version can be used in different files in a program, according to the //go:build lines in those files. Update go/types and cmd/compile/internal/types2 to track and use per-file Go versions. The two must be updated together because of the files in go/types that are generated from files in types2. The effect of the //go:build go1.N line depends on the Go version declared in the 'go 1.M' line in go.mod. If N > M, the file gets go1.N semantics when built with a Go 1.N or later toolchain (when built with an earlier toolchain the //go:build line will keep the file from being built at all). If N < M, then in general we want the file to get go1.N semantics as well, meaning later features are disabled. However, older Go 1.M did not apply this kind of downgrade, so for compatibility, N < M only has an effect when M >= 21, meaning when using semantics from Go 1.21 or later. For #59033. Change-Id: I93cf07e6c687d37bd37a9461dc60cc032bafd01d Reviewed-on: https://go-review.googlesource.com/c/go/+/476278 TryBot-Result: Gopher Robot Auto-Submit: Russ Cox Reviewed-by: Robert Griesemer Run-TryBot: Russ Cox --- src/cmd/compile/internal/types2/api.go | 6 +- src/cmd/compile/internal/types2/builtins.go | 12 +-- src/cmd/compile/internal/types2/call.go | 6 +- src/cmd/compile/internal/types2/check.go | 27 ++++++ .../compile/internal/types2/conversions.go | 4 +- src/cmd/compile/internal/types2/decl.go | 4 +- src/cmd/compile/internal/types2/expr.go | 2 +- .../compile/internal/types2/instantiate.go | 6 +- src/cmd/compile/internal/types2/lookup.go | 5 +- src/cmd/compile/internal/types2/operand.go | 4 +- src/cmd/compile/internal/types2/resolver.go | 4 +- src/cmd/compile/internal/types2/typeset.go | 8 +- src/cmd/compile/internal/types2/typexpr.go | 4 +- src/cmd/compile/internal/types2/version.go | 85 ++++++++++++++++--- src/go/build/deps_test.go | 13 ++- src/go/types/api.go | 6 +- src/go/types/builtins.go | 12 +-- src/go/types/call.go | 6 +- src/go/types/check.go | 33 +++++++ src/go/types/conversions.go | 4 +- src/go/types/decl.go | 4 +- src/go/types/expr.go | 2 +- src/go/types/generate_test.go | 2 +- src/go/types/instantiate.go | 6 +- src/go/types/lookup.go | 5 +- src/go/types/operand.go | 4 +- src/go/types/resolver.go | 4 +- src/go/types/typeset.go | 8 +- src/go/types/typexpr.go | 4 +- src/go/types/version.go | 71 +++++++++++++--- .../types/testdata/check/go1_19_20.go | 17 ++++ .../types/testdata/check/go1_20_19.go | 17 ++++ .../types/testdata/check/go1_21_19.go | 17 ++++ 33 files changed, 315 insertions(+), 97 deletions(-) create mode 100644 src/internal/types/testdata/check/go1_19_20.go create mode 100644 src/internal/types/testdata/check/go1_20_19.go create mode 100644 src/internal/types/testdata/check/go1_21_19.go diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index e027b9a7e2..24131192f8 100644 --- a/src/cmd/compile/internal/types2/api.go +++ b/src/cmd/compile/internal/types2/api.go @@ -451,7 +451,7 @@ func AssertableTo(V *Interface, T Type) bool { if T.Underlying() == Typ[Invalid] { return false } - return (*Checker)(nil).newAssertableTo(V, T, nil) + return (*Checker)(nil).newAssertableTo(nopos, V, T, nil) } // AssignableTo reports whether a value of type V is assignable to a variable @@ -489,7 +489,7 @@ func Implements(V Type, T *Interface) bool { if V.Underlying() == Typ[Invalid] { return false } - return (*Checker)(nil).implements(V, T, false, nil) + return (*Checker)(nil).implements(nopos, V, T, false, nil) } // Satisfies reports whether type V satisfies the constraint T. @@ -497,7 +497,7 @@ func Implements(V Type, T *Interface) bool { // The behavior of Satisfies is unspecified if V is Typ[Invalid] or an uninstantiated // generic type. func Satisfies(V Type, T *Interface) bool { - return (*Checker)(nil).implements(V, T, true, nil) + return (*Checker)(nil).implements(nopos, V, T, true, nil) } // Identical reports whether x and y are identical types. diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go index 67aa37e401..915eb2db9e 100644 --- a/src/cmd/compile/internal/types2/builtins.go +++ b/src/cmd/compile/internal/types2/builtins.go @@ -234,7 +234,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _Clear: // clear(m) - if !check.allowVersion(check.pkg, 1, 21) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 21) { check.versionErrorf(call.Fun, "go1.21", "clear") return } @@ -626,7 +626,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _Add: // unsafe.Add(ptr unsafe.Pointer, len IntegerType) unsafe.Pointer - if !check.allowVersion(check.pkg, 1, 17) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 17) { check.versionErrorf(call.Fun, "go1.17", "unsafe.Add") return } @@ -762,7 +762,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _Slice: // unsafe.Slice(ptr *T, len IntegerType) []T - if !check.allowVersion(check.pkg, 1, 17) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 17) { check.versionErrorf(call.Fun, "go1.17", "unsafe.Slice") return } @@ -787,7 +787,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _SliceData: // unsafe.SliceData(slice []T) *T - if !check.allowVersion(check.pkg, 1, 20) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 20) { check.versionErrorf(call.Fun, "go1.20", "unsafe.SliceData") return } @@ -806,7 +806,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _String: // unsafe.String(ptr *byte, len IntegerType) string - if !check.allowVersion(check.pkg, 1, 20) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 20) { check.versionErrorf(call.Fun, "go1.20", "unsafe.String") return } @@ -830,7 +830,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) ( case _StringData: // unsafe.StringData(str string) *byte - if !check.allowVersion(check.pkg, 1, 20) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 20) { check.versionErrorf(call.Fun, "go1.20", "unsafe.StringData") return } diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index a47deb9a22..08c90b9f8f 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -23,7 +23,7 @@ import ( func (check *Checker) funcInst(tsig *Signature, pos syntax.Pos, x *operand, inst *syntax.IndexExpr) { assert(tsig != nil || inst != nil) - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, pos, 1, 18) { check.versionErrorf(inst.Pos(), "go1.18", "function instantiation") } @@ -278,7 +278,7 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind { // is an error checking its arguments (for example, if an incorrect number // of arguments is supplied). if got == want && want > 0 { - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, x.Pos(), 1, 18) { check.versionErrorf(inst.Pos(), "go1.18", "function instantiation") } @@ -444,7 +444,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T // infer type arguments and instantiate signature if necessary if sig.TypeParams().Len() > 0 { - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 18) { if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil { check.versionErrorf(iexpr.Pos(), "go1.18", "function instantiation") } else { diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go index 0c9e80e014..5f0c521a2a 100644 --- a/src/cmd/compile/internal/types2/check.go +++ b/src/cmd/compile/internal/types2/check.go @@ -116,6 +116,7 @@ type Checker struct { // (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 + posVers map[*syntax.PosBase]version // Pos -> Go version mapping 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 @@ -281,6 +282,32 @@ func (check *Checker) initFiles(files []*syntax.File) { // ignore this file } } + + for _, file := range check.files { + 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 v.before(check.version) && check.version.before(version{1, 21}) { + continue + } + if check.posVers == nil { + check.posVers = make(map[*syntax.PosBase]version) + } + check.posVers[base(file.Pos())] = v + } + } } // A bailout panic is used for early termination. diff --git a/src/cmd/compile/internal/types2/conversions.go b/src/cmd/compile/internal/types2/conversions.go index 267324421d..7cb7d490be 100644 --- a/src/cmd/compile/internal/types2/conversions.go +++ b/src/cmd/compile/internal/types2/conversions.go @@ -183,7 +183,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool { switch a := Tu.(type) { case *Array: if Identical(s.Elem(), a.Elem()) { - if check == nil || check.allowVersion(check.pkg, 1, 20) { + if check == nil || check.allowVersion(check.pkg, x.Pos(), 1, 20) { return true } // check != nil @@ -196,7 +196,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool { case *Pointer: if a, _ := under(a.Elem()).(*Array); a != nil { if Identical(s.Elem(), a.Elem()) { - if check == nil || check.allowVersion(check.pkg, 1, 17) { + if check == nil || check.allowVersion(check.pkg, x.Pos(), 1, 17) { return true } // check != nil diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index afa32c1a5f..f7c6a8e573 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -506,7 +506,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named check.validType(t) } // If typ is local, an error was already reported where typ is specified/defined. - if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, 1, 18) { + if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, tdecl.Pos(), 1, 18) { check.versionErrorf(tdecl.Type, "go1.18", "using type constraint %s", rhs) } }).describef(obj, "validType(%s)", obj.Name()) @@ -521,7 +521,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named // alias declaration if alias { - if !check.allowVersion(check.pkg, 1, 9) { + if !check.allowVersion(check.pkg, tdecl.Pos(), 1, 9) { check.versionErrorf(tdecl, "go1.9", "type aliases") } diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 1424e43876..7c3b40f086 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -977,7 +977,7 @@ func (check *Checker) shift(x, y *operand, e syntax.Expr, op syntax.Operator) { // Check that RHS is otherwise at least of integer type. switch { case allInteger(y.typ): - if !allUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) { + if !allUnsigned(y.typ) && !check.allowVersion(check.pkg, x.Pos(), 1, 13) { check.versionErrorf(y, "go1.13", invalidOp+"signed shift count %s", y) x.mode = invalid return diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index 8d3fee9edd..7329fffc86 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -176,7 +176,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type, // the parameterized type. bound := check.subst(pos, tpar.bound, smap, nil, ctxt) var cause string - if !check.implements(targs[i], bound, true, &cause) { + if !check.implements(pos, targs[i], bound, true, &cause) { return i, errors.New(cause) } } @@ -189,7 +189,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type, // // If the provided cause is non-nil, it may be set to an error string // explaining why V does not implement (or satisfy, for constraints) T. -func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool { +func (check *Checker) implements(pos syntax.Pos, V, T Type, constraint bool, cause *string) bool { Vu := under(V) Tu := under(T) if Vu == Typ[Invalid] || Tu == Typ[Invalid] { @@ -262,7 +262,7 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool // so that ordinary, non-type parameter interfaces implement comparable. if constraint && comparable(V, true /* spec comparability */, nil, nil) { // V is comparable if we are at Go 1.20 or higher. - if check == nil || check.allowVersion(check.pkg, 1, 20) { + if check == nil || check.allowVersion(check.pkg, pos, 1, 20) { return true } if cause != nil { diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index d2694fc974..e0b19718a1 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -8,6 +8,7 @@ package types2 import ( "bytes" + "cmd/compile/internal/syntax" "strings" ) @@ -505,14 +506,14 @@ func (check *Checker) assertableTo(V, T Type, cause *string) bool { // in constraint position (we have not yet defined that behavior in the spec). // The underlying type of V must be an interface. // If the result is false and cause is not nil, *cause is set to the error cause. -func (check *Checker) newAssertableTo(V, T Type, cause *string) bool { +func (check *Checker) newAssertableTo(pos syntax.Pos, V, T Type, cause *string) bool { // no static check is required if T is an interface // spec: "If T is an interface type, x.(T) asserts that the // dynamic type of x implements the interface T." if IsInterface(T) { return true } - return check.implements(T, V, false, cause) + return check.implements(pos, T, V, false, cause) } // deref dereferences typ if it is a *Pointer (but not a *Named type diff --git a/src/cmd/compile/internal/types2/operand.go b/src/cmd/compile/internal/types2/operand.go index e49afee987..344fe292c5 100644 --- a/src/cmd/compile/internal/types2/operand.go +++ b/src/cmd/compile/internal/types2/operand.go @@ -293,7 +293,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod // T is an interface type and x implements T and T is not a type parameter. // Also handle the case where T is a pointer to an interface. if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) { - if !check.implements(V, T, false, cause) { + if !check.implements(x.Pos(), V, T, false, cause) { return false, InvalidIfaceAssign } return true, 0 @@ -301,7 +301,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod // If V is an interface, check if a missing type assertion is the problem. if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil { - if check.implements(T, V, false, nil) { + if check.implements(x.Pos(), T, V, false, nil) { // T implements V, so give hint about type assertion. if cause != nil { *cause = "need type assertion" diff --git a/src/cmd/compile/internal/types2/resolver.go b/src/cmd/compile/internal/types2/resolver.go index 8aeafceadb..cfaca2b665 100644 --- a/src/cmd/compile/internal/types2/resolver.go +++ b/src/cmd/compile/internal/types2/resolver.go @@ -406,7 +406,7 @@ func (check *Checker) collectObjects() { } case *syntax.TypeDecl: - if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) { + if len(s.TParamList) != 0 && !check.allowVersion(pkg, s.Pos(), 1, 18) { check.versionErrorf(s.TParamList[0], "go1.18", "type parameter") } obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil) @@ -455,7 +455,7 @@ func (check *Checker) collectObjects() { } check.recordDef(s.Name, obj) } - if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError { + if len(s.TParamList) != 0 && !check.allowVersion(pkg, s.Pos(), 1, 18) && !hasTParamError { check.versionErrorf(s.TParamList[0], "go1.18", "type parameter") } info := &declInfo{file: fileScope, fdecl: s} diff --git a/src/cmd/compile/internal/types2/typeset.go b/src/cmd/compile/internal/types2/typeset.go index af5aa40949..26c20cb380 100644 --- a/src/cmd/compile/internal/types2/typeset.go +++ b/src/cmd/compile/internal/types2/typeset.go @@ -244,7 +244,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ } // check != nil check.later(func() { - if !check.allowVersion(m.pkg, 1, 14) || !Identical(m.typ, other.Type()) { + if !check.allowVersion(m.pkg, pos, 1, 14) || !Identical(m.typ, other.Type()) { var err error_ err.code = DuplicateDecl err.errorf(pos, "duplicate method %s", m.name) @@ -278,7 +278,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ assert(!isTypeParam(typ)) tset := computeInterfaceTypeSet(check, pos, u) // If typ is local, an error was already reported where typ is specified/defined. - if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, 1, 18) { + if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, pos, 1, 18) { check.versionErrorf(pos, "go1.18", "embedding constraint interface %s", typ) continue } @@ -288,7 +288,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ } terms = tset.terms case *Union: - if check != nil && !check.allowVersion(check.pkg, 1, 18) { + if check != nil && !check.allowVersion(check.pkg, pos, 1, 18) { check.versionErrorf(pos, "go1.18", "embedding interface element %s", u) continue } @@ -303,7 +303,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_ if u == Typ[Invalid] { continue } - if check != nil && !check.allowVersion(check.pkg, 1, 18) { + if check != nil && !check.allowVersion(check.pkg, pos, 1, 18) { check.versionErrorf(pos, "go1.18", "embedding non-interface type %s", typ) continue } diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 03b2a8488e..f973454645 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -42,7 +42,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType boo } return case universeAny, universeComparable: - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, e.Pos(), 1, 18) { check.versionErrorf(e, "go1.18", "predeclared %s", e.Value) return // avoid follow-on errors } @@ -272,7 +272,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) { } case *syntax.IndexExpr: - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, e.Pos(), 1, 18) { check.versionErrorf(e.Pos(), "go1.18", "type instantiation") } return check.instantiatedType(e.X, unpackExpr(e.Index), def) diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go index 8fd76a381d..37e86a2bb4 100644 --- a/src/cmd/compile/internal/types2/version.go +++ b/src/cmd/compile/internal/types2/version.go @@ -6,9 +6,7 @@ package types2 import ( "cmd/compile/internal/syntax" - "fmt" - "internal/lazyregexp" - "strconv" + "errors" "strings" ) @@ -16,7 +14,7 @@ import ( // literal is not compatible with the current language version. func (check *Checker) langCompat(lit *syntax.BasicLit) { s := lit.Value - if len(s) <= 2 || check.allowVersion(check.pkg, 1, 13) { + if len(s) <= 2 || check.allowVersion(check.pkg, lit.Pos(), 1, 13) { return } // len(s) > 2 @@ -43,20 +41,45 @@ func (check *Checker) langCompat(lit *syntax.BasicLit) { // allowVersion reports whether the given package // is allowed to use version major.minor. -func (check *Checker) allowVersion(pkg *Package, major, minor int) bool { +func (check *Checker) allowVersion(pkg *Package, pos syntax.Pos, major, minor int) 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, use that to decide. + if check.posVers != nil { + if v, ok := check.posVers[base(pos)]; ok && v.major >= 1 { + return v.major > major || v.major == major && v.minor >= minor + } + } + + // Otherwise fall back to the version in the checker. ma, mi := check.version.major, check.version.minor return ma == 0 && mi == 0 || ma > major || ma == major && mi >= minor } +// base finds the underlying PosBase of the source file containing pos, +// skipping over intermediate PosBase layers created by //line directives. +func base(pos syntax.Pos) *syntax.PosBase { + b := pos.Base() + for { + bb := b.Pos().Base() + if bb == nil || bb == b { + break + } + b = bb + } + return b +} + type version struct { major, minor int } +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. @@ -64,18 +87,52 @@ func parseGoVersion(s string) (v version, err error) { if s == "" { return } - matches := goVersionRx.FindStringSubmatch(s) - if matches == nil { - err = fmt.Errorf(`should be something like "go1.12"`) + if !strings.HasPrefix(s, "go") { + return version{}, errVersionSyntax + } + 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 + } + v.major = 10*v.major + int(s[i]) - '0' + } + if i > 0 && i == len(s) { + return + } + if i == 0 || s[i] != '.' { + return version{}, errVersionSyntax + } + 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 } - v.major, err = strconv.Atoi(matches[1]) - if err != nil { + i = 0 + for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + if i >= 10 || i == 0 && s[i] == '0' { + return version{}, errVersionSyntax + } + v.minor = 10*v.minor + int(s[i]) - '0' + } + if i > 0 && i == len(s) { return } - v.minor, err = strconv.Atoi(matches[2]) - return + return version{}, errVersionSyntax } -// goVersionRx matches a Go version string, e.g. "go1.12". -var goVersionRx = lazyregexp.New(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) +func (v version) equal(u version) bool { + return v.major == u.major && v.minor == u.minor +} + +func (v version) before(u version) bool { + return v.major < u.major || v.major == u.major && v.minor < u.minor +} + +func (v version) after(u version) bool { + return v.major > u.major || v.major == u.major && v.minor > u.minor +} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 306d039034..de80ed041f 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -275,15 +275,20 @@ var depsRules = ` < go/printer < go/format; - go/doc/comment, go/parser, internal/lazyregexp, text/template - < go/doc; - math/big, go/token < go/constant; - container/heap, go/constant, go/parser, internal/types/errors, internal/lazyregexp + container/heap, go/constant, go/parser, internal/types/errors < go/types; + # The vast majority of standard library packages should not be resorting to regexp. + # go/types is a good chokepoint. It shouldn't use regexp, nor should anything + # that is low-enough level to be used by go/types. + regexp !< go/types; + + go/doc/comment, go/parser, internal/lazyregexp, text/template + < go/doc; + FMT, internal/goexperiment < internal/buildcfg; diff --git a/src/go/types/api.go b/src/go/types/api.go index 06bdb5616d..a144462968 100644 --- a/src/go/types/api.go +++ b/src/go/types/api.go @@ -435,7 +435,7 @@ func AssertableTo(V *Interface, T Type) bool { if T.Underlying() == Typ[Invalid] { return false } - return (*Checker)(nil).newAssertableTo(V, T, nil) + return (*Checker)(nil).newAssertableTo(nopos, V, T, nil) } // AssignableTo reports whether a value of type V is assignable to a variable @@ -473,7 +473,7 @@ func Implements(V Type, T *Interface) bool { if V.Underlying() == Typ[Invalid] { return false } - return (*Checker)(nil).implements(V, T, false, nil) + return (*Checker)(nil).implements(0, V, T, false, nil) } // Satisfies reports whether type V satisfies the constraint T. @@ -481,7 +481,7 @@ func Implements(V Type, T *Interface) bool { // The behavior of Satisfies is unspecified if V is Typ[Invalid] or an uninstantiated // generic type. func Satisfies(V Type, T *Interface) bool { - return (*Checker)(nil).implements(V, T, true, nil) + return (*Checker)(nil).implements(0, V, T, true, nil) } // Identical reports whether x and y are identical types. diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index a7a3af2725..eb9b3ed3b2 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -235,7 +235,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _Clear: // clear(m) - if !check.allowVersion(check.pkg, 1, 21) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 21) { check.error(call.Fun, UnsupportedFeature, "clear requires go1.21 or later") return } @@ -627,7 +627,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _Add: // unsafe.Add(ptr unsafe.Pointer, len IntegerType) unsafe.Pointer - if !check.allowVersion(check.pkg, 1, 17) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 17) { check.error(call.Fun, UnsupportedFeature, "unsafe.Add requires go1.17 or later") return } @@ -763,7 +763,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _Slice: // unsafe.Slice(ptr *T, len IntegerType) []T - if !check.allowVersion(check.pkg, 1, 17) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 17) { check.error(call.Fun, UnsupportedFeature, "unsafe.Slice requires go1.17 or later") return } @@ -788,7 +788,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _SliceData: // unsafe.SliceData(slice []T) *T - if !check.allowVersion(check.pkg, 1, 20) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 20) { check.error(call.Fun, UnsupportedFeature, "unsafe.SliceData requires go1.20 or later") return } @@ -807,7 +807,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _String: // unsafe.String(ptr *byte, len IntegerType) string - if !check.allowVersion(check.pkg, 1, 20) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 20) { check.error(call.Fun, UnsupportedFeature, "unsafe.String requires go1.20 or later") return } @@ -831,7 +831,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b case _StringData: // unsafe.StringData(str string) *byte - if !check.allowVersion(check.pkg, 1, 20) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 20) { check.error(call.Fun, UnsupportedFeature, "unsafe.StringData requires go1.20 or later") return } diff --git a/src/go/types/call.go b/src/go/types/call.go index fb0a6cea3c..f220efb240 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -25,7 +25,7 @@ import ( func (check *Checker) funcInst(tsig *Signature, pos token.Pos, x *operand, ix *typeparams.IndexExpr) { assert(tsig != nil || ix != nil) - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, pos, 1, 18) { check.softErrorf(inNode(ix.Orig, ix.Lbrack), UnsupportedFeature, "function instantiation requires go1.18 or later") } @@ -283,7 +283,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind { // is an error checking its arguments (for example, if an incorrect number // of arguments is supplied). if got == want && want > 0 { - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, ix.Pos(), 1, 18) { check.softErrorf(inNode(call.Fun, ix.Lbrack), UnsupportedFeature, "function instantiation requires go1.18 or later") } @@ -445,7 +445,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type // infer type arguments and instantiate signature if necessary if sig.TypeParams().Len() > 0 { - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, call.Pos(), 1, 18) { switch call.Fun.(type) { case *ast.IndexExpr, *ast.IndexListExpr: ix := typeparams.UnpackIndexExpr(call.Fun) diff --git a/src/go/types/check.go b/src/go/types/check.go index 83e2995bbc..58cf6d060c 100644 --- a/src/go/types/check.go +++ b/src/go/types/check.go @@ -118,6 +118,7 @@ type Checker struct { // (initialized by Files, valid only for the duration of check.Files; // maps and lists are allocated on demand) files []*ast.File // package files + posVers map[*token.File]version // Pos -> Go version mapping imports []*PkgName // list of imported packages dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through recvTParamMap map[*ast.Ident]*TypeParam // maps blank receiver type parameters to their type @@ -284,6 +285,38 @@ func (check *Checker) initFiles(files []*ast.File) { // ignore this file } } + + for _, file := range check.files { + 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 v.before(check.version) && check.version.before(version{1, 21}) { + continue + } + if check.posVers == nil { + check.posVers = make(map[*token.File]version) + } + check.posVers[check.fset.File(file.FileStart)] = v + } + } +} + +// A posVers records that the file starting at pos declares the Go version vers. +type posVers struct { + pos token.Pos + vers version } // A bailout panic is used for early termination. diff --git a/src/go/types/conversions.go b/src/go/types/conversions.go index 8853926afe..92ae8196c5 100644 --- a/src/go/types/conversions.go +++ b/src/go/types/conversions.go @@ -181,7 +181,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool { switch a := Tu.(type) { case *Array: if Identical(s.Elem(), a.Elem()) { - if check == nil || check.allowVersion(check.pkg, 1, 20) { + if check == nil || check.allowVersion(check.pkg, x.Pos(), 1, 20) { return true } // check != nil @@ -194,7 +194,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool { case *Pointer: if a, _ := under(a.Elem()).(*Array); a != nil { if Identical(s.Elem(), a.Elem()) { - if check == nil || check.allowVersion(check.pkg, 1, 17) { + if check == nil || check.allowVersion(check.pkg, x.Pos(), 1, 17) { return true } // check != nil diff --git a/src/go/types/decl.go b/src/go/types/decl.go index 3065da2e8e..c18c7c1ae1 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -561,7 +561,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { check.validType(t) } // If typ is local, an error was already reported where typ is specified/defined. - if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, 1, 18) { + if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, tdecl.Pos(), 1, 18) { check.errorf(tdecl.Type, UnsupportedFeature, "using type constraint %s requires go1.18 or later", rhs) } }).describef(obj, "validType(%s)", obj.Name()) @@ -576,7 +576,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { // alias declaration if alias { - if !check.allowVersion(check.pkg, 1, 9) { + if !check.allowVersion(check.pkg, tdecl.Pos(), 1, 9) { check.error(atPos(tdecl.Assign), UnsupportedFeature, "type aliases requires go1.9 or later") } diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 04e6f6d9f7..0db80ca44b 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -955,7 +955,7 @@ func (check *Checker) shift(x, y *operand, e ast.Expr, op token.Token) { // Check that RHS is otherwise at least of integer type. switch { case allInteger(y.typ): - if !allUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) { + if !allUnsigned(y.typ) && !check.allowVersion(check.pkg, x.Pos(), 1, 13) { check.errorf(y, UnsupportedFeature, invalidOp+"signed shift count %s requires go1.13 or later", y) x.mode = invalid return diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go index 083a66cfe0..939fdb38f4 100644 --- a/src/go/types/generate_test.go +++ b/src/go/types/generate_test.go @@ -106,7 +106,7 @@ var filemap = map[string]action{ // "initorder.go": fixErrErrorfCall, // disabled for now due to unresolved error_ use implications for gopls "instantiate.go": func(f *ast.File) { fixTokenPos(f); fixCheckErrorfCall(f) }, "instantiate_test.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/types2"`, `"go/types"`) }, - "lookup.go": nil, + "lookup.go": func(f *ast.File) { fixTokenPos(f) }, "main_test.go": nil, "map.go": nil, "named.go": func(f *ast.File) { fixTokenPos(f); fixTraceSel(f) }, diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 2e94e51c6a..652511ffd8 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -178,7 +178,7 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type, // the parameterized type. bound := check.subst(pos, tpar.bound, smap, nil, ctxt) var cause string - if !check.implements(targs[i], bound, true, &cause) { + if !check.implements(pos, targs[i], bound, true, &cause) { return i, errors.New(cause) } } @@ -191,7 +191,7 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type, // // If the provided cause is non-nil, it may be set to an error string // explaining why V does not implement (or satisfy, for constraints) T. -func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool { +func (check *Checker) implements(pos token.Pos, V, T Type, constraint bool, cause *string) bool { Vu := under(V) Tu := under(T) if Vu == Typ[Invalid] || Tu == Typ[Invalid] { @@ -264,7 +264,7 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool // so that ordinary, non-type parameter interfaces implement comparable. if constraint && comparable(V, true /* spec comparability */, nil, nil) { // V is comparable if we are at Go 1.20 or higher. - if check == nil || check.allowVersion(check.pkg, 1, 20) { + if check == nil || check.allowVersion(check.pkg, pos, 1, 20) { return true } if cause != nil { diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index 9e6367f9c9..8187cfb1a5 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -10,6 +10,7 @@ package types import ( "bytes" + "go/token" "strings" ) @@ -507,14 +508,14 @@ func (check *Checker) assertableTo(V, T Type, cause *string) bool { // in constraint position (we have not yet defined that behavior in the spec). // The underlying type of V must be an interface. // If the result is false and cause is not nil, *cause is set to the error cause. -func (check *Checker) newAssertableTo(V, T Type, cause *string) bool { +func (check *Checker) newAssertableTo(pos token.Pos, V, T Type, cause *string) bool { // no static check is required if T is an interface // spec: "If T is an interface type, x.(T) asserts that the // dynamic type of x implements the interface T." if IsInterface(T) { return true } - return check.implements(T, V, false, cause) + return check.implements(pos, T, V, false, cause) } // deref dereferences typ if it is a *Pointer (but not a *Named type diff --git a/src/go/types/operand.go b/src/go/types/operand.go index a23e195ad6..c6c4542830 100644 --- a/src/go/types/operand.go +++ b/src/go/types/operand.go @@ -282,7 +282,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod // T is an interface type and x implements T and T is not a type parameter. // Also handle the case where T is a pointer to an interface. if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) { - if !check.implements(V, T, false, cause) { + if !check.implements(x.Pos(), V, T, false, cause) { return false, InvalidIfaceAssign } return true, 0 @@ -290,7 +290,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod // If V is an interface, check if a missing type assertion is the problem. if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil { - if check.implements(T, V, false, nil) { + if check.implements(x.Pos(), T, V, false, nil) { // T implements V, so give hint about type assertion. if cause != nil { *cause = "need type assertion" diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go index f7a78d2eb2..d0875f5961 100644 --- a/src/go/types/resolver.go +++ b/src/go/types/resolver.go @@ -386,7 +386,7 @@ func (check *Checker) collectObjects() { check.declarePkgObj(name, obj, di) } case typeDecl: - if d.spec.TypeParams.NumFields() != 0 && !check.allowVersion(pkg, 1, 18) { + if d.spec.TypeParams.NumFields() != 0 && !check.allowVersion(pkg, d.spec.Pos(), 1, 18) { check.softErrorf(d.spec.TypeParams.List[0], UnsupportedFeature, "type parameter requires go1.18 or later") } obj := NewTypeName(d.spec.Name.Pos(), pkg, d.spec.Name.Name, nil) @@ -444,7 +444,7 @@ func (check *Checker) collectObjects() { } check.recordDef(d.decl.Name, obj) } - if d.decl.Type.TypeParams.NumFields() != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError { + if d.decl.Type.TypeParams.NumFields() != 0 && !check.allowVersion(pkg, d.decl.Pos(), 1, 18) && !hasTParamError { check.softErrorf(d.decl.Type.TypeParams.List[0], UnsupportedFeature, "type parameter requires go1.18 or later") } info := &declInfo{file: fileScope, fdecl: d.decl} diff --git a/src/go/types/typeset.go b/src/go/types/typeset.go index 926bb86677..3f0b81419a 100644 --- a/src/go/types/typeset.go +++ b/src/go/types/typeset.go @@ -245,7 +245,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } // check != nil check.later(func() { - if !check.allowVersion(m.pkg, 1, 14) || !Identical(m.typ, other.Type()) { + if !check.allowVersion(m.pkg, pos, 1, 14) || !Identical(m.typ, other.Type()) { check.errorf(atPos(pos), DuplicateDecl, "duplicate method %s", m.name) check.errorf(atPos(mpos[other.(*Func)]), DuplicateDecl, "\tother declaration of %s", m.name) // secondary error, \t indented } @@ -276,7 +276,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T assert(!isTypeParam(typ)) tset := computeInterfaceTypeSet(check, pos, u) // If typ is local, an error was already reported where typ is specified/defined. - if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, 1, 18) { + if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, pos, 1, 18) { check.errorf(atPos(pos), UnsupportedFeature, "embedding constraint interface %s requires go1.18 or later", typ) continue } @@ -286,7 +286,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T } terms = tset.terms case *Union: - if check != nil && !check.allowVersion(check.pkg, 1, 18) { + if check != nil && !check.allowVersion(check.pkg, pos, 1, 18) { check.errorf(atPos(pos), UnsupportedFeature, "embedding interface element %s requires go1.18 or later", u) continue } @@ -301,7 +301,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T if u == Typ[Invalid] { continue } - if check != nil && !check.allowVersion(check.pkg, 1, 18) { + if check != nil && !check.allowVersion(check.pkg, pos, 1, 18) { check.errorf(atPos(pos), UnsupportedFeature, "embedding non-interface type %s requires go1.18 or later", typ) continue } diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index 35f27ddcc5..d01289a9d1 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -43,7 +43,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool) } return case universeAny, universeComparable: - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, e.Pos(), 1, 18) { check.versionErrorf(e, "go1.18", "predeclared %s", e.Name) return // avoid follow-on errors } @@ -273,7 +273,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) { case *ast.IndexExpr, *ast.IndexListExpr: ix := typeparams.UnpackIndexExpr(e) - if !check.allowVersion(check.pkg, 1, 18) { + if !check.allowVersion(check.pkg, e.Pos(), 1, 18) { check.softErrorf(inNode(e, ix.Lbrack), UnsupportedFeature, "type instantiation requires go1.18 or later") } return check.instantiatedType(ix, def) diff --git a/src/go/types/version.go b/src/go/types/version.go index 256c3ec05d..e02074cf01 100644 --- a/src/go/types/version.go +++ b/src/go/types/version.go @@ -5,12 +5,10 @@ package types import ( - "fmt" + "errors" "go/ast" "go/token" - "internal/lazyregexp" . "internal/types/errors" - "strconv" "strings" ) @@ -18,7 +16,7 @@ import ( // literal is not compatible with the current language version. func (check *Checker) langCompat(lit *ast.BasicLit) { s := lit.Value - if len(s) <= 2 || check.allowVersion(check.pkg, 1, 13) { + if len(s) <= 2 || check.allowVersion(check.pkg, lit.Pos(), 1, 13) { return } // len(s) > 2 @@ -45,12 +43,21 @@ 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, major, minor int) bool { +func (check *Checker) allowVersion(pkg *Package, pos token.Pos, major, minor int) 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, use that to decide. + if check.posVers != nil { + if v, ok := check.posVers[check.fset.File(pos)]; ok && v.major >= 1 { + return v.major > major || v.major == major && v.minor >= minor + } + } + + // Otherwise fall back to the version in the checker. ma, mi := check.version.major, check.version.minor return ma == 0 && mi == 0 || ma > major || ma == major && mi >= minor } @@ -59,6 +66,8 @@ type version struct { major, minor int } +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. @@ -66,18 +75,52 @@ func parseGoVersion(s string) (v version, err error) { if s == "" { return } - matches := goVersionRx.FindStringSubmatch(s) - if matches == nil { - err = fmt.Errorf(`should be something like "go1.12"`) + if !strings.HasPrefix(s, "go") { + return version{}, errVersionSyntax + } + 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 + } + v.major = 10*v.major + int(s[i]) - '0' + } + if i > 0 && i == len(s) { return } - v.major, err = strconv.Atoi(matches[1]) - if err != nil { + if i == 0 || s[i] != '.' { + return version{}, errVersionSyntax + } + 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 } - v.minor, err = strconv.Atoi(matches[2]) - return + i = 0 + for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { + if i >= 10 || i == 0 && s[i] == '0' { + return version{}, errVersionSyntax + } + v.minor = 10*v.minor + int(s[i]) - '0' + } + if i > 0 && i == len(s) { + return + } + return version{}, errVersionSyntax +} + +func (v version) equal(u version) bool { + return v.major == u.major && v.minor == u.minor } -// goVersionRx matches a Go version string, e.g. "go1.12". -var goVersionRx = lazyregexp.New(`^go([1-9]\d*)\.(0|[1-9]\d*)$`) +func (v version) before(u version) bool { + return v.major < u.major || v.major == u.major && v.minor < u.minor +} + +func (v version) after(u version) bool { + return v.major > u.major || v.major == u.major && v.minor > u.minor +} diff --git a/src/internal/types/testdata/check/go1_19_20.go b/src/internal/types/testdata/check/go1_19_20.go new file mode 100644 index 0000000000..52e5dfdc4a --- /dev/null +++ b/src/internal/types/testdata/check/go1_19_20.go @@ -0,0 +1,17 @@ +// -lang=go1.19 + +// Copyright 2022 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. + +// Check Go language version-specific errors. + +//go:build go1.20 + +package p + +type Slice []byte +type Array [8]byte + +var s Slice +var p = (Array)(s /* ok */) diff --git a/src/internal/types/testdata/check/go1_20_19.go b/src/internal/types/testdata/check/go1_20_19.go new file mode 100644 index 0000000000..08365a7cfb --- /dev/null +++ b/src/internal/types/testdata/check/go1_20_19.go @@ -0,0 +1,17 @@ +// -lang=go1.20 + +// Copyright 2022 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. + +// Check Go language version-specific errors. + +//go:build go1.19 + +package p + +type Slice []byte +type Array [8]byte + +var s Slice +var p = (Array)(s /* ok because Go 1.20 ignored the //go:build go1.19 */) diff --git a/src/internal/types/testdata/check/go1_21_19.go b/src/internal/types/testdata/check/go1_21_19.go new file mode 100644 index 0000000000..2acd25865d --- /dev/null +++ b/src/internal/types/testdata/check/go1_21_19.go @@ -0,0 +1,17 @@ +// -lang=go1.21 + +// Copyright 2022 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. + +// Check Go language version-specific errors. + +//go:build go1.19 + +package p + +type Slice []byte +type Array [8]byte + +var s Slice +var p = (Array)(s /* ERROR "requires go1.20 or later" */) -- 2.48.1