Skip to content

docs+lint: state the arity-sensitive spread rule; W022 flags silent over-arity (#733) - #802

Merged
InauguralPhysicist merged 1 commit into
mainfrom
fix-733-arity-docs-w022
Aug 2, 2026
Merged

docs+lint: state the arity-sensitive spread rule; W022 flags silent over-arity (#733)#802
InauguralPhysicist merged 1 commit into
mainfrom
fix-733-arity-docs-w022

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Closes #733.

Docs: state the rule the runtime actually has

The runtime table from the issue reproduces exactly on main. docs/SPEC.md already documents the arity-1 carve-out correctly (the "Arity-1 carve-out" paragraph) — but docs/llms.txt was wrong twice over: the "which spreads" framing the issue quotes, and worse, its "#1 TRAP" paragraph still described the pre-#405 semantics (it claimed f 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 → namespaced list.filter, vs load_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 comprehension if clause.

Lint: W022 for silent over-arity

two of [1, 2, 99] binds a=1, b=2 and discards 99 — rc=0, --lint --json [], nothing reports it at any stage. New W022 flags 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 define anywhere and no other binding (assignment, param, lambda param, loop/comprehension var, catch name, list-pattern name, import, or any identifier inside a match pattern poisons the name). Two structural exemptions:

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_CHILD generic child iterator whose switch has no default: arm-Werror=switch now forces a new ASTType to 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.sh 134/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.
  • Release suite 3360/3360; ASan+UBSan detect_leaks=1 suite 3364/3364, leak tally 0.
  • tools/doc_drift_check.sh clean.

🤖 Generated with Claude Code

…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>
Copilot AI review requested due to automatic review settings August 2, 2026 05:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.txt and CLAUDE.md, plus fill several previously-missing “surface area” gaps (lambda syntax, sort_by, lib import/load patterns, listcomp if).
  • Add lint warning W022 to 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 thread src/lint.c
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
InauguralPhysicist merged commit f59d3f1 into main Aug 2, 2026
19 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the fix-733-arity-docs-w022 branch August 2, 2026 06:04
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants