# Tests the explicitopen experiment: allows using the spread operator (...) to
# explicitly open closed structs. NewClosed1: {#A} is closed; NewOpen1: {#A...}
# is open. The test covers interactions between old and new style closedness.
-- cue.mod/module.cue --
module: "mod.test"

language: version: "v0.15.0"

-- old.cue --
package foo

@test(shareID=AOpen2, at=#A)
#A: a: 1
Old1: {
	{
		#A
		b: 1
	}
}

#Old2: {
	{
		#A
		b: 1
	}
}

useNewClosed1: {
	t1: err: NewClosed1 & {c: 3} @test(err, code=eval, contains="field not allowed", pos=[0:25])
	t2: ok:  {NewClosed1, c: 3} @test(eq, { a: 1, c: 3 })
}

useNewClosed2: {
	t1: err: #NewClosed2 & {c: 3} @test(err, code=eval, contains="field not allowed", pos=[0:26])
	t2: ok:  {#NewClosed2, c: 3} @test(eq, { a: 1, b: 1, c: 3 })
}

useNewOpen1: {
	t1: ok: NewOpen1 & {c: 3} @test(eq, { a: 1, b: 1, c: 3 })
	t2: ok: {NewOpen1, c: 3} @test(eq, { a: 1, b: 1, c: 3 })
}

useNewOpen2: {
	t1: ok: NewOpen2 & {c: 3} @test(eq, { a: 1, c: 3 })
	t2: ok: {NewOpen2, c: 3} @test(eq, { a: 1, c: 3 })
}

combineold: {
	// all {a: 1}
	a0: ok: #A & NewClosed1 @test(eq, { a: 1 })
	a1: ok: {#A, NewClosed1} @test(eq, { a: 1 })

	a2: ok: NewClosed1 & NewOpen2 @test(eq, { a: 1 })
	a3: ok: {NewClosed1, NewOpen2} @test(eq, { a: 1 })

	// all {a: 1, b: 2}
	b0: ok: Old1 & #NewClosed2 @test(eq, { a: 1, b: 1 })
	b1: ok: #Old2 & #NewClosed2 @test(eq, { a: 1, b: 1 })

	c1: ok: {#NewClosed2,  Old1} @test(eq, { a: 1, b: 1 })
	c2: ok: {#NewClosed2,  #Old2} @test(eq, { a: 1, b: 1 })

	// {a: 1} and {a: 1, b: 2}
	e0: err: #A & Old1 @test(err, code=eval, contains="field not allowed", pos=[-47:3])
	e1: err: #A & #Old2 @test(err, code=eval, contains="field not allowed", pos=[-41:3])
	e2: err: #A & #NewClosed2 @test(err, code=eval, contains="field not allowed", pos=[new.cue:12:2])
	e3: err: #A & NewOpen1 @test(err, code=eval, contains="field not allowed", pos=[new.cue:17:2])

	e4: ok: {#A, Old1} @test(eq, { a: 1, b: 1 })
	e5: ok: {#A, #Old2} @test(eq, { a: 1, b: 1 })
	e6: ok: {#A, #NewClosed2} @test(eq, { a: 1, b: 1 })
	e7: ok: {#A, NewOpen1} @test(eq, { a: 1, b: 1 })

	f0: err: NewClosed1 & Old1 @test(err, code=eval, contains="field not allowed", pos=[-57:3])
	f1: err: NewClosed1 & #Old2 @test(err, code=eval, contains="field not allowed", pos=[-51:3])
	f2: err: NewClosed1 & #NewClosed2 @test(err, code=eval, contains="field not allowed", pos=[new.cue:12:2])
	f3: err: NewClosed1 & NewOpen1 @test(err, code=eval, contains="field not allowed", pos=[new.cue:17:2])

	f4: ok: {NewClosed1, Old1} @test(eq, { a: 1, b: 1 })
	f5: ok: {NewClosed1, #Old2} @test(eq, { a: 1, b: 1 })
	f6: ok: {NewClosed1, NewClosed1} @test(eq, { a: 1 })
	f7: ok: {NewClosed1, #NewClosed2} @test(eq, { a: 1, b: 1 })
	f8: ok: {NewClosed1, NewOpen1} @test(eq, { a: 1, b: 1 })
}

-- new.cue --
@experiment(explicitopen)

package foo

NewClosed1: {
	@test(shareID=NC1)
	#A
}

#NewClosed2: {
	#A...
	b: 1
}

NewOpen1: {
	#A...
	b: 1
}

NewOpen2: #A... @test(shareID=AOpen2)

useOld1: {
	t1: err: Old1 & {c: 3} @test(err, code=eval, contains="field not allowed", pos=[0:19])
	t2: err: {Old1, c: 3} @test(err, code=eval, contains="field not allowed", pos=[0:18]) // TODO
	t3: ok:  {Old1..., c: 3} @test(eq, {a: 1, b: 1, c: 3})
}

useOld2: {
	t1: err: #Old2 & {c: 3} @test(err, code=eval, contains="field not allowed", pos=[0:20])
	t2: err: {#Old2, c: 3} @test(err, code=eval, contains="field not allowed", pos=[0:19])
	t3: ok:  {#Old2..., c: 3} @test(eq, {a: 1, b: 1, c: 3})
}

combinenew: {
	// all {a: 1}
	a0: ok: #A & NewClosed1 @test(eq, {a: 1})
	a1: ok: {#A, NewClosed1} @test(eq, {a: 1})

	a2: ok: NewClosed1 & NewOpen2 @test(eq, {a: 1})
	a3: ok: {NewClosed1, NewOpen2} @test(eq, {a: 1})
	a4: ok: {NewClosed1..., NewOpen2} @test(eq, {a: 1})

	a5: ok: NewOpen2 & NewClosed1 @test(eq, {a: 1})
	a6: ok: {NewOpen2, NewClosed1} @test(eq, {a: 1})
	a7: ok: {NewOpen2..., NewClosed1} @test(eq, {a: 1})

	// all {a: 1, b: 2}
	b0: ok: Old1 & #NewClosed2 @test(eq, {a: 1, b: 1})
	b1: ok: #Old2 & #NewClosed2 @test(eq, {a: 1, b: 1})
	b2: ok: #NewClosed2 & Old1 @test(eq, {a: 1, b: 1})
	b3: ok: #NewClosed2 & #Old2 @test(eq, {a: 1, b: 1})

	c1: ok: {#NewClosed2,  Old1} @test(eq, {a: 1, b: 1})
	c2: ok: {#NewClosed2,  #Old2} @test(eq, {a: 1, b: 1})
	c3: ok: {Old1, #NewClosed2} @test(eq, {a: 1, b: 1})
	c4: ok: {#Old2, #NewClosed2} @test(eq, {a: 1, b: 1})

	d0: ok: {#NewClosed2,  Old1...} @test(eq, {a: 1, b: 1})
	d1: ok: {#NewClosed2,  #Old2...} @test(eq, {a: 1, b: 1})
	d2: ok: {#NewClosed2...,  Old1} @test(eq, {a: 1, b: 1})
	d3: ok: {#NewClosed2...,  #Old2} @test(eq, {a: 1, b: 1})
	d4: ok: {#NewClosed2...,  Old1...} @test(eq, {a: 1, b: 1})
	d5: ok: {#NewClosed2...,  #Old2...} @test(eq, {a: 1, b: 1})

	d6: ok: {Old1..., #NewClosed2} @test(eq, {a: 1, b: 1})
	d7: ok: {#Old2..., #NewClosed2} @test(eq, {a: 1, b: 1})
	d8: ok: {Old1, #NewClosed2...,} @test(eq, {a: 1, b: 1})
	d9: ok: {#Old2, #NewClosed2...,} @test(eq, {a: 1, b: 1})
	d10: ok: {Old1..., #NewClosed2...,} @test(eq, {a: 1, b: 1})
	d11: ok: {#Old2..., #NewClosed2...,} @test(eq, {a: 1, b: 1})

	// {a: 1} and {a: 1, b: 2}
	e0: err: #A & Old1 @test(err, code=eval, contains="field not allowed", pos=[old.cue:8:3])
	e1: err: #A & #Old2 @test(err, code=eval, contains="field not allowed", pos=[old.cue:15:3])
	e2: err: #A & #NewClosed2 @test(err, code=eval, contains="field not allowed", pos=[-63:2])
	e3: err: #A & NewOpen1 @test(err, code=eval, contains="field not allowed", pos=[-59:2])

	e4: err: {#A, Old1} @test(err, code=eval, contains="field not allowed", pos=[old.cue:8:3])
	e5: err: {#A, #Old2} @test(err, code=eval, contains="field not allowed", pos=[old.cue:15:3])
	e6: err: {#A, #NewClosed2} @test(err, code=eval, contains="field not allowed", pos=[-68:2])
	e7: err: {#A, NewOpen1} @test(err, code=eval, contains="field not allowed", pos=[-64:2])

	e8: ok: {#A..., Old1} @test(eq, {a: 1, b: 1})
	e9: ok: {#A..., #Old2} @test(eq, {a: 1, b: 1})
	e10: ok: {#A..., #NewClosed2} @test(eq, {a: 1, b: 1})
	e11: ok: {#A..., NewOpen1} @test(eq, {a: 1, b: 1})

	f0: err: NewClosed1 & Old1 @test(err, code=eval, contains="field not allowed", pos=[old.cue:8:3])
	f1: err: NewClosed1 & #Old2 @test(err, code=eval, contains="field not allowed", pos=[old.cue:15:3])
	f2: err: NewClosed1 & #NewClosed2 @test(err, code=eval, contains="field not allowed", pos=[-78:2])
	f3: err: NewClosed1 & NewOpen1 @test(err, code=eval, contains="field not allowed", pos=[-74:2])
	f4: ok: NewClosed1 & NewClosed1 @test(eq, {a: 1}) @test(shareID=NC1)

	f5: err: {NewClosed1, Old1} @test(err, code=eval, contains="field not allowed", pos=[old.cue:8:3])
	f6: err: {NewClosed1, #Old2} @test(err, code=eval, contains="field not allowed", pos=[old.cue:15:3])
	f7: err: {NewClosed1, #NewClosed2} @test(err, code=eval, contains="field not allowed", pos=[-84:2])
	f8: err: {NewClosed1, NewOpen1} @test(err, code=eval, contains="field not allowed", pos=[-80:2])
	f9: ok: {NewClosed1, NewClosed1} @test(err)

	f9: ok:  {NewClosed1..., Old1} @test(err, code=eval, contains="field not allowed", pos=[old.cue:8:3], hint="see todo")
	f10: ok: {NewClosed1..., #Old2} @test(eq, {a: 1, b: 1})
	f11: ok: {NewClosed1..., NewClosed1} @test(eq, {a: 1})
	f12: ok: {NewClosed1..., #NewClosed2} @test(eq, {a: 1, b: 1})
	f13: ok: {NewClosed1..., NewOpen1} @test(eq, {a: 1, b: 1})

	f14: err: Old1 & NewClosed1 @test(eq, {a: 1, b: 1}, hint="see todo")
	f15: err: #Old2 & NewClosed1 @test(eq, {a: 1, b: 1}, hint="see todo")
	f16: err: #NewClosed2 & NewClosed1 @test(err, code=eval, contains="field not allowed", pos=[-96:2])
	f17: err: NewOpen1 & NewClosed1 @test(err, code=eval, contains="field not allowed", pos=[-92:2])
	f18: ok: NewClosed1 & NewClosed1 @test(eq, {a: 1}) @test(shareID=NC1)
}
-- issues.cue --
@experiment(explicitopen)
// explicitopen experiment: postfix `...` spread cannot directly combine with
// `&`; tests open-struct spread behavior and non-struct operand tolerance.
// https://cuelang.org/issue/4150
issue4150: {
	#Def: a: int

	x: #Def... & {
			a: 42
			b: 42
	} @test(eq, {a: 42, b: 42}) @test(closed)

	foo: {a: int}
	bar: {b: int}
	y: foo... & bar... @test(eq, {a: int, b: int})

	// NOTE: we allow this, even though it doesn't mean anything. The benefit
	// of this is that code doesn't become brittle using the spread operator
	// for references. Consider this example:
	//
	//  #Def: a: int
	//	foo: 1 | #Def
	//	bar: foo...
	//
	// Not allowing ... on any expression would mean #foo... may or may not be
	// erroneous, depending on the value foo is set to.
	z: 1... @test(eq, 1)
}
-- out/errors.txt --
[eval] useOld1.t1.err.c: field not allowed:
    ./new.cue:23:19
[eval] useOld1.t2.err.c: field not allowed:
    ./new.cue:24:18
[eval] useOld2.t1.err.c: field not allowed:
    ./new.cue:29:20
[eval] useOld2.t2.err.c: field not allowed:
    ./new.cue:30:19
[eval] combinenew.e0.err.b: field not allowed:
    ./old.cue:8:3
[eval] combinenew.e1.err.b: field not allowed:
    ./old.cue:15:3
[eval] combinenew.e2.err.b: field not allowed:
    ./new.cue:12:2
[eval] combinenew.e3.err.b: field not allowed:
    ./new.cue:17:2
[eval] combinenew.e4.err.b: field not allowed:
    ./old.cue:8:3
[eval] combinenew.e5.err.b: field not allowed:
    ./old.cue:15:3
[eval] combinenew.e6.err.b: field not allowed:
    ./new.cue:12:2
[eval] combinenew.e7.err.b: field not allowed:
    ./new.cue:17:2
[eval] combinenew.f0.err.b: field not allowed:
    ./old.cue:8:3
[eval] combinenew.f1.err.b: field not allowed:
    ./old.cue:15:3
[eval] combinenew.f2.err.b: field not allowed:
    ./new.cue:12:2
[eval] combinenew.f3.err.b: field not allowed:
    ./new.cue:17:2
[eval] combinenew.f5.err.b: field not allowed:
    ./old.cue:8:3
[eval] combinenew.f6.err.b: field not allowed:
    ./old.cue:15:3
[eval] combinenew.f7.err.b: field not allowed:
    ./new.cue:12:2
[eval] combinenew.f8.err.b: field not allowed:
    ./new.cue:17:2
[eval] combinenew.f9.ok.b: field not allowed:
    ./old.cue:8:3
[eval] combinenew.f16.err.b: field not allowed:
    ./new.cue:12:2
[eval] combinenew.f17.err.b: field not allowed:
    ./new.cue:17:2
[eval] useNewClosed1.t1.err.c: field not allowed:
    ./old.cue:20:25
[eval] useNewClosed2.t1.err.c: field not allowed:
    ./old.cue:25:26
[eval] combineold.e0.err.b: field not allowed:
    ./old.cue:8:3
[eval] combineold.e1.err.b: field not allowed:
    ./old.cue:15:3
[eval] combineold.e2.err.b: field not allowed:
    ./new.cue:12:2
[eval] combineold.e3.err.b: field not allowed:
    ./new.cue:17:2
[eval] combineold.f0.err.b: field not allowed:
    ./old.cue:8:3
[eval] combineold.f1.err.b: field not allowed:
    ./old.cue:15:3
[eval] combineold.f2.err.b: field not allowed:
    ./new.cue:12:2
[eval] combineold.f3.err.b: field not allowed:
    ./new.cue:17:2
-- out/diff-v2-v3.txt --
f9: combinenew.f9.ok is in error because two f9 declarations conflict.
f14/f15: combinenew.f14.err and f15.err are NOT in error (v3 allows these).
Structure sharing: NewOpen2: ~(#A), combinenew.f4.ok ~(NewClosed1), combinenew.f18.ok ~(NewClosed1).
-- out/eval/stats --
Leaks:  0
Freed:  432
Reused: 343
Allocs: 89
Retain: 0

Unifications: 432
Conjuncts:    1147
Disjuncts:    0

NumCloseIDs: 488
