go/types, cmd/compile/internal/types2: use per-file Go version

For #57001, compilers and others tools will need to understand that
a different Go version can be used in different files in a program,
according to the //go:build lines in those files.

Update go/types and cmd/compile/internal/types2 to track and
use per-file Go versions. The two must be updated together because
of the files in go/types that are generated from files in types2.

The effect of the //go:build go1.N line depends on the Go version
declared in the 'go 1.M' line in go.mod. If N > M, the file gets go1.N
semantics when built with a Go 1.N or later toolchain
(when built with an earlier toolchain the //go:build line will keep
the file from being built at all).
If N < M, then in general we want the file to get go1.N semantics
as well, meaning later features are disabled. However, older Go 1.M
did not apply this kind of downgrade, so for compatibility, N < M
only has an effect when M >= 21, meaning when using semantics
from Go 1.21 or later.

For #59033.

Change-Id: I93cf07e6c687d37bd37a9461dc60cc032bafd01d
Reviewed-on: https://go-review.googlesource.com/c/go/+/476278
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
This commit is contained in:
Russ Cox 2023-03-13 21:06:49 -04:00 committed by Gopher Robot
parent 8854be4180
commit 804d786a30
33 changed files with 315 additions and 97 deletions

View 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.

View 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
}

View 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 {

View 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.

View 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

View 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")
}

View 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

View 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 {

View 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

View 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"

View 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}

View 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
}

View 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)

View 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
}
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
}
// 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
}

View 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;

View 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.

View 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
}

View 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)

View 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.

View 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

View 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")
}

View 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

View 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) },

View 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 {

View 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

View 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"

View 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}

View 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
}

View 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)

View 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
}
// 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) 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
}

View File

@ -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 */)

View File

@ -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 */)

View File

@ -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" */)