From 36bd6562f7ba43afbd850abfa565da5576117154 Mon Sep 17 00:00:00 2001 From: Marcel van Lohuizen Date: Wed, 3 Aug 2022 17:51:45 +0900 Subject: [PATCH] internal/core: implement let as fields This makes it easier to get upCounts aligned, as they will follow the same paradigm as fields. It also allows debug information to be shown as to where lets are added in the tree. Mote, however, that lets do not always behave as fields: even if the same let is added into the same struct multiple times, the various instance should be treated as unique fields and should not be unified. For this purpose, the old cache is still used. Fixes #1828 Signed-off-by: Marcel van Lohuizen Change-Id: Ifa459a2f0f16b2cf57c72c45a1f7b906326527b7 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/543362 Reviewed-by: Roger Peppe TryBot-Result: CUEcueckoo --- .../basicrewrite/aliases/aliases.txtar | 8 +- cue/testdata/builtins/closed.txtar | 13 +- cue/testdata/compile/erralias.txtar | 1 + cue/testdata/compile/labels.txtar | 3 +- cue/testdata/compile/let.txtar | 47 ++++- cue/testdata/compile/scope.txtar | 15 +- cue/testdata/comprehensions/issue837.txtar | 38 +++- cue/testdata/comprehensions/nested.txtar | 6 +- cue/testdata/cycle/evaluate.txtar | 7 +- cue/testdata/cycle/issue990.txtar | 87 +++++++++- cue/testdata/cycle/structural.txtar | 7 +- cue/testdata/eval/let.txtar | 90 +++++++++- cue/testdata/eval/letjoin.txtar | 49 +++++- .../049_alias_reuse_in_nested_scope.txtar | 20 ++- cue/testdata/references/let.txtar | 79 +++++++-- cue/testdata/references/letcycle.txtar | 31 +++- .../035_excluded_embedding_from_closing.txtar | 11 +- cue/types.go | 3 + internal/core/adt/adt.go | 3 + internal/core/adt/composite.go | 10 +- internal/core/adt/context.go | 15 ++ internal/core/adt/eval.go | 13 +- internal/core/adt/eval_test.go | 3 +- internal/core/adt/expr.go | 70 ++++++-- internal/core/adt/expr_test.go | 1 + internal/core/adt/feature.go | 38 +++- internal/core/compile/compile.go | 44 +++-- internal/core/compile/compile_test.go | 3 +- internal/core/debug/compact.go | 25 ++- internal/core/debug/debug.go | 39 ++++- internal/core/dep/dep.go | 19 +- internal/core/dep/mixed.go | 3 + internal/core/export/adt.go | 9 + internal/core/export/export.go | 14 +- internal/core/export/export_test.go | 31 +--- internal/core/export/expr.go | 7 + internal/core/export/self_test.go | 1 + .../core/export/testdata/main/alias.txtar | 1 + internal/core/export/testdata/main/let.txtar | 163 ++++++++++-------- internal/core/runtime/imports.go | 11 ++ internal/core/runtime/index.go | 4 + internal/core/subsume/structural.go | 9 +- internal/core/validate/validate.go | 3 + internal/core/walk/walk.go | 21 +-- 44 files changed, 852 insertions(+), 223 deletions(-) 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) -- 2.51.2