cue/errors: add path info to Error
Also fixes RemoveMultiples so that multiple errors
can be retained and shown.
Note that the path is not part of the standard error
message and where it is, tests often only test for
substrings. This is why not many of the tests changed.
Change-Id: Ibf8d7bf719608216cbaddb6b6c8d58d3d1685c57
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2207
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/testdata/partial/eval_conc.out b/cmd/cue/cmd/testdata/partial/eval_conc.out
index 3c83dc1..f2b417e 100644
--- a/cmd/cue/cmd/testdata/partial/eval_conc.out
+++ b/cmd/cue/cmd/testdata/partial/eval_conc.out
@@ -1,2 +1,6 @@
-more than one element remaining (1 and 2):
+sum:more than one element remaining (1 and 2):
./testdata/partial/partial.cue:4:6
+b.idx:: invalid non-ground value string (must be concrete int|string):
+ ./testdata/partial/partial.cue:7:7
+b.str:incomplete value (string):
+ ./testdata/partial/partial.cue:8:7
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 717cc89..652c0cd 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -18,6 +18,7 @@
import (
"io"
"sort"
+ "strings"
"cuelang.org/go/cue/token"
"github.com/mpvl/unique"
@@ -66,6 +67,19 @@
// Error reports the error message without position information.
Error() string
+
+ // Path returns the path into the data tree where the error occurred.
+ // This path may be nil if the error is not associated with such a location.
+ Path() []string
+}
+
+// Path returns the path of an Error if err is of that type.
+func Path(err error) []string {
+ e, ok := err.(Error)
+ if !ok {
+ return nil
+ }
+ return e.Path()
}
// // TODO: make Error an interface that returns a list of positions.
@@ -102,6 +116,10 @@
err error
}
+func (p *posError) Path() []string {
+ return Path(p.err)
+}
+
// E creates a new error.
func E(args ...interface{}) error {
e := &posError{}
@@ -229,9 +247,36 @@
if e.Column() != f.Column() {
return e.Column() < f.Column()
}
+ if !equalPath(p[i].Path(), p[j].Path()) {
+ return lessPath(p[i].Path(), p[j].Path())
+ }
return p[i].Error() < p[j].Error()
}
+func lessPath(a, b []string) bool {
+ for i, x := range a {
+ if i >= len(b) {
+ return false
+ }
+ if x != b[i] {
+ return x < b[i]
+ }
+ }
+ return len(a) < len(b)
+}
+
+func equalPath(a, b []string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for i, x := range a {
+ if x != b[i] {
+ return false
+ }
+ }
+ return true
+}
+
// Sort sorts an List. *posError entries are sorted by position,
// other errors are sorted by error message, and before any *posError
// entry.
@@ -243,12 +288,15 @@
// RemoveMultiples sorts an List and removes all but the first error per line.
func (p *List) RemoveMultiples() {
sort.Sort(p)
- var last token.Pos // initial last.Line is != any legal error line
+ var last Error
i := 0
for _, e := range *p {
pos := e.Position()
- if pos.Filename() != last.Filename() || pos.Line() != last.Line() {
- last = pos
+ if last == nil ||
+ pos.Filename() != last.Position().Filename() ||
+ pos.Line() != last.Position().Line() ||
+ !equalPath(e.Path(), last.Path()) {
+ last = e
(*p)[i] = e
i++
}
@@ -313,6 +361,10 @@
}
}
+ if p := Path(err); p != nil {
+ fmt.Fprintf(w, "%v:", strings.Join(p, "."))
+ }
+
if len(positions) == 0 {
fmt.Fprintln(w, err)
return
diff --git a/cue/types.go b/cue/types.go
index 046a3c5..4425930 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -1345,9 +1345,8 @@
}, nil)
if len(list) > 0 {
list.Sort()
- // list.RemoveMultiples() // TODO: use RemoveMultiples when it is fixed
- // return list
- return list[0]
+ list.RemoveMultiples() // TODO: use RemoveMultiples when it is fixed
+ return list
}
return nil
}