Skip to content

fix: clarify kebab-case is atomic in undefined-variable hint#230

Merged
danieljohnmorris merged 3 commits into
mainfrom
fix/kebab-precedence
May 13, 2026
Merged

fix: clarify kebab-case is atomic in undefined-variable hint#230
danieljohnmorris merged 3 commits into
mainfrom
fix/kebab-precedence

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

Persona reports (lines 503, 622, 958 in ilo_assessment_feedback.md) kept claiming best-d in str best-d parses as str(best) - d when best and d are both bound, eroding the kebab-case identifier win. The reports came in steadily enough that they read like a real precedence bug.

It isn't. The lexer regex is [a-z][a-z0-9]*(-[a-z0-9]+)* with logos longest-match at priority 1, so best-d is always one Ident token. The parser never sees Minus between the halves and cannot produce BinOp::Subtract. The cost the persona was actually paying was the error message:

ILO-T004: undefined variable 'best-d'
hint: did you mean 'best'?

"Did you mean 'best'" reads exactly like "we split your identifier and only the left half resolved." So a model that hits this error mid-task concludes "kebab-case is broken in str-call position," paws around for a workaround, and logs the bug again next session.

This PR is diagnostic-layer only - no grammar surgery. When an undefined kebab-case identifier has every dash-separated half resolving as a scoped variable, function, or builtin, the hint now says:

'best-d' is a single identifier (kebab-case); for subtraction write '- best d'

For 3+ segments the explicit subtraction form is dropped (there's no single binary-subtract spelling for a-b-c). When only one half resolves the standard closest-match suggestion stays. Token cost of the kill-the-confusion-at-the-source path is much lower than the recurring "log it, find the workaround, document the workaround" loop.

Repro

$ ilo "f>t;best=10;d=3;str best-d" f

Before:

ILO-T004 undefined variable 'best-d'
hint: did you mean 'best'?

After:

ILO-T004 undefined variable 'best-d'
hint: 'best-d' is a single identifier (kebab-case); for subtraction write '- best d'

When the kebab ident IS bound, output is unchanged (99, not 7) - the parser was always doing the right thing here, only the error path was misleading.

What's in the diff

  • verify: clarify kebab-case in undefined-variable hint - new kebab_subtract_hint helper next to closest_match, wired into the ILO-T004 site for Expr::Ref in infer_expr. Falls back to the existing closest-match suggestion when the kebab theory doesn't apply.
  • tests: cross-engine regression for kebab-case lexer and diagnostic - new tests/regression_kebab_precedence.rs. Five positive tests pin the lexer guarantee in str-call arg, list element, multi-segment, digit-segment, and explicit-subtraction forms across tree/VM/Cranelift. Three diagnostic tests cover the new hint, the 3-segment no-subtract-form variant, and the closest-match fallback.
  • examples: kebab-vs-subtract, three forms side by side - new examples/kebab-vs-subtract.ilo exercised by examples_engines.rs. Same scope binds best, d, and best-d; [str best-d, str best, str d] prints [99, 10, 3] to make the atomicity obvious to any future agent that lands in the file.

Test plan

  • cargo test --release --features cranelift passes (full suite, 0 failures)
  • cargo fmt --all -- --check clean
  • cargo clippy --release --features cranelift --all-targets -- -D warnings clean
  • Manual repro of all three diagnostic paths (kebab-both-halves, 3-segment, closest-match fallback)
  • Manual confirmation that str best-d with best-d bound still returns the kebab value, not subtraction
  • examples/kebab-vs-subtract.ilo runs green via the engine harness on tree, VM, and Cranelift

Follow-ups

  • Move persona entries at lines 503, 622, 958 of ilo_assessment_feedback.md to the canonical ✅ Addressed section after merge, with a one-line note clarifying the diagnostic-layer root cause.

When `best-d` is undefined but `best` and `d` are both bound, the
default ILO-T004 suggestion `did you mean 'best'?` reads like the
parser split the identifier into `best - d`. The lexer regex
`[a-z][a-z0-9]*(-[a-z0-9]+)*` (logos longest-match) guarantees
kebab-case is atomic, so the misread is a diagnostic-layer footgun,
not a parser bug.

Add `kebab_subtract_hint` that fires when every dash-separated half
of an undefined kebab-case ident resolves as a scoped variable,
function, or builtin. For 2-segment names it also shows the explicit
subtraction spelling `- a b`. Falls back to closest-match for the
single-half-bound case, so unrelated typos still get the standard
suggestion.
Pin the lexer guarantee that kebab-case is one Ident token in
str-call argument position, list elements, multi-segment names, and
digit-segment names, across tree, VM, and Cranelift engines. Also
covers the explicit subtraction escape hatch `- best d` so the
recommended form in the new diagnostic stays valid.

Three diagnostic tests cover the new kebab-aware hint, the
3-segment variant (no subtract form recommended), and the
closest-match fallback when only one half is bound.
Shows the explicit subtraction spelling (`- best d`), the kebab-case
identifier lookup (`best-d` distinct from `best` and `d`), and the
multi-segment form (`a-b-c`) all working in one file. The mixed
function binds `best`, `d`, and `best-d` in the same scope and
prints `[99, 10, 3]` to prove the parser never splits the kebab
ident.

Wired into the engine-asserted harness via `-- run` / `-- out`
annotations so tree, VM, and Cranelift all check the same outputs.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/verify.rs 96.77% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit 45fa2a7 into main May 13, 2026
5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/kebab-precedence branch May 13, 2026 12:40
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