docs+lint: state the arity-sensitive spread rule; W022 flags silent over-arity (#733) - #802
Merged
Merged
Conversation
…ver-arity (#733) docs/llms.txt's "#1 TRAP" paragraph still described the PRE-#405 semantics (claimed `f of [x]` binds the whole list — the opposite of the current runtime), and the "which spreads" framing predicted the wrong binding for every 2+-element call to a 1-param function: `one of [5, 6]` binds a = [5, 6] (arity-1 re-collection), not a = 5. The call section now states the rule in terms of the callee's parameter count, with that exact case shown, plus the silent over-arity behavior. CLAUDE.md's always-on rule gains the same carve-out sentence; docs/SPEC.md already had it (the "Arity-1 carve-out" paragraph), so SPEC is untouched. llms.txt also gains the four gaps the #733 experiment hit: lib/ is NOT ambient (import list -> list.filter, vs load_file for bare names), lambda syntax (absent entirely — `=>` did not appear in the file), sort_by (a C builtin, no import needed), and the comprehension `if` clause. New lint W022: a bare literal argument list with more elements than the callee's parameters — `two of [1, 2, 99]` silently discards 99 at runtime (rc=0, nothing reports it; the silent-wrong-answer class). Conservative by construction: fires only when the callee name has exactly one `define` in the file and no other binding anywhere; any assignment, param, lambda param, loop/comprehension var, catch name, list-pattern name, import, or match-pattern identifier poisons the name. Arity-1 callees are exempt by the semantics themselves (the whole-list re-collection IS the variadic idiom), and `define f()` / `define f` both carry the implicit `n`, so every define has arity >= 1. Infrastructure: LINT_FOR_EACH_CHILD, a generic direct-child iterator over every ASTType with NO default arm — -Werror=switch now forces a new node kind to update the walker instead of silently pruning its subtree (the #738 instinct applied in the new code). Validation: test_lint.sh 134/134 (6 new W022 cases incl. rebind / duplicate-define / parenthesized / defaulted-param arity); doc_drift_check clean. Closes #733 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates EigenScript’s AI-facing documentation to accurately describe the arity-sensitive “bare list after of” call semantics, and introduces a new lint warning (W022) to flag silent over-arity when the callee’s parameter count is provably known in-file.
Changes:
- Document the arity-1 “re-collection” carve-out and related call-site rules in
docs/llms.txtandCLAUDE.md, plus fill several previously-missing “surface area” gaps (lambda syntax,sort_by, lib import/load patterns, listcompif). - Add lint warning
W022to detect bare literal argument lists that exceed a same-file callee’s arity (with conservative name-poisoning to avoid false positives). - Add regression tests and diagnostics/changelog entries for
W022.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/lint.c |
Adds W022 implementation and a new LINT_FOR_EACH_CHILD AST child iterator used to walk the tree exhaustively. |
tests/test_lint.sh |
Adds test cases validating W022 firing/suppression behavior across key scenarios. |
docs/llms.txt |
Corrects and expands model-facing guidance on call semantics and common “generation gaps”. |
docs/DIAGNOSTICS.md |
Documents the new W022 warning semantics and its conservative firing criteria. |
CLAUDE.md |
Updates the always-on “call/list” rule to include the arity-1 carve-out and mention W022. |
CHANGELOG.md |
Records the doc correction and the new W022 lint under Unreleased. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+2267
to
+2272
| if (t->count == t->cap) { | ||
| t->cap = t->cap ? t->cap * 2 : 16; | ||
| t->fns = realloc(t->fns, (size_t)t->cap * sizeof(W022Fn)); | ||
| } | ||
| t->fns[t->count].name = strdup(n->data.func.name); | ||
| t->fns[t->count].param_count = n->data.func.param_count; |
InauguralPhysicist
added a commit
that referenced
this pull request
Aug 2, 2026
… caught real drift (#806) The ASTType half of #738 landed earlier (9d970d4..4d8c617); this finishes the issue. The remaining default: arms over closed enums are deleted and their absorbed enumerators written out at 12 sites: val_type_name, the Value destructor, values_equal, chan_clone_rec, GC_FOR_EACH_CHILD, gc_clear_node, eigs_value_type, store_json_encode, both trace-tape value formatters, the observer dump, and OP_SLICE's type check. A new ValType is now a build error at 16 sites (verified by planting an 11th enumerator; a planted 34th ASTType fails at 73). One ASTType straggler cleared: W022's binding collector (added by #802 AFTER the sweep) had reintroduced a default: arm — the exact drift mode the issue predicted. The first -Werror=switch compile found real drift: store_json_encode did not handle VAL_BUFFER, so buffers stored via ext_store silently encode as null (data loss — behavior preserved here, filed as #805); and the tape's full nondet formatter renders VAL_JSON_RAW/ VAL_TEXT_BUILDER as "<heap>" where the short form has "<json>"/ "<text>" (latent, noted in #805). All arms are otherwise strictly mechanical — suite output is byte-identical. Kept their default: on purpose — untrusted bytecode bytes (leaf- accessor scan, non-GNU dispatch), guarded subsets (JIT binop/cmp, compound-assign tokens), and TokType (out of #738's scope). The CLAUDE.md #709 claim the issue flagged was already removed by the #734 rework; the build-error property it asserted is now actually true, and .claude/rules/c-runtime-memory.md states the discipline. Gate: release 3364/3364; ASan/UBSan detect_leaks=1 3368/3368, leak tally 0; make http / gfx / freestanding-check clean. Closes #738 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #733.
Docs: state the rule the runtime actually has
The runtime table from the issue reproduces exactly on main.
docs/SPEC.mdalready documents the arity-1 carve-out correctly (the "Arity-1 carve-out" paragraph) — butdocs/llms.txtwas wrong twice over: the "which spreads" framing the issue quotes, and worse, its "#1 TRAP" paragraph still described the pre-#405 semantics (it claimedf of [x]binds the whole list to the first parameter — the opposite of the current runtime, and of W017's own message). The file billing itself as "the whole surface you need" was teaching semantics two revisions old.The call section now states both halves with the issue's own case shown: bare-list-is-an-argument-list at the call site; arity-1 re-collection at the binding (
one of [5]→a = 5,one of [5, 6]→a = [5, 6]); over-arity silence + W022.CLAUDE.md's always-on rule gains the same carve-out sentence. SPEC untouched (already right).The four gaps from the issue's experiment are filled in llms.txt: lib/ is not ambient (
import list→ namespacedlist.filter, vsload_file of "lib/list.eigs"→ bare names — both verified against the binary), lambda syntax (=>did not appear in the file at all),sort_by(a C builtin — needs no import, which STDLIB's own sort.eigs comment confirms), and the comprehensionifclause.Lint: W022 for silent over-arity
two of [1, 2, 99]bindsa=1, b=2and discards99— rc=0,--lint --json[], nothing reports it at any stage. NewW022flags a bare literal argument list with more elements than the callee's parameters.Conservative by construction — it fires only when the callee name provably has one meaning in the file: exactly one
defineanywhere and no other binding (assignment, param, lambda param, loop/comprehension var, catch name, list-pattern name, import, or any identifier inside amatchpattern poisons the name). Two structural exemptions:reverse of [1, 2, 3]), not an arity error.define f()anddefine fboth carry the implicitn(parser.c), so every define has arity ≥ 1.Parenthesized lists (
f of ([...])) are a single argument and never fire. Defaulted params count toward the arity (pinned by test).Infrastructure note: the rule is built on a new
LINT_FOR_EACH_CHILDgeneric child iterator whose switch has nodefault:arm —-Werror=switchnow forces a newASTTypeto update the walker instead of silently pruning its subtree from every walk. That's the #738 complaint applied to new code.Validation
tests/test_lint.sh134/134 — six new W022 cases: fires on the issue's repro and on defaulted-param arity; silent on the 1-param variadic shape, exact arity, parenthesized lists, a rebound name, and a duplicate define.detect_leaks=1suite 3364/3364, leak tally 0.tools/doc_drift_check.shclean.🤖 Generated with Claude Code