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: