Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ data else_less_if_statement_has_no_lowered_form: RecurringFailureMode = Recurrin
"SPECIMEN AND PINNED LOCUS: `dag/gunbc/machine_intake/mtcollins1_handoff_probe.dag` `mtcollins1_admission_probe`, at `if admitted == false { return exit_failure(..) }`, a guard followed by more statements.",
"EXPOSED, NOT INTRODUCED: the parent of gunbc#12145 (2de78893dfe) accepted this shape only by SILENT NARROWING -- the operand reader answered a sequence with its left element -- so the refusal on main after gunbc#12173 is a pre-existing defect made visible, not a new regression. Found by the XL-2 three-baseline identity census on gunbc#12194 (321 paths: 18 refusing at 2de78893dfe, 167 at ada85b9452, 101 after gunbc#12173).",
"RUNG FOUND AT: mitigated (a located refusal). CEILING: structurally guaranteed -- an early-return guard lowers to a Branch whose else is the remainder of the statement spine, or refuses. NEXT-RUNG TRIGGER, NAMING THE CAPABILITY: v2 body lowering lowers a statement-form else-less `if` whose arm returns by binding the rest of the statement spine as its else, so that every statement after the guard reaches the reference-site collector.",
"SEQUENCED BEHIND THE RETURN FLOOR FIX (XL-2, sharp-carp-336): the guard's lowering needs the tail position carried through the value readers, the next-rung trigger of `gunbc.recurring_failure_mode` `return_lowered_as_its_operand_outside_the_function_tail`. That row's repair made every return outside a function's own tail refuse located instead of lowering to its operand; the guard keeps refusing here, pinned by `v2.test.claim.namespace_xl0.return_tail_position` `an_else_less_guard_still_refuses_as_unlowered`.",
],

evidence: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module gunbc.recurring_failure_mode.return_lowered_as_its_operand_outside_the_function_tail

import std.types { NonEmptyStr }
import std.decl_ref { DeclarationRef, WholeDeclaration }
import gunbc.recurring_failure_mode { RecurringFailureMode }

data return_lowered_as_its_operand_outside_the_function_tail: RecurringFailureMode = RecurringFailureMode {
identity: "return_lowered_as_its_operand_outside_the_function_tail" as NonEmptyStr,

receipts: [
"INVALID STATE: a `return e` lowered to `e`, the value of whatever block held it, wherever that block was. The dag grammar's stmt admits seq(`return`, expr) (`v2.extdeps.languages.dag` `dag_grammar_stmt_expr`), and no reader in `v2.compiler.body_lowering_fold` named `dag_token_kw_return`: a deep first-match search answered the statement with its operand, and the keyword was dropped. In a function's tail that is the right value by luck. Anywhere else it is a different program: the function continues where it should have exited.",
"SPECIMEN, MEASURED ON MAIN 88436e585c3 (2026-09-29), PRE-EXISTING: `fn f(c: Bool, j: Int, k: Int) -> Int { let x = if c { return j } else { k } \\n x }` was ACCEPTED, and its normalized tree binds x to Branch(c, j, k); a return in an if arm of a tail-position if, and a return before a further statement in a fn value, were accepted the same way. Found by the XL-2 else-less-if probe (sharp-carp-336); the tree was read through `v2.compiler.program_assembly` `module_roots_from_source_root_ingest`.",
"RUNG FOUND AT: below the floor (silent wrongness). RUNG NOW: mitigated, a located refusal. `v2.compiler.body_lowering_fold` recognises the statement (`body_lower_return_statement_expr_optional`) and lowers it only as the tail statement of a function's own body (`body_lower_tail_statement_read`, reached from the statement spine); `body_lower_return_outside_tail_optional` refuses every other return under a fn declaration, a fn value's block body or a data initializer as `body_lowering_reason_return_not_in_tail_position`, located at the return, before any reader can search past it. A nested fn value owns its own returns. A return in an if or match arm refuses even when the enclosing if is itself the function's tail -- loud, and counted by the census on the PR.",
"CEILING: structurally guaranteed -- a return lowers to its operand exactly where its block's value is the function's result, which is structured early exit: a tail return in any arm whose enclosing form is itself in tail position, and a statement-form else-less guard `if c { ..; return e }` followed by statements lowering to Branch(c, <arm>, <rest of the spine>) (`gunbc.recurring_failure_mode` `else_less_if_statement_has_no_lowered_form`, whose trigger this carries). NEXT-RUNG TRIGGER, NAMING THE CAPABILITY: the value readers carry the tail position, so a statement spine, an if arm and a match arm each know whether their value is the function's result, and a return lowers in exactly those positions. Sequenced after the first-match readers' consolidation (XL-2 PR-2, bright-boar-848), which owns those readers' signatures.",
],

evidence: [
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_return_inside_a_let_bound_if_refuses_rather_than_binding_its_operand", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_return_followed_by_a_statement_refuses_as_not_in_tail_position", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_return_in_an_if_arm_refuses_as_not_in_tail_position", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_return_before_another_statement_in_a_fn_value_refuses", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "an_else_less_guard_still_refuses_as_unlowered", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_tail_return_of_a_fn_body_is_accepted", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_tail_return_after_a_let_is_accepted", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_tail_return_of_a_fn_value_block_body_is_accepted", field: WholeDeclaration },
DeclarationRef { module_path: "v2.test.claim.namespace_xl0.return_tail_position", decl_name: "a_tail_return_operand_is_read_and_refuses_unbound_at_its_atom", field: WholeDeclaration },
],
}
163 changes: 155 additions & 8 deletions src/v2/compiler/body_lowering_fold.dag
Original file line number Diff line number Diff line change
Expand Up @@ -3168,13 +3168,26 @@ fn body_lower_test_fn_decl_to_member(shell: Node) -> Outcome<BodyLowering> {
// find the first primary expression inside an else-less `if` -- its condition -- and return THAT as
// the body, silently discarding the guard and every statement after it. So the fn refuses the whole
// declaration when its subtree holds one, with the same typed cause the statement arm raises.
// The fn declaration's own fn_body is the first fn_body shell in its captured sequence (the same
// search body_lower_body_from_fn_captured makes); its tail statement is the one return it may hold.
fn body_lower_fn_decl_tail_statement_optional(captured: Node) -> Optional<Node> {
match body_lower_find_production_shell_optional(root: captured, emitted: ^dag_surface_fn_body) {
Present { value: fn_body } => body_lower_fn_body_tail_statement_optional(fn_body: fn_body)
Absent => optional_absent()
}
}

fn body_lower_fn_decl_to_arrow(shell: Node) -> Outcome<BodyLowering> {
match body_lower_unwrap_captured(shell: shell) {
Absent => body_lower_wrapper_retained_shell(shell: shell)
Present { value: captured } =>
match body_lower_find_captured(root: captured, emitted: ^dag_surface_else_less_if_stmt) {
ParseSubtreeFound { captured: guard } => body_lowering_lowered(o: body_lower_else_less_if_reject(shell: guard))
ParseSubtreeAbsent => body_lower_fn_decl_to_arrow_admitted(shell: shell, captured: captured)
ParseSubtreeAbsent =>
match body_lower_return_outside_tail_optional(root: captured, tail: body_lower_fn_decl_tail_statement_optional(captured: captured)) {
Present { value: refused } => body_lowering_lowered(o: refused)
Absent => body_lower_fn_decl_to_arrow_admitted(shell: shell, captured: captured)
}
}
}
}
Expand Down Expand Up @@ -3314,6 +3327,9 @@ fn body_lower_data_decl_to_member(shell: Node) -> Outcome<BodyLowering> {
match body_lower_body_after_eq_token(captured: captured) {
Absent => body_lower_wrapper_retained_shell(shell: shell)
Present { value: expr } =>
match body_lower_return_outside_tail_optional(root: expr, tail: optional_absent()) {
Present { value: refused } => body_lowering_lowered(o: refused)
Absent =>
match body_lower_data_initializer_body(expr: expr) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: body_opt, diagnostics: bd0 } =>
Expand Down Expand Up @@ -3358,6 +3374,7 @@ fn body_lower_data_decl_to_member(shell: Node) -> Outcome<BodyLowering> {
}
}
}
}
}
}
}
Expand Down Expand Up @@ -3905,9 +3922,13 @@ fn body_lower_function_value_body(body: FunctionLiteralBody) -> Outcome<Optional
match body {
FunctionLiteralExprBody { expr: e } => body_lower_function_value_lowered_whole(o: body_lower_fold(node: body_lower_deep_unwrap_optional(node: e)))
FunctionLiteralBlockBody { fn_body: b } =>
match body_lower_fn_body_stmt_seq_optional(fn_body: b) {
Absent => outcome_accepted(value: optional_absent())
Present { value: stmt_seq } => body_lower_function_value_lowered_whole(o: body_lower_stmt_seq(shell: stmt_seq))
match body_lower_return_outside_tail_optional(root: b, tail: body_lower_fn_body_tail_statement_optional(fn_body: b)) {
Present { value: refused } => body_lower_function_value_lowered_whole(o: refused)
Absent =>
match body_lower_fn_body_stmt_seq_optional(fn_body: b) {
Absent => outcome_accepted(value: optional_absent())
Present { value: stmt_seq } => body_lower_function_value_lowered_whole(o: body_lower_stmt_seq(shell: stmt_seq))
}
}
}
}
Expand Down Expand Up @@ -6728,16 +6749,19 @@ fn body_lower_stmt_spine(spine: Node) -> Outcome<Node> {
)
Present { value: head } =>
match body_lower_stmt_spine_rest_optional(spine: spine) {
Absent => body_lower_value_read(value: head)
Absent => body_lower_tail_statement_read(stmt: head)
Present { value: rest } =>
match body_lower_stmt_spine_head_optional(spine: rest) {
Absent => body_lower_value_read(value: head)
Absent => body_lower_tail_statement_read(stmt: head)
Present { value: _ } =>
match body_lower_statement_let_captured_optional(stmt: head) {
Absent =>
outcome_rejected(
d: body_lower_diagnostic(
reason: ^body_lowering_reason_statement_precedes_without_binding,
reason: match body_lower_return_statement_expr_optional(stmt: head) {
Present { value: _ } => ^body_lowering_reason_return_not_in_tail_position
Absent => ^body_lowering_reason_statement_precedes_without_binding
},
n: head
)
)
Expand Down Expand Up @@ -6831,11 +6855,24 @@ fn body_lower_sole_function_value_statement(spine: Node) -> Outcome<Optional<Nod
}
}

// A one-statement spine whose statement is a return is read by the spine, which recognises it,
// rather than by the expression walkers, which search past the keyword.
fn body_lower_stmt_spine_head_is_return(spine: Node) -> Bool {
match body_lower_stmt_spine_head_optional(spine: spine) {
Present { value: head } =>
match body_lower_return_statement_expr_optional(stmt: head) {
Present { value: _ } => true
Absent => false
}
Absent => false
}
}

fn body_lower_try_statement_spine(captured: Node) -> Outcome<Optional<Node>> {
match body_lower_find_captured(root: captured, emitted: ^dag_surface_stmt_seq) {
ParseSubtreeAbsent => outcome_accepted(value: optional_absent())
ParseSubtreeFound { captured: spine } =>
if body_lower_stmt_spine_has_successor(spine: spine) {
if body_lower_stmt_spine_has_successor(spine: spine) || body_lower_stmt_spine_head_is_return(spine: spine) {
bind_outcome(
o: body_lower_stmt_spine(spine: spine),
f: fn(lowered) { outcome_accepted(value: optional_present(value: lowered)) }
Expand Down Expand Up @@ -8721,6 +8758,116 @@ fn body_lower_else_less_if_reject(shell: Node) -> Outcome<Node> {
)
}

// A `return` IS RECOGNISED, NEVER SEARCHED PAST. The dag grammar's stmt admits seq(`return`, expr)
// (v2.extdeps.languages.dag dag_grammar_stmt_expr), and no reader here named the keyword: a deep
// first-match search answered the statement with its expression, so the exit was dropped. In tail
// position that was the right value by luck; anywhere else it was a wrong answer -- `let x = if c {
// return j } else { k }` lowered to x = Branch(c, j, k), the function continuing where it should
// have exited (gunbc.recurring_failure_mode return_lowered_as_its_operand_outside_the_function_tail).
//
// So a return lowers in exactly ONE position, the tail statement of a function's own body (a fn
// declaration's, or a fn value's block body), where "exit with e" and "the body's value is e" are
// the same fact. Every other return refuses, located at it, as return_not_in_tail_position: a
// structured tail-return in an if or match arm, and the else-less guard, need the tail position
// carried through the value readers, which this lowering does not have yet (the same row's trigger).
// Answers the returned expression when `stmt` -- a stmt shell or its captured sequence -- is one.
fn body_lower_return_statement_expr_optional(stmt: Node) -> Optional<Node> {
let captured = match parse_production_captured_child_optional(node: stmt) {
Present { value: c } => c
Absent => stmt
}
match sugar_sequence_pair_optional(node: body_lower_unwrap_surface_shell(node: captured)) {
Absent => optional_absent()
Present { value: pair } =>
match node_atom_identity_optional(node: body_lower_deep_unwrap_optional(node: pair.left)) {
Present { value: id } =>
if id == ^dag_token_kw_return { optional_present(value: pair.right) } else { optional_absent() }
Absent => optional_absent()
}
}
}

// Every return statement under `root`, in pre-order, up to a function value's boundary: a nested
// function value owns its own returns (body_lower_find_captured_prunable).
fn body_lower_return_statement_sites(root: Node) -> FreeMonoid<Node> {
match parse_production_emitted_identity_optional(node: root) {
Present { value: id } =>
if body_lower_find_captured_prunable(id: id) {
Empty
} else if id == ^dag_surface_stmt {
match body_lower_return_statement_expr_optional(stmt: root) {
Present { value: _ } => Cons { head: root, tail: body_lower_return_statement_sites_under(root: root) }
Absent => body_lower_return_statement_sites_under(root: root)
}
} else {
body_lower_return_statement_sites_under(root: root)
}
Absent => body_lower_return_statement_sites_under(root: root)
}
}

fn body_lower_return_statement_sites_under(root: Node) -> FreeMonoid<Node> {
fold(root.children, init: Empty, f: fn(acc, e) {
list_append(left: acc, right: body_lower_return_statement_sites(root: e.target))
})
}

// The last statement of a statement spine (body_lower_stmt_spine's shape).
fn body_lower_stmt_spine_last_optional(spine: Node) -> Optional<Node> {
match body_lower_stmt_spine_head_optional(spine: spine) {
Absent => optional_absent()
Present { value: head } =>
if body_lower_stmt_spine_has_successor(spine: spine) {
match body_lower_stmt_spine_rest_optional(spine: spine) {
Present { value: rest } => body_lower_stmt_spine_last_optional(spine: rest)
Absent => optional_present(value: head)
}
} else {
optional_present(value: head)
}
}
}

// The one return a function body may hold: its tail statement, read positionally from the fn_body
// (seq(`{`, seq(stmt_seq, `}`)), body_lower_fn_body_stmt_seq_optional).
fn body_lower_fn_body_tail_statement_optional(fn_body: Node) -> Optional<Node> {
match body_lower_fn_body_stmt_seq_optional(fn_body: fn_body) {
Absent => optional_absent()
Present { value: stmt_seq } =>
match body_lower_unwrap_captured(shell: stmt_seq) {
Absent => optional_absent()
Present { value: spine } => body_lower_stmt_spine_last_optional(spine: spine)
}
}
}

// THE WALL: the first return under `root` that is not `tail` refuses, located at it.
fn body_lower_return_outside_tail_optional(root: Node, tail: Optional<Node>) -> Optional<Outcome<Node>> {
fold_list(xs: body_lower_return_statement_sites(root: root), empty: optional_absent(), cons: fn(acc, site) {
let is_tail = match tail {
Present { value: t } => t == site
Absent => false
}
if is_tail {
acc
} else {
optional_present(
value: outcome_rejected(
d: body_lower_diagnostic(reason: ^body_lowering_reason_return_not_in_tail_position, n: site)
)
)
}
})
}

// A statement read as a value in the tail of a function body: a return lowers to what it returns.
fn body_lower_tail_statement_read(stmt: Node) -> Outcome<Node> {
match body_lower_return_statement_expr_optional(stmt: stmt) {
Present { value: e } => body_lower_value_read(value: e)
Absent => body_lower_value_read(value: stmt)
}
}

fn body_lower_after_children(folded: Node, fn_body_pass_through: Bool) -> Outcome<BodyLowering> {
if body_lower_is_core_substrate(node: folded) {
body_lowering_lowered(o: outcome_accepted(value: folded))
Expand Down
Loading
Loading