cue/errors: make List implement Error
The semants is for List to adopt the features of the first
error. This is consistent with the default error message
being printed.
Advantages:
- Allows improved type checking.
- Avoids the nil-error return issues for List.
- Simplifies combining code.
Also:
- get rid of E
- Implement: Promote, Append
- clean up code accordingly
- tighten type checking
Change-Id: Iea90ab001111d914aad93731d4ebb50e91865e1d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2480
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast.go b/cue/ast.go
index 2b5bab4..d203675 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -35,7 +35,7 @@
//
// There should be no unresolved identifiers in file, meaning the Node field
// of all identifiers should be set to a non-nil value.
-func (inst *Instance) insertFile(f *ast.File) error {
+func (inst *Instance) insertFile(f *ast.File) errors.Error {
// TODO: insert by converting to value first so that the trim command can
// also remove top-level fields.
// First process single file.
@@ -48,7 +48,8 @@
if v.errors.Len() > 0 {
return v.errors
}
- return &callError{result.(*bottom)}
+ v := newValueRoot(v.ctx(), result)
+ return v.toErr(result.(*bottom))
}
return nil
diff --git a/cue/build/import.go b/cue/build/import.go
index ab814e5..04866b2 100644
--- a/cue/build/import.go
+++ b/cue/build/import.go
@@ -26,6 +26,17 @@
type LoadFunc func(pos token.Pos, path string) *Instance
+type cueError = errors.Error
+
+type buildError struct {
+ cueError
+ inputs []token.Pos
+}
+
+func (e *buildError) InputPositions() []token.Pos {
+ return e.inputs
+}
+
func (inst *Instance) complete() errors.Error {
// TODO: handle case-insensitive collisions.
// dir := inst.Dir
@@ -65,7 +76,10 @@
for path := range imported {
paths = append(paths, path)
if path == "" {
- return errors.E(imported[path], "empty import path")
+ return &buildError{
+ errors.Newf(token.NoPos, "empty import path"),
+ imported[path],
+ }
}
}
diff --git a/cue/build/instance.go b/cue/build/instance.go
index c40cf1d..c599e8f 100644
--- a/cue/build/instance.go
+++ b/cue/build/instance.go
@@ -59,7 +59,7 @@
// The Err for loading this package or nil on success. This does not
// include any errors of dependencies. Incomplete will be set if there
// were any errors in dependencies.
- Err error
+ Err errors.Error
// Incomplete reports whether any dependencies had an error.
Incomplete bool
@@ -125,22 +125,7 @@
// ReportError reports an error processing this instance.
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)
- }
- list.Add(err)
- inst.Err = list
+ inst.Err = errors.Append(inst.Err, err)
}
// Context defines the build context for this instance. All files defined
@@ -181,18 +166,8 @@
file, err := parser.ParseFile(filename, src, parser.ParseComments)
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
- }
+ err := errors.Promote(err, "error adding file")
+ inst.ReportError(err)
return err
}
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 8c38812..7b9277b 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -148,31 +148,14 @@
}
}
-// 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
- }
+// Promote converts a regular Go error to an Error if it isn't already so.
+func Promote(err error, msg string) Error {
+ switch x := err.(type) {
+ case Error:
+ return x
+ default:
+ return Wrapf(err, token.NoPos, msg)
}
- return e
}
var _ Error = &posError{}
@@ -201,25 +184,70 @@
if e.err == nil {
return e.Message.Error()
}
+ if e.Message.format == "" {
+ return e.err.Error()
+ }
return fmt.Sprintf("%s: %s", e.Message.Error(), e.err)
}
+// Append combines two errors, flattening Lists as necessary.
+func Append(a, b Error) Error {
+ switch x := a.(type) {
+ case nil:
+ return b
+ case List:
+ return appendToList(x, b)
+ }
+ // Preserve order of errors.
+ list := appendToList(nil, a)
+ list = appendToList(list, b)
+ return list
+}
+
+// Errors reports the individual errors associated with an error, which is
+// the error itself if there is only one or, if the underlying type is List,
+// its individual elements. If the given error is not an Error, it will be
+// promoted to one.
+func Errors(err error) []Error {
+ switch x := err.(type) {
+ case nil:
+ return nil
+ case List:
+ return []Error(x)
+ case Error:
+ return []Error{x}
+ default:
+ return []Error{Promote(err, "")}
+ }
+}
+
+func appendToList(list List, err Error) List {
+ switch x := err.(type) {
+ case nil:
+ return list
+ case List:
+ if list == nil {
+ return x
+ }
+ return append(list, x...)
+ default:
+ return append(list, err)
+ }
+}
+
// 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)
-}
-
// 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}})
+ err := &posError{pos: pos, Message: Message{format: msg, args: args}}
+ *p = append(*p, err)
}
// Add adds an Error with given position and error message to an List.
func (p *List) Add(err Error) {
- p.add(err)
+ *p = appendToList(*p, err)
}
// Reset resets an List to no errors.
@@ -316,13 +344,44 @@
// An List implements the error interface.
func (p List) Error() string {
+ format, args := p.Msg()
+ return fmt.Sprintf(format, args...)
+}
+
+// Msg reports the unformatted error message for the first error, if any.
+func (p List) Msg() (format string, args []interface{}) {
switch len(p) {
case 0:
- return "no errors"
+ return "no errors", nil
case 1:
- return p[0].Error()
+ return p[0].Msg()
}
- return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1)
+ format, args = p[0].Msg()
+ return "%s (and %d more errors)", append(args, len(p)-1)
+}
+
+// Position reports the primary position for the first error, if any.
+func (p List) Position() token.Pos {
+ if len(p) == 0 {
+ return token.NoPos
+ }
+ return p[0].Position()
+}
+
+// InputPositions reports the input positions for the first error, if any.
+func (p List) InputPositions() []token.Pos {
+ if len(p) == 0 {
+ return nil
+ }
+ return p[0].InputPositions()
+}
+
+// Path reports the path location of the first error, if any.
+func (p List) Path() []string {
+ if len(p) == 0 {
+ return nil
+ }
+ return p[0].Path()
}
// Err returns an error equivalent to this error list.
@@ -356,12 +415,8 @@
if cfg == nil {
cfg = &Config{}
}
- if list, ok := err.(List); ok {
- for _, e := range list {
- printError(w, e, cfg)
- }
- } else if err != nil {
- printError(w, err, cfg)
+ for _, e := range Errors(err) {
+ printError(w, e, cfg)
}
}
diff --git a/cue/instance.go b/cue/instance.go
index 3a4eeec..8df4cda 100644
--- a/cue/instance.go
+++ b/cue/instance.go
@@ -20,7 +20,6 @@
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/errors"
- "cuelang.org/go/cue/token"
"cuelang.org/go/internal"
)
@@ -40,8 +39,8 @@
Dir string
Name string
- Incomplete bool // true if Pkg and all its dependencies are free of errors
- Err error // non-nil if the package had errors
+ Incomplete bool // true if Pkg and all its dependencies are free of errors
+ Err errors.Error // non-nil if the package had errors
inst *build.Instance
@@ -84,41 +83,14 @@
return i
}
-func (inst *Instance) setListOrError(err error) {
+func (inst *Instance) setListOrError(err errors.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"))
- }
+ inst.Err = errors.Append(inst.Err, err)
}
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
+ inst.Err = errors.Append(inst.Err, err)
}
func (inst *Instance) eval(ctx *context) evaluated {
diff --git a/cue/load/config.go b/cue/load/config.go
index 2da8efb..da4be24 100644
--- a/cue/load/config.go
+++ b/cue/load/config.go
@@ -144,16 +144,7 @@
func (c Config) newErrInstance(m *match, path string, err error) *build.Instance {
i := c.Context.NewInstance(path, nil)
i.DisplayPath = path
- switch x := err.(type) {
- case errors.Error:
- i.ReportError(x)
- case errors.List:
- for _, e := range x {
- i.ReportError(e)
- }
- default:
- i.ReportError(errors.Wrapf(err, token.NoPos, "instance"))
- }
+ i.ReportError(errors.Promote(err, "instance"))
return i
}
diff --git a/cue/load/import.go b/cue/load/import.go
index 9607252..b011fcb 100644
--- a/cue/load/import.go
+++ b/cue/load/import.go
@@ -358,16 +358,7 @@
pf, perr := parser.ParseFile(filename, data, parser.ImportsOnly, parser.ParseComments)
if perr != nil {
// should always be an errors.List, but just in case.
- switch x := perr.(type) {
- case errors.List:
- for _, e := range x {
- badFile(e)
- }
- case errors.Error:
- badFile(x)
- default:
- badFile(errors.Wrapf(err, token.NoPos, "add failed"))
- }
+ badFile(errors.Promote(perr, "add failed"))
return true
}
diff --git a/encoding/protobuf/protobuf.go b/encoding/protobuf/protobuf.go
index 17efb1e..8d180d9 100644
--- a/encoding/protobuf/protobuf.go
+++ b/encoding/protobuf/protobuf.go
@@ -118,12 +118,7 @@
}
func (b *Extractor) addErr(err error) {
- switch err := err.(type) {
- case errors.Error:
- b.errs.Add(err)
- default:
- b.errs.AddNewf(token.NoPos, "unknown error: %v", err)
- }
+ b.errs.Add(errors.Promote(err, "unknown error"))
}
// AddFile adds a proto definition file to be converted into CUE by the builder.