cue: improved error message

One of many steps to go.

The most notatble improvement here is that there is more than one location for an "empty disjunction" message.

Issue #129, Issue #52.

Change-Id: I8ff10e9a0be0eea685b9134c7804460249454a09
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3780
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/eval_expr.txt b/cmd/cue/cmd/testdata/script/eval_expr.txt
index 6475b28..4d7b08e 100644
--- a/cmd/cue/cmd/testdata/script/eval_expr.txt
+++ b/cmd/cue/cmd/testdata/script/eval_expr.txt
@@ -9,6 +9,7 @@
 -- expect-stderr --
 invalid non-ground value string (must be concrete int|string):
     ./partial.cue:7:9
+    ./partial.cue:8:7
 -- partial.cue --
 package partial
 
diff --git a/cmd/cue/cmd/testdata/script/vet_concrete.txt b/cmd/cue/cmd/testdata/script/vet_concrete.txt
index 6d557da..2aa0108 100644
--- a/cmd/cue/cmd/testdata/script/vet_concrete.txt
+++ b/cmd/cue/cmd/testdata/script/vet_concrete.txt
@@ -5,6 +5,7 @@
     ./partial.cue:4:6
 b.idx: invalid non-ground value string (must be concrete int|string):
     ./partial.cue:7:9
+    ./partial.cue:8:7
 b.str: incomplete value (string):
     ./partial.cue:8:7
 -- partial.cue --
diff --git a/cmd/cue/cmd/testdata/script/vet_expr.txt b/cmd/cue/cmd/testdata/script/vet_expr.txt
index 5381795..dabbe47 100644
--- a/cmd/cue/cmd/testdata/script/vet_expr.txt
+++ b/cmd/cue/cmd/testdata/script/vet_expr.txt
@@ -8,7 +8,7 @@
     ./data.yaml:13:11
     ./vet.cue:3:11
 field "skip" not allowed in closed struct:
-    ./vet.cue:1:9
+    ./data.yaml:20:7
 -- vet.cue --
 File :: {
 	translations <_>: {
diff --git a/cue/binop.go b/cue/binop.go
index 5a3ca19..278e251 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -684,7 +684,7 @@
 			if a.optional {
 				continue
 			}
-			return ctx.mkErr(src, y, "field %q not allowed in closed struct",
+			return ctx.mkErr(a.v, a.v, "field %q not allowed in closed struct",
 				ctx.labelStr(a.feature))
 		}
 		cp := ctx.copy(a.v)
diff --git a/cue/builtin.go b/cue/builtin.go
index d7fea05..4ce6c57 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -200,7 +200,7 @@
 		for iter.Next() {
 			d = append(d, dValue{iter.Value().path.v, false})
 		}
-		c.ret = &disjunction{baseValue{c.src}, d, false}
+		c.ret = &disjunction{baseValue{c.src}, d, nil, false}
 		if len(d) == 0 {
 			c.ret = errors.New("empty list in call to or")
 		}
diff --git a/cue/debug.go b/cue/debug.go
index 1eb5bff..3742770 100644
--- a/cue/debug.go
+++ b/cue/debug.go
@@ -476,11 +476,20 @@
 		write("_|_")
 		if x.value != nil || x.format != "" {
 			write("(")
-			if x.value != nil && p.showNodeRef {
-				p.str(x.value)
-				p.write(":")
+			errs := x.sub
+			if errs == nil {
+				errs = []*bottom{x}
 			}
-			write(x.msg())
+			for i, x := range errs {
+				if i > 0 {
+					p.write(";")
+				}
+				if x.value != nil && p.showNodeRef {
+					p.str(x.value)
+					p.write(":")
+				}
+				write(x.msg())
+			}
 			write(")")
 		}
 	case *top:
diff --git a/cue/errors.go b/cue/errors.go
index 98af40e..f9c05ab 100644
--- a/cue/errors.go
+++ b/cue/errors.go
@@ -50,14 +50,26 @@
 	return e.path
 }
 
-func (v Value) toErr(b *bottom) errors.Error {
-	if b.err != nil {
-		return b.err
+func (v Value) appendErr(err errors.Error, b *bottom) errors.Error {
+	switch {
+	case len(b.sub) > 0:
+		for _, b := range b.sub {
+			err = v.appendErr(err, b)
+		}
+		fallthrough
+	case b.err != nil:
+		err = errors.Append(err, b.err)
+	default:
+		err = errors.Append(err, &valueError{
+			v:   v,
+			err: b,
+		})
 	}
-	return &valueError{
-		v:   v,
-		err: b,
-	}
+	return err
+}
+
+func (v Value) toErr(b *bottom) (err errors.Error) {
+	return v.appendErr(nil, b)
 }
 
 var _ errors.Error = &valueError{}
@@ -77,7 +89,7 @@
 }
 
 func (e *valueError) InputPositions() []token.Pos {
-	return e.err.Positions()
+	return e.err.Positions(e.v.ctx())
 }
 
 func (e *valueError) Msg() (string, []interface{}) {
@@ -139,33 +151,33 @@
 	args      []interface{}
 
 	err     errors.Error // pass-through from higher-level API
+	sub     []*bottom    // sub errors
 	value   value
 	wrapped *bottom
 }
 
 func (x *bottom) kind() kind { return bottomKind }
 
-func (x *bottom) Positions() []token.Pos {
+func (x *bottom) Positions(ctx *context) []token.Pos {
 	if x.index != nil { // TODO: remove check?
-		return appendPositions(nil, x.pos)
+		return appendPositions(ctx, nil, x.pos)
 	}
 	return nil
 }
 
-func appendPositions(pos []token.Pos, src source) []token.Pos {
+func appendPositions(ctx *context, pos []token.Pos, src source) []token.Pos {
 	if src != nil {
-		if b, ok := src.(*binaryExpr); ok {
-			if _, isUnify := b.op.unifyType(); isUnify {
-				pos = appendPositions(pos, b.left)
-				pos = appendPositions(pos, b.right)
-			}
-		}
 		if p := src.Pos(); p != token.NoPos {
-			return append(pos, src.Pos())
+			pos = append(pos, src.Pos())
 		}
 		if c := src.computed(); c != nil {
-			pos = appendPositions(pos, c.x)
-			pos = appendPositions(pos, c.y)
+			pos = appendPositions(ctx, pos, c.x)
+			pos = appendPositions(ctx, pos, c.y)
+		}
+		switch x := src.(type) {
+		case evaluated:
+		case value:
+			pos = appendPositions(ctx, pos, x.evalPartial(ctx))
 		}
 	}
 	return pos
@@ -218,6 +230,8 @@
 			e.code = x
 		case *bottom:
 			e.wrapped = x
+		case []*bottom:
+			e.sub = x
 		case errors.Error:
 			e.err = x
 		case value:
diff --git a/cue/eval.go b/cue/eval.go
index 5950ef9..ba20f4a 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -355,7 +355,12 @@
 	if len(ctx.evalStack) > 1 {
 		ctx.inSum++
 	}
-	dn := &disjunction{x.baseValue, make([]dValue, 0, len(x.values)), x.hasDefaults}
+	dn := &disjunction{
+		x.baseValue,
+		make([]dValue, 0, len(x.values)),
+		make([]*bottom, 0, len(x.errors)),
+		x.hasDefaults,
+	}
 	changed := false
 	for _, v := range x.values {
 		n := v.val.evalPartial(ctx)
@@ -413,7 +418,7 @@
 		return values[0].val.evalPartial(ctx)
 	}
 
-	x = &disjunction{x.baseValue, values, true}
+	x = &disjunction{x.baseValue, values, x.errors, true}
 	return x.normalize(ctx, x).val
 }
 
diff --git a/cue/export.go b/cue/export.go
index 656b8ff..0604457 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -616,7 +616,18 @@
 	case *bottom:
 		err := &ast.BottomLit{}
 		if x.format != "" {
-			comment := &ast.Comment{Text: "// " + x.msg()}
+			msg := x.msg()
+			if len(x.sub) > 0 {
+				buf := strings.Builder{}
+				for i, b := range x.sub {
+					if i > 0 {
+						buf.WriteString("; ")
+						buf.WriteString(b.msg())
+					}
+				}
+				msg = buf.String()
+			}
+			comment := &ast.Comment{Text: "// " + msg}
 			err.AddComment(&ast.CommentGroup{
 				Line:     true,
 				Position: 2,
diff --git a/cue/export_test.go b/cue/export_test.go
index eed802c..3bf8181 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -858,6 +858,20 @@
 					})
 				})
 			}`),
+	}, {
+		eval: true,
+		in: `
+				a?: 1
+				b?: 2
+				b?: 2
+				c?: 3
+				c: 3`,
+		out: unindent(`
+		{
+			a?: 1
+			b?: 2
+			c:  3
+		}`),
 	}}
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {
diff --git a/cue/go.go b/cue/go.go
index b61026d..9c18208 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -668,6 +668,7 @@
 		values: []dValue{
 			{val: &nullLit{}, marked: nullIsDefault},
 			{val: e}},
+		errors:      nil,
 		hasDefaults: nullIsDefault,
 	}
 }
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 689a75b..b46ac22 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -1270,11 +1270,11 @@
 		`,
 		out: `<0>{` +
 			`S :: <1>{<>: <2>(_: string)-><3>C{a: int}, }, ` +
-			`a: <4>{<>: <5>(_: string)-><6>C{a: int}, v: _|_(<7>{<>: <5>(_: string)-><6>C{a: int}, v: <8>{b: int}}:field "b" not allowed in closed struct)}, ` +
-			`b: <9>{<>: <10>(_: string)->(<11>C{a: int} | <12>C{b: int}), w: _|_(<13>{<>: <10>(_: string)->(<11>C{a: int} | <12>C{b: int}), w: <14>{c: int}}:empty disjunction: field "c" not allowed in closed struct)}, ` +
-			`Q :: <15>{<>: <16>(_: string)->(<17>C{a: int} | <18>C{b: int}), }, ` +
-			`c: <19>{<>: <20>(_: string)->[<21>C{a: int},<22>C{b: int}], w: [_|_((<23>{d: int} & close(<24>C{a: int})):field "d" not allowed in closed struct),<25>C{b: int}]}, ` +
-			`R :: <26>{<>: <27>(_: string)->[<28>C{a: int},<29>C{b: int}], }}`,
+			`a: <4>{<>: <5>(_: string)-><6>C{a: int}, v: _|_(int:field "b" not allowed in closed struct)}, ` +
+			`b: <7>{<>: <8>(_: string)->(<9>C{a: int} | <10>C{b: int}), w: _|_(int:empty disjunction: field "c" not allowed in closed struct)}, ` +
+			`Q :: <11>{<>: <12>(_: string)->(<13>C{a: int} | <14>C{b: int}), }, ` +
+			`c: <15>{<>: <16>(_: string)->[<17>C{a: int},<18>C{b: int}], w: [_|_(int:field "d" not allowed in closed struct),<19>C{b: int}]}, ` +
+			`R :: <20>{<>: <21>(_: string)->[<22>C{a: int},<23>C{b: int}], }}`,
 	}, {
 		desc: "definitions with disjunctions",
 		in: `
@@ -1297,8 +1297,8 @@
 		out: `<0>{` +
 			`Foo :: (<1>C{field: int, a: 1} | <2>C{field: int, b: 2}), ` +
 			`foo: <3>C{field: int, a: 1}, ` +
-			`bar: _|_((<4>.Foo & <5>{c: 2}):empty disjunction: field "c" not allowed in closed struct), ` +
-			`baz: <6>C{field: int, b: 2}}`,
+			`bar: _|_(2:empty disjunction: field "c" not allowed in closed struct), ` +
+			`baz: <4>C{field: int, b: 2}}`,
 	}, {
 		desc: "definitions with disjunctions recurisive",
 		in: `
@@ -1787,7 +1787,7 @@
 			`a3: <3>{a: _|_((=~"oo" & "bar"):invalid value "bar" (does not match =~"oo")), b: =~"oo", c: =~"fo"}, ` +
 			`o1: <4>{a: string, b: string, c: "bar"}, ` +
 			`o2: <5>{a: "foo", b: string, c: "bar"}, ` +
-			`o3: <6>{a: _|_((or ([<7>.b,<7>.c]) & "foo"):empty disjunction: conflicting values "baz" and "foo"), b: "baz", c: "bar"}}`,
+			`o3: <6>{a: _|_(("baz" & "foo"):empty disjunction: conflicting values "baz" and "foo";("bar" & "foo"):empty disjunction: conflicting values "bar" and "foo"), b: "baz", c: "bar"}}`,
 	}, {
 		desc: "self-reference cycles conflicts with strings",
 		in: `
@@ -2396,7 +2396,7 @@
 			x: {a:1}|{a:2}
 			y: x & {a:3}
 		`,
-		out: `<0>{x: (<1>{a: 1} | <2>{a: 2}), y: _|_((<3>.x & <4>{a: 3}):empty disjunction: {a: (1 & 3)})}`,
+		out: `<0>{x: (<1>{a: 1} | <2>{a: 2}), y: _|_((1 & 3):empty disjunction: conflicting values 1 and 3;(2 & 3):empty disjunction: conflicting values 2 and 3)}`,
 	}, {
 		desc: "cannot resolve references that would be ambiguous",
 		in: `
diff --git a/cue/rewrite.go b/cue/rewrite.go
index 3a2b477..3f81daf 100644
--- a/cue/rewrite.go
+++ b/cue/rewrite.go
@@ -222,7 +222,7 @@
 	if !changed {
 		return x
 	}
-	return &disjunction{x.baseValue, values, x.hasDefaults}
+	return &disjunction{x.baseValue, values, x.errors, x.hasDefaults}
 }
 
 func (x *listComprehension) rewrite(ctx *context, fn rewriteFunc) value {
diff --git a/cue/value.go b/cue/value.go
index 17ef829..495f4d5 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -1317,6 +1317,11 @@
 
 	values []dValue
 
+	// errors is used to keep track of all errors that occurred in
+	// a disjunction for better error reporting down the road.
+	// TODO: consider storing the errors in values.
+	errors []*bottom
+
 	hasDefaults bool // also true if it had elminated defaults.
 
 	// bind is the node that a successful disjunction will bind to. This
@@ -1347,6 +1352,9 @@
 // add add a value to the disjunction. It is assumed not to be a disjunction.
 func (x *disjunction) add(ctx *context, v value, marked bool) {
 	x.values = append(x.values, dValue{v, marked})
+	if b, ok := v.(*bottom); ok {
+		x.errors = append(x.errors, b)
+	}
 }
 
 // normalize removes redundant element from unification.
@@ -1370,11 +1378,12 @@
 outer:
 	for i, v := range x.values {
 		// TODO: this is pre-evaluation is quite aggressive. Verify whether
-		// this does not trigger structural cycles. If so, this can check for
+		// this does not trigger structural cycles (it does). If so, this can check for
 		// bottom and the validation can be delayed to as late as picking
 		// defaults. The drawback of this approach is that printed intermediate
 		// results will not look great.
 		if err := validate(ctx, v.val); err != nil {
+			x.errors = append(x.errors, err)
 			if v.marked {
 				markedErr = err
 			}
@@ -1419,7 +1428,7 @@
 			// TODO: use format instead of debugStr.
 			err = ctx.mkErr(src, ctx.str(err))
 		}
-		return mVal{ctx.mkErr(src, "empty disjunction: %v", err), false}
+		return mVal{x.computeError(ctx, src), false}
 	case 1:
 		v := x.values[0]
 		return mVal{v.val.(evaluated), v.marked}
@@ -1429,6 +1438,45 @@
 	return mVal{x, false}
 }
 
+func (x *disjunction) computeError(ctx *context, src source) evaluated {
+	var errors []*bottom
+
+	// Ensure every position is visited at least once.
+	// This prevents discriminators fields from showing up too much. A special
+	// "all errors" flag could be used to expand all errors.
+	visited := map[token.Pos]bool{}
+
+	for _, b := range x.errors {
+		positions := b.Positions(ctx)
+		if len(positions) == 0 {
+			positions = append(positions, token.NoPos)
+		}
+		// Include the error if at least one of its positions wasn't covered
+		// before.
+		done := true
+		for _, p := range positions {
+			if !visited[p] {
+				done = false
+			}
+			visited[p] = true
+		}
+		if !done {
+			b := *b
+			b.format = "empty disjunction: " + b.format
+			errors = append(errors, &b)
+		}
+	}
+	switch len(errors) {
+	case 0:
+		// Should never happen.
+		return ctx.mkErr(src, errors, "empty disjunction")
+	case 1:
+		return ctx.mkErr(src, errors, "empty disjunction: %v", errors[0])
+	default:
+		return ctx.mkErr(src, errors, "empty disjunction: %v (and %d other errors)", errors[0], len(errors)-1)
+	}
+}
+
 type listComprehension struct {
 	baseValue
 	clauses yielder
diff --git a/doc/tutorial/basics/2_types/80_lists.txt b/doc/tutorial/basics/2_types/80_lists.txt
index 652f118..6f0e137 100644
--- a/doc/tutorial/basics/2_types/80_lists.txt
+++ b/doc/tutorial/basics/2_types/80_lists.txt
@@ -40,4 +40,4 @@
 IP: [uint8, uint8, uint8, uint8]
 PrivateIP: [10, uint8, uint8, uint8] | [192, 168, uint8, uint8] | [172, >=16 & <=32 & uint8, uint8, uint8]
 myIP: [10, 2, 3, 4]
-yourIP: _|_ // empty disjunction: [((10 & (int & >=0 & int & <=255)) & 11),((int & >=0 & int & <=255) & 1),((int & >=0 & int & <=255) & 2),((int & >=0 & int & <=255) & 3)]
+yourIP: _|_ // ; empty disjunction: conflicting values 192 and 11; empty disjunction: conflicting values 172 and 11