-- in.cue --
// All these cases whose name end with cycle should fail with a structural
// error. These are all uncommon code paths, triggered when container types
// are evalated within a nested expression such as as an argument to a
// function call.
//
// The builtins are typically used to trigger the uncommon path. The choice of
// builtin is irrelevant, as long as they don't finalize args before processing.

import "list"

// This is a resolvable reference cycle, were b is equated to c.
letCycleOK: t1: {
	b: c
	let X = b
	c: X
}

// The let has structural cycle, but it is only used in a way that the
// structural cycle is avoided.
letCycleOK: t2: {
	a: {
		b: 1
		let X = a
		c: X.b
	}
}

// Ensure that the cycle exemption algorithm does not bypass actual structural
// cycles.
letCycleFail: t1: {
	a: {
		b: 1
		let X = a
		c: X
	}
}

// Cycles should also be detected in evaluation paths that descend into
// recursion at non-field boundaries.
letCycleFail: t2: {
	a: {
		let X = a
		if X == _|_ { }
		x: y: ""
	}
}

listCycleOK: {
	a: b
	b: and([c])
	c: a
}

disjunctionCycle: {
	a: b
	b: and(1 | c)
	c: a
}

forCycle: {
	#A: a: #B // TODO(errors): Correct error position.
	#B: or([for x in #A { b: x }])
}

letCycleWithAnd: {
	a: d: b
	b: and([for x in a let y = x { y }])
	c: a
}

closeCycle: {
	a: b
	b: close({d: c})
	c: a
}

structCycle: {
	a: b
	b: and([{d: c}])
	c: a
}

embedCycle: {
	a: b
	b: close({c})
	c: a
}

listAddCycle: {
	a: b
	b: list.Concat([[c], [c]])
	c: a
}

listMulCycle: {
	a: b
	b: list.Repeat([{a: b: c}], 3)
	c: a
}

// consult the correct closeness info in the face of it being passed down
// from parent.
closeFail: {
	#T: [_]: _
	#T: close({"a": string})
	x:  #T
	x: b: "foo"
}

printCycle: {
	a: {
		X: a
		if X {
		}
	}
}
-- out/evalalpha/stats --
Leaks:  119
Freed:  0
Reused: 0
Allocs: 119
Retain: 0

Unifications: 99
Conjuncts:    133
Disjuncts:    5

CloseIDElems: 11
NumCloseIDs: 26
-- diff/-out/evalalpha/stats<==>+out/eval/stats --
diff old new
--- old
+++ new
@@ -1,9 +1,12 @@
-Leaks:  48
-Freed:  127
-Reused: 120
-Allocs: 55
-Retain: 138
-
-Unifications: 163
-Conjuncts:    299
-Disjuncts:    192
+Leaks:  119
+Freed:  0
+Reused: 0
+Allocs: 119
+Retain: 0
+
+Unifications: 99
+Conjuncts:    133
+Disjuncts:    5
+
+CloseIDElems: 11
+NumCloseIDs: 26
-- out/eval/stats --
Leaks:  48
Freed:  127
Reused: 120
Allocs: 55
Retain: 138

Unifications: 163
Conjuncts:    299
Disjuncts:    192
-- out/evalalpha --
Errors:
closeCycle.c: structural cycle
closeFail.x.b: field not allowed:
    ./in.cue:104:6
    ./in.cue:107:5
letCycleFail.t1.a.c: structural cycle
printCycle.a.X: structural cycle
structCycle.c: structural cycle
disjunctionCycle.b: cannot use 1 (type int) as type list:
    ./in.cue:56:5
    ./in.cue:56:9
b: structural cycle:
    ./in.cue:62:6
listAddCycle.c: structural cycle:
    ./in.cue:91:5
listMulCycle.c: structural cycle:
    ./in.cue:97:5

Result:
(_|_){
  // [eval]
  letCycleOK: (struct){
    t1: (struct){
      b: (_){ _ }
      let X#1 = (_){ _ }
      c: (_){ _ }
    }
    t2: (struct){
      a: (struct){
        b: (int){ 1 }
        let X#2 = (_|_){
          // [structural cycle] letCycleOK.t2.a.X: structural cycle
        }
        c: (int){ 1 }
      }
    }
  }
  letCycleFail: (_|_){
    // [structural cycle]
    t1: (_|_){
      // [structural cycle]
      a: (_|_){
        // [structural cycle]
        b: (int){ 1 }
        let X#3 = (_|_){
          // [structural cycle] letCycleFail.t1.a.X: structural cycle
        }
        c: (_|_){
          // [structural cycle] letCycleFail.t1.a.c: structural cycle
        }
      }
    }
    t2: (struct){
      a: (struct){
        let X#4 = (_|_){
          // [structural cycle] letCycleFail.t2.a.X: structural cycle:
          //     ./in.cue:43:6
        }
        x: (struct){
          y: (string){ "" }
        }
      }
    }
  }
  listCycleOK: (struct){
    a: (_){ _ }
    b: (_){ _ }
    c: (_){ _ }
  }
  disjunctionCycle: (_|_){
    // [eval]
    a: (_|_){
      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
      //     ./in.cue:56:5
      //     ./in.cue:56:9
    }
    b: (_|_){
      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
      //     ./in.cue:56:5
      //     ./in.cue:56:9
    }
    c: (_|_){
      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
      //     ./in.cue:56:5
      //     ./in.cue:56:9
    }
  }
  forCycle: (_|_){
    // [structural cycle]
    #A: (_|_){
      // [structural cycle]
      a: (_|_){
        // [structural cycle] b: structural cycle:
        //     ./in.cue:62:6
      }
    }
    #B: (_|_){
      // [structural cycle] b: structural cycle:
      //     ./in.cue:62:6
    }
  }
  letCycleWithAnd: (struct){
    a: (struct){
      d: (struct){
      }
    }
    b: (struct){
    }
    c: ~(letCycleWithAnd.a)
  }
  closeCycle: (_|_){
    // [structural cycle]
    a: ~(closeCycle.b)
    b: (_|_){
      // [structural cycle]
      d: (_|_){
        // [structural cycle] closeCycle.c: structural cycle
      }
    }
    c: (_|_){
      // [structural cycle] closeCycle.c: structural cycle
    }
  }
  structCycle: (_|_){
    // [structural cycle]
    a: ~(structCycle.b)
    b: (_|_){
      // [structural cycle]
      d: (_|_){
        // [structural cycle] structCycle.c: structural cycle
      }
    }
    c: (_|_){
      // [structural cycle] structCycle.c: structural cycle
    }
  }
  embedCycle: (struct){
    a: (#struct){
    }
    b: (#struct){
    }
    c: (#struct){
    }
  }
  listAddCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle] listAddCycle.c: structural cycle:
      //     ./in.cue:91:5
    }
    b: (_|_){
      // [structural cycle] listAddCycle.c: structural cycle:
      //     ./in.cue:91:5
    }
    c: (_|_){
      // [structural cycle] listAddCycle.c: structural cycle:
      //     ./in.cue:91:5
    }
  }
  listMulCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle] listMulCycle.c: structural cycle:
      //     ./in.cue:97:5
    }
    b: (_|_){
      // [structural cycle] listMulCycle.c: structural cycle:
      //     ./in.cue:97:5
    }
    c: (_|_){
      // [structural cycle] listMulCycle.c: structural cycle:
      //     ./in.cue:97:5
    }
  }
  closeFail: (_|_){
    // [eval]
    #T: (#struct){
      a: (string){ string }
    }
    x: (_|_){
      // [eval]
      b: (_|_){
        // [eval] closeFail.x.b: field not allowed:
        //     ./in.cue:104:6
        //     ./in.cue:107:5
      }
      a: (string){ string }
    }
  }
  printCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle]
      X: (_|_){
        // [structural cycle] printCycle.a.X: structural cycle
      }
    }
  }
}
-- diff/-out/evalalpha<==>+out/eval --
diff old new
--- old
+++ new
@@ -1,33 +1,20 @@
 Errors:
-closeCycle.a: structural cycle
-closeCycle.b.d: structural cycle
+closeCycle.c: structural cycle
 closeFail.x.b: field not allowed:
     ./in.cue:104:6
-    ./in.cue:105:12
-    ./in.cue:106:6
     ./in.cue:107:5
 letCycleFail.t1.a.c: structural cycle
-structCycle.a: structural cycle
-structCycle.b.d: structural cycle
-disjunctionCycle.a: cannot use 1 (type int) as type list:
-    ./in.cue:56:5
-    ./in.cue:56:9
+printCycle.a.X: structural cycle
+structCycle.c: structural cycle
 disjunctionCycle.b: cannot use 1 (type int) as type list:
     ./in.cue:56:5
     ./in.cue:56:9
-disjunctionCycle.c: cannot use 1 (type int) as type list:
-    ./in.cue:56:5
-    ./in.cue:56:9
 b: structural cycle:
     ./in.cue:62:6
-closeCycle.c: structural cycle:
-    ./in.cue:73:15
-structCycle.c: structural cycle:
-    ./in.cue:79:14
-embedCycle: structural cycle:
-    ./in.cue:85:11
-printCycle.a.X.X: structural cycle:
-    ./in.cue:113:6
+listAddCycle.c: structural cycle:
+    ./in.cue:91:5
+listMulCycle.c: structural cycle:
+    ./in.cue:97:5
 
 Result:
 (_|_){
@@ -65,14 +52,9 @@
     }
     t2: (struct){
       a: (struct){
-        let X#4 = (struct){
-          let X#4 = (_|_){
-            // [structural cycle] letCycleFail.t2.a.X.X: structural cycle:
-            //     ./in.cue:43:6
-          }
-          x: (struct){
-            y: (string){ "" }
-          }
+        let X#4 = (_|_){
+          // [structural cycle] letCycleFail.t2.a.X: structural cycle:
+          //     ./in.cue:43:6
         }
         x: (struct){
           y: (string){ "" }
@@ -88,17 +70,17 @@
   disjunctionCycle: (_|_){
     // [eval]
     a: (_|_){
-      // [eval] disjunctionCycle.a: cannot use 1 (type int) as type list:
-      //     ./in.cue:56:5
-      //     ./in.cue:56:9
-    }
-    b: (_|_){
-      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
-      //     ./in.cue:56:5
-      //     ./in.cue:56:9
-    }
-    c: (_|_){
-      // [eval] disjunctionCycle.c: cannot use 1 (type int) as type list:
+      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
+      //     ./in.cue:56:5
+      //     ./in.cue:56:9
+    }
+    b: (_|_){
+      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
+      //     ./in.cue:56:5
+      //     ./in.cue:56:9
+    }
+    c: (_|_){
+      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
       //     ./in.cue:56:5
       //     ./in.cue:56:9
     }
@@ -124,80 +106,70 @@
     }
     b: (struct){
     }
-    c: (struct){
-      d: (struct){
-      }
-    }
+    c: ~(letCycleWithAnd.a)
   }
   closeCycle: (_|_){
     // [structural cycle]
-    a: (_|_){
-      // [structural cycle] closeCycle.a: structural cycle
-    }
-    b: (_|_){
-      // [structural cycle]
-      d: (_|_){
-        // [structural cycle] closeCycle.b.d: structural cycle
-      }
-    }
-    c: (_|_){
-      // [structural cycle] closeCycle.c: structural cycle:
-      //     ./in.cue:73:15
+    a: ~(closeCycle.b)
+    b: (_|_){
+      // [structural cycle]
+      d: (_|_){
+        // [structural cycle] closeCycle.c: structural cycle
+      }
+    }
+    c: (_|_){
+      // [structural cycle] closeCycle.c: structural cycle
     }
   }
   structCycle: (_|_){
     // [structural cycle]
-    a: (_|_){
-      // [structural cycle] structCycle.a: structural cycle
-    }
-    b: (_|_){
-      // [structural cycle]
-      d: (_|_){
-        // [structural cycle] structCycle.b.d: structural cycle
-      }
-    }
-    c: (_|_){
-      // [structural cycle] structCycle.c: structural cycle:
-      //     ./in.cue:79:14
-    }
-  }
-  embedCycle: (_|_){
-    // [structural cycle]
-    a: (_|_){
-      // [structural cycle] embedCycle: structural cycle:
-      //     ./in.cue:85:11
-    }
-    b: (_|_){
-      // [structural cycle] embedCycle: structural cycle:
-      //     ./in.cue:85:11
-    }
-    c: (_|_){
-      // [structural cycle] embedCycle: structural cycle:
-      //     ./in.cue:85:11
+    a: ~(structCycle.b)
+    b: (_|_){
+      // [structural cycle]
+      d: (_|_){
+        // [structural cycle] structCycle.c: structural cycle
+      }
+    }
+    c: (_|_){
+      // [structural cycle] structCycle.c: structural cycle
+    }
+  }
+  embedCycle: (struct){
+    a: (#struct){
+    }
+    b: (#struct){
+    }
+    c: (#struct){
     }
   }
   listAddCycle: (_|_){
     // [structural cycle]
     a: (_|_){
-      // [structural cycle]
-    }
-    b: (_|_){
-      // [structural cycle]
-    }
-    c: (_|_){
-      // [structural cycle]
+      // [structural cycle] listAddCycle.c: structural cycle:
+      //     ./in.cue:91:5
+    }
+    b: (_|_){
+      // [structural cycle] listAddCycle.c: structural cycle:
+      //     ./in.cue:91:5
+    }
+    c: (_|_){
+      // [structural cycle] listAddCycle.c: structural cycle:
+      //     ./in.cue:91:5
     }
   }
   listMulCycle: (_|_){
     // [structural cycle]
     a: (_|_){
-      // [structural cycle]
-    }
-    b: (_|_){
-      // [structural cycle]
-    }
-    c: (_|_){
-      // [structural cycle]
+      // [structural cycle] listMulCycle.c: structural cycle:
+      //     ./in.cue:97:5
+    }
+    b: (_|_){
+      // [structural cycle] listMulCycle.c: structural cycle:
+      //     ./in.cue:97:5
+    }
+    c: (_|_){
+      // [structural cycle] listMulCycle.c: structural cycle:
+      //     ./in.cue:97:5
     }
   }
   closeFail: (_|_){
@@ -207,21 +179,21 @@
     }
     x: (_|_){
       // [eval]
-      a: (string){ string }
       b: (_|_){
         // [eval] closeFail.x.b: field not allowed:
         //     ./in.cue:104:6
-        //     ./in.cue:105:12
-        //     ./in.cue:106:6
         //     ./in.cue:107:5
       }
+      a: (string){ string }
     }
   }
   printCycle: (_|_){
     // [structural cycle]
     a: (_|_){
-      // [structural cycle] printCycle.a.X.X: structural cycle:
-      //     ./in.cue:113:6
+      // [structural cycle]
+      X: (_|_){
+        // [structural cycle] printCycle.a.X: structural cycle
+      }
     }
   }
 }
-- diff/explanation --
embedCycle: technically not a structural cycle, so V3 result
is okay or even an improvement.
-- out/eval --
Errors:
closeCycle.a: structural cycle
closeCycle.b.d: structural cycle
closeFail.x.b: field not allowed:
    ./in.cue:104:6
    ./in.cue:105:12
    ./in.cue:106:6
    ./in.cue:107:5
letCycleFail.t1.a.c: structural cycle
structCycle.a: structural cycle
structCycle.b.d: structural cycle
disjunctionCycle.a: cannot use 1 (type int) as type list:
    ./in.cue:56:5
    ./in.cue:56:9
disjunctionCycle.b: cannot use 1 (type int) as type list:
    ./in.cue:56:5
    ./in.cue:56:9
disjunctionCycle.c: cannot use 1 (type int) as type list:
    ./in.cue:56:5
    ./in.cue:56:9
b: structural cycle:
    ./in.cue:62:6
closeCycle.c: structural cycle:
    ./in.cue:73:15
structCycle.c: structural cycle:
    ./in.cue:79:14
embedCycle: structural cycle:
    ./in.cue:85:11
printCycle.a.X.X: structural cycle:
    ./in.cue:113:6

Result:
(_|_){
  // [eval]
  letCycleOK: (struct){
    t1: (struct){
      b: (_){ _ }
      let X#1 = (_){ _ }
      c: (_){ _ }
    }
    t2: (struct){
      a: (struct){
        b: (int){ 1 }
        let X#2 = (_|_){
          // [structural cycle] letCycleOK.t2.a.X: structural cycle
        }
        c: (int){ 1 }
      }
    }
  }
  letCycleFail: (_|_){
    // [structural cycle]
    t1: (_|_){
      // [structural cycle]
      a: (_|_){
        // [structural cycle]
        b: (int){ 1 }
        let X#3 = (_|_){
          // [structural cycle] letCycleFail.t1.a.X: structural cycle
        }
        c: (_|_){
          // [structural cycle] letCycleFail.t1.a.c: structural cycle
        }
      }
    }
    t2: (struct){
      a: (struct){
        let X#4 = (struct){
          let X#4 = (_|_){
            // [structural cycle] letCycleFail.t2.a.X.X: structural cycle:
            //     ./in.cue:43:6
          }
          x: (struct){
            y: (string){ "" }
          }
        }
        x: (struct){
          y: (string){ "" }
        }
      }
    }
  }
  listCycleOK: (struct){
    a: (_){ _ }
    b: (_){ _ }
    c: (_){ _ }
  }
  disjunctionCycle: (_|_){
    // [eval]
    a: (_|_){
      // [eval] disjunctionCycle.a: cannot use 1 (type int) as type list:
      //     ./in.cue:56:5
      //     ./in.cue:56:9
    }
    b: (_|_){
      // [eval] disjunctionCycle.b: cannot use 1 (type int) as type list:
      //     ./in.cue:56:5
      //     ./in.cue:56:9
    }
    c: (_|_){
      // [eval] disjunctionCycle.c: cannot use 1 (type int) as type list:
      //     ./in.cue:56:5
      //     ./in.cue:56:9
    }
  }
  forCycle: (_|_){
    // [structural cycle]
    #A: (_|_){
      // [structural cycle]
      a: (_|_){
        // [structural cycle] b: structural cycle:
        //     ./in.cue:62:6
      }
    }
    #B: (_|_){
      // [structural cycle] b: structural cycle:
      //     ./in.cue:62:6
    }
  }
  letCycleWithAnd: (struct){
    a: (struct){
      d: (struct){
      }
    }
    b: (struct){
    }
    c: (struct){
      d: (struct){
      }
    }
  }
  closeCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle] closeCycle.a: structural cycle
    }
    b: (_|_){
      // [structural cycle]
      d: (_|_){
        // [structural cycle] closeCycle.b.d: structural cycle
      }
    }
    c: (_|_){
      // [structural cycle] closeCycle.c: structural cycle:
      //     ./in.cue:73:15
    }
  }
  structCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle] structCycle.a: structural cycle
    }
    b: (_|_){
      // [structural cycle]
      d: (_|_){
        // [structural cycle] structCycle.b.d: structural cycle
      }
    }
    c: (_|_){
      // [structural cycle] structCycle.c: structural cycle:
      //     ./in.cue:79:14
    }
  }
  embedCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle] embedCycle: structural cycle:
      //     ./in.cue:85:11
    }
    b: (_|_){
      // [structural cycle] embedCycle: structural cycle:
      //     ./in.cue:85:11
    }
    c: (_|_){
      // [structural cycle] embedCycle: structural cycle:
      //     ./in.cue:85:11
    }
  }
  listAddCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle]
    }
    b: (_|_){
      // [structural cycle]
    }
    c: (_|_){
      // [structural cycle]
    }
  }
  listMulCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle]
    }
    b: (_|_){
      // [structural cycle]
    }
    c: (_|_){
      // [structural cycle]
    }
  }
  closeFail: (_|_){
    // [eval]
    #T: (#struct){
      a: (string){ string }
    }
    x: (_|_){
      // [eval]
      a: (string){ string }
      b: (_|_){
        // [eval] closeFail.x.b: field not allowed:
        //     ./in.cue:104:6
        //     ./in.cue:105:12
        //     ./in.cue:106:6
        //     ./in.cue:107:5
      }
    }
  }
  printCycle: (_|_){
    // [structural cycle]
    a: (_|_){
      // [structural cycle] printCycle.a.X.X: structural cycle:
      //     ./in.cue:113:6
    }
  }
}
-- out/compile --
--- in.cue
{
  letCycleOK: {
    t1: {
      b: 〈0;c〉
      let X#1 = 〈0;b〉
      c: 〈0;let X#1〉
    }
  }
  letCycleOK: {
    t2: {
      a: {
        b: 1
        let X#2 = 〈1;a〉
        c: 〈0;let X#2〉.b
      }
    }
  }
  letCycleFail: {
    t1: {
      a: {
        b: 1
        let X#3 = 〈1;a〉
        c: 〈0;let X#3〉
      }
    }
  }
  letCycleFail: {
    t2: {
      a: {
        let X#4 = 〈1;a〉
        if (〈0;let X#4〉 == _|_(explicit error (_|_ literal) in source)) {}
        x: {
          y: ""
        }
      }
    }
  }
  listCycleOK: {
    a: 〈0;b〉
    b: and([
      〈1;c〉,
    ])
    c: 〈0;a〉
  }
  disjunctionCycle: {
    a: 〈0;b〉
    b: and((1|〈0;c〉))
    c: 〈0;a〉
  }
  forCycle: {
    #A: {
      a: 〈1;#B〉
    }
    #B: or([
      for _, x in 〈1;#A〉 {
        b: 〈1;x〉
      },
    ])
  }
  letCycleWithAnd: {
    a: {
      d: 〈1;b〉
    }
    b: and([
      for _, x in 〈1;a〉 let y = 〈0;x〉 {
        〈1;y〉
      },
    ])
    c: 〈0;a〉
  }
  closeCycle: {
    a: 〈0;b〉
    b: close({
      d: 〈1;c〉
    })
    c: 〈0;a〉
  }
  structCycle: {
    a: 〈0;b〉
    b: and([
      {
        d: 〈2;c〉
      },
    ])
    c: 〈0;a〉
  }
  embedCycle: {
    a: 〈0;b〉
    b: close({
      〈1;c〉
    })
    c: 〈0;a〉
  }
  listAddCycle: {
    a: 〈0;b〉
    b: 〈import;list〉.Concat([
      [
        〈2;c〉,
      ],
      [
        〈2;c〉,
      ],
    ])
    c: 〈0;a〉
  }
  listMulCycle: {
    a: 〈0;b〉
    b: 〈import;list〉.Repeat([
      {
        a: {
          b: 〈3;c〉
        }
      },
    ], 3)
    c: 〈0;a〉
  }
  closeFail: {
    #T: {
      [_]: _
    }
    #T: close({
      a: string
    })
    x: 〈0;#T〉
    x: {
      b: "foo"
    }
  }
  printCycle: {
    a: {
      X: 〈1;a〉
      if 〈0;X〉 {}
    }
  }
}
