blob: e7480dac3a2271f37db293af0763c1fd5acdd733 [file] [log] [blame]
// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cue
import (
"flag"
"strings"
"testing"
)
var traceOn = flag.Bool("debug", false, "enable tracing")
func compileFileWithErrors(t *testing.T, body string) (*context, *structLit, error) {
t.Helper()
ctx, inst, err := compileInstance(t, body)
return ctx, inst.rootValue.evalPartial(ctx).(*structLit), err
}
func compileFile(t *testing.T, body string) (*context, *structLit) {
t.Helper()
ctx, inst, errs := compileInstance(t, body)
if errs != nil {
t.Fatal(errs)
}
return ctx, inst.rootValue.evalPartial(ctx).(*structLit)
}
func compileInstance(t *testing.T, body string) (*context, *Instance, error) {
var r Runtime
inst, err := r.Compile("test", body)
if err != nil {
x := newInstance(newIndex(sharedIndex), nil)
ctx := x.newContext()
return ctx, x, err
}
return r.index().newContext(), inst, nil
}
func rewriteHelper(t *testing.T, cases []testCase, r rewriteMode) {
for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
ctx, obj := compileFile(t, tc.in)
ctx.trace = *traceOn
root := testResolve(ctx, obj, r)
got := debugStr(ctx, root)
// Copy the result
if got != tc.out {
fn := t.Errorf
if tc.skip {
fn = t.Skipf
}
fn("output differs:\ngot %s\nwant %s", got, tc.out)
}
})
}
}
type testCase struct {
desc string
in string
out string
skip bool
}
func TestBasicRewrite(t *testing.T) {
testCases := []testCase{{
desc: "errors",
in: `
a: _|_ & _|_
b: null & _|_
c: b.a == _|_
d: _|_ != b.a
e: _|_ == _|_
`,
out: `<0>{a: _|_(from source), b: _|_(from source), c: true, d: false, e: true}`,
}, {
desc: "regexp",
in: `
c1: "a" =~ "a"
c2: "foo" =~ "[a-z]{3}"
c3: "foo" =~ "[a-z]{4}"
c4: "foo" !~ "[a-z]{4}"
b1: =~ "a"
b1: "a"
b2: =~ "[a-z]{3}"
b2: "foo"
b3: =~ "[a-z]{4}"
b3: "foo"
b4: !~ "[a-z]{4}"
b4: "foo"
s1: != "b" & =~"c" // =~"c"
s2: != "b" & =~"[a-z]" // != "b" & =~"[a-z]"
e1: "foo" =~ 1
e2: "foo" !~ true
e3: != "a" & <5
`,
out: `<0>{c1: true, ` +
`c2: true, ` +
`c3: false, ` +
`c4: true, ` +
`b1: "a", ` +
`b2: "foo", ` +
`b3: _|_((=~"[a-z]{4}" & "foo"):invalid value "foo" (does not match =~"[a-z]{4}")), ` +
`b4: "foo", ` +
`s1: =~"c", ` +
`s2: (!="b" & =~"[a-z]"), ` +
`e1: _|_(("foo" =~ 1):invalid operation "foo" =~ 1 (mismatched types string and int)), ` +
`e2: _|_(("foo" !~ true):invalid operation "foo" !~ true (mismatched types string and bool)), ` +
`e3: _|_((!="a" & <5):conflicting values !="a" and <5 (mismatched types string and number))}`,
}, {
desc: "arithmetic",
in: `
i1: 1 & int
i2: 2 & int
sum: -1 + +2 // 1
div1: 2.0 / 3 * 6 // 4
div2: 2 / 3 * 6 // 4
div3: 1.00 / 1.00
divZero: 1.0 / 0
div00: 0 / 0
b: 1 != 4
add: div1 + 1.0
idiv00: 0 div 0
imod00: 0 mod 0
iquo00: 0 quo 0
irem00: 0 rem 0
v1: 1.0T/2.0
v2: 2.0 == 2
v3: 2.0/3.0
v5: i1 div i2
e0: 2 + "a"
// these are now all alloweed
// e1: 2.0 / i1
// e2: i1 / 2.0
// e3: 3.0 % i2
// e4: i1 % 2.0
e5: 1.0 div 2
e6: 2 rem 2.0
e7: 2 quo 2.0
e8: 1.0 mod 1
`,
out: `<0>{i1: 1, i2: 2, ` +
`sum: 1, ` +
`div1: 4.00000000000000000000000, ` +
`div2: 4.00000000000000000000000, ` +
`div3: 1., ` +
`divZero: _|_((1.0 / 0):division by zero), ` +
`div00: _|_((0 / 0):division undefined), ` +
`b: true, ` +
`add: 5.00000000000000000000000, ` +
`idiv00: _|_((0 div 0):division by zero), ` +
`imod00: _|_((0 mod 0):division by zero), ` +
`iquo00: _|_((0 quo 0):division by zero), ` +
`irem00: _|_((0 rem 0):division by zero), ` +
`v1: 5.0000000000e+11, ` +
`v2: true, ` +
`v3: 0.666666666666666666666667, ` +
`v5: 0, ` +
`e0: _|_((2 + "a"):invalid operation 2 + "a" (mismatched types int and string)), ` +
// `e1: _|_((2.0 / 1):unsupported op /(float, int)), ` +
// `e2: _|_((1 / 2.0):unsupported op /(int, float)), ` +
// `e3: _|_((3.0 % 2):unsupported op %(float, int)), ` +
// `e4: _|_((1 % 2.0):unsupported op %(int, float)), ` +
`e5: _|_((1.0 div 2):invalid operation 1.0 div 2 (mismatched types float and int)), ` +
`e6: _|_((2 rem 2.0):invalid operation 2 rem 2.0 (mismatched types int and float)), ` +
`e7: _|_((2 quo 2.0):invalid operation 2 quo 2.0 (mismatched types int and float)), ` +
`e8: _|_((1.0 mod 1):invalid operation 1.0 mod 1 (mismatched types float and int))}`,
}, {
desc: "integer-specific arithmetic",
in: `
q1: 5 quo 2 // 2
q2: 5 quo -2 // -2
q3: -5 quo 2 // -2
q4: -5 quo -2 // 2
qe1: 2.0 quo 1
qe2: 2 quo 1.0
r1: 5 rem 2 // 1
r2: 5 rem -2 // 1
r3: -5 rem 2 // -1
r4: -5 rem -2 // -1
re1: 2.0 rem 1
re2: 2 rem 1.0
d1: 5 div 2 // 2
d2: 5 div -2 // -2
d3: -5 div 2 // -3
d4: -5 div -2 // 3
de1: 2.0 div 1
de2: 2 div 1.0
m1: 5 mod 2 // 1
m2: 5 mod -2 // 1
m3: -5 mod 2 // 1
m4: -5 mod -2 // 1
me1: 2.0 mod 1
me2: 2 mod 1.0
`,
out: `<0>{q1: 2, q2: -2, q3: -2, q4: 2, ` +
`qe1: _|_((2.0 quo 1):invalid operation 2.0 quo 1 (mismatched types float and int)), ` +
`qe2: _|_((2 quo 1.0):invalid operation 2 quo 1.0 (mismatched types int and float)), ` +
`r1: 1, r2: 1, r3: -1, r4: -1, ` +
`re1: _|_((2.0 rem 1):invalid operation 2.0 rem 1 (mismatched types float and int)), ` +
`re2: _|_((2 rem 1.0):invalid operation 2 rem 1.0 (mismatched types int and float)), ` +
`d1: 2, d2: -2, d3: -3, d4: 3, ` +
`de1: _|_((2.0 div 1):invalid operation 2.0 div 1 (mismatched types float and int)), ` +
`de2: _|_((2 div 1.0):invalid operation 2 div 1.0 (mismatched types int and float)), ` +
`m1: 1, m2: 1, m3: 1, m4: 1, ` +
`me1: _|_((2.0 mod 1):invalid operation 2.0 mod 1 (mismatched types float and int)), ` +
`me2: _|_((2 mod 1.0):invalid operation 2 mod 1.0 (mismatched types int and float))}`,
}, {
desc: "booleans",
in: `
t: true
t: !false
f: false
f: !t
e: true
e: !true
`,
out: "<0>{t: true, f: false, e: _|_(true:conflicting values true and false)}",
}, {
desc: "boolean arithmetic",
in: `
a: true && true
b: true || false
c: false == true
d: false != true
e: true & true
f: true & false
`,
out: "<0>{a: true, b: true, c: false, d: true, e: true, f: _|_(true:conflicting values true and false)}",
}, {
desc: "basic type",
in: `
a: 1 & int
b: number & 1
c: 1.0
c: float
d: int & float // _|_
e: "4" & string
f: true
f: bool
`,
out: `<0>{a: 1, b: 1, c: 1.0, d: _|_((int & float):conflicting values int and float (mismatched types int and float)), e: "4", f: true}`, // TODO: eliminate redundancy
}, {
desc: "strings and bytes",
in: `
s0: "foo" + "bar"
s1: 3 * "abc"
s2: "abc" * 2
b0: 'foo' + 'bar'
b1: 3 * 'abc'
b2: 'abc' * 2
// TODO: consider the semantics of this and perhaps allow this.
e0: "a" + ''
e1: 'b' + "c"
`,
out: `<0>{` +
`s0: "foobar", ` +
`s1: "abcabcabc", ` +
`s2: "abcabc", ` +
`b0: 'foobar', ` +
`b1: 'abcabcabc', ` +
`b2: 'abcabc', ` +
`e0: _|_(("a" + ''):invalid operation "a" + '' (mismatched types string and bytes)), ` +
`e1: _|_(('b' + "c"):invalid operation 'b' + "c" (mismatched types bytes and string))` +
`}`,
}, {
desc: "escaping",
in: `
a: "foo\nbar",
b: a,
// TODO: mimic http://exploringjs.com/es6/ch_template-literals.html#sec_introduction-template-literals
`,
out: `<0>{a: "foo\nbar", b: "foo\nbar"}`,
// out: `<0>{a: "foo\nbar", b: <0>.a}`,
}, {
desc: "reference",
in: `
a: b
b: 2
d: {
d: 3
e: d
}
e: {
e: {
v: 1
}
f: {
v: e.v
}
}
`,
out: "<0>{a: 2, b: 2, d: <1>{d: 3, e: 3}, e: <2>{e: <3>{v: 1}, f: <4>{v: 1}}}",
}, {
desc: "lists",
in: `
list: [1,2,3]
index: [1,2,3][1]
unify: [1,2,3] & [_,2,3]
e: [] & 4
e2: [3]["d"]
e3: [3][-1]
e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8]
e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5]
`,
out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):conflicting values [] and 4 (mismatched types list and int)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: [1,2,4,_|_((<=5 & 8):invalid value 8 (out of bound <=5))], e5: [1,2,4,_|_((<=5 & 8):invalid value 8 (out of 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}
index: {a: 1, b: 2}["b"]
mulidx: {a: 1, b: {a:1, b: 3}}["b"]["b"]
e: {a: 1}[4]
f: {a: 1}.b
g: {a: 1}["b"]
h: [3].b
`,
out: `<0>{obj: <1>{a: 1, b: 2}, index: 2, mulidx: 3, e: _|_(4:invalid struct index 4 (type int)), f: <2>{a: 1}.b, g: <3>{a: 1}["b"], h: _|_([3]:invalid operation: [3].b (type list does not support selection))}`,
}, {
desc: "obj unify",
in: `
o1: {a: 1 } & { b: 2} // {a:1,b:2}
o2: {a: 1, b:2 } & { b: 2} // {a:1,b:2}
o3: {a: 1 } & { a:1, b: 2} // {a:1,b:2}
o4: {a: 1 } & { b: 2} // {a:1,b:2}
o4: {a: 1, b:2 } & { b: 2}
o4: {a: 1 } & { a:1, b: 2}
e: 1 // 1 & {a:3}
e: {a:3}
`,
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}):conflicting values 1 and {a: 3} (mismatched types int and struct))}",
}, {
desc: "disjunctions",
in: `
o1: 1 | 2 | 3
o2: (1 | 2 | 3) & 1
o3: 2 & (1 | *2 | 3)
o4: (1 | *2 | 3) & (1 | 2 | *3)
o5: (1 | *2 | 3) & (3 | *2 | 1)
o6: (1 | 2 | 3) & (3 | 1 | 2)
o7: (1 | 2 | 3) & (2 | 3)
o8: (1 | 2 | 3) & (3 | 2)
o9: (2 | 3) & (1 | 2 | 3)
o10: (3 | 2) & (1 | *2 | 3)
m1: (*1 | (*2 | 3)) & (>=2 & <=3)
m2: (*1 | (*2 | 3)) & (2 | 3)
m3: (*1 | *(*2 | 3)) & (2 | 3)
m4: (2 | 3) & (*2 | 3)
m5: (*2 | 3) & (2 | 3)
// (*2 | 3) & (2 | 3)
// (2 | 3) & (*2 | 3)
// 2&(*2 | 3) | 3&(*2 | 3)
// (*1 | (*2 | 3)) & (2 | 3)
// *1& (2 | 3) | (*2 | 3)&(2 | 3)
// *2&(2 | 3) | 3&(2 | 3)
// (2 | 3)&(*1 | (*2 | 3))
// 2&(*1 | (*2 | 3)) | 3&(*1 | (*2 | 3))
// *1&2 | (*2 | 3)&2 | *1&3 | (*2 | 3)&3
// (*2 | 3)&2 | (*2 | 3)&3
// *2 | 3
// All errors are treated the same as per the unification model.
i1: [1, 2][3] | "c"
`,
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), m1: (*2 | 3), m2: (*2 | 3), m3: (*2 | 3), m4: (*2 | 3), m5: (*2 | 3), i1: "c"}`,
}, {
desc: "types",
in: `
i: int
j: int & 3
s: string
t: "s" & string
e: int & string
e2: 1 & string
b: !int
p: +true
m: -false
`,
out: `<0>{i: int, j: 3, s: string, t: "s", e: _|_((int & string):conflicting values int and string (mismatched types int and string)), e2: _|_((1 & string):conflicting values 1 and string (mismatched types int and string)), b: _|_(!int:invalid operation !int (! int)), p: _|_(+true:invalid operation +true (+ bool)), m: _|_(-false:invalid operation -false (- bool))}`,
}, {
desc: "comparison",
in: `
lss: 1 < 2
leq: 1 <= 1.0
leq: 2.0 <= 3
eql: 1 == 1.0
neq: 1.0 == 1
gtr: !(2 > 3)
geq: 2.0 >= 2
seq: "a" + "b" == "ab"
err: 2 == "s"
`,
out: `<0>{lss: true, leq: true, eql: true, neq: true, gtr: true, geq: true, seq: true, err: _|_((2 == "s"):invalid operation 2 == "s" (mismatched types int and string))}`,
}, {
desc: "null",
in: `
eql: null == null
neq: null != null
unf: null & null
// errors
eq1: null == 1
eq2: 1 == null
ne1: "s" != null
call: null()
`,
out: `<0>{eql: true, neq: false, unf: null, eq1: false, eq2: false, ne1: true, call: _|_(null:cannot call non-function null (type null))}`,
}, {
desc: "self-reference cycles",
in: `
a: b - 100
b: a + 100
c: [c[1], c[0]]
`,
out: `<0>{a: (<1>.b - 100), ` +
`b: (<1>.a + 100), ` +
`c: [<1>.c[1],<1>.c[0]]}`,
}, {
desc: "resolved self-reference cycles",
in: `
a: b - 100
b: a + 100
b: 200
c: [c[1], a]
s1: s2 & {a: 1}
s2: s3 & {b: 2}
s3: s1 & {c: 3}
`,
out: `<0>{a: 100, b: 200, c: [100,100], s1: <1>{a: 1, b: 2, c: 3}, s2: <2>{a: 1, b: 2, c: 3}, s3: <3>{a: 1, b: 2, c: 3}}`,
}, {
desc: "resolved self-reference cycles: Issue 19",
in: `
// CUE knows how to resolve the following:
x: y + 100
y: x - 100
x: 200
z1: z2 + 1
z2: z3 + 2
z3: z1 - 3
z3: 8
// TODO: extensive tests with disjunctions.
`,
out: `<0>{x: 200, y: 100, z1: 11, z2: 10, z3: 8}`,
}, {
desc: "delayed constraint failure",
in: `
a: b - 100
b: a + 110
b: 200
x: 100
x: x + 1
`,
out: `<0>{` +
`x: _|_((100 & 101):conflicting values 100 and 101), ` +
`a: _|_((210 & 200):conflicting values 210 and 200), ` +
`b: _|_((210 & 200):conflicting values 210 and 200)}`,
// TODO: find a way to mark error in data.
}}
rewriteHelper(t, testCases, evalPartial)
}
func TestChooseDefault(t *testing.T) {
testCases := []testCase{{
desc: "pick first",
in: `
a: *5 | "a" | true
b: c: *{
a: 2
} | {
a : 3
}
`,
out: "<0>{a: 5, b: <1>{c: <2>{a: 2}}}",
}, {
// In this test, default results to bottom, meaning that the non-default
// value remains.
desc: "simple disambiguation conflict",
in: `
a: *"a" | "b"
b: *"b" | "a"
c: a & b
`,
out: `<0>{a: "a", b: "b", c: ("a" | "b")}`,
}, {
desc: "associativity of defaults",
in: `
a: *"a" | ("b" | "c")
b: (*"a" | "b") | "c"
c: *"a" | (*"b" | "c")
x: a & b
y: b & c
`,
out: `<0>{x: "a", y: (*"a" | *"b"), a: "a", b: "a", c: (*"a" | *"b")}`,
}}
rewriteHelper(t, testCases, evalFull)
}
func TestResolve(t *testing.T) {
testCases := []testCase{{
desc: "convert _ to top",
in: `a: { [_]: _ }`,
out: `<0>{a: <1>{...}}`,
}, {
in: `
a: b.c.d
b: c: { d: 3 }
c: { c: d.d, }
d: { d: 2 }
`,
out: "<0>{a: 3, b: <1>{c: <2>{d: 3}}, c: <3>{c: 2}, d: <4>{d: 2}}",
}, {
in: "`foo-bar`: 3\n x: `foo-bar`,",
out: `<0>{x: 3, "foo-bar": 3}`,
}, {
desc: "resolution of quoted identifiers",
in: `
package foo
` + "`foo-bar`" + `: 2
"baz": ` + "`foo-bar`" + `
a: {
qux: 3
` + "`qux-quux`" + `: qux
"qaz": ` + "`qux-quux`" + `
}`,
out: `<0>{"foo-bar": 2, baz: 2, a: <1>{qux: 3, "qux-quux": 3, qaz: 3}}`,
}, {
in: `
a: _
b: a
a: { d: 1, d: _ }
b: _
`,
out: `<0>{a: <1>{d: 1}, b: <2>{d: 1}}`,
}, {
desc: "JSON",
in: `
a="a": 3
b: a
o: { "a\nb": 2 } // TODO: use $ for root?
c: o["a\nb"]
`,
out: `<0>{a: 3, b: 3, o: <1>{"a\nb": 2}, c: 2}`,
}, {
desc: "arithmetic",
in: `
v1: 1.0T/2.0
v2: 2.0 == 2
n1: 1
v5: 2.0 / n1
v6: 1.0 / 1.0
e2: int & 4.0/2.0
`,
out: `<0>{v1: 5.0000000000e+11, v2: true, n1: 1, v5: 2.0, v6: 1., e2: _|_((int & (4.0 / 2.0)):conflicting values int and (4.0 / 2.0) (mismatched types int and float))}`,
}, {
desc: "inequality",
in: `
a: 1 != 2
b: 1 != null
c: true == null
d: null != {}
e: null == []
f: 0 == 0.0 // types are unified first TODO: make this consistent
`,
out: `<0>{a: true, b: true, c: false, d: true, e: false, f: true}`,
}, {
desc: "attributes",
in: `
a: { foo: 1 @foo() @baz(1) }
b: { foo: 1 @bar() @foo() }
c: a & b
e: a & { foo: 1 @foo(other) }
`,
out: `<0>{a: <1>{foo: 1 @baz(1) @foo()}, ` +
`b: <2>{foo: 1 @bar() @foo()}, ` +
`c: <3>{foo: 1 @bar() @baz(1) @foo()}, ` +
`e: _|_((<4>.a & <5>{foo: 1 @foo(other)}):conflicting attributes for key "foo")}`,
}, {
desc: "optional field unification",
in: `
a: { foo?: string }
b: { foo: "foo" }
c: a & b
d: a & { "foo"?: "bar" }
g1: 1
"g\(1)"?: 1
"g\(2)"?: 2
`,
out: `<0>{a: <1>{foo?: string}, ` +
`b: <2>{foo: "foo"}, ` +
`c: <3>{foo: "foo"}, ` +
`d: <4>{foo?: "bar"}, ` +
`g1: 1, ` +
`g2?: 2}`,
}, {
desc: "optional field resolves to incomplete",
in: `
r: {
a?: 3
b: a
c: r["a"]
}
`,
out: `<0>{r: <1>{a?: 3, b: <2>.a, c: <3>.r["a"]}}`,
// TODO(#152): should be
// out: `<0>{r: <1>{a?: 3, b: <2>.a, c: <2>["a"]}}`,
}, {
desc: "bounds",
in: `
i1: >1 & 5
i2: (>=0 & <=10) & 5
i3: !=null & []
i4: !=2 & !=4
s1: >=0 & <=10 & !=1 // no simplification
s2: >=0 & <=10 & !=11 // >=0 & <=10
s3: >5 & !=5 // >5
s4: <10 & !=10 // <10
s5: !=2 & !=2
// TODO: could change inequality
s6: !=2 & >=2
s7: >=2 & !=2
s8: !=5 & >5
s10: >=0 & <=10 & <12 & >1 // >1 & <=10
s11: >0 & >=0 & <=12 & <12 // >0 & <12
s20: >=10 & <=10 // 10
s22: >5 & <=6 // no simplification
s22a: >5 & (<=6 & int) // 6
s22b: (int & >5) & <=6 // 6
s22c: >=5 & (<6 & int) // 5
s22d: (int & >=5) & <6 // 5
s22e: (>=5 & <6) & int // 5
s22f: int & (>=5 & <6) // 5
s23: >0 & <2 // no simplification
s23a: (>0 & <2) & int // int & 1
s23b: int & (>0 & <2) // int & 1
s23c: (int & >0) & <2 // int & 1
s23d: >0 & (int & <2) // int & 1
s23e: >0.0 & <2.0 // no simplification
s30: >0 & int
e1: null & !=null
e2: !=null & null
e3: >1 & 1
e4: <0 & 0
e5: >1 & <0
e6: >11 & <11
e7: >=11 & <11
e8: >11 & <=11
e9: >"a" & <1
`,
out: `<0>{i1: 5, i2: 5, i3: [], i4: (!=2 & !=4), ` +
`s1: (>=0 & <=10 & !=1), ` +
`s2: (>=0 & <=10), ` +
`s3: >5, ` +
`s4: <10, ` +
`s5: !=2, ` +
`s6: (!=2 & >=2), ` +
`s7: (>=2 & !=2), ` +
`s8: >5, ` +
`s10: (<=10 & >1), ` +
`s11: (>0 & <12), ` +
`s20: 10, ` +
`s22: (>5 & <=6), ` +
`s22a: 6, ` +
`s22b: 6, ` +
`s22c: 5, ` +
`s22d: 5, ` +
`s22e: 5, ` +
`s22f: 5, ` +
`s23: (>0 & <2), ` +
`s23a: 1, ` +
`s23b: 1, ` +
`s23c: 1, ` +
`s23d: 1, ` +
`s23e: (>0.0 & <2.0), ` +
`s30: int & >0, ` +
`e1: _|_((!=null & null):invalid value null (excluded by !=null)), ` +
`e2: _|_((!=null & null):invalid value null (excluded by !=null)), ` +
`e3: _|_((>1 & 1):invalid value 1 (out of bound >1)), ` +
`e4: _|_((<0 & 0):invalid value 0 (out of bound <0)), ` +
`e5: _|_(conflicting bounds >1 and <0), ` +
`e6: _|_(conflicting bounds >11 and <11), ` +
`e7: _|_(conflicting bounds >=11 and <11), ` +
`e8: _|_(conflicting bounds >11 and <=11), ` +
`e9: _|_((>"a" & <1):conflicting values >"a" and <1 (mismatched types string and number))}`,
}, {
desc: "bound conversions",
in: `
r0: int & >0.1 & <=1.9
r1: int & >0.1 & <1.9
r2: int & >=0.1 & <1.9
r3: int & >=-1.9 & <=-0.1
r4: int & >-1.9 & <=-0.1
r5: >=1.1 & <=1.1
r6: r5 & 1.1
c1: (1.2 & >1.3) & <2
c2: 1.2 & (>1.3 & <2)
c3: 1.2 & (>=1 & <2)
c4: 1.2 & (>=1 & <2 & int)
`,
out: `<0>{` +
`r0: 1, ` +
`r1: 1, ` +
`r2: 1, ` +
`r3: -1, ` +
`r4: -1, ` +
`r5: 1.1, ` +
`r6: 1.1, ` +
`c1: _|_((>1.3 & 1.2):invalid value 1.2 (out of bound >1.3)), ` +
`c2: _|_((>1.3 & 1.2):invalid value 1.2 (out of bound >1.3)), ` +
`c3: 1.2, ` +
`c4: _|_((1.2 & ((>=1 & <2) & int)):conflicting values 1.2 and ((>=1 & <2) & int) (mismatched types float and int))}`,
}, {
desc: "custom validators",
in: `
import "strings"
a: strings.ContainsAny("ab")
a: "after"
b: strings.ContainsAny("c")
b: "dog"
c: strings.ContainsAny("d") & strings.ContainsAny("g")
c: "dog"
`,
out: `<0>{` +
`a: "after", ` +
`b: _|_(strings.ContainsAny ("c"):invalid value "dog" (does not satisfy strings.ContainsAny("c"))), ` +
`c: "dog"` +
`}`,
}, {
desc: "null coalescing",
in: `
a: null
b: a.x | "b"
c: a["x"] | "c"
`,
out: `<0>{a: null, b: "b", c: "c"}`,
}, {
desc: "reference across tuples and back",
// Tests that it is okay to partially evaluate structs.
in: `
a: { c: b.e, d: b.f }
b: { e: 3, f: a.c }
`,
out: "<0>{a: <1>{c: 3, d: 3}, b: <2>{e: 3, f: 3}}",
}, {
desc: "index",
in: `
a: [2][0]
b: {foo:"bar"}["foo"]
c: (*l|{"3":3})["3"]
d: (*[]|[1])[0]
l: []
e1: [2][""]
e2: 2[2]
e3: [][true]
e4: [1,2,3][3]
e5: [1,2,3][-1]
e6: (*[]|{})[1]
def: {
a: 1
#b: 3
}
e7: def["b"]
`,
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 int 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), def: <1>{a: 1, #b: 3}, e7: <2>.def["b"]}`,
// }, {
// NOTE: string indexing no longer supported.
// Keeping it around until this is no longer an experiment.
// desc: "string index",
// in: `
// a0: "abc"[0]
// a1: "abc"[1]
// a2: "abc"[2]
// a3: "abc"[3]
// a4: "abc"[-1]
// b: "zoëven"[2]
// `,
// 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: "ë"}`,
}, {
desc: "disjunctions of lists",
in: `
l: [ int, int ] | [ string, string ]
l1: [ "a", "b" ]
l2: l & [ "c", "d" ]
`,
out: `<0>{l: ([int,int] | [string,string]), l1: ["a","b"], l2: ["c","d"]}`,
}, {
desc: "slice",
in: `
a: [2][0:0]
b: [0][1:1]
e1: [][1:1]
e2: [0][-1:0]
e3: [0][1:0]
e4: [0][1:2]
e5: 4[1:2]
e6: [2]["":]
e7: [2][:"9"]
`,
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 int)), e6: _|_("":invalid slice index "" (type string)), e7: _|_("9":invalid slice index "9" (type string))}`,
// }, {
// NOTE: string indexing no longer supported.
// Keeping it around until this is no longer an experiment.
// desc: "string slice",
// in: `
// a0: ""[0:0]
// a1: ""[:]
// a2: ""[0:]
// a3: ""[:0]
// b0: "abc"[0:0]
// b1: "abc"[0:1]
// b2: "abc"[0:2]
// b3: "abc"[0:3]
// b4: "abc"[3:3]
// b5: "abc"[1:]
// b6: "abc"[:2]
// // TODO: supported extended graphemes, instead of just runes.
// u: "Spaß"[3:4]
// `,
// out: `<0>{a0: "", a1: "", a2: "", a3: "", b0: "", b1: "a", b2: "ab", b3: "abc", b4: "", b5: "bc", b6: "ab", u: "ß"}`,
}, {
desc: "list types",
in: `
l0: 3*[int]
l0: [1, 2, 3]
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]
s2: [0,2,3][1:2]
i1: (6*[int])[2]
i2: [0,2,3][2]
t0: [...{a: 8}]
t0: [{}]
t1: [...]
t1: [...int]
e0: 2*[{}]
e0: [{}]
e1: [...int]
e1: [...float]
`,
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>{}]):conflicting list lengths: conflicting values 2 and 1), ` +
`e1: [, ..._|_((int & float):conflicting values int and float (mismatched types int and float))]` +
`}`,
}, {
// TODO: consider removing list arithmetic altogether. It is no longer
// needed to indicate the allowed capacity of a list and that didn't
// work anyway.
desc: "list arithmetic",
in: `
l0: 3*[1, 2, 3]
l1: 0*[1, 2, 3]
l2: 10*[]
l3: <=2*[]
l4: <=2*[int]
l5: <=2*(int*[int])
l6: 3*[...int]
l7: 3*[1, ...int]
l8: 3*[1, 2, ...int]
s0: [] + []
s1: [1] + []
s2: [] + [2]
s3: [1] + [2]
s4: [1,2] + []
s5: [] + [1,2]
s6: [1] + [1,2]
s7: [1,2] + [1]
s8: [1,2] + [1,2]
s9: [] + [...]
s10: [1] + [...]
s11: [] + [2, ...]
s12: [1] + [2, ...]
s13: [1,2] + [...]
s14: [] + [1,2, ...]
s15: [1] + [1,2, ...]
s16: [1,2] + [1, ...]
s17: [1,2] + [1,2, ...]
s18: [...] + []
s19: [1, ...] + []
s20: [...] + [2]
s21: [1, ...] + [2]
s22: [1,2, ...] + []
s23: [...] + [1,2]
s24: [1, ...] + [1,2]
s25: [1,2, ...] + [1]
s26: [1,2, ...] + [1,2]
s27: [...] + [...]
s28: [1, ...] + [...]
s29: [...] + [2, ...]
s30: [1, ...] + [2, ...]
s31: [1,2, ...] + [...]
s32: [...] + [1,2, ...]
s33: [1, ...] + [1,2, ...]
s34: [1,2, ...] + [1, ...]
s35: [1,2, ...] + [1,2, ...]
`,
out: `<0>{l0: [1,2,3,1,2,3,1,2,3], ` +
`l1: [], ` +
`l2: [], ` +
`l3: (<=2 * []), ` +
`l4: (<=2 * [int]), ` +
`l5: (<=2 * (int * [int])), ` +
`l6: [], ` +
`l7: [1,1,1], ` +
`l8: [1,2,1,2,1,2], ` +
`s0: [], ` +
`s1: [1], ` +
`s2: [2], ` +
`s3: [1,2], ` +
`s4: [1,2], ` +
`s5: [1,2], ` +
`s6: [1,1,2], ` +
`s7: [1,2,1], ` +
`s8: [1,2,1,2], ` +
`s9: [], ` +
`s10: [1], ` +
`s11: [2], ` +
`s12: [1,2], ` +
`s13: [1,2], ` +
`s14: [1,2], ` +
`s15: [1,1,2], ` +
`s16: [1,2,1], ` +
`s17: [1,2,1,2], ` +
`s18: [], ` +
`s19: [1], ` +
`s20: [2], ` +
`s21: [1,2], ` +
`s22: [1,2], ` +
`s23: [1,2], ` +
`s24: [1,1,2], ` +
`s25: [1,2,1], ` +
`s26: [1,2,1,2], ` +
`s27: [], ` +
`s28: [1], ` +
`s29: [2], ` +
`s30: [1,2], ` +
`s31: [1,2], ` +
`s32: [1,2], ` +
`s33: [1,1,2], ` +
`s34: [1,2,1], ` +
`s35: [1,2,1,2]` +
`}`,
}, {
desc: "list equality",
in: `
eq0: [] == []
eq1: [...] == []
eq2: [] == [...]
eq3: [...] == [...]
eq4: [1] == [1]
eq5: [1, ...] == [1]
eq6: [1] == [1, ...]
eq7: [1, ...] == [1, ...]
eq8: [1, 2] == [1, 2]
eq9: [1, 2, ...] == [1, 2]
eq10: [1, 2] == [1, 2, ...]
eq11: [1, 2, ...] == [1, 2, ...]
ne0: [] != []
ne1: [...] != []
ne2: [] != [...]
ne3: [...] != [...]
ne4: [1] != [1]
ne5: [1, ...] != [1]
ne6: [1] != [1, ...]
ne7: [1, ...] != [1, ...]
ne8: [1, 2] != [1, 2]
ne9: [1, 2, ...] != [1, 2]
ne10: [1, 2] != [1, 2, ...]
ne11: [1, 2, ...] != [1, 2, ...]
feq0: [] == [1]
feq1: [...] == [1]
feq2: [] == [1, ...]
feq3: [...] == [1, ...]
feq4: [1] == []
feq5: [1, ...] == []
feq6: [1] == [...]
feq7: [1, ...] == [...]
feq8: [1, 2] == [1]
feq9: [1, ...] == [1, 2]
feq10: [1, 2] == [1, ...]
feq11: [1, ...] == [1, 2, ...]
fne0: [] != [1]
fne1: [...] != [1]
fne2: [] != [1, ...]
fne3: [1, ...] != [1, ...]
fne4: [1] != []
fne5: [1, ...] != []
fne6: [1] != [...]
fne7: [1, ...] != [...]
fne8: [1, 2] != [1]
fne9: [1, ...] != [1, 2]
fne10: [1, 2] != [1, ...]
fne11: [1, ...] != [1, 2, ...]
`,
out: `<0>{` +
`eq0: true, eq1: true, eq2: true, eq3: true, eq4: true, eq5: true, eq6: true, eq7: true, eq8: true, eq9: true, eq10: true, eq11: true, ` +
`ne0: true, ne1: true, ne2: true, ne3: true, ne4: false, ne5: false, ne6: false, ne7: false, ne8: false, ne9: false, ne10: false, ne11: false, ` +
`feq0: false, feq1: false, feq2: false, feq3: false, feq4: false, feq5: false, feq6: false, feq7: false, feq8: false, feq9: false, feq10: false, feq11: false, ` +
`fne0: false, fne1: false, fne2: false, fne3: false, fne4: false, fne5: false, fne6: false, fne7: false, fne8: false, fne9: false, fne10: false, fne11: false}`,
}, {
desc: "list unification",
in: `
a: { l: ["foo", v], v: l[1] }
b: a & { l: [_, "bar"] }
`,
out: `<0>{` +
`a: <1>{l: ["foo",<2>.v], ` +
`v: <2>.l[1]}, ` +
`b: <3>{l: ["foo","bar"], v: "bar"}}`,
}, {
desc: "correct error messages",
// Tests that it is okay to partially evaluate structs.
in: `
a: "a" & 1
`,
out: `<0>{a: _|_(("a" & 1):conflicting values "a" and 1 (mismatched types string and int))}`,
}, {
desc: "structs",
in: `
a: t & { c: 5 } // {c:5,d:15}
b: ti & { c: 7 } // {c:7,d:21}
t: { c: number, d: c * 3 } // {c:number,d:number*3}
ti: t & { c: int }
`,
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)}}`,
}, {
desc: "definitions",
in: `
#Foo: {
field: int
recursive: {
field: string
}
}
// Allowed
#Foo1: { field: int }
#Foo1: { field2: string }
foo: #Foo
foo: { feild: 2 }
foo1: #Foo
foo1: {
field: 2
recursive: {
feild: 2 // Not caught as per spec. TODO: change?
}
}
#Bar: {
field: int
{[A=_]: int}
}
bar: #Bar
bar: { feild: 2 }
#Mixed: string
Mixed: string
mixedRec: { #Mixed: string }
mixedRec: { Mixed: string }
`,
out: `<0>{` +
`#Foo: <1>C{field: int, recursive: <2>C{field: string}}, ` +
`#Foo1: <3>C{field: int, field2: string}, ` +
`foo: _|_(2:field "feild" not allowed in closed struct), ` +
`foo1: <4>C{field: 2, recursive: _|_(2:field "feild" not allowed in closed struct)}, ` +
`#Bar: <5>{[]: <6>(A: string)->int, field: int}, ` +
`bar: <7>{[]: <8>(A: string)->int, field: int, feild: 2}, ` +
`#Mixed: string, ` +
`Mixed: string, ` +
`mixedRec: <9>{#Mixed: string, Mixed: string}}`,
}, {
desc: "combined definitions",
in: `
// Allow combining of structs within a definition
#D1: {
env: a: "A"
env: b: "B"
#def: {a: "A"}
#def: {b: "B"}
}
d1: #D1 & { env: c: "C" }
#D2: {
a: int
}
#D2: {
b: int
}
#D3: {
env: a: "A"
}
#D3: {
env: b: "B"
}
#D4: {
env: #DC
env: b: int
}
#DC: { a: int }
`,
out: `<0>{` +
`#D1: <1>C{env: <2>C{a: "A", b: "B"}, #def: <3>C{a: "A", b: "B"}}, ` +
`d1: <4>C{env: _|_("C":field "c" not allowed in closed struct), #def: <5>C{a: "A", b: "B"}}, ` +
`#D2: <6>C{a: int, b: int}, ` +
`#D3: <7>C{env: <8>C{a: "A", b: "B"}}, ` +
`#D4: <9>C{env: _|_(int:field "b" not allowed in closed struct)}, ` +
`#DC: <10>C{a: int}` +
`}`,
}, {
desc: "new-style definitions",
in: `
#Foo: {
a: 1
b: int
}
"#Foo": #Foo & {b: 1}
bulk: {[string]: string} & {
#def: 4 // Different namespace, so bulk option does not apply.
_hid: 3
a: "foo"
}
`,
out: `<0>{` +
`"#Foo": <1>C{a: 1, b: 1}, ` +
`#Foo: <2>C{a: 1, b: int}, ` +
`bulk: <3>{[]: <4>(_: string)->string, a: "foo", #def: 4, _hid: 3}` +
`}`,
}, {
desc: "recursive closing starting at non-definition",
in: `
z: a: {
#B: {
c: d: 1
c: f: 1
}
}
A: z & { a: { #B: { c: e: 2 } } }
`,
out: `<0>{z: <1>{a: <2>{#B: <3>C{c: <4>C{d: 1, f: 1}}}}, A: <5>{a: <6>{#B: <7>C{c: _|_(2:field "e" not allowed in closed struct)}}}}`,
}, {
desc: "non-closed definition carries over closedness to enclosed template",
in: `
#S: {
[string]: { a: int }
}
a: #S & {
v: { b: int }
}
#Q: {
[string]: { a: int } | { b: int }
}
b: #Q & {
w: { c: int }
}
#R: {
[string]: [{ a: int }, { b: int }]
}
c: #R & {
w: [{ d: int }, ...]
}
`,
out: `<0>{` +
`#S: <1>{[]: <2>(_: string)-><3>C{a: int}, }, ` +
`a: <4>{[]: <5>(_: string)-><6>C{a: int}, v: _|_(int:field "b" not allowed in closed struct)}, ` +
`b: <7>{[]: <8>(_: string)->(<9>C{a: int} | <10>C{b: int}), w: _|_(int:empty disjunction: field "c" not allowed in closed struct)}, ` +
`#Q: <11>{[]: <12>(_: string)->(<13>C{a: int} | <14>C{b: int}), }, ` +
`c: <15>{[]: <16>(_: string)->[<17>C{a: int},<18>C{b: int}], w: [_|_(int:field "d" not allowed in closed struct),<19>C{b: int}]}, ` +
`#R: <20>{[]: <21>(_: string)->[<22>C{a: int},<23>C{b: int}], }}`,
}, {
desc: "definitions with disjunctions",
in: `
#Foo: {
field: int
{ a: 1 } |
{ b: 2 }
}
foo: #Foo
foo: { a: 1 }
bar: #Foo
bar: { c: 2 }
baz: #Foo
baz: { b: 2 }
`,
out: `<0>{` +
`#Foo: (<1>C{field: int, a: 1} | <2>C{field: int, b: 2}), ` +
`foo: <3>C{field: int, a: 1}, ` +
`bar: _|_(2:empty disjunction: field "c" not allowed in closed struct), ` +
`baz: <4>C{field: int, b: 2}}`,
}, {
desc: "definitions with disjunctions recurisive",
in: `
#Foo: {
x: {
field: int
{ a: 1 } |
{ b: 2 }
}
x: c: 3
}
`,
out: `<0>{` +
`#Foo: <1>C{x: (<2>C{field: int, a: 1, c: 3} | <3>C{field: int, b: 2, c: 3})}` +
`}`,
}, {
desc: "definitions with embedding",
in: `
#E: {
a: { b: int }
}
#S: {
#E
a: { c: int }
b: 3
}
// adding a field to a nested struct that is closed.
#e1: #S & { a: d: 4 }
// literal struct not closed until after unification.
#v1: #S & { a: c: 4 }
`,
out: `<0>{` +
`#E: <1>C{a: <2>C{b: int}}, ` +
`#S: <3>C{a: <4>C{b: int, c: int}, b: 3}, ` +
`#e1: <5>C{a: _|_(4:field "d" not allowed in closed struct), b: 3}, ` +
`#v1: <6>C{a: <7>C{b: int, c: 4}, b: 3}}`,
}, {
desc: "top-level definition with struct and disjunction",
in: `
#def: {
Type: string
Text: string
Size: int
}
#def: {
Type: "B"
Size: 0
} | {
Type: "A"
Size: 1
}`,
out: `<0>{` +
`#def: (<1>C{Size: (0 & int), Type: ("B" & string), Text: string} | ` +
`<2>C{Size: (1 & int), Type: ("A" & string), Text: string})` +
`}`,
}, {
desc: "closing structs",
in: `
op: {x: int} // {x: int}
ot: {x: int, ...} // {x: int, ...}
cp: close({x: int}) // closed({x: int})
ct: close({x: int, ...}) // {x: int, ...}
opot: op & ot // {x: int, ...}
otop: ot & op // {x: int, ...}
opcp: op & cp // closed({x: int})
cpop: cp & op // closed({x: int})
opct: op & ct // {x: int, ...}
ctop: ct & op // {x: int, ...}
otcp: ot & cp // closed({x: int})
cpot: cp & ot // closed({x: int})
otct: ot & ct // {x: int, ...}
ctot: ct & ot // {x: int, ...}
cpct: cp & ct // closed({x: int})
ctcp: ct & cp // closed({x: int})
ctct: ct & ct // {x: int, ...}
`,
out: `<0>{` +
`op: <1>{x: int}, ` +
`ot: <2>{x: int, ...}, ` +
`cp: <3>C{x: int}, ` +
`ct: <4>{x: int, ...}, ` +
`opot: <5>{x: int, ...}, ` +
`otop: <6>{x: int, ...}, ` +
`opcp: <7>C{x: int}, ` +
`cpop: <8>C{x: int}, ` +
`opct: <9>{x: int, ...}, ` +
`ctop: <10>{x: int, ...}, ` +
`otcp: <11>C{x: int}, ` +
`cpot: <12>C{x: int}, ` +
`otct: <13>{x: int, ...}, ` +
`ctot: <14>{x: int, ...}, ` +
`cpct: <15>C{x: int}, ` +
`ctcp: <16>C{x: int}, ` +
`ctct: <17>{x: int, ...}}`,
}, {
desc: "excluded embedding from closing",
in: `
#S: {
a: { c: int }
{
c: { d: int }
}
B = { open: int }
b: B
}
V: #S & {
c: e: int
b: extra: int
}
`,
out: `<0>{` +
`#S: <1>C{` +
`a: <2>C{c: int}, ` +
`c: <3>{d: int}, ` +
`b: <4>{open: int}}, ` +
`V: <5>C{` +
`a: <6>C{c: int}, ` +
`c: <7>{d: int, e: int}, ` +
`b: <8>{open: int, extra: int}}}`,
}, {
desc: "closing with failed optional",
in: `
#k1: {a: int, b?: int} & #A // closed({a: int})
#k2: #A & {a: int, b?: int} // closed({a: int})
o1: {a?: 3} & {a?: 4} // {a?: _|_}
// Optional fields with error values can be elimintated when closing
#o2: {a?: 3} & {a?: 4} // close({})
#d1: {a?: 2, b: 4} | {a?: 3, c: 5}
v1: #d1 & {a?: 3, b: 4} // close({b: 4})
#A: {a: int}
`,
out: `<0>{` +
`#k1: <1>C{a: int}, ` +
`#A: <2>C{a: int}, ` +
`#k2: <3>C{a: int}, ` +
`o1: <4>{a?: _|_((3 & 4):conflicting values 3 and 4)}, ` +
`#o2: <5>C{a?: _|_((3 & 4):conflicting values 3 and 4)}, ` +
`#d1: (<6>C{a?: 2, b: 4} | <7>C{a?: 3, c: 5}), ` +
`v1: <8>C{a?: _|_((2 & 3):conflicting values 2 and 3), b: 4}` +
`}`,
}, {
desc: "closing with comprehensions",
in: `
#A: {f1: int, f2: int}
for k, v in {f3 : int} {
a: #A & { "\(k)": v }
}
#B: {
for k, v in {f1: int} {
"\(k)": v
}
}
#C: {
f1: _
for k, v in {f1: int} {
"\(k)": v
}
}
#D: {
for k, v in {f1: int} {
"\(k)": v
}
...
}
#E: #A & {
for k, v in { f3: int } {
"\(k)": v
}
}
`,
out: `<0>{` +
`#A: <1>C{f1: int, f2: int}, ` +
`a: _|_(<2>.v:field "f3" not allowed in closed struct), ` +
`#B: <3>C{f1: int}, ` +
`#C: <4>C{f1: int}, ` +
`#D: <5>{f1: int, ...}, ` +
`#E: _|_(<6>.v:field "f3" not allowed in closed struct)` +
`}`,
}, {
desc: "incomplete comprehensions",
in: `
A: {
for v in src {
"\(v)": v
}
src: _
if true {
baz: "baz"
}
}
B: A & {
src: ["foo", "bar"]
}
`,
out: `<0>{` +
`A: <1>{src: _, baz: "baz" <2>for _, v in <3>.src yield <4>{""+<2>.v+"": <2>.v}}, ` +
`B: <5>{src: ["foo","bar"], baz: "baz", foo: "foo", bar: "bar"}}`,
}, {
desc: "reference to root",
in: `
a: { b: int }
c: a & {
b: 100
d: a.b + 3 // do not resolve as c != a.
}
x: {
b: int
c: b + 5
}
y: x & {
b: 100
// c should resolve to 105
}
v: {
b: int
c: v.b + 5 // reference starting from copied node.
}
w: v & { b: 100 }
wp: v & { b: 100 }
`,
out: `<0>{x: <1>{b: int, c: (<2>.b + 5)}, y: <3>{b: 100, c: 105}, a: <4>{b: int}, c: <5>{b: 100, d: (<6>.a.b + 3)}, v: <7>{b: int, c: (<6>.v.b + 5)}, w: <8>{b: 100, c: (<6>.v.b + 5)}, wp: <9>{b: 100, c: (<6>.v.b + 5)}}`,
// TODO(#152): should be
// 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}}`,
}, {
desc: "references from template to concrete",
in: `
res: [t]
t: [X=string]: {
a: c + b.str
b: str: string
c: "X"
}
t: x: { b: str: "DDDD" }
`,
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"}}}}`,
}, {
desc: "interpolation",
in: `
a: "\(4)"
b: "one \(a) two \( a + c )"
c: "one"
d: "\(r)"
u: "\(_)"
r: _
e: "\([])"`,
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))}`,
}, {
desc: "multiline interpolation",
in: `
a1: """
before
\(4)
after
"""
a2: """
before
\(4)
"""
a3: """
\(4)
after
"""
a4: """
\(4)
"""
m1: """
before
\(
4)
after
"""
m2: """
before
\(
4)
"""
m3: """
\(
4)
after
"""
m4: """
\(
4)
"""
`,
out: `<0>{` +
`a1: "before\n4\nafter", a2: "before\n4\n", a3: "\n4\nafter", a4: "\n4\n", ` +
`m1: "before\n4\nafter", m2: "before\n4\n", m3: "\n4\nafter", m4: "\n4\n"` +
`}`,
}, {
desc: "diamond-shaped constraints",
in: `
S: {
A: {
a: 1,
},
B: A & {
b: 2,
}
},
T: S & { // S == { A: { a:1 }, B: { a:1, b:2 } }
A: {
c: 3,
},
B: { // S.B & A
d: 4, // Combines constraints S.A, S.B, T.A, and T.B
}
}`,
out: "<0>{T: <1>{A: <2>{a: 1, c: 3}, B: <3>{a: 1, b: 2, c: 3, d: 4}}, S: <4>{A: <5>{a: 1}, B: <6>{a: 1, b: 2}}}",
}, {
desc: "field templates",
in: `
a: {
{[name=_]: int}
k: 1
}
b: {
{[X=_]: { x: 0, y: *1 | int }}
v: {}
w: { x: 0 }
}
b: { [y=_]: {} }
c: {
{[Name=_]: { name: Name, y: 1 }}
foo: {}
bar: _
}
`,
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>{y: 1, name: <10>.Name}, foo: <12>{y: 1, name: "foo"}, bar: <13>{y: 1, name: "bar"}}}`,
}, {
desc: "range unification",
in: `
// with concrete values
a1: >=1 & <=5 & 3
a2: >=1 & <=5 & 1
a3: >=1 & <=5 & 5
a4: >=1 & <=5 & 6
a5: >=1 & <=5 & 0
a6: 3 & >=1 & <=5
a7: 1 & >=1 & <=5
a8: 5 & >=1 & <=5
a9: 6 & >=1 & <=5
a10: 0 & >=1 & <=5
// with ranges
b1: >=1 & <=5 & >=1 & <=5
b2: >=1 & <=5 & >=1 & <=1
b3: >=1 & <=5 & >=5 & <=5
b4: >=1 & <=5 & >=2 & <=3
b5: >=1 & <=5 & >=3 & <=9
b6: >=1 & <=5 & >=5 & <=9
b7: >=1 & <=5 & >=6 & <=9
b8: >=1 & <=5 & >=1 & <=5
b9: >=1 & <=1 & >=1 & <=5
b10: >=5 & <=5 & >=1 & <=5
b11: >=2 & <=3 & >=1 & <=5
b12: >=3 & <=9 & >=1 & <=5
b13: >=5 & <=9 & >=1 & <=5
b14: >=6 & <=9 & >=1 & <=5
// ranges with more general types
c1: int & >=1 & <=5
c2: >=1 & <=5 & int
c3: string & >=1 & <=5
c4: >=1 & <=5 & string
// other types
s1: >="d" & <="z" & "e"
s2: >="d" & <="z" & "ee"
n1: number & >=1 & <=2
n2: int & >=1.1 & <=1.3
n3: >=1.0 & <=3.0 & 2
n4: >=0.0 & <=0.1 & 0.09999
n5: >=1 & <=5 & 2.5
`,
out: `<0>{` +
`a1: 3, ` +
`a2: 1, ` +
`a3: 5, ` +
`a4: _|_((<=5 & 6):invalid value 6 (out of bound <=5)), ` +
`a5: _|_((>=1 & 0):invalid value 0 (out of bound >=1)), ` +
`a6: 3, ` +
`a7: 1, ` +
`a8: 5, ` +
`a9: _|_((<=5 & 6):invalid value 6 (out of bound <=5)), ` +
`a10: _|_((>=1 & 0):invalid value 0 (out of bound >=1)), ` +
`b1: (>=1 & <=5), ` +
`b2: 1, ` +
`b3: 5, ` +
`b4: (>=2 & <=3), ` +
`b5: (>=3 & <=5), ` +
`b6: 5, ` +
`b7: _|_(conflicting bounds >=6 and <=5), ` +
`b8: (>=1 & <=5), ` +
`b9: 1, ` +
`b10: 5, ` +
`b11: (>=2 & <=3), ` +
`b12: (>=3 & <=5), ` +
`b13: 5, ` +
`b14: _|_(conflicting bounds >=6 and <=5), ` +
`c1: (int & >=1 & <=5), ` +
`c2: (<=5 & int & >=1), ` +
`c3: _|_((string & >=1):conflicting values string and >=1 (mismatched types string and number)), ` +
`c4: _|_(((>=1 & <=5) & string):conflicting values (>=1 & <=5) and string (mismatched types number and string)), ` +
`s1: "e", ` +
`s2: "ee", ` +
`n1: (>=1 & <=2), ` +
`n2: _|_(conflicting bounds int & >=1.1 and <=1.3), ` +
`n3: 2, ` +
`n4: 0.09999, ` +
`n5: 2.5}`,
}, {
desc: "predefined ranges",
in: `
k1: int8
k1: 44
k2: int64
k2: -8_000_000_000
e1: int16
e1: 100_000
`,
out: `<0>{k1: 44, k2: -8000000000, ` +
`e1: _|_((int & <=32767 & 100000):invalid value 100000 (out of bound int & <=32767))}`,
}, {
desc: "struct comprehensions",
in: `
obj: foo: a: "bar"
obj: [Name=string]: {
a: *"dummy" | string
if true {
sub: as: a
}
}
for k, v in { #def: 1, opt?: 2, _hid: 3, reg: 4 } {
"\(k)": v
}
`,
out: `<0>{obj: <1>{[]: <2>(Name: string)-><3>{a: (*"dummy" | string) if true yield <4>{sub: <5>{as: <3>.a}}}, foo: <6>{a: "bar", sub: <7>{as: "bar"}}}, reg: 4}`,
}, {
desc: "builtins",
in: `
a1: {
a: and([b, c])
b: =~"oo"
c: =~"fo"
}
a2: a1 & { a: "foo" }
a3: a1 & { a: "bar" }
o1: {
a: or([b, c])
b: string
c: "bar"
}
o2: o1 & { a: "foo" }
o3: o1 & { a: "foo", b: "baz" }
`,
out: `<0>{` +
`a1: <1>{a: (=~"oo" & =~"fo"), b: =~"oo", c: =~"fo"}, ` +
`a2: <2>{a: "foo", b: =~"oo", c: =~"fo"}, ` +
`a3: <3>{a: _|_((=~"oo" & "bar"):invalid value "bar" (does not match =~"oo")), b: =~"oo", c: =~"fo"}, ` +
`o1: <4>{a: string, b: string, c: "bar"}, ` +
`o2: <5>{a: "foo", b: string, c: "bar"}, ` +
`o3: <6>{a: _|_(("baz" & "foo"):empty disjunction: conflicting values "baz" and "foo";("bar" & "foo"):empty disjunction: conflicting values "bar" and "foo"), b: "baz", c: "bar"}}`,
}, {
desc: "self-reference cycles conflicts with strings",
in: `
a: {
x: y+"?"
y: x+"!"
}
a: x: "hey"
`,
out: `<0>{a: <1>{x: _|_(("hey!?" & "hey"):conflicting values "hey!?" and "hey"), y: "hey!"}}`,
}, {
desc: "resolved self-reference cycles with disjunctions",
in: `
a: b&{x:1} | {y:1} // {x:1,y:3,z:2} | {y:1}
b: {x:2} | c&{z:2} // {x:2} | {x:1,y:3,z:2}
c: a&{y:3} | {z:3} // {x:1,y:3,z:2} | {z:3}
`,
out: `<0>{a: (<1>{x: 1, y: 3, z: 2} | <2>{y: 1}), b: (<3>{x: 2} | <4>{x: 1, y: 3, z: 2}), c: (<5>{x: 1, y: 3, z: 2} | <6>{z: 3})}`,
}, {
// We take a very conservative stance on delaying arithmetic
// expressions within disjunctions. It should remain resolvable, though,
// once the user specifies one.
desc: "resolved self-reference cycles with disjunction",
in: `
// The second disjunct in xa1 is not resolvable and can be
// eliminated:
// xa4 & 9
// (xa2 + 2) & 9
// ((xa3 + 2) + 2) & 9
// (((6 & xa1-2) + 2) + 2) & 9
// ((6 + 2) + 2) & 9 // 6 == xa1-2
// 10 & 9 => _|_
// The remaining values resolve.
xa1: (xa2 & 8) | (xa4 & 9)
xa2: xa3 + 2
xa3: 6 & xa1-2
xa4: xa2 + 2
// The second disjunct in xb4 can be eliminated as both disjuncts
// of xb3 result in an incompatible sum when substituted.
xb1: (xb2 & 8) | (xb4 & 9)
xb2: xb3 + 2
xb3: (6 & (xb1-2)) | (xb4 & 9)
xb4: xb2 + 2
// Another variant with more disjunctions. xc1 remains with two
// possibilities. Technically, only the first value is valid.
// However, to fully determine that, all options of the remaining
// disjunction will have to be evaluated algebraically, which is
// not done.
xc1: xc2 & 8 | xc4 & 9 | xc5 & 9
xc2: xc3 + 2
xc3: 6 & xc1-2
xc4: xc2 + 1
xc5: xc2 + 2
// The above is resolved by setting xd1 explicitly.
xd1: xd2 & 8 | xd4 & 9 | xd5 & 9
xd2: xd3 + 2
xd3: 6 & xd1-2
xd4: xd2 + 1
xd5: xd2 + 2
xd1: 8
// The above is resolved by setting xd1 explicitly to the wrong
// value, resulting in an error.
xe1: xe2 & 8 | xe4 & 9 | xe5 & 9
xe2: xe3 + 2
xe3: 6 & xe1-2
xe4: xe2 + 1
xe5: xe2 + 2
xe1: 9
// Only one solution.
xf1: xf2 & 8 | xf4 & 9
xf2: xf3 + 2
xf3: 6 & xf1-2 | xf4 & 9
xf4: xf2 + 2
z1: z2 + 1 | z3 + 5
z2: z3 + 2
z3: z1 - 3
z3: 8
`,
out: `<0>{` +
`xa1: 8, ` +
`xa2: 8, ` +
`xa4: 10, ` +
`xa3: 6, ` +
`xb1: 8, ` +
`xb2: 8, ` +
`xb4: 10, ` +
`xb3: 6, ` +
`xc1: ((<1>.xc2 & 8) | (<1>.xc4 & 9) | (<1>.xc5 & 9)), ` +
`xc2: (<1>.xc3 + 2), ` +
`xc4: (<1>.xc2 + 1), ` +
`xc5: (<1>.xc2 + 2), ` +
`xc3: (6 & (<1>.xc1 - 2)), ` +
`xd1: 8, ` +
`xd2: 8, ` +
`xd4: 9, ` +
`xd5: 10, ` +
`xd3: 6, ` +
`xe1: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe2: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe4: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe5: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe3: _|_((6 & 7):conflicting values 6 and 7), ` +
`xf1: 8, ` +
`xf2: 8, ` +
`xf4: 10, ` +
`xf3: 6, ` +
`z1: ((<1>.z2 + 1) | (<1>.z3 + 5)), ` +
`z2: (<1>.z3 + 2), ` +
`z3: ((<1>.z1 - 3) & 8)}`,
}, {
// Defaults should not alter the result of the above disjunctions.
// The results may differ, but errors and resolution should be roughly
// the same.
desc: "resolved self-reference cycles with disjunction with defaults",
in: `
// The disjunction in xa could be resolved, but as disjunctions
// are not resolved for expression, it remains unresolved.
xa1: (xa2 & 8) | *(xa4 & 9)
xa2: xa3 + 2
xa3: 6 & xa1-2
xa4: xa2 + 2
// As xb3 is a disjunction, xb2 cannot be resolved and evaluating
// the cycle completely is broken. However, it is not an error
// as the user might still resolve the disjunction.
xb1: *(xb2 & 8) | (xb4 & 9)
xb2: xb3 + 2
xb3: *(6 & (xb1-2)) | (xb4 & 9)
xb4: xb2 + 2
// Another variant with more disjunctions. xc1 remains with two
// possibilities. Technically, only the first value is valid.
// However, to fully determine that, all options of the remaining
// disjunction will have to be evaluated algebraically, which is
// not done.
xc1: *(xc2 & 8) | (xc4 & 9) | (xc5 & 9)
xc2: xc3 + 2
xc3: 6 & xc1-2
xc4: xc2 + 1
xc5: xc2 + 2
// The above is resolved by setting xd1 explicitly.
xd1: *(xd2 & 8) | xd4 & 9 | xd5 & 9
xd2: xd3 + 2
xd3: 6 & xd1-2
xd4: xd2 + 1
xd5: xd2 + 2
// The above is resolved by setting xd1 explicitly to the wrong
// value, resulting in an error.
xe1: *(xe2 & 8) | xe4 & 9 | xe5 & 9
xe2: xe3 + 2
xe3: 6 & xe1-2
xe4: xe2 + 1
xe5: xe2 + 2
xe1: 9
z1: *(z2 + 1) | z3 + 5
z2: z3 + 2
z3: z1 - 3
z3: 8
`,
out: `<0>{` +
`xa1: 8, ` +
`xa2: 8, ` +
`xa4: 10, ` +
`xa3: 6, ` +
`xb1: 8, ` +
`xb2: 8, ` +
`xb4: 10, ` +
`xb3: 6, ` +
`xc1: (*8 | 9), ` + // not resolved because we use evalPartial
`xc2: 8, ` +
`xc4: 9, ` +
`xc5: 10, ` +
`xc3: 6, ` +
`xd1: (*8 | 9), ` + // TODO: eliminate 9?
`xd2: 8, ` +
`xd4: 9, ` +
`xd5: 10, ` +
`xd3: 6, ` +
`xe1: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe2: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe4: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe5: _|_((6 & 7):conflicting values 6 and 7), ` +
`xe3: _|_((6 & 7):conflicting values 6 and 7), ` +
`z1: (*11 | 13), ` + // 13 is eliminated with evalFull
`z2: 10, ` +
`z3: 8}`,
}}
rewriteHelper(t, testCases, evalPartial)
}
func TestFullEval(t *testing.T) {
testCases := []testCase{{
desc: "detect conflicting value",
in: `
a: 8000.9
a: 7080 | int`,
out: `<0>{a: _|_((8000.9 & (int | int)):conflicting values 8000.9 and int (mismatched types float and int))}`, // TODO: fix repetition
}, {
desc: "conflicts in optional fields are okay ",
in: `
d: {a: 1, b?: 3} | {a: 2}
// the following conjunction should not eliminate any disjuncts
c: d & {b?:4}
`,
out: `<0>{d: (<1>{a: 1, b?: 3} | <2>{a: 2}), c: (<3>{a: 1, b?: (3 & 4)} | <4>{a: 2, b?: 4})}`,
}, {
desc: "resolve all disjunctions",
in: `
service: [Name=string]: {
name: string | *Name
port: int | *7080
}
service: foo: _
service: bar: { port: 8000 }
service: baz: { name: "foobar" }
`,
out: `<0>{service: <1>{[]: <2>(Name: string)-><3>{name: (string | *<2>.Name), port: (int | *7080)}, foo: <4>{name: "foo", port: 7080}, bar: <5>{name: "bar", port: 8000}, baz: <6>{name: "foobar", port: 7080}}}`,
}, {
desc: "field templates",
in: `
a: {
[name=_]: int
k: 1
}
b: {
[X=_]: { x: 0, y: *1 | int }
v: {}
w: { y: 0 }
}
b: { [y=_]: {} } // TODO: allow different name
c: {
[Name=_]: { name: Name, y: 1 }
foo: {}
bar: _
}
`,
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>{y: 1, name: <10>.Name}, foo: <12>{y: 1, name: "foo"}, bar: <13>{y: 1, name: "bar"}}}`,
}, {
desc: "field comprehension",
in: `
a: {
for k, v in b
if k < "d"
if v > b.a {
"\(k)": v
}
}
b: {
a: 1
b: 2
c: 3
d: 4
}
c: {
for k, v in b
if k < "d"
if v > b.a {
"\(k)": v
}
}
`,
out: `<0>{a: <1>{b: 2, c: 3}, b: <2>{a: 1, b: 2, c: 3, d: 4}, c: <3>{b: 2, c: 3}}`,
}, {
desc: "conditional field",
in: `
if b {
a: "foo"
}
b: true
c: {
a: 3
if a > 1 {
a: 3
}
}
d: {
a: int
if a > 1 {
a: 3
}
}
`,
// NOTE: the node numbers are not correct here, but this is an artifact
// of the testing code.
out: `<0>{b: true, a: "foo", c: <1>{a: 3}, d: <2>{a: int if (<3>.a > 1) yield <4>{a: 3}}}`,
}, {
desc: "referencing field in field comprehension",
in: `
a: { b: c: 4 }
a: {
b: d: 5
for k, v in b {
"\(k)": v
}
}
`,
out: `<0>{a: <1>{b: <2>{c: 4, d: 5}, c: 4, d: 5}}`,
}, {
desc: "different labels for templates",
in: `
a: [X=string]: { name: X }
a: [Name=string]: { name: Name }
a: foo: {}
`,
out: `<0>{a: <1>{[]: <2>(X: string)->(<3>{name: <2>.X} & <4>{name: <2>.X}), foo: <5>{name: "foo"}}}`,
}, {
// TODO: rename EE and FF to E and F to check correct ordering.
desc: "nested templates in one field",
in: `
a: [A=string]: b: [B=string]: {
name: A
kind: B
}
a: "A": b: "B": _
a: "C": b: "D": _
a: "EE": b: "FF": { c: "bar" }
`,
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"}}}, ` +
`EE: <19>{b: <20>{[]: <21>(B: string)-><22>{name: <23>.A, kind: <21>.B}, ` +
`FF: <24>{name: "EE", kind: "FF", c: "bar"}}}}}`,
}, {
desc: "template unification within one struct",
in: `
a: {
[A=string]: { name: A }
// TODO: allow duplicate alias here
[X=string]: { kind: X }
}
a: "A": _
a: "C": _
a: "E": { c: "bar" }
`,
out: `<0>{a: <1>{[]: <2>(A: string)->(<3>{name: <2>.A} & <4>{kind: <2>.A}), ` +
`E: <5>{name: "E", kind: "E", c: "bar"}, ` +
`A: <6>{name: "A", kind: "A"}, ` +
`C: <7>{name: "C", kind: "C"}}}`,
}, {
desc: "field comprehensions with multiple keys",
in: `
for x in [
{a: "A", b: "B" },
{a: "C", b: "D" },
{a: "E", b: "F" },
] {
a: "\(x.a)": b: "\(x.b)": x
}
for x in [
{a: "A", b: "B" },
{a: "C", b: "D" },
{a: "E", b: "F" },
] {
"\(x.a)": "\(x.b)": x
}
`,
out: `<0>{E: <1>{F: <2>{a: "E", b: "F"}}, ` +
`a: <3>{` +
`E: <4>{b: <5>{F: <6>{a: "E", b: "F"}}}, ` +
`A: <7>{b: <8>{B: <9>{a: "A", b: "B"}}}, ` +
`C: <10>{b: <11>{D: <12>{a: "C", b: "D"}}}}, ` +
`A: <13>{B: <14>{a: "A", b: "B"}}, ` +
`C: <15>{D: <16>{a: "C", b: "D"}}}`,
// TODO: this order would be desirable.
// out: `<0>{a: <1>{` +
// `A: <2>{b: <3>{B: <4>{a: "A", b: "B"}}}, ` +
// `C: <5>{b: <6>{D: <7>{a: "C", b: "D"}}}, ` +
// `E: <8>{b: <9>{F: <10>{a: "E", b: "F"}}}}, ` +
// `A: <11>{B: <12>{a: "A", b: "B"}}, ` +
// `C: <13>{D: <14>{a: "C", b: "D"}}, ` +
// `E: <15>{F: <16>{a: "E", b: "F"}}}`,
}, {
desc: "field comprehensions with templates",
in: `
num: 1
a: {
if num < 5 {
[A=string]: [B=string]: {
name: A
kind: B
}
}
}
a: b: c: d: "bar"
`,
out: `<0>{num: 1, a: <1>{[]: <2>(A: string)-><3>{[]: <4>(B: string)-><5>{name: <2>.A, kind: <4>.B}, }, ` +
`b: <6>{[]: <7>(B: string)-><8>{name: <9>.A, kind: <7>.B}, ` +
`c: <10>{name: "b", kind: "c", ` +
`d: "bar"}}}}`,
}, {
desc: "disjunctions of lists",
in: `
l: *[ int, int ] | [ string, string ]
l1: [ "a", "b" ]
l2: l & [ "c", "d" ]
`,
out: `<0>{l: [int,int], l1: ["a","b"], l2: ["c","d"]}`,
}, {
desc: "normalization",
in: `
a: string | string
b: *1 | *int
c: *1.0 | *float
`,
out: `<0>{a: string, b: int, c: float}`,
}, {
desc: "default disambiguation and elimination",
in: `
a: *1 | int
b: *3 | int
c: a & b
d: b & a
e: *1 | *1
`,
out: `<0>{a: 1, b: 3, c: int, d: int, e: 1}`,
}, {
desc: "list comprehension",
in: `
a: [ for k, v in b if k < "d" if v > b.a { k }]
b: {
a: 1
b: 2
c: 3
d: 4
}
c: [ for _, x in b for _, y in b if x < y { x } ]
d: [ for x, _ in a { x } ]
`,
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]}`,
}, {
desc: "struct comprehension with template",
in: `
result: [ for _, v in service { v } ]
service: [Name=string]: {
name: *Name | string
type: "service"
port: *7080 | int
}
service: foo: {}
service: bar: { port: 8000 }
service: baz: { name: "foobar" }
`,
out: `<0>{result: [` +
`<1>{name: "foo", type: "service", port: 7080},` +
`<2>{name: "bar", type: "service", port: 8000},` +
`<3>{name: "foobar", type: "service", port: 7080}], ` +
`service: <4>{` +
`[]: <5>(Name: string)-><6>{name: (*<5>.Name | string), type: "service", port: (*7080 | int)}, ` +
`foo: <7>{name: "foo", type: "service", port: 7080}, ` +
`bar: <8>{name: "bar", type: "service", port: 8000}, ` +
`baz: <9>{name: "foobar", type: "service", port: 7080}}}`,
}, {
desc: "resolutions in struct comprehension keys",
in: `
a: { for _, b in ["c"] { "\(b + ".")": "a" } }
`,
out: `<0>{a: <1>{"c.": "a"}}`,
}, {
desc: "recursive evaluation within list",
in: `
l: [a]
a: b & { c: "t" }
b: {
d: c
c: string
}
l1: [a1]
a1: b1 & { c: "t" }
b1: {
d: "s" + c
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" + <7>.c)}}`,
}, {
desc: "ips",
in: `
IP: 4*[ uint8 ]
Private:
*[ 192, 168, uint8, uint8 ] |
[ 10, uint8, uint8, uint8] |
[ 172, >=16 & <=32, uint8, uint8 ]
Inst: Private & [ _, 10, ... ]
MyIP: Inst & [_, _, 10, 10 ]
`,
out: `<0>{` +
`IP: [(int & >=0 & int & <=255),(int & >=0 & int & <=255),(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
`Private: [192,168,(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
`Inst: [10,10,(int & >=0 & int & <=255),(int & >=0 & int & <=255)], ` +
`MyIP: [10,10,10,10]` +
`}`,
}, {
desc: "complex interaction of groundness",
in: `
res: [ for x in a for y in x { y & { d: "b" } }]
res: [ a.b.c & { d: "b" } ]
a: b: [C=string]: { d: string, s: "a" + d }
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" + <7>.d)}}}}`,
}, {
desc: "complex groundness 2",
in: `
r1: f1 & { y: "c" }
f1: { y: string, res: a.b.c & { d: y } }
a: b: c: { d: string, s: "a" + d }
a: b: [C=string]: { 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" + <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: `
res: [t]
t: [X=string]: {
a: c + b.str
b: str: string
c: "X"
}
t: x: { b: str: "DDDD" }
`,
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"}}}}`,
}, {
// TODO: A nice property for CUE to have would be that evaluation time
// is proportional to the number of output nodes (note that this is
// not the same as saying that the running time is O(n)).
// We should probably disallow shenanigans like the one below. But until
// this is allowed, it should at least be correct. At least we are not
// making reentrant coding easy.
desc: "reentrance",
in: `
// This indirection is needed to avoid binding references to fib
// within fib to the instantiated version.
fibRec: {nn: int, out: (fib & {n: nn}).out}
fib: {
n: int
if n >= 2 {
out: (fibRec & {nn: n - 2}).out + (fibRec & {nn: n - 1}).out
}
if n < 2 {
out: n
}
}
fib2: (fib & {n: 2}).out
fib7: (fib & {n: 7}).out
fib12: (fib & {n: 12}).out
`,
out: `<0>{` +
`fibRec: <1>{` +
`nn: int, ` +
`out: (<2>.fib & <3>{n: <4>.nn}).out}, ` +
// NOTE: the node numbers are not correct here, but this is an artifact
// of the testing code.
`fib: <5>{n: int if (<6>.n >= 2) yield <7>{out: ((<2>.fibRec & <8>{nn: (<6>.n - 2)}).out + (<2>.fibRec & <9>{nn: (<6>.n - 1)}).out)}, if (<6>.n < 2) yield <10>{out: <6>.n}}, ` +
`fib2: 1, ` +
`fib7: 13, ` +
`fib12: 144}`,
}, {
desc: "Issue #23",
in: `
x: {a:1}|{a:2}
y: x & {a:3}
`,
out: `<0>{x: (<1>{a: 1} | <2>{a: 2}), y: _|_((1 & 3):empty disjunction: conflicting values 1 and 3;(2 & 3):empty disjunction: conflicting values 2 and 3)}`,
}, {
desc: "cannot resolve references that would be ambiguous",
in: `
a1: *0 | 1
a1: a3 - a2
a2: *0 | 1
a2: a3 - a1
a3: 1
b1: (*0 | 1) & b2
b2: (0 | *1) & b1
c1: (*{a:1} | {b:1}) & c2
c2: (*{a:2} | {b:2}) & c1
`,
out: `<0>{` +
`a1: ((*0 | 1) & (<1>.a3 - <1>.a2)), ` +
`a3: 1, ` +
`a2: ((*0 | 1) & (<1>.a3 - <1>.a1)), ` +
`b1: (0 | 1), ` +
`b2: (0 | 1), ` +
`c1: (<2>{a: 1, b: 2} | <3>{a: 2, b: 1}), ` +
`c2: (<4>{a: 2, b: 1} | <5>{a: 1, b: 2})}`,
}, {
desc: "dont convert incomplete errors to non-incomplete",
in: `
import "strings"
n1: {min: <max, max: >min}
n2: -num
n3: +num
n4: num + num
n5: num - num
n6: num * num
n7: num / num
b1: !is
s1: "\(str)"
s2: strings.ContainsAny("dd")
s3: strings.ContainsAny(str, "dd")
str: string
num: <4
is: bool
`,
out: `<0>{` +
`n1: <1>{min: <<2>.max, max: ><2>.min}, ` +
`n2: -<3>.num, num: <4, ` +
`n3: +<3>.num, ` +
`n4: (<3>.num + <3>.num), ` +
`n5: (<3>.num - <3>.num), ` +
`n6: (<3>.num * <3>.num), ` +
`n7: (<3>.num / <3>.num), ` +
`b1: !<3>.is, ` +
`is: bool, ` +
`s1: ""+<3>.str+"", ` +
`str: string, ` +
`s2: strings.ContainsAny ("dd"), ` +
`s3: <4>.ContainsAny (<3>.str,"dd")}`,
}, {
desc: "len of incomplete types",
in: `
args: *[] | [...string]
v1: len(args)
v2: len([])
v3: len({})
v4: len({a: 3})
v5: len({a: 3} | {a: 4})
v6: len('sf' | 'dd')
v7: len([2] | *[1, 2])
v8: len([2] | [1, 2])
v9: len("😂")
v10: len("")
`,
out: `<0>{` +
`args: [], ` +
`v1: 0, ` +
`v2: 0, ` +
`v3: 0, ` +
`v4: 1, ` +
`v5: len ((<1>{a: 3} | <2>{a: 4})), ` +
`v6: len (('sf' | 'dd')), ` +
`v7: 2, ` +
`v8: len (([2] | [1,2])), ` +
`v9: 4, ` +
`v10: 0}`,
}, {
desc: "slice rewrite bug",
in: `
fn: {
arg: [...int] & [1]
out: arg[1:]
}
fn1: fn & {arg: [1]}
`,
out: `<0>{fn: <1>{arg: [1], out: []}, fn1: <2>{arg: [1], out: []}}`,
}, {
desc: "Issue #94",
in: `
foo: {
opt?: 1
"txt": 2
#def: 3
regular: 4
_hidden: 5
}
comp: { for k, v in foo { "\(k)": v } }
select: {
opt: foo.opt
"txt": foo.txt
#def: foo.#def
regular: foo.regular
_hidden: foo._hidden
}
index: {
opt: foo["opt"]
"txt": foo["txt"]
#def: foo["#def"]
regular: foo["regular"]
_hidden: foo["_hidden"]
}
`,
out: `<0>{` +
`foo: <1>{opt?: 1, txt: 2, #def: 3, regular: 4, _hidden: 5}, ` +
`comp: <2>{txt: 2, regular: 4}, ` +
`select: <3>{opt: <4>.foo.opt, txt: 2, #def: 3, regular: 4, _hidden: 5}, ` +
`index: <5>{opt: <4>.foo["opt"], txt: 2, #def: <4>.foo["#def"], regular: 4, _hidden: <4>.foo["_hidden"]}}`,
}, {
desc: "retain references with interleaved embedding",
in: `
a: d: {
#base
#info: {...}
Y: #info.X
}
#base: {
#info: {...}
}
a: [Name=string]: { #info: {
X: "foo"
}}
`,
out: `<0>{a: <1>{[]: <2>(Name: string)-><3>{#info: <4>C{X: "foo"}}, d: <5>C{#info: <6>C{X: "foo"}, Y: "foo"}}, #base: <7>C{#info: <8>{...}}}`,
}, {
desc: "comparison against bottom",
in: `
a: _|_ == _|_
b: err == 1&2 // not a literal error, so not allowed
c: err == _|_ // allowed
d: err != _|_ // allowed
e: err != 1&3
// z: err == err // TODO: should infer to be true?
f: ({a: 1} & {a: 2}) == _|_
g: ({a: 1} & {b: 2}) == _|_
h: _|_ == ({a: 1} & {a: 2})
i: _|_ == ({a: 1} & {b: 2})
err: 1 & 2
`,
out: `<0>{a: true, b: _|_((1 & 2):conflicting values 1 and 2), err: _|_((1 & 2):conflicting values 1 and 2), c: true, d: false, e: _|_((1 & 2):conflicting values 1 and 2), f: true, g: false, h: true, i: false}`,
}, {
desc: "or builtin should not fail on non-concrete empty list",
in: `
#Workflow: {
jobs: {
[jobID=string]: {
}
}
#JobID: or([ for k, _ in jobs { k } ])
}
foo: #Workflow & {
jobs: foo: {
}
}
`,
out: `<0>{#Workflow: <1>C{jobs: <2>{[]: <3>(jobID: string)-><4>C{}, }, #JobID: or ([ <5>for k, _ in <6>.jobs yield <5>.k ])}, foo: <7>C{jobs: <8>{[]: <9>(jobID: string)-><10>C{}, foo: <11>C{}}, #JobID: "foo"}}`,
}, {
desc: "Issue #153",
in: `
Foo: {
listOfCloseds: [...#Closed]
}
#Closed: {
a: int | *0
}
Junk: {
b: 2
}
Foo & {
listOfCloseds: [{
for k, v in Junk {
"\(k)": v
}
}]
}
`,
out: `<0>{<1>{listOfCloseds: [_|_(<2>.v:field "b" not allowed in closed struct)]}, Foo: <3>{listOfCloseds: []}, #Closed: <4>C{a: 0}, Junk: <5>{b: 2}}`,
}, {
desc: "label and field aliases",
in: `
p: [ID=string]: { name: ID }
A="foo=bar": "str"
a: A
B=bb: 4
b1: B
b1: bb
C="\(a)": 5
c: C
`,
out: `<0>{` +
`p: <1>{[]: <2>(ID: string)-><3>{name: <2>.ID}, }, ` +
`"foo=bar": "str", ` +
`a: "str", ` +
`bb: 4, ` +
`b1: 4, ` +
`c: 5, ` +
`str: 5}`,
}, {
desc: "optionals with label filters",
in: `
#JobID: =~"^[a-zA-Z]*$"
#Job: {
name: string
cmd: string
}
#Jobs: {
[#JobID]: #Job
[=~"Test$"]: name: =~"^test" // Must work without ...
}
jobs: foo: name: "allGood"
jobs: foo: name: "allGood"
jobs1: #Jobs
jobs1: foo1: {} // faulty
jobs2: #Jobs
jobs2: fooTest: name: "badName" // faulty
jobs3: #Jobs
jobs3: [string]: #Job
jobs3: fooTest1: name: "badName" // faulty
`,
out: `<0>{` +
`#JobID: =~"^[a-zA-Z]*$", ` +
`#Job: <1>C{name: string, cmd: string}, ` +
`#Jobs: <2>C{[=~"^[a-zA-Z]*$"]: <3>(_: string)-><4>.#Job, [=~"Test$"]: <5>(_: string)-><6>C{name: =~"^test"}, }, ` +
`jobs: <7>{foo: <8>{name: "allGood"}}, ` +
`jobs1: _|_(<9>{}:field "foo1" not allowed in closed struct), ` +
`jobs2: <10>C{[=~"^[a-zA-Z]*$"]: <11>(_: string)-><4>.#Job, [=~"Test$"]: <12>(_: string)-><13>C{name: =~"^test"}, fooTest: _|_(string:field "cmd" not allowed in closed struct)}, ` +
`jobs3: _|_(<14>{name: "badName"}:field "fooTest1" not allowed in closed struct)}`,
}, {
desc: "optionals in open structs",
in: `
A: {
[=~"^[a-s]*$"]: int
}
B: {
[=~"^[m-z]*$"]: int
}
#C: {A & B}
c: #C & { aaa: 3 }
`,
out: `<0>{A: <1>{[=~"^[a-s]*$"]: <2>(_: string)->int, }, B: <3>{[=~"^[m-z]*$"]: <4>(_: string)->int, }, #C: <5>C{[=~"^[a-s]*$"]: <6>(_: string)->int, [=~"^[m-z]*$"]: <7>(_: string)->int, }, c: <8>C{[=~"^[a-s]*$"]: <9>(_: string)->int, [=~"^[m-z]*$"]: <10>(_: string)->int, aaa: 3}}`,
}, {
desc: "conjunction of optional sets",
in: `
#A: {
[=~"^[a-s]*$"]: int
}
#B: {
[=~"^[m-z]*$"]: int
}
#C: #A & #B
c: #C & { aaa: 3 }
#D: {#A & #B}
d: #D & { aaa: 3 }
`,
out: `<0>{` +
`#A: <1>C{[=~"^[a-s]*$"]: <2>(_: string)->int, }, ` +
`#B: <3>C{[=~"^[m-z]*$"]: <4>(_: string)->int, }, ` +
`#C: <5>C{(C{[=~"^[a-s]*$"]: <6>(_: string)->int} & C{[=~"^[m-z]*$"]: <7>(_: string)->int}), }, ` +
`c: _|_(3:field "aaa" not allowed in closed struct), ` +
`#D: <8>C{(C{[=~"^[a-s]*$"]: <9>(_: string)->int} & C{[=~"^[m-z]*$"]: <10>(_: string)->int}), }, ` +
`d: _|_(3:field "aaa" not allowed in closed struct)` +
`}`,
}, {
desc: "continue recursive closing for optionals",
in: `
#S: {
[string]: { a: int }
}
a: #S & {
v: { b: int }
}
`,
out: `<0>{#S: <1>{[]: <2>(_: string)-><3>C{a: int}, }, a: <4>{[]: <5>(_: string)-><6>C{a: int}, v: _|_(int:field "b" not allowed in closed struct)}}`,
}, {
desc: "augment closed optionals",
in: `
#A: {
[=~"^[a-s]*$"]: int
}
#B: {
[=~"^[m-z]*?"]: int
}
#C: {
#A & #B
[=~"^Q*$"]: int
}
c: #C & { QQ: 3 }
#D: {
#A
#B
}
d: #D & { aaa: 4 }
`,
out: `<0>{` +
`#A: <1>C{[=~"^[a-s]*$"]: <2>(_: string)->int, }, ` +
`#B: <3>C{[=~"^[m-z]*?"]: <4>(_: string)->int, }, ` +
`#C: <5>C{C{[=~"^Q*$"]: <6>(_: string)->int}, C{(C{[=~"^[a-s]*$"]: <7>(_: string)->int} & C{[=~"^[m-z]*?"]: <8>(_: string)->int})}, }, ` +
`c: <9>C{C{[=~"^Q*$"]: <10>(_: string)->int}, C{(C{[=~"^[a-s]*$"]: <11>(_: string)->int} & C{[=~"^[m-z]*?"]: <12>(_: string)->int})}, QQ: 3}, ` +
`#D: <13>C{[=~"^[a-s]*$"]: <14>(_: string)->int, [=~"^[m-z]*?"]: <15>(_: string)->int, }, ` +
`d: <16>C{[=~"^[a-s]*$"]: <17>(_: string)->int, [=~"^[m-z]*?"]: <18>(_: string)->int, aaa: 4}}`,
}, {
in: `
#Task: {
{
op: "pull"
tag: *"latest" | string
refToTag: tag
tagExpr: tag + "dd"
tagInString: "\(tag)"
} | {
op: "scratch"
}
}
foo: #Task & {"op": "pull"}
`,
out: `<0>{#Task: (<1>C{op: "pull", tag: (*"latest" | string), refToTag: <1>.tag, tagExpr: (<1>.tag + "dd"), tagInString: ""+<1>.tag+""} | <2>C{op: "scratch"}), foo: <3>C{op: "pull", tag: "latest", refToTag: "latest", tagExpr: "latestdd", tagInString: "latest"}}`,
}, {
in: `
t: {
#ok: *true | bool
if #ok {
x: int
}
}
s: t & {
#ok: false
}`,
out: `<0>{t: <1>{x: int, #ok: true}, s: <2>{#ok: false}}`,
}, {
desc: "cross-dependent comprehension",
// TODO(eval): fix: c should ultimately be allowed the struct. Current
// semantics require, however, that generated fields are not available
// for evaluation. This, however, does not have to hold, for closedness
// checks and allowing this would be more intuitive.
// Until that time, ensure that the behavior is at commutative.
in: `
#a: {
if b {
c: 4
}
b: bool
}
x: (#a & { b: true}) & {c: 4 }
y: x
`,
// c should not be allowed, as it would break commutativiy.
// See comments above.
out: `<0>{x: _|_(4:field "c" not allowed in closed struct), y: _|_(4:field "c" not allowed in closed struct), #a: <1>C{b: bool if <2>.b yield <3>C{c: 4}}}`,
}, {
desc: "optional expanded before lookup",
in: `
test: [ID=_]: {
name: ID
}
test: A: {
field1: "1"
field2: "2"
}
B: test.A & {}
`,
out: `<0>{test: <1>{[]: <2>(ID: string)-><3>{name: <2>.ID}, A: <4>{name: "A", field1: "1", field2: "2"}}, B: <5>{name: "A", field1: "1", field2: "2"}}`,
}, {
desc: "Issue #178",
in: `
import "encoding/csv"
import "encoding/hex"
foo: csv.Decode(data)
data: bytes
len: int
bar: hex.EncodedLen(len)
`,
out: `<0>{foo: <1>.Decode (<2>.data), data: bytes, len: int, bar: <3>.EncodedLen (<2>.len)}`,
}, {
// This resulted in an issue in an older version. Prevent regression.
desc: "comprehension and skipped field",
in: `
for key, value in {x: v: 1} {
"\(key)": {
v: *{for pod, _ in value.v {}} | {"\(value.v)": 2}
_p: 3
}
}
`,
out: `<0>{x: <1>{v: <2>{"1": 2}, _p: 3}}`,
}, {
desc: "non-structural direct cycles",
in: `
c1: {bar: baz: 2} & c1.bar
c2: {bar: 1} & c2.bar
`,
out: `<0>{` +
`c1: <1>{bar: <2>{baz: 2}, baz: 2}, ` +
`c2: _|_(conflicting values {bar: 1} and 1 (mismatched types struct and int))}`,
}, {
desc: "dont bind to string labels",
in: `
x: 1
y: {
"x": 2
z: x
}
`,
out: `<0>{x: 1, y: <1>{x: 2, z: 1}}`,
}, {
desc: "dont pass incomplete values to builtins",
in: `
import "encoding/json"
input: string
foo: json.Marshal(input)
`,
out: `<0>{input: string, foo: <1>.Marshal (<2>.input)}`,
}, {
desc: "alias reuse in nested scope",
in: `
#Foo: {
let X = or([ for k, _ in {} { k } ])
connection: [X]: X
}
#A: {
foo: "key"
let X = foo
a: foo: [X]: X
}
#B: {
foo: string
let X = foo
a: foo: [X]: X
}
b: #B & { foo: "key" }
`,
out: `<0>{` +
`#Foo: <1>C{connection: <2>C{[or ([ <3>for k, _ in <4>{} yield <3>.k ])]: <5>(_: string)->or ([ <3>for k, _ in <4>{} yield <3>.k ]), }}, ` +
`#A: <6>C{foo: "key", a: <7>C{foo: <8>C{["key"]: <9>(_: string)-><10>.foo, }}}, ` +
`#B: <11>C{foo: string, a: <12>C{foo: <13>C{[string]: <14>(_: string)-><15>.foo, }}}, ` +
`b: <16>C{foo: "key", a: <17>C{foo: <18>C{["key"]: <19>(_: string)-><20>.foo, }}}` +
`}`,
}, {
desc: "json Marshaling detects incomplete",
in: `
import "encoding/json"
a: json.Marshal({ a: string} )
foo: { a: 3, b: foo.c }
b: json.Marshal(foo)
`,
out: `<0>{` +
`a: <1>.Marshal (<2>{a: string}), ` +
`foo: <3>{a: 3, b: <4>.foo.c}, ` +
`b: <1>.Marshal (<4>.foo)}`,
}, {
desc: "detectIncompleteYAML",
in: `
package foobar
import yaml "encoding/yaml"
#Spec: {
_vars: {something: string}
data: {
#foo: {
use: _vars.something
}
baz: yaml.Marshal(_vars.something)
foobar: yaml.Marshal(#foo)
}
}
Val: #Spec & {
_vars: something: "var-string"
}
`,
out: `<0>{#Spec: <1>C{_vars: <2>C{something: string}, data: <3>C{#foo: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.#foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{#foo: <11>C{use: "var-string"}, baz: "var-string\n", foobar: "use: var-string\n"}}}`,
}, {
desc: "detectIncompleteJSON",
in: `
package foobar
import "encoding/json"
#Spec: {
_vars: {something: string}
data: {
#foo: {
use: _vars.something
}
baz: json.Marshal(_vars.something)
foobar: json.Marshal(#foo)
}
}
Val: #Spec & {
_vars: something: "var-string"
}
`,
out: `<0>{#Spec: <1>C{_vars: <2>C{something: string}, data: <3>C{#foo: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.#foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{#foo: <11>C{use: "var-string"}, baz: "\"var-string\"", foobar: "{\"use\":\"var-string\"}"}}}`,
}, {
desc: "issue312",
in: `
for x in [1] {
*close({}) | { [_]: null }
}
`,
out: `<0>{ <1>for _, x in [1] yield <2>{}, (*close (<3>{}) | <4>{[]: <5>(_: string)->null, })}`,
}, {
// TODO(eval): note that this behavior is incompatible with allowing
// non-struct as emit values. If we ever want to do this, we need to
// do it soon.
desc: "issue312",
in: `
y: *1 | {a: 2}
for x in [1] { y }
`,
out: `<0>{y: 1, a: 2}`,
}, {
desc: "issue318",
in: `
#T: {
arg: x: string
out1: "\(arg.x) \(arg.y)"
out2: "\(arg.y)"
vx: arg.x
vy: arg.y
}
`,
out: `<0>{` +
`#T: <1>C{` +
`arg: <2>C{x: string}, ` +
`out1: _|_(<3>.arg.y:undefined field "y"), ` +
`out2: _|_(<3>.arg.y:undefined field "y"), ` +
`vx: string, ` +
`vy: _|_(<3>.arg.y:undefined field "y")}}`,
}, {
desc: "issue314",
in: `
import (
"text/template"
"encoding/yaml"
"encoding/json"
)
x: {
s: "myname"
#T
}
#T: {
s: string
out: template.Execute("{{.s}}", {
"s": s
})
}
#V: {
s: string
out: json.Marshal({"s": s})
}
#U: {
s: string
out: yaml.Marshal({"s": s})
}`,
out: `<0>{` +
`x: <1>C{s: "myname", out: "myname"}, ` +
`#T: <2>C{s: string, out: <3>.Execute ("{{.s}}",<4>C{s: <5>.s})}, ` +
`#V: <6>C{s: string, out: <7>.Marshal (<8>C{s: <9>.s})}, ` +
`#U: <10>C{s: string, out: <11>.Marshal (<12>C{s: <13>.s})}}`,
}}
rewriteHelper(t, testCases, evalFull)
}
func TestX(t *testing.T) {
// Don't remove. For debugging.
in := `
`
if strings.TrimSpace(in) == "" {
t.Skip()
}
rewriteHelper(t, []testCase{{in: in}}, evalFull)
}