Skip to content

go/ast, go/build/constraint: support for per-file Go versions using //go:build lines #59033

@rsc

Description

@rsc

As part of the discussion during the acceptance of #57001, we decided the following.

  1. Starting in Go 1.21, we will interpret the go line in go.mod as the minimum Go version required. For example if Go 1.21 encounters a go.mod file that says go 1.22, it will refuse to build code in that module.
  2. 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:build constraints that require a newer Go version than the go.mod go line and allow newer Go features in those files. We are also going to allow a //go:build constraint to declare an older version, which will lock out newer Go features that would normally be allowed by the go.mod go version. For compatibility reasons, that “downgrading” can only apply for go.mod files saying go 1.21 or 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions