-- in.cue --
self: {
	// This is an incomplete error, as it may succeed when the user
	// explicitly specifies a value for fail.a.b.
	fail: {
		a: {
			if a.b == _|_ {
				b: 1
			}
			@test(err, code=incomplete|cycle)
		}
	}

	// This is an incomplete error, as it may succeed when the user
	// explicitly specifies a value for a.b.
	isConcreteFail: t1:{
		a: {
			if a.b == _|_ {
				b: 1
			}
			b: int
			@test(eq, {b: 1})
		}
	}

	isConcreteFail: t2:{
		a: {
			if a.b == _|_ {
				b: 1
			}
			b?: int
			@test(err, code=cycle,
				contains="circular dependency in evaluation of conditionals: %v changed after evaluation", args=[a.b], pos=[1:7])
		}
	}

	// This is an incomplete error, as it may succeed when the user
	// explicitly specifies a value for a.b.
	// TODO: new builtin semantics.
	//     if isconcrete(a.b)  -->> cyclic error evaluating isconcrete.
	//     if isdefined(a.b)   -->> evaluate to true as a.b is int, result is 1
	//     if !isdefined(a.b)  -->> evaluate to false, a.b remains int
	isNotConcrete: t1: {
		a: {
			if a.b != _|_ {
				b: 1
			}
			b: int
			@test(eq, {b: int})
		}
	}
	isNotConcrete: t2: {
		a: {
			if a.b != _|_ {
				b: 1
			}
			b?: int
			@test(eq, {b?: int})
		}
	}

}

-- mutual.cue --
mutual: {
	noConflicts: {
		a: {if b.foo == _|_ {new: ""}}
		b: {if a.bar == _|_ {new: ""}}
		@test(eq, {a: {new: ""}, b: {new: ""}})
	}

	mutualCycleFail: {
		b: {
			if a.bar == _|_ {foo: ""}
			@test(err, code=incomplete|cycle)
		}
		a: {
			if b.foo == _|_ {bar: ""}
			@test(err, code=incomplete|cycle)
		}
	}

	brokenCycleSuccess: {
		a: { if b.foo == _|_ { foo: "" } }
		b: { if a.bar == _|_ { bar: "" } }
		a: bar: ""
		@test(eq, {a: {foo: "", bar: ""}, b: {}})
	}

	allowOneDirectionalDependency: {
		p1: {
			a: { if b.foo == _|_ { bar: "" } } // added
			b: { if a.bar == _|_ { new: "" } } // not added
			@test(eq, {a: {bar: ""}, b: {}})
		}
		p2: {
			a: { if b.foo == _|_ { new: "" } }
			b: { if a.bar == _|_ { foo: "" } }
			@test(eq, {a: {}, b: {foo: ""}})
		}
	}

	oneDirectionalBrokenConflictSuccess: {
		@test(permute, count=6)
		b: foo: ""
		a: { if b.foo == _|_ { bar: "" } }
		b: { if a.bar == _|_ { new: "" } }
		@test(eq, {b: {foo: "", new: ""}, a: {}})
	}
}

-- mutualsamestruct.cue --
sameStruct: {
	chainSuccess: a: {
		raises?: {}
		if raises == _|_ {
			ret: a: 1
		}
		ret?: {}
		if ret != _|_ {
			foo: a: 1
		}
		@test(eq, {raises?: {}, ret: {a: 1}, foo: {a: 1}})
	}

	chainSuccess: b: {
		if ret != _|_ {
			foo: a: 1
		}
		raises?: {}
		if raises == _|_ {
			ret: a: 1
		}
		ret?: {}
		@test(eq, {foo: {a: 1}, raises?: {}, ret: {a: 1}})
	}

	cycleFail: t1: p1: {
		raises?: {}
		if raises == _|_ {
			ret: a: 1
		}
		ret?: {}
		if ret != _|_ {
			raises: a: 1
		}
		@test(err, code=cycle|incomplete, any, contains="circular dependency in evaluation of conditionals: %v changed after evaluation", args=[raises])
	}

	cycleFail: t1: p2: {
		ret?: {}
		if ret != _|_ {
			raises: a: 1
		}
		raises?: {}
		if raises == _|_ {
			ret: a: 1
		}
		@test(err, code=cycle, contains="circular dependency in evaluation of conditionals: %v changed after evaluation", args=[ret], pos=[2:6])
	}

	// This test should fail with a cycle error. Even though raises and ret are
	// both known to be defined, comparison against bottom requires that the
	// structs be recursively checked for errors. We disallow that here, because
	// the structs mutually depend on each other.
	// TODO: consider allowing a specific postCheck for determining if an arc
	// is erroneous.
	// Pushdown improvement: raises and ret are both defined as {} (not
	// optional), so their bottom checks resolve eagerly without a cycle.
	// raises == _|_ is false (raises is {}), so ret: a: 1 is NOT added.
	// ret != _|_ is true (ret is {}), so raises: a: 1 IS added.
	// ret still has a sub-field cyclic reference error.
	cycleFail: t2: p1: {
		raises: {}
		if raises == _|_ {
			ret: a: 1
		}
		ret: {}
		if ret != _|_ {
			raises: a: 1
		}
		@test(err, any, code=cycle|incomplete)
	}

	// Pushdown improvement: with this ordering, ret != _|_ fires first
	// (ret is {}), so raises: a: 1 is added. But raises has a sub-field
	// cyclic reference error. Result is ordering-dependent (compare p1).
	cycleFail: t2: p2: {
		ret: {}
		if ret != _|_ {
			raises: a: 1
		}
		raises: {}
		if raises == _|_ {
			ret: a: 1
		}
		@test(err, any, code=cycle|incomplete)
	}

	// Pushdown improvement: raises is _ (top), raises == _|_ is false,
	// so ret: a: 1 is NOT added. ret is _ (top), ret != _|_ is true,
	// so raises: a: 1 IS added. ret has a sub-field cyclic reference.
	// Result is ordering-dependent (compare with p2 below).
	cycleFail: t3: p1: {
		raises: _
		if raises == _|_ {
			ret: a: 1
		}
		ret: _
		if ret != _|_ {
			raises: a: 1
		}
		@test(err, any, code=cycle|incomplete)
	}

	// Pushdown improvement: different ordering gives different result.
	// Only raises has a sub-field cyclic reference error.
	cycleFail: t3: p2: {
		ret: _
		if ret != _|_ {
			raises: a: 1
		}
		raises: _
		if raises == _|_ {
			ret: a: 1
		}
		@test(err, any, code=cycle|incomplete)
	}

	defCloseSuccess: {
		#Example: {
			raises?: {
				runtime?: string
			}

			if raises == _|_ {
				ret?: _
			}
		}

		expr: #Example & {
			ret: 2
		}
		@test(eq, {
			#Example: {raises?: {runtime?: string}, ret?: _}
			expr: {ret: 2, raises?: {runtime?: string}}
		})
	}
}

-- nestedchain.cue --
// Issue
nestedChain: {
	cycleFail: {
		if #E.x != _|_ {
			#E: y: true
		}
		if #E.y == _|_ {
			#E: x: true
		}
		@test(err, code=incomplete|cycle)
		#E: [_]: bool
	}

	brokenCycleSuccess: {
		if #E.x != _|_ {
			#E: y: true
		}
		if #E.y == _|_ {
			#E: x: true
		}
		#E: [_]: bool
		#E: x:   true
		@test(eq, {#E: {y: true, x: true}})
	}

	doubleAddfail: {
		if #E.x == _|_ {
			#E: y: true
		}
		if #E.y == _|_ {
			#E: x: true
		}
		#E: [_]: bool
		@test(err, code=incomplete|cycle)
	}

	trippleSuccess: {
		if #E.x != _|_ {
			#E: y: true
		}
		if #E.y != _|_ {
			z: true
		}
		#E: x: true
		@test(eq, {#E: {y: true, x: true}, z: true})
	}
}
-- issue3836.cue --
// evalv3 regression: a conditional checking `(foo | bar) == _|_` (comparison
// to bottom) stopped working; the branch that fires when no optional field is
// set no longer triggers correctly.
// https://cuelang.org/issue/3836
issue3836: hidden: {
	_Value: {
		foo?: _
		bar?: _
		if (foo | bar) == _|_ {
			bar: type: "bar"
		}
		out: [if bar != _|_ {"bar is set"}]
	}
	_Value.out
	// Pushdown improvement: the comprehension guard (foo|bar) == _|_ now
	// correctly fires and sets bar, where previously (without pushdown) the
	// guard saw a spurious non-bottom value and did not fire.
	@test(eq, {
		_Value: {foo?: _, bar: type: "bar", out: ["bar is set"]}
		["bar is set"]
	})
}
issue3836: exposed: {
	foo?: _
	bar?: _
	if (foo | bar) == _|_ {
		bar: type: "bar"
	}
	out: [if bar != _|_ {"bar is set"}]
	@test(err, code=cycle, contains="circular dependency in evaluation of conditionals: %v changed after evaluation", args=[(foo|bar)], pos=[3:6])
}
-- issue3838.cue --
// evalv3 regression: `if in.opt != _|_` inside a list expression stopped
// evaluating correctly; evalv2 exported cleanly but evalv3 produced wrong output.
// https://cuelang.org/issue/3838
issue3838: {
	#T: {
		subject: {
			in: opt?: foo: int
			out: [
				if in.opt != _|_ {in.opt.foo},
				0,
			][0]
		}
		_test: {
			assert: {
				pass: bool
			}
			assert: {
				notOk: _
				pass:  false
			} | {
				invoke: _
				res:    subject.out
				pass:   true
			}
		}
		results: _test.assert.pass
	}
	(#T & {
		_test: assert: invoke: "foo"
	}).results
	@test(eq, {
		true
		#T: {
			subject: {in: {opt?: {foo: int}}, out: 0}
			_test: {
				assert: {pass: false, notOk: _} | {pass: true, invoke: _, res: 0}
			}
			results: _|_ @test(err, code=incomplete, contains="issue3838.#T.results: unresolved disjunction {pass:false,notOk:_} | {pass:true,invoke:_,res:0} (type struct)", pos=[22:12])
		}
	})
}
-- out/errors.txt --
[cycle] self.fail.a: cyclic reference to field b:
    ./in.cue:6:9
[cycle] self.isConcreteFail.t2.a: circular dependency in evaluation of conditionals: a.b changed after evaluation:
    ./in.cue:27:7
[cycle] issue3836.hidden._Value: circular dependency in evaluation of conditionals: (foo|bar) changed after evaluation:
    ./issue3836.cue:9:7
[cycle] issue3836.exposed: circular dependency in evaluation of conditionals: (foo|bar) changed after evaluation:
    ./issue3836.cue:26:6
[incomplete] issue3838.#T.results: unresolved disjunction {pass:false,notOk:_} | {pass:true,invoke:_,res:0} (type struct):
    ./issue3838.cue:26:12
[cycle] mutual.mutualCycleFail.b: cyclic reference to field bar:
    ./mutual.cue:10:9
[cycle] mutual.mutualCycleFail.a: undefined field: foo:
    ./mutual.cue:14:9
[cycle] sameStruct.cycleFail.t1.p1: circular dependency in evaluation of conditionals: raises changed after evaluation:
    ./mutualsamestruct.cue:28:6
[cycle] sameStruct.cycleFail.t1.p2: circular dependency in evaluation of conditionals: ret changed after evaluation:
    ./mutualsamestruct.cue:40:6
[incomplete] sameStruct.cycleFail.t2.p1.ret: cyclic reference to field a:
    ./mutualsamestruct.cue:67:6
[incomplete] sameStruct.cycleFail.t2.p2.raises: cyclic reference to field a:
    ./mutualsamestruct.cue:82:6
[incomplete] sameStruct.cycleFail.t3.p1.ret: cyclic reference to field a:
    ./mutualsamestruct.cue:98:6
[incomplete] sameStruct.cycleFail.t3.p2.raises: cyclic reference to field a:
    ./mutualsamestruct.cue:112:6
[cycle] nestedChain.cycleFail: cyclic reference to field x:
    ./nestedchain.cue:4:9
[cycle] nestedChain.doubleAddfail: cyclic reference to field x:
    ./nestedchain.cue:27:9
-- out/todo.txt --
self.fail.a: different error code in v2 (cycle) and v3 (incomplete).
self.isConcreteFail.t1.a: error in v2 (cycle) but success (b: 1) in v3.
-- out/eval/stats --
Leaks:  28
Freed:  204
Reused: 189
Allocs: 43
Retain: 0

Unifications: 214
Conjuncts:    318
Disjuncts:    16
Notifications: 2

NumCloseIDs: 112
