Skip to content

resolve: an anonymous parameter slot hides nothing (nested function-type false refusal) - #13395

Merged
gunbai-bot[bot] merged 1 commit into
mainfrom
fix-anon-binder-hides
Oct 5, 2026
Merged

gunbai-bot[bot] merged 1 commit into
mainfrom
fix-anon-binder-hides

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

The bug. A function whose declared return is nested function types was falsely refused with resolve_reason_binder_hides_visible_value. For example, fn f(p: Int) -> fn(Int) -> fn(Int) -> Int { x => y => x }. This is the eighth silent red on main: v2.test.claim.body_lowering.function_value_body_route a_curried_function_typed_return_emits_impl_fn_over_box_dyn_fn.

First bad #12506 (861c829). It passes at the parent e5859c5 and fails at 861c829, by the claim_batch first-parent bisect.

Which binder, measured. I instrumented binder_hides_visible_value_diagnostic locally so its reason carries the flagged binder, then ran the emit route on main:

  • the curried x => y => x refuses on <anonymous-parameter-1>;
  • the nested fn(x) { fn(y) { x } } also refuses on <anonymous-parameter-1>;
  • a one-lambda function emits.

So the cause is not a fresh return type variable, which was the first guess.

Why it refuses. Each Arrow type's unnamed domain slot is labelled by its position within its OWN list (v2.std.anonymous_binder anonymous_param_label, a ruled naming: a counter- or occurrence-derived label would make fn(_: Int) -> Int two types at two sites). So both levels of fn(Int) -> fn(Int) -> Int bind <anonymous-parameter-1>, and #12506's value-binder gate (admit_value_binders / admit_one_value_binder) found the outer slot in frame.

Earliest unjustified link: the gate, not the naming. Hiding is the risk that a body reference silently rebinds to the inner binder. An anonymous slot is unauthorable and never referenceable by construction, so it cannot hide anything.

Fix. admit_one_value_binder admits a name answering is_anonymous_param_label (the one recogniser, already used by emission and reference_conservation) into the frame's locals without the outer lookup. The duplicate-in-frame check still applies, and the naming is unchanged.

Controls (function_value_body_route_test.dag; all 7 claims of the module):

claim without the fix with the fix
a_curried_function_typed_return_emits_impl_fn_over_box_dyn_fn FAIL PASS
nested_function_types_with_anonymous_slots_resolve_and_reach_infer (new) FAIL PASS
a_fn_returning_fn_emits_impl_fn_with_a_move_closure (one-lambda control) PASS PASS
an_authored_binder_hiding_an_outer_parameter_still_refuses_as_hiding (new NEGATIVE control) PASS (refuses) PASS (refuses)
the other 3 claims PASS PASS

Runs: with the fix https://app.buildbuddy.io/invocation/b82aaed0-f7a5-4ef2-8a55-626aaa9720ea ; without it https://app.buildbuddy.io/invocation/e5b00b13-06b3-4729-aa36-6e125cd60f5c .

Pre-queue gate (the 12 lambda/fold modules at head vs merge base; the N7 native diff): results are posted below as they land.

🤖 Generated with Claude Code

…type false refusal)

A function whose declared return is nested function types, `fn(Int) -> fn(Int) -> Int`,
was refused resolve_reason_binder_hides_visible_value: each Arrow type's unnamed slot is
labelled by its position in its own list (v2.std.anonymous_binder anonymous_param_label,
ruled), so both levels bind <anonymous-parameter-1>, and the value-binder gate added by
#12506 (861c829) found the outer slot in frame. Measured by instrumenting the gate:
the flagged binder is <anonymous-parameter-1>, not a fresh return type variable.

An anonymous slot is unauthorable, so no reference can name it and it cannot hide
anything. admit_one_value_binder admits it into the frame without the outer lookup, read
through is_anonymous_param_label, the one recogniser; the duplicate-in-frame check still
applies, and an authored binder hiding an outer parameter still refuses.

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

gunbai-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Pre-queue gate at head 4c141e7 against merge base 4bf26c8:

N7 native route (gunbc test //v2/test/parse/expression_bodied_fn_decl_parse:all, GITHUB_SHA set to each sha): identical at both. All eight verdicts are refused at prepare with the same cause (infer_reason_projection_receiver_declaration_unavailable), and the same 3 file refusals appear at both: filesystem_io, machine_constraints and body_lowering_fold. No new refusals.

The 12 lambda/fold modules, every test function, head vs base: 0 new failures. The only change is a_curried_function_typed_return_emits_impl_fn_over_box_dyn_fn, FAIL to PASS.

— sent from calm-boar-904

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit d531831 Oct 5, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the fix-anon-binder-hides branch October 5, 2026 18:04
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.

0 participants