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

noClose: {
	foo: a: int
	a: __reclose({
		foo...
	})

	t1: ok: a & {b: 1}
	@test(eq, {
		foo: {a: int}
		a: {a: int}
		t1: {ok: {b: 1, a: int}}
	})
}

recursive: {
	#x: { foo: int}
	// Basic usage of __reclose - makes structs recursively closed
	a: __reclose({
		#x...
		nested: field2: int
	})

	// Test that extra fields are not allowed (should error)
	t1: err: a & {extra: "not allowed"} @test(err, code=eval, path=extra, contains="field not allowed", pos=[0:16])

	t2: ok: a & {nested: field2: 5} @test(eq, {nested: {field2: 5}, foo: int})
	t3: ok: a & {foo: 5}  @test(eq, {foo: 5, nested: {field2: int}})


	// Test that nested extra fields are not allowed (should error)
	t4: err: a & {
		nested: {
			field2: 1
			extra: "not allowed"
		}
	} @test(err, code=eval, path=nested.extra, contains="field not allowed", pos=[3:4])

	// Test with empty struct
	// TODO(eval): is this correct?
	e: __reclose({})
	t5: err: e & {extra: "not allowed"} @test(eq, {extra: "not allowed"})
}


nonRecursive: {
	x: __reclose({
		close({b: int})...
		c: int
		d: e: int
	})
	y: err: x & {f: int}   @test(err, code=eval, path=f, contains="field not allowed", pos=[0:15])
	z: ok: x & {d: f: int} @test(eq, {d: {f: int, e: int}, c: int, b: int})
}

// Test that __reclose detects and restores non-recursive closing from
// an embedded close()-d value that was opened with ....
nonRecursiveDetect: {
	foo: close({b: {}})
	a: __reclose({
		foo...
	})
	// Top-level field not allowed: non-recursive close restored by __reclose.
	t1: err: a & {f: int} @test(err, code=eval, path=f, contains="field not allowed", pos=[0:16])
	// Nested field allowed: close was non-recursive, so b is still open.
	t2: ok: a & {b: c: 1} @test(eq, {b: {c: 1}})
}

// Test that __reclose handles both recursive and non-recursive closing
// when both are present.
//
// NOTE: one would expect that with the old semantics, b.c is allowed in t2.
// But it is NOT. To mimic the old behavior, cue fix _could_ just use __closeAll
// in this case. Users can than chose to use __reclose instead to relax the
// condition.
mixedClose: {
	#D: {x: y: int}
	foo: close({b: {}})
	a: __reclose({
		#D...
		foo...
	})
	// Top-level extra field not allowed.
	t1: err: a & {f: int} @test(err, code=eval, path=f, contains="field not allowed", pos=[0:16])
	// Allowed fields work.
	t2: ok: a & {x: y: 5, b: c: 1} @test(eq, {x: {y: 5}, b: {c: 1}})
}

// Test that __reclose can wrap a close() call and conditionally add
// recursive closing on top. This is the pattern generated by cue fix
// when a close() at field-value level wraps a struct with embeddings.
recloseWrapsClose: {
	#def: {a: b: int}
	a: __reclose(close({
		#def...
	}))
	// Top-level extra field not allowed (close provides non-recursive).
	t1: err: a & {f: int} @test(err, code=eval, path=f, contains="field not allowed", pos=[0:16])
	// Nested extra field not allowed (__reclose detects recursive from #def).
	t2: err: a & {a: c: 1} @test(err, code=eval, path=a.c, contains="field not allowed", pos=[0:19])
	// Allowed nested field works.
	t3: ok: a & {a: b: 5} @test(eq, {a: {b: 5}})
}

nonStructLiterals: {
	// Test with non-struct should error
	t1: err: __reclose("not a struct") @test(err, code=eval, contains="error in call to %s", args=[__reclose], pos=[0:11])


	#A: a: int
	t4: err: __reclose(#A) @test(err, code=eval, contains="error in call to %s", args=[__reclose], pos=[0:11])
}

nonStructDefinitions: {
	#Def: 1

	a: __reclose({
		#Def...
	})

	t1: ok: a & 1  @test(eq, 1)

	// errors because of regular field + embedded scalar
	t2: err: a & {b: 1}  @test(err, code=eval,
		contains="conflicting values %s and %s (mismatched types %s and %s)",
		args=[{b:1}, 1, struct, int], pos=[-9:8, 0:11, 0:15])
}

-- old.cue --
retro: {
	// May not be used if explicitopen is not set.
	t1: err: __reclose({}) @test(err, code=eval, contains="error in call to %s", args=[__reclose], pos=[0:11])
}

-- out/errors.txt --
[eval] recursive.t1.err.extra: field not allowed:
    ./in.cue:26:16
[eval] recursive.t4.err.nested.extra: field not allowed:
    ./in.cue:36:4
[eval] nonRecursive.y.err.f: field not allowed:
    ./in.cue:53:15
[eval] nonRecursiveDetect.t1.err.f: field not allowed:
    ./in.cue:65:16
[eval] mixedClose.t1.err.f: field not allowed:
    ./in.cue:85:16
[eval] recloseWrapsClose.t1.err.f: field not allowed:
    ./in.cue:99:16
[eval] recloseWrapsClose.t2.err.a.c: field not allowed:
    ./in.cue:101:19
[eval] nonStructLiterals.t1.err: error in call to __reclose: argument must be a struct or list literal:
    ./in.cue:108:11
[eval] nonStructLiterals.t4.err: error in call to __reclose: argument must be a struct or list literal:
    ./in.cue:112:11
[eval] nonStructDefinitions.t2.err: conflicting values {b:1} and 1 (mismatched types struct and int):
    ./in.cue:116:8
    ./in.cue:125:11
    ./in.cue:125:15
[eval] retro.t1.err: error in call to __reclose: __reclose may only be used when explicitopen is enabled:
    ./old.cue:3:11
-- out/eval/stats --
Leaks:  0
Freed:  281
Reused: 250
Allocs: 31
Retain: 0

Unifications: 206
Conjuncts:    445
Disjuncts:    0

NumCloseIDs: 102
