diff --git a/cue/testdata/basicrewrite/aliases/aliases.txtar b/cue/testdata/basicrewrite/aliases/aliases.txtar index 03436061b..a16cda9e5 100644 --- a/cue/testdata/basicrewrite/aliases/aliases.txtar +++ b/cue/testdata/basicrewrite/aliases/aliases.txtar @@ -14,10 +14,12 @@ t1: { { t0: { _a: _ - _out: 〈0;let _b〉 + let _b#1 = 〈0;_a〉 + _out: 〈0;let _b#1〉 } t1: { - _a: 〈0;let b〉 + _a: 〈0;let b#2〉 + let b#2 = 〈0;d〉 d: 3 } } @@ -25,10 +27,12 @@ t1: { (struct){ t0: (struct){ _a: (_){ _ } + let _b#1 = (_){ _ } _out: (_){ _ } } t1: (struct){ _a: (int){ 3 } + let b#2 = (int){ 3 } d: (int){ 3 } } } diff --git a/cue/testdata/builtins/closed.txtar b/cue/testdata/builtins/closed.txtar index a17cb8d5f..b8eab88a3 100644 --- a/cue/testdata/builtins/closed.txtar +++ b/cue/testdata/builtins/closed.txtar @@ -81,6 +81,9 @@ Result: a: (#struct){ b: (bool){ true } } + let X#1 = (#struct){ + b: (bool){ true } + } uint: (#struct){ a: (#struct){ b: (bool){ true } @@ -90,6 +93,9 @@ Result: a: (#struct){ b: (bool){ true } } + let X#1 = (#struct){ + b: (bool){ true } + } string: (#struct){ a: (#struct){ b: (bool){ true } @@ -102,6 +108,7 @@ Result: a: (#struct){ b: (bool){ true } } + let X#1 = 〈0;a〉 // multi uint: (#struct){ a: (#struct){ b: (bool){ true } @@ -111,6 +118,7 @@ Result: a: (#struct){ b: (bool){ true } } + let X#1 = 〈0;a〉 // multi string: (#struct){ a: (#struct){ b: (bool){ true } @@ -121,6 +129,7 @@ Result: a: (#struct){ b: (bool){ true } } + let X#1 = 〈0;a〉 // multi uint: (#struct){ a: (#struct){ b: (bool){ true } @@ -130,6 +139,7 @@ Result: a: (#struct){ b: (bool){ true } } + let X#1 = 〈0;a〉 // multi string: (#struct){ a: (#struct){ b: (bool){ true } @@ -191,10 +201,11 @@ Result: a: { b: true } + let X#1 = 〈0;a〉 (close({ uint: { a: { - b: 〈3;let X〉.b + b: 〈3;let X#1〉.b } } })|close({ diff --git a/cue/testdata/compile/erralias.txtar b/cue/testdata/compile/erralias.txtar index f3d41bb56..c68ff4b6f 100644 --- a/cue/testdata/compile/erralias.txtar +++ b/cue/testdata/compile/erralias.txtar @@ -22,6 +22,7 @@ for[].a: reference "E" not found: ./in.cue:10:16 --- in.cue { + let X#1 = {} ["foo"]: 3 a: _|_(reference "Y" not found) "\(〈0;b〉)": 3 diff --git a/cue/testdata/compile/labels.txtar b/cue/testdata/compile/labels.txtar index 99ed90f4e..563c3a40f 100644 --- a/cue/testdata/compile/labels.txtar +++ b/cue/testdata/compile/labels.txtar @@ -51,6 +51,7 @@ disallowTopAsLabel: cannot use _ as label: dis1: ("dev"|"prd") dis2: (*"dev"|"prd") con1: string + let con2#1 = string ok0: { [string]: string } @@ -61,7 +62,7 @@ disallowTopAsLabel: cannot use _ as label: [〈1;con1〉]: string } ok3: { - [〈1;let con2〉]: string + [〈1;let con2#1〉]: string } ok4: { ["foo"]: string diff --git a/cue/testdata/compile/let.txtar b/cue/testdata/compile/let.txtar index 50597c98f..d3aeb1307 100644 --- a/cue/testdata/compile/let.txtar +++ b/cue/testdata/compile/let.txtar @@ -62,34 +62,67 @@ b.let[]: cyclic references in let clause or alias: --- in.cue { a: { - b: 〈0;let X〉 + let X#1 = 〈0;let Y#2〉 + let Y#2 = 〈0;c〉 + b: 〈0;let X#1〉 c: 5 } b: { - b: 〈0;let X〉 + let X#3 = 〈0;let Y#4〉 + let Y#4 = 〈0;let X#3〉 + b: 〈0;let X#3〉 c: 5 } fieldOffset: { a: { p1: { - x: 〈0;let X〉 + let X#5 = { + value: 〈1;let Y#6〉 + } + let Y#6 = 2 + x: 〈0;let X#5〉 } p2: { - x: 〈0;let X〉 + x: 〈0;let X#8〉 + let Y#7 = 2 + let X#8 = { + value: 〈1;let Y#7〉 + } } } b: { p1: { - x: 〈0;let X〉 + let X#9 = { + x: { + y: 〈2;let Y#A〉 + } + } + let Y#A = 2 + x: 〈0;let X#9〉 } p2: { - x: 〈0;let X〉 + x: 〈0;let X#C〉 + let Y#B = 2 + let X#C = { + x: { + y: 〈2;let Y#B〉 + } + } } } } issue767: { #Foo: { - out: 〈0;let _#volmnts〉 + let _#bar#D = { + value: "" + } + let _#volmnts#E = { + x: 〈1;let _#baz#F〉.value + } + let _#baz#F = { + 〈1;let _#bar#D〉 + } + out: 〈0;let _#volmnts#E〉 } } } diff --git a/cue/testdata/compile/scope.txtar b/cue/testdata/compile/scope.txtar index 73bc141f9..3fd63df48 100644 --- a/cue/testdata/compile/scope.txtar +++ b/cue/testdata/compile/scope.txtar @@ -68,10 +68,14 @@ let _schema_1 = schema b: 〈1;-〉 } } - f: 〈0;let B〉 + let B#1 = { + open: int + } + f: 〈0;let B#1〉 schema: { - next: 〈1;let _schema_1〉 + next: 〈1;let _schema_1#2〉 } + let _schema_1#2 = 〈0;schema〉 } -- out/eval -- Errors: @@ -96,6 +100,9 @@ Result: c: (string){ "foo" } d: (struct){ } + let B#1 = (struct){ + open: (int){ int } + } f: (struct){ open: (int){ int } } @@ -105,4 +112,8 @@ Result: // [structural cycle] schema.next: structural cycle } } + let _schema_1#2 = (_|_){ + // [structural cycle] _schema_1: structural cycle: + // ./in.cue:32:8 + } } diff --git a/cue/testdata/comprehensions/issue837.txtar b/cue/testdata/comprehensions/issue837.txtar index 4070e262d..fc925177e 100644 --- a/cue/testdata/comprehensions/issue837.txtar +++ b/cue/testdata/comprehensions/issue837.txtar @@ -155,6 +155,19 @@ Result: // [eval] description: (_|_){ // [eval] + let dep#1 = (#struct){ + service: (#struct){ + ref: (#struct){ + kind: (string){ "service" } + } + description: (_|_){ + // [incomplete] #DoDeploy.deployment.description.dep.service.description: undefined field: service: + // ./in.cue:5:28 + } + } + hsize: (#struct){ + } + } service: (_|_){ // [eval] _params.hsize.$_instances: conflicting values 1 and {$_instances:(>=0|*1)} (mismatched types int and struct): // ./in.cue:29:20 @@ -182,6 +195,20 @@ Result: // [eval] description: (_|_){ // [eval] + let configed#2 = (_|_){ + // [eval] _params.hsize.$_instances: conflicting values 1 and {$_instances:(>=0|*1)} (mismatched types int and struct): + // ./in.cue:29:20 + // ./in.cue:35:13 + // ./in.cue:55:12 + // ./in.cue:56:22 + // _params.hsize.$_instances: conflicting values >=0 and {$_instances:(>=0|*1)} (mismatched types number and struct): + // ./in.cue:29:20 + // ./in.cue:35:13 + // ./in.cue:55:12 + // ./in.cue:56:15 + // _params.hsize.$_instances: 2 errors in empty disjunction:: + // ./in.cue:33:20 + } role: (_|_){ // [eval] _params.hsize.$_instances: conflicting values 1 and {$_instances:(>=0|*1)} (mismatched types int and struct): // ./in.cue:29:20 @@ -270,9 +297,10 @@ Result: } deployment: { description: { + let dep#1 = 〈2;_params〉.manifest.description service: (〈3;#Configure〉 & { _params: { - hsize: 〈2;let dep〉.hsize + hsize: 〈2;let dep#1〉.hsize } }).service } @@ -286,7 +314,13 @@ Result: } service: { description: { - role: 〈0;let configed〉.role + let configed#2 = (〈3;#RelabelService〉 & { + _params: { + hsize: 〈4;_params〉.hsize + } + role: "admin" + }).out + role: 〈0;let configed#2〉.role } } } diff --git a/cue/testdata/comprehensions/nested.txtar b/cue/testdata/comprehensions/nested.txtar index 855eb2c8d..e5877ca7e 100644 --- a/cue/testdata/comprehensions/nested.txtar +++ b/cue/testdata/comprehensions/nested.txtar @@ -68,6 +68,7 @@ for k, v in deployment { name: (string){ "metrics" } port: (int){ 9100 } protocol: (string){ "TCP" } + let Port#1 = (int){ 9100 } targetPort: (int){ |(*(int){ 9100 }, (int){ int }) } } } @@ -213,8 +214,9 @@ for k, v in deployment { spec: { ports: [ for _, c in 〈5;v〉.spec.template.spec.containers for _, p in 〈0;c〉.ports if 〈0;p〉._export { - port: (*〈0;let Port〉|int) - targetPort: (*〈0;let Port〉|int) + let Port#1 = 〈1;p〉.containerPort + port: (*〈0;let Port#1〉|int) + targetPort: (*〈0;let Port#1〉|int) }, ] } diff --git a/cue/testdata/cycle/evaluate.txtar b/cue/testdata/cycle/evaluate.txtar index 3fd514e17..71080e4fa 100644 --- a/cue/testdata/cycle/evaluate.txtar +++ b/cue/testdata/cycle/evaluate.txtar @@ -115,6 +115,10 @@ Result: // [structural cycle] letCycle.0: structural cycle: // ./in.cue:34:9 } + let X#1 = (_|_){ + // [structural cycle] letCycle.0: structural cycle: + // ./in.cue:34:9 + } c: (_|_){ // [structural cycle] letCycle.0: structural cycle: // ./in.cue:34:9 @@ -273,7 +277,8 @@ Result: { letCycle: { b: 〈0;c〉 - c: 〈0;let X〉 + let X#1 = 〈0;b〉 + c: 〈0;let X#1〉 } listCycle: { a: 〈0;b〉 diff --git a/cue/testdata/cycle/issue990.txtar b/cue/testdata/cycle/issue990.txtar index 1ac139c45..f79db4ef8 100644 --- a/cue/testdata/cycle/issue990.txtar +++ b/cue/testdata/cycle/issue990.txtar @@ -106,7 +106,10 @@ out: #sub & {#p: _test.s1} [string]: _ } for rn, rd in 〈0;#p〉.d.r if (〈0;rd〉.a.k == "complex") { - for sn, sd in 〈0;let subs〉.dict { + let subs#1 = (〈3;#sub〉 & { + #p: 〈2;rd〉.a + }) + for sn, sd in 〈0;let subs#1〉.dict { dict: { "\(〈4;rn〉)_\(〈2;sn〉)": 〈2;sd〉 } @@ -409,5 +412,87 @@ out: #sub & {#p: _test.s1} } } } + let subs#1 = (#struct){ + dict: (#struct){ + r1: (#struct){ + a: (#struct){ + k: (string){ "simple" } + d: (#struct){ + n: (string){ "c1" } + } + } + } + r2_r1: (#struct){ + a: (#struct){ + k: (string){ "simple" } + d: (#struct){ + n: (string){ "c1" } + } + } + } + } + #p: (#struct){ + k: (string){ "complex" } + d: (#struct){ + n: (string){ "s2" } + r: (#struct){ + r1: (#struct){ + a: (#struct){ + k: (string){ "simple" } + d: (#struct){ + n: (string){ "c1" } + } + } + } + r2: (#struct){ + a: (#struct){ + k: (string){ "complex" } + d: (#struct){ + n: (string){ "s3" } + r: (#struct){ + r1: (#struct){ + a: (#struct){ + k: (string){ "simple" } + d: (#struct){ + n: (string){ "c1" } + } + } + } + } + } + } + } + } + } + } + let subs#1 = (#struct){ + dict: (#struct){ + r1: (#struct){ + a: (#struct){ + k: (string){ "simple" } + d: (#struct){ + n: (string){ "c1" } + } + } + } + } + #p: (#struct){ + k: (string){ "complex" } + d: (#struct){ + n: (string){ "s3" } + r: (#struct){ + r1: (#struct){ + a: (#struct){ + k: (string){ "simple" } + d: (#struct){ + n: (string){ "c1" } + } + } + } + } + } + } + } + } } } diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar index 5d4256084..26975c53a 100644 --- a/cue/testdata/cycle/structural.txtar +++ b/cue/testdata/cycle/structural.txtar @@ -1293,6 +1293,10 @@ Result: // [structural cycle] withLetFail.schema.next: structural cycle } } + let _schema_1#1 = (_|_){ + // [structural cycle] withLetFail._schema_1: structural cycle: + // ./in.cue:355:17 + } } listOptOK: (struct){ list: (struct){ @@ -2227,8 +2231,9 @@ Result: } withLetFail: { schema: { - next: 〈1;let _schema_1〉 + next: 〈1;let _schema_1#1〉 } + let _schema_1#1 = 〈0;schema〉 } listOptOK: { list: { diff --git a/cue/testdata/eval/let.txtar b/cue/testdata/eval/let.txtar index f5b243fff..b03b50b10 100644 --- a/cue/testdata/eval/let.txtar +++ b/cue/testdata/eval/let.txtar @@ -24,18 +24,104 @@ b: { a: T b: F } +issue1828: { + volumes: L3 + + let L3 = { + for v2 in [ for v1 in L2 {} ] {} + } + + let L2 = L1 + let L1 = [] +} +ignoreErrorInLet: { + let X = 1 & 2 + disjunction: *X | 3 +} -- out/eval -- (struct){ + let A#1 = (int){ 9 } + let B#2 = (int){ 18 } + let C#3 = (int){ 36 } + let D#4 = (int){ 72 } + let E#5 = (int){ 144 } + let F#6 = (int){ 288 } b: (struct){ + let G#7 = (int){ 576 } + let H#8 = (int){ 1152 } + let I#9 = (int){ 2304 } + let J#A = (int){ 4608 } + let K#B = (int){ 9216 } + let L#C = (int){ 18432 } + let M#D = (int){ 36864 } + let N#E = (int){ 73728 } + let O#F = (int){ 147456 } + let P#10 = (int){ 294912 } + let Q#11 = (int){ 589824 } + let R#12 = (int){ 1179648 } + let S#13 = (int){ 2359296 } + let T#14 = (int){ 4718592 } a: (int){ 4718592 } b: (int){ 288 } } + issue1828: (struct){ + volumes: (struct){ + } + let L3#15 = (struct){ + } + let L2#16 = (#list){ + } + let L1#17 = (#list){ + } + } + ignoreErrorInLet: (struct){ + let X#18 = (_|_){ + // [eval] ignoreErrorInLet.X: conflicting values 2 and 1: + // ./in.cue:37:10 + // ./in.cue:37:14 + } + disjunction: (int){ 3 } + } } -- out/compile -- --- in.cue { + let A#1 = (3 * 3) + let B#2 = (〈0;let A#1〉 + 〈0;let A#1〉) + let C#3 = (〈0;let B#2〉 + 〈0;let B#2〉) + let D#4 = (〈0;let C#3〉 + 〈0;let C#3〉) + let E#5 = (〈0;let D#4〉 + 〈0;let D#4〉) + let F#6 = (〈0;let E#5〉 + 〈0;let E#5〉) b: { - a: 〈0;let T〉 - b: 〈1;let F〉 + let G#7 = (〈1;let F#6〉 + 〈1;let F#6〉) + let H#8 = (〈0;let G#7〉 + 〈0;let G#7〉) + let I#9 = (〈0;let H#8〉 + 〈0;let H#8〉) + let J#A = (〈0;let I#9〉 + 〈0;let I#9〉) + let K#B = (〈0;let J#A〉 + 〈0;let J#A〉) + let L#C = (〈0;let K#B〉 + 〈0;let K#B〉) + let M#D = (〈0;let L#C〉 + 〈0;let L#C〉) + let N#E = (〈0;let M#D〉 + 〈0;let M#D〉) + let O#F = (〈0;let N#E〉 + 〈0;let N#E〉) + let P#10 = (〈0;let O#F〉 + 〈0;let O#F〉) + let Q#11 = (〈0;let P#10〉 + 〈0;let P#10〉) + let R#12 = (〈0;let Q#11〉 + 〈0;let Q#11〉) + let S#13 = (〈0;let R#12〉 + 〈0;let R#12〉) + let T#14 = (〈0;let S#13〉 + 〈0;let S#13〉) + a: 〈0;let T#14〉 + b: 〈1;let F#6〉 + } + issue1828: { + volumes: 〈0;let L3#15〉 + let L3#15 = { + for _, v2 in [ + for _, v1 in 〈2;let L2#16〉 {}, + ] {} + } + let L2#16 = 〈0;let L1#17〉 + let L1#17 = [] + } + ignoreErrorInLet: { + let X#18 = (1 & 2) + disjunction: (*〈0;let X#18〉|3) } } diff --git a/cue/testdata/eval/letjoin.txtar b/cue/testdata/eval/letjoin.txtar index 23ec4699c..6bcf0d65d 100644 --- a/cue/testdata/eval/letjoin.txtar +++ b/cue/testdata/eval/letjoin.txtar @@ -49,22 +49,38 @@ t2: { x: (struct){ a: (int){ int } y: (struct){ + let X#1 = (struct){ + b: (int){ int } + c: (int){ 1 } + } v: (int){ 1 } } } x1: (struct){ a: (int){ 1 } y: (struct){ + let X#1 = (struct){ + b: (int){ 1 } + c: (int){ 1 } + } v: (int){ 1 } } } x2: (struct){ a: (int){ 2 } y: (struct){ + let X#1 = (struct){ + b: (int){ 2 } + c: (int){ 1 } + } v: (int){ 1 } } } xy: (struct){ + let X#1 = { + b: 〈2;a〉 + c: 1 + } // multi v: (int){ 1 } } } @@ -73,6 +89,11 @@ t2: { a: (struct){ } y: (struct){ + let X#2 = (struct){ + b: (struct){ + } + c: (int){ 1 } + } v: (int){ 1 } } } @@ -81,6 +102,12 @@ t2: { q: (int){ 1 } } y: (struct){ + let X#2 = (struct){ + b: (struct){ + q: (int){ 1 } + } + c: (int){ 1 } + } v: (int){ 1 } } } @@ -89,6 +116,12 @@ t2: { r: (int){ 2 } } y: (struct){ + let X#2 = (struct){ + b: (struct){ + r: (int){ 2 } + } + c: (int){ 1 } + } v: (int){ 1 } } } @@ -98,6 +131,10 @@ t2: { r: (int){ 2 } } y: (struct){ + let X#2 = { + b: 〈2;a〉 + c: 1 + } // multi v: (int){ 1 } } } @@ -110,7 +147,11 @@ t2: { x: { a: int y: { - v: 〈0;let X〉.c + let X#1 = { + b: 〈2;a〉 + c: 1 + } + v: 〈0;let X#1〉.c } } x1: 〈0;x〉 @@ -127,7 +168,11 @@ t2: { x: { a: {} y: { - v: 〈0;let X〉.c + let X#2 = { + b: 〈2;a〉 + c: 1 + } + v: 〈0;let X#2〉.c } } x1: 〈0;x〉 diff --git a/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar b/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar index 38c58898a..3d6c7c2d8 100644 --- a/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar +++ b/cue/testdata/fulleval/049_alias_reuse_in_nested_scope.txtar @@ -67,11 +67,16 @@ b: -- out/eval -- (struct){ #Foo: (#struct){ + let X#1 = (_|_){ + // [incomplete] empty list in call to or: + // ./in.cue:2:10 + } connection: (#struct){ } } #A: (#struct){ foo: (string){ "key" } + let X#2 = (string){ "key" } a: (#struct){ foo: (#struct){ } @@ -79,6 +84,7 @@ b: } #B: (#struct){ foo: (string){ string } + let X#3 = (string){ string } a: (#struct){ foo: (#struct){ } @@ -86,6 +92,7 @@ b: } b: (#struct){ foo: (string){ "key" } + let X#3 = (string){ "key" } a: (#struct){ foo: (#struct){ } @@ -96,23 +103,30 @@ b: --- in.cue { #Foo: { + let X#1 = or([ + for k, _ in {} { + 〈1;k〉 + }, + ]) connection: { - [〈1;let X〉]: 〈1;let X〉 + [〈1;let X#1〉]: 〈1;let X#1〉 } } #A: { foo: "key" + let X#2 = 〈0;foo〉 a: { foo: { - [〈2;let X〉]: 〈2;let X〉 + [〈2;let X#2〉]: 〈2;let X#2〉 } } } #B: { foo: string + let X#3 = 〈0;foo〉 a: { foo: { - [〈2;let X〉]: 〈2;let X〉 + [〈2;let X#3〉]: 〈2;let X#3〉 } } } diff --git a/cue/testdata/references/let.txtar b/cue/testdata/references/let.txtar index 447520b98..87eb19323 100644 --- a/cue/testdata/references/let.txtar +++ b/cue/testdata/references/let.txtar @@ -83,23 +83,26 @@ issue1116: { 1 }, ] - a1: ((100 * 〈0;let A1〉[0]) + 〈0;let A1〉[0]) + let A1#1 = 〈0;a1list〉 + a1: ((100 * 〈0;let A1#1〉[0]) + 〈0;let A1#1〉[0]) a2list: [ { 2 }, ] + let A2#2 = 〈0;a2list〉 a2: { - b: ((100 * 〈1;let A2〉[0]) + 〈1;let A2〉[0]) + b: ((100 * 〈1;let A2#2〉[0]) + 〈1;let A2#2〉[0]) } a3list: [ { 3 }, ] + let A3#3 = 〈0;a3list〉 a3: { b: { - c: ((100 * 〈2;let A3〉[0]) + 〈2;let A3〉[0]) + c: ((100 * 〈2;let A3#3〉[0]) + 〈2;let A3#3〉[0]) } } a4list: [ @@ -107,8 +110,9 @@ issue1116: { 4 }, ] + let A4#4 = 〈0;a4list〉 a4: [ - for _, x in 〈1;let A4〉 { + for _, x in 〈1;let A4#4〉 { v: 404 }, ] @@ -117,9 +121,10 @@ issue1116: { 5 }, ] + let A5#5 = 〈0;a5list〉 a5: { b: [ - for _, x in 〈2;let A5〉 { + for _, x in 〈2;let A5#5〉 { v: 505 }, ] @@ -129,10 +134,11 @@ issue1116: { 6 }, ] + let A6#6 = 〈0;a6list〉 a6: { b: { c: [ - for _, x in 〈3;let A6〉 { + for _, x in 〈3;let A6#6〉 { v: 606 }, ] @@ -143,8 +149,9 @@ issue1116: { 7 }, ] + let A7#7 = 〈0;a7list〉 a7: { - for _, x in 〈1;let A7〉 { + for _, x in 〈1;let A7#7〉 { v: 707 } } @@ -153,9 +160,10 @@ issue1116: { 8 }, ] + let A8#8 = 〈0;a8list〉 a8: { b: { - for _, x in 〈2;let A8〉 { + for _, x in 〈2;let A8#8〉 { v: 808 } } @@ -165,10 +173,11 @@ issue1116: { 9 }, ] + let A9#9 = 〈0;a9list〉 a9: { b: { c: { - for _, x in 〈3;let A9〉 { + for _, x in 〈3;let A9#9〉 { v: 909 } } @@ -187,8 +196,10 @@ issue1116: { max: 〈import;list〉.Max(〈1;input〉) } bar: { - min: 〈0;let mn〉 - max: 〈0;let mx〉 + let mn#A = 〈1;last〉.min + let mx#B = 〈import;list〉.max + min: 〈0;let mn#A〉 + max: 〈0;let mx#B〉 } x: { if (〈import;list〉.max < 0) {} @@ -200,10 +211,12 @@ issue1116: { "a", "b", ] - 〈0;let List〉[(len(〈0;let List〉) - 1)] + let List#C = 〈0;#a〉 + 〈0;let List#C〉[(len(〈0;let List#C〉) - 1)] } b: { - 〈0;let List〉[(len(〈0;let List〉) - 1)] + let List#D = 〈0;#a〉 + 〈0;let List#D〉[(len(〈0;let List#D〉) - 1)] #a: [ "a", "b", @@ -216,16 +229,25 @@ issue1116: { a1list: (#list){ 0: (int){ 1 } } + let A1#1 = (#list){ + 0: (int){ 1 } + } a1: (int){ 101 } a2list: (#list){ 0: (int){ 2 } } + let A2#2 = (#list){ + 0: (int){ 2 } + } a2: (struct){ b: (int){ 202 } } a3list: (#list){ 0: (int){ 3 } } + let A3#3 = (#list){ + 0: (int){ 3 } + } a3: (struct){ b: (struct){ c: (int){ 303 } @@ -234,6 +256,9 @@ issue1116: { a4list: (#list){ 0: (int){ 4 } } + let A4#4 = (#list){ + 0: (int){ 4 } + } a4: (#list){ 0: (struct){ v: (int){ 404 } @@ -242,6 +267,9 @@ issue1116: { a5list: (#list){ 0: (int){ 5 } } + let A5#5 = (#list){ + 0: (int){ 5 } + } a5: (struct){ b: (#list){ 0: (struct){ @@ -252,6 +280,9 @@ issue1116: { a6list: (#list){ 0: (int){ 6 } } + let A6#6 = (#list){ + 0: (int){ 6 } + } a6: (struct){ b: (struct){ c: (#list){ @@ -264,12 +295,18 @@ issue1116: { a7list: (#list){ 0: (int){ 7 } } + let A7#7 = (#list){ + 0: (int){ 7 } + } a7: (struct){ v: (int){ 707 } } a8list: (#list){ 0: (int){ 8 } } + let A8#8 = (#list){ + 0: (int){ 8 } + } a8: (struct){ b: (struct){ v: (int){ 808 } @@ -278,6 +315,9 @@ issue1116: { a9list: (#list){ 0: (int){ 9 } } + let A9#9 = (#list){ + 0: (int){ 9 } + } a9: (struct){ b: (struct){ c: (struct){ @@ -298,6 +338,11 @@ issue1116: { max: (int){ 5 } } bar: (struct){ + let mn#A = (int){ 1 } + let mx#B = (_|_){ + // [incomplete] incompleteLet.bar.mx: undefined field: max: + // ./in.cue:54:17 + } min: (int){ 1 } max: (_|_){ // [incomplete] incompleteLet.bar.max: undefined field: max: @@ -316,9 +361,17 @@ issue1116: { 0: (string){ "a" } 1: (string){ "b" } } + let List#C = (#list){ + 0: (string){ "a" } + 1: (string){ "b" } + } } b: (string){ "b" + let List#D = (#list){ + 0: (string){ "a" } + 1: (string){ "b" } + } #a: (#list){ 0: (string){ "a" } 1: (string){ "b" } diff --git a/cue/testdata/references/letcycle.txtar b/cue/testdata/references/letcycle.txtar index f636a73f7..24ef47bc2 100644 --- a/cue/testdata/references/letcycle.txtar +++ b/cue/testdata/references/letcycle.txtar @@ -56,10 +56,20 @@ cycles.issue1042.#Add16.let[].for[].c.for[].for[].c: cyclic references in let cl { cycles: { a: { - out: 〈0;let A〉 + let A#1 = { + c: 〈1;let B#2〉 + } + let B#2 = 〈0;let A#1〉 + out: 〈0;let A#1〉 } b: { - out: 〈0;let A〉 + let A#3 = { + c: 〈1;let B#4〉 + } + let B#4 = { + 〈1;let A#3〉.c + } + out: 〈0;let A#3〉 } issue1042: { #FullAdder: { @@ -79,9 +89,24 @@ cycles.issue1042.#Add16.let[].for[].c.for[].for[].c: cyclic references in let cl out: ([ bool, ] * 16) + let fulladders#5 = [ + for _, i in 〈import;list〉.Range(0, 4, 1) { + (〈4;#FullAdder〉 & { + a: 〈4;a〉[〈2;i〉] + b: 〈4;b〉[〈2;i〉] + c: 〈4;let carries#6〉[〈2;i〉] + }) + }, + ] + let carries#6 = [ + false, + for _, i in 〈import;list〉.Range(0, 4, 1) { + 〈3;let fulladders#5〉[〈1;i〉].carry + }, + ] out: [ for _, i in 〈import;list〉.Range(0, 4, 1) { - 〈3;let fulladders〉[〈1;i〉].sum + 〈3;let fulladders#5〉[〈1;i〉].sum }, ] } diff --git a/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar b/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar index d1dcf7f23..bd09d75c9 100644 --- a/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar +++ b/cue/testdata/resolve/035_excluded_embedding_from_closing.txtar @@ -51,7 +51,10 @@ V: #S & { d: int } } - b: 〈0;let B〉 + let B#1 = { + open: int + } + b: 〈0;let B#1〉 } V: (〈0;#S〉 & { c: { @@ -85,6 +88,9 @@ Result: a: (#struct){ c: (int){ int } } + let B#1 = (#struct){ + open: (int){ int } + } b: (#struct){ open: (int){ int } } @@ -105,6 +111,9 @@ Result: a: (#struct){ c: (int){ int } } + let B#1 = (#struct){ + open: (int){ int } + } b: (_|_){ // [eval] open: (int){ int } diff --git a/cue/types.go b/cue/types.go index fc3b5263d..412ef49c7 100644 --- a/cue/types.go +++ b/cue/types.go @@ -1402,6 +1402,9 @@ func (v Value) structValOpts(ctx *adt.OpContext, o options) (s structValue, err k := 0 for _, f := range features { + if f.IsLet() { + continue + } if f.IsDef() && (o.omitDefinitions || o.concrete) { continue } diff --git a/internal/core/adt/adt.go b/internal/core/adt/adt.go index 315726481..82acdb52d 100644 --- a/internal/core/adt/adt.go +++ b/internal/core/adt/adt.go @@ -236,6 +236,8 @@ func (*Field) declNode() {} func (x *Field) expr() Expr { return x.Value } func (*OptionalField) declNode() {} func (x *OptionalField) expr() Expr { return x.Value } +func (*LetField) declNode() {} +func (x *LetField) expr() Expr { return x.Value } func (*BulkOptionalField) declNode() {} func (x *BulkOptionalField) expr() Expr { return x.Value } func (*DynamicField) declNode() {} @@ -370,6 +372,7 @@ func (*CallExpr) node() {} func (*DisjunctionExpr) node() {} func (*Field) node() {} func (*OptionalField) node() {} +func (*LetField) node() {} func (*BulkOptionalField) node() {} func (*DynamicField) node() {} func (*Ellipsis) node() {} diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go index bbce22ecf..f35f563f0 100644 --- a/internal/core/adt/composite.go +++ b/internal/core/adt/composite.go @@ -91,6 +91,8 @@ type Environment struct { // TODO(perf): make the following public fields a shareable struct as it // mostly is going to be the same for child nodes. + // TODO: This can probably move into the nodeContext, making it a map from + // conjunct to Value. cache map[Expr]Value } @@ -103,8 +105,7 @@ func (e *Environment) up(count int32) *Environment { type ID int32 -// evalCached is used to look up let expressions. Caching let expressions -// prevents a possible combinatorial explosion. +// evalCached is used to look up dynamic field pattern constraint expressions. func (e *Environment) evalCached(c *OpContext, x Expr) Value { if v, ok := x.(Value); ok { return v @@ -163,6 +164,11 @@ type Vertex struct { // conjuncts, or ancestor conjuncts, is a definition. Closed bool + // MultiLet indicates whether multiple let fields were added from + // different sources. If true, a LetReference must be resolved using + // the per-Environment value cache. + MultiLet bool + // arcType indicates the level of optionality of this arc. arcType arcType diff --git a/internal/core/adt/context.go b/internal/core/adt/context.go index 61168fea9..55ea06069 100644 --- a/internal/core/adt/context.go +++ b/internal/core/adt/context.go @@ -474,6 +474,21 @@ func (c *OpContext) Resolve(x Conjunct, r Resolver) (*Vertex, *Bottom) { return arc, err } +// Lookup looks up r in env without further resolving the value. +func (c *OpContext) Lookup(env *Environment, r Resolver) (*Vertex, *Bottom) { + s := c.PushState(env, r.Source()) + + arc := r.resolve(c, Partial) + + err := c.PopState(s) + + if arc != nil { + arc = arc.Indirect() + } + + return arc, err +} + // Validate calls validates value for the given validator. // // TODO(errors): return boolean instead: only the caller has enough information diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go index 5298f1874..882d5252b 100644 --- a/internal/core/adt/eval.go +++ b/internal/core/adt/eval.go @@ -708,7 +708,7 @@ func (n *nodeContext) completeArcs(state VertexStatus) { continue } - if err, _ := a.BaseValue.(*Bottom); err != nil { + if err, _ := a.BaseValue.(*Bottom); err != nil && !a.Label.IsLet() { n.node.AddChildError(err) } @@ -1808,7 +1808,7 @@ func (n *nodeContext) addStruct( for _, d := range s.Decls { switch x := d.(type) { - case *Field: + case *Field, *LetField: // handle in next iteration. case *DynamicField: @@ -1854,6 +1854,9 @@ func (n *nodeContext) addStruct( n.aStructID = closeInfo } n.insertField(x.Label, MakeConjunct(childEnv, x, closeInfo)) + + case *LetField: + n.insertField(x.Label, MakeConjunct(childEnv, x, closeInfo)) } } } @@ -1871,7 +1874,11 @@ func (n *nodeContext) addStruct( // disjunctions. func (n *nodeContext) insertField(f Feature, x Conjunct) *Vertex { ctx := n.ctx - arc, _ := n.node.GetArc(ctx, f, arcMember) + arc, isNew := n.node.GetArc(ctx, f, arcMember) + if f.IsLet() && !isNew { + arc.MultiLet = true + return arc + } arc.addConjunct(x) switch { diff --git a/internal/core/adt/eval_test.go b/internal/core/adt/eval_test.go index dc59259c5..18af09e51 100644 --- a/internal/core/adt/eval_test.go +++ b/internal/core/adt/eval_test.go @@ -49,10 +49,9 @@ func TestEval(t *testing.T) { test.ToDo = nil } - r := runtime.New() - test.Run(t, func(t *cuetxtar.Test) { a := t.Instance() + r := runtime.New() v, err := r.Build(nil, a) if err != nil { diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go index 544bfd21e..06d1e97cf 100644 --- a/internal/core/adt/expr.go +++ b/internal/core/adt/expr.go @@ -112,6 +112,12 @@ func (o *StructLit) Init() { o.Fields[p].Optional = append(o.Fields[p].Optional, x) o.types |= HasField + case *LetField: + if o.fieldIndex(x.Label) >= 0 { + panic("duplicate let identifier") + } + o.Fields = append(o.Fields, FieldInfo{Label: x.Label}) + case *DynamicField: o.Dynamic = append(o.Dynamic, x) o.types |= HasDynamic @@ -219,6 +225,22 @@ func (x *OptionalField) Source() ast.Node { return x.Src } +// A LetField represents a field that is only visible in the local scope. +// +// let X = expr +type LetField struct { + Src *ast.LetClause + Label Feature + Value Expr +} + +func (x *LetField) Source() ast.Node { + if x.Src == nil { + return nil + } + return x.Src +} + // A BulkOptionalField represents a set of optional field. // // [expr]: expr @@ -866,25 +888,43 @@ func (x *LetReference) Source() ast.Node { return x.Src } -func (x *LetReference) resolve(c *OpContext, state VertexStatus) *Vertex { - e := c.Env(x.UpCount) - label := e.Vertex.Label - if x.X == nil { - panic("nil expression") +func (x *LetReference) resolve(ctx *OpContext, state VertexStatus) *Vertex { + e := ctx.Env(x.UpCount) + n := e.Vertex + + // No need to Unify n, as Let references can only result from evaluating + // an experssion within n, in which case evaluation must already have + // started. + if n.status < Evaluating { + panic("unexpected node state < Evaluating") } - // Anonymous arc. - return &Vertex{ - Parent: e.Vertex, - Label: label, - Conjuncts: []Conjunct{{e, x.X, c.ci}}, + + arc := ctx.lookup(n, pos(x), x.Label, state) + if arc == nil { + return nil } -} -func (x *LetReference) evaluate(c *OpContext) Value { - e := c.Env(x.UpCount) + if !arc.MultiLet { + return arc + } // Not caching let expressions may lead to exponential behavior. - return e.evalCached(c, x.X) + // The expr uses the expression of a Let field, which can never be used in + // any other context. + expr := arc.Conjuncts[0].Expr() + v, ok := e.cache[expr] + if !ok { + if e.cache == nil { + e.cache = map[Expr]Value{} + } + v = &Vertex{ + Parent: n, + Label: x.Label, + Conjuncts: []Conjunct{{e, expr, ctx.ci}}, + } + e.cache[expr] = v + } + return v.(*Vertex) } // A SelectorExpr looks up a fixed field in an expression. @@ -1755,7 +1795,7 @@ func (x *IfClause) yield(ctx *OpContext, f YieldFunc) { } } -// An LetClause represents a let clause in a comprehension. +// A LetClause represents a let clause in a comprehension. // // let x = y type LetClause struct { diff --git a/internal/core/adt/expr_test.go b/internal/core/adt/expr_test.go index fddfe057c..809a5b292 100644 --- a/internal/core/adt/expr_test.go +++ b/internal/core/adt/expr_test.go @@ -48,6 +48,7 @@ func TestNilSource(t *testing.T) { &Interpolation{}, &LabelReference{}, &LetClause{}, + &LetField{}, &LetReference{}, &ListLit{}, &ListMarker{}, diff --git a/internal/core/adt/feature.go b/internal/core/adt/feature.go index 43a9d95ee..795c49434 100644 --- a/internal/core/adt/feature.go +++ b/internal/core/adt/feature.go @@ -60,6 +60,9 @@ type StringIndexer interface { // ToString returns a string s for index such that ToIndex(s) == index. IndexToString(index int64) string + + // NextUniqueID returns a new unique identifier. + NextUniqueID() uint64 } // SelectorString reports the shortest string representation of f when used as a @@ -90,7 +93,7 @@ func (f Feature) SelectorString(index StringIndexer) string { // is not an identifier label. func (f Feature) IdentString(index StringIndexer) string { s := index.IndexToString(f.safeIndex()) - if f.IsHidden() { + if f.IsHidden() || f.IsLet() { if p := strings.IndexByte(s, '\x00'); p >= 0 { s = s[:p] } @@ -121,6 +124,12 @@ func (f Feature) StringValue(index StringIndexer) string { return index.IndexToString(x) } +// RawString reports the underlying string value of f without interpretation. +func (f Feature) RawString(index StringIndexer) string { + x := f.safeIndex() + return index.IndexToString(x) +} + // ToValue converts a label to a value, which will be a Num for integer labels // and a String for string labels. It panics when f is not a regular label. func (f Feature) ToValue(ctx *OpContext) Value { @@ -191,6 +200,23 @@ func MakeNamedLabel(r StringIndexer, t FeatureType, s string) Feature { return f } +// MakeLetLabel creates a label for the given let identifier s. +// +// A let declaration is always logically unique within its scope and will never +// unify with a let field of another struct. This is enforced by ensuring that +// the let identifier is unique across an entire configuration. This, in turn, +// is done by adding a unique number to each let identifier. +func MakeLetLabel(r StringIndexer, s string) Feature { + id := r.NextUniqueID() + s = fmt.Sprintf("%s\x00%X", s, id) + i := r.StringToIndex(s) + f, err := MakeLabel(nil, i, LetLabel) + if err != nil { + panic("out of free string slots") + } + return f +} + // MakeIntLabel creates an integer label. func MakeIntLabel(t FeatureType, i int64) Feature { f, err := MakeLabel(nil, i, t) @@ -294,6 +320,7 @@ const ( DefinitionLabel HiddenLabel HiddenDefinitionLabel + LetLabel ) const ( @@ -310,6 +337,10 @@ func (f FeatureType) IsHidden() bool { return f == HiddenLabel || f == HiddenDefinitionLabel } +func (f FeatureType) IsLet() bool { + return f == LetLabel +} + // IsValid reports whether f is a valid label. func (f Feature) IsValid() bool { return f != InvalidLabel } @@ -340,6 +371,11 @@ func (f Feature) IsHidden() bool { return f.Typ().IsHidden() } +// IsLet reports whether this label is a let field (like `let X = value`). +func (f Feature) IsLet() bool { + return f.Typ().IsLet() +} + // Index reports the abstract index associated with f. func (f Feature) Index() int { return int(f >> indexShift) diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go index c9dcdd7b0..31b58d91a 100644 --- a/internal/core/compile/compile.go +++ b/internal/core/compile/compile.go @@ -153,6 +153,7 @@ type aliasEntry struct { srcExpr ast.Expr expr adt.Expr source ast.Node + feature adt.Feature // For let declarations used bool } @@ -202,6 +203,8 @@ func (c *compiler) lookupAlias(k int, id *ast.Ident) aliasEntry { switch { case entry.label != nil: + // TODO: allow cyclic references in let expressions once these can be + // encoded as a ValueReference. if entry.srcExpr == nil { entry.expr = c.errf(id, "cyclic references in let clause or alias") break @@ -344,7 +347,10 @@ func (c *compiler) resolve(n *ast.Ident) adt.Expr { upCount += c.upCountOffset for p := c.Scope; p != nil; p = p.Parent() { for _, a := range p.Vertex().Arcs { - if a.Label == label { + switch { + case a.Label.IsLet() && a.Label.IdentString(c.index) == n.Name: + label = a.Label + case a.Label == label: return &adt.FieldReference{ Src: n, UpCount: upCount, @@ -442,13 +448,14 @@ func (c *compiler) resolve(n *ast.Ident) adt.Expr { if entry.expr == nil { panic("unreachable") } + label = entry.feature // let x = y return &adt.LetReference{ Src: n, UpCount: upCount, Label: label, - X: entry.expr, + X: entry.expr, // TODO: remove usage } // TODO: handle new-style aliases @@ -529,6 +536,7 @@ func (c *compiler) markAlias(d ast.Decl) { label: (*letScope)(x), srcExpr: x.Expr, source: x, + feature: adt.MakeLetLabel(c.index, x.Ident.Name), } c.insertAlias(x.Ident, a) @@ -634,8 +642,25 @@ func (c *compiler) decl(d ast.Decl) adt.Decl { } } - // Handled in addLetDecl. case *ast.LetClause: + m := c.stack[len(c.stack)-1].aliases + entry := m[x.Ident.Name] + + // A reference to the let should, in principle, be interpreted as a + // value reference, not field reference: + // - this is syntactically consistent for the use of = + // - this is semantically the only valid interpretation + // In practice this amounts to the same thing, as let expressions cannot + // be addressed from outside their scope. But it will matter once + // expressions may refer to a let from within the let. + value := c.labeledExpr(x, (*letScope)(x), x.Expr) + + return &adt.LetField{ + Src: x, + Label: entry.feature, + Value: value, + } + // case: *ast.Alias: // TODO(value alias) case *ast.CommentGroup: @@ -664,16 +689,6 @@ func (c *compiler) decl(d ast.Decl) adt.Decl { func (c *compiler) addLetDecl(d ast.Decl) { switch x := d.(type) { - // An alias reference will have an expression that is looked up in the - // environment cash. - case *ast.LetClause: - // Cache the parsed expression. Creating a unique expression for each - // reference allows the computation to be shared given that we don't - // have fields for expressions. This, in turn, prevents exponential - // blowup in x2: x1+x1, x3: x2+x2, ... patterns. - expr := c.labeledExpr(nil, (*letScope)(x), x.Expr) - c.updateAlias(x.Ident, expr) - case *ast.Field: lab := x.Label if a, ok := lab.(*ast.Alias); ok { @@ -799,9 +814,6 @@ func (c *compiler) labeledExpr(f ast.Decl, lab labeler, expr ast.Expr) adt.Expr } func (c *compiler) labeledExprAt(k int, f ast.Decl, lab labeler, expr ast.Expr) adt.Expr { - if c.stack[k].field != nil { - panic("expected nil field") - } saved := c.stack[k] c.stack[k].label = lab diff --git a/internal/core/compile/compile_test.go b/internal/core/compile/compile_test.go index e124c8c44..e1b43a8c0 100644 --- a/internal/core/compile/compile_test.go +++ b/internal/core/compile/compile_test.go @@ -44,9 +44,8 @@ func TestCompile(t *testing.T) { test.ToDo = nil } - r := runtime.New() - test.Run(t, func(t *cuetxtar.Test) { + r := runtime.New() // TODO: use high-level API. a := t.Instance() diff --git a/internal/core/debug/compact.go b/internal/core/debug/compact.go index 4a1794f0c..9f6c0cc1a 100644 --- a/internal/core/debug/compact.go +++ b/internal/core/debug/compact.go @@ -50,9 +50,21 @@ func (w *compactPrinter) node(n adt.Node) { if i > 0 { w.string(",") } - w.label(a.Label) - w.string(":") - w.node(a) + if a.Label.IsLet() { + w.string("let ") + w.label(a.Label) + w.string("=") + if c := a.Conjuncts[0]; a.MultiLet { + w.node(c.Expr()) + w.string(" // multi") + continue + } + w.node(a) + } else { + w.label(a.Label) + w.string(":") + w.node(a) + } } w.string("}") @@ -108,6 +120,13 @@ func (w *compactPrinter) node(n adt.Node) { w.string("?:") w.node(x.Value) + case *adt.LetField: + w.string("let ") + s := w.labelString(x.Label) + w.string(s) + w.string("=") + w.node(x.Value) + case *adt.BulkOptionalField: w.string("[") w.node(x.Filter) diff --git a/internal/core/debug/debug.go b/internal/core/debug/debug.go index 43f8cd013..57f4d89e0 100644 --- a/internal/core/debug/debug.go +++ b/internal/core/debug/debug.go @@ -94,14 +94,22 @@ func (w *printer) ident(f adt.Feature) { // TODO: fold into label once :: is no longer supported. func (w *printer) labelString(f adt.Feature) string { - if f.IsHidden() { + switch { + case f.IsHidden(): ident := f.IdentString(w.index) if pkgName := f.PkgID(w.index); pkgName != "_" { ident = fmt.Sprintf("%s(%s)", ident, pkgName) } return ident + + case f.IsLet(): + ident := f.RawString(w.index) + ident = strings.Replace(ident, "\x00", "#", 1) + return ident + + default: + return f.SelectorString(w.index) } - return f.SelectorString(w.index) } func (w *printer) shortError(errs errors.Error) { @@ -214,9 +222,21 @@ func (w *printer) node(n adt.Node) { for _, a := range x.Arcs { w.string("\n") - w.label(a.Label) - w.string(": ") - w.node(a) + if a.Label.IsLet() { + w.string("let ") + w.label(a.Label) + w.string(" = ") + if c := a.Conjuncts[0]; a.MultiLet { + w.node(c.Expr()) + w.string(" // multi") + continue + } + w.node(a) + } else { + w.label(a.Label) + w.string(": ") + w.node(a) + } } if x.BaseValue == nil { @@ -289,6 +309,13 @@ func (w *printer) node(n adt.Node) { w.string(" ") w.node(x.Value) + case *adt.LetField: + w.string("let ") + s := w.labelString(x.Label) + w.string(s) + w.string(" = ") + w.node(x.Value) + case *adt.BulkOptionalField: w.string("[") w.node(x.Filter) @@ -391,7 +418,7 @@ func (w *printer) node(n adt.Node) { w.string(openTuple) w.string(strconv.Itoa(int(x.UpCount))) w.string(";let ") - w.ident(x.Label) + w.label(x.Label) w.string(closeTuple) case *adt.SelectorExpr: diff --git a/internal/core/dep/dep.go b/internal/core/dep/dep.go index 4595e6474..42aefa017 100644 --- a/internal/core/dep/dep.go +++ b/internal/core/dep/dep.go @@ -218,20 +218,19 @@ func (c *visitor) markExpr(env *adt.Environment, expr adt.Elem) { // markResolve resolves dependencies. func (c *visitor) markResolver(env *adt.Environment, r adt.Resolver) { - switch x := r.(type) { - case nil: - case *adt.LetReference: - saved := c.ctxt.PushState(env, nil) - env := c.ctxt.Env(x.UpCount) - c.markExpr(env, x.X) - c.ctxt.PopState(saved) - return - } - // Note: it is okay to pass an empty CloseInfo{} here as we assume that // all nodes are finalized already and we need neither closedness nor cycle // checks. if ref, _ := c.ctxt.Resolve(adt.MakeConjunct(env, r, adt.CloseInfo{}), r); ref != nil { + if ref.Label.IsLet() { + x := r.(*adt.LetReference) + saved := c.ctxt.PushState(env, nil) + env := c.ctxt.Env(x.UpCount) + c.markExpr(env, ref.Conjuncts[0].Expr()) + c.ctxt.PopState(saved) + return + } + if ref != c.node && ref != empty { d := Dependency{ Node: ref, diff --git a/internal/core/dep/mixed.go b/internal/core/dep/mixed.go index 20a82f692..7635a1d29 100644 --- a/internal/core/dep/mixed.go +++ b/internal/core/dep/mixed.go @@ -85,6 +85,9 @@ func (m marked) markExpr(x adt.Expr) { case *adt.BulkOptionalField: m.markExpr(x.Value) + case *adt.LetField: + m.markExpr(x.Value) + case *adt.DynamicField: m.markExpr(x.Value) diff --git a/internal/core/export/adt.go b/internal/core/export/adt.go index 1fd091276..2bfc92754 100644 --- a/internal/core/export/adt.go +++ b/internal/core/export/adt.go @@ -68,6 +68,11 @@ func (e *exporter) adt(env *adt.Environment, expr adt.Elem) ast.Expr { } } decl := e.decl(env, d) + // decl may be nil if it represents a let. Lets are added later, and + // only when they are still used. + if decl == nil { + continue + } if a != nil { if f, ok := decl.(*ast.Field); ok { @@ -432,6 +437,10 @@ func (e *exporter) decl(env *adt.Environment, d adt.Decl) ast.Decl { // extractDocs(nil) return f + case *adt.LetField: + // Handled elsewhere + return nil + case *adt.BulkOptionalField: e.setDocs(x) // set bulk in frame. diff --git a/internal/core/export/export.go b/internal/core/export/export.go index b29ef4744..d821681fe 100644 --- a/internal/core/export/export.go +++ b/internal/core/export/export.go @@ -425,7 +425,19 @@ func (e *exporter) resolveLet(env *adt.Environment, x *adt.LetReference) ast.Exp switch { case let == nil: - return e.expr(env, x.X) + ref, _ := e.ctx.Lookup(env, x) + if ref == nil { + // This can happen if x.X does not resolve to a valid value. At this + // point we will not get a valid configuration. + + // TODO: get rid of the use of x.X. + // str := x.Label.IdentString(e.ctx) + // ident := ast.NewIdent(str) + // return ident + + return e.expr(env, x.X) + } + return e.expr(env, ref.Conjuncts[0].Expr()) case let.Expr == nil: label := e.uniqueLetIdent(x.Label, x.X) diff --git a/internal/core/export/export_test.go b/internal/core/export/export_test.go index 0ef8afb15..413787003 100644 --- a/internal/core/export/export_test.go +++ b/internal/core/export/export_test.go @@ -269,34 +269,6 @@ func TestX(t *testing.T) { in := ` -- in.cue -- -package test - -// // Foo -// a: [X=string]: [Y=string]: { -// name: X+Y -// } - -// [Y=string]: [X=string]: name: {Y+X} -// { -// name: X.other + Y -// other: string -// } - -// c: [X=string]: X - -// #pkg1: Object - -// "Hello \(#pkg1)!" - - -// Object: "World" - -// // A Foo fooses stuff. -// foos are instances of Foo. -// foos: [string]: {} - -// // // My first little foo. -// foos: MyFoo: {} ` archive := txtar.Parse([]byte(in)) @@ -308,7 +280,8 @@ package test // x := a[0].Files[0] // astutil.Sanitize(x) - r := runtime.New() + ctx := cuecontext.New() + r := (*runtime.Runtime)(ctx) v, errs := compile.Files(nil, r, "", a[0].Files...) if errs != nil { t.Fatal(errs) diff --git a/internal/core/export/expr.go b/internal/core/export/expr.go index 5459400ec..5ccc6661a 100644 --- a/internal/core/export/expr.go +++ b/internal/core/export/expr.go @@ -229,6 +229,9 @@ func (x *exporter) mergeValues(label adt.Feature, src *adt.Vertex, a []conjunct, } for _, f := range fields { + if f.IsLet() { + continue + } field := e.fields[f] c := field.conjuncts @@ -375,6 +378,8 @@ func (e *conjuncts) addExpr(env *adt.Environment, src *adt.Vertex, x adt.Elem, i case *adt.OptionalField: // TODO: mark optional here. label = f.Label + case *adt.LetField: + continue case *adt.Ellipsis: e.hasEllipsis = true continue @@ -507,6 +512,8 @@ func isComplexStruct(s *adt.StructLit) bool { } } + case *adt.LetField: + case adt.Expr: case *adt.Ellipsis: diff --git a/internal/core/export/self_test.go b/internal/core/export/self_test.go index 053cc7529..bee0adb0e 100644 --- a/internal/core/export/self_test.go +++ b/internal/core/export/self_test.go @@ -83,6 +83,7 @@ func TestSelfContained(t *testing.T) { } func buildFile(t *testing.T, r *cue.Context, b *build.Instance) cue.Value { + t.Helper() v := r.BuildInstance(b) if err := v.Err(); err != nil { t.Fatal(errors.Details(err, nil)) diff --git a/internal/core/export/testdata/main/alias.txtar b/internal/core/export/testdata/main/alias.txtar index 05a80db88..08921a611 100644 --- a/internal/core/export/testdata/main/alias.txtar +++ b/internal/core/export/testdata/main/alias.txtar @@ -170,6 +170,7 @@ is no need to rename the variable here as the expression was known to compile and is known to be correct. [issue1308] +[issue1308 _xA] [issue1308 sub] [issue1308 sub 0] [issue1308 sub 0 _A] diff --git a/internal/core/export/testdata/main/let.txtar b/internal/core/export/testdata/main/let.txtar index 136cf5aad..834731c45 100644 --- a/internal/core/export/testdata/main/let.txtar +++ b/internal/core/export/testdata/main/let.txtar @@ -1,7 +1,7 @@ # Issue #590 -- in.cue -- - let X = 1 + 1 +let X = 1 + 1 #Foo: X -- x.cue -- x: string @@ -143,9 +143,9 @@ let Y_1 = x } }] for cfg in cfgs { - let filepath_1 = "kind-\(cfg.name)" + let filepath = "kind-\(cfg.name)" files: { - "\(filepath_1)": { + "\(filepath)": { patches: cfg } } @@ -155,8 +155,8 @@ comprehension: { for cfg in [{ a: "one" }] { - let filepath = "kind-\(cfg.name)" - "\(filepath)": { + let filepath_1 = "kind-\(cfg.name)" + "\(filepath_1)": { patches: cfg } } @@ -174,30 +174,30 @@ scoped: { required: 1 } direct: { + let Args = _args + a: Args.required + } + embed1: { let Args_1 = _args a: Args_1.required } - embed1: { + embed2: { let Args_2 = _args a: Args_2.required } - embed2: { - let Args_3 = _args - a: Args_3.required - } list: { - let Args_4 = _args - a: [Args_4.required] + let Args_3 = _args + a: [Args_3.required] } listStruct: { - let Args_8 = _args + let Args_4 = _args a: [{ - a: Args_8.required + a: Args_4.required }] } listEmbed: { - let Args_32 = _args - a: [Args_32.required] + let Args_8 = _args + a: [Args_8.required] } } x: "foo" @@ -229,9 +229,9 @@ incomplete: { } d: { let A_4 = run.a - let A_9 = run2.a + let A_32 = run2.a x: "a \(A_4) z" - x2: "a \(A_9) z" + x2: "a \(A_32) z" run: { a: string } @@ -242,13 +242,13 @@ incomplete: { } unresolvedDisjunction: { #TypePrimitive: { - let Args = _args + let Args_9 = _args { "*": {} } | { bool: #TypeBool & { _args: { - required: Args.required + required: Args_9.required } } } @@ -271,8 +271,11 @@ y: Y & Y_1 -- out/doc -- [] [comprehension] +[comprehension filepath] +[X] [#Foo] [complete] +[complete A] [complete x] [complete run] [complete run a] @@ -282,20 +285,25 @@ y: Y & Y_1 [incomplete a run] [incomplete a run a] [incomplete b] +[incomplete b A] [incomplete b x] [incomplete b run] [incomplete b run a] [incomplete c] +[incomplete c A] [incomplete c x] [incomplete c run] [incomplete c run a] +[incomplete c A] [incomplete c x2] [incomplete c run2] [incomplete c run2 a] [incomplete d] +[incomplete d A] [incomplete d x] [incomplete d run] [incomplete d run a] +[incomplete d A] [incomplete d x2] [incomplete d run2] [incomplete d run2 a] @@ -303,11 +311,15 @@ y: Y & Y_1 [unresolvedDisjunction #TypePrimitive] [unresolvedDisjunction #TypePrimitive _args] [unresolvedDisjunction #TypePrimitive _args required] +[unresolvedDisjunction #TypePrimitive Args] +[unresolvedDisjunction #TypePrimitive Args required] [unresolvedDisjunction #TypePrimitive "*"] [unresolvedDisjunction #TypeBool] [unresolvedDisjunction #TypeBool default] [unresolvedDisjunction #TypeBool _args] [unresolvedDisjunction #TypeBool _args required] +[unresolvedDisjunction #TypeBool Args] +[unresolvedDisjunction #TypeBool Args required] [files] [cfgs] [cfgs 0] @@ -320,23 +332,38 @@ y: Y & Y_1 [scoped _args] [scoped _args required] [scoped direct] +[scoped direct Args] +[scoped direct Args required] [scoped direct a] [scoped embed1] +[scoped embed1 Args] +[scoped embed1 Args required] [scoped embed1 a] [scoped embed2] +[scoped embed2 Args] +[scoped embed2 Args required] [scoped embed2 a] [scoped list] +[scoped list Args] +[scoped list Args required] [scoped list a] [scoped list a 0] [scoped listStruct] +[scoped listStruct Args] +[scoped listStruct Args required] [scoped listStruct a] [scoped listStruct a 0] [scoped listStruct a 0 a] [scoped listEmbed] +[scoped listEmbed Args] +[scoped listEmbed Args required] [scoped listEmbed a] [scoped listEmbed a 0] [x] +[Y] [y] +[Y] +[filepath] -- out/value -- == Simplified { @@ -401,17 +428,17 @@ y: Y & Y_1 } } b: { - let A_1 = run.a - x: "a \(A_1) z" + let A = run.a + x: "a \(A) z" run: { a: string } } c: { - let A_2 = run.a - let A_3 = run2.a - x: "a \(A_2) z" - x2: "a \(A_3) z" + let A_1 = run.a + let A_2 = run2.a + x: "a \(A_1) z" + x2: "a \(A_2) z" run: { a: string } @@ -420,10 +447,10 @@ y: Y & Y_1 } } d: { - let A_4 = run.a - let A_8 = run2.a - x: "a \(A_4) z" - x2: "a \(A_8) z" + let A_3 = run.a + let A_4 = run2.a + x: "a \(A_3) z" + x2: "a \(A_4) z" run: { a: string } @@ -502,17 +529,17 @@ y: Y & Y_1 } } b: { - let A_1 = run.a - x: "a \(A_1) z" + let A = run.a + x: "a \(A) z" run: { a: string } } c: { - let A_2 = run.a - let A_3 = run2.a - x: "a \(A_2) z" - x2: "a \(A_3) z" + let A_1 = run.a + let A_2 = run2.a + x: "a \(A_1) z" + x2: "a \(A_2) z" run: { a: string } @@ -521,10 +548,10 @@ y: Y & Y_1 } } d: { - let A_4 = run.a - let A_8 = run2.a - x: "a \(A_4) z" - x2: "a \(A_8) z" + let A_3 = run.a + let A_4 = run2.a + x: "a \(A_3) z" + x2: "a \(A_4) z" run: { a: string } @@ -541,11 +568,11 @@ y: Y & Y_1 "*": {} } #TypeBool: { - let Args_1 = _args + let Args = _args _args: { required: bool } - if !Args_1.required { + if !Args.required { // `default` sets the default value. default: bool | null } @@ -555,7 +582,7 @@ y: Y & Y_1 } == Final { - comprehension: _|_ // invalid interpolation: invalid interpolation: comprehension: undefined field: name + comprehension: _|_ // invalid interpolation: cycle error complete: { x: "a foo z" run: { @@ -571,7 +598,7 @@ y: Y & Y_1 name: "two" } }] - files: _|_ // invalid interpolation: invalid interpolation: files: undefined field: name (and 3 more errors) + files: _|_ // invalid interpolation: cycle error (and 3 more errors) scoped: { direct: { a: 1 @@ -699,17 +726,17 @@ y: Y & Y_1 } } b: { - let A_1 = run.a - x: "a \(A_1) z" + let A = run.a + x: "a \(A) z" run: { a: string } } c: { - let A_2 = run.a - let A_3 = run2.a - x: "a \(A_2) z" - x2: "a \(A_3) z" + let A_1 = run.a + let A_2 = run2.a + x: "a \(A_1) z" + x2: "a \(A_2) z" run: { a: string } @@ -718,10 +745,10 @@ y: Y & Y_1 } } d: { - let A_4 = run.a - let A_8 = run2.a - x: "a \(A_4) z" - x2: "a \(A_8) z" + let A_3 = run.a + let A_4 = run2.a + x: "a \(A_3) z" + x2: "a \(A_4) z" run: { a: string } @@ -738,11 +765,11 @@ y: Y & Y_1 "*": {} } #TypeBool: { - let Args_1 = _args + let Args = _args _args: { required: bool } - if !Args_1.required { + if !Args.required { // `default` sets the default value. default: bool | null } @@ -814,17 +841,17 @@ y: Y & Y_1 } } b: { - let A_1 = run.a - x: "a \(A_1) z" + let A = run.a + x: "a \(A) z" run: { a: string } } c: { - let A_2 = run.a - let A_3 = run2.a - x: "a \(A_2) z" - x2: "a \(A_3) z" + let A_1 = run.a + let A_2 = run2.a + x: "a \(A_1) z" + x2: "a \(A_2) z" run: { a: string } @@ -833,10 +860,10 @@ y: Y & Y_1 } } d: { - let A_4 = run.a - let A_8 = run2.a - x: "a \(A_4) z" - x2: "a \(A_8) z" + let A_3 = run.a + let A_4 = run2.a + x: "a \(A_3) z" + x2: "a \(A_4) z" run: { a: string } @@ -850,11 +877,11 @@ y: Y & Y_1 "*": {} } #TypeBool: { - let Args_1 = _args + let Args = _args _args: { required: bool } - if !Args_1.required { + if !Args.required { default: bool | null } } diff --git a/internal/core/runtime/imports.go b/internal/core/runtime/imports.go index 7903a3639..4ac292c17 100644 --- a/internal/core/runtime/imports.go +++ b/internal/core/runtime/imports.go @@ -65,6 +65,8 @@ type index struct { importsByPath map[string]*adt.Vertex importsByBuild map[*build.Instance]*adt.Vertex + nextUniqueID uint64 + // These are initialized during Go package initialization time and do not // need to be guarded. builtinPaths map[string]PackageFunc // Full path @@ -73,6 +75,15 @@ type index struct { typeCache sync.Map // map[reflect.Type]evaluated } +func (i *index) getNextUniqueID() uint64 { + // TODO: use atomic increment instead. + i.lock.Lock() + i.nextUniqueID++ + x := i.nextUniqueID + i.lock.Unlock() + return x +} + func newIndex() *index { i := &index{ imports: map[*adt.Vertex]*build.Instance{}, diff --git a/internal/core/runtime/index.go b/internal/core/runtime/index.go index a50e37808..edfdec014 100644 --- a/internal/core/runtime/index.go +++ b/internal/core/runtime/index.go @@ -29,6 +29,10 @@ func (r *Runtime) StringToIndex(s string) int64 { return getKey(s) } +func (r *Runtime) NextUniqueID() uint64 { + return r.index.getNextUniqueID() +} + func (r *Runtime) LabelStr(l adt.Feature) string { return l.IdentString(r) } diff --git a/internal/core/subsume/structural.go b/internal/core/subsume/structural.go index 450a03e18..e91498e9f 100644 --- a/internal/core/subsume/structural.go +++ b/internal/core/subsume/structural.go @@ -58,9 +58,6 @@ func (s *subsumer) node(env *adt.Environment, up int32) *adt.Vertex { } func (s *subsumer) structural(a, b adt.Conjunct) bool { - if y, ok := b.Expr().(*adt.LetReference); ok { - return s.conjunct(a, s.c(b.Env, y.X)) - } if isBottomConjunct(b) { return true } @@ -99,7 +96,11 @@ func (s *subsumer) structural(a, b adt.Conjunct) bool { } case *adt.LetReference: - return s.conjunct(s.c(a.Env, x.X), b) + if y, ok := b.Elem().(*adt.LetReference); ok && x.Label == y.Label { + if s.node(a.Env, x.UpCount) == s.node(b.Env, y.UpCount) { + return true + } + } case *adt.SelectorExpr: if y, ok := a.Elem().(*adt.SelectorExpr); ok && diff --git a/internal/core/validate/validate.go b/internal/core/validate/validate.go index 132c216df..aaf469164 100644 --- a/internal/core/validate/validate.go +++ b/internal/core/validate/validate.go @@ -98,6 +98,9 @@ func (v *validator) validate(x *adt.Vertex) { } for _, a := range x.Arcs { + if a.Label.IsLet() { + continue + } if !v.AllErrors && v.err != nil { break } diff --git a/internal/core/walk/walk.go b/internal/core/walk/walk.go index f58ddfc4a..f163820f6 100644 --- a/internal/core/walk/walk.go +++ b/internal/core/walk/walk.go @@ -31,21 +31,11 @@ func Features(x adt.Expr, f func(label adt.Feature, src adt.Node)) { } type Visitor struct { - // TODO: lets really should be special fields - letDone map[adt.Expr]bool - Feature func(f adt.Feature, src adt.Node) Before func(adt.Node) bool } -func (w *Visitor) init() { - if w.letDone == nil { - w.letDone = map[adt.Expr]bool{} - } -} - func (w *Visitor) Elem(x adt.Elem) { - w.init() w.node(x) } @@ -92,13 +82,6 @@ func (w *Visitor) node(n adt.Node) { case *adt.LetReference: w.feature(x.Label, x) - if w.letDone == nil { - w.letDone = map[adt.Expr]bool{} - } - if !w.letDone[x.X] { - w.letDone[x.X] = true - w.node(x.X) - } case *adt.SelectorExpr: w.node(x.X) @@ -155,6 +138,10 @@ func (w *Visitor) node(n adt.Node) { w.feature(x.Label, x) w.node(x.Value) + case *adt.LetField: + w.feature(x.Label, x) + w.node(x.Value) + case *adt.BulkOptionalField: w.node(x.Filter) w.node(x.Value)