blob: 6f9f12da5b335f7ea5420b7b29d452f3371a79aa [file] [log] [blame]
Marcel van Lohuizen17157ea2018-12-11 10:41:10 +01001// Copyright 2018 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package cue
16
17import (
18 "flag"
19 "fmt"
20 "testing"
21
22 "cuelang.org/go/cue/errors"
23 "cuelang.org/go/cue/parser"
24 "cuelang.org/go/cue/token"
25)
26
27var traceOn = flag.Bool("debug", false, "enable tracing")
28
29func compileFileWithErrors(t *testing.T, body string) (*context, *structLit, errors.List) {
30 t.Helper()
31 ctx, inst, errs := compileInstance(t, body)
32 return ctx, inst.rootValue.evalPartial(ctx).(*structLit), errs
33}
34
35func compileFile(t *testing.T, body string) (*context, *structLit) {
36 t.Helper()
37 ctx, inst, errs := compileInstance(t, body)
38 if errs != nil {
39 t.Fatal(errs)
40 }
41 return ctx, inst.rootValue.evalPartial(ctx).(*structLit)
42}
43
44func compileInstance(t *testing.T, body string) (*context, *Instance, errors.List) {
45 t.Helper()
46
47 fset := token.NewFileSet()
48 x := newIndex(fset).NewInstance(nil)
49 f, err := parser.ParseFile(fset, "test", body, parser.ParseLambdas)
50 ctx := x.newContext()
51
52 switch errs := err.(type) {
53 case nil:
54 x.insertFile(f)
55 case errors.List:
56 return ctx, x, errs
57 default:
58 t.Fatal(err)
59 }
60 return ctx, x, nil
61}
62
63func rewriteHelper(t *testing.T, cases []testCase, r rewriteMode) {
64 t.Helper()
65 for _, tc := range cases {
66 t.Run(tc.desc, func(t *testing.T) {
67 t.Helper()
68 ctx, obj := compileFile(t, tc.in)
69 ctx.trace = *traceOn
70 root := testResolve(ctx, obj, r)
71
72 got := debugStr(ctx, root)
73 if v := ctx.processDelayedConstraints(); v != nil {
74 got += fmt.Sprintf("\n%s", debugStr(ctx, v))
75 }
76
77 // Copy the result
78 if got != tc.out {
79 fn := t.Errorf
80 if tc.skip {
81 fn = t.Skipf
82 }
83 fn("output differs:\ngot %s\nwant %s", got, tc.out)
84 }
85 })
86 }
87}
88
89type testCase struct {
90 desc string
91 in string
92 out string
93 skip bool
94}
95
96func TestBasicRewrite(t *testing.T) {
97 testCases := []testCase{{
98 desc: "errors",
99 in: `
100 a: _|_ & _|_
101 b: null & _|_
102 c: b.a == _|_
103 d: _|_ != b.a
104 e: _|_ == _|_
105 `,
106 out: `<0>{a: _|_(from source), b: _|_(from source), c: true, d: false, e: true}`,
107 }, {
108 desc: "arithmetic",
109 in: `
110 sum: -1 + +2 // 1
111 str: "foo" + "bar" // "foobar"
112 div1: 2.0 / 3 * 6 // 4
113 div2: 2 / 3 * 6 // 4
114 rem: 2 % 3 // 2
115 e: 2 + "a" // _|_: unsupported op +(int, string))
116 b: 1 != 4
117 `,
118 out: `<0>{sum: 1, str: "foobar", div1: 4.00000000000000000000000, div2: 4.00000000000000000000000, rem: 2, e: _|_((2 + "a"):unsupported op +(number, string)), b: true}`,
119 }, {
120 desc: "integer-specific arithmetic",
121 in: `
122 q1: 5 quo 2 // 2
123 q2: 5 quo -2 // -2
124 q3: -5 quo 2 // -2
125 q4: -5 quo -2 // 2
126 qe1: 2.0 quo 1
127 qe2: 2 quo 1.0
128
129 r1: 5 rem 2 // 1
130 r2: 5 rem -2 // 1
131 r3: -5 rem 2 // -1
132 r4: -5 rem -2 // -1
133 re1: 2.0 rem 1
134 re2: 2 rem 1.0
135
136 d1: 5 div 2 // 2
137 d2: 5 div -2 // -2
138 d3: -5 div 2 // -3
139 d4: -5 div -2 // 3
140 de1: 2.0 div 1
141 de2: 2 div 1.0
142
143 m1: 5 mod 2 // 1
144 m2: 5 mod -2 // 1
145 m3: -5 mod 2 // 1
146 m4: -5 mod -2 // 1
147 me1: 2.0 mod 1
148 me2: 2 mod 1.0
149
150 // TODO: handle divide by zero
151 `,
152 out: `<0>{q1: 2, q2: -2, q3: -2, q4: 2, ` +
153 `qe1: _|_((2.0 quo 1):unsupported op quo(float, number)), ` +
154 `qe2: _|_((2 quo 1.0):unsupported op quo(number, float)), ` +
155 `r1: 1, r2: 1, r3: -1, r4: -1, re1: ` +
156 `_|_((2.0 rem 1):unsupported op rem(float, number)), ` +
157 `re2: _|_((2 rem 1.0):unsupported op rem(number, float)), ` +
158 `d1: 2, d2: -2, d3: -3, d4: 3, ` +
159 `de1: _|_((2.0 div 1):unsupported op div(float, number)), ` +
160 `de2: _|_((2 div 1.0):unsupported op div(number, float)), ` +
161 `m1: 1, m2: 1, m3: 1, m4: 1, ` +
162 `me1: _|_((2.0 mod 1):unsupported op mod(float, number)), ` +
163 `me2: _|_((2 mod 1.0):unsupported op mod(number, float))}`,
164 }, {
165 desc: "booleans",
166 in: `
167 t: true
168 t: !false
169 f: false
170 f: !t
171 e: true
172 e: !true
173 `,
174 out: "<0>{t: true, f: false, e: _|_(true:failed to unify: true != false)}",
175 }, {
176 desc: "boolean arithmetic",
177 in: `
178 a: true && true
179 b: true || false
180 c: false == true
181 d: false != true
182 e: true & true
183 f: true & false
184 `,
185 out: "<0>{a: true, b: true, c: false, d: true, e: true, f: _|_(true:failed to unify: true != false)}",
186 }, {
187 desc: "basic type",
188 in: `
189 a: 1 & int
190 b: number & 1
191 c: 1.0
192 c: float
193 d: int & float // _|_
194 e: "4" & string
195 f: true
196 f: bool
197 `,
198 out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):unsupported op &((int)*, (float)*)), e: "4", f: true}`,
199 }, {
200 desc: "escaping",
201
202 in: `
203 a: "foo\nbar",
204 b: a,
205
206 // TODO: mimic http://exploringjs.com/es6/ch_template-literals.html#sec_introduction-template-literals
207 `,
208 out: `<0>{a: "foo\nbar", b: "foo\nbar"}`,
209 // out: `<0>{a: "foo\nbar", b: <0>.a}`,
210 }, {
211 desc: "reference",
212 in: `
213 a: b
214 b: 2
215 d: {
216 d: 3
217 e: d
218 }
219 e: {
220 e: {
221 v: 1
222 }
223 f: {
224 v: e.v
225 }
226 }
227 `,
228 out: "<0>{a: 2, b: 2, d: <1>{d: 3, e: 3}, e: <2>{e: <3>{v: 1}, f: <4>{v: 1}}}",
229 }, {
230 desc: "lists",
231 in: `
232 list: [1,2,3]
233 index: [1,2,3][1]
234 unify: [1,2,3] & [_,2,3]
235 e: [] & 4
236 e2: [3]["d"]
237 e3: [3][-1]
238 e4: [1, 2, ...4..5] & [1, 2, 4, 8]
239 e5: [1, 2, 4, 8] & [1, 2, ...4..5]
240 `,
241 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: _|_(((4..5) & 8):value 8 not in range (4..5)), e5: _|_(((4..5) & 8):value 8 not in range (4..5))}`,
242 }, {
243 desc: "selecting",
244 in: `
245 obj: {a: 1, b: 2}
246 index: {a: 1, b: 2}["b"]
247 mulidx: {a: 1, b: {a:1, b: 3}}["b"]["b"]
248 e: {a: 1}[4]
249 f: {a: 1}.b
250 g: {a: 1}["b"]
251 h: [3].b
252 `,
253 out: `<0>{obj: <1>{a: 1, b: 2}, index: 2, mulidx: 3, e: _|_(4:invalid struct index 4 (type number)), f: _|_(<2>{a: 1}.b:undefined field "b"), g: _|_(<3>{a: 1}["b"]:undefined field "b"), h: _|_([3]:invalid operation: [3].b (type list does not support selection))}`,
254 }, {
255 desc: "obj unify",
256 in: `
257 o1: {a: 1 } & { b: 2} // {a:1,b:2}
258 o2: {a: 1, b:2 } & { b: 2} // {a:1,b:2}
259 o3: {a: 1 } & { a:1, b: 2} // {a:1,b:2}
260 o4: {a: 1 } & { b: 2} // {a:1,b:2}
261 o4: {a: 1, b:2 } & { b: 2}
262 o4: {a: 1 } & { a:1, b: 2}
263 e: 1 // 1 & {a:3}
264 e: {a:3}
265 `,
266 out: "<0>{o1: <1>{a: 1, b: 2}, o2: <2>{a: 1, b: 2}, o3: <3>{a: 1, b: 2}, o4: <4>{a: 1, b: 2}, e: _|_((1 & <5>{a: 3}):unsupported op &(number, struct))}",
267 }, {
268 desc: "disjunctions",
269 in: `
270 o1: 1 | 2 | 3
271 o2: (1 | 2 | 3) & 1
272 o3: 2 & (1 | 2 | 3)
273 o4: (1 | 2 | 3) & (1 | 2 | 3)
274 o5: (1 | 2 | 3) & (3 | 2 | 1)
275 o6: (1 | 2 | 3) & (3 | 1 | 2)
276 o7: (1 | 2 | 3) & (2 | 3)
277 o8: (1 | 2 | 3) & (3 | 2)
278 o9: (2 | 3) & (1 | 2 | 3)
279 o10: (3 | 2) & (1 | 2 | 3)
280
281 // All errors are treated the same as per the unification model.
282 i1: [1, 2][3] | "c"
283 `,
284 out: `<0>{o1: (1 | 2 | 3), o2: 1, o3: 2, o4: (1 | 2 | 3), o5: (1! | 2! | 3!), o6: (1! | 2! | 3!), o7: (2 | 3), o8: (2! | 3!), o9: (2 | 3), o10: (3! | 2!), i1: "c"}`,
285 }, {
286 desc: "lambda",
287 in: `
288 o1(A:1, B:2) -> { a: A, b: B }
289 oe() -> { a: 1, b: 2 }
290 l1: (A:1, B:2) -> { a: A, b: B }
291 c1: ((A:int, B:int) -> {a:A, b:B})(1, 2)
292 `,
293 // TODO(P1): don't let values refer to themselves.
294 out: "<0>{o1: <1>(A: 1, B: 2)-><2>{a: <1>.A, b: <1>.B}, oe: <3>()-><4>{a: 1, b: 2}, l1: <5>(A: 1, B: 2)-><6>{a: <5>.A, b: <5>.B}, c1: <7>{a: 1, b: 2}}",
295 }, {
296 desc: "types",
297 in: `
298 i: int
299 j: int & 3
300 s: string
301 t: "s" & string
302 e: int & string
303 e2: 1 & string
304 b: !int
305 p: +true
306 m: -false
307 `,
308 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)}`,
309 }, {
310 desc: "comparisson",
311 in: `
312 lss: 1 < 2
313 leq: 1 <= 1.0
314 leq: 2.0 <= 3
315 eql: 1 == 1.0
316 neq: 1.0 == 1
317 gtr: !(2 > 3)
318 geq: 2.0 >= 2
319 seq: "a" + "b" == "ab"
320 err: 2 == "s"
321 `,
322 out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):unsupported op ==(number, string))}`,
323 }, {
324 desc: "null",
325 in: `
326 eql: null == null
327 neq: null != null
328 unf: null & null
329
330 // errors
331 eqe1: null == 1
332 eqe2: 1 == null
333 nee1: "s" != null
334 call: null()
335 `,
336 out: `<0>{eql: true, neq: false, unf: null, eqe1: _|_((null == 1):unsupported op ==(null, number)), eqe2: _|_((1 == null):unsupported op ==(number, null)), nee1: _|_(("s" != null):unsupported op !=(string, null)), call: _|_(null:cannot call non-function null (type null))}`,
337 }, {
338 desc: "self-reference cycles",
339 in: `
340 a: b - 100
341 b: a + 100
342
343 c: [c[1], c[0]]
344 `,
345 out: `<0>{a: _|_(cycle detected), b: _|_(cycle detected), c: _|_(cycle detected)}`,
346 // }, {
347 // desc: "resolved self-reference cycles",
348 // in: `
349 // a: b - 100
350 // b: a + 100
351 // b: 200
352
353 // c: [c[1], a] // TODO: should be allowed
354
355 // s1: s2 & {a: 1}
356 // s2: s3 & {b: 2}
357 // s3: s1 & {c: 3}
358 // `,
359 // out: `<0>{a: 100, b: 200, c: _|_(cycle detected)}`,
360 }, {
361 desc: "delayed constraint failure",
362 in: `
363 a: b - 100
364 b: a + 110
365 b: 200
366 `,
367 out: `<0>{a: 100, b: 200}
368_|_(((<1>.a + 110) & 200):constraint violated: _|_((210 & 200):cannot unify numbers 210 and 200))`,
369 // TODO: find a way to mark error in data.
370 }}
371 rewriteHelper(t, testCases, evalPartial)
372}
373
374func TestChooseFirst(t *testing.T) {
375 testCases := []testCase{{
376 desc: "pick first",
377 in: `
378 a: 5 | "a" | true
379 b c: {
380 a: 2
381 } | {
382 a : 3
383 }
384 `,
385 out: "<0>{a: 5, b: <1>{c: <2>{a: 2}}}",
386 }, {
387 desc: "simple disambiguation conflict",
388 in: `
389 a: "a" | "b"
390 b: "b" | "a"
391 c: a & b
392 `,
393 out: `<0>{a: "a", b: "b", c: _|_(("a"! | "b"!):ambiguous disjunction)}`,
394 }, {
395 desc: "disambiguation non-conflict",
396 in: `
397 a: "a" | ("b" | "c")
398 b: ("a" | "b") | "c"
399 c: a & b
400 `,
401 out: `<0>{a: "a", b: "a", c: "a"}`,
402 }}
403 rewriteHelper(t, testCases, evalFull)
404}
405
406func TestResolve(t *testing.T) {
407 testCases := []testCase{{
408 in: `
409 a: b.c.d
410 b c: { d: 3 }
411 c: { c: d.d, }
412 d: { d: 2 }
413 `,
414 out: "<0>{a: 3, b: <1>{c: <2>{d: 3}}, c: <3>{c: 2}, d: <4>{d: 2}}",
415 }, {
416 in: `
417 a: _
418 b: a
419 a: { d: 1, d: _ }
420 b: _
421 `,
422 out: `<0>{a: <1>{d: 1}, b: <2>{d: 1}}`,
423 }, {
424 desc: "JSON",
425 in: `
426 "a": 3
427 b: a
428 o: { "a\nb": 2 } // TODO: use $ for root?
429 c: o["a\nb"]
430 `,
431 out: `<0>{a: 3, b: 3, o: <1>{"a\nb": 2}, c: 2}`,
432 }, {
433 desc: "arithmetic",
434 in: `
435 v1: 1.0T/2.0 //
436 v2: 2.0 == 2
437 i1: 1
438 v5: 2.0 / i1 // TODO: should probably fail
439 e1: 2.0 % 3
440 e2: int & 4.0/2.0
441 `,
442 out: `<0>{v1: 5e+11, v2: true, i1: 1, v5: 2, e1: _|_((2.0 % 3):unsupported op %(float, number)), e2: _|_((int & 2):unsupported op &((int)*, float))}`,
443 // }, {
444 // desc: "null coalescing",
445 // in: `
446 // a: null
447 // b: a.x
448 // c: a["x"]
449 // `,
450 // out: ``,
451 }, {
452 desc: "call",
453 in: `
454 a: { a: (P, Q) -> {p:P, q:Q} }
455 b: a // reference different nodes
456 c: a.a(1, 2)
457 `,
458 out: "<0>{a: <1>{a: <2>(P: _, Q: _)-><3>{p: <2>.P, q: <2>.Q}}, b: <4>{a: <2>(P: _, Q: _)-><3>{p: <2>.P, q: <2>.Q}}, c: <5>{p: 1, q: 2}}",
459 }, {
460 desc: "call of lambda",
461 in: `
462 a(P, Q) -> {p:P, q:Q}
463 a(P, Q) -> {p:P, q:Q}
464 ai(P, Q) -> {p:Q, q:P}
465 b: a(1,2)
466 c: (a | b)(1)
467 d: ([] | (a) -> 3)(2)
468 e1: a(1)
469 `,
470 out: "<1>{a: <2>(P: _, Q: _)->(<3>{p: <2>.P, q: <2>.Q} & <4>{p: <2>.P, q: <2>.Q}), ai: <5>(P: _, Q: _)-><6>{p: <5>.Q, q: <5>.P}, b: <7>{p: 1, q: 2}, c: _|_((<8>.a | <8>.b) (1):number of arguments does not match (2 vs 1)), d: _|_(([] | <0>(a: _)->3):cannot call non-function [] (type list)), e1: _|_(<8>.a (1):number of arguments does not match (2 vs 1))}",
471 }, {
472 desc: "reference across tuples and back",
473 // Tests that it is okay to partially evaluate structs.
474 in: `
475 a: { c: b.e, d: b.f }
476 b: { e: 3, f: a.c }
477 `,
478 out: "<0>{a: <1>{c: 3, d: 3}, b: <2>{e: 3, f: 3}}",
479 }, {
480 desc: "index",
481 in: `
482 a: [2][0]
483 b: {foo:"bar"}["foo"]
484 c: (l|{"3":3})["3"]
485 d: ([]|[1])[0]
486 l: []
487 e1: [2][""]
488 e2: 2[2]
489 e3: [][true]
490 e4: [1,2,3][3]
491 e5: [1,2,3][-1]
492 e6: ([]|{})[1]
493 `,
494 out: `<0>{a: 2, b: "bar", c: _|_("3":invalid list index "3" (type string)), l: [], d: _|_([]:index 0 out of bounds), e1: _|_("":invalid list index "" (type string)), e2: _|_(2:invalid operation: 2[2] (type number does not support indexing)), e3: _|_(true:invalid list index true (type bool)), e4: _|_([1,2,3]:index 3 out of bounds), e5: _|_(-1:invalid list index -1 (index must be non-negative)), e6: _|_([]:index 1 out of bounds)}`,
495 }, {
496 desc: "string index",
497 in: `
498 a0: "abc"[0]
499 a1: "abc"[1]
500 a2: "abc"[2]
501 a3: "abc"[3]
502 a4: "abc"[-1]
503
504 b: "zoëven"[2]
505 `,
506 out: `<0>{a0: "a", a1: "b", a2: "c", a3: _|_("abc":index 3 out of bounds), a4: _|_(-1:invalid string index -1 (index must be non-negative)), b: "ë"}`,
507 }, {
508 desc: "disjunctions of lists",
509 in: `
510 l: [ int, int ] | [ string, string ]
511
512 l1: [ "a", "b" ]
513 l2: l & [ "c", "d" ]
514 `,
515 out: `<0>{l: ([int,int] | [string,string]), l1: ["a","b"], l2: ["c","d"]}`,
516 }, {
517 desc: "slice",
518 in: `
519 a: [2][0:0]
520 b: [0][1:1]
521 e1: [][1:1]
522 e2: [0][-1:0]
523 e3: [0][1:0]
524 e4: [0][1:2]
525 e5: 4[1:2]
526 e6: [2]["":]
527 e7: [2][:"9"]
528
529 `,
530 out: `<0>{a: [], b: [], e1: _|_(1:slice bounds out of range), e2: _|_([0]:negative slice index), e3: _|_([0]:invalid slice index: 1 > 0), e4: _|_(2:slice bounds out of range), e5: _|_(4:cannot slice 4 (type number)), e6: _|_("":invalid slice index "" (type string)), e7: _|_("9":invalid slice index "9" (type string))}`,
531 }, {
532 desc: "string slice",
533 in: `
534 a0: ""[0:0]
535 a1: ""[:]
536 a2: ""[0:]
537 a3: ""[:0]
538 b0: "abc"[0:0]
539 b1: "abc"[0:1]
540 b2: "abc"[0:2]
541 b3: "abc"[0:3]
542 b4: "abc"[3:3]
543 b5: "abc"[1:]
544 b6: "abc"[:2]
545
546 // TODO: supported extended graphemes, instead of just runes.
547 u: "Spaß"[3:4]
548 `,
549 out: `<0>{a0: "", a1: "", a2: "", a3: "", b0: "", b1: "a", b2: "ab", b3: "abc", b4: "", b5: "bc", b6: "ab", u: "ß"}`,
550 }, {
551 desc: "list types",
552 in: `
553 l0: 3*[int]
554 l0: [1, 2, 3]
555 l1:(0..5)*[string]
556 l1: ["a", "b"]
557 l2: (0..5)*[{ a: int }]
558 l2: [{a: 1}, {a: 2, b: 3}]
559 l3: (0..10)*[int]
560 l3: [1, 2, 3, ...]
561
562 s1: ((0..6)*[int])[2:3] // TODO: simplify 1*[int] to [int]
563 s2: [0,2,3][1:2]
564
565 i1: ((0..6)*[int])[2]
566 i2: [0,2,3][2]
567
568 t0: [...{a: 8}]
569 t0: [{}]
570
571 e0: (2..5)*[{}]
572 e0: [{}]
573
574 e1: 0.._*[...int]
575 `,
576 out: `<0>{l0: [1,2,3], l1: ["a","b"], l2: [<1>{a: 1},<2>{a: 2, b: 3}], l3: (3..10)*[int]([1,2,3, ...int]), s1: 1*[int], s2: [2], i1: int, i2: 3, t0: [<3>{a: 8}], e0: _|_(((2..5)*[<4>{}] & [<5>{}]):incompatible list lengths: value 1 not in range (2..5)), e1: [, ...int]}`,
577 }, {
578 desc: "list arithmetic",
579 in: `
580 l0: 3*[1, 2, 3]
581 l1: 0*[1, 2, 3]
582 l2: 10*[]
583 l3: (0..2)*[]
584 l4: (0..2)*[int]
585 l5: (0..2)*(int*[int])
586 l6: 3*((3..4)*[int])
587 `,
588 out: `<0>{l0: [1,2,3,1,2,3,1,2,3], l1: [], l2: [], l3: [], l4: (0..2)*[int], l5: (0..2)*[int], l6: (9..12)*[int]}`,
589 }, {
590 desc: "correct error messages",
591 // Tests that it is okay to partially evaluate structs.
592 in: `
593 a: "a" & 1
594 `,
595 out: `<0>{a: _|_(("a" & 1):unsupported op &(string, number))}`,
596 }, {
597 desc: "structs",
598 in: `
599 a: t & { c: 5 } // {c:5,d:15}
600 b: ti & { c: 7 } // {c:7,d:21}
601 t: { c: number, d: c * 3 } // {c:number,d:number*3}
602 ti: t & { c: int }
603 `,
604 out: `<0>{a: <1>{c: 5, d: 15}, t: <2>{c: number, d: (<3>.c * 3)}, b: <4>{c: 7, d: 21}, ti: <5>{c: int, d: (<6>.c * 3)}}`,
605 }, {
606 desc: "reference to root",
607 in: `
608 a: { b: int }
609 c: a & {
610 b: 100
611 d: a.b + 3 // do not resolve as c != a.
612
613 // TODO(crash)
614 // e: int; e < 100 // where clause can be different.
615 }
616 x: {
617 b: int
618 c: b + 5
619 }
620 y: x & {
621 b: 100
622 // c should resolve to 105
623 }
624 v: {
625 b: int
626 c: v.b + 5 // reference starting from copied node.
627 }
628 w: v & { b: 100 }
629 wp: v & { b: 100 }
630 `,
631 out: `<0>{a: <1>{b: int}, c: <2>{b: 100, d: (<3>.a.b + 3)}, x: <4>{b: int, c: (<5>.b + 5)}, y: <6>{b: 100, c: 105}, v: <7>{b: int, c: (<8>.b + 5)}, w: <9>{b: 100, c: 105}, wp: <10>{b: 100, c: 105}}`,
632 }, {
633 desc: "references from template to concrete",
634 in: `
635 res: [t]
636 t <X>: {
637 a: c + b.str
638 b str: string
639 c: "X"
640 }
641 t x: { b str: "DDDD" }
642 `,
643 out: `<0>{res: [<1>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], t: <7>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`,
644 }, {
645 desc: "interpolation",
646 in: `
647 a: "\(4)"
648 b: "one \(a) two \( a + c )"
649 c: "one"
650 d: "\(r)"
651 u: "\(_)"
652 r: _
653 e: "\([])"`,
654 out: `<0>{a: "4", b: "one 4 two 4one", c: "one", d: ""+<1>.r+"", r: _, u: ""+_+"", e: _|_([]:expression in interpolation must evaluate to a number kind or string (found list))}`,
655 }, {
656 desc: "diamond-shaped constraints",
657 in: `
658 S: {
659 A: {
660 a: 1,
661 },
662 B: A & {
663 b: 2,
664 }
665 },
666 T: S & { // S == { A: { a:1 }, B: { a:1, b:2 } }
667 A: {
668 c: 3,
669 },
670 B: { // S.B & A
671 d: 4, // Combines constraints S.A, S.B, T.A, and T.B
672 }
673 }`,
674 out: "<0>{S: <1>{A: <2>{a: 1}, B: <3>{a: 1, b: 2}}, T: <4>{A: <5>{a: 1, c: 3}, B: <6>{a: 1, b: 2, c: 3, d: 4}}}",
675 }, {
676 desc: "field templates",
677 in: `
678 a: {
679 <name>: int
680 k: 1
681 }
682 b: {
683 <x>: { x: 0, y: 1 | int }
684 v: {}
685 w: { x: 0 }
686 }
687 b: { <y>: {} } // TODO: allow different name
688 c: {
689 <Name>: { name: Name, y: 1 }
690 foo: {}
691 bar: _
692 }
693 `,
694 out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (1 | int)} & <6>{}), v: <7>{x: 0, y: (1 | int)}, w: <8>{x: 0, y: (1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
695 }, {
696 desc: "simple ranges",
697 in: `
698 a: 1..2
699 c: "a".."b"
700 d: (2+3)..(4+5) // 5..9
701
702 s1: 1..1 // 1
703 s2: 1..2..3 // simplify (1..2)..3 to 1..3
704 s3: (1..10)..5 // This is okay!
705 s4: 5..(1..10) // This is okay!
706 s5: (0..(5..6))..(1..10)
707 `,
708 out: `<0>{a: (1..2), c: ("a".."b"), d: (5..9), s1: 1, s2: (1..3), s3: (1..5), s4: (5..10), s5: (0..10)}`,
709 }, {
710 desc: "range unification",
711 in: `
712 // with concrete values
713 a1: 1..5 & 3
714 a2: 1..5 & 1
715 a3: 1..5 & 5
716 a4: 1..5 & 6
717 a5: 1..5 & 0
718
719 a6: 3 & 1..5
720 a7: 1 & 1..5
721 a8: 5 & 1..5
722 a9: 6 & 1..5
723 a10: 0 & 1..5
724
725 // with ranges
726 b1: 1..5 & 1..5
727 b2: 1..5 & 1..1
728 b3: 1..5 & 5..5
729 b4: 1..5 & 2..3
730 b5: 1..5 & 3..9
731 b6: 1..5 & 5..9
732 b7: 1..5 & 6..9
733
734 b8: 1..5 & 1..5
735 b9: 1..1 & 1..5
736 b10: 5..5 & 1..5
737 b11: 2..3 & 1..5
738 b12: 3..9 & 1..5
739 b13: 5..9 & 1..5
740 b14: 6..9 & 1..5
741
742 // ranges with more general types
743 c1: int & 1..5
744 c2: 1..5 & int
745 c3: string & 1..5
746 c4: 1..5 & string
747
748 // other types
749 s1: "d" .. "z" & "e"
750 s2: "d" .. "z" & "ee"
751
752 n1: number & 1..2
753 n2: int & 1.1 .. 1.3
754 n3: 1.0..3.0 & 2
755 n4: 0.0..0.1 & 0.09999
756 n5: 1..5 & 2.5
757 `,
758 out: `<0>{a1: 3, a2: 1, a3: 5, a4: _|_(((1..5) & 6):value 6 not in range (1..5)), a5: _|_(((1..5) & 0):value 0 not in range (1..5)), a6: 3, a7: 1, a8: 5, a9: _|_(((1..5) & 6):value 6 not in range (1..5)), a10: _|_(((1..5) & 0):value 0 not in range (1..5)), b1: (1..5), b2: 1, b3: 5, b4: (2..3), b5: (3..5), b6: 5, b7: _|_(((1..5) & (6..9)):non-overlapping ranges (1..5) and (6..9)), b8: (1..5), b9: 1, b10: 5, b11: (2..3), b12: (3..5), b13: 5, b14: _|_(((6..9) & (1..5)):non-overlapping ranges (6..9) and (1..5)), c1: (1..5), c2: (1..5), c3: _|_((string & (1..5)):unsupported op &((string)*, (number)*)), c4: _|_(((1..5) & string):unsupported op &((number)*, (string)*)), s1: "e", s2: "ee", n1: (1..2), n2: _|_((int & (1.1..1.3)):unsupported op &((int)*, (float)*)), n3: 2, n4: 0.09999, n5: 2.5}`,
759 }, {
760 desc: "range arithmetic",
761 in: `
762 r0: (1..2) * (4..5)
763 r1: (1..2) * (-1..2)
764 r2: (1.0..2.0) * (-0.5..1.0)
765 r3: (1..2) + (4..5)
766
767 i0: (1..2) * 2
768 i1: (2..3) * -2
769 i2: (1..2) * 2
770 i3: (2..3) * -2
771
772 t0: int * (1..2) // TODO: should be int
773 t1: (1..2) * int
774 t2: (1..2) * (0..int)
775 t3: (1..int) * (0..2)
776 t4: (1..int) * (-1..2)
777 t5: _ * (1..2) // TODO: should be int
778
779 s0: (1..2) - (3..5)
780 s1: (1..2) - 1
781
782 str0: ("ab".."cd") + "ef"
783 str1: ("ab".."cd") + ("ef".."gh")
784 str2: ("ab".."cd") + string
785
786 `,
787 out: `<0>{r0: (4..10), r1: (-2..4), r2: (-1.00..2.00), r3: (5..7), i0: (2..4), i1: (-6..-4), i2: (2..4), i3: (-6..-4), t0: (int * (1..2)), t1: int, t2: (0..int), t3: (0..int), t4: int, t5: _|_((_ * (1..2)):binary operation on non-ground top value), s0: (-4..-1), s1: (0..1), str0: ("abef".."cdef"), str1: ("abef".."cdgh"), str2: ("ab".."cd")}`,
788 }, {
789 desc: "predefined ranges",
790 in: `
791 k1: int8
792 k1: 44
793
794 k2: int64
795 k2: -8_000_000_000
796
797 e1: int16
798 e1: 100_000
799 `,
800 out: `<0>{k1: 44, k2: -8000000000, ` +
801 `e1: _|_(((-32768..32767) & 100000):value 100000 not in range (-32768..32767))}`,
802 // TODO(P3): if two fields are evaluating to the same field, their
803 // values could be bound.
804 // nodes: use a shared node
805 // other: change to where clause
806 // unknown: change to where clause.
807 // in: `
808 // a: b
809 // b: a
810 // a: { d: 1 }
811 // `,
812 // out: `<0>{a: {d:1}, b: {d:1}}`,
813
814 // TODO(P2): circular references in expressions can be resolved when
815 // unified with complete values.
816 // in: `
817 // a: 20
818 // a: b + 10 // 20 & b+10 ==> 20; where a == b+10
819 // b: a - 10 // 10-10 = 10 ==> 20 == 10+10
820 // `
821 // out: `<0>{a_0:20, b_1:10}`
822 }}
823 rewriteHelper(t, testCases, evalPartial)
824}
825
826func TestFullEval(t *testing.T) {
827 testCases := []testCase{{
828 desc: "detect conflicting value",
829 in: `
830 a: 8000.9
831 a: 7080 | int`,
832 out: `<0>{a: _|_(empty disjunction after evaluation)}`,
833 }, {
834 desc: "resolve all disjunctions",
835 in: `
836 service <Name>: {
837 name: Name | string
838 port: 7080 | int
839 }
840 service foo: _
841 service bar: { port: 8000 }
842 service baz: { name: "foobar" }
843 `,
844 out: `<0>{service: <1>{<>: <2>(Name: string)-><3>{name: (<2>.Name | string), port: (7080 | int)}, foo: <4>{name: "foo", port: 7080}, bar: <5>{name: "bar", port: 8000}, baz: <6>{name: "foobar", port: 7080}}}`,
845 }, {
846 desc: "field templates",
847 in: `
848 a: {
849 <name>: int
850 k: 1
851 }
852 b: {
853 <x>: { x: 0, y: 1 | int }
854 v: {}
855 w: { y: 0 }
856 }
857 b: { <y>: {} } // TODO: allow different name
858 c: {
859 <Name>: { name: Name, y: 1 }
860 foo: {}
861 bar: _
862 }
863 `,
864 out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (1 | int)} & <6>{}), v: <7>{x: 0, y: 1}, w: <8>{x: 0, y: 0}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
865 }, {
866 desc: "field comprehension",
867 in: `
868 a: { "\(k)": v for k, v in b if k < "d" if v > b.a }
869 b: {
870 a: 1
871 b: 2
872 c: 3
873 d: 4
874 }
875 c: {
876 "\(k)": v <-
877 for k, v in b
878 if k < "d"
879 if v > b.a
880 }
881 // TODO: Propagate error:
882 // e: { "\(k)": v for k, v in b if k < "d" if v > b.a }
883 `,
884 out: `<0>{a: <1>{b: 2, c: 3}, b: <2>{a: 1, b: 2, c: 3, d: 4}, c: <3>{b: 2, c: 3}}`,
885 }, {
886 desc: "nested templates in one field",
887 in: `
888 a <A> b <B>: {
889 name: A
890 kind: B
891 }
892 a "A" b "B": _
893 a "C" b "D": _
894 a "E" b "F": { c: "bar" }
895 `,
896 out: `<0>{a: <1>{<>: <2>(A: string)-><3>{b: <4>{<>: <5>(B: string)-><6>{name: <2>.A, kind: <5>.B}, }}, A: <7>{b: <8>{<>: <9>(B: string)-><10>{name: <11>.A, kind: <9>.B}, B: <12>{name: "A", kind: "B"}}}, C: <13>{b: <14>{<>: <15>(B: string)-><16>{name: <17>.A, kind: <15>.B}, D: <18>{name: "C", kind: "D"}}}, E: <19>{b: <20>{<>: <21>(B: string)-><22>{name: <23>.A, kind: <21>.B}, F: <24>{name: "E", kind: "F", c: "bar"}}}}}`,
897 }, {
898 desc: "template unification within one struct",
899 in: `
900 a: {
901 <A>: { name: A }
902 <A>: { kind: A }
903 }
904 a "A": _
905 a "C": _
906 a "E": { c: "bar" }
907 `,
908 out: `<0>{a: <1>{<>: <2>(A: string)->(<3>{name: <2>.A} & <4>{kind: <2>.A}), ` +
909 `A: <5>{name: "A", kind: "A"}, ` +
910 `C: <6>{name: "C", kind: "C"}, ` +
911 `E: <7>{name: "E", kind: "E", c: "bar"}}}`,
912 }, {
913 desc: "field comprehensions with multiple keys",
914 in: `
915 a "\(x.a)" b "\(x.b)": x for x in [
916 {a: "A", b: "B" },
917 {a: "C", b: "D" },
918 {a: "E", b: "F" },
919 ]
920
921 "\(x.a)" "\(x.b)": x for x in [
922 {a: "A", b: "B" },
923 {a: "C", b: "D" },
924 {a: "E", b: "F" },
925 ]`,
926 out: `<0>{a: <1>{` +
927 `A: <2>{b: <3>{B: <4>{a: "A", b: "B"}}}, ` +
928 `C: <5>{b: <6>{D: <7>{a: "C", b: "D"}}}, ` +
929 `E: <8>{b: <9>{F: <10>{a: "E", b: "F"}}}}, ` +
930 `A: <11>{B: <12>{a: "A", b: "B"}}, ` +
931 `C: <13>{D: <14>{a: "C", b: "D"}}, ` +
932 `E: <15>{F: <16>{a: "E", b: "F"}}}`,
933 }, {
934 desc: "field comprehensions with templates",
935 in: `
936 num: 1
937 a: {
938 <A> <B>: {
939 name: A
940 kind: B
941 } if num < 5
942
943 }
944 a b c d: "bar"
945 `,
946 out: `<0>{num: 1, a: <1>{<>: <2>(A: string)-><3>{<>: <4>(B: string)-><5>{name: <2>.A, kind: <4>.B}, }, ` +
947 `b: <6>{<>: <7>(B: string)-><8>{name: <9>.A, kind: <7>.B}, ` +
948 `c: <10>{name: "b", kind: "c", ` +
949 `d: "bar"}}}}`,
950 }, {
951 desc: "disjunctions of lists",
952 in: `
953 l: [ int, int ] | [ string, string ]
954
955 l1: [ "a", "b" ]
956 l2: l & [ "c", "d" ]
957 `,
958 out: `<0>{l: [int,int], l1: ["a","b"], l2: ["c","d"]}`,
959 }, {
960 desc: "list comprehension",
961 in: `
962 // a: [ k for k: v in b if k < "d" if v > b.a ] // TODO test error using common iso colon
963 a: [ k for k, v in b if k < "d" if v > b.a ]
964 b: {
965 a: 1
966 b: 2
967 c: 3
968 d: 4
969 }
970 c: [ x for _, x in b for _, y in b if x < y ]
971 d: [ x for x, _ in a ]
972 `,
973 out: `<0>{a: ["b","c"], b: <1>{a: 1, b: 2, c: 3, d: 4}, c: [1,1,1,2,2,3], d: [0,1]}`,
974 }, {
975 desc: "struct comprehension with template",
976 in: `
977 result: [ v for _, v in service ]
978
979 service <Name>: {
980 type: "service"
981 name: Name | string
982 port: 7080 | int
983 }
984 service foo: {}
985 service bar: { port: 8000 }
986 service baz: { name: "foobar" }
987 `,
988 out: `<0>{result: [` +
989 `<1>{type: "service", name: "foo", port: 7080},` +
990 `<2>{type: "service", name: "bar", port: 8000},` +
991 `<3>{type: "service", name: "foobar", port: 7080}], ` +
992
993 `service: <4>{` +
994 `<>: <5>(Name: string)-><6>{type: "service", name: (<5>.Name | string), port: (7080 | int)}, ` +
995 `foo: <7>{type: "service", name: "foo", port: 7080}, ` +
996 `bar: <8>{type: "service", name: "bar", port: 8000}, ` +
997 `baz: <9>{type: "service", name: "foobar", port: 7080}}}`,
998 }, {
999 desc: "resolutions in struct comprehension keys",
1000 in: `
Marcel van Lohuizen76b92b52018-12-16 10:47:03 +01001001 a: { "\(b + ".")": "a" for _, b in ["c"] }
Marcel van Lohuizen17157ea2018-12-11 10:41:10 +01001002 `,
1003 out: `<0>{a: <1>{c.: "a"}}`,
1004 }, {
1005 desc: "recursive evaluation within list",
1006 in: `
1007 l: [a]
1008 a: b & { c: "t" }
1009 b: {
1010 d: c
1011 c: string
1012 }
1013 l1: [a1]
1014 a1: b1 & { c: "t" }
1015 b1: {
1016 d: "s" + c
1017 c: string
1018 }
1019 `,
1020 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)*))}}`,
1021 }, {
1022 desc: "ips",
1023 in: `
1024 IP: 4*[ 0..255 ]
1025
1026 Private:
1027 [ 192, 168, 0..255, 0..255 ] |
1028 [ 10, 0..255, 0..255, 0..255] |
1029 [ 172, 16..32, 0..255, 0..255 ]
1030
1031 Inst: Private & [ _, 10, ... ]
1032
1033 MyIP: Inst & [_, _, 10, 10 ]
1034 `,
1035 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]}`,
1036 }, {
1037 desc: "complex interaction of groundness",
1038 in: `
1039 res: [ y & { d: "b" } for x in a for y in x ]
1040 res: [ a.b.c & { d: "b" } ]
1041
1042 a b <C>: { d: string, s: "a" + d }
1043 a b c d: string
1044 `,
1045 // TODO(perf): unification should catch shared node.
1046 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)*))}}}}`,
1047 }, {
1048 desc: "complex groundness 2",
1049 in: `
1050 r1: f1 & { y: "c" }
1051
1052 f1: { y: string, res: a.b.c & { d: y } }
1053
1054 a b c: { d: string, s: "a" + d }
1055 a b <C>: { d: string, s: "a" + d }
1056 a b c d: string
1057 `,
1058 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)*))}}}}`,
1059 }, {
1060 desc: "references from template to concrete",
1061 in: `
1062 res: [t]
1063 t <X>: {
1064 a: c + b.str
1065 b str: string
1066 c: "X"
1067 }
1068 t x: { b str: "DDDD" }
1069 `,
1070 out: `<0>{res: [<1>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <5>{a: "XDDDD", c: "X", b: <6>{str: "DDDD"}}}], ` +
1071 `t: <7>{<>: <2>(X: string)-><3>{a: (<3>.c + <3>.b.str), c: "X", b: <4>{str: string}}, x: <8>{a: "XDDDD", c: "X", b: <9>{str: "DDDD"}}}}`,
1072 }}
1073 rewriteHelper(t, testCases, evalFull)
1074}