go/packages: coerce all errors to a single type
Rather than document the range of possible error types, requiring clumsy client code to extract position information, we now expose a single concrete type for all errors. Position information (in standardized string form) is technically optional, but we should strive for 100%, fixing gaps as they arise. This change enables us to unify the Package and JSON structs in a follow-up. Question: should we eliminate the Config.Error hook and be silent by default? Pro: + most clients suppress it. Con: - clients that want to print errors (e.g. vet-like tools) would have to traverse the entire import graph to find them. - silence is not the most fail-safe behavior. Change-Id: Ie92b9fb7641ceda429f00928474b650d1dfadedd Reviewed-on: https://go-review.googlesource.com/130576 Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
parent
7ca1327549
commit
f1c1faf65a
|
@ -12,6 +12,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
|
"go/scanner"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"log"
|
"log"
|
||||||
|
@ -189,14 +190,9 @@ type Package struct {
|
||||||
// PkgPath is the package path as used by the go/types package.
|
// PkgPath is the package path as used by the go/types package.
|
||||||
PkgPath string
|
PkgPath string
|
||||||
|
|
||||||
// Errors contains any errors encountered while parsing or type-checking the package.
|
// Errors contains any errors encountered querying the metadata
|
||||||
// Possible error types include *scanner.ErrorList and types.Error,
|
// of the package, or while parsing or type-checking its files.
|
||||||
// whose fields provide structured position information.
|
Errors []Error
|
||||||
// Error strings are typically of the form "file:line: message" or
|
|
||||||
// "file:line:col: message".
|
|
||||||
// TODO(adonovan): export packageError as packages.Error
|
|
||||||
// and add that type to the list of structured errors.
|
|
||||||
Errors []error
|
|
||||||
|
|
||||||
// GoFiles lists the absolute file paths of the package's Go source files.
|
// GoFiles lists the absolute file paths of the package's Go source files.
|
||||||
GoFiles []string
|
GoFiles []string
|
||||||
|
@ -228,7 +224,7 @@ type Package struct {
|
||||||
// It is set only when Types is set.
|
// It is set only when Types is set.
|
||||||
Fset *token.FileSet
|
Fset *token.FileSet
|
||||||
|
|
||||||
// IllTyped indicates whether the package has any type errors.
|
// IllTyped indicates whether the package or any dependency contains errors.
|
||||||
// It is set only when Types is set.
|
// It is set only when Types is set.
|
||||||
IllTyped bool
|
IllTyped bool
|
||||||
|
|
||||||
|
@ -243,25 +239,30 @@ type Package struct {
|
||||||
TypesInfo *types.Info
|
TypesInfo *types.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
// packageError is used to serialize structured errors as much as possible.
|
// An Error describes a problem with a package's metadata, syntax, or types.
|
||||||
// This has members compatible with the golist error type, and possibly some
|
type Error struct {
|
||||||
// more if we need other error information to survive.
|
Pos string // "file:line:col" or "file:line" or "" or "-"
|
||||||
type packageError struct {
|
Msg string
|
||||||
Pos string // position of error
|
|
||||||
Err string // the error itself
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *packageError) Error() string {
|
func (err Error) Error() string {
|
||||||
return e.Pos + ": " + e.Err
|
pos := err.Pos
|
||||||
|
if pos == "" {
|
||||||
|
pos = "-" // like token.Position{}.String()
|
||||||
|
}
|
||||||
|
return pos + ": " + err.Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// flatPackage is the JSON form of Package
|
// flatPackage is the JSON form of Package
|
||||||
// It drops all the type and syntax fields, and transforms the Imports and Errors
|
// It drops all the type and syntax fields, and transforms the Imports
|
||||||
|
//
|
||||||
|
// TODO(adonovan): identify this struct with Package, effectively
|
||||||
|
// publishing the JSON protocol.
|
||||||
type flatPackage struct {
|
type flatPackage struct {
|
||||||
ID string
|
ID string
|
||||||
Name string `json:",omitempty"`
|
Name string `json:",omitempty"`
|
||||||
PkgPath string `json:",omitempty"`
|
PkgPath string `json:",omitempty"`
|
||||||
Errors []*packageError `json:",omitempty"`
|
Errors []Error `json:",omitempty"`
|
||||||
GoFiles []string `json:",omitempty"`
|
GoFiles []string `json:",omitempty"`
|
||||||
CompiledGoFiles []string `json:",omitempty"`
|
CompiledGoFiles []string `json:",omitempty"`
|
||||||
OtherFiles []string `json:",omitempty"`
|
OtherFiles []string `json:",omitempty"`
|
||||||
|
@ -283,23 +284,12 @@ func (p *Package) MarshalJSON() ([]byte, error) {
|
||||||
ID: p.ID,
|
ID: p.ID,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
PkgPath: p.PkgPath,
|
PkgPath: p.PkgPath,
|
||||||
|
Errors: p.Errors,
|
||||||
GoFiles: p.GoFiles,
|
GoFiles: p.GoFiles,
|
||||||
CompiledGoFiles: p.CompiledGoFiles,
|
CompiledGoFiles: p.CompiledGoFiles,
|
||||||
OtherFiles: p.OtherFiles,
|
OtherFiles: p.OtherFiles,
|
||||||
ExportFile: p.ExportFile,
|
ExportFile: p.ExportFile,
|
||||||
}
|
}
|
||||||
if len(p.Errors) > 0 {
|
|
||||||
flat.Errors = make([]*packageError, len(p.Errors))
|
|
||||||
for i, err := range p.Errors {
|
|
||||||
//TODO: best effort mapping of errors to the serialized form
|
|
||||||
switch err := err.(type) {
|
|
||||||
case *packageError:
|
|
||||||
flat.Errors[i] = err
|
|
||||||
default:
|
|
||||||
flat.Errors[i] = &packageError{Err: err.Error()}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(p.Imports) > 0 {
|
if len(p.Imports) > 0 {
|
||||||
flat.Imports = make(map[string]string, len(p.Imports))
|
flat.Imports = make(map[string]string, len(p.Imports))
|
||||||
for path, ipkg := range p.Imports {
|
for path, ipkg := range p.Imports {
|
||||||
|
@ -320,17 +310,12 @@ func (p *Package) UnmarshalJSON(b []byte) error {
|
||||||
ID: flat.ID,
|
ID: flat.ID,
|
||||||
Name: flat.Name,
|
Name: flat.Name,
|
||||||
PkgPath: flat.PkgPath,
|
PkgPath: flat.PkgPath,
|
||||||
|
Errors: flat.Errors,
|
||||||
GoFiles: flat.GoFiles,
|
GoFiles: flat.GoFiles,
|
||||||
CompiledGoFiles: flat.CompiledGoFiles,
|
CompiledGoFiles: flat.CompiledGoFiles,
|
||||||
OtherFiles: flat.OtherFiles,
|
OtherFiles: flat.OtherFiles,
|
||||||
ExportFile: flat.ExportFile,
|
ExportFile: flat.ExportFile,
|
||||||
}
|
}
|
||||||
if len(flat.Errors) > 0 {
|
|
||||||
p.Errors = make([]error, len(flat.Errors))
|
|
||||||
for i, err := range flat.Errors {
|
|
||||||
p.Errors[i] = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(flat.Imports) > 0 {
|
if len(flat.Imports) > 0 {
|
||||||
p.Imports = make(map[string]*Package, len(flat.Imports))
|
p.Imports = make(map[string]*Package, len(flat.Imports))
|
||||||
for path, id := range flat.Imports {
|
for path, id := range flat.Imports {
|
||||||
|
@ -573,6 +558,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
|
||||||
// This avoids skew between golist and go/types when the files'
|
// This avoids skew between golist and go/types when the files'
|
||||||
// package declarations are inconsistent.
|
// package declarations are inconsistent.
|
||||||
lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name)
|
lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name)
|
||||||
|
lpkg.Fset = ld.Fset
|
||||||
|
|
||||||
// Subtle: we populate all Types fields with an empty Package
|
// Subtle: we populate all Types fields with an empty Package
|
||||||
// before loading export data so that export data processing
|
// before loading export data so that export data processing
|
||||||
|
@ -588,15 +574,63 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
|
||||||
return // not a source package, don't get syntax trees
|
return // not a source package, don't get syntax trees
|
||||||
}
|
}
|
||||||
|
|
||||||
hardErrors := false
|
|
||||||
appendError := func(err error) {
|
appendError := func(err error) {
|
||||||
if terr, ok := err.(types.Error); ok && terr.Soft {
|
// Convert various error types into the one true Error.
|
||||||
// Don't mark the package as bad.
|
var errs []Error
|
||||||
} else {
|
switch err := err.(type) {
|
||||||
hardErrors = true
|
case Error:
|
||||||
|
// from driver
|
||||||
|
errs = append(errs, err)
|
||||||
|
|
||||||
|
case *os.PathError:
|
||||||
|
// from parser
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: err.Path + ":1",
|
||||||
|
Msg: err.Err.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
|
case scanner.ErrorList:
|
||||||
|
// from parser
|
||||||
|
for _, err := range err {
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: err.Pos.String(),
|
||||||
|
Msg: err.Msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.Error:
|
||||||
|
// from type checker
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: err.Fset.Position(err.Pos).String(),
|
||||||
|
Msg: err.Msg,
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unexpected impoverished error from parser?
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: "-",
|
||||||
|
Msg: err.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// If you see this error message, please file a bug.
|
||||||
|
log.Printf("internal error: error %q (%T) without position", err, err)
|
||||||
}
|
}
|
||||||
ld.Error(err)
|
|
||||||
lpkg.Errors = append(lpkg.Errors, err)
|
// Allow application to print errors.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): the real purpose of this hook is to
|
||||||
|
// allow (most) applications to _disable_ printing,
|
||||||
|
// while printing by default.
|
||||||
|
// Should we remove it, and make clients responsible for
|
||||||
|
// walking the import graph and printing errors?
|
||||||
|
// Though convenient for the common case,
|
||||||
|
// it seems like an unsafe default, and is decidedly less
|
||||||
|
// convenient for a tool that wants to print the errors.
|
||||||
|
for _, err := range errs {
|
||||||
|
ld.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lpkg.Errors = append(lpkg.Errors, errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
files, errs := ld.parseFiles(lpkg.CompiledGoFiles)
|
files, errs := ld.parseFiles(lpkg.CompiledGoFiles)
|
||||||
|
@ -604,7 +638,6 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
|
||||||
appendError(err)
|
appendError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lpkg.Fset = ld.Fset
|
|
||||||
lpkg.Syntax = files
|
lpkg.Syntax = files
|
||||||
|
|
||||||
lpkg.TypesInfo = &types.Info{
|
lpkg.TypesInfo = &types.Info{
|
||||||
|
@ -681,14 +714,16 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record accumulated errors.
|
// Record accumulated errors.
|
||||||
for _, imp := range lpkg.Imports {
|
illTyped := len(lpkg.Errors) > 0
|
||||||
if imp.IllTyped {
|
if !illTyped {
|
||||||
hardErrors = true
|
for _, imp := range lpkg.Imports {
|
||||||
break
|
if imp.IllTyped {
|
||||||
|
illTyped = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lpkg.IllTyped = illTyped
|
||||||
lpkg.IllTyped = hardErrors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// An importFunc is an implementation of the single-method
|
// An importFunc is an implementation of the single-method
|
||||||
|
|
|
@ -548,12 +548,12 @@ package b`,
|
||||||
|
|
||||||
type errCollector struct {
|
type errCollector struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
errors []error
|
errors []packages.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *errCollector) add(err error) {
|
func (ec *errCollector) add(err error) {
|
||||||
ec.mu.Lock()
|
ec.mu.Lock()
|
||||||
ec.errors = append(ec.errors, err)
|
ec.errors = append(ec.errors, err.(packages.Error))
|
||||||
ec.mu.Unlock()
|
ec.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1401,15 +1401,10 @@ EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorMessages(errors []error) []string {
|
func errorMessages(errors []packages.Error) []string {
|
||||||
var msgs []string
|
var msgs []string
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
msg := err.Error()
|
msgs = append(msgs, err.Msg)
|
||||||
// Strip off /tmp filename.
|
|
||||||
if i := strings.Index(msg, ": "); i >= 0 {
|
|
||||||
msg = msg[i+len(": "):]
|
|
||||||
}
|
|
||||||
msgs = append(msgs, msg)
|
|
||||||
}
|
}
|
||||||
sort.Strings(msgs)
|
sort.Strings(msgs)
|
||||||
return msgs
|
return msgs
|
||||||
|
|
Loading…
Reference in New Issue