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?