cue: improve error messages

- more Go-style error messages
- report original value and type level
- remove struct IDs from printed structs
- more context information overall

Note: this is only a partial update. Errors
relating to disjunctions, for instance, need
a lot more love.

Change-Id: I9025a2cace8b38a987e4de3e73620bea734b7414
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2580
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/partial/eval_conc.out b/cmd/cue/cmd/testdata/partial/eval_conc.out
index f26c68d..006c7ca 100644
--- a/cmd/cue/cmd/testdata/partial/eval_conc.out
+++ b/cmd/cue/cmd/testdata/partial/eval_conc.out
@@ -1,6 +1,6 @@
-sum: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):
+b.idx: invalid non-ground value string (must be concrete int|string):
     ./testdata/partial/partial.cue:7:9
-b.str:incomplete value (string):
+b.str: incomplete value (string):
     ./testdata/partial/partial.cue:8:7
diff --git a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
index fa756db..780db31 100644
--- a/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
+++ b/cmd/cue/cmd/testdata/tasks/cmd_baddisplay.out
@@ -1,3 +1,3 @@
-text:unsupported op &(int, string):
+text: conflicting values 42 and string (mismatched types int and string):
     ./testdata/tasks/task_tool.cue:29:9
     tool/cli:4:9
diff --git a/cue/binop.go b/cue/binop.go
index 791bfe4..1e487ab 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"fmt"
 	"math/big"
 	"regexp"
 	"sort"
@@ -54,9 +55,27 @@
 
 	leftKind := left.kind()
 	rightKind := right.kind()
-	kind, invert := matchBinOpKind(op, leftKind, rightKind)
+	kind, invert, msg := matchBinOpKind(op, leftKind, rightKind)
 	if kind == bottomKind {
-		return ctx.mkIncompatible(src, op, left, right)
+		simplify := func(v, orig value) value {
+			switch x := v.(type) {
+			case *disjunction:
+				return orig
+			case *binaryExpr:
+				if x.op == opDisjunction {
+					return orig
+				}
+			default:
+				return x
+			}
+			return v
+		}
+		var l, r value = left, right
+		if x, ok := src.(*binaryExpr); ok {
+			l = simplify(x.left, left)
+			r = simplify(x.right, right)
+		}
+		return ctx.mkErr(src, msg, op, ctx.str(l), ctx.str(r), leftKind, rightKind)
 	}
 	if kind.hasReferences() {
 		panic("unexpected references in expression")
@@ -274,14 +293,14 @@
 		pos = r.Pos()
 	}
 	e := mkBin(ctx, pos, opUnify, r, v)
-	msg := "%v not within bound %v"
+	msg := "invalid value %v (out of bound %v)"
 	switch r.op {
 	case opNeq, opNMat:
-		msg = "%v excluded by %v"
+		msg = "invalid value %v (excluded by %v)"
 	case opMat:
-		msg = "%v does not match %v"
+		msg = "invalid value %v (does not match %v)"
 	}
-	return ctx.mkErr(e, msg, debugStr(ctx, v), debugStr(ctx, r))
+	return ctx.mkErr(e, msg, ctx.str(v), ctx.str(r))
 }
 
 func opInfo(op op) (cmp op, norm int) {
@@ -310,8 +329,9 @@
 	newSrc := binSrc(src.Pos(), op, x, other)
 	switch op {
 	case opUnify:
-		k, _ := matchBinOpKind(opUnify, x.kind(), other.kind())
+		k, _, msg := matchBinOpKind(opUnify, x.kind(), other.kind())
 		if k == bottomKind {
+			return ctx.mkErr(src, msg, opUnify, ctx.str(x), ctx.str(other), x.kind(), other.kind())
 			break
 		}
 		switch y := other.(type) {
@@ -433,8 +453,8 @@
 					fallthrough
 
 				case d.Negative:
-					return ctx.mkErr(newSrc, "incompatible bounds %v and %v",
-						debugStr(ctx, x), debugStr(ctx, y))
+					return ctx.mkErr(newSrc, "conflicting bounds %v and %v",
+						ctx.str(x), ctx.str(y))
 				}
 
 			case x.op == opNeq:
@@ -477,8 +497,9 @@
 	newSrc := binSrc(src.Pos(), op, x, other)
 	switch op {
 	case opUnify:
-		k, _ := matchBinOpKind(opUnify, x.kind(), other.kind())
+		k, _, msg := matchBinOpKind(opUnify, x.kind(), other.kind())
 		if k == bottomKind {
+			return ctx.mkErr(src, msg, op, ctx.str(x), ctx.str(other), x.kind(), other.kind())
 			break
 		}
 		switch y := other.(type) {
@@ -513,7 +534,7 @@
 			return y
 		}
 	}
-	return ctx.mkIncompatible(src, op, x, other)
+	return ctx.mkErr(src, "invalid operation %v and %v (operator not defined for custom validator)")
 }
 
 func (x *customValidator) check(ctx *context, v evaluated) evaluated {
@@ -531,13 +552,13 @@
 		return ctx.mkErr(x, "invalid custom validator")
 	} else if !b.b {
 		var buf bytes.Buffer
-		buf.WriteString(x.call.Name)
+		fmt.Fprintf(&buf, "%s.%s", ctx.labelStr(x.call.pkg), x.call.Name)
 		buf.WriteString("(")
 		for _, a := range x.args {
-			buf.WriteString(debugStr(ctx, a))
+			buf.WriteString(ctx.str(a))
 		}
 		buf.WriteString(")")
-		return ctx.mkErr(x, "value %v not in %v", debugStr(ctx, v), buf.String())
+		return ctx.mkErr(x, "invalid value %s (does not satisfy %s)", ctx.str(v), buf.String())
 	}
 	return nil
 }
@@ -681,7 +702,7 @@
 		switch op {
 		case opUnify:
 			if x.b != y.b {
-				return ctx.mkErr(x, "conflicting values: %v != %v", x.b, y.b)
+				return ctx.mkErr(x, "conflicting values %v and %v", x.b, y.b)
 			}
 			return x
 		case opLand:
@@ -711,7 +732,8 @@
 			str := other.strValue()
 			if x.str != str {
 				src := mkBin(ctx, src.Pos(), op, x, other)
-				return ctx.mkErr(src, "conflicting values: %v != %v", x.str, str)
+				return ctx.mkErr(src, "conflicting values %v and %v",
+					ctx.str(x), ctx.str(y))
 			}
 			return x
 		case opLss, opLeq, opEql, opNeq, opGeq, opGtr:
@@ -762,7 +784,8 @@
 		switch op {
 		case opUnify:
 			if !bytes.Equal(x.b, b) {
-				return ctx.mkErr(x, "conflicting values: %v != %v", x.b, b)
+				return ctx.mkErr(x, "conflicting values %v and %v",
+					ctx.str(x), ctx.str(y))
 			}
 			return x
 		case opLss, opLeq, opEql, opNeq, opGeq, opGtr:
@@ -867,7 +890,8 @@
 		case opUnify:
 			if x.v.Cmp(&y.v) != 0 {
 				src = mkBin(ctx, src.Pos(), op, x, other)
-				return ctx.mkErr(src, "conflicting values: %v != %v", x.strValue(), y.strValue())
+				return ctx.mkErr(src, "conflicting values %v and %v",
+					ctx.str(x), ctx.str(y))
 			}
 			if k != x.k {
 				n.v = x.v
@@ -1010,7 +1034,7 @@
 		n := unify(ctx, src, x.len.(evaluated), y.len.(evaluated))
 		if isBottom(n) {
 			src = mkBin(ctx, src.Pos(), op, x, other)
-			return ctx.mkErr(src, "incompatible list lengths: %v", n)
+			return ctx.mkErr(src, "conflicting list lengths: %v", n)
 		}
 		sx := x.elem.arcs
 		xa := sx
@@ -1026,10 +1050,10 @@
 		typ := x.typ
 		max, ok := n.(*numLit)
 		if !ok || len(xa) < max.intValue(ctx) {
+			src := mkBin(ctx, src.Pos(), op, x.typ, y.typ)
 			typ = unify(ctx, src, x.typ.(evaluated), y.typ.(evaluated))
 			if isBottom(typ) {
-				src = mkBin(ctx, src.Pos(), op, x, other)
-				return ctx.mkErr(src, "incompatible list types: %v: ", typ)
+				return ctx.mkErr(src, "conflicting list element types: %v", typ)
 			}
 		}
 
diff --git a/cue/build_test.go b/cue/build_test.go
index da37549..cb3607c 100644
--- a/cue/build_test.go
+++ b/cue/build_test.go
@@ -79,7 +79,7 @@
 		emit      string
 	}{{
 		insts(&bimport{"", files(`test: "ok"`)}),
-		`<0>{test: "ok"}`,
+		`{test: "ok"}`,
 		// }, {
 		// 	insts(pkg1, &bimport{"",
 		// 		files(
diff --git a/cue/builtin.go b/cue/builtin.go
index 95282ef..dccb441 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -167,7 +167,7 @@
 		}
 		c.ret = &disjunction{baseValue{c.src}, d, false}
 		if len(d) == 0 {
-			c.ret = errors.New("empty or")
+			c.ret = errors.New("empty list in call to or")
 		}
 	},
 }
@@ -187,26 +187,38 @@
 	return false
 }
 
+func (x *builtin) name(ctx *context) string {
+	if x.pkg == 0 {
+		return x.Name
+	}
+	return fmt.Sprintf("%s.%s", ctx.labelStr(x.pkg), x.Name)
+}
+
 func (x *builtin) call(ctx *context, src source, args ...evaluated) (ret value) {
 	if x.Func == nil {
-		return ctx.mkErr(x, "Builtin %q is not a function", x.Name)
+		return ctx.mkErr(x, "builtin %s is not a function", x.name(ctx))
 	}
 	if len(x.Params)-1 == len(args) && x.Result == boolKind {
 		// We have a custom builtin
 		return &customValidator{src.base(), args, x}
 	}
-	if len(x.Params) != len(args) {
-		return ctx.mkErr(src, x, "number of arguments does not match (%d vs %d)",
-			len(x.Params), len(args))
+	switch {
+	case len(x.Params) < len(args):
+		return ctx.mkErr(src, x, "too many arguments in call to %s (have %d, want %d)",
+			x.name(ctx), len(args), len(x.Params))
+	case len(x.Params) > len(args):
+		return ctx.mkErr(src, x, "not enough arguments in call to %s (have %d, want %d)",
+			x.name(ctx), len(args), len(x.Params))
 	}
 	for i, a := range args {
 		if x.Params[i] != bottomKind {
 			if unifyType(x.Params[i], a.kind()) == bottomKind {
-				return ctx.mkErr(src, x, "argument %d requires type %v, found %v", i+1, x.Params[i], a.kind())
+				const msg = "cannot use %s (type %s) as %s in argument %d to %s"
+				return ctx.mkErr(src, x, msg, ctx.str(a), a.kind(), x.Params[i], i+1, x.name(ctx))
 			}
 		}
 	}
-	call := callCtxt{src: src, ctx: ctx, args: args}
+	call := callCtxt{src: src, ctx: ctx, builtin: x, args: args}
 	defer func() {
 		var errVal interface{} = call.err
 		if err := recover(); err != nil {
@@ -218,7 +230,7 @@
 			ret = err.b
 		default:
 			// TODO: store the underlying error explicitly
-			ret = ctx.mkErr(src, x, "call error: %v", err)
+			ret = ctx.mkErr(src, x, "error in call to %s: %v", x.name(ctx), err)
 		}
 	}()
 	x.Func(&call)
@@ -230,11 +242,16 @@
 
 // callCtxt is passed to builtin implementations.
 type callCtxt struct {
-	src  source
-	ctx  *context
-	args []evaluated
-	err  error
-	ret  interface{}
+	src     source
+	ctx     *context
+	builtin *builtin
+	args    []evaluated
+	err     error
+	ret     interface{}
+}
+
+func (c *callCtxt) name() string {
+	return c.builtin.name(c.ctx)
 }
 
 var builtins = map[string]*structLit{}
@@ -321,47 +338,56 @@
 	return newValueRoot(c.ctx, c.args[i])
 }
 
-func (c *callCtxt) int(i int) int     { return int(c.intValue(i, 64)) }
-func (c *callCtxt) int8(i int) int8   { return int8(c.intValue(i, 8)) }
-func (c *callCtxt) int16(i int) int16 { return int16(c.intValue(i, 16)) }
-func (c *callCtxt) int32(i int) int32 { return int32(c.intValue(i, 32)) }
-func (c *callCtxt) rune(i int) rune   { return rune(c.intValue(i, 32)) }
-func (c *callCtxt) int64(i int) int64 { return int64(c.intValue(i, 64)) }
+func (c *callCtxt) invalidArgType(arg value, i int, typ string, err error) {
+	if err != nil {
+		c.errf(c.src, err, "cannot use %s (type %s) as %s in argument %d to %s: %v",
+			c.ctx.str(arg), arg.kind(), typ, i, c.name(), err)
+	} else {
+		c.errf(c.src, nil, "cannot use %s (type %s) as %s in argument %d to %s",
+			c.ctx.str(arg), arg.kind(), typ, i, c.name())
+	}
+}
 
-func (c *callCtxt) intValue(i, bits int) int64 {
-	x := newValueRoot(c.ctx, c.args[i])
+func (c *callCtxt) int(i int) int     { return int(c.intValue(i, 64, "int64")) }
+func (c *callCtxt) int8(i int) int8   { return int8(c.intValue(i, 8, "int8")) }
+func (c *callCtxt) int16(i int) int16 { return int16(c.intValue(i, 16, "int16")) }
+func (c *callCtxt) int32(i int) int32 { return int32(c.intValue(i, 32, "int32")) }
+func (c *callCtxt) rune(i int) rune   { return rune(c.intValue(i, 32, "rune")) }
+func (c *callCtxt) int64(i int) int64 { return int64(c.intValue(i, 64, "int64")) }
+
+func (c *callCtxt) intValue(i, bits int, typ string) int64 {
+	arg := c.args[i]
+	x := newValueRoot(c.ctx, arg)
 	n, err := x.Int(nil)
 	if err != nil {
-		c.errf(c.src, err, "argument %d must be in int, found number", i)
+		c.invalidArgType(arg, i, typ, err)
 		return 0
 	}
 	if n.BitLen() > bits {
-		c.errf(c.src, err, "argument %d out of range: has %d > %d bits", n.BitLen(), bits)
+		c.errf(c.src, err, "int %s overflows %s in argument %d in call to %s",
+			n, typ, i, c.name())
 	}
 	res, _ := x.Int64()
 	return res
 }
 
-func (c *callCtxt) uint(i int) uint     { return uint(c.uintValue(i, 64)) }
-func (c *callCtxt) uint8(i int) uint8   { return uint8(c.uintValue(i, 8)) }
-func (c *callCtxt) byte(i int) uint8    { return byte(c.uintValue(i, 8)) }
-func (c *callCtxt) uint16(i int) uint16 { return uint16(c.uintValue(i, 16)) }
-func (c *callCtxt) uint32(i int) uint32 { return uint32(c.uintValue(i, 32)) }
-func (c *callCtxt) uint64(i int) uint64 { return uint64(c.uintValue(i, 64)) }
+func (c *callCtxt) uint(i int) uint     { return uint(c.uintValue(i, 64, "uint64")) }
+func (c *callCtxt) uint8(i int) uint8   { return uint8(c.uintValue(i, 8, "uint8")) }
+func (c *callCtxt) byte(i int) uint8    { return byte(c.uintValue(i, 8, "byte")) }
+func (c *callCtxt) uint16(i int) uint16 { return uint16(c.uintValue(i, 16, "uint16")) }
+func (c *callCtxt) uint32(i int) uint32 { return uint32(c.uintValue(i, 32, "uint32")) }
+func (c *callCtxt) uint64(i int) uint64 { return uint64(c.uintValue(i, 64, "uint64")) }
 
-func (c *callCtxt) uintValue(i, bits int) uint64 {
+func (c *callCtxt) uintValue(i, bits int, typ string) uint64 {
 	x := newValueRoot(c.ctx, c.args[i])
 	n, err := x.Int(nil)
-	if err != nil {
-		c.errf(c.src, err, "argument %d must be an integer", i)
-		return 0
-	}
-	if n.Sign() < 0 {
-		c.errf(c.src, nil, "argument %d must be a positive integer", i)
+	if err != nil || n.Sign() < 0 {
+		c.invalidArgType(c.args[i], i, typ, err)
 		return 0
 	}
 	if n.BitLen() > bits {
-		c.errf(c.src, nil, "argument %d out of range: has %d > %d bits", i, n.BitLen(), bits)
+		c.errf(c.src, err, "int %s overflows %s in argument %d in call to %s",
+			n, typ, i, c.name())
 	}
 	res, _ := x.Uint64()
 	return res
@@ -371,7 +397,7 @@
 	x := newValueRoot(c.ctx, c.args[i])
 	res, err := x.Float64()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "float64", err)
 		return 0
 	}
 	return res
@@ -381,7 +407,7 @@
 	x := newValueRoot(c.ctx, c.args[i])
 	n, err := x.Int(nil)
 	if err != nil {
-		c.errf(c.src, err, "argument %d must be in int, found number", i)
+		c.invalidArgType(c.args[i], i, "int", err)
 		return nil
 	}
 	return n
@@ -392,7 +418,7 @@
 	var mant big.Int
 	exp, err := x.MantExp(&mant)
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "float", err)
 		return nil
 	}
 	f := &big.Float{}
@@ -409,7 +435,7 @@
 	x := newValueRoot(c.ctx, c.args[i])
 	v, err := x.String()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "string", err)
 		return ""
 	}
 	return v
@@ -419,7 +445,7 @@
 	x := newValueRoot(c.ctx, c.args[i])
 	v, err := x.Bytes()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "bytes", err)
 		return nil
 	}
 	return v
@@ -430,7 +456,7 @@
 	// TODO: optimize for string and bytes cases
 	r, err := x.Reader()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "bytes|string", err)
 		return nil
 	}
 	return r
@@ -440,33 +466,37 @@
 	x := newValueRoot(c.ctx, c.args[i])
 	b, err := x.Bool()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "bool", err)
 		return false
 	}
 	return b
 }
 
 func (c *callCtxt) list(i int) (a Iterator) {
-	x := newValueRoot(c.ctx, c.args[i])
+	arg := c.args[i]
+	x := newValueRoot(c.ctx, arg)
 	v, err := x.List()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "list", err)
 		return Iterator{ctx: c.ctx}
 	}
 	return v
 }
 
 func (c *callCtxt) strList(i int) (a []string) {
-	x := newValueRoot(c.ctx, c.args[i])
+	arg := c.args[i]
+	x := newValueRoot(c.ctx, arg)
 	v, err := x.List()
 	if err != nil {
-		c.errf(c.src, err, "invalid argument %d: %v", i, err)
+		c.invalidArgType(c.args[i], i, "list", err)
 		return nil
 	}
-	for i := 0; v.Next(); i++ {
+	for j := 0; v.Next(); j++ {
 		str, err := v.Value().String()
 		if err != nil {
-			c.errf(c.src, err, "list element %d: %v", i, err)
+			c.errf(c.src, err, "invalid list element %d in argument %d to %s: %v",
+				j, i, c.name(), err)
+			break
 		}
 		a = append(a, str)
 	}
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index a560b08..3ebd34e 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -48,13 +48,13 @@
 		`3`,
 	}, {
 		test("math", "math.Pi(3)"),
-		`_|_(<0>.Pi:cannot call non-function 3.14159265358979323846264338327950288419716939937510582097494459 (type float))`,
+		`_|_(cannot call non-function Pi (type float))`,
 	}, {
 		test("math", "math.Floor(3, 5)"),
-		`_|_(<0>.Floor (3,5):number of arguments does not match (1 vs 2))`,
+		`_|_(too many arguments in call to math.Floor (have 2, want 1))`,
 	}, {
 		test("math", `math.Floor("foo")`),
-		`_|_(<0>.Floor ("foo"):argument 1 requires type number, found string)`,
+		`_|_(cannot use "foo" (type string) as number in argument 1 to math.Floor)`,
 	}, {
 		test("encoding/hex", `hex.Encode("foo")`),
 		`"666f6f"`,
@@ -63,32 +63,32 @@
 		`'foo'`,
 	}, {
 		test("encoding/hex", `hex.Decode("foo")`),
-		`_|_(<0>.Decode ("foo"):call error: encoding/hex: invalid byte: U+006F 'o')`,
+		`_|_(error in call to encoding/hex.Decode: encoding/hex: invalid byte: U+006F 'o')`,
 	}, {
 		test("strconv", `strconv.FormatUint(64, 16)`),
 		`"40"`,
 	}, {
 		// Find a better alternative, as this call should go.
 		test("strconv", `strconv.FormatFloat(3.02, 300, 4, 64)`),
-		`_|_(<0>.FormatFloat (3.02,300,4,64):argument 1 out of range: has 9 > 8 bits)`,
+		`_|_(int 300 overflows byte in argument 1 in call to strconv.FormatFloat)`,
 	}, {
 		// Find a better alternative, as this call should go.
 		test("strconv", `strconv.FormatFloat(3.02, -1, 4, 64)`),
-		`_|_(<0>.FormatFloat (3.02,-1,4,64):argument 1 must be a positive integer)`,
+		`_|_(cannot use -1 (type int) as byte in argument 1 to strconv.FormatFloat)`,
 	}, {
 		// Find a better alternative, as this call should go.
 		test("strconv", `strconv.FormatFloat(3.02, 1.0, 4, 64)`),
-		`_|_(<0>.FormatFloat (3.02,1.0,4,64):argument 2 requires type int, found float)`,
+		`_|_(cannot use 1.0 (type float) as int in argument 2 to strconv.FormatFloat)`,
 	}, {
 		// Panics
 		test("math", `math.Jacobi(1000, 2000)`),
-		`_|_(<0>.Jacobi (1000,2000):call error: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000)`,
+		`_|_(error in call to math.Jacobi: big: invalid 2nd argument to Int.Jacobi: need odd integer but got 2000)`,
 	}, {
 		test("math", `math.Jacobi(1000, 201)`),
 		`1`,
 	}, {
 		test("math", `math.Asin(2.0e400)`),
-		`_|_(<0>.Asin (2.0e+400):invalid argument 0: cue: value was rounded up)`,
+		`_|_(cannot use 2.0e+400 (type float) as float64 in argument 0 to math.Asin: value was rounded up)`,
 	}, {
 		test("encoding/csv", `csv.Decode("1,2,3\n4,5,6")`),
 		`[["1","2","3"],["4","5","6"]]`,
@@ -100,7 +100,7 @@
 		`"Hello World!"`,
 	}, {
 		test("strings", `strings.Join([1, 2], " ")`),
-		`_|_(<0>.Join ([1,2]," "):list element 1: not of right kind (int vs string))`,
+		`_|_(invalid list element 0 in argument 0 to strings.Join: cannot use value 1 (type int) as string)`,
 	}, {
 		test("math/bits", `bits.Or(0x8, 0x1)`),
 		`9`,
@@ -133,7 +133,7 @@
 		`2`,
 	}, {
 		testExpr(`or([])`),
-		`_|_(builtin:or:empty or)`,
+		`_|_(empty list in call to or)`,
 	}, {
 		test("encoding/csv", `csv.Encode([[1,2,3],[4,5],[7,8,9]])`),
 		`"1,2,3\n4,5\n7,8,9\n"`,
diff --git a/cue/debug.go b/cue/debug.go
index d3d1700..25199f4 100644
--- a/cue/debug.go
+++ b/cue/debug.go
@@ -23,7 +23,14 @@
 
 func debugStr(ctx *context, v value) string {
 	p := newPrinter(ctx)
-	p.debugStr(v)
+	p.showNodeRef = true
+	p.str(v)
+	return p.w.String()
+}
+
+func (c *context) str(v value) string {
+	p := newPrinter(c)
+	p.str(v)
 	return p.w.String()
 }
 
@@ -123,8 +130,9 @@
 }
 
 type printer struct {
-	ctx *context
-	w   *bytes.Buffer
+	ctx         *context
+	w           *bytes.Buffer
+	showNodeRef bool
 }
 
 func (p *printer) label(f label) string {
@@ -154,7 +162,7 @@
 	return f
 }
 
-func (p *printer) debugStr(v interface{}) {
+func (p *printer) str(v interface{}) {
 	writef := p.writef
 	write := p.write
 	switch x := v.(type) {
@@ -164,45 +172,50 @@
 		write(x)
 	case *builtin:
 		write("builtin:")
-		p.debugStr(x.Name)
+		p.str(x.Name)
 	case *nodeRef:
-		writef("<%s>", p.ctx.ref(x.node))
-		// p.debugStr(x.node)
+		if p.showNodeRef {
+			writef("<%s>", p.ctx.ref(x.node))
+		}
 	case *selectorExpr:
-		p.debugStr(x.x)
 		f := lambdaName(x.feature, x.x)
-		writef(".%v", p.label(f))
+		if _, ok := x.x.(*nodeRef); ok && !p.showNodeRef {
+			write(p.label(f))
+		} else {
+			p.str(x.x)
+			writef(".%v", p.label(f))
+		}
 	case *indexExpr:
-		p.debugStr(x.x)
+		p.str(x.x)
 		write("[")
-		p.debugStr(x.index)
+		p.str(x.index)
 		write("]")
 	case *sliceExpr:
-		p.debugStr(x.x)
+		p.str(x.x)
 		write("[")
 		if x.lo != nil {
-			p.debugStr(x.lo)
+			p.str(x.lo)
 		}
 		write(":")
 		if x.hi != nil {
-			p.debugStr(x.hi)
+			p.str(x.hi)
 		}
 		write("]")
 	case *callExpr:
-		p.debugStr(x.x)
+		p.str(x.x)
 		write(" (")
 		for i, a := range x.args {
-			p.debugStr(a)
+			p.str(a)
 			if i < len(x.args)-1 {
 				write(",")
 			}
 		}
 		write(")")
 	case *customValidator:
-		p.debugStr(x.call)
+		p.str(x.call)
 		write(" (")
 		for i, a := range x.args {
-			p.debugStr(a)
+			p.str(a)
 			if i < len(x.args)-1 {
 				write(",")
 			}
@@ -210,12 +223,12 @@
 		write(")")
 	case *unaryExpr:
 		write(x.op)
-		p.debugStr(x.x)
+		p.str(x.x)
 	case *binaryExpr:
 		write("(")
-		p.debugStr(x.left)
+		p.str(x.left)
 		writef(" %v ", x.op)
-		p.debugStr(x.right)
+		p.str(x.right)
 		write(")")
 	case *unification:
 		write("(")
@@ -223,7 +236,7 @@
 			if i != 0 {
 				writef(" & ")
 			}
-			p.debugStr(v)
+			p.str(v)
 		}
 		write(")")
 	case *disjunction:
@@ -235,30 +248,35 @@
 			if v.marked {
 				writef("*")
 			}
-			p.debugStr(v.val)
+			p.str(v.val)
 		}
 		write(")")
 	case *lambdaExpr:
-		writef("<%s>(", p.ctx.ref(x))
-		p.debugStr(x.params.arcs)
+		if p.showNodeRef {
+			writef("<%s>", p.ctx.ref(x))
+		}
+		write("(")
+		p.str(x.params.arcs)
 		write(")->")
-		p.debugStr(x.value)
+		p.str(x.value)
 
 	case *structLit:
 		if x == nil {
 			write("*nil node*")
 			break
 		}
-		p.writef("<%s>", p.ctx.ref(x))
+		if p.showNodeRef {
+			p.writef("<%s>", p.ctx.ref(x))
+		}
 		writef("{")
 		if x.template != nil {
 			write("<>: ")
-			p.debugStr(x.template)
+			p.str(x.template)
 			write(", ")
 		}
-		p.debugStr(x.arcs)
+		p.str(x.arcs)
 		for i, c := range x.comprehensions {
-			p.debugStr(c)
+			p.str(c)
 			if i < len(x.comprehensions)-1 {
 				p.write(", ")
 			}
@@ -267,7 +285,7 @@
 
 	case []arc:
 		for i, a := range x {
-			p.debugStr(a)
+			p.str(a)
 
 			if i < len(x)-1 {
 				p.write(", ")
@@ -286,7 +304,7 @@
 			p.write("?")
 		}
 		p.write(": ")
-		p.debugStr(n)
+		p.str(n)
 		if x.attrs != nil {
 			for _, a := range x.attrs.attr {
 				p.write(" ", a.text)
@@ -294,22 +312,22 @@
 		}
 
 	case *fieldComprehension:
-		p.debugStr(x.clauses)
+		p.str(x.clauses)
 
 	case *listComprehension:
 		writef("[")
-		p.debugStr(x.clauses)
+		p.str(x.clauses)
 		write(" ]")
 
 	case *yield:
 		writef(" yield ")
 		writef("(")
-		p.debugStr(x.key)
+		p.str(x.key)
 		if x.opt {
 			writef("?")
 		}
 		writef("): ")
-		p.debugStr(x.value)
+		p.str(x.value)
 
 	case *feed:
 		writef(" <%s>for ", p.ctx.ref(x.fn))
@@ -319,13 +337,13 @@
 		a = x.fn.params.arcs[1]
 		p.writef(p.label(a.feature))
 		writef(" in ")
-		p.debugStr(x.source)
-		p.debugStr(x.fn.value)
+		p.str(x.source)
+		p.str(x.fn.value)
 
 	case *guard:
 		writef(" if ")
-		p.debugStr(x.condition)
-		p.debugStr(x.value)
+		p.str(x.condition)
+		p.str(x.value)
 
 	case *nullLit:
 		write("null")
@@ -353,13 +371,13 @@
 			p.writef("float & ")
 		}
 		p.writef("%v", x.op)
-		p.debugStr(x.value)
+		p.str(x.value)
 	case *interpolation:
 		for i, e := range x.parts {
 			if i != 0 {
 				write("+")
 			}
-			p.debugStr(e)
+			p.str(e)
 		}
 	case *list:
 		// TODO: do not evaluate
@@ -384,9 +402,9 @@
 		}
 		if !ok || ln > len(x.elem.arcs) {
 			if !open && !isTop(x.typ) {
-				p.debugStr(x.len)
+				p.str(x.len)
 				write("*[")
-				p.debugStr(x.typ)
+				p.str(x.typ)
 				write("]")
 				if len(x.elem.arcs) == 0 {
 					break
@@ -398,7 +416,7 @@
 		}
 		write("[")
 		for i, a := range x.elem.arcs {
-			p.debugStr(a.v)
+			p.str(a.v)
 			if i < len(x.elem.arcs)-1 {
 				write(",")
 			}
@@ -406,7 +424,7 @@
 		if ellipsis {
 			write(", ...")
 			if !isTop(x.typ) {
-				p.debugStr(x.typ)
+				p.str(x.typ)
 			}
 		}
 		write("]")
@@ -418,8 +436,9 @@
 		write("_|_")
 		if x.value != nil || x.format != "" {
 			write("(")
-			if x.value != nil {
-				writef("%s:", debugStr(p.ctx, x.value))
+			if x.value != nil && p.showNodeRef {
+				p.str(x.value)
+				p.write(":")
 			}
 			write(x.msg())
 			write(")")
diff --git a/cue/errors.go b/cue/errors.go
index 3bfa563..73b54ae 100644
--- a/cue/errors.go
+++ b/cue/errors.go
@@ -193,7 +193,8 @@
 		return err
 	}
 	e := mkBin(c, src.Pos(), op, a, b)
-	return c.mkErr(e, "unsupported op %s(%s, %s)", op, a.kind(), b.kind())
+	return c.mkErr(e, "invalid operation %s %s %s (mismatched types %s and %s)",
+		c.str(a), op, c.str(b), a.kind(), b.kind())
 }
 
 func (idx *index) mkErr(src source, args ...interface{}) *bottom {
@@ -249,7 +250,7 @@
 		case value:
 			// TODO: convert to Go values so that localization frameworks
 			// can format values accordingly.
-			args[i] = debugStr(ctx, v)
+			args[i] = ctx.str(v)
 		}
 	}
 }
diff --git a/cue/errors/errors.go b/cue/errors/errors.go
index f5c5619..93dd7e7 100644
--- a/cue/errors/errors.go
+++ b/cue/errors/errors.go
@@ -477,7 +477,7 @@
 	}
 
 	if path := Path(err); path != nil {
-		fprintf(w, "%s:", strings.Join(path, "."))
+		fprintf(w, "%s: ", strings.Join(path, "."))
 	}
 
 	if len(positions) == 0 {
diff --git a/cue/eval.go b/cue/eval.go
index 96edf07..44db675 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -60,6 +60,7 @@
 		if n.val() == nil {
 			field := ctx.labelStr(x.feature)
 			//	m.foo undefined (type map[string]bool has no field or method foo)
+			// TODO: mention x.x in error message?
 			return ctx.mkErr(x, "undefined field %q", field)
 		}
 		// TODO: do we need to evaluate here?
@@ -150,7 +151,7 @@
 
 	e := newEval(ctx, true)
 
-	fn := e.eval(x.x, lambdaKind, "cannot call non-function %[1]s (type %[3]s)")
+	fn := e.eval(x.x, lambdaKind, "cannot call non-function %[2]s (type %[3]s)")
 	args := make([]evaluated, len(x.args))
 	for i, a := range x.args {
 		args[i] = e.evalPartial(a, typeKinds, "never triggers")
@@ -328,7 +329,7 @@
 		case d.marked:
 			if marked != nil {
 				// TODO: allow disjunctions to be returned as is.
-				return ctx.mkErr(x, codeIncomplete, "more than one default remaining (%v and %v)", debugStr(ctx, marked), debugStr(ctx, d.val))
+				return ctx.mkErr(x, codeIncomplete, "more than one default remaining (%v and %v)", ctx.str(marked), ctx.str(d.val))
 			}
 			marked = d.val.(evaluated)
 		case unmarked1 == nil:
@@ -343,7 +344,7 @@
 
 	case unmarked2 != nil:
 		return ctx.mkErr(x, codeIncomplete, "more than one element remaining (%v and %v)",
-			debugStr(ctx, unmarked1), debugStr(ctx, unmarked2))
+			ctx.str(unmarked1), ctx.str(unmarked2))
 
 	case unmarked1 != nil:
 		return unmarked1
@@ -418,7 +419,7 @@
 	switch op {
 	case opSub:
 		if kind&numeric == bottomKind {
-			return ctx.mkErr(src, "unary '-' requires numeric value, found %s", kind)
+			return ctx.mkErr(src, "invalid operation -%s (- %s)", ctx.str(x), kind)
 		}
 		switch v := v.(type) {
 		case *numLit:
@@ -434,7 +435,7 @@
 
 	case opAdd:
 		if kind&numeric == bottomKind {
-			return ctx.mkErr(src, "unary '+' requires numeric value, found %s", kind)
+			return ctx.mkErr(src, "invalid operation +%s (+ %s)", ctx.str(x), kind)
 		}
 		if kind&^(numeric|nonGround|referenceKind) == bottomKind {
 			return v
@@ -450,7 +451,7 @@
 
 	case opNot:
 		if kind&boolKind == bottomKind {
-			return ctx.mkErr(src, "unary '!' requires bool value, found %s", kind)
+			return ctx.mkErr(src, "invalid operation !%s (! %s)", ctx.str(x), kind)
 		}
 		switch v := v.(type) {
 		case *top:
@@ -461,5 +462,5 @@
 			return &boolLit{src.base(), !v.b}
 		}
 	}
-	return ctx.mkErr(src, "invalid operand type %v for unary operator %v", v, op)
+	return ctx.mkErr(src, "invalid operation %s%s (%s %s)", op, ctx.str(x), op, kind)
 }
diff --git a/cue/evaluator.go b/cue/evaluator.go
index 529e509..f8c7bb4 100644
--- a/cue/evaluator.go
+++ b/cue/evaluator.go
@@ -69,7 +69,7 @@
 	for i := 3; i < len(args); i++ {
 		switch v := args[i].(type) {
 		case value:
-			args[i] = debugStr(e.ctx, v)
+			args[i] = e.ctx.str(v)
 		}
 	}
 	err = e.ctx.mkErr(orig, args...)
diff --git a/cue/go.go b/cue/go.go
index 5daeb2c..adf67bc 100644
--- a/cue/go.go
+++ b/cue/go.go
@@ -211,7 +211,10 @@
 	case *big.Rat:
 		// should we represent this as a binary operation?
 		n := newNum(src, numKind)
-		ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
+		_, err := ctx.Quo(&n.v, apd.NewWithBigInt(v.Num(), 0), apd.NewWithBigInt(v.Denom(), 0))
+		if err != nil {
+			return ctx.mkErr(src, err)
+		}
 		if !v.IsInt() {
 			n.k = floatKind
 		}
@@ -219,7 +222,7 @@
 
 	case *big.Float:
 		n := newNum(src, floatKind)
-		n.v.SetString(v.String())
+		_, _, _ = n.v.SetString(v.String())
 		return n
 
 	case *apd.Decimal:
diff --git a/cue/kind.go b/cue/kind.go
index fd6382d..e078153 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -159,9 +159,9 @@
 // - keep type compatibility mapped at a central place
 // - reduce the amount op type switching.
 // - simplifies testing
-func matchBinOpKind(op op, a, b kind) (k kind, swap bool) {
+func matchBinOpKind(op op, a, b kind) (k kind, swap bool, msg string) {
 	if op == opDisjunction {
-		return a | b, false
+		return a | b, false, ""
 	}
 	u := unifyType(a, b)
 	valBits := u & typeKinds
@@ -171,57 +171,72 @@
 	a = a & typeKinds
 	b = b & typeKinds
 	if valBits == bottomKind {
+		msg := "invalid operation %[2]s %[1]s %[3]s (mismatched types %[4]s and %[5]s)"
 		k := nullKind
 		switch op {
 		case opLss, opLeq, opGtr, opGeq:
 			if a.isAnyOf(numKind) && b.isAnyOf(numKind) {
-				return boolKind, false
+				return boolKind, false, ""
 			}
 		case opEql, opNeq:
 			if a.isAnyOf(numKind) && b.isAnyOf(numKind) {
-				return boolKind, false
+				return boolKind, false, ""
 			}
-			fallthrough
-		case opUnify:
 			if a&nullKind != 0 {
-				return k, false
+				return k, false, ""
 			}
 			if b&nullKind != 0 {
-				return k, true
+				return k, true, ""
 			}
-			return bottomKind, false
+			return bottomKind, false, msg
+		case opUnify:
+			if a&nullKind != 0 {
+				return k, false, ""
+			}
+			if b&nullKind != 0 {
+				return k, true, ""
+			}
+			switch {
+			case a.isGround() && !b.isGround():
+				msg = "invalid value %[2]s (must be %[5]s)"
+			case !a.isGround() && b.isGround():
+				msg = "invalid value %[3]s (must be %[4]s)"
+			default:
+				msg = "conflicting values %[2]s and %[3]s (mismatched types %[4]s and %[5]s)"
+			}
+			return bottomKind, false, msg
 		case opRem, opQuo, opMul, opAdd, opSub:
 			if a.isAnyOf(numKind) && b.isAnyOf(numKind) {
-				return floatKind, false
+				return floatKind, false, ""
 			}
 		}
 		if op == opMul {
 			if a.isAnyOf(listKind|stringKind|bytesKind) && b.isAnyOf(intKind) {
-				return a | catBits, false
+				return a | catBits, false, ""
 			}
 			if b.isAnyOf(listKind|stringKind|bytesKind) && a.isAnyOf(intKind) {
-				return b | catBits, true
+				return b | catBits, true, ""
 			}
 		}
 		// non-overlapping types
 		if a&scalarKinds == 0 || b&scalarKinds == 0 {
-			return bottomKind, false
+			return bottomKind, false, msg
 		}
 		// a and b have different numeric types.
 		switch {
 		case b.isAnyOf(durationKind):
 			// a must be a numeric, non-duration type.
 			if op == opMul {
-				return durationKind | catBits, true
+				return durationKind | catBits, true, msg
 			}
 		case a.isAnyOf(durationKind):
 			if opIn(op, opMul, opQuo, opRem) {
-				return durationKind | catBits, false
+				return durationKind | catBits, false, msg
 			}
 		case op.isCmp():
-			return boolKind, false
+			return boolKind, false, ""
 		}
-		return bottomKind, false
+		return bottomKind, false, msg
 	}
 	switch {
 	case a&nonGround == 0 && b&nonGround == 0:
@@ -236,50 +251,52 @@
 	switch op {
 	case opUnify:
 		// Increase likelihood of unification succeeding on first try.
-		return u, swap
+		return u, swap, ""
 
 	case opLand, opLor:
 		if u.isAnyOf(boolKind) {
-			return boolKind | catBits, swap
+			return boolKind | catBits, swap, ""
 		}
 	case opEql, opNeq, opMat, opNMat:
 		if u.isAnyOf(fixedKinds) {
-			return boolKind | catBits, false
+			return boolKind | catBits, false, ""
 		}
 	case opLss, opLeq, opGeq, opGtr:
 		if u.isAnyOf(fixedKinds) {
-			return boolKind | catBits, false
+			return boolKind | catBits, false, ""
 		}
 	case opAdd:
 		if u.isAnyOf(addableKind) {
-			return u&(addableKind) | catBits, false
+			return u&(addableKind) | catBits, false, ""
 		}
 	case opSub:
 		if u.isAnyOf(scalarKinds) {
-			return u&scalarKinds | catBits, false
+			return u&scalarKinds | catBits, false, ""
 		}
 	case opRem:
 		if u.isAnyOf(numKind) {
-			return floatKind | catBits, false
+			return floatKind | catBits, false, ""
 		}
 	case opQuo:
 		if u.isAnyOf(numKind) {
-			return floatKind | catBits, false
+			return floatKind | catBits, false, ""
 		}
 	case opIRem, opIMod:
 		if u.isAnyOf(intKind) {
-			return u&(intKind) | catBits, false
+			return u&(intKind) | catBits, false, ""
 		}
 	case opIQuo, opIDiv:
 		if u.isAnyOf(intKind) {
-			return intKind | catBits, false
+			return intKind | catBits, false, ""
 		}
 	case opMul:
 		if u.isAnyOf(numKind) {
-			return u&numKind | catBits, false
+			return u&numKind | catBits, false, ""
 		}
 	default:
 		panic("unimplemented")
 	}
-	return bottomKind, false
+	msg = "invalid operation %[2]s %[1]s %[3]s"
+	msg += fmt.Sprintf(" (operator not defined on %s)", valBits)
+	return bottomKind, false, msg
 }
diff --git a/cue/kind_test.go b/cue/kind_test.go
index ac5a76a..48f48db 100644
--- a/cue/kind_test.go
+++ b/cue/kind_test.go
@@ -54,7 +54,7 @@
 	for _, tc := range testCases {
 		key := fmt.Sprintf("%s(%v, %v)", tc.op, tc.a, tc.b)
 		t.Run(key, func(t *testing.T) {
-			got, _ := matchBinOpKind(tc.op, tc.a, tc.b)
+			got, _, _ := matchBinOpKind(tc.op, tc.a, tc.b)
 			if got != tc.want {
 				t.Errorf("got %v, want %v", got, tc.want)
 			}
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 6125c7a..0db51ed 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -123,15 +123,15 @@
 
 			`b1: "a", ` +
 			`b2: "foo", ` +
-			`b3: _|_((=~"[a-z]{4}" & "foo"):"foo" does not match =~"[a-z]{4}"), ` +
+			`b3: _|_((=~"[a-z]{4}" & "foo"):invalid value "foo" (does not match =~"[a-z]{4}")), ` +
 			`b4: "foo", ` +
 
 			`s1: =~"c", ` +
 			`s2: (!="b" & =~"[a-z]"), ` +
 
-			`e1: _|_(("foo" =~ 1):unsupported op =~(string, int)), ` +
-			`e2: _|_(("foo" !~ true):unsupported op !~(string, bool)), ` +
-			`e3: _|_((!="a" & <5):unsupported op &(string, number))}`,
+			`e1: _|_(("foo" =~ 1):invalid operation "foo" =~ 1 (mismatched types string and int)), ` +
+			`e2: _|_(("foo" !~ true):invalid operation "foo" !~ true (mismatched types string and bool)), ` +
+			`e3: _|_((!="a" & <5):conflicting values !="a" and <5 (mismatched types string and number))}`,
 	}, {
 		desc: "arithmetic",
 		in: `
@@ -173,15 +173,15 @@
 			`v4: 2.0, ` +
 			`v5: 0, ` +
 
-			`e0: _|_((2 + "a"):unsupported op +(int, string)), ` +
+			`e0: _|_((2 + "a"):invalid operation 2 + "a" (mismatched types int and string)), ` +
 			// `e1: _|_((2.0 / 1):unsupported op /(float, int)), ` +
 			// `e2: _|_((1 / 2.0):unsupported op /(int, float)), ` +
 			// `e3: _|_((3.0 % 2):unsupported op %(float, int)), ` +
 			// `e4: _|_((1 % 2.0):unsupported op %(int, float)), ` +
-			`e5: _|_((1.0 div 2):unsupported op div(float, int)), ` +
-			`e6: _|_((2 rem 2.0):unsupported op rem(int, float)), ` +
-			`e7: _|_((2 quo 2.0):unsupported op quo(int, float)), ` +
-			`e8: _|_((1.0 mod 1):unsupported op mod(float, int))}`,
+			`e5: _|_((1.0 div 2):invalid operation 1.0 div 2 (mismatched types float and int)), ` +
+			`e6: _|_((2 rem 2.0):invalid operation 2 rem 2.0 (mismatched types int and float)), ` +
+			`e7: _|_((2 quo 2.0):invalid operation 2 quo 2.0 (mismatched types int and float)), ` +
+			`e8: _|_((1.0 mod 1):invalid operation 1.0 mod 1 (mismatched types float and int))}`,
 	}, {
 		desc: "integer-specific arithmetic",
 		in: `
@@ -216,17 +216,17 @@
 			// TODO: handle divide by zero
 			`,
 		out: `<0>{q1: 2, q2: -2, q3: -2, q4: 2, ` +
-			`qe1: _|_((2.0 quo 1):unsupported op quo(float, int)), ` +
-			`qe2: _|_((2 quo 1.0):unsupported op quo(int, float)), ` +
-			`r1: 1, r2: 1, r3: -1, r4: -1, re1: ` +
-			`_|_((2.0 rem 1):unsupported op rem(float, int)), ` +
-			`re2: _|_((2 rem 1.0):unsupported op rem(int, float)), ` +
+			`qe1: _|_((2.0 quo 1):invalid operation 2.0 quo 1 (mismatched types float and int)), ` +
+			`qe2: _|_((2 quo 1.0):invalid operation 2 quo 1.0 (mismatched types int and float)), ` +
+			`r1: 1, r2: 1, r3: -1, r4: -1, ` +
+			`re1: _|_((2.0 rem 1):invalid operation 2.0 rem 1 (mismatched types float and int)), ` +
+			`re2: _|_((2 rem 1.0):invalid operation 2 rem 1.0 (mismatched types int and float)), ` +
 			`d1: 2, d2: -2, d3: -3, d4: 3, ` +
-			`de1: _|_((2.0 div 1):unsupported op div(float, int)), ` +
-			`de2: _|_((2 div 1.0):unsupported op div(int, float)), ` +
+			`de1: _|_((2.0 div 1):invalid operation 2.0 div 1 (mismatched types float and int)), ` +
+			`de2: _|_((2 div 1.0):invalid operation 2 div 1.0 (mismatched types int and float)), ` +
 			`m1: 1, m2: 1, m3: 1, m4: 1, ` +
-			`me1: _|_((2.0 mod 1):unsupported op mod(float, int)), ` +
-			`me2: _|_((2 mod 1.0):unsupported op mod(int, float))}`,
+			`me1: _|_((2.0 mod 1):invalid operation 2.0 mod 1 (mismatched types float and int)), ` +
+			`me2: _|_((2 mod 1.0):invalid operation 2 mod 1.0 (mismatched types int and float))}`,
 	}, {
 		desc: "booleans",
 		in: `
@@ -237,7 +237,7 @@
 			e: true
 			e: !true
 			`,
-		out: "<0>{t: true, f: false, e: _|_(true:conflicting values: true != false)}",
+		out: "<0>{t: true, f: false, e: _|_(true:conflicting values true and false)}",
 	}, {
 		desc: "boolean arithmetic",
 		in: `
@@ -248,7 +248,7 @@
 			e: true & true
 			f: true & false
 			`,
-		out: "<0>{a: true, b: true, c: false, d: true, e: true, f: _|_(true:conflicting values: true != false)}",
+		out: "<0>{a: true, b: true, c: false, d: true, e: true, f: _|_(true:conflicting values true and false)}",
 	}, {
 		desc: "basic type",
 		in: `
@@ -261,7 +261,7 @@
 			f: true
 			f: bool
 			`,
-		out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):unsupported op &(int, float)), e: "4", f: true}`,
+		out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):conflicting values int and float (mismatched types int and float)), e: "4", f: true}`, // TODO: eliminate redundancy
 	}, {
 		desc: "strings and bytes",
 		in: `
@@ -285,8 +285,8 @@
 			`b1: 'abcabcabc', ` +
 			`b2: 'abcabc', ` +
 
-			`e0: _|_(("a" + ''):unsupported op +(string, bytes)), ` +
-			`e1: _|_(('b' + "c"):unsupported op +(bytes, string))` +
+			`e0: _|_(("a" + ''):invalid operation "a" + '' (mismatched types string and bytes)), ` +
+			`e1: _|_(('b' + "c"):invalid operation 'b' + "c" (mismatched types bytes and string))` +
 			`}`,
 	}, {
 		desc: "escaping",
@@ -330,7 +330,7 @@
 			e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8]
 			e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5]
 			`,
-		out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, int)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: [1,2,4,_|_((<=5 & 8):8 not within bound <=5)], e5: [1,2,4,_|_((<=5 & 8):8 not within bound <=5)]}`,
+		out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):conflicting values [] and 4 (mismatched types list and int)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: [1,2,4,_|_((<=5 & 8):invalid value 8 (out of bound <=5))], e5: [1,2,4,_|_((<=5 & 8):invalid value 8 (out of bound <=5))]}`,
 	}, {
 		desc: "list arithmetic",
 		in: `
@@ -377,7 +377,7 @@
 			e: 1                       // 1 & {a:3}
 			e: {a:3}
 			`,
-		out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):unsupported op &(int, struct))}",
+		out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):conflicting values 1 and {a: 3} (mismatched types int and struct))}",
 	}, {
 		desc: "disjunctions",
 		in: `
@@ -429,7 +429,7 @@
 			p: +true
 			m: -false
 		`,
-		out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):unsupported op &(int, string)), e2: _|_((1 & string):unsupported op &(int, string)), b: _|_(!int:unary '!' requires bool value, found int), p: _|_(+true:unary '+' requires numeric value, found bool), m: _|_(-false:unary '-' requires numeric value, found bool)}`,
+		out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):conflicting values int and string (mismatched types int and string)), e2: _|_((1 & string):conflicting values 1 and string (mismatched types int and string)), b: _|_(!int:invalid operation !int (! int)), p: _|_(+true:invalid operation +true (+ bool)), m: _|_(-false:invalid operation -false (- bool))}`,
 	}, {
 		desc: "comparison",
 		in: `
@@ -443,7 +443,7 @@
 			seq: "a" + "b" == "ab"
 			err: 2 == "s"
 		`,
-		out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):unsupported op ==(int, string))}`,
+		out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):invalid operation 2 == "s" (mismatched types int and string))}`,
 	}, {
 		desc: "null",
 		in: `
@@ -510,9 +510,9 @@
 			x: x + 1
 		`,
 		out: `<0>{` +
-			`a: _|_((210 & 200):conflicting values: 210 != 200), ` +
-			`b: _|_((210 & 200):conflicting values: 210 != 200), ` +
-			`x: _|_((100 & 101):conflicting values: 100 != 101)}`,
+			`a: _|_((210 & 200):conflicting values 210 and 200), ` +
+			`b: _|_((210 & 200):conflicting values 210 and 200), ` +
+			`x: _|_((100 & 101):conflicting values 100 and 101)}`,
 		// TODO: find a way to mark error in data.
 	}}
 	rewriteHelper(t, testCases, evalPartial)
@@ -588,7 +588,7 @@
 				e1: 2.0 % (3&int)
 				e2: int & 4.0/2.0
 				`,
-		out: `<0>{v1: 5e+11, v2: true, n1: 1, v5: 2, e1: 2.0, e2: _|_((int & 2):unsupported op &(int, float))}`,
+		out: `<0>{v1: 5e+11, v2: true, n1: 1, v5: 2, e1: 2.0, e2: _|_((int & (4.0 / 2.0)):conflicting values int and (4.0 / 2.0) (mismatched types int and float))}`,
 	}, {
 		desc: "inequality",
 		in: `
@@ -719,15 +719,15 @@
 
 			`s30: int & >0, ` +
 
-			`e1: _|_((!=null & null):null excluded by !=null), ` +
-			`e2: _|_((!=null & null):null excluded by !=null), ` +
-			`e3: _|_((>1 & 1):1 not within bound >1), ` +
-			`e4: _|_((<0 & 0):0 not within bound <0), ` +
-			`e5: _|_(incompatible bounds >1 and <0), ` +
-			`e6: _|_(incompatible bounds >11 and <11), ` +
-			`e7: _|_(incompatible bounds >=11 and <11), ` +
-			`e8: _|_(incompatible bounds >11 and <=11), ` +
-			`e9: _|_((>"a" & <1):unsupported op &(string, number))}`,
+			`e1: _|_((!=null & null):invalid value null (excluded by !=null)), ` +
+			`e2: _|_((!=null & null):invalid value null (excluded by !=null)), ` +
+			`e3: _|_((>1 & 1):invalid value 1 (out of bound >1)), ` +
+			`e4: _|_((<0 & 0):invalid value 0 (out of bound <0)), ` +
+			`e5: _|_(conflicting bounds >1 and <0), ` +
+			`e6: _|_(conflicting bounds >11 and <11), ` +
+			`e7: _|_(conflicting bounds >=11 and <11), ` +
+			`e8: _|_(conflicting bounds >11 and <=11), ` +
+			`e9: _|_((>"a" & <1):conflicting values >"a" and <1 (mismatched types string and number))}`,
 	}, {
 		desc: "custom validators",
 		in: `
@@ -741,7 +741,7 @@
 		`,
 		out: `<0>{` +
 			`a: "after", ` +
-			`b: _|_(builtin:ContainsAny ("c"):value "dog" not in ContainsAny("c"))` +
+			`b: _|_(builtin:ContainsAny ("c"):invalid value "dog" (does not satisfy strings.ContainsAny("c")))` +
 			`}`,
 	}, {
 		desc: "null coalescing",
@@ -750,7 +750,7 @@
 			b: a.x | "b"
 			c: a["x"] | "c"
 			`,
-		out: `<1>{a: null, b: "b", c: "c"}`,
+		out: `<0>{a: null, b: "b", c: "c"}`,
 	}, {
 		desc: "reference across tuples and back",
 		// Tests that it is okay to partially evaluate structs.
@@ -867,8 +867,8 @@
 			`i2: 3, ` +
 			`t0: [<3>{a: 8}], ` +
 			`t1: [, ...int], ` +
-			`e0: _|_(([<4>{},<4>{}] & [<5>{}]):incompatible list lengths: conflicting values: 2 != 1), ` +
-			`e1: _|_(([, ...int] & [, ...float]):incompatible list types: unsupported op &(int, float): )` +
+			`e0: _|_(([<4>{},<4>{}] & [<5>{}]):conflicting list lengths: conflicting values 2 and 1), ` +
+			`e1: _|_((int & float):conflicting list element types: conflicting values int and float (mismatched types int and float))` +
 			`}`,
 	}, {
 		// TODO: consider removing list arithmetic altogether. It is no longer
@@ -1057,7 +1057,7 @@
 		in: `
 			a: "a" & 1
 			`,
-		out: `<0>{a: _|_(("a" & 1):unsupported op &(string, int))}`,
+		out: `<0>{a: _|_(("a" & 1):conflicting values "a" and 1 (mismatched types string and int))}`,
 	}, {
 		desc: "structs",
 		in: `
@@ -1260,15 +1260,14 @@
 			`a1: 3, ` +
 			`a2: 1, ` +
 			`a3: 5, ` +
-			`a4: _|_((<=5 & 6):6 not within bound <=5), ` +
-			`a5: _|_((>=1 & 0):0 not within bound >=1), ` +
+			`a4: _|_((<=5 & 6):invalid value 6 (out of bound <=5)), ` +
+			`a5: _|_((>=1 & 0):invalid value 0 (out of bound >=1)), ` +
 			`a6: 3, ` +
 			`a7: 1, ` +
 			`a8: 5, ` +
 
-			// TODO: improve error
-			`a9: _|_((<=5 & 6):6 not within bound <=5), ` +
-			`a10: _|_((>=1 & 0):0 not within bound >=1), ` +
+			`a9: _|_((<=5 & 6):invalid value 6 (out of bound <=5)), ` +
+			`a10: _|_((>=1 & 0):invalid value 0 (out of bound >=1)), ` +
 
 			`b1: (>=1 & <=5), ` +
 			`b2: 1, ` +
@@ -1276,18 +1275,18 @@
 			`b4: (>=2 & <=3), ` +
 			`b5: (>=3 & <=5), ` +
 			`b6: 5, ` +
-			`b7: _|_(incompatible bounds >=6 and <=5), ` +
+			`b7: _|_(conflicting bounds >=6 and <=5), ` +
 			`b8: (>=1 & <=5), ` +
 			`b9: 1, ` +
 			`b10: 5, ` +
 			`b11: (>=2 & <=3), ` +
 			`b12: (>=3 & <=5), ` +
 			`b13: 5, ` +
-			`b14: _|_(incompatible bounds >=6 and <=5), ` +
+			`b14: _|_(conflicting bounds >=6 and <=5), ` +
 			`c1: (int & >=1 & <=5), ` +
 			`c2: (<=5 & int & >=1), ` +
-			`c3: _|_((string & >=1):unsupported op &(string, number)), ` +
-			`c4: _|_(((>=1 & <=5) & string):unsupported op &(number, string)), ` +
+			`c3: _|_((string & >=1):conflicting values string and >=1 (mismatched types string and number)), ` +
+			`c4: _|_(((>=1 & <=5) & string):conflicting values (>=1 & <=5) and string (mismatched types number and string)), ` +
 			`s1: "e", ` +
 			`s2: "ee", ` +
 			`n1: (>=1 & <=2), ` +
@@ -1308,7 +1307,7 @@
 			e1: 100_000
 		`,
 		out: `<0>{k1: 44, k2: -8000000000, ` +
-			`e1: _|_((int & <=32767 & 100000):100000 not within bound int & <=32767)}`,
+			`e1: _|_((int & <=32767 & 100000):invalid value 100000 (out of bound int & <=32767))}`,
 	}, {
 		desc: "field comprehensions",
 		in: `
@@ -1342,10 +1341,10 @@
 		out: `<0>{` +
 			`a1: <1>{a: (=~"oo" & =~"fo"), b: =~"oo", c: =~"fo"}, ` +
 			`a2: <2>{a: "foo", b: =~"oo", c: =~"fo"}, ` +
-			`a3: <3>{a: _|_((=~"oo" & "bar"):"bar" does not match =~"oo"), b: =~"oo", c: =~"fo"}, ` +
+			`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: _|_((builtin:or ([<7>.b,<7>.c]) & "foo"):empty disjunction: conflicting values: baz != foo), b: "baz", c: "bar"}}`,
+			`o3: <6>{a: _|_((builtin:or ([<7>.b,<7>.c]) & "foo"):empty disjunction: conflicting values "baz" and "foo"), b: "baz", c: "bar"}}`,
 	}, {
 		desc: "self-reference cycles conflicts with strings",
 		in: `
@@ -1355,7 +1354,7 @@
 			}
 			a x: "hey"
 		`,
-		out: `<0>{a: <1>{x: _|_(("hey!?" & "hey"):conflicting values: hey!? != hey), y: "hey!"}}`,
+		out: `<0>{a: <1>{x: _|_(("hey!?" & "hey"):conflicting values "hey!?" and "hey"), y: "hey!"}}`,
 	}, {
 		desc: "resolved self-reference cycles with disjunctions",
 		in: `
@@ -1453,11 +1452,11 @@
 			`xd5: 10, ` +
 			`xd3: 6, ` +
 
-			`xe1: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe2: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe4: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe5: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe3: _|_((6 & 7):conflicting values: 6 != 7), ` +
+			`xe1: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe2: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe4: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe5: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe3: _|_((6 & 7):conflicting values 6 and 7), ` +
 
 			`xf1: 8, ` +
 			`xf2: 8, ` +
@@ -1543,11 +1542,11 @@
 			`xd5: 10, ` +
 			`xd3: 6, ` +
 
-			`xe1: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe2: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe4: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe5: _|_((6 & 7):conflicting values: 6 != 7), ` +
-			`xe3: _|_((6 & 7):conflicting values: 6 != 7), ` +
+			`xe1: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe2: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe4: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe5: _|_((6 & 7):conflicting values 6 and 7), ` +
+			`xe3: _|_((6 & 7):conflicting values 6 and 7), ` +
 
 			`z1: (*11 | 13), ` + // 13 is eliminated with evalFull
 			`z2: 10, ` +
@@ -1562,7 +1561,7 @@
 		in: `
 				a: 8000.9
 				a: 7080 | int`,
-		out: `<0>{a: _|_((8000.9 & int):unsupported op &(float, int))}`,
+		out: `<0>{a: _|_((8000.9 & (int | int)):conflicting values 8000.9 and int (mismatched types float and int))}`, // TODO: fix repetition
 	}, {
 		desc: "resolve all disjunctions",
 		in: `
@@ -1907,7 +1906,7 @@
 			x: {a:1}|{a:2}
 			y: x & {a:3}
 		`,
-		out: `<3>{x: _|_((<0>{a: 1} | <1>{a: 2}):more than one element remaining (<0>{a: 1} and <1>{a: 2})), y: _|_((<4>.x & <5>{a: 3}):empty disjunction: <2>{a: (1 & 3)})}`,
+		out: `<0>{x: _|_((<1>{a: 1} | <2>{a: 2}):more than one element remaining ({a: 1} and {a: 2})), y: _|_((<3>.x & <4>{a: 3}):empty disjunction: {a: (1 & 3)})}`,
 	}, {
 		desc: "cannot resolve references that would be ambiguous",
 		in: `
@@ -1923,14 +1922,14 @@
 		c1: (*{a:1} | {b:1}) & c2
 		c2: (*{a:2} | {b:2}) & c1
 		`,
-		out: `<4>{` +
-			`a1: _|_(((*0 | 1) & (<5>.a3 - <5>.a2)):cycle detected), ` +
+		out: `<0>{` +
+			`a1: _|_(((*0 | 1) & (<1>.a3 - <1>.a2)):cycle detected), ` +
 			`a3: 1, ` +
-			`a2: _|_(((*0 | 1) & (<5>.a3 - <5>.a1)):cycle detected), ` +
+			`a2: _|_(((*0 | 1) & (<1>.a3 - <1>.a1)):cycle detected), ` +
 			`b1: _|_((0 | 1 | *_|_):more than one element remaining (0 and 1)), ` +
 			`b2: _|_((0 | 1 | *_|_):more than one element remaining (0 and 1)), ` +
-			`c1: _|_((<0>{a: 1, b: 2} | <1>{a: 2, b: 1} | *_|_):more than one element remaining (<0>{a: 1, b: 2} and <1>{a: 2, b: 1})), ` +
-			`c2: _|_((<2>{a: 2, b: 1} | <3>{a: 1, b: 2} | *_|_):more than one element remaining (<2>{a: 2, b: 1} and <3>{a: 1, b: 2}))}`,
+			`c1: _|_((<2>{a: 1, b: 2} | <3>{a: 2, b: 1} | *_|_):more than one element remaining ({a: 1, b: 2} and {a: 2, b: 1})), ` +
+			`c2: _|_((<4>{a: 2, b: 1} | <5>{a: 1, b: 2} | *_|_):more than one element remaining ({a: 2, b: 1} and {a: 1, b: 2}))}`,
 	}}
 	rewriteHelper(t, testCases, evalFull)
 }
diff --git a/cue/types.go b/cue/types.go
index be07f26..ca3bc09 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -330,13 +330,13 @@
 
 var (
 	// ErrBelow indicates that a value was rounded down in a conversion.
-	ErrBelow = errors.New("cue: value was rounded down")
+	ErrBelow = errors.New("value was rounded down")
 
 	// ErrAbove indicates that a value was rounded up in a conversion.
-	ErrAbove = errors.New("cue: value was rounded up")
+	ErrAbove = errors.New("value was rounded up")
 
 	// ErrInfinite indicates that a value is infinite.
-	ErrInfinite = errors.New("cue: infinite")
+	ErrInfinite = errors.New("infinite")
 )
 
 // Int converts the underlying integral number to an big.Int. It reports an
@@ -711,12 +711,12 @@
 		return nil, toMarshalErr(v, x.(*bottom))
 	default:
 		if k.hasReferences() {
-			return nil, marshalErrf(v, x, "value %q contains unresolved references", debugStr(ctx, x))
+			return nil, marshalErrf(v, x, "value %q contains unresolved references", ctx.str(x))
 		}
 		if !k.isGround() {
-			return nil, marshalErrf(v, x, "cannot convert incomplete value %q to JSON", debugStr(ctx, x))
+			return nil, marshalErrf(v, x, "cannot convert incomplete value %q to JSON", ctx.str(x))
 		}
-		return nil, marshalErrf(v, x, "cannot convert value %q of type %T to JSON", debugStr(ctx, x), x)
+		return nil, marshalErrf(v, x, "cannot convert value %q of type %T to JSON", ctx.str(x), x)
 	}
 }
 
@@ -880,7 +880,8 @@
 	got := x.kind()
 	if want != bottomKind {
 		if got&want&concreteKind == bottomKind {
-			return ctx.mkErr(x, "not of right kind (%v vs %v)", got, want)
+			return ctx.mkErr(x, "cannot use value %v (type %s) as %s",
+				v.ctx().str(x), got, want)
 		}
 		if !got.isGround() {
 			return ctx.mkErr(x, codeIncomplete,
@@ -1171,7 +1172,7 @@
 		fmt.Fprint(state, "<nil>")
 		return
 	}
-	io.WriteString(state, debugStr(ctx, v.path.cache))
+	_, _ = io.WriteString(state, ctx.str(v.path.cache))
 }
 
 // Reference returns path from the root of the instance referred to by this
@@ -1387,7 +1388,7 @@
 		}
 	default:
 		if !x.kind().isGround() {
-			return ctx.mkErr(v, "incomplete value (%v)", debugStr(ctx, v))
+			return ctx.mkErr(v, "incomplete value (%v)", ctx.str(v))
 		}
 	}
 	return nil
@@ -1429,9 +1430,10 @@
 // The returned attribute will return an error for any of its methods if there
 // is no attribute for the requested key.
 func (v Value) Attribute(key string) Attribute {
+	const msgNotExist = "attribute %q does not exist"
 	// look up the attributes
 	if v.path == nil || v.path.attrs == nil {
-		return Attribute{err: v.toErr(errNotExists)}
+		return Attribute{err: errors.Newf(token.NoPos, msgNotExist, key)}
 	}
 	for _, a := range v.path.attrs.attr {
 		if a.key() != key {
@@ -1443,7 +1445,7 @@
 		}
 		return at
 	}
-	return Attribute{err: v.toErr(errNotExists)}
+	return Attribute{err: errors.Newf(token.NoPos, msgNotExist, key)}
 }
 
 // An Attribute contains meta data about a field.
diff --git a/cue/types_test.go b/cue/types_test.go
index c715819..d4d6dd1 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -220,8 +220,8 @@
 		errU:  ErrBelow.Error(),
 	}, {
 		value:  "1.0",
-		err:    "not of right kind (float vs int)",
-		errU:   "not of right kind (float vs int)",
+		err:    "cannot use value 1.0 (type float) as int",
+		errU:   "cannot use value 1.0 (type float) as int",
 		notInt: true,
 	}, {
 		value:  "int",
@@ -478,7 +478,7 @@
 		err:   "from source",
 	}, {
 		value: `"str"`,
-		err:   "not of right kind (string vs null)",
+		err:   "cannot use value \"str\" (type string) as null",
 	}, {
 		value: `null`,
 	}, {
@@ -503,7 +503,7 @@
 		err:   "from source",
 	}, {
 		value: `"str"`,
-		err:   "not of right kind (string vs bool)",
+		err:   "cannot use value \"str\" (type string) as bool",
 	}, {
 		value: `true`,
 		bool:  true,
@@ -535,7 +535,7 @@
 		err:   "from source",
 	}, {
 		value: `"str"`,
-		err:   "not of right kind (string vs list)",
+		err:   "cannot use value \"str\" (type string) as list",
 	}, {
 		value: `[]`,
 		res:   "[]",
@@ -583,7 +583,7 @@
 		err:   "from source",
 	}, {
 		value: `"str"`,
-		err:   "not of right kind (string vs struct)",
+		err:   "cannot use value \"str\" (type string) as struct",
 	}, {
 		value: `{}`,
 		res:   "{}",
@@ -697,12 +697,12 @@
 		ok:    true,
 	}, {
 		value: `*{a:1,b:2}|{a:1}|{b:2}`,
-		def:   "<0>{a: 1, b: 2}",
-		val:   "<0>{a: 1}|<0>{b: 2}",
+		def:   "{a: 1, b: 2}",
+		val:   "{a: 1}|{b: 2}",
 		ok:    true,
 	}, {
 		value: `{a:1}&{b:2}`,
-		def:   `<0>{a: 1, b: 2}`,
+		def:   `{a: 1, b: 2}`,
 		val:   ``,
 		ok:    false,
 	}}
@@ -756,7 +756,7 @@
 		// 	length: "2",
 	}, {
 		input:  "3",
-		length: "_|_(3:len not supported for type 8)",
+		length: "_|_(len not supported for type 8)", // TODO: fix kind name
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.input, func(t *testing.T) {
@@ -1145,7 +1145,7 @@
 	}, {
 		config: config,
 		path:   strList(),
-		str:    "<0>{a: <1>{a: 0, b: 1, c: 2}, b: <2>{d: <0>.a.a, e: int}",
+		str:    "{a: {a: 0, b: 1, c: 2}, b: {d: a.a, e: int}",
 	}, {
 		config: config,
 		path:   strList("a", "a"),
@@ -1153,7 +1153,7 @@
 	}, {
 		config: config,
 		path:   strList("a"),
-		str:    "<0>{a: 0, b: 1, c: 2}",
+		str:    "{a: 0, b: 1, c: 2}",
 	}, {
 		config: config,
 		path:   strList("b", "d"),
@@ -1166,7 +1166,7 @@
 	}, {
 		config: config,
 		path:   strList("b", "d", "lookup in non-struct"),
-		str:    "not of right kind (int vs struct)",
+		str:    "cannot use value 0 (type int) as struct",
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.str, func(t *testing.T) {
@@ -1214,15 +1214,15 @@
 	}, {
 		path: "a",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}, {
 		path: "xx",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}, {
 		path: "e",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}}
 	for _, tc := range testCases {
 		t.Run(tc.path+"-"+tc.attr, func(t *testing.T) {
@@ -1267,7 +1267,7 @@
 	}, {
 		path: "e",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}, {
 		path: "b",
 		attr: "foo",
@@ -1315,7 +1315,7 @@
 	}, {
 		path: "e",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}, {
 		path: "b",
 		attr: "foo",
@@ -1377,7 +1377,7 @@
 	}, {
 		path: "e",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}, {
 		path: "b",
 		attr: "foo",
@@ -1452,7 +1452,7 @@
 	}, {
 		path: "e",
 		attr: "bar",
-		err:  errors.New("undefined value"),
+		err:  errors.New(`attribute "bar" does not exist`),
 	}, {
 		path: "b",
 		attr: "foo",
@@ -1688,7 +1688,7 @@
 	}, {
 		value: `(a.b)
 		a: {}`,
-		out: `_|_(<0>.a.b:undefined field "b")`,
+		out: `_|_(undefined field "b")`,
 	}, {
 		value: `true`,
 		out:   `true`,
diff --git a/cue/value.go b/cue/value.go
index 88a8af2..859c930 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -76,7 +76,7 @@
 	}
 	got := x.kind()
 	if got&want&concreteKind == bottomKind && want != bottomKind {
-		return ctx.mkErr(x, "not of right kind (%v vs %v)", got, want)
+		return ctx.mkErr(x, "cannot use value %v (type %s) as %s", x, got, want)
 	}
 	if !got.isGround() {
 		return ctx.mkErr(x, codeIncomplete,
@@ -1161,7 +1161,7 @@
 
 func (x *binaryExpr) kind() kind {
 	// TODO: cache results
-	kind, _ := matchBinOpKind(x.op, x.left.kind(), x.right.kind())
+	kind, _, _ := matchBinOpKind(x.op, x.left.kind(), x.right.kind())
 	return kind | nonGround
 }
 
@@ -1286,7 +1286,7 @@
 		err := x.values[0].val
 		if !isBottom(err) {
 			// TODO: use format instead of debugStr.
-			err = ctx.mkErr(src, debugStr(ctx, err))
+			err = ctx.mkErr(src, ctx.str(err))
 		}
 		return mVal{ctx.mkErr(src, "empty disjunction: %v", err), false}
 	case 1:
diff --git a/cuego/examples_test.go b/cuego/examples_test.go
index 0a0c0f4..9e160a9 100644
--- a/cuego/examples_test.go
+++ b/cuego/examples_test.go
@@ -42,7 +42,7 @@
 	//Output:
 	// completed: cuego_test.Sum{A:1, B:5, C:6} (err: <nil>)
 	// completed: cuego_test.Sum{A:2, B:6, C:8} (err: <nil>)
-	// empty disjunction: unsupported op &(null, struct)
+	// empty disjunction: invalid operation null & {A: 2, B: 3, C: 8} (mismatched types null and struct)
 }
 
 func ExampleConstrain() {
@@ -91,6 +91,6 @@
 	//Output:
 	// error: <nil>
 	// validate: <nil>
-	// validate: 39 not within bound <=12
-	// validate: "foo.jso" does not match =~".json$"
+	// validate: invalid value 39 (out of bound <=12)
+	// validate: invalid value "foo.jso" (does not match =~".json$")
 }
diff --git a/doc/tutorial/basics/bottom.md b/doc/tutorial/basics/bottom.md
index 46515da..2a36afa 100644
--- a/doc/tutorial/basics/bottom.md
+++ b/doc/tutorial/basics/bottom.md
@@ -7,7 +7,7 @@
 Specifying duplicate fields with conflicting values results in an error
 or bottom.
 _Bottom_ is a special value in CUE, denoted `_|_`, that indicates an
-error such as incompatible values.
+error such as conflicting values.
 Any error in CUE results in `_|_`.
 Logically all errors are equal, although errors may be associated with
 metadata such as an error message.
@@ -31,8 +31,8 @@
 <!-- result -->
 `$ cue eval -i bottom.cue`
 ```
-a: _|_ /* conflicting values: 4 != 5 */
-l: [1, _|_ /* conflicting values: 2 != 3 */]
+a: _|_ /* conflicting values 4 and 5 */
+l: [1, _|_ /* conflicting values 2 and 3 */]
 list: [0, 1, 2]
 val: _|_ /* index 3 out of bounds */
 ```
diff --git a/doc/tutorial/basics/numbers.md b/doc/tutorial/basics/numbers.md
index d2fb464..deb8329 100644
--- a/doc/tutorial/basics/numbers.md
+++ b/doc/tutorial/basics/numbers.md
@@ -34,6 +34,6 @@
 ```
 a: 4
 b: 4
-c: _|_ /* unsupported op &(int, float) */
+c: _|_ /* conflicting values int and 4.0 (mismatched types int and float) */
 d: 4
 ```
\ No newline at end of file
diff --git a/doc/tutorial/basics/rangedef.md b/doc/tutorial/basics/rangedef.md
index d0939c3..f7fe8ab 100644
--- a/doc/tutorial/basics/rangedef.md
+++ b/doc/tutorial/basics/rangedef.md
@@ -43,7 +43,7 @@
 <!-- result -->
 `$ cue eval -i range.cue`
 ```
-a: _|_ /* -1 not within bound int & >=0 */
+a: _|_ /* invalid value -1 (out of bound int & >=0) */
 b: 128
 c: 2000000000
 ```
\ No newline at end of file
diff --git a/doc/tutorial/basics/ranges.md b/doc/tutorial/basics/ranges.md
index e7677fe..b552bf4 100644
--- a/doc/tutorial/basics/ranges.md
+++ b/doc/tutorial/basics/ranges.md
@@ -35,9 +35,9 @@
 `$ cue eval -i bounds.cue`
 ```
 a:  3.5
-b:  _|_ /* unsupported op &(int, float) */
+b:  _|_ /* conflicting values ri and 3.5 (mismatched types int and float) */
 c:  3
 d:  "ma"
-e:  _|_ /* "mu" not within bound <"mo" */
+e:  _|_ /* invalid value "mu" (out of bound <"mo") */
 r1: >=5 & <8
 ```
diff --git a/doc/tutorial/basics/regexp.md b/doc/tutorial/basics/regexp.md
index 504f40d..3f0e6de 100644
--- a/doc/tutorial/basics/regexp.md
+++ b/doc/tutorial/basics/regexp.md
@@ -35,5 +35,5 @@
 b: true
 c: =~"^[a-z]{3}$"
 d: "foo"
-e: _|_ /* "foo bar" does not match =~"^[a-z]{3}$" */
+e: _|_ /* invalid value "foo bar" (does not match =~"^[a-z]{3}$") */
 ```
\ No newline at end of file
diff --git a/doc/tutorial/basics/unification.md b/doc/tutorial/basics/unification.md
index e631768..f8254ac 100644
--- a/doc/tutorial/basics/unification.md
+++ b/doc/tutorial/basics/unification.md
@@ -45,16 +45,16 @@
 q: {
     x: 1
     y: 2
-    z: _|_ /* conflicting values: 3 != 4 */
+    z: _|_ /* conflicting values 3 and 4 */
 }
 r: {
     x: 1
     y: 2
-    z: _|_ /* conflicting values: 3 != 4 */
+    z: _|_ /* conflicting values 3 and 4 */
 }
 s: {
     x: 1
     y: 2
-    z: _|_ /* conflicting values: 4 != 3 */
+    z: _|_ /* conflicting values 4 and 3 */
 }
 ```
\ No newline at end of file