Skip to content

Commit ce26db4

Browse files
committed
go/analysis: add Pass.TypeErrors field
The result of type checking is a package (types.Package), type annotations on syntax trees (types.Info), and zero or more type errors (types.Error). Hitherto we had assumed that analyzers don't need access to type errors, but in fact it turns out to be useful: for example, analyzers can look at broken code and suggest quick fixes to repair the mistakes. This change adds a Pass.TypeErrors field that holds the errors produced during type checking. It may be non-nil only when the Analyzer.RunDespiteErrors flag was enabled. Similarly, it adds a TypeErrors field to go/packages.Package. (The existing Packages.Error field loses important details.) Gopls was already using analyzers in this way, (ab)using its privileged position in the x/tools repo. This change removes the need for such hacks. We expect that all analysis drivers that support RunDespiteErrors will in due course populate the Pass.TypesErrors field. This change updates the go/packages-based driver to do so; no changes were needed to unitchecker since it does not support RunDespiteErrors. In the meantime, not populating this field is not expected to cause any compatibility problems. Fixes golang/go#54619 Change-Id: Ia7c72242e332782e8919a4c30b2107c37bcec9ab Reviewed-on: https://go-review.googlesource.com/c/tools/+/425095 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Run-TryBot: Alan Donovan <[email protected]> Reviewed-by: Tim King <[email protected]>
1 parent b0ad6b2 commit ce26db4

File tree

12 files changed

+29
-118
lines changed

12 files changed

+29
-118
lines changed

go/analysis/analysis.go

+2-13
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
"go/token"
1212
"go/types"
1313
"reflect"
14-
15-
"golang.org/x/tools/internal/analysisinternal"
1614
)
1715

1816
// An Analyzer describes an analysis function and its options.
@@ -48,6 +46,7 @@ type Analyzer struct {
4846
// RunDespiteErrors allows the driver to invoke
4947
// the Run method of this analyzer even on a
5048
// package that contains parse or type errors.
49+
// The Pass.TypeErrors field may consequently be non-empty.
5150
RunDespiteErrors bool
5251

5352
// Requires is a set of analyzers that must run successfully
@@ -75,17 +74,6 @@ type Analyzer struct {
7574

7675
func (a *Analyzer) String() string { return a.Name }
7776

78-
func init() {
79-
// Set the analysisinternal functions to be able to pass type errors
80-
// to the Pass type without modifying the go/analysis API.
81-
analysisinternal.SetTypeErrors = func(p interface{}, errors []types.Error) {
82-
p.(*Pass).typeErrors = errors
83-
}
84-
analysisinternal.GetTypeErrors = func(p interface{}) []types.Error {
85-
return p.(*Pass).typeErrors
86-
}
87-
}
88-
8977
// A Pass provides information to the Run function that
9078
// applies a specific analyzer to a single Go package.
9179
//
@@ -106,6 +94,7 @@ type Pass struct {
10694
Pkg *types.Package // type information about the package
10795
TypesInfo *types.Info // type information about the syntax trees
10896
TypesSizes types.Sizes // function for computing sizes of types
97+
TypeErrors []types.Error // type errors (only if Analyzer.RunDespiteErrors)
10998

11099
// Report reports a Diagnostic, a finding about a specific location
111100
// in the analyzed source code such as a potential mistake.

go/analysis/internal/checker/checker.go

+10-76
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@ import (
2626
"runtime/pprof"
2727
"runtime/trace"
2828
"sort"
29-
"strconv"
3029
"strings"
3130
"sync"
3231
"time"
3332

3433
"golang.org/x/tools/go/analysis"
3534
"golang.org/x/tools/go/analysis/internal/analysisflags"
3635
"golang.org/x/tools/go/packages"
37-
"golang.org/x/tools/internal/analysisinternal"
3836
)
3937

4038
var (
@@ -699,14 +697,16 @@ func (act *action) execOnce() {
699697

700698
// Run the analysis.
701699
pass := &analysis.Pass{
702-
Analyzer: act.a,
703-
Fset: act.pkg.Fset,
704-
Files: act.pkg.Syntax,
705-
OtherFiles: act.pkg.OtherFiles,
706-
IgnoredFiles: act.pkg.IgnoredFiles,
707-
Pkg: act.pkg.Types,
708-
TypesInfo: act.pkg.TypesInfo,
709-
TypesSizes: act.pkg.TypesSizes,
700+
Analyzer: act.a,
701+
Fset: act.pkg.Fset,
702+
Files: act.pkg.Syntax,
703+
OtherFiles: act.pkg.OtherFiles,
704+
IgnoredFiles: act.pkg.IgnoredFiles,
705+
Pkg: act.pkg.Types,
706+
TypesInfo: act.pkg.TypesInfo,
707+
TypesSizes: act.pkg.TypesSizes,
708+
TypeErrors: act.pkg.TypeErrors,
709+
710710
ResultOf: inputs,
711711
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
712712
ImportObjectFact: act.importObjectFact,
@@ -718,72 +718,6 @@ func (act *action) execOnce() {
718718
}
719719
act.pass = pass
720720

721-
var errors []types.Error
722-
// Get any type errors that are attributed to the pkg.
723-
// This is necessary to test analyzers that provide
724-
// suggested fixes for compiler/type errors.
725-
// TODO(adonovan): eliminate this hack;
726-
// see https://github.com/golang/go/issues/54619.
727-
for _, err := range act.pkg.Errors {
728-
if err.Kind != packages.TypeError {
729-
continue
730-
}
731-
732-
// Parse err.Pos, a string of form: "file:line:col" or "file:line" or "" or "-"
733-
// The filename may have a single ASCII letter Windows drive prefix such as "C:"
734-
var file string
735-
var line, col int
736-
var convErr error
737-
words := strings.Split(err.Pos, ":")
738-
if runtime.GOOS == "windows" &&
739-
len(words) > 2 &&
740-
len(words[0]) == 1 &&
741-
('A' <= words[0][0] && words[0][0] <= 'Z' ||
742-
'a' <= words[0][0] && words[0][0] <= 'z') {
743-
words[1] = words[0] + ":" + words[1]
744-
words = words[1:]
745-
}
746-
switch len(words) {
747-
case 2:
748-
// file:line
749-
file = words[0]
750-
line, convErr = strconv.Atoi(words[1])
751-
case 3:
752-
// file:line:col
753-
file = words[0]
754-
line, convErr = strconv.Atoi(words[1])
755-
if convErr == nil {
756-
col, convErr = strconv.Atoi(words[2])
757-
}
758-
default:
759-
continue
760-
}
761-
if convErr != nil {
762-
continue
763-
}
764-
765-
// Extract the token positions from the error string.
766-
// (This is guesswork: Fset may contain all manner
767-
// of stale files with the same name.)
768-
offset := -1
769-
act.pkg.Fset.Iterate(func(f *token.File) bool {
770-
if f.Name() != file {
771-
return true
772-
}
773-
offset = int(f.LineStart(line)) + col - 1
774-
return false
775-
})
776-
if offset == -1 {
777-
continue
778-
}
779-
errors = append(errors, types.Error{
780-
Fset: act.pkg.Fset,
781-
Msg: err.Msg,
782-
Pos: token.Pos(offset),
783-
})
784-
}
785-
analysisinternal.SetTypeErrors(pass, errors)
786-
787721
var err error
788722
if act.pkg.IllTyped && !pass.Analyzer.RunDespiteErrors {
789723
err = fmt.Errorf("analysis skipped due to errors in package")

go/analysis/unitchecker/unitchecker.go

+1
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
340340
Pkg: pkg,
341341
TypesInfo: info,
342342
TypesSizes: tc.Sizes,
343+
TypeErrors: nil, // unitchecker doesn't RunDespiteErrors
343344
ResultOf: inputs,
344345
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
345346
ImportObjectFact: facts.ImportObjectFact,

go/packages/packages.go

+4
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ type Package struct {
303303
// of the package, or while parsing or type-checking its files.
304304
Errors []Error
305305

306+
// TypeErrors contains the subset of errors produced during type checking.
307+
TypeErrors []types.Error
308+
306309
// GoFiles lists the absolute file paths of the package's Go source files.
307310
GoFiles []string
308311

@@ -911,6 +914,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
911914

912915
case types.Error:
913916
// from type checker
917+
lpkg.TypeErrors = append(lpkg.TypeErrors, err)
914918
errs = append(errs, Error{
915919
Pos: err.Fset.Position(err.Pos).String(),
916920
Msg: err.Msg,

gopls/internal/lsp/analysis/fillreturns/fillreturns.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
5252
return nil, fmt.Errorf("nil TypeInfo")
5353
}
5454

55-
errors := analysisinternal.GetTypeErrors(pass)
5655
outer:
57-
for _, typeErr := range errors {
56+
for _, typeErr := range pass.TypeErrors {
5857
// Filter out the errors that are not relevant to this analyzer.
5958
if !FixesError(typeErr) {
6059
continue

gopls/internal/lsp/analysis/nonewvars/nonewvars.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ will turn into
3030
`
3131

3232
var Analyzer = &analysis.Analyzer{
33-
Name: string(analysisinternal.NoNewVars),
33+
Name: "nonewvars",
3434
Doc: Doc,
3535
Requires: []*analysis.Analyzer{inspect.Analyzer},
3636
Run: run,
@@ -39,7 +39,6 @@ var Analyzer = &analysis.Analyzer{
3939

4040
func run(pass *analysis.Pass) (interface{}, error) {
4141
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
42-
errors := analysisinternal.GetTypeErrors(pass)
4342

4443
nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}
4544
inspect.Preorder(nodeFilter, func(n ast.Node) {
@@ -60,7 +59,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
6059
return
6160
}
6261

63-
for _, err := range errors {
62+
for _, err := range pass.TypeErrors {
6463
if !FixesError(err.Msg) {
6564
continue
6665
}

gopls/internal/lsp/analysis/noresultvalues/noresultvalues.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ will turn into
2929
`
3030

3131
var Analyzer = &analysis.Analyzer{
32-
Name: string(analysisinternal.NoResultValues),
32+
Name: "noresultvalues",
3333
Doc: Doc,
3434
Requires: []*analysis.Analyzer{inspect.Analyzer},
3535
Run: run,
@@ -38,7 +38,6 @@ var Analyzer = &analysis.Analyzer{
3838

3939
func run(pass *analysis.Pass) (interface{}, error) {
4040
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
41-
errors := analysisinternal.GetTypeErrors(pass)
4241

4342
nodeFilter := []ast.Node{(*ast.ReturnStmt)(nil)}
4443
inspect.Preorder(nodeFilter, func(n ast.Node) {
@@ -55,7 +54,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
5554
return
5655
}
5756

58-
for _, err := range errors {
57+
for _, err := range pass.TypeErrors {
5958
if !FixesError(err.Msg) {
6059
continue
6160
}

gopls/internal/lsp/analysis/stubmethods/stubmethods.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var Analyzer = &analysis.Analyzer{
3535
}
3636

3737
func run(pass *analysis.Pass) (interface{}, error) {
38-
for _, err := range analysisinternal.GetTypeErrors(pass) {
38+
for _, err := range pass.TypeErrors {
3939
ifaceErr := strings.Contains(err.Msg, "missing method") || strings.HasPrefix(err.Msg, "cannot convert")
4040
if !ifaceErr {
4141
continue

gopls/internal/lsp/analysis/undeclaredname/undeclared.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func <>(inferred parameters) {
3838
`
3939

4040
var Analyzer = &analysis.Analyzer{
41-
Name: string(analysisinternal.UndeclaredName),
41+
Name: "undeclaredname",
4242
Doc: Doc,
4343
Requires: []*analysis.Analyzer{},
4444
Run: run,
@@ -49,7 +49,7 @@ var Analyzer = &analysis.Analyzer{
4949
var undeclaredNamePrefixes = []string{"undeclared name: ", "undefined: "}
5050

5151
func run(pass *analysis.Pass) (interface{}, error) {
52-
for _, err := range analysisinternal.GetTypeErrors(pass) {
52+
for _, err := range pass.TypeErrors {
5353
runForError(pass, err)
5454
}
5555
return nil, nil

gopls/internal/lsp/analysis/unusedvariable/unusedvariable.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616

1717
"golang.org/x/tools/go/analysis"
1818
"golang.org/x/tools/go/ast/astutil"
19-
"golang.org/x/tools/internal/analysisinternal"
2019
)
2120

2221
const Doc = `check for unused variables
@@ -36,7 +35,7 @@ var Analyzer = &analysis.Analyzer{
3635
var unusedVariableSuffixes = []string{" declared and not used", " declared but not used"}
3736

3837
func run(pass *analysis.Pass) (interface{}, error) {
39-
for _, typeErr := range analysisinternal.GetTypeErrors(pass) {
38+
for _, typeErr := range pass.TypeErrors {
4039
for _, suffix := range unusedVariableSuffixes {
4140
if strings.HasSuffix(typeErr.Msg, suffix) {
4241
varName := strings.TrimSuffix(typeErr.Msg, suffix)

gopls/internal/lsp/cache/analysis.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"golang.org/x/tools/go/analysis"
1818
"golang.org/x/tools/gopls/internal/lsp/source"
1919
"golang.org/x/tools/gopls/internal/span"
20-
"golang.org/x/tools/internal/analysisinternal"
2120
"golang.org/x/tools/internal/bug"
2221
"golang.org/x/tools/internal/event"
2322
"golang.org/x/tools/internal/event/tag"
@@ -291,6 +290,7 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
291290
Pkg: pkg.GetTypes(),
292291
TypesInfo: pkg.GetTypesInfo(),
293292
TypesSizes: pkg.GetTypesSizes(),
293+
TypeErrors: pkg.typeErrors,
294294
ResultOf: inputs,
295295
Report: func(d analysis.Diagnostic) {
296296
// Prefix the diagnostic category with the analyzer's name.
@@ -351,7 +351,6 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
351351
return facts
352352
},
353353
}
354-
analysisinternal.SetTypeErrors(pass, pkg.typeErrors)
355354

356355
if (pkg.HasListOrParseErrors() || pkg.HasTypeErrors()) && !analyzer.RunDespiteErrors {
357356
return nil, fmt.Errorf("skipping analysis %s because package %s contains errors", analyzer.Name, pkg.ID())

internal/analysisinternal/analysis.go

+2-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Package analysisinternal exposes internal-only fields from go/analysis.
5+
// Package analysisinternal provides gopls' internal analyses with a
6+
// number of helper functions that operate on typed syntax trees.
67
package analysisinternal
78

89
import (
@@ -18,11 +19,6 @@ import (
1819
// in Go 1.18+.
1920
var DiagnoseFuzzTests bool = false
2021

21-
var (
22-
GetTypeErrors func(p interface{}) []types.Error
23-
SetTypeErrors func(p interface{}, errors []types.Error)
24-
)
25-
2622
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
2723
// Get the end position for the type error.
2824
offset, end := fset.PositionFor(start, false).Offset, start
@@ -210,14 +206,6 @@ func TypeExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
210206
}
211207
}
212208

213-
type TypeErrorPass string
214-
215-
const (
216-
NoNewVars TypeErrorPass = "nonewvars"
217-
NoResultValues TypeErrorPass = "noresultvalues"
218-
UndeclaredName TypeErrorPass = "undeclaredname"
219-
)
220-
221209
// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable.
222210
// Some examples:
223211
//

0 commit comments

Comments
 (0)