cmd: update vendored x/tools to 904c6ba

This fixes one of the many obstacles to enabling
types.Alias by updating vet.

The 'loopclosure' checker (formerly 'rangeloop') no longer
reports any findings with go1.22, so the test needed work
to ensure that it still runs on files with go1.21 semantics.

Updates #65294

Change-Id: I987ff529d4e165b56b7241563c6003e63bf92bb1
Reviewed-on: https://go-review.googlesource.com/c/go/+/575315
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Alan Donovan 2024-03-29 15:49:38 -04:00 committed by Gopher Robot
parent e565720e49
commit 3b29222ffd
50 changed files with 19862 additions and 279 deletions

View file

@ -31,13 +31,13 @@ Maintaining vendor directories
Before updating vendor directories, ensure that module mode is enabled. Before updating vendor directories, ensure that module mode is enabled.
Make sure that GO111MODULE is not set in the environment, or that it is Make sure that GO111MODULE is not set in the environment, or that it is
set to 'on' or 'auto'. set to 'on' or 'auto', and if you use a go.work file, set GOWORK=off.
Requirements may be added, updated, and removed with 'go get'. Requirements may be added, updated, and removed with 'go get'.
The vendor directory may be updated with 'go mod vendor'. The vendor directory may be updated with 'go mod vendor'.
A typical sequence might be: A typical sequence might be:
cd src cd src # or src/cmd
go get golang.org/x/net@master go get golang.org/x/net@master
go mod tidy go mod tidy
go mod vendor go mod vendor

View file

@ -11,7 +11,7 @@ require (
golang.org/x/sys v0.18.0 golang.org/x/sys v0.18.0
golang.org/x/telemetry v0.0.0-20240401194020-3640ba572dd1 golang.org/x/telemetry v0.0.0-20240401194020-3640ba572dd1
golang.org/x/term v0.18.0 golang.org/x/term v0.18.0
golang.org/x/tools v0.18.0 golang.org/x/tools v0.19.1-0.20240329171618-904c6baa6e14
) )
require ( require (

View file

@ -38,7 +38,7 @@ golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.19.1-0.20240329171618-904c6baa6e14 h1:apiqzCtEqbg/NjzevIbwubwnRo7WZDOgdr8s6ZvIKi0=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.19.1-0.20240329171618-904c6baa6e14/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650 h1:fuOABZYWclLVNotDsHVaFixLdtoC7+UQZJ0KSC1ocm0= rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650 h1:fuOABZYWclLVNotDsHVaFixLdtoC7+UQZJ0KSC1ocm0=
rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=

View file

@ -1,12 +0,0 @@
// Copyright 2022 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.19
// +build !go1.19
package asmdecl
func additionalArches() []*asmArch {
return nil
}

View file

@ -1,14 +0,0 @@
// Copyright 2022 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.19
// +build go1.19
package asmdecl
var asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true}
func additionalArches() []*asmArch {
return []*asmArch{&asmArchLoong64}
}

View file

@ -96,6 +96,7 @@ var (
asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}} asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}}
asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true} asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false} asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true}
arches = []*asmArch{ arches = []*asmArch{
&asmArch386, &asmArch386,
@ -111,11 +112,11 @@ var (
&asmArchRISCV64, &asmArchRISCV64,
&asmArchS390X, &asmArchS390X,
&asmArchWasm, &asmArchWasm,
&asmArchLoong64,
} }
) )
func init() { func init() {
arches = append(arches, additionalArches()...)
for _, arch := range arches { for _, arch := range arches {
arch.sizes = types.SizesFor("gc", arch.name) arch.sizes = types.SizesFor("gc", arch.name)
if arch.sizes == nil { if arch.sizes == nil {

View file

@ -15,6 +15,7 @@ import (
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typeparams"
) )
@ -71,7 +72,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
return return
} }
var structuralTypes []types.Type var structuralTypes []types.Type
switch typ := typ.(type) { switch typ := aliases.Unalias(typ).(type) {
case *types.TypeParam: case *types.TypeParam:
terms, err := typeparams.StructuralTerms(typ) terms, err := typeparams.StructuralTerms(typ)
if err != nil { if err != nil {
@ -83,9 +84,9 @@ func run(pass *analysis.Pass) (interface{}, error) {
default: default:
structuralTypes = append(structuralTypes, typ) structuralTypes = append(structuralTypes, typ)
} }
for _, typ := range structuralTypes { for _, typ := range structuralTypes {
under := deref(typ.Underlying()) strct, ok := typeparams.Deref(typ).Underlying().(*types.Struct)
strct, ok := under.(*types.Struct)
if !ok { if !ok {
// skip non-struct composite literals // skip non-struct composite literals
continue continue
@ -142,29 +143,18 @@ func run(pass *analysis.Pass) (interface{}, error) {
return nil, nil return nil, nil
} }
func deref(typ types.Type) types.Type { // isLocalType reports whether typ belongs to the same package as pass.
for { // TODO(adonovan): local means "internal to a function"; rename to isSamePackageType.
ptr, ok := typ.(*types.Pointer)
if !ok {
break
}
typ = ptr.Elem().Underlying()
}
return typ
}
func isLocalType(pass *analysis.Pass, typ types.Type) bool { func isLocalType(pass *analysis.Pass, typ types.Type) bool {
switch x := typ.(type) { switch x := aliases.Unalias(typ).(type) {
case *types.Struct: case *types.Struct:
// struct literals are local types // struct literals are local types
return true return true
case *types.Pointer: case *types.Pointer:
return isLocalType(pass, x.Elem()) return isLocalType(pass, x.Elem())
case *types.Named: case interface{ Obj() *types.TypeName }: // *Named or *TypeParam (aliases were removed already)
// names in package foo are local to foo_test too // names in package foo are local to foo_test too
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test") return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
case *types.TypeParam:
return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(pass.Pkg.Path(), "_test")
} }
return false return false
} }

View file

@ -18,6 +18,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typeparams"
) )
@ -255,7 +256,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ
} }
seen[typ] = true seen[typ] = true
if tpar, ok := typ.(*types.TypeParam); ok { if tpar, ok := aliases.Unalias(typ).(*types.TypeParam); ok {
terms, err := typeparams.StructuralTerms(tpar) terms, err := typeparams.StructuralTerms(tpar)
if err != nil { if err != nil {
return nil // invalid type return nil // invalid type

View file

@ -14,6 +14,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typesinternal"
) )
const Doc = `check for mistakes using HTTP responses const Doc = `check for mistakes using HTTP responses
@ -116,7 +118,8 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
if res.Len() != 2 { if res.Len() != 2 {
return false // the function called does not return two values. return false // the function called does not return two values.
} }
if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !analysisutil.IsNamedType(ptr.Elem(), "net/http", "Response") { isPtr, named := typesinternal.ReceiverNamed(res.At(0))
if !isPtr || named == nil || !analysisutil.IsNamedType(named, "net/http", "Response") {
return false // the first return type is not *http.Response. return false // the first return type is not *http.Response.
} }
@ -134,7 +137,7 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
if analysisutil.IsNamedType(typ, "net/http", "Client") { if analysisutil.IsNamedType(typ, "net/http", "Client") {
return true // method on http.Client. return true // method on http.Client.
} }
ptr, ok := typ.(*types.Pointer) ptr, ok := aliases.Unalias(typ).(*types.Pointer)
return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
} }

View file

@ -13,6 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/typeparams"
) )
//go:embed doc.go //go:embed doc.go
@ -28,7 +29,7 @@ var Analyzer = &analysis.Analyzer{
// assertableTo checks whether interface v can be asserted into t. It returns // assertableTo checks whether interface v can be asserted into t. It returns
// nil on success, or the first conflicting method on failure. // nil on success, or the first conflicting method on failure.
func assertableTo(v, t types.Type) *types.Func { func assertableTo(free *typeparams.Free, v, t types.Type) *types.Func {
if t == nil || v == nil { if t == nil || v == nil {
// not assertable to, but there is no missing method // not assertable to, but there is no missing method
return nil return nil
@ -42,7 +43,7 @@ func assertableTo(v, t types.Type) *types.Func {
// Mitigations for interface comparisons and generics. // Mitigations for interface comparisons and generics.
// TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion. // TODO(https://github.com/golang/go/issues/50658): Support more precise conclusion.
if isParameterized(V) || isParameterized(T) { if free.Has(V) || free.Has(T) {
return nil return nil
} }
if f, wrongType := types.MissingMethod(V, T, false); wrongType { if f, wrongType := types.MissingMethod(V, T, false); wrongType {
@ -57,6 +58,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
(*ast.TypeAssertExpr)(nil), (*ast.TypeAssertExpr)(nil),
(*ast.TypeSwitchStmt)(nil), (*ast.TypeSwitchStmt)(nil),
} }
var free typeparams.Free
inspect.Preorder(nodeFilter, func(n ast.Node) { inspect.Preorder(nodeFilter, func(n ast.Node) {
var ( var (
assert *ast.TypeAssertExpr // v.(T) expression assert *ast.TypeAssertExpr // v.(T) expression
@ -86,7 +88,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
V := pass.TypesInfo.TypeOf(assert.X) V := pass.TypesInfo.TypeOf(assert.X)
for _, target := range targets { for _, target := range targets {
T := pass.TypesInfo.TypeOf(target) T := pass.TypesInfo.TypeOf(target)
if f := assertableTo(V, T); f != nil { if f := assertableTo(&free, V, T); f != nil {
pass.Reportf( pass.Reportf(
target.Pos(), target.Pos(),
"impossible type assertion: no type can implement both %v and %v (conflicting types for %v method)", "impossible type assertion: no type can implement both %v and %v (conflicting types for %v method)",

View file

@ -14,6 +14,7 @@ import (
"go/types" "go/types"
"os" "os"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/analysisinternal" "golang.org/x/tools/internal/analysisinternal"
) )
@ -115,7 +116,7 @@ func Imports(pkg *types.Package, path string) bool {
// This function avoids allocating the concatenation of "pkg.Name", // This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching. // which is important for the performance of syntax matching.
func IsNamedType(t types.Type, pkgPath string, names ...string) bool { func IsNamedType(t types.Type, pkgPath string, names ...string) bool {
n, ok := t.(*types.Named) n, ok := aliases.Unalias(t).(*types.Named)
if !ok { if !ok {
return false return false
} }

View file

@ -14,6 +14,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions" "golang.org/x/tools/internal/versions"
) )
@ -54,9 +55,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
switch n := n.(type) { switch n := n.(type) {
case *ast.File: case *ast.File:
// Only traverse the file if its goversion is strictly before go1.22. // Only traverse the file if its goversion is strictly before go1.22.
goversion := versions.Lang(versions.FileVersions(pass.TypesInfo, n)) goversion := versions.FileVersion(pass.TypesInfo, n)
// goversion is empty for older go versions (or the version is invalid). return versions.Before(goversion, versions.Go1_22)
return goversion == "" || versions.Compare(goversion, "go1.22") < 0
case *ast.RangeStmt: case *ast.RangeStmt:
body = n.Body body = n.Body
addVar(n.Key) addVar(n.Key)
@ -367,9 +367,6 @@ func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method str
// Check that the receiver is a <pkgPath>.<typeName> or // Check that the receiver is a <pkgPath>.<typeName> or
// *<pkgPath>.<typeName>. // *<pkgPath>.<typeName>.
rtype := recv.Type() _, named := typesinternal.ReceiverNamed(recv)
if ptr, ok := recv.Type().(*types.Pointer); ok { return analysisutil.IsNamedType(named, pkgPath, typeName)
rtype = ptr.Elem()
}
return analysisutil.IsNamedType(rtype, pkgPath, typeName)
} }

View file

@ -24,6 +24,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typeparams"
) )
@ -959,6 +960,8 @@ func isStringer(sig *types.Signature) bool {
// It is almost always a mistake to print a function value. // It is almost always a mistake to print a function value.
func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool { func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
if typ := pass.TypesInfo.Types[e].Type; typ != nil { if typ := pass.TypesInfo.Types[e].Type; typ != nil {
// Don't call Underlying: a named func type with a String method is ok.
// TODO(adonovan): it would be more precise to check isStringer.
_, ok := typ.(*types.Signature) _, ok := typ.(*types.Signature)
return ok return ok
} }
@ -1010,7 +1013,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
// Skip checking functions with unknown type. // Skip checking functions with unknown type.
return return
} }
if sig, ok := typ.(*types.Signature); ok { if sig, ok := typ.Underlying().(*types.Signature); ok {
if !sig.Variadic() { if !sig.Variadic() {
// Skip checking non-variadic functions. // Skip checking non-variadic functions.
return return
@ -1020,7 +1023,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
typ := params.At(firstArg).Type() typ := params.At(firstArg).Type()
typ = typ.(*types.Slice).Elem() typ = typ.(*types.Slice).Elem()
it, ok := typ.(*types.Interface) it, ok := aliases.Unalias(typ).(*types.Interface)
if !ok || !it.Empty() { if !ok || !it.Empty() {
// Skip variadic functions accepting non-interface{} args. // Skip variadic functions accepting non-interface{} args.
return return

View file

@ -10,6 +10,7 @@ import (
"go/types" "go/types"
"golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typeparams"
) )
@ -72,7 +73,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
return true return true
} }
if typ, _ := typ.(*types.TypeParam); typ != nil { if typ, _ := aliases.Unalias(typ).(*types.TypeParam); typ != nil {
// Avoid infinite recursion through type parameters. // Avoid infinite recursion through type parameters.
if m.seen[typ] { if m.seen[typ] {
return true return true
@ -275,7 +276,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
} }
func isConvertibleToString(typ types.Type) bool { func isConvertibleToString(typ types.Type) bool {
if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil { if bt, ok := aliases.Unalias(typ).(*types.Basic); ok && bt.Kind() == types.UntypedNil {
// We explicitly don't want untyped nil, which is // We explicitly don't want untyped nil, which is
// convertible to both of the interfaces below, as it // convertible to both of the interfaces below, as it
// would just panic anyway. // would just panic anyway.

View file

@ -21,6 +21,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typeparams"
) )
@ -89,7 +90,8 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
if v == nil { if v == nil {
return return
} }
amt, ok := constant.Int64Val(v) u := constant.ToInt(v) // either an Int or Unknown
amt, ok := constant.Int64Val(u)
if !ok { if !ok {
return return
} }
@ -98,7 +100,7 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
return return
} }
var structuralTypes []types.Type var structuralTypes []types.Type
switch t := t.(type) { switch t := aliases.Unalias(t).(type) {
case *types.TypeParam: case *types.TypeParam:
terms, err := typeparams.StructuralTerms(t) terms, err := typeparams.StructuralTerms(t)
if err != nil { if err != nil {

View file

@ -20,6 +20,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
) )
//go:embed doc.go //go:embed doc.go
@ -48,6 +49,7 @@ const (
) )
func run(pass *analysis.Pass) (any, error) { func run(pass *analysis.Pass) (any, error) {
var attrType types.Type // The type of slog.Attr
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{ nodeFilter := []ast.Node{
(*ast.CallExpr)(nil), (*ast.CallExpr)(nil),
@ -66,6 +68,11 @@ func run(pass *analysis.Pass) (any, error) {
// Not a slog function that takes key-value pairs. // Not a slog function that takes key-value pairs.
return return
} }
// Here we know that fn.Pkg() is "log/slog".
if attrType == nil {
attrType = fn.Pkg().Scope().Lookup("Attr").Type()
}
if isMethodExpr(pass.TypesInfo, call) { if isMethodExpr(pass.TypesInfo, call) {
// Call is to a method value. Skip the first argument. // Call is to a method value. Skip the first argument.
skipArgs++ skipArgs++
@ -91,8 +98,19 @@ func run(pass *analysis.Pass) (any, error) {
pos = key pos = key
case types.IsInterface(t): case types.IsInterface(t):
// As we do not do dataflow, we do not know what the dynamic type is. // As we do not do dataflow, we do not know what the dynamic type is.
// It could be a string or an Attr so we don't know what to expect next. // But we might be able to learn enough to make a decision.
pos = unknown if types.AssignableTo(stringType, t) {
// t must be an empty interface. So it can also be an Attr.
// We don't know enough to make an assumption.
pos = unknown
continue
} else if attrType != nil && types.AssignableTo(attrType, t) {
// Assume it is an Attr.
pos = key
continue
}
// Can't be either a string or Attr. Definitely an error.
fallthrough
default: default:
if unknownArg == nil { if unknownArg == nil {
pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)", pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)",
@ -150,14 +168,10 @@ func isAttr(t types.Type) bool {
func shortName(fn *types.Func) string { func shortName(fn *types.Func) string {
var r string var r string
if recv := fn.Type().(*types.Signature).Recv(); recv != nil { if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
t := recv.Type() if _, named := typesinternal.ReceiverNamed(recv); named != nil {
if pt, ok := t.(*types.Pointer); ok { r = named.Obj().Name()
t = pt.Elem()
}
if nt, ok := t.(*types.Named); ok {
r = nt.Obj().Name()
} else { } else {
r = recv.Type().String() r = recv.Type().String() // anon struct/interface
} }
r += "." r += "."
} }
@ -173,17 +187,12 @@ func kvFuncSkipArgs(fn *types.Func) (int, bool) {
return 0, false return 0, false
} }
var recvName string // by default a slog package function var recvName string // by default a slog package function
recv := fn.Type().(*types.Signature).Recv() if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
if recv != nil { _, named := typesinternal.ReceiverNamed(recv)
t := recv.Type() if named == nil {
if pt, ok := t.(*types.Pointer); ok { return 0, false // anon struct/interface
t = pt.Elem()
}
if nt, ok := t.(*types.Named); !ok {
return 0, false
} else {
recvName = nt.Obj().Name()
} }
recvName = named.Obj().Name()
} }
skip, ok := kvFuncs[recvName][fn.Name()] skip, ok := kvFuncs[recvName][fn.Name()]
return skip, ok return skip, ok

View file

@ -60,10 +60,12 @@ func describe(typ, inType types.Type, inName string) string {
} }
func typeName(typ types.Type) string { func typeName(typ types.Type) string {
if v, _ := typ.(interface{ Name() string }); v != nil { typ = aliases.Unalias(typ)
// TODO(adonovan): don't discard alias type, return its name.
if v, _ := typ.(*types.Basic); v != nil {
return v.Name() return v.Name()
} }
if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil { if v, _ := typ.(interface{ Obj() *types.TypeName }); v != nil { // Named, TypeParam
return v.Obj().Name() return v.Obj().Name()
} }
return "" return ""

View file

@ -17,6 +17,7 @@ import (
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/aliases"
) )
//go:embed doc.go //go:embed doc.go
@ -270,7 +271,7 @@ func forbiddenMethod(info *types.Info, call *ast.CallExpr) (*types.Var, *types.S
func formatMethod(sel *types.Selection, fn *types.Func) string { func formatMethod(sel *types.Selection, fn *types.Func) string {
var ptr string var ptr string
rtype := sel.Recv() rtype := sel.Recv()
if p, ok := rtype.(*types.Pointer); ok { if p, ok := aliases.Unalias(rtype).(*types.Pointer); ok {
ptr = "*" ptr = "*"
rtype = p.Elem() rtype = p.Elem()
} }

View file

@ -30,7 +30,7 @@ func localFunctionDecls(info *types.Info, files []*ast.File) func(*types.Func) *
} }
} }
} }
// TODO: once we only support go1.19+, set f = f.Origin() here. // TODO: set f = f.Origin() here.
return fnDecls[f] return fnDecls[f]
} }
} }

View file

@ -252,6 +252,8 @@ func validateFuzzArgs(pass *analysis.Pass, params *types.Tuple, expr ast.Expr) b
} }
func isTestingType(typ types.Type, testingType string) bool { func isTestingType(typ types.Type, testingType string) bool {
// No Unalias here: I doubt "go test" recognizes
// "type A = *testing.T; func Test(A) {}" as a test.
ptr, ok := typ.(*types.Pointer) ptr, ok := typ.(*types.Pointer)
if !ok { if !ok {
return false return false

View file

@ -107,7 +107,7 @@ func badFormatAt(info *types.Info, e ast.Expr) int {
return -1 return -1
} }
t, ok := tv.Type.(*types.Basic) t, ok := tv.Type.(*types.Basic) // sic, no unalias
if !ok || t.Info()&types.IsString == 0 { if !ok || t.Info()&types.IsString == 0 {
return -1 return -1
} }

View file

@ -14,6 +14,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typesinternal"
) )
//go:embed doc.go //go:embed doc.go
@ -69,12 +70,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
// (*"encoding/json".Decoder).Decode // (*"encoding/json".Decoder).Decode
// (* "encoding/gob".Decoder).Decode // (* "encoding/gob".Decoder).Decode
// (* "encoding/xml".Decoder).Decode // (* "encoding/xml".Decoder).Decode
t := recv.Type() _, named := typesinternal.ReceiverNamed(recv)
if ptr, ok := t.(*types.Pointer); ok { if tname := named.Obj(); tname.Name() == "Decoder" {
t = ptr.Elem()
}
tname := t.(*types.Named).Obj()
if tname.Name() == "Decoder" {
switch tname.Pkg().Path() { switch tname.Pkg().Path() {
case "encoding/json", "encoding/xml", "encoding/gob": case "encoding/json", "encoding/xml", "encoding/gob":
argidx = 0 // func(interface{}) argidx = 0 // func(interface{})

View file

@ -17,6 +17,7 @@ import (
"golang.org/x/tools/go/analysis/passes/internal/analysisutil" "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector" "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
) )
//go:embed doc.go //go:embed doc.go
@ -88,7 +89,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool {
// by the time we get to the conversion at the end. // by the time we get to the conversion at the end.
// For now approximate by saying that *Header is okay // For now approximate by saying that *Header is okay
// but Header is not. // but Header is not.
pt, ok := info.Types[x.X].Type.(*types.Pointer) pt, ok := aliases.Unalias(info.Types[x.X].Type).(*types.Pointer)
if ok && isReflectHeader(pt.Elem()) { if ok && isReflectHeader(pt.Elem()) {
return true return true
} }

View file

@ -16,8 +16,8 @@ type builder struct {
cfg *CFG cfg *CFG
mayReturn func(*ast.CallExpr) bool mayReturn func(*ast.CallExpr) bool
current *Block current *Block
lblocks map[*ast.Object]*lblock // labeled blocks lblocks map[string]*lblock // labeled blocks
targets *targets // linked stack of branch targets targets *targets // linked stack of branch targets
} }
func (b *builder) stmt(_s ast.Stmt) { func (b *builder) stmt(_s ast.Stmt) {
@ -42,7 +42,7 @@ start:
b.add(s) b.add(s)
if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) { if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) {
// Calls to panic, os.Exit, etc, never return. // Calls to panic, os.Exit, etc, never return.
b.current = b.newBlock("unreachable.call") b.current = b.newBlock(KindUnreachable, s)
} }
case *ast.DeclStmt: case *ast.DeclStmt:
@ -57,7 +57,7 @@ start:
} }
case *ast.LabeledStmt: case *ast.LabeledStmt:
label = b.labeledBlock(s.Label) label = b.labeledBlock(s.Label, s)
b.jump(label._goto) b.jump(label._goto)
b.current = label._goto b.current = label._goto
_s = s.Stmt _s = s.Stmt
@ -65,7 +65,7 @@ start:
case *ast.ReturnStmt: case *ast.ReturnStmt:
b.add(s) b.add(s)
b.current = b.newBlock("unreachable.return") b.current = b.newBlock(KindUnreachable, s)
case *ast.BranchStmt: case *ast.BranchStmt:
b.branchStmt(s) b.branchStmt(s)
@ -77,11 +77,11 @@ start:
if s.Init != nil { if s.Init != nil {
b.stmt(s.Init) b.stmt(s.Init)
} }
then := b.newBlock("if.then") then := b.newBlock(KindIfThen, s)
done := b.newBlock("if.done") done := b.newBlock(KindIfDone, s)
_else := done _else := done
if s.Else != nil { if s.Else != nil {
_else = b.newBlock("if.else") _else = b.newBlock(KindIfElse, s)
} }
b.add(s.Cond) b.add(s.Cond)
b.ifelse(then, _else) b.ifelse(then, _else)
@ -128,7 +128,7 @@ func (b *builder) branchStmt(s *ast.BranchStmt) {
switch s.Tok { switch s.Tok {
case token.BREAK: case token.BREAK:
if s.Label != nil { if s.Label != nil {
if lb := b.labeledBlock(s.Label); lb != nil { if lb := b.labeledBlock(s.Label, nil); lb != nil {
block = lb._break block = lb._break
} }
} else { } else {
@ -139,7 +139,7 @@ func (b *builder) branchStmt(s *ast.BranchStmt) {
case token.CONTINUE: case token.CONTINUE:
if s.Label != nil { if s.Label != nil {
if lb := b.labeledBlock(s.Label); lb != nil { if lb := b.labeledBlock(s.Label, nil); lb != nil {
block = lb._continue block = lb._continue
} }
} else { } else {
@ -155,14 +155,14 @@ func (b *builder) branchStmt(s *ast.BranchStmt) {
case token.GOTO: case token.GOTO:
if s.Label != nil { if s.Label != nil {
block = b.labeledBlock(s.Label)._goto block = b.labeledBlock(s.Label, nil)._goto
} }
} }
if block == nil { if block == nil { // ill-typed (e.g. undefined label)
block = b.newBlock("undefined.branch") block = b.newBlock(KindUnreachable, s)
} }
b.jump(block) b.jump(block)
b.current = b.newBlock("unreachable.branch") b.current = b.newBlock(KindUnreachable, s)
} }
func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
@ -172,7 +172,7 @@ func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
if s.Tag != nil { if s.Tag != nil {
b.add(s.Tag) b.add(s.Tag)
} }
done := b.newBlock("switch.done") done := b.newBlock(KindSwitchDone, s)
if label != nil { if label != nil {
label._break = done label._break = done
} }
@ -188,13 +188,13 @@ func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
for i, clause := range s.Body.List { for i, clause := range s.Body.List {
body := fallthru body := fallthru
if body == nil { if body == nil {
body = b.newBlock("switch.body") // first case only body = b.newBlock(KindSwitchCaseBody, clause) // first case only
} }
// Preallocate body block for the next case. // Preallocate body block for the next case.
fallthru = done fallthru = done
if i+1 < ncases { if i+1 < ncases {
fallthru = b.newBlock("switch.body") fallthru = b.newBlock(KindSwitchCaseBody, s.Body.List[i+1])
} }
cc := clause.(*ast.CaseClause) cc := clause.(*ast.CaseClause)
@ -208,7 +208,7 @@ func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) {
var nextCond *Block var nextCond *Block
for _, cond := range cc.List { for _, cond := range cc.List {
nextCond = b.newBlock("switch.next") nextCond = b.newBlock(KindSwitchNextCase, cc)
b.add(cond) // one half of the tag==cond condition b.add(cond) // one half of the tag==cond condition
b.ifelse(body, nextCond) b.ifelse(body, nextCond)
b.current = nextCond b.current = nextCond
@ -247,7 +247,7 @@ func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) {
b.add(s.Assign) b.add(s.Assign)
} }
done := b.newBlock("typeswitch.done") done := b.newBlock(KindSwitchDone, s)
if label != nil { if label != nil {
label._break = done label._break = done
} }
@ -258,10 +258,10 @@ func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) {
default_ = cc default_ = cc
continue continue
} }
body := b.newBlock("typeswitch.body") body := b.newBlock(KindSwitchCaseBody, cc)
var next *Block var next *Block
for _, casetype := range cc.List { for _, casetype := range cc.List {
next = b.newBlock("typeswitch.next") next = b.newBlock(KindSwitchNextCase, cc)
// casetype is a type, so don't call b.add(casetype). // casetype is a type, so don't call b.add(casetype).
// This block logically contains a type assertion, // This block logically contains a type assertion,
// x.(casetype), but it's unclear how to represent x. // x.(casetype), but it's unclear how to represent x.
@ -300,7 +300,7 @@ func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) {
} }
} }
done := b.newBlock("select.done") done := b.newBlock(KindSelectDone, s)
if label != nil { if label != nil {
label._break = done label._break = done
} }
@ -312,8 +312,8 @@ func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) {
defaultBody = &clause.Body defaultBody = &clause.Body
continue continue
} }
body := b.newBlock("select.body") body := b.newBlock(KindSelectCaseBody, clause)
next := b.newBlock("select.next") next := b.newBlock(KindSelectAfterCase, clause)
b.ifelse(body, next) b.ifelse(body, next)
b.current = body b.current = body
b.targets = &targets{ b.targets = &targets{
@ -358,15 +358,15 @@ func (b *builder) forStmt(s *ast.ForStmt, label *lblock) {
if s.Init != nil { if s.Init != nil {
b.stmt(s.Init) b.stmt(s.Init)
} }
body := b.newBlock("for.body") body := b.newBlock(KindForBody, s)
done := b.newBlock("for.done") // target of 'break' done := b.newBlock(KindForDone, s) // target of 'break'
loop := body // target of back-edge loop := body // target of back-edge
if s.Cond != nil { if s.Cond != nil {
loop = b.newBlock("for.loop") loop = b.newBlock(KindForLoop, s)
} }
cont := loop // target of 'continue' cont := loop // target of 'continue'
if s.Post != nil { if s.Post != nil {
cont = b.newBlock("for.post") cont = b.newBlock(KindForPost, s)
} }
if label != nil { if label != nil {
label._break = done label._break = done
@ -414,12 +414,12 @@ func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) {
// jump loop // jump loop
// done: (target of break) // done: (target of break)
loop := b.newBlock("range.loop") loop := b.newBlock(KindRangeLoop, s)
b.jump(loop) b.jump(loop)
b.current = loop b.current = loop
body := b.newBlock("range.body") body := b.newBlock(KindRangeBody, s)
done := b.newBlock("range.done") done := b.newBlock(KindRangeDone, s)
b.ifelse(body, done) b.ifelse(body, done)
b.current = body b.current = body
@ -461,14 +461,19 @@ type lblock struct {
// labeledBlock returns the branch target associated with the // labeledBlock returns the branch target associated with the
// specified label, creating it if needed. // specified label, creating it if needed.
func (b *builder) labeledBlock(label *ast.Ident) *lblock { func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock {
lb := b.lblocks[label.Obj] lb := b.lblocks[label.Name]
if lb == nil { if lb == nil {
lb = &lblock{_goto: b.newBlock(label.Name)} lb = &lblock{_goto: b.newBlock(KindLabel, nil)}
if b.lblocks == nil { if b.lblocks == nil {
b.lblocks = make(map[*ast.Object]*lblock) b.lblocks = make(map[string]*lblock)
} }
b.lblocks[label.Obj] = lb b.lblocks[label.Name] = lb
}
// Fill in the label later (in case of forward goto).
// Stmt may be set already if labels are duplicated (ill-typed).
if stmt != nil && lb._goto.Stmt == nil {
lb._goto.Stmt = stmt
} }
return lb return lb
} }
@ -477,11 +482,12 @@ func (b *builder) labeledBlock(label *ast.Ident) *lblock {
// slice and returns it. // slice and returns it.
// It does not automatically become the current block. // It does not automatically become the current block.
// comment is an optional string for more readable debugging output. // comment is an optional string for more readable debugging output.
func (b *builder) newBlock(comment string) *Block { func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block {
g := b.cfg g := b.cfg
block := &Block{ block := &Block{
Index: int32(len(g.Blocks)), Index: int32(len(g.Blocks)),
comment: comment, Kind: kind,
Stmt: stmt,
} }
block.Succs = block.succs2[:0] block.Succs = block.succs2[:0]
g.Blocks = append(g.Blocks, block) g.Blocks = append(g.Blocks, block)

View file

@ -9,7 +9,10 @@
// //
// The blocks of the CFG contain all the function's non-control // The blocks of the CFG contain all the function's non-control
// statements. The CFG does not contain control statements such as If, // statements. The CFG does not contain control statements such as If,
// Switch, Select, and Branch, but does contain their subexpressions. // Switch, Select, and Branch, but does contain their subexpressions;
// also, each block records the control statement (Block.Stmt) that
// gave rise to it and its relationship (Block.Kind) to that statement.
//
// For example, this source code: // For example, this source code:
// //
// if x := f(); x != nil { // if x := f(); x != nil {
@ -20,14 +23,14 @@
// //
// produces this CFG: // produces this CFG:
// //
// 1: x := f() // 1: x := f() Body
// x != nil // x != nil
// succs: 2, 3 // succs: 2, 3
// 2: T() // 2: T() IfThen
// succs: 4 // succs: 4
// 3: F() // 3: F() IfElse
// succs: 4 // succs: 4
// 4: // 4: IfDone
// //
// The CFG does contain Return statements; even implicit returns are // The CFG does contain Return statements; even implicit returns are
// materialized (at the position of the function's closing brace). // materialized (at the position of the function's closing brace).
@ -50,6 +53,7 @@ import (
// //
// The entry point is Blocks[0]; there may be multiple return blocks. // The entry point is Blocks[0]; there may be multiple return blocks.
type CFG struct { type CFG struct {
fset *token.FileSet
Blocks []*Block // block[0] is entry; order otherwise undefined Blocks []*Block // block[0] is entry; order otherwise undefined
} }
@ -64,9 +68,63 @@ type Block struct {
Succs []*Block // successor nodes in the graph Succs []*Block // successor nodes in the graph
Index int32 // index within CFG.Blocks Index int32 // index within CFG.Blocks
Live bool // block is reachable from entry Live bool // block is reachable from entry
Kind BlockKind // block kind
Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
comment string // for debugging succs2 [2]*Block // underlying array for Succs
succs2 [2]*Block // underlying array for Succs }
// A BlockKind identifies the purpose of a block.
// It also determines the possible types of its Stmt field.
type BlockKind uint8
const (
KindInvalid BlockKind = iota // Stmt=nil
KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt
KindBody // function body BlockStmt
KindForBody // body of ForStmt
KindForDone // block after ForStmt
KindForLoop // head of ForStmt
KindForPost // post condition of ForStmt
KindIfDone // block after IfStmt
KindIfElse // else block of IfStmt
KindIfThen // then block of IfStmt
KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label)
KindRangeBody // body of RangeStmt
KindRangeDone // block after RangeStmt
KindRangeLoop // head of RangeStmt
KindSelectCaseBody // body of SelectStmt
KindSelectDone // block after SelectStmt
KindSelectAfterCase // block after a CommClause
KindSwitchCaseBody // body of CaseClause
KindSwitchDone // block after {Type.}SwitchStmt
KindSwitchNextCase // secondary expression of a multi-expression CaseClause
)
func (kind BlockKind) String() string {
return [...]string{
KindInvalid: "Invalid",
KindUnreachable: "Unreachable",
KindBody: "Body",
KindForBody: "ForBody",
KindForDone: "ForDone",
KindForLoop: "ForLoop",
KindForPost: "ForPost",
KindIfDone: "IfDone",
KindIfElse: "IfElse",
KindIfThen: "IfThen",
KindLabel: "Label",
KindRangeBody: "RangeBody",
KindRangeDone: "RangeDone",
KindRangeLoop: "RangeLoop",
KindSelectCaseBody: "SelectCaseBody",
KindSelectDone: "SelectDone",
KindSelectAfterCase: "SelectAfterCase",
KindSwitchCaseBody: "SwitchCaseBody",
KindSwitchDone: "SwitchDone",
KindSwitchNextCase: "SwitchNextCase",
}[kind]
} }
// New returns a new control-flow graph for the specified function body, // New returns a new control-flow graph for the specified function body,
@ -82,7 +140,7 @@ func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
mayReturn: mayReturn, mayReturn: mayReturn,
cfg: new(CFG), cfg: new(CFG),
} }
b.current = b.newBlock("entry") b.current = b.newBlock(KindBody, body)
b.stmt(body) b.stmt(body)
// Compute liveness (reachability from entry point), breadth-first. // Compute liveness (reachability from entry point), breadth-first.
@ -110,7 +168,15 @@ func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
} }
func (b *Block) String() string { func (b *Block) String() string {
return fmt.Sprintf("block %d (%s)", b.Index, b.comment) return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil))
}
func (b *Block) comment(fset *token.FileSet) string {
s := b.Kind.String()
if fset != nil && b.Stmt != nil {
s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line)
}
return s
} }
// Return returns the return statement at the end of this block if present, nil // Return returns the return statement at the end of this block if present, nil
@ -129,7 +195,7 @@ func (b *Block) Return() (ret *ast.ReturnStmt) {
func (g *CFG) Format(fset *token.FileSet) string { func (g *CFG) Format(fset *token.FileSet) string {
var buf bytes.Buffer var buf bytes.Buffer
for _, b := range g.Blocks { for _, b := range g.Blocks {
fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment) fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset))
for _, n := range b.Nodes { for _, n := range b.Nodes {
fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n))
} }
@ -145,6 +211,34 @@ func (g *CFG) Format(fset *token.FileSet) string {
return buf.String() return buf.String()
} }
// Dot returns the control-flow graph in the [Dot graph description language].
// Use a command such as 'dot -Tsvg' to render it in a form viewable in a browser.
// This method is provided as a debugging aid; the details of the
// output are unspecified and may change.
//
// [Dot graph description language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language)
func (g *CFG) Dot(fset *token.FileSet) string {
var buf bytes.Buffer
buf.WriteString("digraph CFG {\n")
buf.WriteString(" node [shape=box];\n")
for _, b := range g.Blocks {
// node label
var text bytes.Buffer
text.WriteString(b.comment(fset))
for _, n := range b.Nodes {
fmt.Fprintf(&text, "\n%s", formatNode(fset, n))
}
// node and edges
fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text)
for _, succ := range b.Succs {
fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index)
}
}
buf.WriteString("}\n")
return buf.String()
}
func formatNode(fset *token.FileSet, n ast.Node) string { func formatNode(fset *token.FileSet, n ast.Node) string {
var buf bytes.Buffer var buf bytes.Buffer
format.Node(&buf, fset, n) format.Node(&buf, fset, n)

View file

@ -29,9 +29,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/typesinternal"
) )
// TODO(adonovan): think about generic aliases.
// A Path is an opaque name that identifies a types.Object // A Path is an opaque name that identifies a types.Object
// relative to its package. Conceptually, the name consists of a // relative to its package. Conceptually, the name consists of a
// sequence of destructuring operations applied to the package scope // sequence of destructuring operations applied to the package scope
@ -223,7 +226,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
// Reject obviously non-viable cases. // Reject obviously non-viable cases.
switch obj := obj.(type) { switch obj := obj.(type) {
case *types.TypeName: case *types.TypeName:
if _, ok := obj.Type().(*types.TypeParam); !ok { if _, ok := aliases.Unalias(obj.Type()).(*types.TypeParam); !ok {
// With the exception of type parameters, only package-level type names // With the exception of type parameters, only package-level type names
// have a path. // have a path.
return "", fmt.Errorf("no path for %v", obj) return "", fmt.Errorf("no path for %v", obj)
@ -310,7 +313,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
} }
// Inspect declared methods of defined types. // Inspect declared methods of defined types.
if T, ok := o.Type().(*types.Named); ok { if T, ok := aliases.Unalias(o.Type()).(*types.Named); ok {
path = append(path, opType) path = append(path, opType)
// The method index here is always with respect // The method index here is always with respect
// to the underlying go/types data structures, // to the underlying go/types data structures,
@ -391,17 +394,12 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
// of objectpath will only be giving us origin methods, anyway, as referring // of objectpath will only be giving us origin methods, anyway, as referring
// to instantiated methods is usually not useful. // to instantiated methods is usually not useful.
if typeparams.OriginMethod(meth) != meth { if meth.Origin() != meth {
return "", false return "", false
} }
recvT := meth.Type().(*types.Signature).Recv().Type() _, named := typesinternal.ReceiverNamed(meth.Type().(*types.Signature).Recv())
if ptr, ok := recvT.(*types.Pointer); ok { if named == nil {
recvT = ptr.Elem()
}
named, ok := recvT.(*types.Named)
if !ok {
return "", false return "", false
} }
@ -444,6 +442,8 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
// nil, it will be allocated as necessary. // nil, it will be allocated as necessary.
func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte { func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte {
switch T := T.(type) { switch T := T.(type) {
case *aliases.Alias:
return find(obj, aliases.Unalias(T), path, seen)
case *types.Basic, *types.Named: case *types.Basic, *types.Named:
// Named types belonging to pkg were handled already, // Named types belonging to pkg were handled already,
// so T must belong to another package. No path. // so T must belong to another package. No path.
@ -616,6 +616,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
// Inv: t != nil, obj == nil // Inv: t != nil, obj == nil
t = aliases.Unalias(t)
switch code { switch code {
case opElem: case opElem:
hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map

View file

@ -6,7 +6,11 @@ package typeutil
// This file defines utilities for user interfaces that display types. // This file defines utilities for user interfaces that display types.
import "go/types" import (
"go/types"
"golang.org/x/tools/internal/aliases"
)
// IntuitiveMethodSet returns the intuitive method set of a type T, // IntuitiveMethodSet returns the intuitive method set of a type T,
// which is the set of methods you can call on an addressable value of // which is the set of methods you can call on an addressable value of
@ -24,7 +28,7 @@ import "go/types"
// The order of the result is as for types.MethodSet(T). // The order of the result is as for types.MethodSet(T).
func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection { func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection {
isPointerToConcrete := func(T types.Type) bool { isPointerToConcrete := func(T types.Type) bool {
ptr, ok := T.(*types.Pointer) ptr, ok := aliases.Unalias(T).(*types.Pointer)
return ok && !types.IsInterface(ptr.Elem()) return ok && !types.IsInterface(ptr.Elem())
} }

View file

@ -13,6 +13,8 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"strconv" "strconv"
"golang.org/x/tools/internal/aliases"
) )
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
@ -28,7 +30,10 @@ func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos
} }
func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { func ZeroValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
under := typ // TODO(adonovan): think about generics, and also generic aliases.
under := aliases.Unalias(typ)
// Don't call Underlying unconditionally: although it removed
// Named and Alias, it also removes TypeParam.
if n, ok := typ.(*types.Named); ok { if n, ok := typ.(*types.Named); ok {
under = n.Underlying() under = n.Underlying()
} }

View file

@ -6,6 +6,8 @@ package facts
import ( import (
"go/types" "go/types"
"golang.org/x/tools/internal/aliases"
) )
// importMap computes the import map for a package by traversing the // importMap computes the import map for a package by traversing the
@ -45,6 +47,8 @@ func importMap(imports []*types.Package) map[string]*types.Package {
addType = func(T types.Type) { addType = func(T types.Type) {
switch T := T.(type) { switch T := T.(type) {
case *aliases.Alias:
addType(aliases.Unalias(T))
case *types.Basic: case *types.Basic:
// nop // nop
case *types.Named: case *types.Named:

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,97 @@
// Copyright 2022 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:generate go run generate.go
// Package stdlib provides a table of all exported symbols in the
// standard library, along with the version at which they first
// appeared.
package stdlib
import (
"fmt"
"strings"
)
type Symbol struct {
Name string
Kind Kind
Version Version // Go version that first included the symbol
}
// A Kind indicates the kind of a symbol:
// function, variable, constant, type, and so on.
type Kind int8
const (
Invalid Kind = iota // Example name:
Type // "Buffer"
Func // "Println"
Var // "EOF"
Const // "Pi"
Field // "Point.X"
Method // "(*Buffer).Grow"
)
func (kind Kind) String() string {
return [...]string{
Invalid: "invalid",
Type: "type",
Func: "func",
Var: "var",
Const: "const",
Field: "field",
Method: "method",
}[kind]
}
// A Version represents a version of Go of the form "go1.%d".
type Version int8
// String returns a version string of the form "go1.23", without allocating.
func (v Version) String() string { return versions[v] }
var versions [30]string // (increase constant as needed)
func init() {
for i := range versions {
versions[i] = fmt.Sprintf("go1.%d", i)
}
}
// HasPackage reports whether the specified package path is part of
// the standard library's public API.
func HasPackage(path string) bool {
_, ok := PackageSymbols[path]
return ok
}
// SplitField splits the field symbol name into type and field
// components. It must be called only on Field symbols.
//
// Example: "File.Package" -> ("File", "Package")
func (sym *Symbol) SplitField() (typename, name string) {
if sym.Kind != Field {
panic("not a field")
}
typename, name, _ = strings.Cut(sym.Name, ".")
return
}
// SplitMethod splits the method symbol name into pointer, receiver,
// and method components. It must be called only on Method symbols.
//
// Example: "(*Buffer).Grow" -> (true, "Buffer", "Grow")
func (sym *Symbol) SplitMethod() (ptr bool, recv, name string) {
if sym.Kind != Method {
panic("not a method")
}
recv, name, _ = strings.Cut(sym.Name, ".")
recv = recv[len("(") : len(recv)-len(")")]
ptr = recv[0] == '*'
if ptr {
recv = recv[len("*"):]
}
return
}

View file

@ -2,20 +2,10 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package typeparams contains common utilities for writing tools that interact // Package typeparams contains common utilities for writing tools that
// with generic Go code, as introduced with Go 1.18. // interact with generic Go code, as introduced with Go 1.18. It
// // supplements the standard library APIs. Notably, the StructuralTerms
// Many of the types and functions in this package are proxies for the new APIs // API computes a minimal representation of the structural
// introduced in the standard library with Go 1.18. For example, the
// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
// versions older than 1.18 these helpers are implemented as stubs, allowing
// users of this package to write code that handles generic constructs inline,
// even if the Go version being used to compile does not support generics.
//
// Additionally, this package contains common utilities for working with the
// new generic constructs, to supplement the standard library APIs. Notably,
// the StructuralTerms API computes a minimal representation of the structural
// restrictions on a type parameter. // restrictions on a type parameter.
// //
// An external version of these APIs is available in the // An external version of these APIs is available in the
@ -23,10 +13,11 @@
package typeparams package typeparams
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"go/types" "go/types"
"golang.org/x/tools/internal/aliases"
) )
// UnpackIndexExpr extracts data from AST nodes that represent index // UnpackIndexExpr extracts data from AST nodes that represent index
@ -72,68 +63,12 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke
} }
} }
// IsTypeParam reports whether t is a type parameter. // IsTypeParam reports whether t is a type parameter (or an alias of one).
func IsTypeParam(t types.Type) bool { func IsTypeParam(t types.Type) bool {
_, ok := t.(*types.TypeParam) _, ok := aliases.Unalias(t).(*types.TypeParam)
return ok return ok
} }
// OriginMethod returns the origin method associated with the method fn.
// For methods on a non-generic receiver base type, this is just
// fn. However, for methods with a generic receiver, OriginMethod returns the
// corresponding method in the method set of the origin type.
//
// As a special case, if fn is not a method (has no receiver), OriginMethod
// returns fn.
func OriginMethod(fn *types.Func) *types.Func {
recv := fn.Type().(*types.Signature).Recv()
if recv == nil {
return fn
}
base := recv.Type()
p, isPtr := base.(*types.Pointer)
if isPtr {
base = p.Elem()
}
named, isNamed := base.(*types.Named)
if !isNamed {
// Receiver is a *types.Interface.
return fn
}
if named.TypeParams().Len() == 0 {
// Receiver base has no type parameters, so we can avoid the lookup below.
return fn
}
orig := named.Origin()
gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
// This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In:
// package p
// type T *int
// func (*T) f() {}
// LookupFieldOrMethod(T, true, p, f)=nil, but NewMethodSet(*T)={(*T).f}.
// Here we make them consistent by force.
// (The go/types bug is general, but this workaround is reached only
// for generic T thanks to the early return above.)
if gfn == nil {
mset := types.NewMethodSet(types.NewPointer(orig))
for i := 0; i < mset.Len(); i++ {
m := mset.At(i)
if m.Obj().Id() == fn.Id() {
gfn = m.Obj()
break
}
}
}
// In golang/go#61196, we observe another crash, this time inexplicable.
if gfn == nil {
panic(fmt.Sprintf("missing origin method for %s.%s; named == origin: %t, named.NumMethods(): %d, origin.NumMethods(): %d", named, fn, named == orig, named.NumMethods(), orig.NumMethods()))
}
return gfn.(*types.Func)
}
// GenericAssignableTo is a generalization of types.AssignableTo that // GenericAssignableTo is a generalization of types.AssignableTo that
// implements the following rule for uninstantiated generic types: // implements the following rule for uninstantiated generic types:
// //
@ -158,6 +93,9 @@ func OriginMethod(fn *types.Func) *types.Func {
// In this case, GenericAssignableTo reports that instantiations of Container // In this case, GenericAssignableTo reports that instantiations of Container
// are assignable to the corresponding instantiation of Interface. // are assignable to the corresponding instantiation of Interface.
func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool { func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool {
V = aliases.Unalias(V)
T = aliases.Unalias(T)
// If V and T are not both named, or do not have matching non-empty type // If V and T are not both named, or do not have matching non-empty type
// parameter lists, fall back on types.AssignableTo. // parameter lists, fall back on types.AssignableTo.

View file

@ -5,7 +5,10 @@
package typeparams package typeparams
import ( import (
"fmt"
"go/types" "go/types"
"golang.org/x/tools/internal/aliases"
) )
// CoreType returns the core type of T or nil if T does not have a core type. // CoreType returns the core type of T or nil if T does not have a core type.
@ -109,7 +112,7 @@ func CoreType(T types.Type) types.Type {
// _NormalTerms makes no guarantees about the order of terms, except that it // _NormalTerms makes no guarantees about the order of terms, except that it
// is deterministic. // is deterministic.
func _NormalTerms(typ types.Type) ([]*types.Term, error) { func _NormalTerms(typ types.Type) ([]*types.Term, error) {
switch typ := typ.(type) { switch typ := aliases.Unalias(typ).(type) {
case *types.TypeParam: case *types.TypeParam:
return StructuralTerms(typ) return StructuralTerms(typ)
case *types.Union: case *types.Union:
@ -120,3 +123,30 @@ func _NormalTerms(typ types.Type) ([]*types.Term, error) {
return []*types.Term{types.NewTerm(false, typ)}, nil return []*types.Term{types.NewTerm(false, typ)}, nil
} }
} }
// Deref returns the type of the variable pointed to by t,
// if t's core type is a pointer; otherwise it returns t.
//
// Do not assume that Deref(T)==T implies T is not a pointer:
// consider "type T *T", for example.
//
// TODO(adonovan): ideally this would live in typesinternal, but that
// creates an import cycle. Move there when we melt this package down.
func Deref(t types.Type) types.Type {
if ptr, ok := CoreType(t).(*types.Pointer); ok {
return ptr.Elem()
}
return t
}
// MustDeref returns the type of the variable pointed to by t.
// It panics if t's core type is not a pointer.
//
// TODO(adonovan): ideally this would live in typesinternal, but that
// creates an import cycle. Move there when we melt this package down.
func MustDeref(t types.Type) types.Type {
if ptr, ok := CoreType(t).(*types.Pointer); ok {
return ptr.Elem()
}
panic(fmt.Sprintf("%v is not a pointer", t))
}

View file

@ -1,34 +1,34 @@
// Copyright 2022 The Go Authors. All rights reserved. // Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package ifaceassert package typeparams
import ( import (
"go/types" "go/types"
"golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/aliases"
) )
// isParameterized reports whether typ contains any of the type parameters of tparams. // Free is a memoization of the set of free type parameters within a
// type. It makes a sequence of calls to [Free.Has] for overlapping
// types more efficient. The zero value is ready for use.
// //
// NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy. // NOTE: Adapted from go/types/infer.go. If it is later exported, factor.
func isParameterized(typ types.Type) bool { type Free struct {
w := tpWalker{
seen: make(map[types.Type]bool),
}
return w.isParameterized(typ)
}
type tpWalker struct {
seen map[types.Type]bool seen map[types.Type]bool
} }
func (w *tpWalker) isParameterized(typ types.Type) (res bool) { // Has reports whether the specified type has a free type parameter.
func (w *Free) Has(typ types.Type) (res bool) {
// detect cycles // detect cycles
if x, ok := w.seen[typ]; ok { if x, ok := w.seen[typ]; ok {
return x return x
} }
if w.seen == nil {
w.seen = make(map[types.Type]bool)
}
w.seen[typ] = false w.seen[typ] = false
defer func() { defer func() {
w.seen[typ] = res w.seen[typ] = res
@ -38,26 +38,29 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
case nil, *types.Basic: // TODO(gri) should nil be handled here? case nil, *types.Basic: // TODO(gri) should nil be handled here?
break break
case *aliases.Alias:
return w.Has(aliases.Unalias(t))
case *types.Array: case *types.Array:
return w.isParameterized(t.Elem()) return w.Has(t.Elem())
case *types.Slice: case *types.Slice:
return w.isParameterized(t.Elem()) return w.Has(t.Elem())
case *types.Struct: case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ { for i, n := 0, t.NumFields(); i < n; i++ {
if w.isParameterized(t.Field(i).Type()) { if w.Has(t.Field(i).Type()) {
return true return true
} }
} }
case *types.Pointer: case *types.Pointer:
return w.isParameterized(t.Elem()) return w.Has(t.Elem())
case *types.Tuple: case *types.Tuple:
n := t.Len() n := t.Len()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if w.isParameterized(t.At(i).Type()) { if w.Has(t.At(i).Type()) {
return true return true
} }
} }
@ -70,37 +73,42 @@ func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
// Similarly, the receiver of a method may declare (rather than // Similarly, the receiver of a method may declare (rather than
// use) type parameters, we don't care about those either. // use) type parameters, we don't care about those either.
// Thus, we only need to look at the input and result parameters. // Thus, we only need to look at the input and result parameters.
return w.isParameterized(t.Params()) || w.isParameterized(t.Results()) return w.Has(t.Params()) || w.Has(t.Results())
case *types.Interface: case *types.Interface:
for i, n := 0, t.NumMethods(); i < n; i++ { for i, n := 0, t.NumMethods(); i < n; i++ {
if w.isParameterized(t.Method(i).Type()) { if w.Has(t.Method(i).Type()) {
return true return true
} }
} }
terms, err := typeparams.InterfaceTermSet(t) terms, err := InterfaceTermSet(t)
if err != nil { if err != nil {
panic(err) panic(err)
} }
for _, term := range terms { for _, term := range terms {
if w.isParameterized(term.Type()) { if w.Has(term.Type()) {
return true return true
} }
} }
case *types.Map: case *types.Map:
return w.isParameterized(t.Key()) || w.isParameterized(t.Elem()) return w.Has(t.Key()) || w.Has(t.Elem())
case *types.Chan: case *types.Chan:
return w.isParameterized(t.Elem()) return w.Has(t.Elem())
case *types.Named: case *types.Named:
list := t.TypeArgs() args := t.TypeArgs()
for i, n := 0, list.Len(); i < n; i++ { // TODO(taking): this does not match go/types/infer.go. Check with rfindley.
if w.isParameterized(list.At(i)) { if params := t.TypeParams(); params.Len() > args.Len() {
return true
}
for i, n := 0, args.Len(); i < n; i++ {
if w.Has(args.At(i)) {
return true return true
} }
} }
return w.Has(t.Underlying()) // recurse for types local to parameterized functions
case *types.TypeParam: case *types.TypeParam:
return true return true

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,179 @@
// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT.
package typesinternal
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[InvalidSyntaxTree - -1]
_ = x[Test-1]
_ = x[BlankPkgName-2]
_ = x[MismatchedPkgName-3]
_ = x[InvalidPkgUse-4]
_ = x[BadImportPath-5]
_ = x[BrokenImport-6]
_ = x[ImportCRenamed-7]
_ = x[UnusedImport-8]
_ = x[InvalidInitCycle-9]
_ = x[DuplicateDecl-10]
_ = x[InvalidDeclCycle-11]
_ = x[InvalidTypeCycle-12]
_ = x[InvalidConstInit-13]
_ = x[InvalidConstVal-14]
_ = x[InvalidConstType-15]
_ = x[UntypedNilUse-16]
_ = x[WrongAssignCount-17]
_ = x[UnassignableOperand-18]
_ = x[NoNewVar-19]
_ = x[MultiValAssignOp-20]
_ = x[InvalidIfaceAssign-21]
_ = x[InvalidChanAssign-22]
_ = x[IncompatibleAssign-23]
_ = x[UnaddressableFieldAssign-24]
_ = x[NotAType-25]
_ = x[InvalidArrayLen-26]
_ = x[BlankIfaceMethod-27]
_ = x[IncomparableMapKey-28]
_ = x[InvalidIfaceEmbed-29]
_ = x[InvalidPtrEmbed-30]
_ = x[BadRecv-31]
_ = x[InvalidRecv-32]
_ = x[DuplicateFieldAndMethod-33]
_ = x[DuplicateMethod-34]
_ = x[InvalidBlank-35]
_ = x[InvalidIota-36]
_ = x[MissingInitBody-37]
_ = x[InvalidInitSig-38]
_ = x[InvalidInitDecl-39]
_ = x[InvalidMainDecl-40]
_ = x[TooManyValues-41]
_ = x[NotAnExpr-42]
_ = x[TruncatedFloat-43]
_ = x[NumericOverflow-44]
_ = x[UndefinedOp-45]
_ = x[MismatchedTypes-46]
_ = x[DivByZero-47]
_ = x[NonNumericIncDec-48]
_ = x[UnaddressableOperand-49]
_ = x[InvalidIndirection-50]
_ = x[NonIndexableOperand-51]
_ = x[InvalidIndex-52]
_ = x[SwappedSliceIndices-53]
_ = x[NonSliceableOperand-54]
_ = x[InvalidSliceExpr-55]
_ = x[InvalidShiftCount-56]
_ = x[InvalidShiftOperand-57]
_ = x[InvalidReceive-58]
_ = x[InvalidSend-59]
_ = x[DuplicateLitKey-60]
_ = x[MissingLitKey-61]
_ = x[InvalidLitIndex-62]
_ = x[OversizeArrayLit-63]
_ = x[MixedStructLit-64]
_ = x[InvalidStructLit-65]
_ = x[MissingLitField-66]
_ = x[DuplicateLitField-67]
_ = x[UnexportedLitField-68]
_ = x[InvalidLitField-69]
_ = x[UntypedLit-70]
_ = x[InvalidLit-71]
_ = x[AmbiguousSelector-72]
_ = x[UndeclaredImportedName-73]
_ = x[UnexportedName-74]
_ = x[UndeclaredName-75]
_ = x[MissingFieldOrMethod-76]
_ = x[BadDotDotDotSyntax-77]
_ = x[NonVariadicDotDotDot-78]
_ = x[MisplacedDotDotDot-79]
_ = x[InvalidDotDotDotOperand-80]
_ = x[InvalidDotDotDot-81]
_ = x[UncalledBuiltin-82]
_ = x[InvalidAppend-83]
_ = x[InvalidCap-84]
_ = x[InvalidClose-85]
_ = x[InvalidCopy-86]
_ = x[InvalidComplex-87]
_ = x[InvalidDelete-88]
_ = x[InvalidImag-89]
_ = x[InvalidLen-90]
_ = x[SwappedMakeArgs-91]
_ = x[InvalidMake-92]
_ = x[InvalidReal-93]
_ = x[InvalidAssert-94]
_ = x[ImpossibleAssert-95]
_ = x[InvalidConversion-96]
_ = x[InvalidUntypedConversion-97]
_ = x[BadOffsetofSyntax-98]
_ = x[InvalidOffsetof-99]
_ = x[UnusedExpr-100]
_ = x[UnusedVar-101]
_ = x[MissingReturn-102]
_ = x[WrongResultCount-103]
_ = x[OutOfScopeResult-104]
_ = x[InvalidCond-105]
_ = x[InvalidPostDecl-106]
_ = x[InvalidChanRange-107]
_ = x[InvalidIterVar-108]
_ = x[InvalidRangeExpr-109]
_ = x[MisplacedBreak-110]
_ = x[MisplacedContinue-111]
_ = x[MisplacedFallthrough-112]
_ = x[DuplicateCase-113]
_ = x[DuplicateDefault-114]
_ = x[BadTypeKeyword-115]
_ = x[InvalidTypeSwitch-116]
_ = x[InvalidExprSwitch-117]
_ = x[InvalidSelectCase-118]
_ = x[UndeclaredLabel-119]
_ = x[DuplicateLabel-120]
_ = x[MisplacedLabel-121]
_ = x[UnusedLabel-122]
_ = x[JumpOverDecl-123]
_ = x[JumpIntoBlock-124]
_ = x[InvalidMethodExpr-125]
_ = x[WrongArgCount-126]
_ = x[InvalidCall-127]
_ = x[UnusedResults-128]
_ = x[InvalidDefer-129]
_ = x[InvalidGo-130]
_ = x[BadDecl-131]
_ = x[RepeatedDecl-132]
_ = x[InvalidUnsafeAdd-133]
_ = x[InvalidUnsafeSlice-134]
_ = x[UnsupportedFeature-135]
_ = x[NotAGenericType-136]
_ = x[WrongTypeArgCount-137]
_ = x[CannotInferTypeArgs-138]
_ = x[InvalidTypeArg-139]
_ = x[InvalidInstanceCycle-140]
_ = x[InvalidUnion-141]
_ = x[MisplacedConstraintIface-142]
_ = x[InvalidMethodTypeParams-143]
_ = x[MisplacedTypeParam-144]
_ = x[InvalidUnsafeSliceData-145]
_ = x[InvalidUnsafeString-146]
}
const (
_ErrorCode_name_0 = "InvalidSyntaxTree"
_ErrorCode_name_1 = "TestBlankPkgNameMismatchedPkgNameInvalidPkgUseBadImportPathBrokenImportImportCRenamedUnusedImportInvalidInitCycleDuplicateDeclInvalidDeclCycleInvalidTypeCycleInvalidConstInitInvalidConstValInvalidConstTypeUntypedNilUseWrongAssignCountUnassignableOperandNoNewVarMultiValAssignOpInvalidIfaceAssignInvalidChanAssignIncompatibleAssignUnaddressableFieldAssignNotATypeInvalidArrayLenBlankIfaceMethodIncomparableMapKeyInvalidIfaceEmbedInvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDotInvalidDotDotDotOperandInvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDeclInvalidChanRangeInvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString"
)
var (
_ErrorCode_index_1 = [...]uint16{0, 4, 16, 33, 46, 59, 71, 85, 97, 113, 126, 142, 158, 174, 189, 205, 218, 234, 253, 261, 277, 295, 312, 330, 354, 362, 377, 393, 411, 428, 443, 450, 461, 484, 499, 511, 522, 537, 551, 566, 581, 594, 603, 617, 632, 643, 658, 667, 683, 703, 721, 740, 752, 771, 790, 806, 823, 842, 856, 867, 882, 895, 910, 926, 940, 956, 971, 988, 1006, 1021, 1031, 1041, 1058, 1080, 1094, 1108, 1128, 1146, 1166, 1184, 1207, 1223, 1238, 1251, 1261, 1273, 1284, 1298, 1311, 1322, 1332, 1347, 1358, 1369, 1382, 1398, 1415, 1439, 1456, 1471, 1481, 1490, 1503, 1519, 1535, 1546, 1561, 1577, 1591, 1607, 1621, 1638, 1658, 1671, 1687, 1701, 1718, 1735, 1752, 1767, 1781, 1795, 1806, 1818, 1831, 1848, 1861, 1872, 1885, 1897, 1906, 1913, 1925, 1941, 1959, 1977, 1992, 2009, 2028, 2042, 2062, 2074, 2098, 2121, 2139, 2161, 2180}
)
func (i ErrorCode) String() string {
switch {
case i == -1:
return _ErrorCode_name_0
case 1 <= i && i <= 146:
i -= 1
return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]]
default:
return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View file

@ -0,0 +1,43 @@
// 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 typesinternal
import (
"go/types"
"golang.org/x/tools/internal/aliases"
)
// ReceiverNamed returns the named type (if any) associated with the
// type of recv, which may be of the form N or *N, or aliases thereof.
// It also reports whether a Pointer was present.
func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) {
t := recv.Type()
if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok {
isPtr = true
t = ptr.Elem()
}
named, _ = aliases.Unalias(t).(*types.Named)
return
}
// Unpointer returns T given *T or an alias thereof.
// For all other types it is the identity function.
// It does not look at underlying types.
// The result may be an alias.
//
// Use this function to strip off the optional pointer on a receiver
// in a field or method selection, without losing the named type
// (which is needed to compute the method set).
//
// See also [typeparams.MustDeref], which removes one level of
// indirection from the type, regardless of named types (analogous to
// a LOAD instruction).
func Unpointer(t types.Type) types.Type {
if ptr, ok := aliases.Unalias(t).(*types.Pointer); ok {
return ptr.Elem()
}
return t
}

View file

@ -0,0 +1,89 @@
// 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 typesinternal
import (
"go/types"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/versions"
)
// TooNewStdSymbols computes the set of package-level symbols
// exported by pkg that are not available at the specified version.
// The result maps each symbol to its minimum version.
//
// The pkg is allowed to contain type errors.
func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]string {
disallowed := make(map[types.Object]string)
// Pass 1: package-level symbols.
symbols := stdlib.PackageSymbols[pkg.Path()]
for _, sym := range symbols {
symver := sym.Version.String()
if versions.Before(version, symver) {
switch sym.Kind {
case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type:
disallowed[pkg.Scope().Lookup(sym.Name)] = symver
}
}
}
// Pass 2: fields and methods.
//
// We allow fields and methods if their associated type is
// disallowed, as otherwise we would report false positives
// for compatibility shims. Consider:
//
// //go:build go1.22
// type T struct { F std.Real } // correct new API
//
// //go:build !go1.22
// type T struct { F fake } // shim
// type fake struct { ... }
// func (fake) M () {}
//
// These alternative declarations of T use either the std.Real
// type, introduced in go1.22, or a fake type, for the field
// F. (The fakery could be arbitrarily deep, involving more
// nested fields and methods than are shown here.) Clients
// that use the compatibility shim T will compile with any
// version of go, whether older or newer than go1.22, but only
// the newer version will use the std.Real implementation.
//
// Now consider a reference to method M in new(T).F.M() in a
// module that requires a minimum of go1.21. The analysis may
// occur using a version of Go higher than 1.21, selecting the
// first version of T, so the method M is Real.M. This would
// spuriously cause the analyzer to report a reference to a
// too-new symbol even though this expression compiles just
// fine (with the fake implementation) using go1.21.
for _, sym := range symbols {
symVersion := sym.Version.String()
if !versions.Before(version, symVersion) {
continue // allowed
}
var obj types.Object
switch sym.Kind {
case stdlib.Field:
typename, name := sym.SplitField()
if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == "" {
obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name)
}
case stdlib.Method:
ptr, recvname, name := sym.SplitMethod()
if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == "" {
obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name)
}
}
if obj != nil {
disallowed[obj] = symVersion
}
}
return disallowed
}

View file

@ -0,0 +1,50 @@
// Copyright 2020 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 typesinternal provides access to internal go/types APIs that are not
// yet exported.
package typesinternal
import (
"go/token"
"go/types"
"reflect"
"unsafe"
)
func SetUsesCgo(conf *types.Config) bool {
v := reflect.ValueOf(conf).Elem()
f := v.FieldByName("go115UsesCgo")
if !f.IsValid() {
f = v.FieldByName("UsesCgo")
if !f.IsValid() {
return false
}
}
addr := unsafe.Pointer(f.UnsafeAddr())
*(*bool)(addr) = true
return true
}
// ReadGo116ErrorData extracts additional information from types.Error values
// generated by Go version 1.16 and later: the error code, start position, and
// end position. If all positions are valid, start <= err.Pos <= end.
//
// If the data could not be read, the final result parameter will be false.
func ReadGo116ErrorData(err types.Error) (code ErrorCode, start, end token.Pos, ok bool) {
var data [3]int
// By coincidence all of these fields are ints, which simplifies things.
v := reflect.ValueOf(err)
for i, name := range []string{"go116code", "go116start", "go116end"} {
f := v.FieldByName(name)
if !f.IsValid() {
return 0, 0, 0, false
}
data[i] = int(f.Int())
}
return ErrorCode(data[0]), token.Pos(data[1]), token.Pos(data[2]), true
}

View file

@ -0,0 +1,43 @@
// Copyright 2023 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
// This file contains predicates for working with file versions to
// decide when a tool should consider a language feature enabled.
// GoVersions that features in x/tools can be gated to.
const (
Go1_18 = "go1.18"
Go1_19 = "go1.19"
Go1_20 = "go1.20"
Go1_21 = "go1.21"
Go1_22 = "go1.22"
)
// Future is an invalid unknown Go version sometime in the future.
// Do not use directly with Compare.
const Future = ""
// AtLeast reports whether the file version v comes after a Go release.
//
// Use this predicate to enable a behavior once a certain Go release
// has happened (and stays enabled in the future).
func AtLeast(v, release string) bool {
if v == Future {
return true // an unknown future version is always after y.
}
return Compare(Lang(v), Lang(release)) >= 0
}
// Before reports whether the file version v is strictly before a Go release.
//
// Use this predicate to disable a behavior once a certain Go release
// has happened (and stays enabled in the future).
func Before(v, release string) bool {
if v == Future {
return false // an unknown future version happens after y.
}
return Compare(Lang(v), Lang(release)) < 0
}

View file

@ -0,0 +1,14 @@
// 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
// toolchain is maximum version (<1.22) that the go toolchain used
// to build the current tool is known to support.
//
// When a tool is built with >=1.22, the value of toolchain is unused.
//
// x/tools does not support building with go <1.18. So we take this
// as the minimum possible maximum.
var toolchain string = Go1_18

View file

@ -0,0 +1,14 @@
// 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.19
// +build go1.19
package versions
func init() {
if Compare(toolchain, Go1_19) < 0 {
toolchain = Go1_19
}
}

View file

@ -0,0 +1,14 @@
// 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.20
// +build go1.20
package versions
func init() {
if Compare(toolchain, Go1_20) < 0 {
toolchain = Go1_20
}
}

View file

@ -0,0 +1,14 @@
// 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
func init() {
if Compare(toolchain, Go1_21) < 0 {
toolchain = Go1_21
}
}

View file

@ -12,9 +12,19 @@ import (
"go/types" "go/types"
) )
// FileVersions always reports the a file's Go version as the // FileVersion returns a language version (<=1.21) derived from runtime.Version()
// zero version at this Go version. // or an unknown future version.
func FileVersions(info *types.Info, file *ast.File) string { return "" } func FileVersion(info *types.Info, file *ast.File) string {
// In x/tools built with Go <= 1.21, we do not have Info.FileVersions
// available. We use a go version derived from the toolchain used to
// compile the tool by default.
// This will be <= go1.21. We take this as the maximum version that
// this tool can support.
//
// There are no features currently in x/tools that need to tell fine grained
// differences for versions <1.22.
return toolchain
}
// InitFileVersions is a noop at this Go version. // InitFileVersions is a noop when compiled with this Go version.
func InitFileVersions(*types.Info) {} func InitFileVersions(*types.Info) {}

View file

@ -12,10 +12,27 @@ import (
"go/types" "go/types"
) )
// FileVersions maps a file to the file's semantic Go version. // FileVersions returns a file's Go version.
// The reported version is the zero version if a version cannot be determined. // The reported version is an unknown Future version if a
func FileVersions(info *types.Info, file *ast.File) string { // version cannot be determined.
return info.FileVersions[file] func FileVersion(info *types.Info, file *ast.File) string {
// In tools built with Go >= 1.22, the Go version of a file
// follow a cascades of sources:
// 1) types.Info.FileVersion, which follows the cascade:
// 1.a) file version (ast.File.GoVersion),
// 1.b) the package version (types.Config.GoVersion), or
// 2) is some unknown Future version.
//
// File versions require a valid package version to be provided to types
// in Config.GoVersion. Config.GoVersion is either from the package's module
// or the toolchain (go run). This value should be provided by go/packages
// or unitchecker.Config.GoVersion.
if v := info.FileVersions[file]; IsValid(v) {
return v
}
// Note: we could instead return runtime.Version() [if valid].
// This would act as a max version on what a tool can support.
return Future
} }
// InitFileVersions initializes info to record Go versions for Go files. // InitFileVersions initializes info to record Go versions for Go files.

View file

@ -4,6 +4,10 @@
package versions package versions
import (
"strings"
)
// Note: If we use build tags to use go/versions when go >=1.22, // Note: If we use build tags to use go/versions when go >=1.22,
// we run into go.dev/issue/53737. Under some operations users would see an // we run into go.dev/issue/53737. Under some operations users would see an
// import of "go/versions" even if they would not compile the file. // import of "go/versions" even if they would not compile the file.
@ -45,6 +49,7 @@ func IsValid(x string) bool { return isValid(stripGo(x)) }
// stripGo converts from a "go1.21" version to a "1.21" version. // stripGo converts from a "go1.21" version to a "1.21" version.
// If v does not start with "go", stripGo returns the empty string (a known invalid version). // If v does not start with "go", stripGo returns the empty string (a known invalid version).
func stripGo(v string) string { func stripGo(v string) string {
v, _, _ = strings.Cut(v, "-") // strip -bigcorp suffix.
if len(v) < 2 || v[:2] != "go" { if len(v) < 2 || v[:2] != "go" {
return "" return ""
} }

View file

@ -71,8 +71,8 @@ golang.org/x/text/internal/tag
golang.org/x/text/language golang.org/x/text/language
golang.org/x/text/transform golang.org/x/text/transform
golang.org/x/text/unicode/norm golang.org/x/text/unicode/norm
# golang.org/x/tools v0.18.0 # golang.org/x/tools v0.19.1-0.20240329171618-904c6baa6e14
## explicit; go 1.18 ## explicit; go 1.19
golang.org/x/tools/cmd/bisect golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover golang.org/x/tools/cover
golang.org/x/tools/go/analysis golang.org/x/tools/go/analysis
@ -122,7 +122,9 @@ golang.org/x/tools/internal/aliases
golang.org/x/tools/internal/analysisinternal golang.org/x/tools/internal/analysisinternal
golang.org/x/tools/internal/bisect golang.org/x/tools/internal/bisect
golang.org/x/tools/internal/facts golang.org/x/tools/internal/facts
golang.org/x/tools/internal/stdlib
golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typeparams
golang.org/x/tools/internal/typesinternal
golang.org/x/tools/internal/versions golang.org/x/tools/internal/versions
# rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650 # rsc.io/markdown v0.0.0-20240117044121-669d2fdf1650
## explicit; go 1.20 ## explicit; go 1.20

3
src/cmd/vet/testdata/rangeloop/go.mod vendored Normal file
View file

@ -0,0 +1,3 @@
module rangeloop
go 1.21

View file

@ -78,7 +78,6 @@ func TestVet(t *testing.T) {
"method", "method",
"nilfunc", "nilfunc",
"print", "print",
"rangeloop",
"shift", "shift",
"slog", "slog",
"structtag", "structtag",
@ -120,6 +119,39 @@ func TestVet(t *testing.T) {
errchk(cmd, files, t) errchk(cmd, files, t)
}) })
} }
// The loopclosure analyzer (aka "rangeloop" before CL 140578)
// is a no-op for files whose version >= go1.22, so we use a
// go.mod file in the rangeloop directory to "downgrade".
//
// TOOD(adonovan): delete when go1.21 goes away.
t.Run("loopclosure", func(t *testing.T) {
cmd := testenv.Command(t, testenv.GoToolPath(t), "vet", "-vettool="+vetPath(t), ".")
cmd.Env = append(os.Environ(), "GOWORK=off")
cmd.Dir = "testdata/rangeloop"
cmd.Stderr = new(strings.Builder) // all vet output goes to stderr
cmd.Run()
stderr := cmd.Stderr.(fmt.Stringer).String()
filename := filepath.FromSlash("testdata/rangeloop/rangeloop.go")
// Unlike the tests above, which runs vet in cmd/vet/, this one
// runs it in subdirectory, so the "full names" in the output
// are in fact short "./rangeloop.go".
// But we can't just pass "./rangeloop.go" as the "full name"
// argument to errorCheck as it does double duty as both a
// string that appears in the output, and as file name
// openable relative to the test directory, containing text
// expectations.
//
// So, we munge the file.
stderr = strings.ReplaceAll(stderr, filepath.FromSlash("./rangeloop.go"), filename)
if err := errorCheck(stderr, false, filename, filepath.Base(filename)); err != nil {
t.Errorf("error check failed: %s", err)
t.Log("vet stderr:\n", cmd.Stderr)
}
})
} }
func cgoEnabled(t *testing.T) bool { func cgoEnabled(t *testing.T) bool {