Skip to content

Commit b886b0f

Browse files
committed
internal/core/compile: fix mutual dependent let clauses
Allow let clauses to refer to each other, as long as they don't introduce a cycle. Change-Id: Ia7f2aa8188bb29e308fce8df8c0a81f8838b65bf Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7601 Reviewed-by: CUE cueckoo <[email protected]> Reviewed-by: Marcel van Lohuizen <[email protected]>
1 parent aa87887 commit b886b0f

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

cue/testdata/compile/let.txtar

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
-- in.cue --
2+
a: {
3+
let X = Y
4+
let Y = c
5+
6+
b: X
7+
c: 5
8+
}
9+
10+
b: {
11+
let X = Y
12+
let Y = X
13+
14+
b: X
15+
c: 5
16+
}
17+
-- out/compile --
18+
--- in.cue
19+
{
20+
a: {
21+
b: 〈0;let X〉
22+
c: 5
23+
}
24+
b: {
25+
b: 〈0;let X〉
26+
c: 5
27+
}
28+
}
29+
-- out/eval --
30+
Errors:
31+
b.let[]: cyclic references in let clause or alias:
32+
./in.cue:10:13
33+
34+
Result:
35+
(_|_){
36+
// [eval]
37+
a: (struct){
38+
b: (int){ 5 }
39+
c: (int){ 5 }
40+
}
41+
b: (_|_){
42+
// [eval]
43+
b: (_|_){
44+
// [eval] b.let[]: cyclic references in let clause or alias:
45+
// ./in.cue:10:13
46+
}
47+
c: (int){ 5 }
48+
}
49+
}

internal/core/adt/expr.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,9 @@ func (x *LetReference) Source() ast.Node {
553553
func (x *LetReference) resolve(c *OpContext) *Vertex {
554554
e := c.Env(x.UpCount)
555555
label := e.Vertex.Label
556+
if x.X == nil {
557+
panic("nil expression")
558+
}
556559
// Anonymous arc.
557560
return &Vertex{Parent: nil, Label: label, Conjuncts: []Conjunct{{e, x.X, 0}}}
558561
}

internal/core/compile/compile.go

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,11 @@ type frame struct {
145145
}
146146

147147
type aliasEntry struct {
148-
expr adt.Expr
149-
source ast.Node
150-
used bool
148+
label labeler
149+
srcExpr ast.Expr
150+
expr adt.Expr
151+
source ast.Node
152+
used bool
151153
}
152154

153155
func (c *compiler) insertAlias(id *ast.Ident, a aliasEntry) *adt.Bottom {
@@ -178,6 +180,8 @@ func (c *compiler) updateAlias(id *ast.Ident, expr adt.Expr) {
178180

179181
x := m[id.Name]
180182
x.expr = expr
183+
x.label = nil
184+
x.srcExpr = nil
181185
m[id.Name] = x
182186
}
183187

@@ -192,6 +196,21 @@ func (c compiler) lookupAlias(k int, id *ast.Ident) aliasEntry {
192196
return aliasEntry{expr: err}
193197
}
194198

199+
switch {
200+
case entry.label != nil:
201+
if entry.srcExpr == nil {
202+
entry.expr = c.errf(id, "cyclic references in let clause or alias")
203+
break
204+
}
205+
206+
src := entry.srcExpr
207+
entry.srcExpr = nil // mark to allow detecting cycles
208+
m[name] = entry
209+
210+
entry.expr = c.labeledExpr(nil, entry.label, src)
211+
entry.label = nil
212+
}
213+
195214
entry.used = true
196215
m[name] = entry
197216
return entry
@@ -483,11 +502,19 @@ func (c *compiler) markAlias(d ast.Decl) {
483502
}
484503

485504
case *ast.LetClause:
486-
a := aliasEntry{source: x}
505+
a := aliasEntry{
506+
label: (*letScope)(x),
507+
srcExpr: x.Expr,
508+
source: x,
509+
}
487510
c.insertAlias(x.Ident, a)
488511

489512
case *ast.Alias:
490-
a := aliasEntry{source: x}
513+
a := aliasEntry{
514+
label: (*deprecatedAliasScope)(x),
515+
srcExpr: x.Expr,
516+
source: x,
517+
}
491518
c.insertAlias(x.Ident, a)
492519
}
493520
}
@@ -609,14 +636,12 @@ func (c *compiler) addLetDecl(d ast.Decl) {
609636
// Cache the parsed expression. Creating a unique expression for each
610637
// reference allows the computation to be shared given that we don't
611638
// have fields for expressions. This, in turn, prevents exponential
612-
// blowup in x2: x1+x1, x3: x2+x2, ... patterns.
613-
639+
// blowup in x2: x1+x1, x3: x2+x2, ... patterns.
614640
expr := c.labeledExpr(nil, (*letScope)(x), x.Expr)
615641
c.updateAlias(x.Ident, expr)
616642

617643
case *ast.Alias:
618644
// TODO(legacy): deprecated, remove this use of Alias
619-
620645
expr := c.labeledExpr(nil, (*deprecatedAliasScope)(x), x.Expr)
621646
c.updateAlias(x.Ident, expr)
622647
}
@@ -733,11 +758,14 @@ func (c *compiler) labeledExpr(f *ast.Field, lab labeler, expr ast.Expr) adt.Exp
733758
if c.stack[k].field != nil {
734759
panic("expected nil field")
735760
}
761+
saved := c.stack[k]
762+
736763
c.stack[k].label = lab
737764
c.stack[k].field = f
765+
738766
value := c.expr(expr)
739-
c.stack[k].label = nil
740-
c.stack[k].field = nil
767+
768+
c.stack[k] = saved
741769
return value
742770
}
743771

0 commit comments

Comments
 (0)