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,29 @@
module gunbc.recurring_failure_mode.tail_call_match_lowering_skips_rc_arm_grouping

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

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

receipts: [
"INVALID STATE: an exhaustive .dag match whose arms discriminate a variant nested under an Rc-shared field (`TypeNode { connective: Conj }` beside `TypeNode { connective: Disj }` ...) inside a function whose self-call is lowered to a loop. The front end accepts it with zero diagnostics; the emitted Rust refuses with E0004 (`pattern NodeKind::TypeNode { .. } not covered`).",

"MECHANISM: the ordinary match renderer groups arms that share an outer variant into one nested match on the Rc field (v1.compiler.emit_rust rc_grouped_arm_plan, the #8570 grouping), so an enumerated nested arm set stays visible to rustc's exhaustiveness check. The tail-call renderer (emit_rust_tco_match / emit_typed_tco_match_arm) never consults that grouping: every nested discrimination is emitted as a match GUARD (collect_pattern_rc_variant_guards), and rustc does not count guarded arms toward exhaustiveness. No arm is dropped and none is selected wrongly -- the arms are all present, guarded -- so the failure is loud at the target compiler, an instance of accepted_source_emits_uncompilable_target specific to the loop path.",

"WHY IT WAS DORMANT: every such match on main ended in a `_ =>` wildcard, which gave rustc an unguarded arm to close over. The typed NFR burn-down (gunbc#12610 census) replaced those wildcards with enumerated variants, and the first enumerated nested arm set inside a tail-recursive walk surfaced it: v2.compiler.target_serialize grammar_tokens_fold_list_targets_into, emit-build of the 00_compile closure on gunbc#12667.",

"DISPOSITION (silent-koi-310): no per-site workaround. A site that hits this keeps its wildcard until the root fix lands, then is enumerated; grammar_tokens_fold_list_targets_into was reverted to its wildcard on that ruling. The root fix is dispatched as its own lane.",

"RUNG FOUND AT: 1 at best (the target compiler refuses; gunbc accepts). CEILING: 3 -- the emitter can decide exhaustiveness on the same facts for both renderers. NEXT-RUNG TRIGGER: the tail-call match renderer consumes rc_grouped_arm_plan (one grouping authority for both match lowerings), proven by an enumerated nested arm set in a tail-recursive fixture that emits and builds.",
"CLIMB (gunbc#12742): the trigger's lowering is built -- the grouping fold is one function, v1.compiler.emit_rust rc_grouped_match_arm_strs, parameterized by the arm-body emitter, and emit_rust_tco_match and the ordinary match renderer both call it. Its discriminating control is the stage0 integration target tco_rc_grouped_match_emitted_rust (E0004 on the prior seed, compiles and runs after), which required CI does not execute (gunbc.rung_drop rust_unit_tests_off_the_merge_path), so the rung stays 1 by rule-1 honesty until that control executes on the merge path or an enumerated nested arm set in a required emit-build closure (the grammar_tokens_fold_list_targets_into enumeration) builds.",
],

evidence: [
decl_ref(module_path: "v1.compiler.emit_rust", decl_name: "emit_rust_tco_match"),
decl_ref(module_path: "v1.compiler.emit_rust", decl_name: "rc_grouped_arm_plan"),
decl_ref(module_path: "v1.compiler.emit_rust", decl_name: "rc_grouped_match_arm_strs"),
decl_ref(module_path: "v2.compiler.target_serialize", decl_name: "grammar_tokens_fold_list_targets_into"),
],
}
85 changes: 57 additions & 28 deletions src/v1/05_emit_rust.dag
Original file line number Diff line number Diff line change
Expand Up @@ -13442,8 +13442,8 @@ fn emit_native_freemonoid_match(scrut_str: String, arms: List<Node>, registry: M
// field, or a deeper Rc level inside the nested pattern is NOT grouped and keeps the #8570 guard —
// so it keeps the E0004 too. A match containing an irrefutable arm is left alone entirely: the
// wildcard already covers the outer variant, there is no E0004 to remove, and rewriting it would
// only risk making the inner match non-exhaustive. The tail-call arm emitter
// (emit_typed_tco_match_arm) is untouched and keeps the guard for the same shape. Next-rung trigger: a general decision-tree
// only risk making the inner match non-exhaustive. The tail-call lowering
// shares this grouping through rc_grouped_match_arm_strs. Next-rung trigger: a general decision-tree
// lowering over the whole arm matrix, which subsumes this one shape and needs a fallthrough
// representation this grouping deliberately does not invent.

Expand Down Expand Up @@ -13641,31 +13641,42 @@ fn emit_typed_match_arm_strs(arms: List<Node>, registry: Map<String, ItemInfo>,
present_binding: optional_match_arm_sees_present_value(scrutinee_type: scrut_rt, arm_pattern: arm_pattern(n: pair.second), arm_index: pair.first, absent_arm_index: absent_arm_index))
)
} else {
let entries = arms |> map(arm => RcGroupedArmEntry {
arm: arm,
plan: rc_grouped_arm_plan(arm: arm, scope: scope, shared_types: shared_types, emit_info: emit_info, scrut_type: scrut_type)
})
let folded = entries |> fold(init: RcGroupedArmAcc { seen: [], out: [] }, f: (acc, e) =>
let representative = if e.plan.groupable { rc_group_representative(entries: entries, variant: e.plan.variant) } else { none }
match representative {
Present { value: rep } =>
if acc.seen |> any(s => s == e.plan.variant) {
acc
} else {
RcGroupedArmAcc {
seen: list_push(acc.seen, e.plan.variant),
out: list_push(acc.out, emit_rc_grouped_match_arm(members: rc_group_members(entries: entries, variant: e.plan.variant), plan: rep, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, match_result_type: match_result_type))
}
}
Absent =>
rc_grouped_match_arm_strs(arms: arms, scope: scope, shared_types: shared_types, emit_info: emit_info, scrut_type: scrut_type,
emit_group: (members, rep) => emit_rc_grouped_match_arm(members: members, plan: rep, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, match_result_type: match_result_type),
emit_single: (index, arm) => emit_typed_match_arm(arm: arm, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, scrut_type: scrut_type, match_result_type: match_result_type, string_from_mode: false, present_binding: false))
}
}

// THE ONE ARM-GROUPING AUTHORITY for both match lowerings. The ordinary renderer and the tail-call
// renderer differ only in how an arm body is emitted (a value versus a loop continuation), so they
// pass their body emitters in and share the grouping decision. The tail-call renderer used to render
// every nested Rc discrimination as a guard, and rustc does not count guarded arms toward
// exhaustiveness, so an enumerated nested arm set in a tail-recursive fold was E0004
// (gunbc.recurring_failure_mode tail_call_match_lowering_skips_rc_arm_grouping). `emit_single`
// receives the arm's source index so a renderer can decide which arm sees an optional's value.
fn rc_grouped_match_arm_strs(arms: List<Node>, scope: InferScope, shared_types: Set<String>, emit_info: EmitGraphInfo, scrut_type: String, emit_group: fn(List<RcGroupedArmEntry>, RcGroupedArmPlan) -> String, emit_single: fn(Int, Node) -> String) -> List<String> {
let entries = arms |> map(arm => RcGroupedArmEntry {
arm: arm,
plan: rc_grouped_arm_plan(arm: arm, scope: scope, shared_types: shared_types, emit_info: emit_info, scrut_type: scrut_type)
})
let folded = entries |> enumerate |> fold(init: RcGroupedArmAcc { seen: [], out: [] }, f: (acc, pair) =>
let e = pair.second
let representative = if e.plan.groupable { rc_group_representative(entries: entries, variant: e.plan.variant) } else { none }
match representative {
Present { value: rep } =>
if acc.seen |> any(s => s == e.plan.variant) {
acc
} else {
RcGroupedArmAcc {
seen: acc.seen,
out: list_push(acc.out, emit_typed_match_arm(arm: e.arm, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, scrut_type: scrut_type, match_result_type: match_result_type, string_from_mode: false, present_binding: false))
seen: list_push(acc.seen, e.plan.variant),
out: list_push(acc.out, emit_group(rc_group_members(entries: entries, variant: e.plan.variant), rep))
}
}
)
folded.out
}
}
Absent =>
RcGroupedArmAcc { seen: acc.seen, out: list_push(acc.out, emit_single(pair.first, e.arm)) }
}
)
folded.out
}

fn emit_typed_match(scrutinee: Node, arms: List<Node>, registry: Map<String, ItemInfo>, scope: InferScope, depth: Int, shared_types: Set<String>, emit_info: EmitGraphInfo) -> String {
Expand Down Expand Up @@ -15074,6 +15085,15 @@ fn emit_native_freemonoid_tco_match(scrut_str: String, arms: List<Node>, fn_name
}
}

// The tail-call rendering of one grouped variant: the #8570 outer pattern, then a nested match on
// the Rc field whose arms are loop continuations rather than values.
fn emit_rc_grouped_tco_match_arm(members: List<RcGroupedArmEntry>, plan: RcGroupedArmPlan, fn_name: String, params: List<Node>, registry: Map<String, ItemInfo>, scope: InferScope, depth: Int, shared_types: Set<String>, emit_info: EmitGraphInfo) -> String {
let inner_arms = members |> map(e =>
concat(" ", e.plan.inner_pat_str, " => { ", emit_typed_tco_expr(texpr: arm_body(n: e.arm), fn_name: fn_name, params: params, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info), " },")
) |> join(separator: "\n")
concat(" ", plan.pat_str, " => { match ", emit_ident(name: plan.ref_field, target: Rust), ".as_ref() {\n", inner_arms, "\n} },")
}

fn emit_rust_tco_match(frame: TcoFrame, fn_name: String, params: List<Node>, registry: Map<String, ItemInfo>, shared_types: Set<String>, emit_info: EmitGraphInfo) -> String {
match frame.expr.expr_data {
ExprMatch =>
Expand All @@ -15093,9 +15113,18 @@ fn emit_rust_tco_match(frame: TcoFrame, fn_name: String, params: List<Node>, reg
} else {
let rc_match = analyze_rc_match(scrutinee: s, arms: arm_list, scrut_type: tco_scrut_type, shared_types: shared_types, emit_info: emit_info, source_indices: frame.scope.type_env.source_indices)
let absent_arm_index = match_unguarded_absent_arm_index(arm_nodes: arm_list)
let arm_strs = arm_list |> enumerate |> map(pair =>
emit_typed_tco_match_arm(arm: pair.second, fn_name: fn_name, params: params, registry: registry, scope: frame.scope, depth: frame.depth, shared_types: shared_types, emit_info: emit_info, scrut_type: tco_scrut_type,
present_binding: optional_match_arm_sees_present_value(scrutinee_type: resolved_type(n: s), arm_pattern: arm_pattern(n: pair.second), arm_index: pair.first, absent_arm_index: absent_arm_index)))
let has_irrefutable = arm_list |> any(a => match_pattern_is_irrefutable(pattern: arm_pattern(n: a)))
let arm_strs = if has_irrefutable {
arm_list |> enumerate |> map(pair =>
emit_typed_tco_match_arm(arm: pair.second, fn_name: fn_name, params: params, registry: registry, scope: frame.scope, depth: frame.depth, shared_types: shared_types, emit_info: emit_info, scrut_type: tco_scrut_type,
present_binding: optional_match_arm_sees_present_value(scrutinee_type: resolved_type(n: s), arm_pattern: arm_pattern(n: pair.second), arm_index: pair.first, absent_arm_index: absent_arm_index)))
} else {
rc_grouped_match_arm_strs(arms: arm_list, scope: frame.scope, shared_types: shared_types, emit_info: emit_info, scrut_type: tco_scrut_type,
emit_group: (members, rep) => emit_rc_grouped_tco_match_arm(members: members, plan: rep, fn_name: fn_name, params: params, registry: registry, scope: frame.scope, depth: frame.depth, shared_types: shared_types, emit_info: emit_info),
emit_single: (index, arm) =>
emit_typed_tco_match_arm(arm: arm, fn_name: fn_name, params: params, registry: registry, scope: frame.scope, depth: frame.depth, shared_types: shared_types, emit_info: emit_info, scrut_type: tco_scrut_type,
present_binding: optional_match_arm_sees_present_value(scrutinee_type: resolved_type(n: s), arm_pattern: arm_pattern(n: arm), arm_index: index, absent_arm_index: absent_arm_index)))
}
let arms_str = arm_strs |> join(separator: "\n")
let tco_needs_as_str = all_arms_are_string_lit(arms: arm_list) && arm_list |> count > 0
if rc_match.needs_rc_option_ref {
Expand Down
Loading
Loading