go/types: first cut at better error messages

- added Error.Full and Error.FullError which provide the full error
  message, possibly containing internal details
- Error.Msg and Error.Error provide a user-friendly error message

Change-Id: Id3044165331af71be31ef423cd2c9b8fe28accbd
This commit is contained in:
Robert Griesemer 2020-04-01 16:23:31 -07:00
parent 62683a7752
commit 6768c4f513
3 changed files with 90 additions and 2 deletions

View File

@ -41,7 +41,8 @@ import (
type Error struct {
Fset *token.FileSet // file set for interpretation of Pos
Pos token.Pos // error position
Msg string // error message
Msg string // default error message, user-friendly
Full string // full error message, for debugging (may contain internal details)
Soft bool // if set, error is "soft"
}
@ -51,6 +52,13 @@ func (err Error) Error() string {
return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg)
}
// FullError returns an error string like Error, buy it may contain
// type-checker internal details such as subscript indices for type
// parameters and more. Useful for debugging.
func (err Error) FullError() string {
return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Full)
}
// An Importer resolves import paths to Packages.
//
// CAUTION: This interface does not support the import of locally

View File

@ -12,6 +12,8 @@ import (
"go/token"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
func assert(p bool) {
@ -82,7 +84,7 @@ func (check *Checker) err(pos token.Pos, msg string, soft bool) {
return
}
err := Error{check.fset, pos, msg, soft}
err := Error{check.fset, pos, cleanMsg(msg), msg, soft}
if check.firstErr == nil {
check.firstErr = err
}
@ -121,3 +123,54 @@ func (check *Checker) invalidArg(pos token.Pos, format string, args ...interface
func (check *Checker) invalidOp(pos token.Pos, format string, args ...interface{}) {
check.errorf(pos, "invalid operation: "+format, args...)
}
// cleanMsg removes subscripts and replaces <>'s in instantiated type names with ()'s.
func cleanMsg(s string) string {
var b strings.Builder
var p rune // previous rune
n := 0 // nesting level
copy := false // indicates that we need a copy
for i := 0; ; {
r, w := utf8.DecodeRuneInString(s[i:])
i += w
if r == utf8.RuneError {
if w == 0 {
break // we're done
}
if w == 1 {
continue // ignore (this should never happen)
}
}
// strip subscript digits
if '₀' <= r && r < '₀'+10 { // '₀' == U+2080
copy = true
continue
}
// replace <>'s in instantiated type names by ()'s
// (use previous rune p and nesting level n for more
// accurate replacement)
if r == '<' && isIdentChar(p) {
n++
copy = true
r = '('
} else if r == '>' && n > 0 {
n--
copy = true
r = ')'
}
b.WriteRune(r)
p = r
}
if copy {
return b.String()
}
return s
}
func isIdentChar(r rune) bool {
return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_'
}

View File

@ -0,0 +1,27 @@
// 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 types
import "testing"
func TestCleanMsg(t *testing.T) {
for _, test := range []struct {
in, want string
}{
{"", ""},
{" ", " "},
{"foo", "foo"},
{"foo<T>", "foo(T)"},
{"foo <T>", "foo <T>"},
{"foo << bar", "foo << bar"},
{"foo₀", "foo"},
{"foo<T₀>", "foo(T)"},
} {
got := cleanMsg(test.in)
if got != test.want {
t.Errorf("%q: got %q; want %q", test.in, got, test.want)
}
}
}