Skip to content

Commit aeac0b6

Browse files
committed
go/types, types2: use max(fileVersion, go1.21) if fileVersion present
Change the rules for how //go:build "file versions" are applied: instead of considering whether a file version is an upgrade or downgrade from the -lang version, always use max(fileVersion, go1.21). This prevents file versions from downgrading the version below go1.21. Before Go 1.21 the //go:build version did not have the meaning of setting the file's langage version. This fixes an issue that was appearing in GOPATH builds: Go 1.23.0 started providing -lang versions to the compiler in GOPATH mode (among other places) which it wasn't doing before, and it set -lang to the toolchain version (1.23). Because the -lang version was greater than go1.21, language version used to compile the file would be set to the //go:build file version. //go:build file versions below 1.21 could cause files that could previously build to stop building. For example, take a Go file with a //go:build line specifying go1.10. If that file used a 1.18 feature, that use would compile fine with a Go 1.22 toolchain. But it would produce an error when compiling with the 1.23.0 toolchain because it set the language version to 1.10 and disallowed the 1.18 feature. This breaks backwards compatibility: when the build tag was added, it did not have the meaning of restricting the language version. For #68658 Change-Id: I6cedda81a55bcccffaa3501eef9e2be6541b6ece Reviewed-on: https://go-review.googlesource.com/c/go/+/607955 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Griesemer <gri@google.com>
1 parent 54fe0fd commit aeac0b6

File tree

11 files changed

+174
-99
lines changed

11 files changed

+174
-99
lines changed

src/cmd/compile/internal/types2/api_test.go

+38-12
Original file line numberDiff line numberDiff line change
@@ -3031,22 +3031,48 @@ func TestFileVersions(t *testing.T) {
30313031
fileVersion string
30323032
wantVersion string
30333033
}{
3034-
{"", "", ""}, // no versions specified
3035-
{"go1.19", "", "go1.19"}, // module version specified
3036-
{"", "go1.20", ""}, // file upgrade ignored
3037-
{"go1.19", "go1.20", "go1.20"}, // file upgrade permitted
3038-
{"go1.20", "go1.19", "go1.20"}, // file downgrade not permitted
3039-
{"go1.21", "go1.19", "go1.19"}, // file downgrade permitted (module version is >= go1.21)
3034+
{"", "", ""}, // no versions specified
3035+
{"go1.19", "", "go1.19"}, // module version specified
3036+
{"", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3037+
{"go1", "", "go1"}, // no file version specified
3038+
{"go1", "goo1.22", "go1"}, // invalid file version specified
3039+
{"go1", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3040+
{"go1", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3041+
{"go1", "go1.21", "go1.21"}, // file version specified at 1.21
3042+
{"go1", "go1.22", "go1.22"}, // file version specified above 1.21
3043+
{"go1.19", "", "go1.19"}, // no file version specified
3044+
{"go1.19", "goo1.22", "go1.19"}, // invalid file version specified
3045+
{"go1.19", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3046+
{"go1.19", "go1.21", "go1.21"}, // file version specified at 1.21
3047+
{"go1.19", "go1.22", "go1.22"}, // file version specified above 1.21
3048+
{"go1.20", "", "go1.20"}, // no file version specified
3049+
{"go1.20", "goo1.22", "go1.20"}, // invalid file version specified
3050+
{"go1.20", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3051+
{"go1.20", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3052+
{"go1.20", "go1.21", "go1.21"}, // file version specified at 1.21
3053+
{"go1.20", "go1.22", "go1.22"}, // file version specified above 1.21
3054+
{"go1.21", "", "go1.21"}, // no file version specified
3055+
{"go1.21", "goo1.22", "go1.21"}, // invalid file version specified
3056+
{"go1.21", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3057+
{"go1.21", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3058+
{"go1.21", "go1.21", "go1.21"}, // file version specified at 1.21
3059+
{"go1.21", "go1.22", "go1.22"}, // file version specified above 1.21
3060+
{"go1.22", "", "go1.22"}, // no file version specified
3061+
{"go1.22", "goo1.22", "go1.22"}, // invalid file version specified
3062+
{"go1.22", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3063+
{"go1.22", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3064+
{"go1.22", "go1.21", "go1.21"}, // file version specified at 1.21
3065+
{"go1.22", "go1.22", "go1.22"}, // file version specified above 1.21
30403066

30413067
// versions containing release numbers
30423068
// (file versions containing release numbers are considered invalid)
30433069
{"go1.19.0", "", "go1.19.0"}, // no file version specified
3044-
{"go1.20", "go1.20.1", "go1.20"}, // file upgrade ignored
3045-
{"go1.20.1", "go1.20", "go1.20.1"}, // file upgrade ignored
3046-
{"go1.20.1", "go1.21", "go1.21"}, // file upgrade permitted
3047-
{"go1.20.1", "go1.19", "go1.20.1"}, // file downgrade not permitted
3048-
{"go1.21.1", "go1.19.1", "go1.21.1"}, // file downgrade not permitted (invalid file version)
3049-
{"go1.21.1", "go1.19", "go1.19"}, // file downgrade permitted (module version is >= go1.21)
3070+
{"go1.20.1", "go1.19.1", "go1.20.1"}, // invalid file version
3071+
{"go1.20.1", "go1.21.1", "go1.20.1"}, // invalid file version
3072+
{"go1.21.1", "go1.19.1", "go1.21.1"}, // invalid file version
3073+
{"go1.21.1", "go1.21.1", "go1.21.1"}, // invalid file version
3074+
{"go1.22.1", "go1.19.1", "go1.22.1"}, // invalid file version
3075+
{"go1.22.1", "go1.21.1", "go1.22.1"}, // invalid file version
30503076
} {
30513077
var src string
30523078
if test.fileVersion != "" {

src/cmd/compile/internal/types2/check.go

+19-28
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@ func (check *Checker) initFiles(files []*syntax.File) {
326326
check.errorf(files[0], TooNew, "package requires newer Go version %v (application built with %v)",
327327
check.version, go_current)
328328
}
329-
downgradeOk := check.version.cmp(go1_21) >= 0
330329

331330
// determine Go version for each file
332331
for _, file := range check.files {
@@ -335,33 +334,18 @@ func (check *Checker) initFiles(files []*syntax.File) {
335334
// unlike file versions which are Go language versions only, if valid.)
336335
v := check.conf.GoVersion
337336

338-
fileVersion := asGoVersion(file.GoVersion)
339-
if fileVersion.isValid() {
340-
// use the file version, if applicable
341-
// (file versions are either the empty string or of the form go1.dd)
342-
if pkgVersionOk {
343-
cmp := fileVersion.cmp(check.version)
344-
// Go 1.21 introduced the feature of setting the go.mod
345-
// go line to an early version of Go and allowing //go:build lines
346-
// to “upgrade” (cmp > 0) the Go version in a given file.
347-
// We can do that backwards compatibly.
348-
//
349-
// Go 1.21 also introduced the feature of allowing //go:build lines
350-
// to “downgrade” (cmp < 0) the Go version in a given file.
351-
// That can't be done compatibly in general, since before the
352-
// build lines were ignored and code got the module's Go version.
353-
// To work around this, downgrades are only allowed when the
354-
// module's Go version is Go 1.21 or later.
355-
//
356-
// If there is no valid check.version, then we don't really know what
357-
// Go version to apply.
358-
// Legacy tools may do this, and they historically have accepted everything.
359-
// Preserve that behavior by ignoring //go:build constraints entirely in that
360-
// case (!pkgVersionOk).
361-
if cmp > 0 || cmp < 0 && downgradeOk {
362-
v = file.GoVersion
363-
}
364-
}
337+
// If the file specifies a version, use max(fileVersion, go1.21).
338+
if fileVersion := asGoVersion(file.GoVersion); fileVersion.isValid() {
339+
// Go 1.21 introduced the feature of allowing //go:build lines
340+
// to sometimes set the Go version in a given file. Versions Go 1.21 and later
341+
// can be set backwards compatibly as that was the first version
342+
// files with go1.21 or later build tags could be built with.
343+
//
344+
// Set the version to max(fileVersion, go1.21): That will allow a
345+
// downgrade to a version before go1.22, where the for loop semantics
346+
// change was made, while being backwards compatible with versions of
347+
// go before the new //go:build semantics were introduced.
348+
v = string(versionMax(fileVersion, go1_21))
365349

366350
// Report a specific error for each tagged file that's too new.
367351
// (Normally the build system will have filtered files by version,
@@ -376,6 +360,13 @@ func (check *Checker) initFiles(files []*syntax.File) {
376360
}
377361
}
378362

363+
func versionMax(a, b goVersion) goVersion {
364+
if a.cmp(b) > 0 {
365+
return a
366+
}
367+
return b
368+
}
369+
379370
// A bailout panic is used for early termination.
380371
type bailout struct{}
381372

src/go/types/api_test.go

+38-12
Original file line numberDiff line numberDiff line change
@@ -3035,22 +3035,48 @@ func TestFileVersions(t *testing.T) {
30353035
fileVersion string
30363036
wantVersion string
30373037
}{
3038-
{"", "", ""}, // no versions specified
3039-
{"go1.19", "", "go1.19"}, // module version specified
3040-
{"", "go1.20", ""}, // file upgrade ignored
3041-
{"go1.19", "go1.20", "go1.20"}, // file upgrade permitted
3042-
{"go1.20", "go1.19", "go1.20"}, // file downgrade not permitted
3043-
{"go1.21", "go1.19", "go1.19"}, // file downgrade permitted (module version is >= go1.21)
3038+
{"", "", ""}, // no versions specified
3039+
{"go1.19", "", "go1.19"}, // module version specified
3040+
{"", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3041+
{"go1", "", "go1"}, // no file version specified
3042+
{"go1", "goo1.22", "go1"}, // invalid file version specified
3043+
{"go1", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3044+
{"go1", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3045+
{"go1", "go1.21", "go1.21"}, // file version specified at 1.21
3046+
{"go1", "go1.22", "go1.22"}, // file version specified above 1.21
3047+
{"go1.19", "", "go1.19"}, // no file version specified
3048+
{"go1.19", "goo1.22", "go1.19"}, // invalid file version specified
3049+
{"go1.19", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3050+
{"go1.19", "go1.21", "go1.21"}, // file version specified at 1.21
3051+
{"go1.19", "go1.22", "go1.22"}, // file version specified above 1.21
3052+
{"go1.20", "", "go1.20"}, // no file version specified
3053+
{"go1.20", "goo1.22", "go1.20"}, // invalid file version specified
3054+
{"go1.20", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3055+
{"go1.20", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3056+
{"go1.20", "go1.21", "go1.21"}, // file version specified at 1.21
3057+
{"go1.20", "go1.22", "go1.22"}, // file version specified above 1.21
3058+
{"go1.21", "", "go1.21"}, // no file version specified
3059+
{"go1.21", "goo1.22", "go1.21"}, // invalid file version specified
3060+
{"go1.21", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3061+
{"go1.21", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3062+
{"go1.21", "go1.21", "go1.21"}, // file version specified at 1.21
3063+
{"go1.21", "go1.22", "go1.22"}, // file version specified above 1.21
3064+
{"go1.22", "", "go1.22"}, // no file version specified
3065+
{"go1.22", "goo1.22", "go1.22"}, // invalid file version specified
3066+
{"go1.22", "go1.19", "go1.21"}, // file version specified below minimum of 1.21
3067+
{"go1.22", "go1.20", "go1.21"}, // file version specified below minimum of 1.21
3068+
{"go1.22", "go1.21", "go1.21"}, // file version specified at 1.21
3069+
{"go1.22", "go1.22", "go1.22"}, // file version specified above 1.21
30443070

30453071
// versions containing release numbers
30463072
// (file versions containing release numbers are considered invalid)
30473073
{"go1.19.0", "", "go1.19.0"}, // no file version specified
3048-
{"go1.20", "go1.20.1", "go1.20"}, // file upgrade ignored
3049-
{"go1.20.1", "go1.20", "go1.20.1"}, // file upgrade ignored
3050-
{"go1.20.1", "go1.21", "go1.21"}, // file upgrade permitted
3051-
{"go1.20.1", "go1.19", "go1.20.1"}, // file downgrade not permitted
3052-
{"go1.21.1", "go1.19.1", "go1.21.1"}, // file downgrade not permitted (invalid file version)
3053-
{"go1.21.1", "go1.19", "go1.19"}, // file downgrade permitted (module version is >= go1.21)
3074+
{"go1.20.1", "go1.19.1", "go1.20.1"}, // invalid file version
3075+
{"go1.20.1", "go1.21.1", "go1.20.1"}, // invalid file version
3076+
{"go1.21.1", "go1.19.1", "go1.21.1"}, // invalid file version
3077+
{"go1.21.1", "go1.21.1", "go1.21.1"}, // invalid file version
3078+
{"go1.22.1", "go1.19.1", "go1.22.1"}, // invalid file version
3079+
{"go1.22.1", "go1.21.1", "go1.22.1"}, // invalid file version
30543080
} {
30553081
var src string
30563082
if test.fileVersion != "" {

src/go/types/check.go

+20-28
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,6 @@ func (check *Checker) initFiles(files []*ast.File) {
347347
check.errorf(files[0], TooNew, "package requires newer Go version %v (application built with %v)",
348348
check.version, go_current)
349349
}
350-
downgradeOk := check.version.cmp(go1_21) >= 0
351350

352351
// determine Go version for each file
353352
for _, file := range check.files {
@@ -356,33 +355,19 @@ func (check *Checker) initFiles(files []*ast.File) {
356355
// unlike file versions which are Go language versions only, if valid.)
357356
v := check.conf.GoVersion
358357

359-
fileVersion := asGoVersion(file.GoVersion)
360-
if fileVersion.isValid() {
361-
// use the file version, if applicable
362-
// (file versions are either the empty string or of the form go1.dd)
363-
if pkgVersionOk {
364-
cmp := fileVersion.cmp(check.version)
365-
// Go 1.21 introduced the feature of setting the go.mod
366-
// go line to an early version of Go and allowing //go:build lines
367-
// to “upgrade” (cmp > 0) the Go version in a given file.
368-
// We can do that backwards compatibly.
369-
//
370-
// Go 1.21 also introduced the feature of allowing //go:build lines
371-
// to “downgrade” (cmp < 0) the Go version in a given file.
372-
// That can't be done compatibly in general, since before the
373-
// build lines were ignored and code got the module's Go version.
374-
// To work around this, downgrades are only allowed when the
375-
// module's Go version is Go 1.21 or later.
376-
//
377-
// If there is no valid check.version, then we don't really know what
378-
// Go version to apply.
379-
// Legacy tools may do this, and they historically have accepted everything.
380-
// Preserve that behavior by ignoring //go:build constraints entirely in that
381-
// case (!pkgVersionOk).
382-
if cmp > 0 || cmp < 0 && downgradeOk {
383-
v = file.GoVersion
384-
}
385-
}
358+
// If the file specifies a version, use max(fileVersion, go1.21).
359+
if fileVersion := asGoVersion(file.GoVersion); fileVersion.isValid() {
360+
// Go 1.21 introduced the feature of setting the go.mod
361+
// go line to an early version of Go and allowing //go:build lines
362+
// to set the Go version in a given file. Versions Go 1.21 and later
363+
// can be set backwards compatibly as that was the first version
364+
// files with go1.21 or later build tags could be built with.
365+
//
366+
// Set the version to max(fileVersion, go1.21): That will allow a
367+
// downgrade to a version before go1.22, where the for loop semantics
368+
// change was made, while being backwards compatible with versions of
369+
// go before the new //go:build semantics were introduced.
370+
v = string(versionMax(fileVersion, go1_21))
386371

387372
// Report a specific error for each tagged file that's too new.
388373
// (Normally the build system will have filtered files by version,
@@ -397,6 +382,13 @@ func (check *Checker) initFiles(files []*ast.File) {
397382
}
398383
}
399384

385+
func versionMax(a, b goVersion) goVersion {
386+
if a.cmp(b) < 0 {
387+
return b
388+
}
389+
return a
390+
}
391+
400392
// A bailout panic is used for early termination.
401393
type bailout struct{}
402394

src/internal/types/testdata/check/go1_20_19.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ type Slice []byte
1414
type Array [8]byte
1515

1616
var s Slice
17-
var p = (Array)(s /* ok because Go 1.20 ignored the //go:build go1.19 */)
17+
var p = (Array)(s /* ok because file versions below go1.21 set the langage version to go1.21 */)

src/internal/types/testdata/check/go1_21_19.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ type Slice []byte
1414
type Array [8]byte
1515

1616
var s Slice
17-
var p = (Array)(s /* ERROR "requires go1.20 or later" */)
17+
var p = (Array)(s /* ok because file versions below go1.21 set the langage version to go1.21 */)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// -lang=go1.21
2+
3+
// Copyright 2022 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// Check Go language version-specific errors.
8+
9+
//go:build go1.22
10+
11+
package p
12+
13+
func f() {
14+
for _ = range /* ok because of upgrade to 1.22 */ 10 {
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// -lang=go1.22
2+
3+
// Copyright 2024 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
// Check Go language version-specific errors.
8+
9+
//go:build go1.21
10+
11+
package p
12+
13+
func f() {
14+
for _ = range 10 /* ERROR "requires go1.22 or later" */ {
15+
}
16+
}

src/internal/types/testdata/fixedbugs/issue66285.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
// -lang=go1.21
1+
// -lang=go1.13
22

33
// Copyright 2024 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style
55
// license that can be found in the LICENSE file.
66

7-
// Note: Downgrading to go1.13 requires at least go1.21,
8-
// hence the need for -lang=go1.21 at the top.
9-
10-
//go:build go1.13
11-
127
package p
138

149
import "io"

test/fixedbugs/issue63489a.go

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
// errorcheck -lang=go1.21
1+
// errorcheck -lang=go1.22
22

33
// Copyright 2023 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style
55
// license that can be found in the LICENSE file.
66

7-
//go:build go1.4
7+
// This file has been changed from its original version as
8+
// //go:build file versions below 1.21 set the language version to 1.21.
9+
// The original tested a -lang version of 1.21 with a file version of
10+
// go1.4 while this new version tests a -lang version of go1.22
11+
// with a file version of go1.21.
812

9-
package p
10-
11-
const c = 0o123 // ERROR "file declares //go:build go1.4"
13+
//go:build go1.21
1214

13-
// ERROR "file declares //go:build go1.4"
15+
package p
1416

15-
//line issue63489a.go:13:1
16-
const d = 0o124
17+
func f() {
18+
for _ = range 10 { // ERROR "file declares //go:build go1.21"
19+
}
20+
}

test/fixedbugs/issue63489b.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
// errorcheck -lang=go1.4
1+
// errorcheck -lang=go1.21
22

33
// Copyright 2023 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style
55
// license that can be found in the LICENSE file.
66

7-
//go:build go1.4
7+
// This file has been changed from its original version as
8+
// //go:build file versions below 1.21 set the language version to 1.21.
9+
// The original tested a -lang version of 1.4 with a file version of
10+
// go1.4 while this new version tests a -lang version of go1.1
11+
// with a file version of go1.21.
12+
13+
//go:build go1.21
814

915
package p
1016

11-
const c = 0o123 // ERROR "file declares //go:build go1.4"
17+
func f() {
18+
for _ = range 10 { // ERROR "file declares //go:build go1.21"
19+
}
20+
}

0 commit comments

Comments
 (0)