internal/core: support field value aliases
Support aliases of the form:
a: X=b
This implementation also allows for general alias values,
but these have not yet been implemented as alias
declarations are still being deprecated.
Issue #620
Issue #380
Change-Id: Ide4c888bea187042898e8123480a1cfeb909914c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9543
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/cue/ast/astutil/resolve.go b/cue/ast/astutil/resolve.go
index 5c832c2..68155e4 100644
--- a/cue/ast/astutil/resolve.go
+++ b/cue/ast/astutil/resolve.go
@@ -54,9 +54,12 @@
// Let Clause File/Struct LetClause
// Alias declaration File/Struct Alias (deprecated)
// Illegal Reference File/Struct
+// Value
+// X in a: X=y Field Alias
// Fields
// X in X: y File/Struct Expr (y)
// X in X=x: y File/Struct Field
+// X in X=(x): y File/Struct Field
// X in X="\(x)": y File/Struct Field
// X in [X=x]: y Field Expr (x)
// X in X=[x]: y Field Field
@@ -143,7 +146,12 @@
// default:
name, isIdent, _ := ast.LabelName(label)
if isIdent {
- s.insert(name, x.Value, x)
+ v := x.Value
+ // Avoid interpreting value aliases at this point.
+ if a, ok := v.(*ast.Alias); ok {
+ v = a.Expr
+ }
+ s.insert(name, v, x)
}
case *ast.LetClause:
name, isIdent, _ := ast.LabelName(x.Ident)
@@ -335,9 +343,16 @@
}
}
- if x.Value != nil {
+ if n := x.Value; n != nil {
+ if alias, ok := x.Value.(*ast.Alias); ok {
+ // TODO: this should move into Before once decl attributes
+ // have been fully deprecated and embed attributes are introduced.
+ s = newScope(s.file, s, x, nil)
+ s.insert(alias.Ident.Name, alias, x)
+ n = alias.Expr
+ }
s.inField = true
- walk(s, x.Value)
+ walk(s, n)
s.inField = false
}
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 0a87b2b..abf4d91 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -846,73 +846,62 @@
this := &ast.Field{Label: nil}
m := this
- for i := 0; ; i++ {
- tok := p.tok
+ tok := p.tok
- label, expr, decl, ok := p.parseLabel(false)
- if decl != nil {
- return decl
+ label, expr, decl, ok := p.parseLabel(false)
+ if decl != nil {
+ return decl
+ }
+ m.Label = label
+
+ if !ok {
+ if expr == nil {
+ expr = p.parseRHS()
}
- m.Label = label
-
- if !ok {
- if expr == nil {
- expr = p.parseRHS()
- }
- if a, ok := expr.(*ast.Alias); ok {
- if i > 0 {
- p.errorExpected(p.pos, "label or ':'")
- return &ast.BadDecl{From: pos, To: p.pos}
- }
- p.consumeDeclComma()
- return a
- }
- e := &ast.EmbedDecl{Expr: expr}
+ if a, ok := expr.(*ast.Alias); ok {
p.consumeDeclComma()
- return e
+ return a
}
+ e := &ast.EmbedDecl{Expr: expr}
+ p.consumeDeclComma()
+ return e
+ }
- if p.tok == token.OPTION {
- m.Optional = p.pos
- p.next()
+ if p.tok == token.OPTION {
+ m.Optional = p.pos
+ p.next()
+ }
+
+ // TODO: consider disallowing comprehensions with more than one label.
+ // This can be a bit awkward in some cases, but it would naturally
+ // enforce the proper style that a comprehension be defined in the
+ // smallest possible scope.
+ // allowComprehension = false
+
+ switch p.tok {
+ case token.COLON, token.ISA:
+ case token.COMMA:
+ p.expectComma() // sync parser.
+ fallthrough
+
+ case token.RBRACE, token.EOF:
+ if a, ok := expr.(*ast.Alias); ok {
+ p.assertV0(p.pos, 1, 3, `old-style alias; use "let X = expr"`)
+
+ return a
}
-
- if p.tok == token.COLON || p.tok == token.ISA {
- break
+ switch tok {
+ case token.IDENT, token.LBRACK, token.LPAREN,
+ token.STRING, token.INTERPOLATION,
+ token.NULL, token.TRUE, token.FALSE,
+ token.FOR, token.IF, token.LET, token.IN:
+ return &ast.EmbedDecl{Expr: expr}
}
+ fallthrough
- // TODO: consider disallowing comprehensions with more than one label.
- // This can be a bit awkward in some cases, but it would naturally
- // enforce the proper style that a comprehension be defined in the
- // smallest possible scope.
- // allowComprehension = false
-
- switch p.tok {
- case token.COMMA:
- p.expectComma() // sync parser.
- fallthrough
-
- case token.RBRACE, token.EOF:
- if i == 0 {
- if a, ok := expr.(*ast.Alias); ok {
- p.assertV0(p.pos, 1, 3, `old-style alias; use "let X = expr"`)
-
- return a
- }
- switch tok {
- case token.IDENT, token.LBRACK, token.LPAREN,
- token.STRING, token.INTERPOLATION,
- token.NULL, token.TRUE, token.FALSE,
- token.FOR, token.IF, token.LET, token.IN:
- return &ast.EmbedDecl{Expr: expr}
- }
- }
- fallthrough
-
- default:
- p.errorExpected(p.pos, "label or ':'")
- return &ast.BadDecl{From: pos, To: p.pos}
- }
+ default:
+ p.errorExpected(p.pos, "label or ':'")
+ return &ast.BadDecl{From: pos, To: p.pos}
}
m.TokenPos = p.pos
@@ -936,9 +925,6 @@
if expr == nil {
expr = p.parseRHS()
}
- if a, ok := expr.(*ast.Alias); ok {
- p.errf(expr.Pos(), "alias %q not allowed as value", debugStr(a.Ident))
- }
m.Value = expr
break
}
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index d752a2c..36d0f7b 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -330,6 +330,16 @@
}`,
"{[foo=_]: {a: int}, a: {a: 1}}",
}, {
+ "value alias",
+ `
+ {
+ a: X=foo
+ b: Y={foo}
+ c: d: e: X=5
+ }
+ `,
+ `{a: X=foo, b: Y={foo}, c: {d: {e: X=5}}}`,
+ }, {
"dynamic labels",
`{
(x): a: int
@@ -567,7 +577,7 @@
in: `
a: int=>2
`,
- out: "a: int=>2\nalias \"int\" not allowed as value",
+ out: "a: int=>2",
}, {
desc: "struct comments",
in: `
diff --git a/cue/testdata/references/value.txtar b/cue/testdata/references/value.txtar
new file mode 100644
index 0000000..8450601
--- /dev/null
+++ b/cue/testdata/references/value.txtar
@@ -0,0 +1,46 @@
+-- in.cue --
+structShorthand: X={b: 3, c: X.b}
+
+// Note that X and Y are subtly different, as they have different bindings:
+// one binds to the field, the other to the value. In this case, that does not
+// make a difference.
+fieldAndValue: X=foo: Y={ 3, #sum: X + Y }
+
+valueCycle: b: X=3+X
+
+-- out/eval --
+(struct){
+ structShorthand: (struct){
+ b: (int){ 3 }
+ c: (int){ 3 }
+ }
+ fieldAndValue: (struct){
+ foo: (int){
+ 3
+ #sum: (int){ 6 }
+ }
+ }
+ valueCycle: (struct){
+ b: (_|_){
+ // [cycle] cycle error:
+ // ./in.cue:8:18
+ }
+ }
+}
+-- out/compile --
+--- in.cue
+{
+ structShorthand: {
+ b: 3
+ c: 〈1〉.b
+ }
+ fieldAndValue: {
+ foo: {
+ 3
+ #sum: (〈1;foo〉 + 〈1〉)
+ }
+ }
+ valueCycle: {
+ b: (3 + 〈0〉)
+ }
+}
diff --git a/internal/core/adt/adt.go b/internal/core/adt/adt.go
index e77df49..9b6ec79 100644
--- a/internal/core/adt/adt.go
+++ b/internal/core/adt/adt.go
@@ -206,6 +206,7 @@
func (*NodeLink) expr() {}
func (*FieldReference) expr() {}
+func (*ValueReference) expr() {}
func (*LabelReference) expr() {}
func (*DynamicReference) expr() {}
func (*ImportReference) expr() {}
@@ -281,6 +282,8 @@
func (*NodeLink) elemNode() {}
func (*FieldReference) declNode() {}
func (*FieldReference) elemNode() {}
+func (*ValueReference) declNode() {}
+func (*ValueReference) elemNode() {}
func (*LabelReference) declNode() {}
func (*LabelReference) elemNode() {}
func (*DynamicReference) declNode() {}
@@ -338,6 +341,7 @@
func (*BoundExpr) node() {}
func (*NodeLink) node() {}
func (*FieldReference) node() {}
+func (*ValueReference) node() {}
func (*LabelReference) node() {}
func (*DynamicReference) node() {}
func (*ImportReference) node() {}
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 67933fa..f508eac 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -714,6 +714,31 @@
return c.lookup(n, pos, x.Label, state)
}
+// A ValueReference represents a lexical reference to a value.
+//
+// a: X=b
+//
+type ValueReference struct {
+ Src *ast.Ident
+ UpCount int32
+ Label Feature // for informative purposes
+}
+
+func (x *ValueReference) Source() ast.Node {
+ if x.Src == nil {
+ return nil
+ }
+ return x.Src
+}
+
+func (x *ValueReference) resolve(c *OpContext, state VertexStatus) *Vertex {
+ if x.UpCount == 0 {
+ return c.vertex
+ }
+ n := c.relNode(x.UpCount - 1)
+ return n
+}
+
// A LabelReference refers to the string or integer value of a label.
//
// [X=Pattern]: b: X
diff --git a/internal/core/compile/compile.go b/internal/core/compile/compile.go
index eefe2af..f2c8f12 100644
--- a/internal/core/compile/compile.go
+++ b/internal/core/compile/compile.go
@@ -363,6 +363,7 @@
// X in [X=x]: y Scope: Field Node: Expr (x)
// X in X=[x]: y Scope: Field Node: Field
+ // X in x: X=y Scope: Field Node: Alias
if f, ok := n.Scope.(*ast.Field); ok {
upCount := int32(0)
@@ -379,13 +380,22 @@
UpCount: upCount,
}
- if f, ok := n.Node.(*ast.Field); ok {
+ switch f := n.Node.(type) {
+ case *ast.Field:
_ = c.lookupAlias(k, f.Label.(*ast.Alias).Ident) // mark as used
return &adt.DynamicReference{
Src: n,
UpCount: upCount,
Label: label,
}
+
+ case *ast.Alias:
+ _ = c.lookupAlias(k, f.Ident) // mark as used
+ return &adt.ValueReference{
+ Src: n,
+ UpCount: upCount,
+ Label: c.label(f.Ident),
+ }
}
return label
}
@@ -408,11 +418,18 @@
n.Name)
}
- switch n.Node.(type) {
+ if n.Scope == nil {
+ // Package.
+ // Should have been handled above.
+ return c.errf(n, "unresolved identifier %v", n.Name)
+ }
+
+ switch f := n.Node.(type) {
// Local expressions
case *ast.LetClause:
entry := c.lookupAlias(k, n)
+ // let x = y
return &adt.LetReference{
Src: n,
UpCount: upCount,
@@ -420,19 +437,12 @@
X: entry.expr,
}
- // TODO: handle new-style aliases
- }
+ // TODO: handle new-style aliases
- if n.Scope == nil {
- // Package.
- // Should have been handled above.
- panic("unreachable") // Or direct ancestor node?
- }
-
- // X=x: y
- // X=(x): y
- // X="\(x)": y
- if f, ok := n.Node.(*ast.Field); ok {
+ case *ast.Field:
+ // X=x: y
+ // X=(x): y
+ // X="\(x)": y
a, ok := f.Label.(*ast.Alias)
if !ok {
return c.errf(n, "illegal reference %s", n.Name)
@@ -534,7 +544,16 @@
}
}
- value := c.labeledExpr(x, (*fieldLabel)(x), x.Value)
+ v := x.Value
+ var value adt.Expr
+ if a, ok := v.(*ast.Alias); ok {
+ c.pushScope(nil, 0, a)
+ c.insertAlias(a.Ident, aliasEntry{source: a})
+ value = c.labeledExpr(x, (*fieldLabel)(x), a.Expr)
+ c.popScope()
+ } else {
+ value = c.labeledExpr(x, (*fieldLabel)(x), v)
+ }
switch l := lab.(type) {
case *ast.Ident, *ast.BasicLit:
@@ -607,7 +626,7 @@
// Handled in addLetDecl.
case *ast.LetClause:
- // case: *ast.Alias:
+ // case: *ast.Alias: // TODO(value aliases)
case *ast.CommentGroup:
// Nothing to do for a free-floating comment group.
diff --git a/internal/core/debug/compact.go b/internal/core/debug/compact.go
index 6a390aa..382c2f3 100644
--- a/internal/core/debug/compact.go
+++ b/internal/core/debug/compact.go
@@ -179,6 +179,9 @@
case *adt.FieldReference:
w.label(x.Label)
+ case *adt.ValueReference:
+ w.label(x.Label)
+
case *adt.LabelReference:
if x.Src == nil {
w.string("LABEL")
diff --git a/internal/core/debug/debug.go b/internal/core/debug/debug.go
index 997a3dc..9e5783b 100644
--- a/internal/core/debug/debug.go
+++ b/internal/core/debug/debug.go
@@ -359,6 +359,11 @@
w.label(x.Label)
w.string(closeTuple)
+ case *adt.ValueReference:
+ w.string(openTuple)
+ w.string(strconv.Itoa(int(x.UpCount)))
+ w.string(closeTuple)
+
case *adt.LabelReference:
w.string(openTuple)
w.string(strconv.Itoa(int(x.UpCount)))
diff --git a/internal/core/export/adt.go b/internal/core/export/adt.go
index 1522f58..4cbb49f 100644
--- a/internal/core/export/adt.go
+++ b/internal/core/export/adt.go
@@ -55,7 +55,26 @@
s := &ast.StructLit{}
for _, d := range x.Decls {
- s.Elts = append(s.Elts, e.decl(d))
+ var a *ast.Alias
+ if orig, ok := d.Source().(*ast.Field); ok {
+ if alias, ok := orig.Value.(*ast.Alias); ok {
+ if e.valueAlias == nil {
+ e.valueAlias = map[*ast.Alias]*ast.Alias{}
+ }
+ a = &ast.Alias{Ident: ast.NewIdent(alias.Ident.Name)}
+ e.valueAlias[alias] = a
+ }
+ }
+ decl := e.decl(d)
+
+ if a != nil {
+ if f, ok := decl.(*ast.Field); ok {
+ a.Expr = f.Value
+ f.Value = a
+ }
+ }
+
+ s.Elts = append(s.Elts, decl)
}
return s
@@ -87,6 +106,16 @@
return ident
+ case *adt.ValueReference:
+ name := x.Label.IdentString(e.ctx)
+ if a, ok := x.Src.Node.(*ast.Alias); ok { // Should always pass
+ if b, ok := e.valueAlias[a]; ok {
+ name = b.Ident.Name
+ }
+ }
+ ident := ast.NewIdent(name)
+ return ident
+
case *adt.LabelReference:
// get potential label from Source. Otherwise use X.
f := e.frame(x.UpCount)
diff --git a/internal/core/export/export.go b/internal/core/export/export.go
index b24e7ec..fa72159 100644
--- a/internal/core/export/export.go
+++ b/internal/core/export/export.go
@@ -230,6 +230,7 @@
// unique let expression.
usedFeature map[adt.Feature]adt.Expr
labelAlias map[adt.Expr]adt.Feature
+ valueAlias map[*ast.Alias]*ast.Alias
usedHidden map[string]bool
}
diff --git a/internal/core/export/expr.go b/internal/core/export/expr.go
index 627d57c..1a1f640 100644
--- a/internal/core/export/expr.go
+++ b/internal/core/export/expr.go
@@ -82,7 +82,7 @@
// For a struct, piece out conjuncts that are already values. Those can be
// unified. All other conjuncts are added verbatim.
-func (x *exporter) mergeValues(label adt.Feature, src *adt.Vertex, a []conjunct, orig ...adt.Conjunct) ast.Expr {
+func (x *exporter) mergeValues(label adt.Feature, src *adt.Vertex, a []conjunct, orig ...adt.Conjunct) (expr ast.Expr) {
e := conjuncts{
exporter: x,
@@ -94,6 +94,30 @@
_, saved := e.pushFrame(orig)
defer e.popFrame(saved)
+ // Handle value aliases
+ var valueAlias *ast.Alias
+ for _, c := range a {
+ if f, ok := c.c.Field().Source().(*ast.Field); ok {
+ if a, ok := f.Value.(*ast.Alias); ok {
+ if valueAlias == nil {
+ if e.valueAlias == nil {
+ e.valueAlias = map[*ast.Alias]*ast.Alias{}
+ }
+ name := a.Ident.Name
+ name = e.uniqueAlias(name)
+ valueAlias = &ast.Alias{Ident: ast.NewIdent(name)}
+ }
+ e.valueAlias[a] = valueAlias
+ }
+ }
+ }
+ defer func() {
+ if valueAlias != nil {
+ valueAlias.Expr = expr
+ expr = valueAlias
+ }
+ }()
+
for _, c := range a {
e.top().upCount = c.up
x := c.c.Expr()
diff --git a/internal/core/export/testdata/alias.txtar b/internal/core/export/testdata/alias.txtar
index 5ce1676..ac1a7ab 100644
--- a/internal/core/export/testdata/alias.txtar
+++ b/internal/core/export/testdata/alias.txtar
@@ -4,91 +4,406 @@
// For now this is better than panicking.
-- x.cue --
-X="a-b": 4
+fieldAlias: simple: {
+ X="a-b": 4
+ foo: X
-foo: X
-bar?: Y
+ bar?: Y
-Y="a-c": 5
+ Y="a-c": 5
+}
+
+valueAlias: merge: {
+ // Merge fields, rename alias to avoid conflict.
+ // TODO: merged values can still be simplified.
+ value: X={ #value: X.b, b: 2 }
+ value: Y={ #value: Y.b, b: 2, v: X: 3 }
+}
+
+valueAlias: selfRef: struct: {
+ a: b: X={ #foo: X.b, b: 2 }
+}
+
+valueAlias: selfRefValue: struct: {
+ // Note: this resolves to a cycle error, which is considered
+ // to be equal to "incomplete". As a result, in case of
+ // non-final evaluation, reference will remain. This is not
+ // an issue exclusive to value aliases, and falls within the
+ // range of what is acceptable for now.
+ // TODO: solve this issue.
+ a: X=or(X)
+}
+
+valueAlias: selfRefValue: pattern: {
+ // this triggers the verbatim "adt" path. Note that there
+ // is no need to rename the variable here as the expression
+ // was known to compile and is known to be correct.
+ a: [string]: X=or(X)
+}
-- y.cue --
-baz: 3
-X="d-2": E=[D="cue"]: C="foo\(baz)": {
- name: "xx"
- foo: C.name
- bar: X
- baz: D
- qux: E
+fieldAlias: cross: {
+ baz: 3
+ X="d-2": E=[D="cue"]: C="foo\(baz)": {
+ name: "xx"
+ foo: C.name
+ bar: X
+ baz: D
+ qux: E
+ }
}
-- out/definition --
-
-{
- X="a-b": 4
- foo: X
- bar?: Y
- Y="a-c": 5
-} & {
- baz: 3
- X_1="d-2": {
- E=[D="cue"]: {
- C="foo\(baz)": {
- name: "xx"
- foo: C.name
- bar: X_1
- baz: D
- qux: E
+fieldAlias: {
+ simple: {
+ X_1="a-b": 4
+ foo: X_1
+ bar?: Y_1
+ Y_1="a-c": 5
+ }
+ cross: {
+ baz: 3
+ X_85="d-2": {
+ E=[D="cue"]: {
+ C="foo\(baz)": {
+ name: "xx"
+ foo: C.name
+ bar: X_85
+ baz: D
+ qux: E
+ }
+ }
+ }
+ }
+}
+valueAlias: {
+ merge: {
+ // Merge fields, rename alias to avoid conflict.
+ // TODO: merged values can still be simplified.
+ value: X_BA={
+ #value: X_BA.b & X_BA.b
+ b: 2
+ v: {
+ X: 3
+ }
+ }
+ }
+ selfRef: {
+ struct: {
+ a: {
+ b: X_57C8={
+ #foo: X_57C8.b
+ b: 2
+ }
+ }
+ }
+ }
+ selfRefValue: {
+ struct: {
+ // Note: this resolves to a cycle error, which is considered
+ // to be equal to "incomplete". As a result, in case of
+ // non-final evaluation, reference will remain. This is not
+ // an issue exclusive to value aliases, and falls within the
+ // range of what is acceptable for now.
+ // TODO: solve this issue.
+ a: X_35B7E=or(X_35B7E)
+ }
+ pattern: {
+ // this triggers the verbatim "adt" path. Note that there
+ // is no need to rename the variable here as the expression
+ // was known to compile and is known to be correct.
+ a: {
+ [string]: X=or(X)
}
}
}
}
-- out/doc --
[]
-["a-b"]
-[foo]
-["a-c"]
-[baz]
-["d-2"]
+[fieldAlias]
+[fieldAlias simple]
+[fieldAlias simple "a-b"]
+[fieldAlias simple foo]
+[fieldAlias simple "a-c"]
+[fieldAlias cross]
+[fieldAlias cross baz]
+[fieldAlias cross "d-2"]
+[valueAlias]
+[valueAlias merge]
+[valueAlias merge value]
+- Merge fields, rename alias to avoid conflict.
+TODO: merged values can still be simplified.
+
+[valueAlias merge value #value]
+[valueAlias merge value b]
+[valueAlias merge value v]
+[valueAlias merge value v X]
+[valueAlias selfRef]
+[valueAlias selfRef struct]
+[valueAlias selfRef struct a]
+[valueAlias selfRef struct a b]
+[valueAlias selfRef struct a b #foo]
+[valueAlias selfRef struct a b b]
+[valueAlias selfRefValue]
+[valueAlias selfRefValue struct]
+[valueAlias selfRefValue struct a]
+- Note: this resolves to a cycle error, which is considered
+to be equal to "incomplete". As a result, in case of
+non-final evaluation, reference will remain. This is not
+an issue exclusive to value aliases, and falls within the
+range of what is acceptable for now.
+TODO: solve this issue.
+
+[valueAlias selfRefValue pattern]
+[valueAlias selfRefValue pattern a]
+- this triggers the verbatim "adt" path. Note that there
+is no need to rename the variable here as the expression
+was known to compile and is known to be correct.
+
-- out/value --
== Simplified
{
- "a-b": 4
- foo: 4
- baz: 3
- "a-c": 5
- "d-2": {}
+ fieldAlias: {
+ simple: {
+ "a-b": 4
+ foo: 4
+ "a-c": 5
+ }
+ cross: {
+ baz: 3
+ "d-2": {}
+ }
+ }
+ valueAlias: {
+ merge: {
+ // Merge fields, rename alias to avoid conflict.
+ // TODO: merged values can still be simplified.
+ value: {
+ b: 2
+ v: {
+ X: 3
+ }
+ }
+ }
+ selfRef: {
+ struct: {
+ a: {
+ b: {
+ b: 2
+ }
+ }
+ }
+ }
+ selfRefValue: {
+ struct: {
+ // Note: this resolves to a cycle error, which is considered
+ // to be equal to "incomplete". As a result, in case of
+ // non-final evaluation, reference will remain. This is not
+ // an issue exclusive to value aliases, and falls within the
+ // range of what is acceptable for now.
+ // TODO: solve this issue.
+ a: or(X)
+ }
+ pattern: {
+ // this triggers the verbatim "adt" path. Note that there
+ // is no need to rename the variable here as the expression
+ // was known to compile and is known to be correct.
+ a: {}
+ }
+ }
+ }
}
== Raw
{
- "a-b": 4
- foo: 4
- bar?: Y
- baz: 3
- Y="a-c": 5
- "d-2": {}
+ fieldAlias: {
+ simple: {
+ "a-b": 4
+ foo: 4
+ bar?: Y_1
+ Y_1="a-c": 5
+ }
+ cross: {
+ baz: 3
+ "d-2": {}
+ }
+ }
+ valueAlias: {
+ merge: {
+ // Merge fields, rename alias to avoid conflict.
+ // TODO: merged values can still be simplified.
+ value: {
+ #value: 2
+ b: 2
+ v: {
+ X: 3
+ }
+ }
+ }
+ selfRef: {
+ struct: {
+ a: {
+ b: {
+ #foo: 2
+ b: 2
+ }
+ }
+ }
+ }
+ selfRefValue: {
+ struct: {
+ // Note: this resolves to a cycle error, which is considered
+ // to be equal to "incomplete". As a result, in case of
+ // non-final evaluation, reference will remain. This is not
+ // an issue exclusive to value aliases, and falls within the
+ // range of what is acceptable for now.
+ // TODO: solve this issue.
+ a: or(X)
+ }
+ pattern: {
+ // this triggers the verbatim "adt" path. Note that there
+ // is no need to rename the variable here as the expression
+ // was known to compile and is known to be correct.
+ a: {}
+ }
+ }
+ }
}
== Final
{
- "a-b": 4
- foo: 4
- baz: 3
- "a-c": 5
- "d-2": {}
+ fieldAlias: {
+ simple: {
+ "a-b": 4
+ foo: 4
+ "a-c": 5
+ }
+ cross: {
+ baz: 3
+ "d-2": {}
+ }
+ }
+ valueAlias: {
+ merge: {
+ value: {
+ b: 2
+ v: {
+ X: 3
+ }
+ }
+ }
+ selfRef: {
+ struct: {
+ a: {
+ b: {
+ b: 2
+ }
+ }
+ }
+ }
+ selfRefValue: {
+ struct: {
+ a: _|_ // cycle error
+ }
+ pattern: {
+ a: {}
+ }
+ }
+ }
}
== All
{
- "a-b": 4
- foo: 4
- bar?: Y
- baz: 3
- Y="a-c": 5
- "d-2": {}
+ fieldAlias: {
+ simple: {
+ "a-b": 4
+ foo: 4
+ bar?: Y_1
+ Y_1="a-c": 5
+ }
+ cross: {
+ baz: 3
+ "d-2": {}
+ }
+ }
+ valueAlias: {
+ merge: {
+ // Merge fields, rename alias to avoid conflict.
+ // TODO: merged values can still be simplified.
+ value: {
+ #value: 2
+ b: 2
+ v: {
+ X: 3
+ }
+ }
+ }
+ selfRef: {
+ struct: {
+ a: {
+ b: {
+ #foo: 2
+ b: 2
+ }
+ }
+ }
+ }
+ selfRefValue: {
+ struct: {
+ // Note: this resolves to a cycle error, which is considered
+ // to be equal to "incomplete". As a result, in case of
+ // non-final evaluation, reference will remain. This is not
+ // an issue exclusive to value aliases, and falls within the
+ // range of what is acceptable for now.
+ // TODO: solve this issue.
+ a: or(X)
+ }
+ pattern: {
+ // this triggers the verbatim "adt" path. Note that there
+ // is no need to rename the variable here as the expression
+ // was known to compile and is known to be correct.
+ a: {}
+ }
+ }
+ }
}
== Eval
{
- "a-b": 4
- foo: 4
- bar?: Y
- baz: 3
- Y="a-c": 5
- "d-2": {}
+ fieldAlias: {
+ simple: {
+ "a-b": 4
+ foo: 4
+ bar?: Y_1
+ Y_1="a-c": 5
+ }
+ cross: {
+ baz: 3
+ "d-2": {}
+ }
+ }
+ valueAlias: {
+ merge: {
+ value: {
+ #value: 2
+ b: 2
+ v: {
+ X: 3
+ }
+ }
+ }
+ selfRef: {
+ struct: {
+ a: {
+ b: {
+ #foo: 2
+ b: 2
+ }
+ }
+ }
+ }
+ selfRefValue: {
+ struct: {
+ a: or(X)
+ }
+ pattern: {
+ a: {}
+ }
+ }
+ }
}
diff --git a/internal/core/walk/walk.go b/internal/core/walk/walk.go
index a0ef5e6..1385462 100644
--- a/internal/core/walk/walk.go
+++ b/internal/core/walk/walk.go
@@ -79,6 +79,9 @@
case *adt.FieldReference:
w.feature(x.Label, x)
+ case *adt.ValueReference:
+ w.feature(x.Label, x)
+
case *adt.LabelReference:
case *adt.DynamicReference: