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
@@ -0,0 +1,17 @@
module gunbc.recurring_failure_mode.a_wildcard_match_arm_resolves_as_an_unbound_name

import std.types { NonEmptyStr }
import gunbc.recurring_failure_mode { RecurringFailureMode }

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

receipts: [
"a match arm whose pattern is the wildcard `_` is refused at resolve as an unbound name at `_`: the native front end treats the wildcard pattern as a reference to a symbol spelled `_`, so a match that is exhaustive only through its wildcard arm cannot be accepted on the native route at all",
"SPECIMEN AND LOCUS, MEASURED ON MAIN 57f5b94e7f7 (2026-09-24), PRE-EXISTING: `fn blg_probe(c: Bool, s: BlgS) -> Bool { match s { BlgA => c \\n _ => c } }` with `type BlgS = BlgA | BlgB { r: Bool } | BlgC` refuses as `resolve_reason_unbound_symbol` whose NodeLocus atom is `_`. The same verdict holds with a record-construct first arm (`BlgA => BlgB { r: c }`) and with the match written across lines. The route is `v2.compiler.compile` `native_test_context_from_ingest` then `native_census_module_resolution` over an inline module, the same route `v2.test.claim.namespace_xl0.call_argument_value_resolve_refusal` drives.",
"HOW IT SURFACED: the XL-2 if-arm repair (`v2.compiler.body_lowering_fold` `body_lower_if_arm_lowered`) made a match inside an if-arm reach resolve for the first time. On main such a match was dropped whole by the operand reader, so `if c { s } else { match s { BlgA => .. _ => .. } }` resolved; after the repair it refuses at `_`, the verdict the same match already had at the top level of a fn body. The repair did not create the refusal; it stopped hiding it.",
"CLASS: a resolve defect, not a lowering one: a wildcard pattern binds nothing and names nothing, and a resolver that looks it up as a symbol has conflated a pattern form with a reference. RUNG FOUND AT: a located refusal (mitigation) -- the wrong answer is loud, never silent, but a correct program is refused. CEILING: structurally guaranteed -- the wildcard is a distinct pattern constructor in the lowered tree, so the resolver can decline to look it up by construction. NEXT-RUNG TRIGGER, NAMING THE CAPABILITY: the lowered pattern carries the wildcard as its own form that resolve does not treat as a reference, with a witness whose red is this specimen on main and whose control is a match exhaustive through named arms only.",
],

evidence: [],
}
95 changes: 68 additions & 27 deletions src/v2/compiler/body_lowering_fold.dag
Original file line number Diff line number Diff line change
Expand Up @@ -5753,26 +5753,19 @@ fn body_lower_stmt_spine(spine: Node) -> Outcome<Node> {
}
}

// THE GUARD IS THE WHOLE OVERLAP STORY. A statement spine is taken over by
// the Bind lowering ONLY when it is headed by a statement-form let AND has a
// following statement -- exactly the case that has no lowering today. Every
// other spine keeps the path it already had, so this cannot re-route a body
// that already lowers.
fn body_lower_stmt_spine_is_let_headed(spine: Node) -> Bool {
match body_lower_stmt_spine_head_optional(spine: spine) {
// A SPINE WITH A FOLLOWING STATEMENT BELONGS TO body_lower_stmt_spine, WHATEVER ITS HEAD IS. The
// guard used to admit only a let-headed spine, so `{ c \n x }` fell to the expression walkers,
// which read the first statement and dropped `x` with no refusal -- an undeclared `x` resolved.
// body_lower_stmt_spine already owns the multi-statement answer: a let binds the rest, and any other
// statement that precedes another refuses as statement_precedes_without_binding, located at it. A
// one-statement spine keeps the path it had.
fn body_lower_stmt_spine_has_successor(spine: Node) -> Bool {
match body_lower_stmt_spine_rest_optional(spine: spine) {
Absent => false
Present { value: head } =>
match body_lower_stmt_spine_rest_optional(spine: spine) {
Present { value: rest } =>
match body_lower_stmt_spine_head_optional(spine: rest) {
Absent => false
Present { value: rest } =>
match body_lower_stmt_spine_head_optional(spine: rest) {
Absent => false
Present { value: _ } =>
match body_lower_statement_let_captured_optional(stmt: head) {
Absent => false
Present { value: _ } => true
}
}
Present { value: _ } => true
}
}
}
Expand All @@ -5785,7 +5778,7 @@ 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_is_let_headed(spine: spine) {
if body_lower_stmt_spine_has_successor(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 @@ -5971,6 +5964,54 @@ fn body_lower_if_condition_lowered(condition_capture: Node) -> Outcome<Optional<
}
}

// AN ARM IS A STATEMENT SEQUENCE, SO IT LOWERS THROUGH THE STATEMENT AUTHORITY. The arms were read
// by body_lower_if_arm_operand_optional alone, the operand reader, which reads a call `g(p: x)` as
// `g`: an undeclared `x` in either arm reached no reference site, and a block arm kept only its
// first statement. An arm is `{` stmt_seq `}`, and body_lower_stmt_spine is the one authority for a
// statement sequence: one statement is folded whole, a let binds the rest, and any other statement
// followed by another refuses located. Its refusal is the arm's refusal. An `else if` arm is an
// if_expr, not a brace body -- a statement-sequence search there would find the nested then-arm --
// so it keeps the if reader. The operand reader stays the fallback for an arm whose lowered
// statement leaves no lowered node behind its shells.
fn body_lower_if_arm_lowered(arm_capture: Node) -> Outcome<Optional<Node>> {
match parse_production_emitted_identity_optional(node: arm_capture) {
Present { value: emitted } =>
if emitted == ^dag_surface_if_expr {
outcome_accepted(value: body_lower_if_arm_operand_resolved_optional(arm_capture: arm_capture))
} else {
body_lower_if_arm_statements_lowered(arm_capture: arm_capture)
}
Absent => body_lower_if_arm_statements_lowered(arm_capture: arm_capture)
}
}

fn body_lower_if_arm_statements_lowered(arm_capture: Node) -> Outcome<Optional<Node>> {
match body_lower_find_captured(root: arm_capture, emitted: ^dag_surface_stmt_seq) {
ParseSubtreeAbsent =>
outcome_accepted(value: body_lower_if_arm_operand_resolved_optional(arm_capture: arm_capture))
ParseSubtreeFound { captured: spine } =>
match body_lower_stmt_spine(spine: spine) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: lowered, diagnostics: d } =>
match body_lower_lowered_behind_shells_optional(node: lowered) {
Present { value: arm } => outcome_with_diagnostics(value: optional_present(value: arm), diagnostics: d)
Absent =>
outcome_with_diagnostics(
value: body_lower_if_arm_operand_resolved_optional(arm_capture: arm_capture),
diagnostics: d
)
}
}
}
}

fn body_lower_if_arm_operand_resolved_optional(arm_capture: Node) -> Optional<Node> {
match body_lower_if_arm_operand_optional(arm_capture: arm_capture) {
Present { value: raw } => body_lower_arm_operand_resolved_optional(arm: raw)
Absent => optional_absent()
}
}

fn body_lower_try_if_from_captured(captured: Node) -> Outcome<Optional<Node>> {
match body_lower_if_condition_optional(captured: captured) {
Present { value: condition_capture } =>
Expand All @@ -5983,13 +6024,15 @@ fn body_lower_try_if_from_captured(captured: Node) -> Outcome<Optional<Node>> {
Accepted { value: condition_opt, diagnostics: _ } =>
match condition_opt {
Present { value: condition } =>
match body_lower_if_arm_operand_optional(arm_capture: then_capture) {
Present { value: then_raw } =>
match body_lower_arm_operand_resolved_optional(arm: then_raw) {
match body_lower_if_arm_lowered(arm_capture: then_capture) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: then_opt, diagnostics: _ } =>
match then_opt {
Present { value: then_arm } =>
match body_lower_if_arm_operand_optional(arm_capture: else_capture) {
Present { value: else_raw } =>
match body_lower_arm_operand_resolved_optional(arm: else_raw) {
match body_lower_if_arm_lowered(arm_capture: else_capture) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: else_opt, diagnostics: _ } =>
match else_opt {
Present { value: else_arm } =>
outcome_accepted(
value: optional_present(
Expand All @@ -6002,11 +6045,9 @@ fn body_lower_try_if_from_captured(captured: Node) -> Outcome<Optional<Node>> {
)
Absent => body_lower_if_navigation_reject(captured: else_capture)
}
Absent => body_lower_if_navigation_reject(captured: else_capture)
}
Absent => body_lower_if_navigation_reject(captured: then_capture)
}
Absent => body_lower_if_navigation_reject(captured: then_capture)
}
Absent => body_lower_if_navigation_reject(captured: condition_capture)
}
Expand Down
Loading