Skip to content

fix(ILO-460): reject nested fn decls with hint to use lambda or top-level helper#774

Merged
danieljohnmorris merged 5 commits into
mainfrom
ilo-460-nested-fn-diag
May 23, 2026
Merged

fix(ILO-460): reject nested fn decls with hint to use lambda or top-level helper#774
danieljohnmorris merged 5 commits into
mainfrom
ilo-460-nested-fn-diag

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

  • A name params>type;body declaration written inside another function's body used to silently hoist to top-level, losing any reference to an enclosing local (the rows case from the type-coerce-records feedback). Emit a precise parse-time diagnostic instead.
  • New diagnostic ILO-P023 fires at the inner header, with a hint naming both rewrites: an inline lambda for one-off captures (proc = (x:n>n; +x rows) / {x> +x rows}), or a top-level helper that takes the captured value as an explicit param.
  • Only fires when the inner decl is NOT separated from the surrounding body by an un-indented newline (decl_boundary), so genuinely-sibling top-level decls on separate physical lines keep parsing as before.
  • SPEC.md gets a row in the cross-language gotchas table plus a one-line note in the function-decl section. ai.txt regenerated automatically by build.rs.

Follow-up

Closure-capturing nested fn decls (the substantive language feature alternative) tracked separately as ILO-485: https://linear.app/ilo-lang/issue/ILO-485

Closes ILO-460

Test plan

  • cargo test --features cranelift,http,golden --lib — 3508 pass / 0 fail / 48 ignored
  • New regression tests: nested_fn_decl_in_body_rejected, top_level_fn_decls_still_parse, inline_lambda_in_body_still_parses
  • Existing top-level fn-decl tests continue to pass
  • Inline lambdas inside fn bodies continue to parse

Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3804 1 3803 0
View the top 1 failed test(s) by shortest run time
ilo::coverage_interpreter::mapr_short_circuits_on_err
Stack Traces | 0.013s run time
thread 'mapr_short_circuits_on_err' (42034) panicked at tests/coverage_interpreter.rs:950:5:
stderr={"code":"ILO-P024","labels":[{"col":49,"end":52,"line":1,"message":"here","primary":true,"start":48}],"message":"fn declarations are top-level only; this one is inside another function's body","notes":[],"severity":"error","suggestion":"use an inline lambda for a one-off helper that captures locals (e.g. `proc = (x:n>n; +x rows)` or `proc = {x> +x rows}`), or lift the helper to the top level and pass the captured value as an explicit parameter"}
{"code":"ILO-T005","labels":[{"col":64,"end":79,"line":1,"message":"","primary":true,"start":63}],"message":"undefined function 'f': its definition failed to parse","notes":["in function 'main'"],"severity":"error","suggestion":"fix the parse error first (see ILO-P024 reported earlier in this file); other references to 'f' are suppressed until then"}

note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Daniel Morris and others added 4 commits May 23, 2026 22:51
…evel helper

A `name params>type;body` declaration written inside another function's
body used to silently hoist to top-level, dropping the enclosing scope.
Any reference to a local of the outer function (the `rows` case from
the type-coerce-records feedback) then failed verification with a
misleading "undefined" cascade.

Emit ILO-P023 at the inner header instead. The hint names the two
canonical rewrites: an inline lambda for one-off captures, or a
top-level helper that takes the captured value as an explicit param.

The diagnostic only fires when the inner decl is NOT separated from
the surrounding body by an un-indented newline (`decl_boundary`), so
genuinely-sibling top-level decls on separate physical lines keep
parsing as before. The VM test that relied on indented-continuation
silent hoisting is updated to put `f>t` on a clean top-level line.

Closure-capturing nested fn decls are a separate language addition;
tracked as ILO-485.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ILO-P023 was allocated concurrently by the ILO-473 reject-guard-in-lambda
PR (#773) which merged into the open-PR set first. Renumbering this
branch's diagnostic to ILO-P024 (next free) to avoid a registry collision
on merge. No semantic change; same diagnostic, same tests, same hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier check fired on any inline ;-separated sibling fn decl
(test fixtures like `key x:n>n;mod x 2;main xs:L n>L n;uniqby key xs`
were broken — aot_cov_partition, aot_cov_uniqby). Restrict to the
actual scope-capture trap: enclosing body must already have a binding
statement (Stmt::Let) before the would-be nested decl.

Sibling top-level decls (one-line or multi-line) parse cleanly again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@danieljohnmorris danieljohnmorris force-pushed the ilo-460-nested-fn-diag branch from 635aa32 to f561680 Compare May 23, 2026 21:51
Three things in this commit:
- Restore registry.rs from main (ILO-P023 entry) and append ILO-P024 cleanly.
  The earlier conflict-marker stripper had merged the two registry entries
  into one broken block.
- Regenerate ai.txt to match SPEC after rebase.
- Rewrite ilo468_braceless_guard_tail_wrong_type_inline_lambda to assert
  ILO-P023 at parse time. Post-ILO-473 the braceless-guard-in-lambda is
  rejected by the parser before T008 can run; the two diagnostics are
  complementary, not overlapping, and either closes the silent-miscompile.
  The named-fn variant of the same trap still pins the T008 path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@danieljohnmorris danieljohnmorris merged commit 76ba416 into main May 23, 2026
7 of 11 checks passed
@danieljohnmorris danieljohnmorris deleted the ilo-460-nested-fn-diag branch May 23, 2026 21:56
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