diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index e027b9a7e2405..24131192f8fd0 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 67aa37e401351..915eb2db9e6eb 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 a47deb9a225d3..08c90b9f8f9a3 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 0c9e80e014ea7..5f0c521a2a488 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 267324421ddab..7cb7d490be325 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 afa32c1a5f7ea..f7c6a8e5734a6 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 1424e43876f34..7c3b40f086811 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 8d3fee9edd6ea..7329fffc867eb 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 d2694fc974b0c..e0b19718a154b 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 e49afee987e0a..344fe292c5999 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 8aeafceadbcdd..cfaca2b665982 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 af5aa40949c47..26c20cb38028c 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 03b2a8488ee60..f9734546455c2 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 8fd76a381d968..37e86a2bb4c4e 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 306d0390343f4..de80ed041f48b 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 06bdb5616d811..a144462968896 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 a7a3af2725eb0..eb9b3ed3b2f09 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 fb0a6cea3cf90..f220efb24056e 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 83e2995bbc2dd..58cf6d060cb5e 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 8853926afe9ea..92ae8196c5a44 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 3065da2e8ebea..c18c7c1ae106c 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 04e6f6d9f7a56..0db80ca44b315 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 083a66cfe068b..939fdb38f4b2f 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 2e94e51c6af7a..652511ffd882a 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 9e6367f9c9f28..8187cfb1a5da1 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 a23e195ad6bd6..c6c454283083d 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 f7a78d2eb2df6..d0875f5961171 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 926bb86677751..3f0b81419afcd 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 35f27ddcc5ae8..d01289a9d1b4d 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 256c3ec05d3f5..e02074cf010fb 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 0000000000000..52e5dfdc4a778 --- /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 0000000000000..08365a7cfb564 --- /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 0000000000000..2acd25865d4b6 --- /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" */)