cue: support multiplication of strings and bytes

Change-Id: I5df8c4449843a2623e9854ab9b1fc625f98b8821
Reviewed-on: https://cue-review.googlesource.com/c/1543
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/binop.go b/cue/binop.go
index c534fc9..ada9cc9 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -605,7 +605,7 @@
 }
 
 func (x *stringLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
-	switch other.(type) {
+	switch y := other.(type) {
 	// case *basicType:
 	// 	return x
 
@@ -624,7 +624,8 @@
 		case opLss, opLeq, opEql, opNeq, opGeq, opGtr:
 			return cmpTonode(src, op, strings.Compare(x.str, str))
 		case opAdd:
-			return &stringLit{binSrc(src.Pos(), op, x, other), x.str + str}
+			src := binSrc(src.Pos(), op, x, other)
+			return &stringLit{src, x.str + str}
 		case opMat:
 			b, err := regexp.MatchString(str, x.str)
 			if err != nil {
@@ -638,6 +639,12 @@
 			}
 			return boolTonode(src, !b)
 		}
+	case *numLit:
+		switch op {
+		case opMul:
+			src := binSrc(src.Pos(), op, x, other)
+			return &stringLit{src, strings.Repeat(x.str, y.intValue(ctx))}
+		}
 	}
 	return ctx.mkIncompatible(src, op, x, other)
 }
@@ -664,6 +671,13 @@
 			copy = append(copy, b...)
 			return &bytesLit{binSrc(src.Pos(), op, x, other), copy}
 		}
+
+	case *numLit:
+		switch op {
+		case opMul:
+			src := binSrc(src.Pos(), op, x, other)
+			return &bytesLit{src, bytes.Repeat(x.b, y.intValue(ctx))}
+		}
 	}
 	return ctx.mkIncompatible(src, op, x, other)
 }
diff --git a/cue/kind.go b/cue/kind.go
index 7355965..8cf7772 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -201,11 +201,13 @@
 			}
 			return bottomKind, false
 		}
-		if a.isAnyOf(listKind) && b.isAnyOf(intKind) {
-			return a, false
-		}
-		if b.isAnyOf(listKind) && a.isAnyOf(intKind) {
-			return b, true
+		if op == opMul {
+			if a.isAnyOf(listKind|stringKind|bytesKind) && b.isAnyOf(intKind) {
+				return a, false
+			}
+			if b.isAnyOf(listKind|stringKind|bytesKind) && a.isAnyOf(intKind) {
+				return b, true
+			}
 		}
 		// non-overlapping types
 		if a&scalarKinds == 0 || b&scalarKinds == 0 {
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 7b5a09d..256177d 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -147,7 +147,6 @@
 			i2: 2 & int
 
 			sum: -1 + +2        // 1
-			str: "foo" + "bar"  // "foobar"
 			div1: 2.0 / 3 * 6   // 4
 			div2: 2 / 3 * 6     // 4
 			rem: 2 % 3          // 2
@@ -171,7 +170,6 @@
 			`,
 		out: `<0>{i1: 1, i2: 2, ` +
 			`sum: 1, ` +
-			`str: "foobar", ` +
 			`div1: 4.00000000000000000000000, ` +
 			`div2: 4.00000000000000000000000, ` +
 			`rem: 2, ` +
@@ -272,6 +270,25 @@
 			`,
 		out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):unsupported op &((int)*, (float)*)), e: "4", f: true}`,
 	}, {
+		desc: "strings and bytes",
+		in: `
+			s0: "foo" + "bar"
+			s1: 3 * "abc"
+			s2: "abc" * 2
+
+			b0: 'foo' + 'bar'
+			b1: 3 * 'abc'
+			b2: 'abc' * 2
+		`,
+		out: `<0>{` +
+			`s0: "foobar", ` +
+			`s1: "abcabcabc", ` +
+			`s2: "abcabc", ` +
+			`b0: 'foobar', ` +
+			`b1: 'abcabcabc', ` +
+			`b2: 'abcabc'` +
+			`}`,
+	}, {
 		desc: "escaping",
 
 		in: `