Skip to content

Commit

Permalink
go/types, cmd/compile/internal/types2: use per-file Go version
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
rsc authored and gopherbot committed Apr 14, 2023
1 parent 8854be4 commit 804d786
Show file tree
Hide file tree
Showing 33 changed files with 315 additions and 97 deletions.
6 changes: 3 additions & 3 deletions src/cmd/compile/internal/types2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -489,15 +489,15 @@ 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.
//
// 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.
Expand Down
12 changes: 6 additions & 6 deletions src/cmd/compile/internal/types2/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions src/cmd/compile/internal/types2/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

Expand Down Expand Up @@ -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")
}

Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions src/cmd/compile/internal/types2/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/decl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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")
}

Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/cmd/compile/internal/types2/instantiate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand All @@ -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] {
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions src/cmd/compile/internal/types2/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package types2

import (
"bytes"
"cmd/compile/internal/syntax"
"strings"
)

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/operand.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,15 @@ 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
}

// 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"
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/compile/internal/types2/typeset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/typexpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 804d786

Please sign in to comment.