cue/errors: collect more positions per error

Issue #52

Change-Id: I71105ba23b737401d3b0d4ba1c13909849fa82f8
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2171
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/errors.go b/cue/errors.go
index 5ffa449..c74708f 100644
--- a/cue/errors.go
+++ b/cue/errors.go
@@ -90,6 +90,12 @@
 
 func appendPositions(pos []token.Pos, src source) []token.Pos {
 	if src != nil {
+		if b, ok := src.(*binaryExpr); ok {
+			if b.op == opUnify {
+				pos = appendPositions(pos, b.left)
+				pos = appendPositions(pos, b.right)
+			}
+		}
 		if p := src.Pos(); p != token.NoPos {
 			return append(pos, src.Pos())
 		}
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index 949da11..f1b1e70 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -19,6 +19,8 @@
 	"io"
 	"sort"
 
+	"github.com/mpvl/unique"
+
 	"cuelang.org/go/cue/token"
 	"golang.org/x/exp/errors"
 	"golang.org/x/exp/errors/fmt"
@@ -248,37 +250,44 @@
 func Print(w io.Writer, err error) {
 	if list, ok := err.(List); ok {
 		for _, e := range list {
-			printError(w, e)
+			printErrors(w, e)
 		}
 	} else if err != nil {
-		printError(w, toErr(err))
+		printErrors(w, err)
+	}
+}
+
+func printErrors(w io.Writer, err error) {
+	for ; err != nil; err = xerrors.Unwrap(err) {
+		printError(w, err)
 	}
 }
 
 func printError(w io.Writer, err error) {
-	fmt.Fprintf(w, "%v", err)
-	printedColon := false
-	for ; err != nil; err = xerrors.Unwrap(err) {
-		switch x := err.(type) {
-		case interface{ Position() token.Position }:
-			if pos := x.Position().String(); pos != "-" {
-				if !printedColon {
-					fmt.Fprint(w, ":")
-					printedColon = true
-				}
-				fmt.Fprintf(w, "\n    %v", pos)
-			}
-		case interface{ Positions() []token.Pos }:
-			for _, p := range x.Positions() {
-				if p.IsValid() {
-					if !printedColon {
-						fmt.Fprint(w, ":")
-						printedColon = true
-					}
-					fmt.Fprintf(w, "\n    %v", p)
-				}
+	positions := []string{}
+	switch x := err.(type) {
+	case interface{ Position() token.Position }:
+		if pos := x.Position().String(); pos != "-" {
+			positions = append(positions, pos)
+		}
+	case interface{ Positions() []token.Pos }:
+		for _, p := range x.Positions() {
+			if p.IsValid() {
+				positions = append(positions, p.String())
 			}
 		}
 	}
+
+	if len(positions) == 0 {
+		fmt.Fprintln(w, err)
+		return
+	}
+
+	unique.Strings(&positions)
+
+	fmt.Fprintf(w, "%v:", err)
+	for _, pos := range positions {
+		fmt.Fprintf(w, "\n    %v", pos)
+	}
 	fmt.Fprintln(w)
 }
diff --git a/go.mod b/go.mod
index feb1209..51bfbf7 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@
 	github.com/kr/pretty v0.1.0
 	github.com/kylelemons/godebug v1.1.0
 	github.com/lib/pq v1.0.0 // indirect
+	github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de
 	github.com/pkg/errors v0.8.0 // indirect
 	github.com/retr0h/go-gilt v0.0.0-20190206215556-f73826b37af2
 	github.com/spf13/cobra v0.0.3
diff --git a/go.sum b/go.sum
index e17baed..d768393 100644
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,8 @@
 github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/logrusorgru/aurora v0.0.0-20180419164547-d694e6f975a9/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto=
+github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=