# Tests various cycle patterns involving constraint propagation through pattern
# constraints, self-triggering cycles, and mutually triggering cycles.
-- in.cue --
// Not a cycle: the structure in x (c) only triggers one further
// instantiation of y, but not enough to trigger another round.
safeCycle: simple2: {
	y: [string]: b: c: y
	x: y
	x: c: y
	@test(eq, {
		y: {}
		x: {c: {b: {c: {}, b: {c: {}}}}}
	})
}

// Not a cycle: the structure in x (c) only triggers one further
// instantiation of y, but not enough to trigger another round.
safeCycle: long1: {
	y: [string]: b: y
	x: y
	x: c: b: e: b: c: _
	@test(eq, {
		y: {}
		x: {c: {b: {e: {b: {c: {b: {}}}}}}}
	})
}

// Not a cycle: the structure in x (c) only triggers one further
// instantiation of y, but not enough to trigger another round.
safeCycle: long2: {
	a: [string]: b: a
	x: a
	x: c: b: c: b: c: {}
	@test(eq, {
		a: {}
		x: {c: {b: {c: {b: {c: {b: {}}}}}}}
	})
}

// noCyclePattern is similar to the above cases, but involving more patterns
// and various lengths.
noCyclePattern: _

noCyclePattern: t1: {
	#D: [string]: #D
	a: {}
	[string]: #D
	[string]: b: c: {}
	@test(eq, {#D: {}, a: {b: {c: {}}}})
}

noCyclePattern: t2: {
	#D: [string]: x: #D
	a: {}
	[string]: #D
	[string]: b: x: c: {}
	@test(eq, {#D: {}, a: {b: {x: {c: {x: {}}}}}})
}

noCyclePattern: t3: {
	_D: [string]: _T
	_T: x: _D
	[string]: _D
	[string]: b: x: c: {}
	a: {}
	@test(eq, {
		_D: {}
		_T: {x: {}}
		a: {b: {x: {c: {x: {}}}}}
	})
}

noCyclePattern: t4: {
	_D: [string]: x: _T
	_T: y: _D
	[string]: _D
	[string]: b: x: {}
	a: {}
	@test(eq, {
		_D: {}
		_T: {y: {}}
		a: {b: {x: {y: {}}}}
	})
}

noCyclePattern: t5: {
	_D: [string]: x: _T
	_T: y: _D
	[string]: _D
	[string]: b: x: y: c: x: {}
	a: {}
	@test(eq, {
		_D: {}
		_T: {y: {}}
		a: {b: {x: {y: {c: {x: {y: {}}}}}}}
	})
}

// This example also has an embedding, in which case it should still behave
// the same.
noCyclePattern: t6: {
	#D: [string]: #E

	#E: t: #T

	#T: {
		{ object: #S }
	}

	#S: y: #D
	[string]: x: #D
	[string]: x: r: t: object: y: foo: t: object: {
	}
	bar: {}
	@test(eq, {
		#D: {}
		#E: {t: {object: {y: {}}}}
		#T: {object: {y: {}}}
		#S: {y: {}}
		bar: {
			x: {
				r: {
					t: {
						object: {y: {foo: {t: {object: {y: {}}}}}}
					}
				}
			}
		}
	})
}

// Cycle: x cyclic. The pattern constraint should not be seen as
// "adding new structure", thereby permitting the cycle.
noCancelSelfInvoke: t1: {
	y: [string]: b: y
	x: y
	x: c: x @test(err, at=b.b, code=structural_cycle) @test(err, at=c, code=structural_cycle)
	@test(err, code=structural_cycle)
}


// Even though these cycles cross pattern boundaries, they are still structural
// cycles as the reference includes a field that triggers the pattern.
selfTriggerCycle: _

// TODO: This does not show an explicit cycle within `a`, only a "child" cycle.
// However, upon investigation this error is present in the tree. Either way,
// the error only needs to be reported once, so this is not a huge problem.
selfTriggerCycle: t1: {
	a: #T @test(shareID=T)
	#T: {
		[string]: #T
		b: {} @test(err, at=b, code=structural_cycle)
	} @test(shareID=T)
}

selfTriggerCycle: t2: {
	#T: [string]: X={
		a: X
	}
	b: #T
	b: c: {} @test(err, at=a, code=structural_cycle)
}

selfTriggerCycle: long1: {
	// The long string of nested fields will initially exempt the structural
	// cycle, but they will eventually run out, causing the cycle to be
	// triggered.
	a: [string]: b: a     // -> record conjunct from pattern per field.
	a: c: b: c: b: c: {}  // -> track if any of the fields is not a cycle.
	@test(err, at=a.c.b.c.b.c.b, code=structural_cycle)
}

// TODO: see comment at selfTriggerCycle.t1
selfTriggerCycle: issue1503: {
	a: #T & {
		a: one: link: a: two: {}
	}
	#T: {
		a: [string]: link: #T
		a: b: {}
	}
	@test(err, at=#T.a.b.link, code=structural_cycle)
	@test(err, at=a.a.b.link, code=structural_cycle)
	@test(err, at=a.a.one.link.a.b.link, code=structural_cycle)
	@test(err, at=a.a.one.link.a.two.link, code=structural_cycle)
}

mutuallyTriggeringCycle: t1: {
	// Even though y itself, as well as each of the x fields are not cyclic,
	// the combination of the two x conjuncts are, as the b fields of y will
	// trigger the pattern constraints in an interleaved fashion.
	y: [string]: b: y
	x: y
	x: c: y
	@test(err, at=x.c.b.b.b.b.b, code=structural_cycle)
}
-- ring.cue --
brokenRing: t1: {
	// NOTE that two permutations here have different results. That is less
	// of an issue than it seems, as the structural cycle is hidden behind
	// an optional path, which we generally allow.
	// TODO: it would be nicer, though, if the representation were consistent.
	p1: {
		D: a?: T
		T: b: D
		@test(err, code=structural_cycle)
	 	// @test(debug)
	}
	p2: {
		// TODO(eval): fix permute or eval: error messages differ based on path in the two cases.
		// @test(permute, count=2)
		T: b: D
		D: a?: T
		@test(eq, {
			T: {
				b: {
					a?: _|_ @test(err, code=structural_cycle, contains="structural cycle", pos=[])
				}
			}
			D: {
				a?: _|_ @test(err, code=structural_cycle, contains="structural cycle", pos=[])
			}
		})
	}
}

brokenRing: t2: p1: {
	D: [string]: T
	T: b: D
	@test(eq, {D: {}, T: {b: {}}})
}

brokenRing: t2: p2: {
	T: b: D
	D: [string]: T
	@test(eq, {T: {b: {}}, D: {}})
}

cyclicRing: t1: {
	D: a: T
	T: b: D
	@test(err, at=T.b, code=structural_cycle)
	@test(err, at=D.a.b, code=structural_cycle)
	// TODO(inline): fix shareID here.
	// @test(shareID=T, at=D.a)
	// @test(shareID=T, at=T)
}
-- issue3476.cue --
// `cue def -e '#a'` causes a stack overflow. The definition #a selects a
// field from a conjunction `(#b & {x: _}).x` where #b contains a
// self-referential pattern `#c: [string]: #c`. This combination causes the
// exporter to recurse infinitely instead of detecting the cycle.
// https://cuelang.org/issue/3476
issue3476: {
	#a:  (#b & {x: _}).x
	#b: {
		x?: #c
		#c: [string]: #c
	}
	@test(eq, {#a: {}, #b: {x?: {}, #c: {}}})
}
-- issue3509.cue --
// evalv3 causes infinite work and OOM panic where evalv2 is fast. A
// self-referential list constraint `#matrixConfig: [...#matrixConfig] | string`
// combined with indirect field access `#job: (#Workflow & {job: _}).job` causes
// the new evaluator to loop indefinitely.
// https://cuelang.org/issue/3509
issue3509: {
	out: #job.step & "foo"

	#job: (#Workflow & {job: _}).job

	#Workflow: {
		job: step: string

		#matrixConfig: [...#matrixConfig] | string
		matrix?: [string]: [...#matrixConfig]
	}

	@test(eq, {
		out: "foo"
		#job: {step: string}
		#Workflow: {
			job: {step: string}
			#matrixConfig: [] | string
			matrix?: {}
		}
	})
}
-- out/errors.txt --
[structural_cycle] noCancelSelfInvoke.t1.x.c.b.b: structural cycle
[structural_cycle] noCancelSelfInvoke.t1.x.c.c: structural cycle
[structural_cycle] selfTriggerCycle.t1.#T.b.b: structural cycle
[structural_cycle] selfTriggerCycle.t2.b.c.a: structural cycle
[structural_cycle] selfTriggerCycle.long1.a.c.b.c.b.c.b: structural cycle
[structural_cycle] selfTriggerCycle.issue1503.#T.a.b.link: structural cycle
[structural_cycle] mutuallyTriggeringCycle.t1.x.c.b.b.b.b.b: structural cycle
[structural_cycle] brokenRing.t1.p1.T.b: structural cycle
[structural_cycle] brokenRing.t1.p2.D.a: structural cycle
[structural_cycle] cyclicRing.t1.T.b: structural cycle
-- out/todo.txt --
v2/v3 differences: structural cycle detection depth differs slightly, resulting in
different error path locations (e.g. noCancelSelfInvoke.t1.x.c.b.b in v3 vs
noCancelSelfInvoke.t1.x.c.b.b.b in v2). Structure sharing in v3 means some fields
appear as ~(...) references rather than expanded structs (cyclicRing.t1.D.a,
selfTriggerCycle.t1.a, brokenRing.t1.p1.D.a?, brokenRing.t1.p2.T.b). v2 additionally
reports cyclicRing.t1.D.a.b as structural cycle.
-- out/eval/stats --
Leaks:  4
Freed:  190
Reused: 164
Allocs: 30
Retain: 0

Unifications: 183
Conjuncts:    359
Disjuncts:    2

NumCloseIDs: 60
