golang.org/x/sys v0.23.0
golang.org/x/telemetry v0.0.0-20240828202201-a797f331ea97
golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292
- golang.org/x/tools v0.23.1-0.20240722161640-ec1a81bfec7c
+ golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c
)
require (
golang.org/x/term v0.22.1-0.20240716160707-d4346f0be292/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.16.1-0.20240716160804-ae0cf96bbcd9 h1:MlCLrwVF1WvXT14xTzwuKN3u4LpUve8sG/gJUCuBpe8=
golang.org/x/text v0.16.1-0.20240716160804-ae0cf96bbcd9/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
-golang.org/x/tools v0.23.1-0.20240722161640-ec1a81bfec7c h1:jGHQpjWnvPa5qmjhxUYeut+TlRYWRGaMcOnAZ4S5IOo=
-golang.org/x/tools v0.23.1-0.20240722161640-ec1a81bfec7c/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
+golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c h1:JImdv91aqIPqamNg5sOTUjNQD++5KkvchZi2BcYlNoE=
+golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c/go.mod h1:IV2Kidsnn7A8K7hHxn/wcUfHXkViw0LLHdu8LnpT8LU=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=
TypesSizes types.Sizes // function for computing sizes of types
TypeErrors []types.Error // type errors (only if Analyzer.RunDespiteErrors)
+ Module *Module // the package's enclosing module (possibly nil in some drivers)
+
// Report reports a Diagnostic, a finding about a specific location
// in the analyzed source code such as a potential mistake.
// It may be called by the Run function.
type Fact interface {
AFact() // dummy method to avoid type errors
}
+
+// A Module describes the module to which a package belongs.
+type Module struct {
+ Path string // module path
+ Version string // module version ("" if unknown, such as for workspace modules)
+ GoVersion string // go version used in module (e.g. "go1.22.0")
+}
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
+ "golang.org/x/tools/internal/versions"
)
const Doc = "check //go:build and // +build directives"
return
}
+ check.tags(pos, x)
+
if check.goBuild == nil {
check.goBuild = x
}
check.crossCheck = false
return
}
+ check.tags(pos, y)
+
if check.plusBuild == nil {
check.plusBuild = y
} else {
return
}
}
+
+// tags reports issues in go versions in tags within the expression e.
+func (check *checker) tags(pos token.Pos, e constraint.Expr) {
+ // Check that constraint.GoVersion is meaningful (>= go1.21).
+ if versions.ConstraintGoVersion == nil {
+ return
+ }
+
+ // Use Eval to visit each tag.
+ _ = e.Eval(func(tag string) bool {
+ if malformedGoTag(tag) {
+ check.pass.Reportf(pos, "invalid go version %q in build constraint", tag)
+ }
+ return false // result is immaterial as Eval does not short-circuit
+ })
+}
+
+// malformedGoTag returns true if a tag is likely to be a malformed
+// go version constraint.
+func malformedGoTag(tag string) bool {
+ // Not a go version?
+ if !strings.HasPrefix(tag, "go1") {
+ // Check for close misspellings of the "go1." prefix.
+ for _, pre := range []string{"go.", "g1.", "go"} {
+ suffix := strings.TrimPrefix(tag, pre)
+ if suffix != tag {
+ if valid, ok := validTag("go1." + suffix); ok && valid {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ // The tag starts with "go1" so it is almost certainly a GoVersion.
+ // Report it if it is not a valid build constraint.
+ valid, ok := validTag(tag)
+ return ok && !valid
+}
+
+// validTag returns (valid, ok) where valid reports when a tag is valid,
+// and ok reports determining if the tag is valid succeeded.
+func validTag(tag string) (valid bool, ok bool) {
+ if versions.ConstraintGoVersion != nil {
+ return versions.ConstraintGoVersion(&constraint.TagExpr{Tag: tag}) != "", true
+ }
+ return false, false
+}
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams"
+ "golang.org/x/tools/internal/versions"
)
const Doc = `check for locks erroneously passed by value
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ var goversion string // effective file version ("" => unknown)
nodeFilter := []ast.Node{
(*ast.AssignStmt)(nil),
(*ast.CallExpr)(nil),
(*ast.CompositeLit)(nil),
+ (*ast.File)(nil),
(*ast.FuncDecl)(nil),
(*ast.FuncLit)(nil),
(*ast.GenDecl)(nil),
(*ast.RangeStmt)(nil),
(*ast.ReturnStmt)(nil),
}
- inspect.Preorder(nodeFilter, func(node ast.Node) {
+ inspect.WithStack(nodeFilter, func(node ast.Node, push bool, stack []ast.Node) bool {
+ if !push {
+ return false
+ }
switch node := node.(type) {
+ case *ast.File:
+ goversion = versions.FileVersion(pass.TypesInfo, node)
case *ast.RangeStmt:
checkCopyLocksRange(pass, node)
case *ast.FuncDecl:
case *ast.CallExpr:
checkCopyLocksCallExpr(pass, node)
case *ast.AssignStmt:
- checkCopyLocksAssign(pass, node)
+ checkCopyLocksAssign(pass, node, goversion, parent(stack))
case *ast.GenDecl:
checkCopyLocksGenDecl(pass, node)
case *ast.CompositeLit:
case *ast.ReturnStmt:
checkCopyLocksReturnStmt(pass, node)
}
+ return true
})
return nil, nil
}
// checkCopyLocksAssign checks whether an assignment
// copies a lock.
-func checkCopyLocksAssign(pass *analysis.Pass, as *ast.AssignStmt) {
- for i, x := range as.Rhs {
+func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion string, parent ast.Node) {
+ lhs := assign.Lhs
+ for i, x := range assign.Rhs {
if path := lockPathRhs(pass, x); path != nil {
- pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, as.Lhs[i]), path)
+ pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, assign.Lhs[i]), path)
+ lhs = nil // An lhs has been reported. We prefer the assignment warning and do not report twice.
+ }
+ }
+
+ // After GoVersion 1.22, loop variables are implicitly copied on each iteration.
+ // So a for statement may inadvertently copy a lock when any of the
+ // iteration variables contain locks.
+ if assign.Tok == token.DEFINE && versions.AtLeast(goversion, versions.Go1_22) {
+ if parent, _ := parent.(*ast.ForStmt); parent != nil && parent.Init == assign {
+ for _, l := range lhs {
+ if id, ok := l.(*ast.Ident); ok && id.Name != "_" {
+ if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil {
+ if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil {
+ pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", analysisutil.Format(pass.Fset, l), path)
+ }
+ }
+ }
+ }
}
}
}
return nil
}
+// parent returns the second from the last node on stack if it exists.
+func parent(stack []ast.Node) ast.Node {
+ if len(stack) >= 2 {
+ return stack[len(stack)-2]
+ }
+ return nil
+}
+
var lockerType *types.Interface
// Construct a sync.Locker interface type.
//
// log.Print("%d", 123) // log.Print call has possible formatting directive %d
//
+// Conversely, it also reports calls to Printf-like functions with a
+// non-constant format string and no other arguments:
+//
+// fmt.Printf(message) // non-constant format string in call to fmt.Printf
+//
+// Such calls may have been intended for the function's Print-like
+// counterpart: if the value of message happens to contain "%",
+// misformatting will occur. In this case, the checker additionally
+// suggests a fix to turn the call into:
+//
+// fmt.Printf("%s", message)
+//
// # Inferred printf wrappers
//
// Functions that delegate their arguments to fmt.Printf are
params := sig.Params()
nparams := params.Len() // variadic => nonzero
+ // Check final parameter is "args ...interface{}".
args := params.At(nparams - 1)
- iface, ok := args.Type().(*types.Slice).Elem().(*types.Interface)
+ iface, ok := aliases.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
if !ok || !iface.Empty() {
- return nil // final (args) param is not ...interface{}
+ return nil
}
// Is second last param 'format string'?
"(testing.TB).Skipf": true,
}
-// formatString returns the format string argument and its index within
-// the given printf-like call expression.
-//
-// The last parameter before variadic arguments is assumed to be
-// a format string.
-//
-// The first string literal or string constant is assumed to be a format string
-// if the call's signature cannot be determined.
-//
-// If it cannot find any format string parameter, it returns ("", -1).
-func formatString(pass *analysis.Pass, call *ast.CallExpr) (format string, idx int) {
+// formatStringIndex returns the index of the format string (the last
+// non-variadic parameter) within the given printf-like call
+// expression, or -1 if unknown.
+func formatStringIndex(pass *analysis.Pass, call *ast.CallExpr) int {
typ := pass.TypesInfo.Types[call.Fun].Type
- if typ != nil {
- if sig, ok := typ.(*types.Signature); ok {
- if !sig.Variadic() {
- // Skip checking non-variadic functions.
- return "", -1
- }
- idx := sig.Params().Len() - 2
- if idx < 0 {
- // Skip checking variadic functions without
- // fixed arguments.
- return "", -1
- }
- s, ok := stringConstantArg(pass, call, idx)
- if !ok {
- // The last argument before variadic args isn't a string.
- return "", -1
- }
- return s, idx
- }
+ if typ == nil {
+ return -1 // missing type
}
-
- // Cannot determine call's signature. Fall back to scanning for the first
- // string constant in the call.
- for idx := range call.Args {
- if s, ok := stringConstantArg(pass, call, idx); ok {
- return s, idx
- }
- if pass.TypesInfo.Types[call.Args[idx]].Type == types.Typ[types.String] {
- // Skip checking a call with a non-constant format
- // string argument, since its contents are unavailable
- // for validation.
- return "", -1
- }
+ sig, ok := typ.(*types.Signature)
+ if !ok {
+ return -1 // ill-typed
}
- return "", -1
-}
-
-// stringConstantArg returns call's string constant argument at the index idx.
-//
-// ("", false) is returned if call's argument at the index idx isn't a string
-// constant.
-func stringConstantArg(pass *analysis.Pass, call *ast.CallExpr, idx int) (string, bool) {
- if idx >= len(call.Args) {
- return "", false
+ if !sig.Variadic() {
+ // Skip checking non-variadic functions.
+ return -1
}
- return stringConstantExpr(pass, call.Args[idx])
+ idx := sig.Params().Len() - 2
+ if idx < 0 {
+ // Skip checking variadic functions without
+ // fixed arguments.
+ return -1
+ }
+ return idx
}
// stringConstantExpr returns expression's string constant value.
// checkPrintf checks a call to a formatted print routine such as Printf.
func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, fn *types.Func) {
- format, idx := formatString(pass, call)
- if idx < 0 {
- if false {
- pass.Reportf(call.Lparen, "can't check non-constant format in call to %s", fn.FullName())
+ idx := formatStringIndex(pass, call)
+ if idx < 0 || idx >= len(call.Args) {
+ return
+ }
+ formatArg := call.Args[idx]
+ format, ok := stringConstantExpr(pass, formatArg)
+ if !ok {
+ // Format string argument is non-constant.
+
+ // It is a common mistake to call fmt.Printf(msg) with a
+ // non-constant format string and no arguments:
+ // if msg contains "%", misformatting occurs.
+ // Report the problem and suggest a fix: fmt.Printf("%s", msg).
+ if idx == len(call.Args)-1 {
+ pass.Report(analysis.Diagnostic{
+ Pos: formatArg.Pos(),
+ End: formatArg.End(),
+ Message: fmt.Sprintf("non-constant format string in call to %s",
+ fn.FullName()),
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: `Insert "%s" format string`,
+ TextEdits: []analysis.TextEdit{{
+ Pos: formatArg.Pos(),
+ End: formatArg.Pos(),
+ NewText: []byte(`"%s", `),
+ }},
+ }},
+ })
}
return
}
import (
_ "embed"
- "fmt"
"go/ast"
"go/token"
"go/types"
i := mismatched[0]
expr := call.Args[i]
t := pass.TypesInfo.Types[expr].Type
- pass.ReportRangef(expr, fmt.Sprintf("mismatched type in call to (*testing.F).Add: %v, fuzz target expects %v", t, params.At(i+1).Type()))
+ pass.ReportRangef(expr, "mismatched type in call to (*testing.F).Add: %v, fuzz target expects %v", t, params.At(i+1).Type())
} else if len(mismatched) > 1 {
var gotArgs, wantArgs []types.Type
for i := 0; i < len(call.Args); i++ {
gotArgs, wantArgs = append(gotArgs, pass.TypesInfo.Types[call.Args[i]].Type), append(wantArgs, params.At(i+1).Type())
}
- pass.ReportRangef(call, fmt.Sprintf("mismatched types in call to (*testing.F).Add: %v, fuzz target expects %v", gotArgs, wantArgs))
+ pass.ReportRangef(call, "mismatched types in call to (*testing.F).Add: %v, fuzz target expects %v", gotArgs, wantArgs)
}
}
return true
}
}
}
- pass.ReportRangef(exprRange, "fuzzing arguments can only have the following types: "+formatAcceptedFuzzType())
+ pass.ReportRangef(exprRange, "fuzzing arguments can only have the following types: %s", formatAcceptedFuzzType())
ok = false
}
}
GoFiles []string
NonGoFiles []string
IgnoredFiles []string
+ ModulePath string // module path
+ ModuleVersion string // module version
ImportMap map[string]string // maps import path to package path
PackageFile map[string]string // maps package path to file of type information
Standard map[string]bool // package belongs to standard library
factFilter[reflect.TypeOf(f)] = true
}
+ module := &analysis.Module{
+ Path: cfg.ModulePath,
+ Version: cfg.ModuleVersion,
+ GoVersion: cfg.GoVersion,
+ }
+
pass := &analysis.Pass{
Analyzer: a,
Fset: fset,
ImportPackageFact: facts.ImportPackageFact,
ExportPackageFact: facts.ExportPackageFact,
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
+ Module: module,
}
pass.ReadFile = analysisinternal.MakeReadFile(pass)
//
// PO package->object Package.Scope.Lookup
// OT object->type Object.Type
-// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying} [EKPRUTrC]
+// TT type->type Type.{Elem,Key,{,{,Recv}Type}Params,Results,Underlying,Rhs} [EKPRUTrCa]
// TO type->object Type.{At,Field,Method,Obj} [AFMO]
//
// All valid paths start with a package and end at an object
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
// - The only OT operator is Object.Type,
// which we encode as '.' because dot cannot appear in an identifier.
-// - The TT operators are encoded as [EKPRUTrC];
+// - The TT operators are encoded as [EKPRUTrCa];
// two of these ({,Recv}TypeParams) require an integer operand,
// which is encoded as a string of decimal digits.
// - The TO operators are encoded as [AFMO];
opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
opRecvTypeParam = 'r' // .RecvTypeParams.At(i) (Signature)
opConstraint = 'C' // .Constraint() (TypeParam)
+ opRhs = 'a' // .Rhs() (Alias)
// type->object operators
opAt = 'A' // .At(i) (Tuple)
path = append(path, opType)
T := o.Type()
+ if alias, ok := T.(*aliases.Alias); ok {
+ if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam, nil); r != nil {
+ return Path(r), nil
+ }
+ if r := find(obj, aliases.Rhs(alias), append(path, opRhs), nil); r != nil {
+ return Path(r), nil
+ }
- if tname.IsAlias() {
- // type alias
+ } else if tname.IsAlias() {
+ // legacy alias
if r := find(obj, T, path, nil); r != nil {
return Path(r), nil
}
- } else {
- if named, _ := T.(*types.Named); named != nil {
- if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam, nil); r != nil {
- // generic named type
- return Path(r), nil
- }
- }
+
+ } else if named, ok := T.(*types.Named); ok {
// defined (named) type
- if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil {
+ if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam, nil); r != nil {
+ return Path(r), nil
+ }
+ if r := find(obj, named.Underlying(), append(path, opUnderlying), nil); r != nil {
return Path(r), nil
}
}
}
t = named.Underlying()
+ case opRhs:
+ if alias, ok := t.(*aliases.Alias); ok {
+ t = aliases.Rhs(alias)
+ } else if false && aliases.Enabled() {
+ // The Enabled check is too expensive, so for now we
+ // simply assume that aliases are not enabled.
+ // TODO(adonovan): replace with "if true {" when go1.24 is assured.
+ return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t)
+ }
+
case opTypeParam:
hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
if !ok {
// GODEBUG=gotypesalias=... by invoking the type checker. The Enabled
// function is expensive and should be called once per task (e.g.
// package import), not once per call to NewAlias.
-func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName {
+//
+// Precondition: enabled || len(tparams)==0.
+// If materialized aliases are disabled, there must not be any type parameters.
+func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName {
if enabled {
tname := types.NewTypeName(pos, pkg, name, nil)
- newAlias(tname, rhs)
+ newAlias(tname, rhs, tparams)
return tname
}
+ if len(tparams) > 0 {
+ panic("cannot create an alias with type parameters when gotypesalias is not enabled")
+ }
return types.NewTypeName(pos, pkg, name, rhs)
}
// It will never be created by go/types.
type Alias struct{}
-func (*Alias) String() string { panic("unreachable") }
-func (*Alias) Underlying() types.Type { panic("unreachable") }
-func (*Alias) Obj() *types.TypeName { panic("unreachable") }
-func Rhs(alias *Alias) types.Type { panic("unreachable") }
+func (*Alias) String() string { panic("unreachable") }
+func (*Alias) Underlying() types.Type { panic("unreachable") }
+func (*Alias) Obj() *types.TypeName { panic("unreachable") }
+func Rhs(alias *Alias) types.Type { panic("unreachable") }
+func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") }
+func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { panic("unreachable") }
+func TypeArgs(alias *Alias) *types.TypeList { panic("unreachable") }
+func Origin(alias *Alias) *Alias { panic("unreachable") }
// Unalias returns the type t for go <=1.21.
func Unalias(t types.Type) types.Type { return t }
-func newAlias(name *types.TypeName, rhs types.Type) *Alias { panic("unreachable") }
+func newAlias(name *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
+ panic("unreachable")
+}
// Enabled reports whether [NewAlias] should create [types.Alias] types.
//
return Unalias(alias)
}
+// TypeParams returns the type parameter list of the alias.
+func TypeParams(alias *Alias) *types.TypeParamList {
+ if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok {
+ return alias.TypeParams() // go1.23+
+ }
+ return nil
+}
+
+// SetTypeParams sets the type parameters of the alias type.
+func SetTypeParams(alias *Alias, tparams []*types.TypeParam) {
+ if alias, ok := any(alias).(interface {
+ SetTypeParams(tparams []*types.TypeParam)
+ }); ok {
+ alias.SetTypeParams(tparams) // go1.23+
+ } else if len(tparams) > 0 {
+ panic("cannot set type parameters of an Alias type in go1.22")
+ }
+}
+
+// TypeArgs returns the type arguments used to instantiate the Alias type.
+func TypeArgs(alias *Alias) *types.TypeList {
+ if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok {
+ return alias.TypeArgs() // go1.23+
+ }
+ return nil // empty (go1.22)
+}
+
+// Origin returns the generic Alias type of which alias is an instance.
+// If alias is not an instance of a generic alias, Origin returns alias.
+func Origin(alias *Alias) *Alias {
+ if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok {
+ return alias.Origin() // go1.23+
+ }
+ return alias // not an instance of a generic alias (go1.22)
+}
+
// Unalias is a wrapper of types.Unalias.
func Unalias(t types.Type) types.Type { return types.Unalias(t) }
// newAlias is an internal alias around types.NewAlias.
// Direct usage is discouraged as the moment.
// Try to use NewAlias instead.
-func newAlias(tname *types.TypeName, rhs types.Type) *Alias {
+func newAlias(tname *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
a := types.NewAlias(tname, rhs)
- // TODO(go.dev/issue/65455): Remove kludgy workaround to set a.actual as a side-effect.
- Unalias(a)
+ SetTypeParams(a, tparams)
return a
}
{"ParseSessionState", Func, 21},
{"QUICClient", Func, 21},
{"QUICConfig", Type, 21},
- {"QUICConfig.EnableStoreSessionEvent", Field, 23},
+ {"QUICConfig.EnableSessionEvents", Field, 23},
{"QUICConfig.TLSConfig", Field, 21},
{"QUICConn", Type, 21},
{"QUICEncryptionLevel", Type, 21},
// InvalidCap occurs when an argument to the cap built-in function is not of
// supported type.
//
- // See https://golang.org/ref/spec#Lengthand_capacity for information on
+ // See https://golang.org/ref/spec#Length_and_capacity for information on
// which underlying types are supported as arguments to cap and len.
//
// Example:
// InvalidCopy occurs when the arguments are not of slice type or do not
// have compatible type.
//
- // See https://golang.org/ref/spec#Appendingand_copying_slices for more
+ // See https://golang.org/ref/spec#Appending_and_copying_slices for more
// information on the type requirements for the copy built-in.
//
// Example:
// InvalidLen occurs when an argument to the len built-in function is not of
// supported type.
//
- // See https://golang.org/ref/spec#Lengthand_capacity for information on
+ // See https://golang.org/ref/spec#Length_and_capacity for information on
// which underlying types are supported as arguments to cap and len.
//
// Example:
// InvalidMake occurs when make is called with an unsupported type argument.
//
- // See https://golang.org/ref/spec#Makingslices_maps_and_channels for
+ // See https://golang.org/ref/spec#Making_slices_maps_and_channels for
// information on the types that may be created using make.
//
// Example:
--- /dev/null
+// Copyright 2024 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.
+
+package versions
+
+import "go/build/constraint"
+
+// ConstraintGoVersion is constraint.GoVersion (if built with go1.21+).
+// Otherwise nil.
+//
+// Deprecate once x/tools is after go1.21.
+var ConstraintGoVersion func(x constraint.Expr) string
--- /dev/null
+// Copyright 2024 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.
+
+//go:build go1.21
+// +build go1.21
+
+package versions
+
+import "go/build/constraint"
+
+func init() {
+ ConstraintGoVersion = constraint.GoVersion
+}
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
-# golang.org/x/tools v0.23.1-0.20240722161640-ec1a81bfec7c
-## explicit; go 1.19
+# golang.org/x/tools v0.24.1-0.20240904143311-70f56264139c
+## explicit; go 1.22.6
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover
golang.org/x/tools/go/analysis