internal/core/adt: fix some regressions

- Mark Evaluating before reprocessing Partial
This avoids a stack overflow.

- Don't finalize too early
It is important to not finalize nodes too early, as
this may result in insertions being ignored. In the
old implementation these bugs were missed as it
did a lot of extraneous recomputation.

- Fix "Finalized" forcing of disjunctions
When expanding disjunctions, only the last in a
sequence should be marked as finalized. The logic
here was flawed and would also tag the final
disjuncts of mid-stream subdisjunctions as final.

- As Resolve no longer finalizes, the cue API now
needs to finalize things itself.

- Ensure ToData does not leave dangling state
The state should always be Finalized, but just
in case.

Fixes #633

Change-Id: Ia6bd0b3bebb25c9033a30d3513b26b96454d0b91
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8141
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/testdata/builtins/closed.txtar b/cue/testdata/builtins/closed.txtar
index 65b496b..badd5f1 100644
--- a/cue/testdata/builtins/closed.txtar
+++ b/cue/testdata/builtins/closed.txtar
@@ -6,6 +6,18 @@
 b: a & { x: int } // err
 c: a & { a: c: int } // okay (non-recursive close)
 
+inDisjunctions: {
+    x: [string]: #Def
+    #Def: [string]: {
+        a: b: true
+        let X = a
+        close({uint: a: b: X.b}) | close({string: a: b: true})
+    }
+    x: socket: string: {}
+    x: syslog: x.socket
+    x: syslog: xxx: {}
+}
+
 -- out/eval --
 Errors:
 b: field `x` not allowed:
@@ -39,6 +51,73 @@
       c: (int){ int }
     }
   }
+  inDisjunctions: (struct){
+    x: (struct){
+      socket: (#struct){
+        string: (struct){ |((#struct){
+            a: (#struct){
+              b: (bool){ true }
+            }
+            uint: (#struct){
+              a: (#struct){
+                b: (bool){ true }
+              }
+            }
+          }, (#struct){
+            a: (#struct){
+              b: (bool){ true }
+            }
+            string: (#struct){
+              a: (#struct){
+                b: (bool){ true }
+              }
+            }
+          }) }
+      }
+      syslog: (#struct){
+        string: (struct){ |((#struct){
+            a: (#struct){
+              b: (bool){ true }
+            }
+            uint: (#struct){
+              a: (#struct){
+                b: (bool){ true }
+              }
+            }
+          }, (#struct){
+            a: (#struct){
+              b: (bool){ true }
+            }
+            string: (#struct){
+              a: (#struct){
+                b: (bool){ true }
+              }
+            }
+          }) }
+        xxx: (struct){ |((#struct){
+            a: (#struct){
+              b: (bool){ true }
+            }
+            uint: (#struct){
+              a: (#struct){
+                b: (bool){ true }
+              }
+            }
+          }, (#struct){
+            a: (#struct){
+              b: (bool){ true }
+            }
+            string: (#struct){
+              a: (#struct){
+                b: (bool){ true }
+              }
+            }
+          }) }
+      }
+    }
+    #Def: (#struct){
+    }
+  }
 }
 -- out/compile --
 --- in.cue
@@ -56,4 +135,42 @@
       c: int
     }
   })
+  inDisjunctions: {
+    x: {
+      [string]: 〈1;#Def〉
+    }
+    #Def: {
+      [string]: {
+        a: {
+          b: true
+        }
+        (close({
+          uint: {
+            a: {
+              b: 〈3;let X〉.b
+            }
+          }
+        })|close({
+          string: {
+            a: {
+              b: true
+            }
+          }
+        }))
+      }
+    }
+    x: {
+      socket: {
+        string: {}
+      }
+    }
+    x: {
+      syslog: 〈1;x〉.socket
+    }
+    x: {
+      syslog: {
+        xxx: {}
+      }
+    }
+  }
 }
diff --git a/cue/testdata/cycle/structural.txtar b/cue/testdata/cycle/structural.txtar
index 798e68c..9944fc9 100644
--- a/cue/testdata/cycle/structural.txtar
+++ b/cue/testdata/cycle/structural.txtar
@@ -137,6 +137,25 @@
     }
 }
 
+// More trigger happy on stack overflows.
+b12b: {
+      #list: {
+    tail: #list
+
+      if tail != null {
+        sum: tail.sum
+      }
+    }
+
+    list1: #list
+    list1: {
+       tail: {
+          tail: {
+          }
+       }
+    }
+}
+
 // Issue #587
 b13: root: a: [ for x in root {x} ]
 
@@ -395,6 +414,7 @@
 Errors:
 a1.f.0: structural cycle
 a3.f.g: structural cycle
+b12b.#list.tail.tail: structural cycle
 b13.root.a.0.0: structural cycle
 b14.root.b.1.1: structural cycle
 b4.x.y.0: structural cycle
@@ -410,67 +430,71 @@
 d1.a.b.c.d.t: structural cycle
 d1.r: structural cycle
 d2.r.c.d.t: structural cycle
+d2.x.d.t.c.d.t: structural cycle
 e1.a.c: structural cycle
 e1.b.c: structural cycle
 e2.a.c: structural cycle
 e2.b.c: structural cycle
 e3.a: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:289:8
-    ./in.cue:290:8
+    ./in.cue:308:8
+    ./in.cue:309:8
 e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:289:8
-    ./in.cue:289:9
-    ./in.cue:290:8
+    ./in.cue:308:8
+    ./in.cue:308:9
+    ./in.cue:309:8
 e3.a.0: structural cycle
 e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-    ./in.cue:289:8
-    ./in.cue:290:8
-    ./in.cue:290:11
+    ./in.cue:308:8
+    ./in.cue:309:8
+    ./in.cue:309:11
 e3.a.c: structural cycle
 e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:292:8
-    ./in.cue:293:8
+    ./in.cue:311:8
+    ./in.cue:312:8
 e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:292:8
-    ./in.cue:292:9
-    ./in.cue:293:8
+    ./in.cue:311:8
+    ./in.cue:311:9
+    ./in.cue:312:8
 e3.b.0: structural cycle
 e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-    ./in.cue:292:8
-    ./in.cue:293:8
-    ./in.cue:293:11
+    ./in.cue:311:8
+    ./in.cue:312:8
+    ./in.cue:312:11
 e3.b.c: structural cycle
 e4.a.0: 4 errors in empty disjunction:
 e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:297:13
-    ./in.cue:298:9
+    ./in.cue:316:13
+    ./in.cue:317:9
 e4.a.0.0: 2 errors in empty disjunction:
 e4.a.0.0: conflicting values [{c:1}] and {c:1} (mismatched types list and struct):
-    ./in.cue:298:9
-    ./in.cue:298:10
+    ./in.cue:317:9
+    ./in.cue:317:10
 e4.a.0.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:297:9
-    ./in.cue:297:13
-    ./in.cue:298:9
+    ./in.cue:316:9
+    ./in.cue:316:13
+    ./in.cue:317:9
 e4.b.0: 4 errors in empty disjunction:
 e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:300:9
-    ./in.cue:301:13
+    ./in.cue:319:9
+    ./in.cue:320:13
 e4.b.0.0: 2 errors in empty disjunction:
 e4.b.0.0: conflicting values [{c:1}] and {c:1} (mismatched types list and struct):
-    ./in.cue:300:9
-    ./in.cue:300:10
+    ./in.cue:319:9
+    ./in.cue:319:10
 e4.b.0.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-    ./in.cue:300:9
-    ./in.cue:301:9
-    ./in.cue:301:13
+    ./in.cue:319:9
+    ./in.cue:320:9
+    ./in.cue:320:13
 p2.#T.a.b.link: structural cycle
 p3.#U.#T.a.b.link: structural cycle
 p5.#T.a.0.link: structural cycle
 p6.#U.#T.a.0.link: structural cycle
+z1.z.f.h.h: structural cycle
 z1.z.g.h: structural cycle
+cycle error:
+    ./in.cue:144:10
 0: structural cycle:
-    ./in.cue:264:19
+    ./in.cue:283:19
 
 Result:
 (_|_){
@@ -557,11 +581,8 @@
   }
   b4: (_|_){
     // [structural cycle]
-    b: (_|_){
-      // [structural cycle]
-      0: (_|_){
-        // [structural cycle] b4.x.y.0: structural cycle
-      }
+    b: (#list){
+      0: (int){ 1 }
     }
     x: (_|_){
       // [structural cycle]
@@ -569,8 +590,6 @@
         // [structural cycle]
         0: (_|_){
           // [structural cycle] b4.x.y.0: structural cycle
-          0: (_|_){// 〈0;y〉
-          }
         }
       }
     }
@@ -621,10 +640,8 @@
   }
   b7: (_|_){
     // [structural cycle]
-    b: (_|_){
-      // [structural cycle]
-      0: (_|_){
-        // [structural cycle] b7.a.0: structural cycle
+    b: (#list){
+      0: (#list){
         0: (int){ 1 }
       }
     }
@@ -632,8 +649,6 @@
       // [structural cycle]
       0: (_|_){
         // [structural cycle] b7.a.0: structural cycle
-        0: (_|_){// 〈0;a〉
-        }
       }
     }
   }
@@ -689,7 +704,9 @@
         }) }
     }
     c: (#struct){
-      d: (string){ string }
+      d: ((string|struct)){ |((string){ string }, (#struct){
+          b: (string){ string }
+        }) }
     }
   }
   b11: (struct){
@@ -727,6 +744,30 @@
       sum: (int){ 10 }
     }
   }
+  b12b: (_|_){
+    // [structural cycle]
+    #list: (_|_){
+      // [structural cycle] cycle error:
+      //     ./in.cue:144:10
+      tail: (_|_){
+        // [structural cycle] cycle error:
+        //     ./in.cue:144:10
+        tail: (_|_){
+          // [structural cycle] b12b.#list.tail.tail: structural cycle
+          tail: (_|_){// 〈1;#list〉
+          }
+        }
+      }
+    }
+    list1: (_|_){
+      // [structural cycle] cycle error:
+      //     ./in.cue:144:10
+      tail: (struct){
+        tail: (struct){
+        }
+      }
+    }
+  }
   b13: (_|_){
     // [structural cycle]
     root: (_|_){
@@ -1055,6 +1096,16 @@
         h: (int){ int }
         t: (_|_){
           // [structural cycle]
+          c: (_|_){
+            // [structural cycle]
+            d: (_|_){
+              // [structural cycle]
+              h: (int){ int }
+              t: (_|_){
+                // [structural cycle] d2.x.d.t.c.d.t: structural cycle
+              }
+            }
+          }
         }
       }
     }
@@ -1067,13 +1118,6 @@
           h: (int){ int }
           t: (_|_){
             // [structural cycle] d2.r.c.d.t: structural cycle
-            c: (_|_){// {
-              //   d: {
-              //     h: int
-              //     t: 〈4;r〉
-              //   }
-              // }
-            }
           }
         }
       }
@@ -1089,16 +1133,6 @@
             h: (int){ int }
             t: (_|_){
               // [structural cycle]
-              c: (_|_){
-                // [structural cycle]
-                d: (_|_){
-                  // [structural cycle]
-                  h: (int){ int }
-                  t: (_|_){
-                    // [structural cycle]
-                  }
-                }
-              }
             }
           }
         }
@@ -1115,19 +1149,19 @@
           // [structural cycle]
           c: (_|_){
             // [structural cycle] 0: structural cycle:
-            //     ./in.cue:264:19
+            //     ./in.cue:283:19
           }
         }
       }
       indirect: (_|_){
         // [structural cycle] 0: structural cycle:
-        //     ./in.cue:264:19
+        //     ./in.cue:283:19
       }
       i: (int){ |(*(int){ 1 }, (int){ int }) }
     }
     x: (_|_){
       // [structural cycle] 0: structural cycle:
-      //     ./in.cue:264:19
+      //     ./in.cue:283:19
       i: (int){ 0 }
     }
   }
@@ -1173,13 +1207,13 @@
     // [eval]
     a: (_|_){
       // [eval] e3.a: conflicting values [a] and {c:a} (mismatched types list and struct):
-      //     ./in.cue:289:8
-      //     ./in.cue:290:8
+      //     ./in.cue:308:8
+      //     ./in.cue:309:8
       c: (_|_){
         // [eval] e3.a.c: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:289:8
-        //     ./in.cue:290:8
-        //     ./in.cue:290:11
+        //     ./in.cue:308:8
+        //     ./in.cue:309:8
+        //     ./in.cue:309:11
         // e3.a.c: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -1188,9 +1222,9 @@
       }
       0: (_|_){
         // [eval] e3.a.0: conflicting values [a] and {c:a} (mismatched types list and struct):
-        //     ./in.cue:289:8
-        //     ./in.cue:289:9
-        //     ./in.cue:290:8
+        //     ./in.cue:308:8
+        //     ./in.cue:308:9
+        //     ./in.cue:309:8
         // e3.a.0: structural cycle
         c: (_|_){// 〈1;a〉
         }
@@ -1200,13 +1234,13 @@
     }
     b: (_|_){
       // [eval] e3.b: conflicting values [b] and {c:b} (mismatched types list and struct):
-      //     ./in.cue:292:8
-      //     ./in.cue:293:8
+      //     ./in.cue:311:8
+      //     ./in.cue:312:8
       c: (_|_){
         // [eval] e3.b.c: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:292:8
-        //     ./in.cue:293:8
-        //     ./in.cue:293:11
+        //     ./in.cue:311:8
+        //     ./in.cue:312:8
+        //     ./in.cue:312:11
         // e3.b.c: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -1215,9 +1249,9 @@
       }
       0: (_|_){
         // [eval] e3.b.0: conflicting values [b] and {c:b} (mismatched types list and struct):
-        //     ./in.cue:292:8
-        //     ./in.cue:292:9
-        //     ./in.cue:293:8
+        //     ./in.cue:311:8
+        //     ./in.cue:311:9
+        //     ./in.cue:312:8
         // e3.b.0: structural cycle
         c: (_|_){// 〈1;b〉
         }
@@ -1233,16 +1267,16 @@
       0: (_|_){
         // [eval] e4.a.0: 4 errors in empty disjunction:
         // e4.a.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:297:13
-        //     ./in.cue:298:9
+        //     ./in.cue:316:13
+        //     ./in.cue:317:9
         // e4.a.0.0: 2 errors in empty disjunction:
         // e4.a.0.0: conflicting values [{c:1}] and {c:1} (mismatched types list and struct):
-        //     ./in.cue:298:9
-        //     ./in.cue:298:10
+        //     ./in.cue:317:9
+        //     ./in.cue:317:10
         // e4.a.0.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:297:9
-        //     ./in.cue:297:13
-        //     ./in.cue:298:9
+        //     ./in.cue:316:9
+        //     ./in.cue:316:13
+        //     ./in.cue:317:9
         0: (struct){
           c: (int){ 1 }
         }
@@ -1253,16 +1287,16 @@
       0: (_|_){
         // [eval] e4.b.0: 4 errors in empty disjunction:
         // e4.b.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:300:9
-        //     ./in.cue:301:13
+        //     ./in.cue:319:9
+        //     ./in.cue:320:13
         // e4.b.0.0: 2 errors in empty disjunction:
         // e4.b.0.0: conflicting values [{c:1}] and {c:1} (mismatched types list and struct):
-        //     ./in.cue:300:9
-        //     ./in.cue:300:10
+        //     ./in.cue:319:9
+        //     ./in.cue:319:10
         // e4.b.0.0: conflicting values [{c:1}] and {} (mismatched types list and struct):
-        //     ./in.cue:300:9
-        //     ./in.cue:301:9
-        //     ./in.cue:301:13
+        //     ./in.cue:319:9
+        //     ./in.cue:320:9
+        //     ./in.cue:320:13
         0: (struct){
           c: (int){ 1 }
         }
@@ -1372,7 +1406,7 @@
         h: (_|_){
           // [structural cycle]
           h: (_|_){
-            // [structural cycle] z1.z.g.h: structural cycle
+            // [structural cycle] z1.z.f.h.h: structural cycle
           }
         }
       }
@@ -1380,8 +1414,6 @@
         // [structural cycle]
         h: (_|_){
           // [structural cycle] z1.z.g.h: structural cycle
-          h: (_|_){// 〈1;g〉
-          }
         }
       }
     }
@@ -1595,6 +1627,20 @@
       }
     }
   }
+  b12b: {
+    #list: {
+      tail: 〈1;#list〉
+      if (〈0;tail〉 != null) {
+        sum: 〈1;tail〉.sum
+      }
+    }
+    list1: 〈0;#list〉
+    list1: {
+      tail: {
+        tail: {}
+      }
+    }
+  }
   b13: {
     root: {
       a: [
diff --git a/cue/types.go b/cue/types.go
index 1b9e369..bf3a10d 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -619,6 +619,7 @@
 	if b != nil {
 		return newErrValue(v, b)
 	}
+	n.Finalize(ctx.opCtx)
 	return makeValue(v.idx, n)
 }
 
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index 1915932..d197a88 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -313,6 +313,8 @@
 func (v *Vertex) ToDataSingle() *Vertex {
 	w := *v
 	w.isData = true
+	w.state = nil
+	w.status = Finalized
 	return &w
 }
 
@@ -326,6 +328,8 @@
 		}
 	}
 	w := *v
+	w.state = nil
+	w.status = Finalized
 
 	w.BaseValue = toDataAll(w.BaseValue)
 	w.Arcs = arcs
diff --git a/internal/core/adt/disjunct.go b/internal/core/adt/disjunct.go
index a4c3cc2..a24de24 100644
--- a/internal/core/adt/disjunct.go
+++ b/internal/core/adt/disjunct.go
@@ -119,6 +119,11 @@
 
 	n.ctx.stats.DisjunctCount++
 
+	node := n.node
+	defer func() {
+		n.node = node
+	}()
+
 	for n.expandOne() {
 	}
 
@@ -126,7 +131,7 @@
 	// save nodeContext.
 
 	if recursive || len(n.disjunctions) > 0 {
-		n.snapshot = snapshotVertex(*n.node)
+		n.snapshot = clone(*n.node)
 	} else {
 		n.snapshot = *n.node
 	}
@@ -171,6 +176,8 @@
 			*n = m
 		}
 		n.result = result
+		n.node = &n.result
+
 		if recursive {
 			n.disjuncts = append(n.disjuncts, n)
 		}
@@ -178,7 +185,6 @@
 	case len(n.disjunctions) > 0:
 		// Process full disjuncts to ensure that erroneous disjuncts are
 		// eliminated.
-		state = Finalized
 
 		n.disjuncts = append(n.disjuncts, n)
 
@@ -201,7 +207,7 @@
 				case d.expr != nil:
 					for _, v := range d.expr.Values {
 						cn := dn.clone()
-						*cn.node = snapshotVertex(dn.snapshot)
+						*cn.node = clone(dn.snapshot)
 						cn.node.state = cn
 
 						c := MakeConjunct(d.env, v.Val, d.cloneID)
@@ -216,7 +222,7 @@
 				case d.value != nil:
 					for i, v := range d.value.Values {
 						cn := dn.clone()
-						*cn.node = snapshotVertex(dn.snapshot)
+						*cn.node = clone(dn.snapshot)
 						cn.node.state = cn
 
 						cn.addValueConjunct(d.env, v, d.cloneID)
@@ -315,27 +321,42 @@
 	return mode
 }
 
-// Clone makes a shallow copy of a Vertex. The purpose is to create different
+// clone makes a shallow copy of a Vertex. The purpose is to create different
 // disjuncts from the same Vertex under computation. This allows the conjuncts
 // of an arc to be reset to a previous position and the reuse of earlier
 // computations.
 //
-// Notes: only Arcs need to be cloned recursively. Structs is assumed to not yet
-// be computed at the time that a Clone is needed and must be nil. Conjuncts no
-// longer needed and can become nil. All other fields can be copied shallowly.
-//
-// USE TO SAVE NODE BRANCH FOR DISJUNCTION, BUT BEFORE POSTDIJSUNCT.
-func snapshotVertex(v Vertex) Vertex {
+// Notes: only Arcs need to be copied recursively. Either the arc is finalized
+// and can be used as is, or Structs is assumed to not yet be computed at the
+// time that a clone is needed and must be nil. Conjuncts no longer needed and
+// can become nil. All other fields can be copied shallowly.
+func clone(v Vertex) Vertex {
 	if a := v.Arcs; len(a) > 0 {
 		v.Arcs = make([]*Vertex, len(a))
 		for i, arc := range a {
-			// For child arcs, only Conjuncts are set and Arcs and
-			// Structs will be nil.
-			a := *arc
-			v.Arcs[i] = &a
+			switch arc.status {
+			case Finalized:
+				v.Arcs[i] = arc
 
-			a.Conjuncts = make([]Conjunct, len(arc.Conjuncts))
-			copy(a.Conjuncts, arc.Conjuncts)
+			case 0:
+				a := *arc
+				v.Arcs[i] = &a
+
+				a.Conjuncts = make([]Conjunct, len(arc.Conjuncts))
+				copy(a.Conjuncts, arc.Conjuncts)
+
+			default:
+				// This should never happen and would be a performance issue.
+				// But try to mend the situation by doing something sensible in
+				// case the user is not running with strict mode enabled.
+				Assertf(false, "invalid state for disjunct")
+
+				a := *arc
+				a.state = arc.state.clone()
+				a.state.node = &a
+				a.state.snapshot = clone(a)
+				v.Arcs[i] = &a
+			}
 		}
 	}
 
diff --git a/internal/core/adt/equality.go b/internal/core/adt/equality.go
index 3dbc14f..c8a7d9c 100644
--- a/internal/core/adt/equality.go
+++ b/internal/core/adt/equality.go
@@ -43,6 +43,7 @@
 		return false
 	}
 
+	// TODO: this really should be subsumption.
 	if x.IsClosed(ctx) != y.IsClosed(ctx) {
 		return false
 	}
diff --git a/internal/core/adt/eval.go b/internal/core/adt/eval.go
index 2e7abc1..47037db 100644
--- a/internal/core/adt/eval.go
+++ b/internal/core/adt/eval.go
@@ -273,6 +273,8 @@
 	case Partial:
 		defer c.PopArc(c.PushArc(v))
 
+		v.status = Evaluating
+
 		// Use maybeSetCache for cycle breaking
 		for n.maybeSetCache(); n.expandOne(); n.maybeSetCache() {
 		}
@@ -312,7 +314,13 @@
 			return
 		}
 
-		n.expandDisjuncts(state, n, maybeDefault, false)
+		// Disjunctions should always be finalized. If there are nested
+		// disjunctions the last one should be finalized.
+		disState := state
+		if len(n.disjunctions) > 0 && disState != Finalized {
+			disState = Finalized
+		}
+		n.expandDisjuncts(disState, n, maybeDefault, false)
 
 		// If the state has changed, it is because a disjunct has been run. In this case, our node will have completed, and it will
 		// set a value soon.
@@ -1231,7 +1239,7 @@
 	// The reason is that disjunctions must be eliminated if checks in
 	// values on which they depend fail.
 	ctx := n.ctx
-	ctx.Unify(ctx, arc, Finalized)
+	ctx.Unify(ctx, arc, AllArcs)
 
 	for _, c := range arc.Conjuncts {
 		var a []*Vertex
@@ -1689,7 +1697,7 @@
 	case arc.Status() == 0:
 	default:
 		// TODO: handle adding to finalized conjunct
-		panic("unhandled")
+		panic(fmt.Sprintf("unhandled %d", arc.status))
 	}
 	return arc
 }
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index c607a8a..1a37a41 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -64,7 +64,11 @@
 func (x *StructLit) evaluate(c *OpContext) Value {
 	e := c.Env(0)
 	v := &Vertex{Conjuncts: []Conjunct{{e, x, CloseInfo{}}}}
-	c.Unifier.Unify(c, v, Finalized) // TODO: also partial okay?
+	// evaluate may not finalize a field, as the resulting value may be
+	// used in a context where more conjuncts are added. It may also lead
+	// to disjuncts being in a partially expanded state, leading to
+	// misaligned nodeContexts.
+	c.Unifier.Unify(c, v, AllArcs)
 	return v
 }
 
@@ -282,6 +286,7 @@
 func (x *ListLit) evaluate(c *OpContext) Value {
 	e := c.Env(0)
 	v := &Vertex{Conjuncts: []Conjunct{{e, x, CloseInfo{}}}}
+	// TODO: should be AllArcs and then use Finalize for builtins?
 	c.Unifier.Unify(c, v, Finalized) // TODO: also partial okay?
 	return v
 }
diff --git a/internal/core/compile/builtin.go b/internal/core/compile/builtin.go
index c1edc7e..ca93e99 100644
--- a/internal/core/compile/builtin.go
+++ b/internal/core/compile/builtin.go
@@ -89,6 +89,8 @@
 			return s
 		}
 		v := *s
+		// TODO(perf): do not copy the arc, but rather find a way to mark the
+		// calling nodeContext.
 		v.BaseValue = &adt.StructMarker{NeedClose: true}
 		return &v
 	},