# This file mostly contains edge cases of disjunction semantics.
# We should ensure that all these cases can be derived from thee spec.
#
# Many of these cases were discovered as part of the development of the new
# evaluator that started at v0.7 and do not pass for that version.
-- in.cue --
cyclicalInsertInDisjunction: {
	// We do not allow lookups across unresolved disjunctions. As such, the
	// references across disjunctions are not allowed to disambiguate a
	// disjunction.
	//
	// Algorithmically, the distinction between allowing this or not is
	// achieved by allowing dereferencing of the node in which a disjunction
	// occurs to the disjunct under evaluation. Effectively this is what the
	// v0.6 evaluator does.

	ok1: {
		// In this case, unifying the comprehension into the processing of the
		// disjunctions would _not_ case the reference to cross disjunction
		// boundaries.
		c: {d: e, e: _}
		c: {d: 1, e: 2} | {d : 2, e: 2}
		@test(eq, {c: {d: 2, e: 2}})
	}
	maybe1: {
		// In this case, the reference c.e crosses a disjunction boundary.
		// However, since the reference starts from within a disjunction, this
		// could be allowed, if we define it to be.
		c: d: c.e
		c: {d: 1, e: 2} | {d : 2, e: 2}
		@test(eq, {c: {d: 2, e: 2}})
	}
	err1: {
		// This should be an error: to resolve x in c.d, we need to first
		// resolve x to a concrete value. As `x` is defined outside the
		// disjunction, and the disjunction cannot be resolved without it, `x`
		// cannot be evaluated to a concrete value, resulting in an error.
		@test(permute, count=6)
		x: c.e
		c: d: x
		c: {d: 1, e: 2} | {d : 2, e: 2}
		@test(eq, {x: 2, c: {d: 2, e: 2}})
	}
	err2: {
		// This is a more obviously erroneous variant of err1. The div in x
		// requires a concrete value for c.e. However, the disjunction can only
		// be disambiguated as a result of computing x. This means c.e cannot
		// be concrete at the time of evaluation.
		// NOTE: not so obvious. It clearly works, and arguably it should.
		@test(permute, count=6)
		x: div(c.e, 2)
		c: d: x
		c: {d: 1, e: 2} | {d : 2, e: 3}
		@test(eq, {x: 1, c: {d: 1, e: 2}})
	}
	err3: {
		// Here it is clear that d will not disambiguate the disjunction at c.
		// So c.e. cannot be resolved.
		@test(permute, count=6)
		x: c.e @test(err, code=incomplete, contains="unresolved disjunction", pos=[0:6])
		c: d: 1 | 2 | x
		c: {d: 1, e: 2} | {d : 2, e: 3}
		@test(eq, {
			x: _|_ @test(err, code=incomplete, contains="unresolved disjunction", pos=[4:6])
			c: {d: 1, e: 2} | {d: 2, e: 3}
		})
	}
	@test(eq, {
		ok1: {c: {d: 2, e: 2}}
		maybe1: {c: {d: 2, e: 2}}
		err1: {x: 2, c: {d: 2, e: 2}}
		err2: {x: 1, c: {d: 1, e: 2}}
		err3: {
			x: _|_ @test(err, code=incomplete, contains="unresolved disjunction {d:1,e:2} | {d:2,e:3} (type struct)", pos=[53:6])
			c: {d: 1, e: 2} | {d: 2, e: 3}
		}
	})
}

preseveBottomSemantics: ok: {
	// `a: a`, which just says that `a` is `a`, is logically equivalent to
	// `a: _`. Consequently, the disjunction `a: a | X` is equivalent to
	// `a: _ | X`. As we do not simplify disjunctions, the result is `a: _ | X`.
	// However, we could consider simplifying in this case to `a: _` if we can
	// come up with a good general mechanism for subsumption.
	#x: { #x | {} }
	@test(eq, {#x: {}})
}

eliminateOpenVariant: ok: {
	// We have two structs in the disjunction that are identical in every way
	// except openness. TODO: should we disambiguate those?
	c: {a: 1} | #b
	#b: {a: 1}
	@test(eq, {c: {a: 1} | {a: 1}, #b: {a: 1}})
}

cyclicSelfElimination: ok: {
	// This should resolve to {y: 1} (works for new evaluator)
	y: 1 | {y: 1}
	y
	@test(eq, {y: 1})
}

// Value aliases must resolve to the specific disjunct arm's value,
// not the original unresolved disjunction.
issue4251_notC: {
	#NotC: x={
		string
		_must: true & x != "c" @test(err, code=incomplete, contains="non-concrete value string in operand to !=", pos=[0:17, -2:11, -1:3])
	}
	@test(err, at=#NotC._must, =incomplete, contains="non-concrete value string in operand to !=", pos=[3:17, 1:11, 2:3])

	a: #NotC
	a: "a" | "b"
	a: "a" @test(eq, {"a", _must: true})
}
issue4251_notB: {
	#NotB: x={
		string
		_must: true & x != "b" @test(err, code=incomplete, contains="non-concrete value string in operand to !=", pos=[0:17, -2:11, -1:3])
	}
	@test(err, at=#NotB._must, code=incomplete, contains="non-concrete value string in operand to !=", pos=[3:17, 1:11, 2:3])
	a: #NotB
	a: "a" | "b"
	a: "a" @test(eq, {"a", _must: true})
}
issue4251_notB_reversed: {
	#NotB: x={
		string
		_must: true & x != "b" @test(err, code=incomplete, contains="non-concrete value string in operand to !=", pos=[0:17, -2:11, -1:3])
	}
	@test(err, at=#NotB._must, code=incomplete, contains="non-concrete value string in operand to !=", pos=[3:17, 1:11, 2:3])
	a: #NotB
	a: "b" | "a"
	a: "a" @test(eq, {"a", _must: true})
}
-- panic.cue --
// This used to cause a panic with evalv3.
panic1: {
	#TypeFoo: {
		name:      "foo"
		// Make foo_field here optional and master panics too!
		foo_field: string
	}
	#TypeBar: {
		#TypeBar2
		bar_field?: string
	}

	#TypeBar2: name: "bar"

	objs: obj1: {
		type: #TypeFoo | #TypeBar
		type: *objs["missing"] | #TypeFoo | #TypeBar
	} 	@test(eq, {
		type: {name: "foo", foo_field: string} | {bar_field?: string, name: "bar"}
	})

	out: #TypeFoo | #TypeBar
	out: objs["obj1"].type
	@test(eq, {
		#TypeFoo: {name: "foo", foo_field: string}
		#TypeBar: {bar_field?: string, name: "bar"}
		#TypeBar2: {name: "bar"}
		objs: {
			obj1: {
				type: {name: "foo", foo_field: string} | {bar_field?: string, name: "bar"}
			}
		}
		out: {name: "foo", foo_field: string} | {bar_field?: string, name: "bar"}
	})
}
-- out/errors.txt --
[incomplete] cyclicalInsertInDisjunction.err3.x: unresolved disjunction {d:1,e:2} | {d:2,e:3} (type struct):
    ./in.cue:54:6
[incomplete] issue4251_notC.#NotC._must: non-concrete value string in operand to !=:
    ./in.cue:104:17
    ./in.cue:102:11
    ./in.cue:103:3
[incomplete] issue4251_notB.#NotB._must: non-concrete value string in operand to !=:
    ./in.cue:115:17
    ./in.cue:113:11
    ./in.cue:114:3
[incomplete] issue4251_notB_reversed.#NotB._must: non-concrete value string in operand to !=:
    ./in.cue:125:17
    ./in.cue:123:11
    ./in.cue:124:3
-- out/eval/stats --
Leaks:  24
Freed:  175
Reused: 144
Allocs: 55
Retain: 0

Unifications: 83
Conjuncts:    228
Disjuncts:    53
Notifications: 9

NumCloseIDs: 64
-- out/todo.txt --
issue4251_notC, issue4251_notB, issue4251_notB_reversed: these are only evaluated correctly by v3.
cyclicalInsertInDisjunction.err1: should be an error for both evaluators but both produce concrete values.
preseveBottomSemantics: both evaluators are incorrect; result should be _ or _|{}.
