Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,18 @@ data accepted_source_emits_uncompilable_target: RecurringFailureMode = Recurring

"A DISTINCTION THIS COMPONENT MUST KEEP, because erasing it would misfile the class and invite a repair that makes things worse: a literal naming a path in the OUTPUT program is not the same defect as a literal INTERROGATING THE CORPUS and branching emission on the answer. The first is wrong loudly, at the target compiler, and is what this component covers; the second emits a structurally different program silently, and is a source-to-target correctness defect of a different kind. Deriving an output path by searching the corpus for the module that declares some symbol converts the first into the second -- it needs a symbol-name literal, reads the corpus, and branches on what it finds -- so it removes the appearance of a hard-coded name while LOWERING the rung. That trade was proposed and declined on 2026-09-05; the component is discharged by the capability below and by no substitution of one literal for another. ",

"NEXT-RUNG COMPONENT (viii): DERIVE USE-LINES FROM THE EMITTED REFERENCE SET RATHER THAN FROM SOURCE REFERENCES, sufficient that no emitted module carries a `use` line for a provider its own emitted bytes never path into, and that no provider path is spelled by the emitter rather than observed in what the emitter produced. The wording is the repository's own, named from both ends at `05_emit_rust` `host_realized_builtin_needs_no_import_note` and `reference_derived_authored_source_gate_note`, which that file records as naming the same terminal shape from opposite ends. It is stated as the capability rather than as any artifact because it clears the whole population in one motion: a trigger naming a single site, or naming the arrival of a second compiler generation, would be satisfied while every other spelling stayed authorable.", ],
"NEXT-RUNG COMPONENT (viii): DERIVE USE-LINES FROM THE EMITTED REFERENCE SET RATHER THAN FROM SOURCE REFERENCES, sufficient that no emitted module carries a `use` line for a provider its own emitted bytes never path into, and that no provider path is spelled by the emitter rather than observed in what the emitter produced. The wording is the repository's own, named from both ends at `05_emit_rust` `host_realized_builtin_needs_no_import_note` and `reference_derived_authored_source_gate_note`, which that file records as naming the same terminal shape from opposite ends. It is stated as the capability rather than as any artifact because it clears the whole population in one motion: a trigger naming a single site, or naming the arrival of a second compiler generation, would be satisfied while every other spelling stayed authorable.",

"**AN INSTANCE WHERE A FOLD INIT IS THE VARIANT Absent WHERE THE INTENDED TYPE IS Optional, 2026-09-10 (witty-boar-671, gunbc#10961).** The self-host SOURCE was repaired -- production folds in `v2.compiler.body_lowering_fold` `body_lower_body_after_eq_token` and `body_lower_last_child_optional` now init with `optional_absent()` -- and that repair does not discharge this class. The interpreter accepted `fold(..., init: Absent, ...)` returning `Optional<Node>` with zero blocking diagnostics; emission rendered `Option<Absent>` (`error[E0425]`) and then could not infer the binder on a match of `body_lower_node_is_eq_token` (`error[E0282]`). The emitted crate is the acceptance; the interpreter green was not. ",

"THE REDUCED FIXTURE IS COMMITTED AT `fixtures/fixture_closure_rustc/bare_absent_fold_init.dag` so the defect does not vanish because production no longer authors it. It is the exact shape: a declared `Optional<Int>` return, `fold` over `List<Int>`, `init: Absent`, step `optional_present`. Do not rewrite that file to `optional_absent()` -- that would delete the subject. Input to ACT realization-fidelity (`v1_compiler.emitted_closure_compile_host` `fixture_closure_rustc_verdict` over this path); wiring that route is the executing consumer, not a green on the repaired self-host source. ",

"WHY IT IS THIS ROW AND NOT A NEW CLASS: the invalid state is this row's own -- Accepted `.dag`, rustc refuses the emitted crate -- at component (vii), a complete per-target realization of a declared optional (type spelling, constructors, return convention). Repairing the self-host source satisfies less than (vii) and retires nothing. The mechanism is already a trigger component, so this append adds a specimen and no new component.",

"**THE SAME CLASS AT A SECOND SITE: TYPED OPTIONAL INIT WITH AN UNTYPED LAMBDA, 2026-09-10 (witty-boar-671, gunbc#10961 at 7a494d9e).** Replacing `init: Absent` with `optional_absent()` retired E0425 and left E0282. A .dag lambda parameter has no type slot, so the emitter renders `acc: _`; the init is a generic constructor call, and rustc cannot infer `T`. The self-host SOURCE is repaired again by a zero-arity `body_lower_eq_body_fold_init() -> Optional<Node>` and declared steps `body_lower_eq_body_fold_step` / `body_lower_last_child_fold_step` with typed params -- not by teaching the emitter to infer through `_`. ",

"THE REDUCED FIXTURE IS `fixtures/fixture_closure_rustc/typed_optional_fold_init_lambda.dag`: `fold(..., init: optional_absent(), f: fn(_acc, e) { ... })`. Do not rewrite that lambda into a declared typed step -- that would delete the subject. Same ACT consumer as the bare-Absent fixture. Same component (vii); no new component.",
],

evidence: [],
}
20 changes: 20 additions & 0 deletions fixtures/fixture_closure_rustc/bare_absent_fold_init.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module fixture.closure.rustc_bare_absent_fold_init

import v2.std.optional { Absent, Optional, optional_present }

// Reduced rustc-red for gunbc.recurring_failure_mode accepted_source_emits_uncompilable_target
// component (vii): a declared Optional return whose fold accumulator is initialized with the
// VARIANT Absent rather than optional_absent(). The interpreter accepts this. Emission has
// rendered Option<Absent> (E0425) and then failed to infer a later binder (E0282). That is
// constructor realization, not self-host source: #10961 repaired production folds to
// optional_absent(); this file keeps the exact bare-constructor shape so the class does not
// vanish because production no longer exercises it.
//
// Consumer: ACT realization-fidelity (fixture_closure_rustc_verdict over this path). Do not
// "fix" this file to optional_absent() -- that would delete the subject.

fn fold_init_bare_absent(xs: List<Int>) -> Optional<Int> {
fold(xs, init: Absent, f: fn(_acc, e) {
optional_present(value: e)
})
}
19 changes: 19 additions & 0 deletions fixtures/fixture_closure_rustc/typed_optional_fold_init_lambda.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module fixture.closure.rustc_typed_optional_fold_init_lambda

import v2.std.optional { Optional, optional_absent, optional_present }

// Second rustc-red for gunbc.recurring_failure_mode accepted_source_emits_uncompilable_target
// component (vii), beside fixtures/fixture_closure_rustc/bare_absent_fold_init.dag.
//
// Init is the generic constructor optional_absent() (typed Optional, not the variant Absent).
// The step is still a .dag lambda, which has no parameter type slot, so emission renders
// acc: _ and rustc cannot infer T (E0282). This is the #10961 head after the first source
// repair. Do not rewrite the lambda into a declared typed step -- that would delete the subject.
//
// Consumer: ACT realization-fidelity (fixture_closure_rustc_verdict over this path).

fn fold_init_typed_optional_lambda(xs: List<Int>) -> Optional<Int> {
fold(xs, init: optional_absent(), f: fn(_acc, e) {
optional_present(value: e)
})
}
96 changes: 95 additions & 1 deletion src/v2/compiler/body_lowering_fold.dag
Original file line number Diff line number Diff line change
Expand Up @@ -1715,10 +1715,104 @@ fn body_lower_body_subtree_lower_rest(captured: Node) -> Outcome<Optional<Node>>
}
}

fn body_lower_node_is_eq_token(n: Node) -> Bool {
match node_atom_identity_optional(node: body_lower_deep_unwrap_optional(node: n)) {
Present { value: id } => id == ^dag_token_eq
Absent => false
}
}

fn body_lower_eq_body_fold_init() -> Optional<Node> {
optional_absent()
}

fn body_lower_last_child_fold_step(_acc: Optional<Node>, e: Edge) -> Optional<Node> {
optional_present(value: e.target)
}

fn body_lower_last_child_optional(n: Node) -> Optional<Node> {
fold(n.children, init: body_lower_eq_body_fold_init(), f: body_lower_last_child_fold_step)
}

fn body_lower_atom_body_optional(expr: Node) -> Optional<Node> {
match body_lower_materialize_int_literal_operand_optional(node: expr) {
Present { value: lit } => Present { value: lit }
Absent => body_lower_operand_ref_optional(node: expr)
}
}

fn body_lower_eq_expr_to_body(expr: Node) -> Outcome<Optional<Node>> {
match body_lower_body_subtree_lower(captured: expr) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: body_opt, diagnostics: d } =>
match body_opt {
Present { value: lowered } =>
outcome_with_diagnostics(
value: optional_present(value: lowered),
diagnostics: d
)
Absent =>
match body_lower_atom_body_optional(expr: expr) {
Present { value: atom } =>
outcome_with_diagnostics(
value: optional_present(value: atom),
diagnostics: d
)
Absent =>
match body_lower_last_child_optional(n: expr) {
Absent => outcome_with_diagnostics(value: optional_absent(), diagnostics: d)
Present { value: last } =>
match body_lower_atom_body_optional(expr: last) {
Present { value: atom } =>
outcome_with_diagnostics(
value: optional_present(value: atom),
diagnostics: d
)
Absent => body_lower_eq_expr_to_body(expr: last)
}
}
}
}
}
}

fn body_lower_body_from_fn_captured(captured: Node) -> Outcome<Optional<Node>> {
match body_lower_find_captured(root: captured, emitted: ^dag_surface_fn_body) {
ParseSubtreeFound { captured: body } => body_lower_body_subtree_lower(captured: body)
ParseSubtreeAbsent => outcome_accepted(value: optional_absent())
ParseSubtreeAbsent =>
match body_lower_body_after_eq_token(captured: captured) {
Present { value: expr } => body_lower_eq_expr_to_body(expr: expr)
Absent => outcome_accepted(value: optional_absent())
}
}
}

fn body_lower_eq_body_fold_step(acc: Optional<Node>, e: Edge) -> Optional<Node> {
match acc {
Present { value: n } =>
if body_lower_node_is_eq_token(n: n) {
optional_present(value: e.target)
} else {
optional_present(value: n)
}
Absent =>
if body_lower_node_is_eq_token(n: e.target) {
optional_present(value: e.target)
} else {
body_lower_body_after_eq_token(captured: e.target)
}
}
}

fn body_lower_body_after_eq_token(captured: Node) -> Optional<Node> {
match fold(captured.children, init: body_lower_eq_body_fold_init(), f: body_lower_eq_body_fold_step) {
Absent => optional_absent()
Present { value: n } =>
if body_lower_node_is_eq_token(n: n) {
optional_absent()
} else {
optional_present(value: n)
}
}
}

Expand Down
54 changes: 53 additions & 1 deletion src/v2/extdeps/languages/dag.dag
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,52 @@ fn dag_formal_productions() -> List<FormalProduction> {
grammar_formal_nonterminal_occurrence(identity: ^dag_production_fn_body)
]
),
grammar_formal_production(
lhs: ^dag_production_fn_decl,
rhs: [
grammar_formal_terminal(identity: ^dag_token_kw_fn),
grammar_formal_terminal_lexeme(identity: ^dag_token_ident),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_param_list),
grammar_formal_terminal(identity: ^dag_token_eq),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_expr)
]
),
grammar_formal_production(
lhs: ^dag_production_fn_decl,
rhs: [
grammar_formal_terminal(identity: ^dag_token_kw_fn),
grammar_formal_terminal_lexeme(identity: ^dag_token_ident),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_param_list),
grammar_formal_terminal(identity: ^dag_token_arrow),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_type_expr),
grammar_formal_terminal(identity: ^dag_token_eq),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_expr)
]
),
grammar_formal_production(
lhs: ^dag_production_fn_decl,
rhs: [
grammar_formal_terminal(identity: ^dag_token_kw_fn),
grammar_formal_terminal_lexeme(identity: ^dag_token_ident),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_generic_params),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_param_list),
grammar_formal_terminal(identity: ^dag_token_eq),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_expr)
]
),
grammar_formal_production(
lhs: ^dag_production_fn_decl,
rhs: [
grammar_formal_terminal(identity: ^dag_token_kw_fn),
grammar_formal_terminal_lexeme(identity: ^dag_token_ident),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_generic_params),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_param_list),
grammar_formal_terminal(identity: ^dag_token_arrow),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_type_expr),
grammar_formal_terminal(identity: ^dag_token_eq),
grammar_formal_nonterminal_occurrence(identity: ^dag_production_expr)
]
),
grammar_formal_production(
lhs: ^dag_production_top_level_item,
rhs: [
Expand Down Expand Up @@ -2922,7 +2968,13 @@ fn dag_grammar_fn_decl_expr() -> GrammarExpr {
right: dag_grammar_nonterminal(production: ^dag_production_type_expr)
)
),
right: dag_grammar_nonterminal(production: ^dag_production_fn_body)
right: dag_grammar_choice(
left: dag_grammar_nonterminal(production: ^dag_production_fn_body),
right: dag_grammar_sequence(
left: dag_grammar_terminal(token_class: ^dag_token_eq),
right: dag_grammar_nonterminal(production: ^dag_production_expr)
)
)
)
)
)
Expand Down
103 changes: 103 additions & 0 deletions src/v2/test/claim/parse/expression_bodied_fn_decl_parse_test.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
module v2.test.parse.expression_bodied_fn_decl_parse

import v2.compiler.normalize { normalize }
import v2.compiler.parse { parse_module_prepared }
import v2.compiler.program_assembly { dag_prepared_grammar }
import v2.compiler.tokenize { tokenize }
import v2.extdeps.languages.dag { dag_lex }
import v2.std.diagnostic { Accepted, Outcome, Rejected, bind_outcome }
import v2.std.logic { Bool }
import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly }
import v2.std.node { Node }
import v2.std.node_query { find_arrow_body_child }

data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly

fn g_tokenize_parse(text: String, file: Symbol) -> Outcome<Node> {
match dag_prepared_grammar() {
Rejected { diagnostics: d } => Rejected { diagnostics: d }
Accepted { value: prepared, diagnostics: residue } =>
bind_outcome(
o: tokenize(text: text, file: file, rules: dag_lex()),
f: fn(token_stream) {
match parse_module_prepared(
tokens: token_stream,
prepared: prepared,
validation_residue: residue
) {
Rejected { diagnostics: r } => Rejected { diagnostics: r }
Accepted { value: artifact, diagnostics: d } =>
Accepted { value: artifact.tree, diagnostics: d }
}
}
)
}
}

fn g_parse_accepted(text: String, file: Symbol) -> Bool {
match g_tokenize_parse(text: text, file: file) {
Accepted { value: _, diagnostics: _ } => true
Rejected { diagnostics: _ } => false
}
}

fn g_tree_has_arrow_body(root: Node) -> Bool {
match find_arrow_body_child(root: root) {
Accepted { value: _, diagnostics: _ } => true
Rejected { diagnostics: _ } =>
fold(root.children, init: false, f: fn(found, e) {
found || g_tree_has_arrow_body(root: e.target)
})
}
}

fn g_normalize_has_arrow_body(text: String, file: Symbol) -> Bool {
match g_tokenize_parse(text: text, file: file) {
Rejected { diagnostics: _ } => false
Accepted { value: tree, diagnostics: _ } =>
match normalize(parse_tree: tree) {
Rejected { diagnostics: _ } => false
Accepted { value: normalized, diagnostics: _ } =>
g_tree_has_arrow_body(root: normalized.root)
}
}
}

data expression_bodied_fn_source: String = "module probe.expr_fn\n\nfn f(a: Int) -> Int = a\n"

data expression_bodied_literal_fn_source: String = "module probe.expr_fn_lit\n\nfn f() -> Int = 1\n"

data braced_fn_control_source: String = "module probe.braced_fn\n\nfn f(a: Int) -> Int {\n a\n}\n"

data empty_eq_fn_source: String = "module probe.empty_eq\n\nfn f(a: Int) -> Int =\n"

test fn expression_bodied_fn_decl_parses_holds() -> Bool {
g_parse_accepted(text: expression_bodied_fn_source, file: ^probe_expr_fn)
}

test fn expression_bodied_literal_fn_decl_parses_holds() -> Bool {
g_parse_accepted(text: expression_bodied_literal_fn_source, file: ^probe_expr_fn_lit)
}

test fn braced_fn_decl_still_parses_holds() -> Bool {
g_parse_accepted(text: braced_fn_control_source, file: ^probe_braced_fn)
}

test fn braced_fn_decl_survives_normalize_holds() -> Bool {
g_normalize_has_arrow_body(text: braced_fn_control_source, file: ^probe_braced_fn)
}

test fn empty_eq_fn_decl_refuses_holds() -> Bool {
match g_parse_accepted(text: empty_eq_fn_source, file: ^probe_empty_eq) {
true => false
false => true
}
}

test fn expression_bodied_fn_decl_survives_normalize_holds() -> Bool {
g_normalize_has_arrow_body(text: expression_bodied_fn_source, file: ^probe_expr_fn)
}

test fn expression_bodied_literal_fn_decl_survives_normalize_holds() -> Bool {
g_normalize_has_arrow_body(text: expression_bodied_literal_fn_source, file: ^probe_expr_fn_lit)
}
Loading