mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Change-Id: I69065f8adf101fdb28682c55997f503013a50e29 Reviewed-on: https://go-review.googlesource.com/c/go/+/449757 Auto-Submit: Ian Lance Taylor <iant@google.com> Reviewed-by: Joedian Reid <joedian@golang.org> Reviewed-by: Keith Randall <khr@google.com> Reviewed-by: Keith Randall <khr@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Joedian Reid <joedian@golang.org> Run-TryBot: Ian Lance Taylor <iant@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com>
285 lines
8.3 KiB
Go
285 lines
8.3 KiB
Go
// 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 base
|
|
|
|
import (
|
|
"fmt"
|
|
"internal/buildcfg"
|
|
"os"
|
|
"runtime/debug"
|
|
"sort"
|
|
"strings"
|
|
|
|
"cmd/internal/src"
|
|
)
|
|
|
|
// An errorMsg is a queued error message, waiting to be printed.
|
|
type errorMsg struct {
|
|
pos src.XPos
|
|
msg string
|
|
}
|
|
|
|
// Pos is the current source position being processed,
|
|
// printed by Errorf, ErrorfLang, Fatalf, and Warnf.
|
|
var Pos src.XPos
|
|
|
|
var (
|
|
errorMsgs []errorMsg
|
|
numErrors int // number of entries in errorMsgs that are errors (as opposed to warnings)
|
|
numSyntaxErrors int
|
|
)
|
|
|
|
// Errors returns the number of errors reported.
|
|
func Errors() int {
|
|
return numErrors
|
|
}
|
|
|
|
// SyntaxErrors returns the number of syntax errors reported.
|
|
func SyntaxErrors() int {
|
|
return numSyntaxErrors
|
|
}
|
|
|
|
// addErrorMsg adds a new errorMsg (which may be a warning) to errorMsgs.
|
|
func addErrorMsg(pos src.XPos, format string, args ...interface{}) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
// Only add the position if know the position.
|
|
// See issue golang.org/issue/11361.
|
|
if pos.IsKnown() {
|
|
msg = fmt.Sprintf("%v: %s", FmtPos(pos), msg)
|
|
}
|
|
errorMsgs = append(errorMsgs, errorMsg{
|
|
pos: pos,
|
|
msg: msg + "\n",
|
|
})
|
|
}
|
|
|
|
// FmtPos formats pos as a file:line string.
|
|
func FmtPos(pos src.XPos) string {
|
|
if Ctxt == nil {
|
|
return "???"
|
|
}
|
|
return Ctxt.OutermostPos(pos).Format(Flag.C == 0, Flag.L == 1)
|
|
}
|
|
|
|
// byPos sorts errors by source position.
|
|
type byPos []errorMsg
|
|
|
|
func (x byPos) Len() int { return len(x) }
|
|
func (x byPos) Less(i, j int) bool { return x[i].pos.Before(x[j].pos) }
|
|
func (x byPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
|
|
// FlushErrors sorts errors seen so far by line number, prints them to stdout,
|
|
// and empties the errors array.
|
|
func FlushErrors() {
|
|
if Ctxt != nil && Ctxt.Bso != nil {
|
|
Ctxt.Bso.Flush()
|
|
}
|
|
if len(errorMsgs) == 0 {
|
|
return
|
|
}
|
|
sort.Stable(byPos(errorMsgs))
|
|
for i, err := range errorMsgs {
|
|
if i == 0 || err.msg != errorMsgs[i-1].msg {
|
|
fmt.Printf("%s", err.msg)
|
|
}
|
|
}
|
|
errorMsgs = errorMsgs[:0]
|
|
}
|
|
|
|
// lasterror keeps track of the most recently issued error,
|
|
// to avoid printing multiple error messages on the same line.
|
|
var lasterror struct {
|
|
syntax src.XPos // source position of last syntax error
|
|
other src.XPos // source position of last non-syntax error
|
|
msg string // error message of last non-syntax error
|
|
}
|
|
|
|
// sameline reports whether two positions a, b are on the same line.
|
|
func sameline(a, b src.XPos) bool {
|
|
p := Ctxt.PosTable.Pos(a)
|
|
q := Ctxt.PosTable.Pos(b)
|
|
return p.Base() == q.Base() && p.Line() == q.Line()
|
|
}
|
|
|
|
// Errorf reports a formatted error at the current line.
|
|
func Errorf(format string, args ...interface{}) {
|
|
ErrorfAt(Pos, format, args...)
|
|
}
|
|
|
|
// ErrorfAt reports a formatted error message at pos.
|
|
func ErrorfAt(pos src.XPos, format string, args ...interface{}) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
|
|
if strings.HasPrefix(msg, "syntax error") {
|
|
numSyntaxErrors++
|
|
// only one syntax error per line, no matter what error
|
|
if sameline(lasterror.syntax, pos) {
|
|
return
|
|
}
|
|
lasterror.syntax = pos
|
|
} else {
|
|
// only one of multiple equal non-syntax errors per line
|
|
// (FlushErrors shows only one of them, so we filter them
|
|
// here as best as we can (they may not appear in order)
|
|
// so that we don't count them here and exit early, and
|
|
// then have nothing to show for.)
|
|
if sameline(lasterror.other, pos) && lasterror.msg == msg {
|
|
return
|
|
}
|
|
lasterror.other = pos
|
|
lasterror.msg = msg
|
|
}
|
|
|
|
addErrorMsg(pos, "%s", msg)
|
|
numErrors++
|
|
|
|
hcrash()
|
|
if numErrors >= 10 && Flag.LowerE == 0 {
|
|
FlushErrors()
|
|
fmt.Printf("%v: too many errors\n", FmtPos(pos))
|
|
ErrorExit()
|
|
}
|
|
}
|
|
|
|
// ErrorfVers reports that a language feature (format, args) requires a later version of Go.
|
|
func ErrorfVers(lang string, format string, args ...interface{}) {
|
|
Errorf("%s requires %s or later (-lang was set to %s; check go.mod)", fmt.Sprintf(format, args...), lang, Flag.Lang)
|
|
}
|
|
|
|
// UpdateErrorDot is a clumsy hack that rewrites the last error,
|
|
// if it was "LINE: undefined: NAME", to be "LINE: undefined: NAME in EXPR".
|
|
// It is used to give better error messages for dot (selector) expressions.
|
|
func UpdateErrorDot(line string, name, expr string) {
|
|
if len(errorMsgs) == 0 {
|
|
return
|
|
}
|
|
e := &errorMsgs[len(errorMsgs)-1]
|
|
if strings.HasPrefix(e.msg, line) && e.msg == fmt.Sprintf("%v: undefined: %v\n", line, name) {
|
|
e.msg = fmt.Sprintf("%v: undefined: %v in %v\n", line, name, expr)
|
|
}
|
|
}
|
|
|
|
// Warn reports a formatted warning at the current line.
|
|
// In general the Go compiler does NOT generate warnings,
|
|
// so this should be used only when the user has opted in
|
|
// to additional output by setting a particular flag.
|
|
func Warn(format string, args ...interface{}) {
|
|
WarnfAt(Pos, format, args...)
|
|
}
|
|
|
|
// WarnfAt reports a formatted warning at pos.
|
|
// In general the Go compiler does NOT generate warnings,
|
|
// so this should be used only when the user has opted in
|
|
// to additional output by setting a particular flag.
|
|
func WarnfAt(pos src.XPos, format string, args ...interface{}) {
|
|
addErrorMsg(pos, format, args...)
|
|
if Flag.LowerM != 0 {
|
|
FlushErrors()
|
|
}
|
|
}
|
|
|
|
// Fatalf reports a fatal error - an internal problem - at the current line and exits.
|
|
// If other errors have already been printed, then Fatalf just quietly exits.
|
|
// (The internal problem may have been caused by incomplete information
|
|
// after the already-reported errors, so best to let users fix those and
|
|
// try again without being bothered about a spurious internal error.)
|
|
//
|
|
// But if no errors have been printed, or if -d panic has been specified,
|
|
// Fatalf prints the error as an "internal compiler error". In a released build,
|
|
// it prints an error asking to file a bug report. In development builds, it
|
|
// prints a stack trace.
|
|
//
|
|
// If -h has been specified, Fatalf panics to force the usual runtime info dump.
|
|
func Fatalf(format string, args ...interface{}) {
|
|
FatalfAt(Pos, format, args...)
|
|
}
|
|
|
|
// FatalfAt reports a fatal error - an internal problem - at pos and exits.
|
|
// If other errors have already been printed, then FatalfAt just quietly exits.
|
|
// (The internal problem may have been caused by incomplete information
|
|
// after the already-reported errors, so best to let users fix those and
|
|
// try again without being bothered about a spurious internal error.)
|
|
//
|
|
// But if no errors have been printed, or if -d panic has been specified,
|
|
// FatalfAt prints the error as an "internal compiler error". In a released build,
|
|
// it prints an error asking to file a bug report. In development builds, it
|
|
// prints a stack trace.
|
|
//
|
|
// If -h has been specified, FatalfAt panics to force the usual runtime info dump.
|
|
func FatalfAt(pos src.XPos, format string, args ...interface{}) {
|
|
FlushErrors()
|
|
|
|
if Debug.Panic != 0 || numErrors == 0 {
|
|
fmt.Printf("%v: internal compiler error: ", FmtPos(pos))
|
|
fmt.Printf(format, args...)
|
|
fmt.Printf("\n")
|
|
|
|
// If this is a released compiler version, ask for a bug report.
|
|
if Debug.Panic == 0 && strings.HasPrefix(buildcfg.Version, "go") {
|
|
fmt.Printf("\n")
|
|
fmt.Printf("Please file a bug report including a short program that triggers the error.\n")
|
|
fmt.Printf("https://go.dev/issue/new\n")
|
|
} else {
|
|
// Not a release; dump a stack trace, too.
|
|
fmt.Println()
|
|
os.Stdout.Write(debug.Stack())
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
hcrash()
|
|
ErrorExit()
|
|
}
|
|
|
|
// Assert reports "assertion failed" with Fatalf, unless b is true.
|
|
func Assert(b bool) {
|
|
if !b {
|
|
Fatalf("assertion failed")
|
|
}
|
|
}
|
|
|
|
// Assertf reports a fatal error with Fatalf, unless b is true.
|
|
func Assertf(b bool, format string, args ...interface{}) {
|
|
if !b {
|
|
Fatalf(format, args...)
|
|
}
|
|
}
|
|
|
|
// AssertfAt reports a fatal error with FatalfAt, unless b is true.
|
|
func AssertfAt(b bool, pos src.XPos, format string, args ...interface{}) {
|
|
if !b {
|
|
FatalfAt(pos, format, args...)
|
|
}
|
|
}
|
|
|
|
// hcrash crashes the compiler when -h is set, to find out where a message is generated.
|
|
func hcrash() {
|
|
if Flag.LowerH != 0 {
|
|
FlushErrors()
|
|
if Flag.LowerO != "" {
|
|
os.Remove(Flag.LowerO)
|
|
}
|
|
panic("-h")
|
|
}
|
|
}
|
|
|
|
// ErrorExit handles an error-status exit.
|
|
// It flushes any pending errors, removes the output file, and exits.
|
|
func ErrorExit() {
|
|
FlushErrors()
|
|
if Flag.LowerO != "" {
|
|
os.Remove(Flag.LowerO)
|
|
}
|
|
os.Exit(2)
|
|
}
|
|
|
|
// ExitIfErrors calls ErrorExit if any errors have been reported.
|
|
func ExitIfErrors() {
|
|
if Errors() > 0 {
|
|
ErrorExit()
|
|
}
|
|
}
|
|
|
|
var AutogeneratedPos src.XPos
|