internal/core/eval: fix interpolation of numbers

Fixes #487

Change-Id: I0a8b0e45cf9d161a8683864764b969d20306fd94
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6948
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/issue315.txt b/cmd/cue/cmd/testdata/script/issue315.txt
index a26a347..7442ffe 100644
--- a/cmd/cue/cmd/testdata/script/issue315.txt
+++ b/cmd/cue/cmd/testdata/script/issue315.txt
@@ -3,7 +3,7 @@
 cmp stderr expect-stderr
 
 -- expect-stderr --
-invalid interpolation: incomplete string value 'string':
+invalid interpolation: non-concrete value string (type string):
     ./file.cue:12:1
 -- file.cue --
 #X: {
diff --git a/cue/testdata/export/019.txtar b/cue/testdata/export/019.txtar
index ea12d29..eefbf38 100644
--- a/cue/testdata/export/019.txtar
+++ b/cue/testdata/export/019.txtar
@@ -15,16 +15,10 @@
   }
 }
 -- out/eval --
-Errors:
-b: invalid interpolation: cannot use >=0 & <=10 (type number) as type string:
-    ./in.cue:1:20
-
-Result:
-(_|_){
-  // [eval]
+(struct){
   a: (number){ &(>=0, <=10) }
   b: (_|_){
-    // [eval] b: invalid interpolation: cannot use >=0 & <=10 (type number) as type string:
+    // [incomplete] b: invalid interpolation: non-concrete value >=0 & <=10 (type number):
     //     ./in.cue:1:20
   }
 }
diff --git a/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar b/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
index bcedb40..121daaf 100644
--- a/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
+++ b/cue/testdata/fulleval/026_dont_convert_incomplete_errors_to_non-incomplete.txtar
@@ -83,7 +83,7 @@
     //     ./in.cue:11:6
   }
   s1: (_|_){
-    // [incomplete] s1: invalid interpolation: incomplete string value 'string':
+    // [incomplete] s1: invalid interpolation: non-concrete value string (type string):
     //     ./in.cue:13:5
   }
   s2: (string){ strings.ContainsAny("dd") }
diff --git a/cue/testdata/interpolation/041_interpolation.txtar b/cue/testdata/interpolation/041_interpolation.txtar
index 06a2adc..f23b618 100644
--- a/cue/testdata/interpolation/041_interpolation.txtar
+++ b/cue/testdata/interpolation/041_interpolation.txtar
@@ -33,7 +33,7 @@
 }
 -- out/eval --
 Errors:
-e: invalid interpolation: cannot use [] (type list) as type string:
+e: invalid interpolation: cannot use [] (type list) as type (string|number):
     ./in.cue:7:4
 
 Result:
@@ -43,16 +43,16 @@
   b: (string){ "one 4 two 4one" }
   c: (string){ "one" }
   d: (_|_){
-    // [incomplete] d: invalid interpolation: incomplete string value '_':
+    // [incomplete] d: invalid interpolation: non-concrete value _ (type _):
     //     ./in.cue:4:4
   }
   u: (_|_){
-    // [incomplete] u: invalid interpolation: incomplete string value '_':
+    // [incomplete] u: invalid interpolation: non-concrete value _ (type _):
     //     ./in.cue:5:4
   }
   r: (_){ _ }
   e: (_|_){
-    // [eval] e: invalid interpolation: cannot use [] (type list) as type string:
+    // [eval] e: invalid interpolation: cannot use [] (type list) as type (string|number):
     //     ./in.cue:7:4
   }
 }
diff --git a/cue/testdata/interpolation/issue487.txtar b/cue/testdata/interpolation/issue487.txtar
new file mode 100644
index 0000000..81a181a
--- /dev/null
+++ b/cue/testdata/interpolation/issue487.txtar
@@ -0,0 +1,66 @@
+-- in.cue --
+t1: {
+	#R: {
+		pos:  uint
+		name: "hello_\(pos)"
+	}
+	a: #R & {pos: 67}
+}
+t2: {
+	#R: {
+		pos:  string
+		name: "hello_\(pos)"
+	}
+	a: #R & {pos: "a"}
+}
+-- out/eval --
+(struct){
+  t1: (struct){
+    #R: (#struct){
+      pos: (int){ &(>=0, int) }
+      name: (_|_){
+        // [incomplete] t1.#R.name: invalid interpolation: non-concrete value >=0 & int (type int):
+        //     ./in.cue:4:9
+      }
+    }
+    a: (#struct){
+      pos: (int){ 67 }
+      name: (string){ "hello_67" }
+    }
+  }
+  t2: (struct){
+    #R: (#struct){
+      pos: (string){ string }
+      name: (_|_){
+        // [incomplete] t2.#R.name: invalid interpolation: non-concrete value string (type string):
+        //     ./in.cue:11:9
+      }
+    }
+    a: (#struct){
+      pos: (string){ "a" }
+      name: (string){ "hello_a" }
+    }
+  }
+}
+-- out/compile --
+--- in.cue
+{
+  t1: {
+    #R: {
+      pos: &(int, >=0)
+      name: "hello_\(〈0;pos〉)"
+    }
+    a: (〈0;#R〉 & {
+      pos: 67
+    })
+  }
+  t2: {
+    #R: {
+      pos: string
+      name: "hello_\(〈0;pos〉)"
+    }
+    a: (〈0;#R〉 & {
+      pos: "a"
+    })
+  }
+}
diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go
index 1a26c5c..db385b8 100644
--- a/internal/core/adt/context.go
+++ b/internal/core/adt/context.go
@@ -738,11 +738,29 @@
 	return c.stringValue(v, nil)
 }
 
+// ToString returns the string value of a numeric or string value.
+func (c *OpContext) ToString(v Value) string {
+	return c.toStringValue(v, StringKind|NumKind, nil)
+
+}
+
 func (c *OpContext) stringValue(v Value, as interface{}) string {
+	return c.toStringValue(v, StringKind, as)
+}
+
+func (c *OpContext) toStringValue(v Value, k Kind, as interface{}) string {
 	v = Unwrap(v)
 	if isError(v) {
 		return ""
 	}
+	if v.Kind()&k == 0 {
+		if as == nil {
+			c.typeError(v, k)
+		} else {
+			c.typeErrorAs(v, k, as)
+		}
+		return ""
+	}
 	switch x := v.(type) {
 	case *String:
 		return x.Str
@@ -754,11 +772,8 @@
 		return x.X.String()
 
 	default:
-		if as == nil {
-			c.typeError(v, StringKind)
-		} else {
-			c.typeErrorAs(v, StringKind, as)
-		}
+		c.addErrf(IncompleteError, c.pos(),
+			"non-concrete value %s (type %s)", c.Str(v), v.Kind())
 	}
 	return ""
 }
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index ece0f98..82df806 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -717,7 +717,7 @@
 	buf := bytes.Buffer{}
 	for _, e := range x.Parts {
 		v := c.value(e)
-		s := c.StringValue(v)
+		s := c.ToString(v)
 		buf.WriteString(s)
 	}
 	if err := c.Err(); err != nil {