-- 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
	@test(eq, {b: _, c: _})
}

// 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
	}
	@test(eq, {a: {b: 1, c: 1}})
}

// Ensure that the cycle exemption algorithm does not bypass actual structural
// cycles.
letCycleFail: t1: {
	a: {
		b: 1
		let X = a
		c: X
	}
	@test(err, code=structural_cycle, path=a.c, contains="structural cycle", pos=[])
}

// 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: ""
	}
	@test(eq, {a: {x: {y: ""}}})
	// TODO(eval): not sure if it really should fail here, so this may be okay.
	@test(debug, """
		(struct){
		  a: (struct){
		    let X#4 = (_|_){
		      // [structural_cycle] letCycleFail.t2.a.X: structural cycle
		    }
		    x: (struct){
		      y: (string){ "" }
		    }
		  }
		}
		""")
}

listCycleOK: {
	a: b
	b: and([c])
	c: a
	@test(eq, {a: _, b: _, c: _})
}

disjunctionCycle: {
	a: b
	b: and(1 | c)
	c: a
	@test(err, code=eval, path=b,
		hint="This probably should be a totally different error message.",
		contains="cannot use %s (type %s) as %s in argument %d to %v",
		args=[1, int, list, 1, and], pos=[2:9])
}

forCycle: {
	#A: a: #B // TODO(errors): Correct error position, or at all.
	#B: or([for x in #A { b: x }])
	@test(err, code=structural_cycle, path=b, contains="structural cycle", pos=[2:6])
}

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

closeCycle: {
	a: b
	b: close({d: c})
	c: a
	@test(err, code=structural_cycle, path=c, contains="structural cycle", pos=[])
}

structCycle: {
	a: b
	b: and([{d: c}])
	c: a
	@test(err, code=structural_cycle, path=c, contains="structural cycle", pos=[])
}

embedCycle: {
	a: b
	b: close({c})
	c: a
	@test(eq, {a: {}, b: {}, c: {}})
}

listAddCycle: {
	a: b
	b: list.Concat([[c], [c]])
	c: a
	@test(err, code=structural_cycle, path=b, contains="error in call to %s", args=[list.Concat], pos=[2:5])
}

listMulCycle: {
	a: b
	b: list.Repeat([{a: b: c}], 3)
	c: a
	@test(err, code=structural_cycle, path=b, contains="error in call to %s", args=[list.Repeat], pos=[2:5])
}

// 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"
	@test(err, code=eval, path=x.b, contains="field not allowed", pos=[1:6, 4:5])
}

printCycle: {
	a: {
		X: a
		if X {
		}
	}
	@test(err, code=structural_cycle, path=a.X, contains="structural cycle", pos=[])
}
-- self.cue --
issue3978: {
	a: self={
		if self.missing != _|_ {
		}
	}
	@test(eq, {a: {}})
}
-- out/errors.txt --
[structural_cycle] letCycleFail.t1.a.c: structural cycle
[eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
    ./in.cue:74:9
[eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
    ./in.cue:74:9
[eval] disjunctionCycle.b: cannot use 1 (type int) as list in argument 1 to and:
    ./in.cue:74:9
[structural_cycle] b: structural cycle:
    ./in.cue:84:6
[structural_cycle] b: structural cycle:
    ./in.cue:84:6
[structural_cycle] closeCycle.c: structural cycle
[structural_cycle] closeCycle.c: structural cycle
[structural_cycle] structCycle.c: structural cycle
[structural_cycle] structCycle.c: structural cycle
[structural_cycle] listAddCycle.b: error in call to list.Concat: structural cycle:
    ./in.cue:118:5
[structural_cycle] listAddCycle.b: error in call to list.Concat: structural cycle:
    ./in.cue:118:5
[structural_cycle] listAddCycle.c: structural cycle:
    ./in.cue:118:5
[structural_cycle] listMulCycle.b: error in call to list.Repeat: structural cycle:
    ./in.cue:125:5
[structural_cycle] listMulCycle.b: error in call to list.Repeat: structural cycle:
    ./in.cue:125:5
[structural_cycle] listMulCycle.c: structural cycle:
    ./in.cue:125:5
[eval] closeFail.x.b: field not allowed:
    ./in.cue:133:6
    ./in.cue:136:5
[structural_cycle] printCycle.a.X: structural cycle
-- out/todo.txt --
embedCycle: error in v2 (structural cycle) but success in v3 (close({c}) not truly structural cycle).
closeCycle/structCycle: different error paths reported in v2 vs v3.
disjunctionCycle: different error message format (type list vs list).
forCycle.#A.a: v2 errors directly, v3 shows as ~(forCycle.#B).
listAddCycle/listMulCycle: different error messages between v2 and v3.
letCycleFail.t2: v2 X was a struct, v3 X is structural cycle error.
-- out/eval/stats --
Leaks:  11
Freed:  116
Reused: 94
Allocs: 33
Retain: 0

Unifications: 106
Conjuncts:    152
Disjuncts:    5

NumCloseIDs: 47
