cue/errors: make API more suitable for localization
- change AddNew to AddNewf
- Add accepts Error, not error
- various helper funcs changed to f version
- whole-string error messages, instead of computed
Issue #52
Change-Id: Iff30a2dfd813c56770efe466b83da94cd3a23873
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2216
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/build.go b/cue/build.go
index 69bd84b..7d775c3 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -20,6 +20,7 @@
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
)
@@ -235,7 +236,7 @@
return n.Pos().String()
}
-func resolveFiles(idx *index, p *build.Instance) error {
+func resolveFiles(idx *index, p *build.Instance) errors.Error {
// Link top-level declarations. As top-level entries get unified, an entry
// may be linked to any top-level entry of any of the files.
allFields := map[string]ast.Node{}
@@ -256,7 +257,7 @@
return nil
}
-func resolveFile(idx *index, f *ast.File, p *build.Instance, allFields map[string]ast.Node) error {
+func resolveFile(idx *index, f *ast.File, p *build.Instance, allFields map[string]ast.Node) errors.Error {
index := map[string][]*ast.Ident{}
for _, u := range f.Unresolved {
index[u.Name] = append(index[u.Name], u)
@@ -269,7 +270,7 @@
}
}
}
- var errUnused error
+ var errUnused errors.Error
for _, spec := range f.Imports {
id, err := strconv.Unquote(spec.Path.Value)
diff --git a/cue/build/context.go b/cue/build/context.go
index 2065c96..4f810e8 100644
--- a/cue/build/context.go
+++ b/cue/build/context.go
@@ -65,10 +65,13 @@
err := inst.complete()
if err != nil {
- inst.Err = err
- inst.Incomplete = true
+ inst.ReportError(err)
}
- return err
+ if inst.Err != nil {
+ inst.Incomplete = true
+ return inst.Err
+ }
+ return nil
}
func (c *Context) init() {
diff --git a/cue/build/import.go b/cue/build/import.go
index d63f7e4..ab814e5 100644
--- a/cue/build/import.go
+++ b/cue/build/import.go
@@ -26,7 +26,7 @@
type LoadFunc func(pos token.Pos, path string) *Instance
-func (inst *Instance) complete() error {
+func (inst *Instance) complete() errors.Error {
// TODO: handle case-insensitive collisions.
// dir := inst.Dir
// names := []string{}
@@ -89,7 +89,7 @@
continue
}
if imp.Err != nil {
- return imp.Err
+ return nil
}
imp.ImportPath = path
// imp.parent = inst
diff --git a/cue/build/instance.go b/cue/build/instance.go
index f0407c0..7824b4e 100644
--- a/cue/build/instance.go
+++ b/cue/build/instance.go
@@ -15,7 +15,6 @@
package build
import (
- "fmt"
pathpkg "path"
"path/filepath"
"strings"
@@ -116,13 +115,6 @@
return filepath.Join(inst.Root, path)
}
-func (inst *Instance) chkErr(err error) error {
- if err != nil {
- inst.ReportError(err)
- }
- return err
-}
-
func (inst *Instance) setPkg(pkg string) bool {
if !inst.hasName {
inst.hasName = true
@@ -133,14 +125,23 @@
}
// ReportError reports an error processing this instance.
-func (inst *Instance) ReportError(err error) {
- if inst.Err == nil {
+func (inst *Instance) ReportError(err errors.Error) {
+ var list errors.List
+ switch x := inst.Err.(type) {
+ default:
+ // Should never happen, but in the worst case at least one error is
+ // recorded.
+ return
+ case nil:
inst.Err = err
+ return
+ case errors.List:
+ list = x
+ case errors.Error:
+ list.Add(x)
}
-}
-
-func (inst *Instance) errorf(pos token.Pos, format string, args ...interface{}) error {
- return inst.chkErr(errors.E(pos, fmt.Sprintf(format, args...)))
+ list.Add(err)
+ inst.Err = list
}
// Context defines the build context for this instance. All files defined
@@ -180,15 +181,33 @@
func (inst *Instance) AddFile(filename string, src interface{}) error {
c := inst.ctxt
file, err := parser.ParseFile(filename, src, c.parseOptions...)
- if err == nil {
- err = inst.addSyntax(file)
+ if err != nil {
+ // should always be an errors.List, but just in case.
+ switch x := err.(type) {
+ case errors.List:
+ for _, e := range x {
+ inst.ReportError(e)
+ }
+ case errors.Error:
+ inst.ReportError(x)
+ default:
+ e := errors.Wrapf(err, token.NoPos, "error adding file")
+ inst.ReportError(e)
+ err = e
+ }
+ return err
}
- return inst.chkErr(err)
+
+ if err := inst.addSyntax(file); err != nil {
+ inst.ReportError(err)
+ return err
+ }
+ return nil
}
// addSyntax adds the given file to list of files for this instance. The package
// name of the file must match the package name of the instance.
-func (inst *Instance) addSyntax(file *ast.File) error {
+func (inst *Instance) addSyntax(file *ast.File) errors.Error {
pkg := ""
pos := file.Pos()
if file.Name != nil {
@@ -196,7 +215,7 @@
pos = file.Name.Pos()
}
if !inst.setPkg(pkg) && pkg != inst.PkgName {
- return inst.errorf(pos,
+ return errors.Newf(pos,
"package name %q conflicts with previous package name %q",
pkg, inst.PkgName)
}
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 4f31959..e5e1a72 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -13,27 +13,33 @@
// limitations under the License.
// Package errors defines shared types for handling CUE errors.
+//
+// The pivotal error type in CUE packages is the interface type Error.
+// The information available in such errors can be most easily retrieved using
+// the Path, Positions, and Print functions.
package errors // import "cuelang.org/go/cue/errors"
import (
+ "errors"
+ "fmt"
"io"
"sort"
"strings"
"cuelang.org/go/cue/token"
"github.com/mpvl/unique"
- "golang.org/x/exp/errors"
- "golang.org/x/exp/errors/fmt"
"golang.org/x/xerrors"
)
// New is a convenience wrapper for errors.New in the core library.
+// It does not return a CUE error.
func New(msg string) error {
return errors.New(msg)
}
// A Message implements the error interface as well as Message to allow
-// internationalized messages.
+// internationalized messages. A Message is typically used as an embedding
+// in a CUE message.
type Message struct {
format string
args []interface{}
@@ -141,6 +147,33 @@
}
}
+// E creates a new error. The following types correspond to the following
+// methods:
+// arg type maps to
+// Message Msg
+// string shorthand for a format message with no arguments
+// token.Pos Position
+// []token.Pos Positions
+// error Unwrap/Cause
+func E(args ...interface{}) Error {
+ e := &posError{}
+ for _, a := range args {
+ switch x := a.(type) {
+ case string:
+ e.Message = NewMessage(x, nil)
+ case Message:
+ e.Message = x
+ case token.Pos:
+ e.pos = x
+ case []token.Pos:
+ e.inputs = x
+ case error:
+ e.err = x
+ }
+ }
+ return e
+}
+
var _ Error = &posError{}
// In an List, an error is represented by an *posError.
@@ -148,122 +181,41 @@
// the offending token, and the error condition is described
// by Msg.
type posError struct {
- pos token.Pos
+ pos token.Pos
+ inputs []token.Pos
Message
// The underlying error that triggered this one, if any.
err error
}
-func (p *posError) Path() []string {
- return Path(p.err)
-}
-
-func (p *posError) InputPositions() []token.Pos { return nil }
-
-// E creates a new error.
-func E(args ...interface{}) error {
- e := &posError{}
- update(e, args)
- return e
-}
-
-func update(e *posError, args []interface{}) {
- err := e.err
- for _, a := range args {
- switch x := a.(type) {
- case string:
- e.Message = NewMessage("%s", []interface{}{x})
- case token.Pos:
- e.pos = x
- case []token.Pos:
- // TODO: do something more clever
- if len(x) > 0 {
- e.pos = x[0]
- }
- case *posError:
- copy := *x
- err = ©
- e.err = combine(e.err, err)
- case error:
- e.err = combine(e.err, x)
- }
- }
-}
-
-func combine(a, b error) error {
- switch x := a.(type) {
- case nil:
- return b
- case List:
- x.add(toErr(b))
- return x
- default:
- return List{toErr(a), toErr(b)}
- }
-}
-
-func toErr(err error) Error {
- if e, ok := err.(Error); ok {
- return e
- }
- return &posError{err: err}
-}
-
-func (e *posError) Position() token.Pos {
- return e.pos
-}
+func (e *posError) Path() []string { return Path(e.err) }
+func (e *posError) InputPositions() []token.Pos { return e.inputs }
+func (e *posError) Position() token.Pos { return e.pos }
+func (e *posError) Unwrap() error { return e.err }
+func (e *posError) Cause() error { return e.err }
// Error implements the error interface.
-func (e *posError) Error() string { return fmt.Sprint(e) }
-
-func (e *posError) FormatError(p errors.Printer) error {
- next := e.err
- if format, args := e.Msg(); format == "" {
- next = errFormat(p, e.err)
- } else {
- p.Printf(format, args...)
- }
- if p.Detail() && e.pos.Filename() != "" || e.pos.IsValid() {
- p.Printf("%s", e.pos.String())
- }
- return next
-
+func (e *posError) Error() string {
+ return e.Message.Error()
}
-func (e posError) Unwrap() error {
- return e.err
-}
-
-func errFormat(p errors.Printer, err error) (next error) {
- switch v := err.(type) {
- case errors.Formatter:
- err = v.FormatError(p)
- default:
- p.Print(err)
- err = nil
- }
-
- return err
-}
-
-// List is a list of *posError.
+// List is a list of Errors.
// The zero value for an List is an empty List ready to use.
-//
type List []Error
func (p *List) add(err Error) {
*p = append(*p, err)
}
-// AddNew adds an Error with given position and error message to an List.
-func (p *List) AddNew(pos token.Pos, msg string) {
- p.add(&posError{pos: pos, Message: Message{format: msg}})
+// AddNewf adds an Error with given position and error message to an List.
+func (p *List) AddNewf(pos token.Pos, msg string, args ...interface{}) {
+ p.add(&posError{pos: pos, Message: Message{format: msg, args: args}})
}
// Add adds an Error with given position and error message to an List.
-func (p *List) Add(err error) {
- p.add(toErr(err))
+func (p *List) Add(err Error) {
+ p.add(err)
}
// Reset resets an List to no errors.
@@ -341,7 +293,7 @@
// RemoveMultiples sorts an List and removes all but the first error per line.
func (p *List) RemoveMultiples() {
- sort.Sort(p)
+ p.Sort()
var last Error
i := 0
for _, e := range *p {
diff --git a/cue/errors/errors_test.go b/cue/errors/errors_test.go
index f670cd8..76c7e68 100644
--- a/cue/errors/errors_test.go
+++ b/cue/errors/errors_test.go
@@ -49,7 +49,7 @@
// TODO: Add test cases.
}
for _, tt := range tests {
- tt.p.AddNew(tt.args.pos, tt.args.msg)
+ tt.p.AddNewf(tt.args.pos, tt.args.msg)
}
}
diff --git a/cue/instance.go b/cue/instance.go
index b797327..f793d3c 100644
--- a/cue/instance.go
+++ b/cue/instance.go
@@ -19,6 +19,8 @@
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
"cuelang.org/go/internal"
"golang.org/x/exp/errors/fmt"
)
@@ -61,15 +63,47 @@
i.Dir = p.Dir
i.Name = p.PkgName
if p.Err != nil {
- i.setError(p.Err)
+ i.setListOrError(p.Err)
}
}
return i
}
-func (inst *Instance) setError(err error) {
- inst.Err = err
+func (inst *Instance) setListOrError(err error) {
inst.Incomplete = true
+ switch x := err.(type) {
+ case errors.List:
+ if inst.Err == nil {
+ inst.Err = x
+ return
+ }
+ for _, e := range x {
+ inst.setError(e)
+ }
+ case errors.Error:
+ inst.setError(x)
+ default:
+ inst.setError(errors.Wrapf(err, token.NoPos, "unknown error"))
+ }
+}
+
+func (inst *Instance) setError(err errors.Error) {
+ inst.Incomplete = true
+ var list errors.List
+ switch x := inst.Err.(type) {
+ default:
+ // Should never happen, but in the worst case at least one error is
+ // recorded.
+ return
+ case nil:
+ inst.Err = err
+ return
+ case errors.List:
+ list = x
+ case errors.Error:
+ list.Add(err)
+ }
+ inst.Err = list
}
func (inst *Instance) eval(ctx *context) evaluated {
@@ -218,9 +252,14 @@
}
i.scope = v.n
- i.Err = resolveFiles(idx, p)
+ if err := resolveFiles(idx, p); err != nil {
+ i.setError(err)
+ return i
+ }
for _, f := range p.Files {
- i.insertFile(f)
+ if err := i.insertFile(f); err != nil {
+ i.setListOrError(err)
+ }
}
i.complete = true
diff --git a/cue/load/config.go b/cue/load/config.go
index 78444a3..b2b7542 100644
--- a/cue/load/config.go
+++ b/cue/load/config.go
@@ -20,6 +20,7 @@
"runtime"
"cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
)
const (
@@ -121,7 +122,7 @@
return i
}
-func (c Config) newErrInstance(m *match, path string, err error) *build.Instance {
+func (c Config) newErrInstance(m *match, path string, err errors.Error) *build.Instance {
i := c.Context.NewInstance(path, nil)
i.DisplayPath = path
i.ReportError(err)
diff --git a/cue/load/errors.go b/cue/load/errors.go
index 6196e2c..3a573d3 100644
--- a/cue/load/errors.go
+++ b/cue/load/errors.go
@@ -20,6 +20,7 @@
"strings"
build "cuelang.org/go/cue/build"
+ "cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
)
@@ -53,17 +54,21 @@
// A packageError describes an error loading information about a package.
type packageError struct {
- ImportStack []string // shortest path from package named on command line to this one
- Pos token.Pos // position of error
- Err string // the error itself
- IsImportCycle bool `json:"-"` // the error is an import cycle
- Hard bool `json:"-"` // whether the error is soft or hard; soft errors are ignored in some places
+ ImportStack []string // shortest path from package named on command line to this one
+ Pos token.Pos // position of error
+ errors.Message // the error itself
+ IsImportCycle bool `json:"-"` // the error is an import cycle
+ Hard bool `json:"-"` // whether the error is soft or hard; soft errors are ignored in some places
}
+func (p *packageError) Position() token.Pos { return p.Pos }
+func (p *packageError) InputPositions() []token.Pos { return nil }
+func (p *packageError) Path() []string { return nil }
+
func (l *loader) errPkgf(importPos []token.Pos, format string, args ...interface{}) *packageError {
err := &packageError{
ImportStack: l.stk.Copy(),
- Err: fmt.Sprintf(format, args...),
+ Message: errors.NewMessage(format, args),
}
err.fillPos(l.cfg.Dir, importPos)
return err
@@ -75,20 +80,21 @@
}
}
+// TODO(localize)
func (p *packageError) Error() string {
// Import cycles deserve special treatment.
if p.IsImportCycle {
- return fmt.Sprintf("%s\npackage %s\n", p.Err, strings.Join(p.ImportStack, "\n\timports "))
+ return fmt.Sprintf("%s\npackage %s\n", p.Message, strings.Join(p.ImportStack, "\n\timports "))
}
if p.Pos.IsValid() {
// Omit import stack. The full path to the file where the error
// is the most important thing.
- return p.Pos.String() + ": " + p.Err
+ return p.Pos.String() + ": " + p.Message.Error()
}
if len(p.ImportStack) == 0 {
- return p.Err
+ return p.Message.Error()
}
- return "package " + strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err
+ return "package " + strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Message.Error()
}
// noCUEError is the error used by Import to describe a directory
@@ -101,14 +107,14 @@
Ignored bool // whether any Go files were ignored due to build tags
}
-// func (e *noCUEError) Error() string {
-// msg := "no buildable CUE config files in " + e.Dir
-// if e.Ignored {
-// msg += " (.cue files ignored due to build tags)"
-// }
-// return msg
-// }
+func (e *noCUEError) Position() token.Pos { return token.NoPos }
+func (e *noCUEError) InputPositions() []token.Pos { return nil }
+func (e *noCUEError) Path() []string { return nil }
+// TODO(localize)
+func (e *noCUEError) Msg() (string, []interface{}) { return e.Error(), nil }
+
+// TODO(localize)
func (e *noCUEError) Error() string {
// Count files beginning with _ and ., which we will pretend don't exist at all.
dummy := 0
@@ -142,7 +148,22 @@
Files []string // corresponding files: Files[i] declares package Packages[i]
}
+func (e *multiplePackageError) Position() token.Pos { return token.NoPos }
+func (e *multiplePackageError) InputPositions() []token.Pos { return nil }
+func (e *multiplePackageError) Path() []string { return nil }
+
+func (e *multiplePackageError) Msg() (string, []interface{}) {
+ return "found packages %s (%s) and %s (%s) in %s", []interface{}{
+ e.Packages[0],
+ e.Files[0],
+ e.Packages[1],
+ e.Files[1],
+ e.Dir,
+ }
+}
+
func (e *multiplePackageError) Error() string {
// Error string limited to two entries for compatibility.
- return fmt.Sprintf("found packages %s (%s) and %s (%s) in %s", e.Packages[0], e.Files[0], e.Packages[1], e.Files[1], e.Dir)
+ format, args := e.Msg()
+ return fmt.Sprintf(format, args...)
}
diff --git a/cue/load/import.go b/cue/load/import.go
index 89f0595..5de8585 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -109,7 +109,7 @@
for dir := p.Dir; ctxt.isDir(dir); {
files, err := ctxt.readDir(dir)
if err != nil {
- p.ReportError(err)
+ p.ReportError(errors.Wrapf(err, pos, "import failed reading dir %v", dir))
return p
}
rootFound := false
@@ -142,8 +142,10 @@
}
rewriteFiles(p, root, false)
- if err := fp.finalize(); err != nil {
- p.ReportError(err)
+ if errs := fp.finalize(); errs != nil {
+ for _, e := range errs {
+ p.ReportError(e)
+ }
return p
}
@@ -191,7 +193,7 @@
}
}
-func updateDirs(c *Config, p *build.Instance, path, srcDir string, mode importMode) error {
+func updateDirs(c *Config, p *build.Instance, path, srcDir string, mode importMode) errors.Error {
ctxt := &c.fileSystem
// path := p.ImportPath
if path == "" {
@@ -277,7 +279,7 @@
c *Config
pkg *build.Instance
- err error
+ err errors.List
}
func newFileProcessor(c *Config, p *build.Instance) *fileProcessor {
@@ -289,13 +291,14 @@
}
}
-func (fp *fileProcessor) finalize() error {
+func (fp *fileProcessor) finalize() errors.List {
p := fp.pkg
if fp.err != nil {
return fp.err
}
if len(p.CUEFiles) == 0 && !fp.c.DataFiles {
- return &noCUEError{Package: p, Dir: p.Dir, Ignored: len(p.IgnoredCUEFiles) > 0}
+ fp.err.Add(&noCUEError{Package: p, Dir: p.Dir, Ignored: len(p.IgnoredCUEFiles) > 0})
+ return fp.err
}
for tag := range fp.allTags {
@@ -319,17 +322,15 @@
ext := nameExt(name)
p := fp.pkg
- badFile := func(err error) bool {
- if fp.err == nil {
- fp.err = err
- }
+ badFile := func(err errors.Error) bool {
+ fp.err.Add(err)
p.InvalidCUEFiles = append(p.InvalidCUEFiles, fullPath)
return true
}
match, data, filename, err := matchFile(fp.c, dir, name, true, fp.allFiles, fp.allTags)
if err != nil {
- return badFile(err)
+ return badFile(errors.Wrapf(err, pos, "no match"))
}
if !match {
if ext == cueSuffix {
@@ -342,7 +343,18 @@
pf, err := parser.ParseFile(filename, data, parser.ImportsOnly, parser.ParseComments)
if err != nil {
- return badFile(err)
+ // should always be an errors.List, but just in case.
+ switch x := err.(type) {
+ case errors.List:
+ for _, e := range x {
+ badFile(e)
+ }
+ case errors.Error:
+ badFile(x)
+ default:
+ badFile(errors.Wrapf(err, token.NoPos, "error adding file"))
+ }
+ return true
}
pkg := ""
diff --git a/cue/load/loader.go b/cue/load/loader.go
index c9a3c78..74d842f 100644
--- a/cue/load/loader.go
+++ b/cue/load/loader.go
@@ -58,7 +58,8 @@
a := []*build.Instance{}
for _, m := range l.importPaths(args) {
if m.Err != nil {
- inst := c.newErrInstance(m, "", m.Err)
+ inst := c.newErrInstance(m, "",
+ errors.Wrapf(m.Err, token.NoPos, "no match"))
a = append(a, inst)
continue
}
@@ -110,7 +111,7 @@
for _, f := range files {
if !strings.HasSuffix(f, ".cue") {
return cfg.newErrInstance(nil, f,
- errors.New("named files must be .cue files"))
+ errors.Newf(pos, "named files must be .cue files"))
}
}
@@ -124,7 +125,8 @@
}
fi, err := os.Stat(path)
if err != nil {
- return cfg.newErrInstance(nil, path, err)
+ return cfg.newErrInstance(nil, path,
+ errors.Wrapf(err, pos, "could not find dir %s", path))
}
if fi.IsDir() {
return cfg.newErrInstance(nil, path,
@@ -136,11 +138,14 @@
// TODO: ModImportFromFiles(files)
_, err := filepath.Abs(cfg.Dir)
if err != nil {
- return cfg.newErrInstance(nil, cfg.Dir, err)
+ return cfg.newErrInstance(nil, cfg.Dir,
+ errors.Wrapf(err, pos, "could convert '%s' to absolute path", cfg.Dir))
}
pkg.Dir = cfg.Dir
rewriteFiles(pkg, pkg.Dir, true)
- err = fp.finalize() // ImportDir(&ctxt, dir, 0)
+ for _, err := range fp.finalize() { // ImportDir(&ctxt, dir, 0)
+ pkg.ReportError(err)
+ }
// TODO: Support module importing.
// if ModDirImportPath != nil {
// // Use the effective import path of the directory
diff --git a/cue/parser/interface.go b/cue/parser/interface.go
index 758e27f..eea578f 100644
--- a/cue/parser/interface.go
+++ b/cue/parser/interface.go
@@ -170,7 +170,7 @@
return nil, pp.errors
}
f.Filename = filename
- resolve(f, pp.error)
+ resolve(f, pp.errf)
return
}
@@ -216,7 +216,7 @@
p.errors.Sort()
return nil, p.errors.Err()
}
- resolveExpr(e, p.error)
+ resolveExpr(e, p.errf)
return e, nil
}
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 630e00a..d0a2c63 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -71,7 +71,7 @@
m = scanner.ScanComments
}
eh := func(pos token.Pos, msg string, args []interface{}) {
- p.errors.AddNew(pos, fmt.Sprintf(msg, args...))
+ p.errors.AddNewf(pos, msg, args...)
}
p.scanner.Init(p.file, src, eh, m)
@@ -344,7 +344,7 @@
}
}
-func (p *parser) error(pos token.Pos, msg string) {
+func (p *parser) errf(pos token.Pos, msg string, args ...interface{}) {
// ePos := p.file.Position(pos)
ePos := pos
@@ -362,24 +362,26 @@
}
}
- p.errors.AddNew(ePos, msg)
+ p.errors.AddNewf(ePos, msg, args...)
}
-func (p *parser) errorExpected(pos token.Pos, msg string) {
- msg = "expected " + msg
- if pos == p.pos {
- // the error happened at the current position;
- // make the error message more specific
- if p.tok == token.COMMA && p.lit == "\n" {
- msg += ", found newline"
- } else {
- msg += ", found '" + p.tok.String() + "'"
- if p.tok.IsLiteral() {
- msg += " " + p.lit
- }
- }
+func (p *parser) errorExpected(pos token.Pos, obj string) {
+ if pos != p.pos {
+ p.errf(pos, "expected %s", obj)
+ return
}
- p.error(pos, msg)
+ // the error happened at the current position;
+ // make the error message more specific
+ if p.tok == token.COMMA && p.lit == "\n" {
+ p.errf(pos, "expected %s, found newline", obj)
+ return
+ }
+
+ if p.tok.IsLiteral() {
+ p.errf(pos, "expected %s, found '%s'", obj, p.tok)
+ } else {
+ p.errf(pos, "expected %s, found '%s' %s", obj, p.tok, p.lit)
+ }
}
func (p *parser) expect(tok token.Token) token.Pos {
@@ -395,7 +397,7 @@
// for the common case of a missing comma before a newline.
func (p *parser) expectClosing(tok token.Token, context string) token.Pos {
if p.tok != tok && p.tok == token.COMMA && p.lit == "\n" {
- p.error(p.pos, "missing ',' before newline in "+context)
+ p.errf(p.pos, "missing ',' before newline in %s", context)
p.next()
}
return p.expect(tok)
@@ -423,12 +425,12 @@
return false
}
}
- msg := "missing ','"
// TODO: find a way to detect crossing lines now we don't have a semi.
if p.lit == "\n" {
- msg += " before newline"
+ p.errf(p.pos, "missing ',' before newline")
+ } else {
+ p.errf(p.pos, "missing ',' in %s", context)
}
- p.error(p.pos, msg+" in "+context)
return true // "insert" comma and continue
}
@@ -673,7 +675,7 @@
d := p.parseField(allowEmit)
if e, ok := d.(*ast.EmitDecl); ok {
if origEmit && !allowEmit {
- p.error(p.pos, "only one emit allowed at top level")
+ p.errf(p.pos, "only one emit allowed at top level")
}
if !origEmit || !allowEmit {
d = &ast.BadDecl{From: e.Pos(), To: e.End()}
@@ -710,7 +712,7 @@
if !ok {
if !allowEmit {
- p.error(pos, "expected label, found "+tok.String())
+ p.errf(pos, "expected label, found %s", tok)
}
if expr == nil {
expr = p.parseExpr()
@@ -798,7 +800,7 @@
case token.FOR, token.IF:
if !allowComprehension {
- p.error(p.pos, "comprehension not alowed for this field")
+ p.errf(p.pos, "comprehension not alowed for this field")
}
clauses := p.parseComprehensionClauses()
return &ast.ComprehensionDecl{
@@ -972,7 +974,7 @@
if clauses := p.parseComprehensionClauses(); clauses != nil {
var expr ast.Expr
if len(elts) != 1 {
- p.error(lbrack.Add(1), "list comprehension must have exactly one element")
+ p.errf(lbrack.Add(1), "list comprehension must have exactly one element")
}
if len(elts) > 0 {
expr = elts[0]
@@ -1028,7 +1030,7 @@
if p.tok == token.RBRACK || p.tok == token.FOR || p.tok == token.IF {
break
}
- p.error(p.pos, "missing ',' before newline in list literal")
+ p.errf(p.pos, "missing ',' before newline in list literal")
} else if !p.atComma("list literal", token.RBRACK, token.FOR, token.IF) {
break
}
@@ -1217,7 +1219,7 @@
cc = p.openComments()
if p.tok != token.RPAREN {
- p.error(p.pos, "expected ')' for string interpolation")
+ p.errf(p.pos, "expected ')' for string interpolation")
}
lit = p.scanner.ResumeInterpolation()
pos = p.pos
@@ -1284,7 +1286,7 @@
if p.tok == token.STRING {
path = p.lit
if !isValidImport(path) {
- p.error(pos, "invalid import path: "+path)
+ p.errf(pos, "invalid import path: %s", path)
}
p.next()
p.expectComma() // call before accessing p.linecomment
@@ -1362,7 +1364,7 @@
p.expect(token.IDENT)
name = p.parseIdent()
if name.Name == "_" && p.mode&declarationErrorsMode != 0 {
- p.error(p.pos, "invalid package name _")
+ p.errf(p.pos, "invalid package name _")
}
p.expectComma()
} else {
diff --git a/cue/parser/resolve.go b/cue/parser/resolve.go
index b7be33d..fd17c1e 100644
--- a/cue/parser/resolve.go
+++ b/cue/parser/resolve.go
@@ -26,11 +26,11 @@
// resolve resolves all identifiers in a file. Unresolved identifiers are
// recorded in Unresolved.
-func resolve(f *ast.File, errFn func(pos token.Pos, msg string)) {
+func resolve(f *ast.File, errFn func(pos token.Pos, msg string, args ...interface{})) {
walk(&scope{errFn: errFn}, f)
}
-func resolveExpr(e ast.Expr, errFn func(pos token.Pos, msg string)) {
+func resolveExpr(e ast.Expr, errFn func(pos token.Pos, msg string, args ...interface{})) {
f := &ast.File{}
walk(&scope{file: f, errFn: errFn}, e)
}
@@ -45,7 +45,7 @@
node ast.Node
index map[string]ast.Node
- errFn func(p token.Pos, msg string)
+ errFn func(p token.Pos, msg string, args ...interface{})
}
func newScope(f *ast.File, outer *scope, node ast.Node, decls []ast.Decl) *scope {
diff --git a/cue/scanner/scanner_test.go b/cue/scanner/scanner_test.go
index 122ddf0..cc5c433 100644
--- a/cue/scanner/scanner_test.go
+++ b/cue/scanner/scanner_test.go
@@ -648,7 +648,7 @@
var list errors.List
eh := func(pos token.Pos, msg string, args []interface{}) {
- list.AddNew(pos, fmt.Sprintf(msg, args...))
+ list.AddNewf(pos, msg, args...)
}
var s Scanner