Skip to content

stdlib: 13 UI fragments disable E003 with the discouraged allow-file escape, ui_layout.eigs has none and fails lint at error severity — and the CI stdlib gate greps only for parse errors so none of it is visible #874

Description

@InauguralPhysicist

Finding

Linting the shipped standard library at v0.38.0 / 078e759 produces two error-severity findings:

$ for f in lib/*.eigs; do eigenscript --lint "$f"; done
...
lib/ui_layout.eigs:26: error[E003]: undefined name '_label_measure' — no binding on any path
lib/ui_layout.eigs:38: error[E003]: undefined name '_layout_dock' — no binding on any path

--lint exits 1 on these. Neither is a runtime bug — _label_measure is defined in lib/ui_w_basic.eigs:26 and _layout_dock in lib/ui_w_dock.eigs:204, and lib/ui.eigs load_files all three into one scope, so late binding resolves them. The problem is that ui_layout.eigs is a fragment linted as if it were an entry point.

The escape exists and is not used consistently

#460 built # lint: loaded-by <relpath> for exactly this case, and DIAGNOSTICS.md:317-319 states the preference:

For the E003 fragment case specifically, prefer # lint: loaded-by (below) — it keeps undefined-name protection that allow-file E003 abandons.

Actual usage across the 18 UI fragments:

Escape Count Files
# lint: allow-file E003 13 ui_draw, ui_focus, ui_w_basic, ui_w_button, ui_w_container, ui_w_data, ui_w_dialog, ui_w_dock, ui_w_input, ui_w_menu, ui_w_slider, ui_w_special, ui_w_viz
# lint: loaded-by 0
none 5 ui_anim, ui_dnd, ui_layout, ui_registry, ui_theme

So the documented-preferred mechanism is used nowhere, and the discouraged one is used everywhere. ui_layout.eigs is the only unmarked file that actually references a sibling's name, which is why it is the one that trips.

Why this matters beyond tidiness

allow-file E003 switches off undefined-name checking for the whole file. DIAGNOSTICS.md:221 describes what that check is for:

The runtime raises undefined variable the moment such a path executes; E003 surfaces it statically, including on cold branches.

The UI subtree is where cold branches are densest — per-widget-type dispatch, event handlers reached only on specific input. That is precisely the code where a typo'd helper name survives testing and raises in front of a user. Thirteen files have that protection turned off, and ui_layout.eigs demonstrates what the check catches when it is on.

The swap is viable — verified

I replaced allow-file E003 with loaded-by lib/ui.eigs on five of the marked fragments and re-linted:

ui_w_basic       (no E003)
ui_w_button      (no E003)
ui_w_dock        (no E003)
ui_focus         no issues found
ui_w_menu        (no E003)

Zero E003 fallout — loaded-by resolves every cross-fragment name from lib/ui.eigs's transitive binding set. (The only output was W021 shadow hints, an artifact of linting my copies from a scratch path; in place they do not appear.) The same marker on ui_layout.eigs takes it to no issues found.

So this is a strictly-better swap available at no cost: 13 files regain undefined-name protection, and the two real E003s disappear.

Why CI does not catch any of it

tests/run_all_tests.sh:3843-3846 runs --lint over every lib/*.eigs, then discards everything except parse errors:

PC_OUT=$(./eigenscript --lint "$libf" 2>&1 | grep -iE 'Parse error line')

The comment above it explains the grep (the nonzero exit also covers style warnings, so exit code is unusable as a gate) — that reasoning is sound for W-class noise, but it also filters E-class errors, which are not style. The gate is a parse check, and its section header says so ([stdlib] parse-check every lib/*.eigs); the risk is that the surrounding comment — "a broken stdlib helper was invisible" — reads as broader coverage than it has.

Suggested scope

  1. Add # lint: loaded-by lib/ui.eigs to lib/ui_layout.eigs (fixes the two errors).
  2. Convert the 13 allow-file E003 fragments to loaded-by, restoring undefined-name checking across the UI subtree.
  3. Tighten the CI gate to fail on E-class findings in lib/, not just parse errors — e.g. --lint --json filtered on "severity":"error". That is what would have caught Initialize EigenScript repository with official README and project structure #1 and will catch the next one.

Step 3 is the one worth doing regardless; without it, 1 and 2 can silently regress.

Related: #460, #404, docs/DIAGNOSTICS.md:221,317-325.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions