#name: comparison
#evalPartial
-- cue.mod/module.cue --
module: "x.test"
language: version: "v0.14.0"
-- in.cue --
// Explanation of error markers:
// t: true
// f: false
// e: permanent error
// i: incomplete error
numbers: {
	tLss: 1 < 2             @test(eq, true)
	tLeq: 1 <= 1.0          @test(eq, true)
	tLeq: 2.0 <= 3          @test(eq, true)
	tEql: 1 == 1.0          @test(eq, true)
	tNeq: 1.0 == 1          @test(eq, true)
	tGeq: 2.0 >= 2          @test(eq, true)
	tGtr: !(2 > 3)          @test(eq, true)
	tExpr: "a"+"b" == "ab"  @test(eq, true)
}
-- lists.cue --
lists: {
	// Equal lists
	t1: [] == []                                @test(eq, true)
	t2: [1, 2, 3] == [1, 2, 3]                  @test(eq, true)
	t3: [1, "foo", true] == [1, "foo", true]    @test(eq, true)
	t4: [[1, 2], [3, 4]] == [[1, 2], [3, 4]]    @test(eq, true)
	t5: ([1, ...int] & [1, 2, 3]) == [1, 2, 3]  @test(eq, true)
	t6: [1] == ([...1] & [_])                   @test(eq, true)
	t7: [{a: 1}] == ([...{a: 1}] & [_])         @test(eq, true)
	t8: [{a: 1}] != [...{a: 1}]                 @test(eq, true) // true as it is not equal
	t9: [...int] == [...string]                 @test(eq, true) // additional types are irrelevant.

	// Non-equal lists
	f1: [1, 2, 3] == [1, 2, 4]        @test(eq, false)
	f2: [1, 2, 3] == [1, 2]           @test(eq, false)
	f3: [1, 2, 3] == [1, 3, 2]        @test(eq, false)  // different order
	f4: [] == [1]                     @test(eq, false)
	f5: [1, 2] == [1, "2"]            @test(eq, false)  // different types
	f6: [*1 | 2 | 3] == [*2 | 3 | 4]  @test(eq, false)


	// TODO: types can never match. This could be a permanent error.
	//       This was V2 behavior.
	eIncompat: [int] == [string]   @test(err, code=incomplete|eval, pos=[0:13]) @test(err:todo, code=eval, pos=[0:13])

	// Error cases
	eErrPassthrough: [1/0] == [1]  @test(err, code=eval, contains="invalid", pos=[0:19])

	// incomplete passtrhough
	_e3l: {}
	eErrPassthrough: [_e3l.b] == [1]  @test(err, code=eval,
		suberr=(code=eval, contains="division by zero", pos=[-4:19]),
		suberr=(code=eval, contains="conflicting values", pos=[6:19]),
	)

	// error reported as part of combined eErrPassthrough error
	eErrPassthrough: [1, 2] == [1, int & string]

	// Inequality tests
	tNeq1: [1, 2] != [1, 3]  @test(eq, true)
	fNeq2: [1, 2] != [1, 2]  @test(eq, false)
}
-- structs.cue --
@experiment(structcmp)

structs: eq: {
	// Equal structs
	t1: {} == {}                                                     @test(eq, true)
	t2: {a: 1, b: "foo"} == {a: 1, b: "foo"}                         @test(eq, true)
	t3: {a: 1, b: 2} == {b: 2, a: 1}                                 @test(eq, true) // different order
	t4: {a: {x: 1, y: 2}} == {a: {x: 1, y: 2}}                       @test(eq, true) // nested structs
	t5: ({a: 1, b: 2, c: 3} & {d: 4}) == {a: 1, b: 2, c: 3, d: 4}    @test(eq, true) // unification
	t6: {{{{a: 1}}}} == {{a: 1}}                                     @test(eq, true) // embedding
	t7: {[string]: int} == {[string]: string}                        @test(eq, true)
	_t8l: *1 | 2 | 3
	t8: {a: _t8l} == {a: 3| *1}                                      @test(eq, true)

	// Non-equal structs
	f1: {a: 1, b: "foo"} == {a: 2, b: "foo"}  @test(eq, false)
	f2: {a: 1, b: "foo"} == {a: 1, c: "foo"}  @test(eq, false)
	f3: {a: {x: 1}} == {a: {x: 2}}            @test(eq, false)
	f4: {a: 1, b: 2} == {a: 1}                @test(eq, false)
	f5: {} == {a: 1}                          @test(eq, false)
	f6: {a: 1} == 1                           @test(eq, false)
	f7: {a: 1, b?: 2} == {a: 1, b: 2}         @test(eq, false) // optional field does not exist
	f8: {{{{a: 1}}}} == {{a: 1, b: 2}}        @test(eq, false) // embedding
	_f9r:  *2 | 3 | 4
	f9: {a: *1 | 2 | 3} == {a: _f9r}          @test(eq, false)

	// Only test some versions of != recognizing the same code paths are
	// followed for ==.
	tNe1: {a: 1} != {a: 2}   @test(eq, true)
	fNe1: {a: 2} != {a: 2}   @test(eq, false)
}
ignore: {
	t0: {_hidden: 1} == {_hidden: 2}   @test(eq, true)
	t1: {_hidden: 1} == {}             @test(eq, true)
	t1: {} == {_hidden: 1}             @test(eq, true)
	t2: {#def: 1} == {#def: 2}         @test(eq, true)
	t3: {#def: 1} == {}                @test(eq, true)
	t4: {} == {#def: 1}                @test(eq, true)
	t5: {_#hiddenDef: 1} == {_#hiddenDef: 2}  @test(eq, true)
	t6: {_#hiddenDef: 1} == {}         @test(eq, true)
	t7: {} == {_#hiddenDef: 1}         @test(eq, true)

	f0: {_hidden: 1} != {_hidden: 2}   @test(eq, false)
	f1: {_hidden: 1} != {}             @test(eq, false)
	f1: {} != {_hidden: 1}             @test(eq, false)
	f2: {#def: 1} != {#def: 2}         @test(eq, false)
	f3: {#def: 1} != {}                @test(eq, false)
	f4: {} != {#def: 1}                @test(eq, false)
	f5: {_#hiddenDef: 1} != {_#hiddenDef: 2}  @test(eq, false)
	f6: {_#hiddenDef: 1} != {}         @test(eq, false)
	f7: {} != {_#hiddenDef: 1}         @test(eq, false)
}
embeddedScalars: {
	_one: 1
	_two: 2

	t1: 1 == {1}              @test(eq, true)
	t2: [1] == [{1}]          @test(eq, true)
	t3: {a: 1} == {a: {1}}    @test(eq, true)
	t4: [_one] == [{_one}]    @test(eq, true)

	f1: _one == [{_two}]      @test(eq, false)
	f2: {a: _two} == {a: {_one}}  @test(eq, false)
	f3: _one == {_two}        @test(eq, false)
	f4: [_two] == [{_one}]    @test(eq, false)
}

// Error cases
incomplete: {
	_int:    int
	_string: string

	iErr1: {a!: _int} == {a!: _int}       @test(err, code=incomplete, contains="field is required but not present", pos=[0:9])
	iErr2: {a!: _int} == {a!: 1}          @test(err, code=incomplete, contains="field is required but not present", pos=[0:9])
	eErr3: {a: _int} == {a: _string}      @test(err, code=incomplete|eval, contains="incomplete value int", pos=[0:9]) @test(err:todo, code=eval)
	eErr4: {a: 1/0} == {a: 1}             @test(err, code=eval, contains="failed arithmetic", pos=[0:9])        // Error passthrough

	eErr5: {a: 1} == {a: _int & _string}  @test(err, code=eval, contains="conflicting values", pos=[0:9]) // Error passthrough
	iErr6: {a!: 1} == {a: 1}             @test(err, code=incomplete, contains="field is required but not present", pos=[0:9])
	eErr7: {a: 1} == {a!: 2}             @test(err, code=incomplete, contains="field is required but not present", pos=[0:9]) @test(err:todo, code=eval) // Required cannot match even when concrete.

	// Only test some versions of != recognizing the same code paths are
	// followed for ==.
	tNe1: {a: 1} != {a: 2}   @test(eq, true)
	fNe1: {a: 2} != {a: 2}   @test(eq, false)
}
-- typediff.cue --
@experiment(structcmp)

diffTypes: {
	// Right now, all these tests are expected to report a type error. The
	// result indicators are assuming, however, that we define equality to be
	// total for all concrete values. This means that those marked as false (or
	// true for !=) are permanent errors.

	// Tests with different types.
	fScalar1:  2 == "s"     @test(eq, false)
	fScalar2:  "s" == 2     @test(eq, false)
	fScalar3: true == 2     @test(eq, false)

	fStruct1: {a: 1} == "s"  @test(eq, false)

	fList1: 5 == [5]           @test(eq, false)
	fList2: "ab" == ["a", "b"] @test(eq, false)
	fList3: true == [true]     @test(eq, false)
	fList4: {a: 1} == [{a: 1}] @test(eq, false)
	fList5: [1, 2] == 3        @test(eq, false)
	fList6: [1, 2] == "ab"     @test(eq, false)
	fList7: [true] == false    @test(eq, false)
	fList8: [1, 2] == {a: 1}   @test(eq, false)
	fList9: [1, 2] == null     @test(eq, false)

	// Negated comparisons
	tNeList1: [1, 2] != 3     @test(eq, true)
	tNeList2: [1, 2] != "ab"  @test(eq, true)

	_int:    int
	_string: string

	// Lists with incompatible element types
	iRecursive1: [_int] == [_int]     @test(err, code=incomplete, contains="incomplete value int", pos=[0:15])    // can still match, but not concrete
	eRecursive2: [_int] == [_string]  @test(err, code=incomplete, contains="incomplete value int", pos=[0:15]) @test(err:todo, code=eval) // can never match
	fRecursive3: [1, 2] == [1, "2"]       @test(eq, false)
	tRecursive4: [...int] == [...string]  @test(eq, true)
	fRecursive5: [1, "a"] == [1, 2]       @test(eq, false)

	eNe1: [1, 2] != [_int]    @test(err, code=incomplete, contains="incomplete value int", pos=[0:8]) @test(err:todo, code=eval) // length can never match
	iNe2: [_int] != [1]       @test(err, code=incomplete, contains="incomplete value int", pos=[0:8])
	iNe3: {a: 1} != {a: _int} @test(err, code=incomplete, contains="incomplete value int", pos=[0:8])
	iNe4: {a: _int} != {a: 1} @test(err, code=incomplete, contains="incomplete value int", pos=[0:8])
}
-- out/errors.txt --
[incomplete] lists.eIncompat: invalid left-hand value to '==' (type list): 0: incomplete value int:
    ./lists.cue:24:13
[eval] lists.eErrPassthrough: invalid left-hand value to '==' (type list): 0: failed arithmetic: division by zero:
    ./lists.cue:27:19
lists.eErrPassthrough: invalid right-hand value to '==' (type list): 1: conflicting values int and string (mismatched types int and string):
    ./lists.cue:37:19
[incomplete] incomplete.iErr1: invalid left-hand value to '==' (type struct): a: field is required but not present:
    ./structs.cue:73:9
[incomplete] incomplete.iErr2: invalid left-hand value to '==' (type struct): a: field is required but not present:
    ./structs.cue:74:9
[incomplete] incomplete.eErr3: invalid left-hand value to '==' (type struct): a: incomplete value int:
    ./structs.cue:75:9
[eval] incomplete.eErr4: invalid left-hand value to '==' (type struct): a: failed arithmetic: division by zero:
    ./structs.cue:76:9
[eval] incomplete.eErr5: invalid right-hand value to '==' (type struct): a: conflicting values string and int (mismatched types string and int):
    ./structs.cue:78:9
[incomplete] incomplete.iErr6: invalid left-hand value to '==' (type struct): a: field is required but not present:
    ./structs.cue:79:9
[incomplete] incomplete.eErr7: invalid right-hand value to '==' (type struct): a: field is required but not present:
    ./structs.cue:80:9
[incomplete] diffTypes.iRecursive1: invalid left-hand value to '==' (type list): 0: incomplete value int:
    ./typediff.cue:34:15
[incomplete] diffTypes.eRecursive2: invalid left-hand value to '==' (type list): 0: incomplete value int:
    ./typediff.cue:35:15
[incomplete] diffTypes.eNe1: invalid right-hand value to '!=' (type list): 0: incomplete value int:
    ./typediff.cue:40:8
[incomplete] diffTypes.iNe2: invalid left-hand value to '!=' (type list): 0: incomplete value int:
    ./typediff.cue:41:8
[incomplete] diffTypes.iNe3: invalid right-hand value to '!=' (type struct): a: incomplete value int:
    ./typediff.cue:42:8
[incomplete] diffTypes.iNe4: invalid left-hand value to '!=' (type struct): a: incomplete value int:
    ./typediff.cue:43:8
-- out/diff-v2-v3.txt --
lists.eIncompat: v3 reports "incomplete" (list element type is abstract) while v2 reports "eval" (permanent type mismatch [int] vs [string]).
Verdict: worsening -- v3 does not yet promote the type mismatch to a permanent eval error.
-- out/eval/stats --
Leaks:  9
Freed:  697
Reused: 625
Allocs: 81
Retain: 0

Unifications: 630
Conjuncts:    891
Disjuncts:    59

NumCloseIDs: 85
