go/ast, go/build/constraint: support for per-file Go versions using //go:build lines
rsc opened this issue · 16 comments
As part of the discussion during the acceptance of #57001, we decided the following.
- Starting in Go 1.21, we will interpret the
goline in go.mod as the minimum Go version required. For example if Go 1.21 encounters a go.mod file that saysgo 1.22, it will refuse to build code in that module. - To allow writing a module with a low minimum Go version but that can use newer features in //go:build-tagged files, we will recognize
//go:buildconstraints that require a newer Go version than the go.modgoline and allow newer Go features in those files. We are also going to allow a//go:buildconstraint to declare an older version, which will lock out newer Go features that would normally be allowed by the go.modgoversion. For compatibility reasons, that “downgrading” can only apply for go.mod files sayinggo 1.21or later.
To implement (2), we need a small amount of new API: go/build/constraint needs to export code to compute the implied minimum Go version from a constraint.Expr, and go/ast needs to expose the minimum Go version as a new field in ast.File.
This proposal is about that new API. I propose:
package constraint
// GoVersion returns the minimum Go version implied by a given build expression.
// If the expression can be satisfied without any Go version tags, GoVersion returns an empty string.
//
// For example:
//
// GoVersion(linux && go1.22) = "go1.22"
// GoVersion((linux && go1.22) || (windows && go1.20)) = "go1.20" => go1.20
// GoVersion(linux) = ""
// GoVersion(linux || (windows && go1.22)) = ""
//
// GoVersion assumes that any tag or negated tag may independently be true,
// so that its analysis can be purely structural, without SAT solving.
// “Impossible” subexpressions may therefore affect the result.
//
// For example:
//
// GoVersion((linux && !linux && go1.20) || go1.21) = "go1.20"
func GoVersion(x Expr) string
And
package ast
type File struct {
...
GoVersion string // minimum Go version required by //go:build directives
}
go/parser would populate ast.File.GoVersion, and go/types would use that information to decide whether specific features are allowed in certain files.
Change https://go.dev/cl/476275 mentions this issue: go/build/constraint: add GoVersion
Change https://go.dev/cl/476276 mentions this issue: go/ast: add File.GoVersion
Change https://go.dev/cl/476277 mentions this issue: go/parser: report //go:build-derived Go version in ast.File.GoVersion
Change https://go.dev/cl/476278 mentions this issue: go/types, cmd/compile/internal/types2: use per-file Go version
It seems like this does slightly interfere with "all developers working on a project will agree on the toolchain version"; if I am using 1.22, someone else is using 1.23, the app under development has a 1.21 go.mod minimum version but contains files with 1.23 build tags, the two of us will see different builds of the app. I still want this change, though.
All developers still get the same, reproducible build. That build just agrees on which files use which Go dialects. I don't think this conflicts with that.
I should add a ! example; GoVersion(!go1.22) = "".
This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group
Is there a comment in the prior issue which summarizes the use case for having an older toolchain version for an individual file then the minimum go version in the module? It seems like a strange case to me.
Is there a comment in the prior issue which summarizes the use case for having an older toolchain version for an individual file then the minimum go version in the module? It seems like a strange case to me.
If you are updating and know that the older file needs older semantics, you can set the default for the module (to cover new files created there) and then special-case the few files you need older semantics in.
Based on the discussion above, this proposal seems like a likely accept.
— rsc for the proposal review group
No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— rsc for the proposal review group
Change https://go.dev/cl/499415 mentions this issue: doc/go1.21: mention directive handling in go/{ast,build}
I think this was implemented in Go 1.21. @rsc, is there anything more to do for it?
Done!