Skip to content

Commit 5414b41

Browse files
committed
internal/core/adt: eagerly evaluate let upon lookup
As the TODO in unify.go suggestes (see where code is edited), process needs to finalize, instead of yield. However, this gives incorrect results for some tests. It is most important, however, to to this for let values, as let values do not always have a guaranteed evaluation path (for instance, they may be a cached version of themselves). We therefore only force a finalize when the value is a let. To detect this, we add a "withinLet" field that is similar to nonRooted and anonymous, but just reflecting let values. Fixes #3763 Signed-off-by: Marcel van Lohuizen <[email protected]> Change-Id: Ie84c06a6a96690588ded4a1bbb4f830bb3af5b51 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1211827 TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 8f2ae49 commit 5414b41

File tree

4 files changed

+28
-23
lines changed

4 files changed

+28
-23
lines changed

cue/testdata/comprehensions/issue3763.txtar

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ Disjuncts: 76
104104
〈1;cps〉
105105
},
106106
]
107-
ports: (list){ list }
107+
ports: (#list){
108+
}
108109
}
109110
reduced: (struct){
110111
A: (struct){
@@ -123,7 +124,8 @@ Disjuncts: 76
123124
〈1;x〉
124125
},
125126
]
126-
B: (list){ list }
127+
B: (#list){
128+
}
127129
}
128130
}
129131
-- diff/-out/evalalpha<==>+out/eval --
@@ -154,17 +156,7 @@ diff old new
154156
}
155157
}
156158
}
157-
@@ -58,8 +43,7 @@
158-
〈1;cps〉
159-
},
160-
]
161-
- ports: (#list){
162-
- }
163-
+ ports: (list){ list }
164-
}
165-
reduced: (struct){
166-
A: (struct){
167-
@@ -73,18 +57,11 @@
159+
@@ -73,17 +58,11 @@
168160
y?: (int){ int }
169161
}
170162
}
@@ -179,16 +171,14 @@ diff old new
179171
- y?: (int){ int }
180172
- }
181173
- }
182-
- B: (#list){
183-
- }
184174
+ let Z#3multi = [
185175
+ for _, x in 〈2;A〉 {
186176
+ 〈1;x〉
187177
+ },
188178
+ ]
189-
+ B: (list){ list }
179+
B: (#list){
180+
}
190181
}
191-
}
192182
-- diff/todo/p1 --
193183
full.ports|reduced.B: incomplete value (list)
194184
-- out/eval --

internal/core/adt/composite.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,16 @@ type Vertex struct {
219219
// rooted.
220220
nonRooted bool // indicates that there is no path from the root of the tree.
221221

222-
// anonymous indicates that this Vertex is being computed within a
222+
// anonymous indicates that this Vertex is being computed without an
223223
// addressable context, or in other words, a context for which there is
224-
// a path from the root of the file. Typically, the only addressable
224+
// np path from the root of the file. Typically, the only addressable
225225
// contexts are fields. Examples of fields that are not addressable are
226226
// the for source of comprehensions and let fields or let clauses.
227227
anonymous bool
228228

229+
// withinLet indicate that this vertex is a let value.
230+
withinLet bool
231+
229232
// hasPendingArc is set if this Vertex has a void arc (e.g. for comprehensions)
230233
hasPendingArc bool
231234

@@ -1356,6 +1359,7 @@ func (v *Vertex) GetArc(c *OpContext, f Feature, t ArcType) (arc *Vertex, isNew
13561359
ArcType: t,
13571360
nonRooted: v.IsDynamic || v.Label.IsLet() || v.nonRooted,
13581361
anonymous: v.anonymous || v.Label.IsLet(),
1362+
withinLet: v.withinLet || v.Label.IsLet(),
13591363
}
13601364
v.Arcs = append(v.Arcs, arc)
13611365
if t == ArcPending {

internal/core/adt/fields.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ func (n *nodeContext) getArc(f Feature, mode ArcType) (arc *Vertex, isNew bool)
169169
ArcType: mode,
170170
nonRooted: v.IsDynamic || v.nonRooted,
171171
anonymous: v.anonymous || v.Label.IsLet(),
172+
withinLet: v.withinLet || v.Label.IsLet(),
172173
}
173174
if n.scheduler.frozen&fieldSetKnown != 0 {
174175
b := n.ctx.NewErrf("adding field %v not allowed as field set was already referenced", f)

internal/core/adt/unify.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,7 @@ func (n *nodeContext) completeAllArcs(needs condition, mode runMode, checkTypos
555555
// of range in case the Arcs grows during processing.
556556
for arcPos := 0; arcPos < len(n.node.Arcs); arcPos++ {
557557
a := n.node.Arcs[arcPos]
558+
// TODO: Consider skipping lets.
558559

559560
if !a.unify(n.ctx, needs, mode, checkTypos) {
560561
success = false
@@ -838,20 +839,29 @@ func (v *Vertex) lookup(c *OpContext, pos token.Pos, f Feature, flags combinedFl
838839
if arc.ArcType == ArcPending {
839840
return arcReturn
840841
}
841-
goto handleArcType
842842

843843
case yield:
844844
arcState.process(needs, yield)
845845
// continue processing, as successful processing may still result
846846
// in an invalid field.
847847

848848
case finalize:
849-
// TODO: should we try to use finalize? Using it results in errors and this works. It would be more principled, though.
850-
arcState.process(needs, yield)
849+
// TODO: we should try to always use finalize? Using it results in
850+
// errors. For now we only use it for let values. Let values are
851+
// not normally finalized (they may be cached) and as such might
852+
// not trigger the usual unblocking. Force unblocking may cause
853+
// some values to be remain unevaluated.
854+
if arc.withinLet {
855+
arcState.process(needs, finalize)
856+
if arc.ArcType == ArcPending {
857+
arc.ArcType = ArcNotPresent
858+
}
859+
} else {
860+
arcState.process(needs, yield)
861+
}
851862
}
852863
}
853864

854-
handleArcType:
855865
switch arc.ArcType {
856866
case ArcMember, ArcRequired:
857867
return arcReturn

0 commit comments

Comments
 (0)