Isolate planner comparison and ownership drop on small stack

This commit is contained in:
2026-09-24 16:39:46 -07:00
parent f1c095654a
commit 18073a6f2e
2 changed files with 67 additions and 56 deletions
@@ -478,63 +478,70 @@ fn combined_shrink_offsets_typed_growth_and_counts_untouched_files() {
}
#[test]
fn deep_and_wide_equality_and_late_refusal_drop_on_small_stack() {
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn(|| {
for (expression, result, nodes) in [
(
format!("{}1{}", "abs(".repeat(127), ")".repeat(127)),
"Number",
128,
),
(
format!("[{}]", vec!["1"; 128].join(", ")),
"List(Number)",
129,
),
] {
let bundle = single(&format!(
"calculation c \"C\" id \"{}\" {{ result {result}; body {expression}; }}",
id(2)
));
// One shared source/desired/result layout exercises all cases
// without separate large debug temporaries for each branch.
for mode in 0..3 {
let source = authored(&bundle);
assert_eq!(source.indexed.resolved.expressions.len(), nodes);
let mut desired = source.project().clone();
match mode {
0 => {}
1 => desired.definitions.get_mut(&id(2)).unwrap().alias = "p".into(),
_ => calculation(&mut desired, 2).body = a::Binding::Unbound,
}
let result = source.plan_rename(&desired);
match result {
Ok(plan) => {
assert_eq!(mode, 0, "mutation unexpectedly accepted");
drop(plan);
}
Err(failure) => {
assert_ne!(mode, 0, "unchanged target unexpectedly refused");
assert_eq!(failure.diagnostics.len(), 1);
assert_eq!(
failure.diagnostics[0].diagnostic.message,
if mode == 1 {
"Rename aliases collide in their final namespace."
} else {
"Rename target changes non-name project semantics."
}
);
drop(failure);
}
}
drop(desired);
}
for (expression, result, nodes) in [
(
format!("{}1{}", "abs(".repeat(127), ")".repeat(127)),
"Number",
128,
),
(
format!("[{}]", vec!["1"; 128].join(", ")),
"List(Number)",
129,
),
] {
let bundle = single(&format!(
"calculation c \"C\" id \"{}\" {{ result {result}; body {expression}; }}",
id(2)
));
for mode in 0..3 {
// Admission/intent construction are prerequisites, outside the
// bounded planner worker. No limits or live owner state are reset.
let source = authored(&bundle);
assert_eq!(source.indexed.resolved.expressions.len(), nodes);
let mut desired = source.project().clone();
match mode {
0 => {}
1 => desired.definitions.get_mut(&id(2)).unwrap().alias = "p".into(),
_ => calculation(&mut desired, 2).body = a::Binding::Unbound,
}
})
.unwrap()
.join()
.unwrap();
std::thread::scope(|scope| {
let desired = &desired;
std::thread::Builder::new()
.stack_size(128 * 1024)
.spawn_scoped(scope, move || {
// The complete owner moves into this worker; successful
// plans and consumed failure owners die here. Nothing
// carrying source/edit authority escapes the worker.
match source.plan_rename(desired) {
Ok(plan) => {
assert_eq!(mode, 0, "mutation unexpectedly accepted");
assert!(plan.edits().is_empty());
assert!(std::ptr::eq(plan.desired(), desired));
drop(plan);
}
Err(failure) => {
assert_ne!(mode, 0, "unchanged target unexpectedly refused");
assert_eq!(failure.diagnostics.len(), 1);
assert_eq!(
failure.diagnostics[0].diagnostic.message,
if mode == 1 {
"Rename aliases collide in their final namespace."
} else {
"Rename target changes non-name project semantics."
}
);
drop(failure);
}
}
})
.unwrap()
.join()
.unwrap();
});
drop(desired);
}
}
}
#[test]
fn zero_expression_original_rejects_target_only_deep_and_wide_variants() {
@@ -27,3 +27,7 @@ Diagnostic source `9e8e77e1` established that deep admission, whole original clo
The bounded repair removes diagnostic markers and restores the general helper. Only this small-stack test directly creates each fresh owner at worker scope and matches each planning result there. It still uses 128 KiB, deep 128 and wide 129 expression nodes, complete admission on the worker, success/plan-drop, late final-namespace collision/refusal-drop and non-name semantic refusal/drop for both shapes. Prior plan, diagnostic and desired owners are explicitly dropped before subsequent cases. No fixture depth/width or stack allowance is weakened; no production code changes. The repaired full 17-test focused run and broader gates remain parent-owned and pending.
The direct-call candidate at `70c20c3` also aborted the small-stack test after the same first four focused tests passed. That candidate is **not** an accepted repair. The next test-only candidate uses a compact mode loop: each shape runs no-op success, final-namespace collision and non-name refusal through one shared source local, one desired clone and one planning result expression, with explicit plan/failure/desired drops. Complete admission remains inside the 128 KiB worker for every case; depths and expected outcomes are unchanged. This removes repeated case-specific large debug temporaries as a bounded layout experiment; no exact compiler frame size or overflow cause is claimed. If it still fails, actual-frame diagnostics are required before further rearrangement. Parent-focused verification remains pending.
Parent's `f1c0956` debugger evidence supersedes the unlocated layout hypothesis: run3 backtrace reaches `mixed::parse_with_profile -> Parser::unit -> parse_typed -> declarations::parse -> syntax::binding -> Cursor::exact_literal -> Budget::exact -> Exact::parse_decimal -> BigInt::parse_bytes -> Windows heap allocation`, before any rename-plan call. The same artifact's debugger run2 passed, so the full admission-on-128-KiB observation remains an **UNRESOLVED environment-sensitive stack boundary**, not a demonstrated planner defect or a repaired production issue. Raw evidence is the parent-owned `rename-debugger-f1c0956-3.log` and receipt; all preceding failed full-pipeline observations remain valid and preserved.
The final narrowly scoped planner evaluator prepares each fresh complete admitted owner and immutable desired Project on the normal test thread. For all six deep128/wide129 × no-op/collision/non-name cases, a scoped128-KiB worker moves the complete owner, borrows desired, invokes planning, checks the exact expected outcome, and drops its successful plan or failure there. No owner/result escapes, policy resets or leaks occur. Desired is externally owned and destroyed outside the worker. Depth, width, worker stack allowance and all planning/drop cases are unchanged. This test now claims only planner comparison/ownership/drop behavior; it makes **no claim** that the complete upstream parse/admission pipeline reliably fits128KiB under every observed environment. Existing separate admission/index small-stack tests remain in the parent full-suite gates. Final focused/full runtime acceptance is still parent-owned and pending.