mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
212 lines
5.8 KiB
212 lines
5.8 KiB
// Package diagnostics formats compiler errors and prints them in a consistent
|
|
// way.
|
|
package diagnostics
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/scanner"
|
|
"go/token"
|
|
"go/types"
|
|
"io"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/tinygo-org/tinygo/builder"
|
|
"github.com/tinygo-org/tinygo/goenv"
|
|
"github.com/tinygo-org/tinygo/interp"
|
|
"github.com/tinygo-org/tinygo/loader"
|
|
)
|
|
|
|
// A single diagnostic.
|
|
type Diagnostic struct {
|
|
Pos token.Position
|
|
Msg string
|
|
}
|
|
|
|
// One or multiple errors of a particular package.
|
|
// It can also represent whole-program errors (like linker errors) that can't
|
|
// easily be connected to a single package.
|
|
type PackageDiagnostic struct {
|
|
ImportPath string // the same ImportPath as in `go list -json`
|
|
Diagnostics []Diagnostic
|
|
}
|
|
|
|
// Diagnostics of a whole program. This can include errors belonging to multiple
|
|
// packages, or just a single package.
|
|
type ProgramDiagnostic []PackageDiagnostic
|
|
|
|
// CreateDiagnostics reads the underlying errors in the error object and creates
|
|
// a set of diagnostics that's sorted and can be readily printed.
|
|
func CreateDiagnostics(err error) ProgramDiagnostic {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
// Right now, the compiler will only show errors for the first package that
|
|
// fails to build. This is likely to change in the future.
|
|
return ProgramDiagnostic{
|
|
createPackageDiagnostic(err),
|
|
}
|
|
}
|
|
|
|
// Create diagnostics for a single package (though, in practice, it may also be
|
|
// used for whole-program diagnostics in some cases).
|
|
func createPackageDiagnostic(err error) PackageDiagnostic {
|
|
// Extract diagnostics for this package.
|
|
var pkgDiag PackageDiagnostic
|
|
switch err := err.(type) {
|
|
case *builder.MultiError:
|
|
if err.ImportPath != "" {
|
|
pkgDiag.ImportPath = err.ImportPath
|
|
}
|
|
for _, err := range err.Errs {
|
|
diags := createDiagnostics(err)
|
|
pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...)
|
|
}
|
|
case loader.Errors:
|
|
if err.Pkg != nil {
|
|
pkgDiag.ImportPath = err.Pkg.ImportPath
|
|
}
|
|
for _, err := range err.Errs {
|
|
diags := createDiagnostics(err)
|
|
pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...)
|
|
}
|
|
case *interp.Error:
|
|
pkgDiag.ImportPath = err.ImportPath
|
|
w := &bytes.Buffer{}
|
|
fmt.Fprintln(w, err.Error())
|
|
if len(err.Inst) != 0 {
|
|
fmt.Fprintln(w, err.Inst)
|
|
}
|
|
if len(err.Traceback) > 0 {
|
|
fmt.Fprintln(w, "\ntraceback:")
|
|
for _, line := range err.Traceback {
|
|
fmt.Fprintln(w, line.Pos.String()+":")
|
|
fmt.Fprintln(w, line.Inst)
|
|
}
|
|
}
|
|
pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, Diagnostic{
|
|
Msg: w.String(),
|
|
})
|
|
default:
|
|
pkgDiag.Diagnostics = createDiagnostics(err)
|
|
}
|
|
|
|
// Sort these diagnostics by file/line/column.
|
|
sort.SliceStable(pkgDiag.Diagnostics, func(i, j int) bool {
|
|
posI := pkgDiag.Diagnostics[i].Pos
|
|
posJ := pkgDiag.Diagnostics[j].Pos
|
|
if posI.Filename != posJ.Filename {
|
|
return posI.Filename < posJ.Filename
|
|
}
|
|
if posI.Line != posJ.Line {
|
|
return posI.Line < posJ.Line
|
|
}
|
|
return posI.Column < posJ.Column
|
|
})
|
|
|
|
return pkgDiag
|
|
}
|
|
|
|
// Extract diagnostics from the given error message and return them as a slice
|
|
// of errors (which in many cases will just be a single diagnostic).
|
|
func createDiagnostics(err error) []Diagnostic {
|
|
switch err := err.(type) {
|
|
case types.Error:
|
|
return []Diagnostic{
|
|
{
|
|
Pos: err.Fset.Position(err.Pos),
|
|
Msg: err.Msg,
|
|
},
|
|
}
|
|
case scanner.Error:
|
|
return []Diagnostic{
|
|
{
|
|
Pos: err.Pos,
|
|
Msg: err.Msg,
|
|
},
|
|
}
|
|
case scanner.ErrorList:
|
|
var diags []Diagnostic
|
|
for _, err := range err {
|
|
diags = append(diags, createDiagnostics(*err)...)
|
|
}
|
|
return diags
|
|
case loader.Error:
|
|
if err.Err.Pos.Filename != "" {
|
|
// Probably a syntax error in a dependency.
|
|
return createDiagnostics(err.Err)
|
|
} else {
|
|
// Probably an "import cycle not allowed" error.
|
|
buf := &bytes.Buffer{}
|
|
fmt.Fprintln(buf, "package", err.ImportStack[0])
|
|
for i := 1; i < len(err.ImportStack); i++ {
|
|
pkgPath := err.ImportStack[i]
|
|
if i == len(err.ImportStack)-1 {
|
|
// last package
|
|
fmt.Fprintln(buf, "\timports", pkgPath+": "+err.Err.Error())
|
|
} else {
|
|
// not the last package
|
|
fmt.Fprintln(buf, "\timports", pkgPath)
|
|
}
|
|
}
|
|
return []Diagnostic{
|
|
{Msg: buf.String()},
|
|
}
|
|
}
|
|
default:
|
|
return []Diagnostic{
|
|
{Msg: err.Error()},
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write program diagnostics to the given writer with 'wd' as the relative
|
|
// working directory.
|
|
func (progDiag ProgramDiagnostic) WriteTo(w io.Writer, wd string) {
|
|
for _, pkgDiag := range progDiag {
|
|
pkgDiag.WriteTo(w, wd)
|
|
}
|
|
}
|
|
|
|
// Write package diagnostics to the given writer with 'wd' as the relative
|
|
// working directory.
|
|
func (pkgDiag PackageDiagnostic) WriteTo(w io.Writer, wd string) {
|
|
if pkgDiag.ImportPath != "" {
|
|
fmt.Fprintln(w, "#", pkgDiag.ImportPath)
|
|
}
|
|
for _, diag := range pkgDiag.Diagnostics {
|
|
diag.WriteTo(w, wd)
|
|
}
|
|
}
|
|
|
|
// Write this diagnostic to the given writer with 'wd' as the relative working
|
|
// directory.
|
|
func (diag Diagnostic) WriteTo(w io.Writer, wd string) {
|
|
if diag.Pos == (token.Position{}) {
|
|
fmt.Fprintln(w, diag.Msg)
|
|
return
|
|
}
|
|
pos := diag.Pos // make a copy
|
|
if !strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) {
|
|
// This file is not from the standard library (either the GOROOT or the
|
|
// TINYGOROOT). Make the path relative, for easier reading. Ignore any
|
|
// errors in the process (falling back to the absolute path).
|
|
pos.Filename = tryToMakePathRelative(pos.Filename, wd)
|
|
}
|
|
fmt.Fprintf(w, "%s: %s\n", pos, diag.Msg)
|
|
}
|
|
|
|
// try to make the path relative to the current working directory. If any error
|
|
// occurs, this error is ignored and the absolute path is returned instead.
|
|
func tryToMakePathRelative(dir, wd string) string {
|
|
if wd == "" {
|
|
return dir // working directory not found
|
|
}
|
|
relpath, err := filepath.Rel(wd, dir)
|
|
if err != nil {
|
|
return dir
|
|
}
|
|
return relpath
|
|
}
|
|
|