cue: fix list multiplication for 1-element lists

Previously, it always returned [].

Also:
- Add tests for list multiplication.
- Modify spec to support list * 0 (which already worked).
- Report an error for list * negative int.

Change-Id: Ifcbe5104b1520db64be79ae5cd81d70f37ffe83f
Reviewed-on: https://cue-review.googlesource.com/c/1500
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/binop.go b/cue/binop.go
index ada9cc9..b3fe6c7 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -65,7 +65,9 @@
 		left, right = right, left
 	}
 	if op != opUnify {
-		if !kind.isGround() {
+		// Any operation other than unification or disjunction must be on
+		// concrete types. Disjunction is handled separately.
+		if !leftKind.isGround() || !rightKind.isGround() {
 			return ctx.mkErr(src, codeIncomplete, "incomplete error")
 		}
 		ctx.exprDepth++
@@ -959,39 +961,30 @@
 		if !k.isAnyOf(intKind) {
 			panic("multiplication must be int type")
 		}
-		typ := x.typ.(evaluated)
-		ln := x.len.(evaluated)
 		n := &list{baseValue: binSrc(src.Pos(), op, x, other), typ: x.typ}
-		switch len(x.a) {
-		case 0:
-		case 1:
-			n.typ = binOp(ctx, src, opUnify, typ, x.a[0].evalPartial(ctx))
-		default:
+		if len(x.a) > 0 {
 			if !k.isGround() {
-				return x
+				// should never reach here.
+				break
 			}
 			if ln := other.(*numLit).intValue(ctx); ln > 0 {
-				// TODO: check error
 				for i := 0; i < ln; i++ {
 					// TODO: copy values
 					n.a = append(n.a, x.a...)
 				}
+			} else if ln < 0 {
+				return ctx.mkErr(src, "negative number %d multiplies list", ln)
 			}
 		}
 		switch v := x.len.(type) {
-		case *top, *basicType:
-			n.len = other
 		case *numLit:
-			switch v.intValue(ctx) {
-			case 0:
-				n.len = x.len
-			case 1:
-				n.len = other
-			default:
-				n.len = binOp(ctx, src, opMul, ln, other)
-			}
+			// Closed list
+			ln := &numLit{numBase: v.numBase}
+			ln.v.SetInt64(int64(len(n.a)))
+			n.len = ln
 		default:
-			n.len = binOp(ctx, src, opMul, ln, other)
+			// Open list
+			n.len = x.len
 		}
 		return n
 	}
diff --git a/cue/export_test.go b/cue/export_test.go
index 7ac8ea4..80d0448 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -87,23 +87,14 @@
 		}`,
 		out: unindent(`
 		{
-			a: 5*[int] & [1, 2, ...int]
-			b: (>=2 & <=5)*[int] & [1, 2, ...int]
-			c: (<=5 & >=3)*[int] & [1, 2, ...int]
-			d: [1, 2, ...int]
+			a: [1, 2, int, int, int]
+			b: <=5*[int] & [1, 2, ...]
+			c: (>=3 & <=5)*[int] & [1, 2, ...]
+			d: >=2*[int] & [1, 2, ...]
 			e: [1, 2, ...int]
 			f: [1, 2, ...]
 		}`),
 	}, {
-		in: `{
-			a: >=0*[int]
-			a: [...int]
-		}`,
-		out: unindent(`
-			{
-				a: [...int]
-			}`),
-	}, {
 		raw: true,
 		in:  `{ a: { b: [] }, c: a.b, d: a["b"] }`,
 		out: unindent(`
diff --git a/cue/kind.go b/cue/kind.go
index 8cf7772..c5d281f 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -203,10 +203,10 @@
 		}
 		if op == opMul {
 			if a.isAnyOf(listKind|stringKind|bytesKind) && b.isAnyOf(intKind) {
-				return a, false
+				return a | catBits, false
 			}
 			if b.isAnyOf(listKind|stringKind|bytesKind) && a.isAnyOf(intKind) {
-				return b, true
+				return b | catBits, true
 			}
 		}
 		// non-overlapping types
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 256177d..ba47879 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -332,6 +332,28 @@
 			`,
 		out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, number)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: _|_((<=5 & 8):8 not within bound <=5), e5: _|_((<=5 & 8):8 not within bound <=5)}`,
 	}, {
+		desc: "list arithmetic",
+		in: `
+			list: [1,2,3]
+			mul0: list*0
+			mul1: list*1
+			mul2: 2*list
+			list1: [1]
+		    mul1_0: list1*0
+			mul1_1: 1*list1
+			mul1_2: list1*2
+			e: list*-1
+			`,
+		out: `<0>{list: [1,2,3], ` +
+			`mul0: [], ` +
+			`mul1: [1,2,3], ` +
+			`mul2: [1,2,3,1,2,3], ` +
+			`list1: [1], ` +
+			`mul1_0: [], ` +
+			`mul1_1: [1], ` +
+			`mul1_2: [1,1], ` +
+			`e: _|_((<1>.list * -1):negative number -1 multiplies list)}`,
+	}, {
 		desc: "selecting",
 		in: `
 			obj: {a: 1, b: 2}
@@ -409,7 +431,7 @@
 		`,
 		out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):unsupported op &((int)*, (string)*)), e2: _|_((1 & string):unsupported op &(number, (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)}`,
 	}, {
-		desc: "comparisson",
+		desc: "comparison",
 		in: `
 			lss: 1 < 2
 			leq: 1 <= 1.0
@@ -765,28 +787,41 @@
 		in: `
 			l0: 3*[int]
 			l0: [1, 2, 3]
-			l1: <=5*[string]
-			l1: ["a", "b"]
-			l2: <=5*[{ a: int }]
+			l2: [...{ a: int }]
 			l2: [{a: 1}, {a: 2, b: 3}]
 
 			// TODO: work out a decent way to specify length ranges of lists.
 			// l3: <=10*[int]
 			// l3: [1, 2, 3, ...]
 
-			s1: (<=6*[int])[2:3] // TODO: simplify 1*[int] to [int]
+			s1: (6*[int])[2:3]
 			s2: [0,2,3][1:2]
 
-			i1: (<=6*[int])[2]
+			i1: (6*[int])[2]
 			i2: [0,2,3][2]
 
 			t0: [...{a: 8}]
 			t0: [{}]
+			t1: [...]
+			t1: [...int]
 
-			e0: >=2*[{}]
+			e0: 2*[{}]
 			e0: [{}]
+			e1: [...int]
+			e1: [...float]
 			`,
-		out: `<0>{l0: [1,2,3], l1: ["a","b"], l2: [<1>{a: 1},<2>{a: 2, b: 3}], s1: 1*[int], s2: [2], i1: int, i2: 3, t0: [<3>{a: 8}], e0: _|_(([, ...<4>{}] & [<5>{}]):incompatible list lengths: 1 not within bound >=2)}`,
+		out: `<0>{` +
+			`l0: [1,2,3], ` +
+			`l2: [<1>{a: 1},<2>{a: 2, b: 3}], ` +
+			`s1: [int], ` +
+			`s2: [2], ` +
+			`i1: int, ` +
+			`i2: 3, ` +
+			`t0: [<3>{a: 8}], ` +
+			`t1: [, ...int], ` +
+			`e0: _|_(([<4>{},<4>{}] & [<5>{}]):incompatible list lengths: cannot unify numbers 2 and 1), ` +
+			`e1: _|_(([, ...int] & [, ...float]):incompatible list types: unsupported op &((int)*, (float)*): )` +
+			`}`,
 	}, {
 		desc: "list arithmetic",
 		in: `
@@ -796,8 +831,20 @@
 			l3: <=2*[]
 			l4: <=2*[int]
 			l5: <=2*(int*[int])
-		`,
-		out: `<0>{l0: [1,2,3,1,2,3,1,2,3], l1: [], l2: [], l3: [], l4: <=2*[int], l5: <=2*[int]}`,
+			l6: 3*[...int]
+			l7: 3*[1, ...int]
+			l8: 3*[1, 2, ...int]
+			`,
+		out: `<0>{l0: [1,2,3,1,2,3,1,2,3], ` +
+			`l1: [], ` +
+			`l2: [], ` +
+			`l3: (<=2 * []), ` +
+			`l4: (<=2 * [int]), ` +
+			`l5: (<=2 * (int * [int])), ` +
+			`l6: [, ...int], ` +
+			`l7: [1,1,1, ...int], ` +
+			`l8: [1,2,1,2,1,2, ...int]` +
+			`}`,
 	}, {
 		desc: "correct error messages",
 		// Tests that it is okay to partially evaluate structs.
@@ -1506,7 +1553,13 @@
 				c:  string
 			}
 		`,
-		out: `<0>{l: [<1>{c: "t", d: "t"}], a: <2>{c: "t", d: "t"}, b: <3>{c: string, d: string}, l1: [<4>{c: "t", d: "st"}], a1: <5>{c: "t", d: "st"}, b1: <6>{c: string, d: _|_(("s" + string):unsupported op +(string, (string)*))}}`,
+		out: `<0>{` +
+			`l: [<1>{c: "t", d: "t"}], ` +
+			`a: <2>{c: "t", d: "t"}, ` +
+			`b: <3>{c: string, d: string}, ` +
+			`l1: [<4>{c: "t", d: "st"}], ` +
+			`a1: <5>{c: "t", d: "st"}, ` +
+			`b1: <6>{c: string, d: ("s" + <7>.c)}}`,
 	}, {
 		desc: "ips",
 		in: `
@@ -1521,7 +1574,12 @@
 
 		MyIP: Inst & [_, _, 10, 10 ]
 		`,
-		out: `<0>{IP: 4*[(>=0 & <=255)], Private: [192,168,(>=0 & <=255),(>=0 & <=255)], Inst: [10,10,(>=0 & <=255),(>=0 & <=255)], MyIP: [10,10,10,10]}`,
+		out: `<0>{` +
+			`IP: [(>=0 & <=255),(>=0 & <=255),(>=0 & <=255),(>=0 & <=255)], ` +
+			`Private: [192,168,(>=0 & <=255),(>=0 & <=255)], ` +
+			`Inst: [10,10,(>=0 & <=255),(>=0 & <=255)], ` +
+			`MyIP: [10,10,10,10]` +
+			`}`,
 	}, {
 		desc: "complex interaction of groundness",
 		in: `
@@ -1532,7 +1590,8 @@
 			a b c d: string
 		`,
 		// TODO(perf): unification should catch shared node.
-		out: `<0>{res: [<1>{d: "b", s: "ab"}], a: <2>{b: <3>{<>: <4>(C: string)-><5>{d: string, s: ("a" + <5>.d)}, c: <6>{d: string, s: _|_(("a" + string):unsupported op +(string, (string)*))}}}}`,
+		out: `<0>{res: [<1>{d: "b", s: "ab"}], ` +
+			`a: <2>{b: <3>{<>: <4>(C: string)-><5>{d: string, s: ("a" + <5>.d)}, c: <6>{d: string, s: ("a" + <7>.d)}}}}`,
 	}, {
 		desc: "complex groundness 2",
 		in: `
@@ -1544,7 +1603,7 @@
 			a b <C>: { d: string, s: "a" + d }
 			a b c d: string
 		`,
-		out: `<0>{r1: <1>{y: "c", res: <2>{d: "c", s: "ac"}}, f1: <3>{y: string, res: <4>{d: string, s: _|_(("a" + string):unsupported op +(string, (string)*))}}, a: <5>{b: <6>{<>: <7>(C: string)-><8>{d: string, s: ("a" + <8>.d)}, c: <9>{d: string, s: _|_(("a" + string):unsupported op +(string, (string)*))}}}}`,
+		out: `<0>{r1: <1>{y: "c", res: <2>{d: "c", s: "ac"}}, f1: <3>{y: string, res: <4>{d: string, s: (("a" + <5>.d) & ("a" + <5>.d))}}, a: <6>{b: <7>{<>: <8>(C: string)-><9>{d: string, s: ("a" + <9>.d)}, c: <10>{d: string, s: (("a" + <11>.d) & ("a" + <11>.d))}}}}`,
 	}, {
 		desc: "references from template to concrete",
 		in: `
diff --git a/cue/types_test.go b/cue/types_test.go
index 9ef0215..d895109 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -514,7 +514,7 @@
 		res:   "[1,2,3,]",
 	}, {
 		value: `>=5*[1,2,3, ...int]`,
-		res:   "[1,2,3,]",
+		err:   "incomplete",
 	}, {
 		value: `[x for x in y if x > 1]
 		y: [1,2,3]`,
@@ -999,7 +999,7 @@
 		err:   `cannot convert incomplete value`,
 	}, {
 		value: `(>=3 * [1, 2])`,
-		json:  `[1,2]`,
+		err:   "incomplete error", // TODO: improve error
 	}, {
 		value: `{}`,
 		json:  `{}`,
@@ -1071,8 +1071,8 @@
 		value: `[int]`,
 		out:   `[int]`,
 	}, {
-		value: `(>=3 * [1, 2])`,
-		out:   `[1,2]`,
+		value: `3 * [1, 2]`,
+		out:   `[1,2,1,2,1,2]`,
 	}, {
 		value: `{}`,
 		out:   `{}`,
diff --git a/cue/value.go b/cue/value.go
index a986b10..c65584e 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -463,6 +463,9 @@
 	a []value // TODO: could be arc?
 
 	typ value
+
+	// TODO: consider removing len. Currently can only be len(a) or >= len(a)
+	// and could be replaced with a bool.
 	len value
 }
 
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 71efed3..09b4015 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -1546,12 +1546,13 @@
 [ 1, 2 ]      + [ 3, 4, ... ]  // [ 1, 2, 3, 4, ... ]
 ```
 
-Lists can be multiplied with a positive `int` using the `*` operator
+Lists can be multiplied with a non-negative`int` using the `*` operator
 to create a repeated the list by the indicated number.
 ```
 3*[1,2]         // [1, 2, 1, 2, 1, 2]
-3*[1, 2, ...]   // [1, 2, 1, 2, 1 ,2]
+3*[1, 2, ...]   // [1, 2, 1, 2, 1 ,2, ...]
 [byte]*4        // [byte, byte, byte, byte]
+0*[1,2]         // []
 ```
 
 <!-- TODO(mpvl): should we allow multiplication with a range?