]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types, cmd/compile/internal/types2: use per-file Go version
authorRuss Cox <rsc@golang.org>
Tue, 14 Mar 2023 01:06:49 +0000 (21:06 -0400)
committerGopher Robot <gobot@golang.org>
Fri, 14 Apr 2023 16:08:31 +0000 (16:08 +0000)
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 <gobot@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>

33 files changed:
src/cmd/compile/internal/types2/api.go
src/cmd/compile/internal/types2/builtins.go
src/cmd/compile/internal/types2/call.go
src/cmd/compile/internal/types2/check.go
src/cmd/compile/internal/types2/conversions.go
src/cmd/compile/internal/types2/decl.go
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/instantiate.go
src/cmd/compile/internal/types2/lookup.go
src/cmd/compile/internal/types2/operand.go
src/cmd/compile/internal/types2/resolver.go
src/cmd/compile/internal/types2/typeset.go
src/cmd/compile/internal/types2/typexpr.go
src/cmd/compile/internal/types2/version.go
src/go/build/deps_test.go
src/go/types/api.go
src/go/types/builtins.go
src/go/types/call.go
src/go/types/check.go
src/go/types/conversions.go
src/go/types/decl.go
src/go/types/expr.go
src/go/types/generate_test.go
src/go/types/instantiate.go
src/go/types/lookup.go
src/go/types/operand.go
src/go/types/resolver.go
src/go/types/typeset.go
src/go/types/typexpr.go
src/go/types/version.go
src/internal/types/testdata/check/go1_19_20.go [new file with mode: 0644]
src/internal/types/testdata/check/go1_20_19.go [new file with mode: 0644]
src/internal/types/testdata/check/go1_21_19.go [new file with mode: 0644]

index e027b9a7e2405eecfd14355d40b4ca1ba8d79f66..24131192f8fd0401866fb81f3c2d025d999f03e9 100644 (file)
@@ -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.
index 67aa37e401351c6468091faff9d6439769d1c66b..915eb2db9e6ebb95e4a36538921ffdfb78d7118d 100644 (file)
@@ -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
                }
index a47deb9a225d38f7d98f1bea8779c336ecb37026..08c90b9f8f9a33ac16e9960c4f3ed574ac033796 100644 (file)
@@ -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 {
index 0c9e80e014ea77a282bf7fe065a76aa37b1bdc50..5f0c521a2a488abd67dc51931dc1c7098f2b9d8b 100644 (file)
@@ -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.
index 267324421ddab9721bde210ca222c39a79b48688..7cb7d490be3250f1e00791e091b8e19059af3190 100644 (file)
@@ -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
index afa32c1a5f7ea04b7274d8e8d649d9f5859a16a0..f7c6a8e5734a6d3e15e64f43793b18d656581318 100644 (file)
@@ -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")
                }
 
index 1424e43876f34956d6eddc73066374bb105ff588..7c3b40f086811e2e7cd5ef02769fa186234240a6 100644 (file)
@@ -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
index 8d3fee9edd6ea7ffe4189b363cb4d4cc9b4d6949..7329fffc867ebcf9e084859598a864c7f7f3ae93 100644 (file)
@@ -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 {
index d2694fc974b0c5c548c34d70eb2672343d4aacf6..e0b19718a154b8803422ceb9b3f60ee0803e469b 100644 (file)
@@ -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
index e49afee987e0ad7b198b589eb2b0aed65ea72031..344fe292c59999741016c4b25c6e2ee75ff4bc79 100644 (file)
@@ -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"
index 8aeafceadbcddc71450f7696ebad99cc4bcfd55f..cfaca2b6659821d17b8299352cc15171e7088772 100644 (file)
@@ -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}
index af5aa40949c473496eb18174eaa6a276a0a4adf7..26c20cb38028cd4f0da79c87afb25a8eac9eb5af 100644 (file)
@@ -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
                        }
index 03b2a8488ee6049ede040dd5d51901fc273a6093..f9734546455c282283486d038c94a0c48b745ac2 100644 (file)
@@ -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)
index 8fd76a381d9680363e639f7c97d7eaacb9a65c0d..37e86a2bb4c4e4dd78c7cfd4177133e0420638e5 100644 (file)
@@ -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
+}
index 306d0390343f4a8a49e1cbd3092e321c33ba8492..de80ed041f48b299c83a9303e9f76d5a33e9ef75 100644 (file)
@@ -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;
 
index 06bdb5616d81142bfee77549dca48334d4d05cba..a144462968896145867d032052ca80e70361427b 100644 (file)
@@ -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.
index a7a3af2725eb09c2f0f221a9b0f915801b175373..eb9b3ed3b2f0917abe3ab9ef2e38432be531b721 100644 (file)
@@ -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
                }
index fb0a6cea3cf90912e65617d2532b08914e71504b..f220efb24056e90018200dcc811f45b367827332 100644 (file)
@@ -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)
index 83e2995bbc2dd9b1ea39598979480d5179ad8cac..58cf6d060cb5e78794a60c91f68fb42c3098f8fb 100644 (file)
@@ -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.
index 8853926afe9eae2b2401d696330278e3133e067d..92ae8196c5a447a29acf91b683d68cc56f7b5943 100644 (file)
@@ -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
index 3065da2e8ebeace92cdc15ef187b63084b2ddb79..c18c7c1ae106cbe5266bb3fe355797b16d07bb66 100644 (file)
@@ -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")
                }
 
index 04e6f6d9f7a565643da44961b2d44c123d338f35..0db80ca44b31554c4b74f83ad0dba8d2e7ce8d98 100644 (file)
@@ -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
index 083a66cfe068b9488e3912be8083ba8924e40b62..939fdb38f4b2fba3fb2ba7add7a4e0ef02dacbb3 100644 (file)
@@ -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) },
index 2e94e51c6af7a02481ce22ef28da76b647d9b276..652511ffd882a8d40ea4257df616ebbde619d358 100644 (file)
@@ -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 {
index 9e6367f9c9f28138291592f9d922adc1bac91dae..8187cfb1a5da155f95e55a6548d313adf62b07d8 100644 (file)
@@ -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
index a23e195ad6bd636930c91f8351e331f2dc388d4d..c6c454283083dc2cc595ac25a69aa7e2ca4ef08a 100644 (file)
@@ -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"
index f7a78d2eb2df6feebb8da5ef56280cadbad50c23..d0875f59611712e38f9db2aedf3e5a38c4f8af91 100644 (file)
@@ -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}
index 926bb8667775148ce6692eb865bbdc52eca68875..3f0b81419afcdae7349805f5ac883c5494f6564a 100644 (file)
@@ -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
                        }
index 35f27ddcc5ae86703dc196c9baeaec8222622f06..d01289a9d1b4d51bb9097b5fc43094a8d61d5ba4 100644 (file)
@@ -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)
index 256c3ec05d3f53d5c48428ef69f4ae1a659d9070..e02074cf010fb7d18482698ba813c45b494cb1c0 100644 (file)
@@ -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 (file)
index 0000000..52e5dfd
--- /dev/null
@@ -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 (file)
index 0000000..08365a7
--- /dev/null
@@ -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 (file)
index 0000000..2acd258
--- /dev/null
@@ -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" */)