Skip to content

Calling a locally bound fn value: Arrow child types itself (infer), lambda list literals at the arrow carrier, typed let-bound lambdas (emit) - #12307

Merged
gunbai-bot[bot] merged 16 commits into
mainfrom
session/loyal-boar-23
Sep 27, 2026
Merged

gunbai-bot[bot] merged 16 commits into
mainfrom
session/loyal-boar-23

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Calling a locally bound function value (for example fold(rs, f: (acc, r) => ... r()), the extdeps.apple.app_attest first_assertion_refusal idiom) refused with function 'r' not found in scope. Work item adhoc-36db743b-fb3; found by swift-bat-511 on P2b.

Where the defect actually was (DESIGN §6b). The brief placed it in emit_rust's callee resolution, but the refusal fires in inference: gunbc run refuses the same source. The emitted call sites (r(), f(v), g(v)) needed no change. Walking the chain from inference found one earliest boundary, then two emitter gaps downstream once the source got that far:

  1. v1.compiler.infer child_type_node (the earliest unjustified boundary). It is the total read of a child position's type, and it returned the child's inferred slot. An Arrow type node keeps its return type in that slot (make_callable_type), so the element of List<fn(Int) -> Int> came back as Int. A lambda parameter bound under that element was therefore not callable. Fix: an Arrow child is its own type. This covers fold and map lambda parameters over nullary and unary callable elements.
  2. List literal of lambdas (inference, declared_callable_list_element). A lambda's typed node carries its body type, and the literal took its type from the first element, so [fn() { none }] against List<fn() -> Int?> was typed List<T>. Fix: when the declared element is an arrow and ANY element is a lambda (the same predicate the emitter's rust_list_lit_holds_callables uses), the declared arrow is the element type; every element is still judged against it.
  3. Emitter, lambdas in value position (v1.compiler.emit_rust):
    • A lambda literal in a list is now the arrow carrier Rc<dyn Fn>, using the existing record-field row rust_callable_field_value_wrap with its capture preamble. It is bound at the rendered arrow by a typed let. An as cast was measured and rejected: it cuts the expectation, so Some(7) becomes Option<i32> (E0271). Previously these were bare closures, which is E0308.
    • A let-bound lambda is now emitted with typed parameters through emit_typed_collection_lambda. Previously |x| was E0282 at x.clone(). It stays a bare closure, so it still passes into an impl Fn parameter.

Evidence (all local on this head's tree; the session seed was built with CTRL_BUILD_MODE=local):

  • Parity fixture test.fixture.emitted_interpreted_parity.local_callable_value_calls covers a let-bound lambda called, a param-bound fn called, a returned closure called, the fold-lambda-parameter specimen over nullary and unary callable lists, and the absent arm.
    • Interpreted: exits 0.
    • Emitted: the crate builds, and a two-line harness over its library prints the same line, 6 8 6 7 9 0, with main returning ExitSuccess.
    • RED control: with the expected line mutated, the interpreter exits 1 and the emitted main returns ExitFailure carrying the observed line.
    • Pre-fix seed: the fixture refuses at resolve (function 'f' not found in scope).
    • Brief deviation: the let-bound lambda is annotated (let h: fn(Int) -> Int = ...); see "Not fixed".
  • Enrolled claim module test.claim.local_callable_value_call_witness_test: three inference controls and two emission controls. All 5 PASS here and all 5 FAIL on the pre-fix seed.
  • Regen: --required-regen reports first_generation_equal=true. The stage0 mirrors are the regen candidate's own output.
  • BuildBuddy: not used. The whole run executed in one local session, because gunbc run refuses remotely without a cgroup budget.
  • Receipt: added to gunbc.recurring_failure_mode accepted_source_emits_uncompilable_target. The rung is unchanged at mitigatable, because required CI still does not emit, compile and run a fixture closure.

Not fixed, stated in the receipt:

  • An unannotated let-bound lambda (let h = fn(x) { x * 2 }) still has no parameter type a Rust closure can be written at, so it emits |x: _| and fails with E0282.
  • A non-lambda element in a List<fn..>, such as a let-bound closure variable, keeps its bare rendering.

v1 seed purpose receipt (gunbc.v1_maintenance_standing v1_seed_standing): the change is to generated mirrors only, adopted from the regen candidate, and adds no hand-authored seed Rust. Purpose: v2 native emission; this idiom blocked native App Attest.

🤖 Generated with Claude Code

@gunbai-bot

gunbai-bot Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 71357 in 34d7041: the list literal's element type is now rendered once with a let before the per-element map and passed into rust_callable_list_element (.dag and mirror). The mirror is the regen candidate's own output. Parity fixture re-run on this build: interpreted rc=0, emitted crate prints 6 8 6 7 9 0, and main returns ExitSuccess. — sent from loyal-boar-23

gunbc-ci-auto-heal and others added 2 commits September 26, 2026 01:20
… receipt credits #12295 for the child_type_node repair

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Merged main to resolve the conflict (head c7d0749). Main landed the same child_type_node repair in #12295, so I took main's version of 04_types.dag, and the receipt now credits #12295 for it. This PR still carries the list-literal element type (infer) and the two emitter value positions. The mirrors are the regen candidate's own output, and a fresh --required-regen reports first_generation_equal=true. On this build the parity fixture gives 6 8 6 7 9 0 both interpreted and emitted, and all 5 claims PASS. — sent from loyal-boar-23

…d of restating it (review 71457)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Took the advisory point in review 71457. declared_callable_list_element and rust_list_lit_holds_callables each spelled out their own lambda match. Both now call the existing v1.compiler.infer is_lambda_expr, so one predicate answers the question. The mirrors are the regen candidate's output. A second --required-regen reports first_generation_equal=true, the parity fixture still gives 6 8 6 7 9 0 interpreted and emitted, and all 5 claims PASS. — sent from loyal-boar-23

…resetting it (review 71475)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Took the fuel point from review 71475. The ExprListLit arm now passes fuel - 1 into rust_callable_list_element, and the non-lambda branch recurses with that fuel instead of a fresh 1024, so a list element no longer resets the budget. The pre-existing resets elsewhere in the file are out of scope here. The mirror is the regen candidate's output, a second --required-regen reports first_generation_equal=true, the parity fixture gives 6 8 6 7 9 0 both ways, and all 5 claims PASS. — sent from loyal-boar-23

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HOLD — exact current head 3ce3693, confirmed both by PR metadata and refs/heads/session/loyal-boar-23. The carried transcript's approval at 34d7041 is not an approval of this head.

[P2 / DESIGN §2 and §5] The newly introduced ExprListLit branch in v1.compiler.infer infer_expr_body tests declared_callable_elem != none, then matches the same Optional again and answers unit_type in the Absent arm. This is the same fabricated-unreachable-default pattern explicitly removed from #12227. The branch is unreachable under the current outer predicate; I am not claiming a current input reaches it. The objection is the new second representation of the presence constraint and an authored plausible type where no type was obtained, when a single match makes that arm unwritable.

Required small repair: match declared_callable_list_element once. Present returns the declared arrow; Absent enters the existing ordinary element-inference continuation. Do not add a replacement unit_type/panic default for the already-proven-present case. Regenerate the mirror and retain the callable-list and let-lambda controls. This request is confined to the introduced branch, not a cleanup demand for old defaults elsewhere.

The main callable repair otherwise follows the right boundary: the Arrow-child fix now comes from landed #12295, declared all-lambda lists retain their declared arrow, and emission reuses the existing callable carrier/capture wrapper with a typed let rather than a cast. The unannotated let-bound lambda is NOT a typed gunbc refusal at this head: the source explicitly records the E0282 residue in accepted_source_emits_uncompilable_target, together with non-lambda callable-list elements and the unchanged mitigatable rung. I accept that as the stated remaining frontier, not as solved behavior; the parity fixture deliberately uses an annotated let.

Exact-head workflow 36217024690 is completed/success. I did not rerun the parity fixture or mutation controls locally. Rebind after the small source/mirror repair.

gunbc-ci-auto-heal and others added 3 commits September 26, 2026 15:02
… Absent enters the ordinary element inference (no fabricated default)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the HOLD (review 5326302612) in faf4acb; the new head is 8f159e7, which adds a merge of main (main's only change under src/v1 is to regen host files, not the mirrors this PR touches).

  • ExprListLit in v1.compiler.infer now matches declared_callable_list_element once. Present returns the declared arrow. Absent goes straight to the ordinary element-inference branch. The != none test and the unit_type answer for the already-present case are gone, and no replacement default was added. Nothing outside the new branch changed.
  • The mirror is the regen candidate's output. A second --required-regen reports first_generation_equal=true.
  • Re-run on this build:
    • Parity fixture: interpreted rc=0, and the emitted crate prints 6 8 6 7 9 0 with main returning ExitSuccess.
    • RED control, re-run this time: with the expected line mutated, the interpreter exits 1 and the emitted main returns ExitFailure { code: 1, reason: "6 8 6 7 9 0" }.
    • All 5 claims PASS, including the callable-list and let-lambda controls.

The unannotated let-bound lambda remains the stated frontier, as you noted, not solved behaviour.

— sent from loyal-boar-23

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE — exact head 8f159e760d5321fc1dda86694b8b51050c7f5b81.

The source repair closes my HOLD at 3ce3693927ee977ecd6de38735f11dad67e51b8b: ExprListLit now matches declared_callable_list_element exactly once. Present supplies the declared Arrow; Absent enters the pre-existing ordinary element-inference continuation. The new branch no longer restates the presence proof or authors a unit_type default for the already-proven-present case. The regenerated stage0 mirror has the same control structure.

The merge of current main preserves the reviewed callable boundary and stated residue. The callable-list, typed-let, parity, and RED evidence remains the right coverage for this change.

Approval is bound only to this SHA. Exact-head GitHub checks were still in progress at review time; the merge queue should require them to complete green.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 26, 2026
gunbc-ci-auto-heal and others added 2 commits September 26, 2026 17:30
…er's predicate (review 71571)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

On review 71571's asymmetry (inference used all lambdas, emission any): I agree it's real, and I've fixed it. declared_callable_list_element now uses any, and every element is still judged against the declared arrow. It is verified locally: regen reports first_generation_equal=true, parity gives 6 8 6 7 9 0 on both sides, the RED control holds, and 5/5 claims pass. I could not push it, because this PR is already in the merge queue at 8f159e7 and GitHub refuses updates to a queued branch. The reviewer marked it non-blocking, so I am not dequeuing. It lands as a follow-up PR once this one merges; until then, a mixed list (lambda plus non-lambda) stays in the stated residue. — sent from loyal-boar-23

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 26, 2026
gunbc-ci-auto-heal and others added 2 commits September 26, 2026 19:56
@gunbai-bot

gunbai-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Resolved the merge conflict. The new head is 7200865. Only the two generated mirrors conflicted. I took main's side of both and regenerated them from the merged .dag sources. A second --required-regen reports first_generation_equal=true. Since the branch had left the queue, this push also carries the review 71571 alignment (7649dd7: the declared-arrow element on any lambda element, the same predicate the emitter uses). On this build: parity gives 6 8 6 7 9 0 interpreted and emitted, the RED control fails on both sides, and all 5 claims PASS. — sent from loyal-boar-23

@briansrls briansrls left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE-MERGE — exact head 72008651293ff2a0b414fc00acbafc7236d4d5b6.

The rebind from approved 8f159e760d5321fc1dda86694b8b51050c7f5b81 is sound.

  • The PR is now based on current main and its exact-head diff remains the same seven-file callable repair: authored inference/emission source, two evidence files, the recurring-failure receipt, and the two generated mirrors.
  • The dashboard alignment is correct: declared_callable_list_element now selects the declared Arrow when any list element is a lambda, matching rust_list_lit_holds_callables. This closes the mixed-list asymmetry without widening admission: every element is still inferred under the declared element expectation and separately judged at PositionListElement, so an incompatible non-lambda still refuses; a compatible function value keeps its own rendering while lambda elements receive the Arrow carrier.
  • The prior single-match repair remains intact: Present supplies the declared Arrow and Absent enters ordinary element inference, with no fabricated default.
  • The final commit changes only v1_compiler_infer.rs and v1_compiler_emit_rust.rs, regenerating the mirrors over the merged source. The source and mirrors carry the same any(is_lambda_expr) decision and the same callable-list control flow.
  • The stated residues remain residues: unannotated let-bound lambdas are not claimed solved, and non-lambda callable values retain their producer-chosen rendering.

Non-blocking metadata note: the PR body’s item 2 still says “every element is a lambda”; the exact source and recurring-failure receipt now correctly say “any element.” That prose can be updated without a source rebind.

Queue once all required exact-head checks are green.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 26, 2026
Merged via the queue into main with commit b432cf1 Sep 27, 2026
5 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/loyal-boar-23 branch September 27, 2026 03:00
@briansrls
briansrls restored the session/loyal-boar-23 branch September 27, 2026 07:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant