Skip to content

cli: error on unknown subcommand in multi-fn files#320

Merged
danieljohnmorris merged 3 commits into
mainfrom
fix/unknown-subcommand-error
May 16, 2026
Merged

cli: error on unknown subcommand in multi-fn files#320
danieljohnmorris merged 3 commits into
mainfrom
fix/unknown-subcommand-error

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

ilo file.ilo wibble x on a multi-function file used to silently route to the FIRST declared function with ["wibble", "x"] as positional args, producing a misleading arity error like helper: expected 1 args, got 2 far from the cause. Filed as interactive-cli rerun5 P1.

Default-engine dispatch now checks the leading positional against the known function names and, when it doesn't match AND looks like an identifier, emits a friendly diagnostic listing the available functions instead.

The token-cost framing: silently routing an unknown subcommand wastes follow-up turns on an arity error the agent then has to chase back through the file. A direct "no such function 'wibble'; available: main, helper, ..." closes the loop in one read.

Repro

Before:

$ ilo file.ilo wibble x
{"code":"ILO-R004","message":"helper: expected 1 args, got 2",...}

After:

$ ilo file.ilo wibble x
error: no such function 'wibble' in file.ilo
available functions:
  helper
  main

  ilo file.ilo <func> [args...]   run a function

What's in the diff

  • cli: error on unknown subcommand in multi-fn files — adds looks_like_subcommand_name (ASCII ident-shape check, exempts true/false/nil) and a new branch in the Default-engine dispatch that fires when the file has 2+ user functions and the leading positional is ident-shaped but unknown. Synthetic __lit_* decls are already filtered out of the listing per cli: restore documented auto-run for main and inline programs #307.
  • test: regression coverage for unknown-subcommand error — eight new integration tests in tests/regression_cli_default.rs covering ident-shaped unknown subcommand, known subcommand happy path, __lit_* filtering, single-fn and inline pass-through, and numeric / quoted-string / bracketed-list leading-arg pass-through (the last three pin the shape guard so tests/eval_inline.rs unwrap_*_inline don't regress).
  • examples: exercise subcommand dispatch across enginesexamples/unknown-subcommand-listing.ilo with main + two helpers and -- run: / -- out: annotations, pinning the happy-path dispatch contracts across every engine via the examples harness.

Three new unit tests in src/main.rs also cover looks_like_subcommand_name directly.

Test plan

  • Full test suite green: cargo test --release --features cranelift — 138 test binaries pass, 0 failures
  • Clippy clean: cargo clippy --release --features cranelift --tests -- -D warnings
  • cargo fmt --check clean
  • Manual repro: ilo /tmp/repro.ilo wibble x produces the new error, ilo /tmp/repro.ilo helper 5 still works, ilo /tmp/repro.ilo still auto-runs main
  • Single-fn file ilo dbl.ilo 21 still pass-throughs as before
  • Inline ilo 'dbl x:n>n;*x 2' 21 still pass-throughs

Follow-ups

  • VM, Tree, and Cranelift engines (when explicitly selected with --vm / --tree / --cranelift) still emit their backend-native "undefined function" error rather than the friendly listing. Default-engine covers the agent default-path which is the common case; lifting the guard to all engines would be a small follow-up if it surfaces.

Running `ilo file.ilo wibble x` on a multi-fn file used to silently
route to the first declared function with `["wibble", "x"]` as args,
producing a misleading arity error (`helper: expected 1 args, got 2`)
far from the cause.

Default-engine dispatch now checks the leading positional against the
known function names and, if it doesn't match AND looks like an
identifier (ASCII alphabetic / underscore start, ident-shaped tail),
emits a friendly "no such function 'wibble' in <file>" error with the
available-function listing instead.

The ident-shape guard preserves the existing pass-through for numeric,
quoted-string, and bracketed-list leading args (they're clearly data,
not a typoed subcommand). Reserved literal words (`true`, `false`,
`nil`) are also exempt since `parse_cli_arg` maps them to Bool/Nil
values and passing them through to the entry function is the
long-standing intent.

Synthetic `__lit_*` lambda-lifted decls remain filtered out of the
listing (already covered by #307).

Single-function files and inline programs are unchanged: unknown
leading tokens still pass through to the sole/entry function per the
SKILL.md auto-run rule.
Five new CLI integration tests pin the new shape-aware
unknown-subcommand behaviour:

- unknown ident-shaped subcommand on a multi-fn file errors with
  "no such function" + available-functions listing, does not leak
  the pre-fix first-fn arity error
- known subcommand still routes correctly on the same multi-fn file
- synthetic __lit_N decls are filtered out of the listing
- single-fn file treats an unknown leading token as a literal arg
  (SKILL.md auto-run rule)
- inline program treats an unknown leading token as a literal arg
- multi-fn file with a numeric leading arg still passes through to
  the entry function (pins the shape guard that prevents the
  unwrap_*_inline tests in tests/eval_inline.rs from regressing)
- multi-fn file with a quoted-string leading arg passes through
- multi-fn file with a bracketed-list leading arg passes through
Multi-fn file with `main` plus two helpers, pinning the happy-path
contracts the unknown-subcommand guard sits next to:

- `ilo file.ilo`          auto-runs `main`
- `ilo file.ilo dbl 21`   dispatches to the named function
- `ilo file.ilo trp 4`    dispatches to the named function

The CLI-level "no such function" error path is covered by
tests/regression_cli_default.rs; the examples harness only exercises
happy-path dispatch across every engine.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit 1df29f1 into main May 16, 2026
5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/unknown-subcommand-error branch May 16, 2026 22:39
danieljohnmorris added a commit that referenced this pull request May 16, 2026
eight fixes since 0.11.4, all from rerun5 personas: bare-bang silent-nil regression (#324), Cranelift AArch64 panic catch_unwind fallback (#319), multi-line body span drift (#318), HOF tree-bridge error parity on Cranelift (#321), bool-ternary brace sugar (#323), single-line body diagnostic with brace-block bodies (#322), unknown-subcommand error in multi-fn files (#320), window perf cliff fused flt/map (#325).
danieljohnmorris added a commit that referenced this pull request May 17, 2026
PR #320's looks_like_subcommand_name rejected anything containing -,
so hyphenated idents like list-orders / wibble-x fell through to the
old greedy first-fn dispatch and produced misleading arity errors.
Per SPEC the canonical ilo ident shape is [a-z][a-z0-9]*(-[a-z0-9]+)*,
so - is legal mid-ident; the helper now matches that.

Trailing dash, doubled dash, and leading dash remain rejected as
data so --flag, -1, foo-, foo--bar still pass through unchanged.
File-shaped args (data.csv, path/to/file) are still rejected because
a dot or slash is not a legal ident char.

Interactive-cli rerun6 surfaced this on a multi-fn file with a
zero-arg load fn; ilo file.ilo list-orders used to produce
'load: expected 0 args' rather than the friendly no-such-function
listing #320 was meant to deliver.
danieljohnmorris added a commit that referenced this pull request May 17, 2026
Eight new integration tests covering the two PR #320 follow-up bugs:

Hyphenated unknown subcommand (interactive-cli rerun6):
- list-orders typo → no-such-function listing, not first-fn dispatch
- wibble-x tail-hyphenated form → same listing shape
- foo- trailing-dash → falls through as data (not an ident shape)

Non-ident positional with main (gis-analyst rerun6, devops-sre rerun6):
- top200.csv → routes to main, not to first-declared hav
- data.csv out.txt → both positionals flow to main in order
- numeric 21 with main defined → routes to main
- /tmp/inc.json with named-helper i:_ first → routes to main
- 41 on a no-main file → legacy passthrough preserved
- explicit fn name still overrides main routing

Each test pins the diagnostic absence too — pre-fix symptoms (load /
hav / gs arity or type errors) must not appear.
danieljohnmorris added a commit that referenced this pull request May 17, 2026
PR #320's looks_like_subcommand_name rejected anything containing -,
so hyphenated idents like list-orders / wibble-x fell through to the
old greedy first-fn dispatch and produced misleading arity errors.
Per SPEC the canonical ilo ident shape is [a-z][a-z0-9]*(-[a-z0-9]+)*,
so - is legal mid-ident; the helper now matches that.

Trailing dash, doubled dash, and leading dash remain rejected as
data so --flag, -1, foo-, foo--bar still pass through unchanged.
File-shaped args (data.csv, path/to/file) are still rejected because
a dot or slash is not a legal ident char.

Interactive-cli rerun6 surfaced this on a multi-fn file with a
zero-arg load fn; ilo file.ilo list-orders used to produce
'load: expected 0 args' rather than the friendly no-such-function
listing #320 was meant to deliver.
danieljohnmorris added a commit that referenced this pull request May 17, 2026
Eight new integration tests covering the two PR #320 follow-up bugs:

Hyphenated unknown subcommand (interactive-cli rerun6):
- list-orders typo → no-such-function listing, not first-fn dispatch
- wibble-x tail-hyphenated form → same listing shape
- foo- trailing-dash → falls through as data (not an ident shape)

Non-ident positional with main (gis-analyst rerun6, devops-sre rerun6):
- top200.csv → routes to main, not to first-declared hav
- data.csv out.txt → both positionals flow to main in order
- numeric 21 with main defined → routes to main
- /tmp/inc.json with named-helper i:_ first → routes to main
- 41 on a no-main file → legacy passthrough preserved
- explicit fn name still overrides main routing

Each test pins the diagnostic absence too — pre-fix symptoms (load /
hav / gs arity or type errors) must not appear.
danieljohnmorris added a commit that referenced this pull request May 17, 2026
…onident

cli: hyphenated subcommands + non-ident positionals route to main (#320 follow-up)
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