internal/core/adt: better message for permanently non-concrete values

Fixes #282

Change-Id: I44270517441c04136e09bb2424e406c92a041668
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7821
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/basicrewrite/015_types.txtar b/cue/testdata/basicrewrite/015_types.txtar
index 04d05fd..1616b60 100644
--- a/cue/testdata/basicrewrite/015_types.txtar
+++ b/cue/testdata/basicrewrite/015_types.txtar
@@ -45,7 +45,7 @@
 e2: conflicting values 1 and string (mismatched types int and string):
     ./in.cue:6:5
     ./in.cue:6:9
-b: value can never become concrete:
+b: invalid operand int ('!' requires concrete value):
     ./in.cue:7:5
 p: invalid operation +true (+ bool):
     ./in.cue:8:5
@@ -70,7 +70,7 @@
     //     ./in.cue:6:9
   }
   b: (_|_){
-    // [eval] b: value can never become concrete:
+    // [eval] b: invalid operand int ('!' requires concrete value):
     //     ./in.cue:7:5
   }
   p: (_|_){
diff --git a/cue/testdata/eval/incomplete.txtar b/cue/testdata/eval/incomplete.txtar
index 057693d..a0a6172 100644
--- a/cue/testdata/eval/incomplete.txtar
+++ b/cue/testdata/eval/incomplete.txtar
@@ -15,8 +15,21 @@
   b: c-a
   c: a+b & >=5
 }
+
+// Issue #129
+permanentlyIncompleteOperands: {
+    a: string + ":" + string
+    a: "golang/go:1.13.5"
+}
+
 -- out/eval --
-(struct){
+Errors:
+permanentlyIncompleteOperands.a: invalid operand string ('+' requires concrete value):
+    ./in.cue:18:8
+
+Result:
+(_|_){
+  // [eval]
   s: (string){ string }
   e1: (_|_){
     // [incomplete] e1: non-concrete value string in operand to +:
@@ -42,6 +55,13 @@
       // [cycle] cycle error
     }
   }
+  permanentlyIncompleteOperands: (_|_){
+    // [eval]
+    a: (_|_){
+      // [eval] permanentlyIncompleteOperands.a: invalid operand string ('+' requires concrete value):
+      //     ./in.cue:18:8
+    }
+  }
 }
 -- out/compile --
 --- in.cue
@@ -57,4 +77,8 @@
     b: (〈0;c〉 - 〈0;a〉)
     c: ((〈0;a〉 + 〈0;b〉) & >=5)
   }
+  permanentlyIncompleteOperands: {
+    a: ((string + ":") + string)
+    a: "golang/go:1.13.5"
+  }
 }
diff --git a/cue/testdata/lists/020_list_arithmetic.txtar b/cue/testdata/lists/020_list_arithmetic.txtar
index 3f0360c..8992835 100644
--- a/cue/testdata/lists/020_list_arithmetic.txtar
+++ b/cue/testdata/lists/020_list_arithmetic.txtar
@@ -338,7 +338,7 @@
 }
 -- out/eval --
 Errors:
-l5: value can never become concrete:
+l5: invalid operand int ('*' requires concrete value):
     ./in.cue:6:12
 
 Result:
@@ -368,7 +368,7 @@
     //     ./in.cue:5:5
   }
   l5: (_|_){
-    // [eval] l5: value can never become concrete:
+    // [eval] l5: invalid operand int ('*' requires concrete value):
     //     ./in.cue:6:12
   }
   l6: (#list){
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index fc19b80..bac1a27 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -228,10 +228,11 @@
 	return e.DynamicLabel
 }
 
-func (c *OpContext) concreteIsPossible(x Expr) bool {
+func (c *OpContext) concreteIsPossible(op Op, x Expr) bool {
 	if v, ok := x.(Value); ok {
 		if v.Concreteness() > Concrete {
-			c.AddErrf("value can never become concrete")
+			c.AddErrf("invalid operand %s ('%s' requires concrete value)",
+				c.Str(x), op)
 			return false
 		}
 	}
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 34ce843..1ed374f 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -768,7 +768,7 @@
 }
 
 func (x *UnaryExpr) evaluate(c *OpContext) Value {
-	if !c.concreteIsPossible(x.X) {
+	if !c.concreteIsPossible(x.Op, x.X) {
 		return nil
 	}
 	v := c.value(x.X)
@@ -838,7 +838,7 @@
 		return v
 	}
 
-	if !c.concreteIsPossible(x.X) || !c.concreteIsPossible(x.Y) {
+	if !c.concreteIsPossible(x.Op, x.X) || !c.concreteIsPossible(x.Op, x.Y) {
 		return nil
 	}