Skip to content

Keep the discarded read-line calls, drop the unused names (angler unused-let-binding) - #2

Merged
hellerve merged 1 commit into
mainfrom
claude/lint-unused-let-binding
Aug 17, 2026
Merged

Keep the discarded read-line calls, drop the unused names (angler unused-let-binding)#2
hellerve merged 1 commit into
mainfrom
claude/lint-unused-let-binding

Conversation

@carpentry-agent

Copy link
Copy Markdown

bufio's CI would have gone red on the next push, through no fault of its own. The Lint step builds angler fresh from master and runs it over every .carp file outside out/, docs/, .carp-src/ and examples/ — tests included — and angler gained an unused-let-binding rule on 2026-08-05 (00d1f8b), after bufio's last green run on 2026-06-19. Running bufio's exact Lint command with an angler built from current master exits 1:

./test/bufio.carp:27:4:  [unused-let-binding] let-do binding 'first-line' is never used
./test/bufio.carp:145:6: [unused-let-binding] let-do binding 'first-line' is never used

The linter is right that the binding is dead, but the call it binds is not. (BufReader.read-line &br) advances the reader, so the next read returns the second line — which is exactly what both assertions pin ("read-line returns second line", and "clear-read discards buffered data", where the first line has to be buffered before clear-read has anything to discard). Deleting the binding is the obvious-looking fix and it is wrong.

What changed

first-line_first-line in both places. A leading underscore is angler's own exemption for a deliberate discard (binding-name, angler.carp), and it is the same spelling core's ignore macro expands to — (let [_ form] ()). The call, its position in the binding list, and its drop point are all unchanged.

Verification

Everything below was run locally with angler and carp-fmt built from current master.

Gate (bufio's exact CI command) Before After
Lint exit 1, 2 findings exit 0
Format check exit 0 exit 0
carp -x test/bufio.carp 17 passed, 0 failed 17 passed, 0 failed
carp -x gendocs.carp exit 0, no doc diff exit 0, no doc diff

Test output is byte-identical before and after (diffed with ANSI codes stripped).

Behaviour-neutral, at the C level. carp -b test/bufio.carp on both trees; the generated main.c differs in exactly four lines, all of them the variable's name:

14754c14754
<             Result__String_String first_MINUS_line = _77;
>             Result__String_String _first_MINUS_line = _77;
14780c14780
<             Result_delete__String_String(first_MINUS_line);
>             Result_delete__String_String(_first_MINUS_line);
15245c15245
<             Result__String_String first_MINUS_line = _683;
>             Result__String_String _first_MINUS_line = _683;
15278c15278
<             Result_delete__String_String(first_MINUS_line);
>             Result_delete__String_String(_first_MINUS_line);

Same call sites, same order, same drop points.

The calls really are load-bearing. Deleting the two bindings outright — the naive way to clear the finding — drops the suite to 15 passed / 2 failed, and it is precisely the two affected assertions that fail:

Test 'read-line returns second line' failed:
Test 'clear-read discards buffered data' failed:

Alternative considered

Moving the discarded read into the let-do body as (ignore (BufReader.read-line &br)), which is the idiom the rest of this file already uses for discarded results. It reads well at the second site, but the first site would need an extra nested let-do to keep the reads ordered before r is bound, and it moves the drop point earlier. That is a test rewrite rather than a lint fix, so I kept the rename. Happy to switch if you prefer the ignore shape.

No CHANGELOG entry: bufio has no changelog, and nothing user-visible changed.


Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

angler grew an unused-let-binding rule after bufio's last green run, and
it fires twice on test/bufio.carp: 'first-line' is bound but never read.
The binding is dead, the call is not — (BufReader.read-line &br) advances
the reader so the following read returns the second line, which is what
both assertions pin.

Prefixing the name with an underscore is angler's own exemption for a
deliberate discard (binding-name in angler.carp), the same spelling core's
'ignore' macro expands to. The call, its position in the binding list and
its drop point are all unchanged: the generated C differs only in the
four lines that name the variable.

@carpentry-reviewer carpentry-reviewer Bot 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.

Build & Tests

Checked out claude/lint-unused-let-binding at bcffa71 and ran bufio's four CI gates locally with angler and carp-fmt built from current master (fd6d682, both binaries newer than that commit — I checked, since a stale local linter is exactly what hides this class of bug).

Gate (the repo's exact CI command) main this branch
carp -x test/bufio.carp 17 / 0 17 / 0, exit 0
Lint (angler $(find …), 3 files) exit 1, 2 findings exit 0
Format check (carp-fmt --check) exit 0 exit 0
carp -x gendocs.carp exit 0, no doc diff exit 0, no doc diff

CI is green on ubuntu-latest and macos-latest, and both runs are at the current head bcffa71. Branch is 0 behind / 1 ahead of origin/main.

Findings

None. I tried to break each claim in the body and every one held.

The premise is exact. Reverting only test/bufio.carp to main and re-running the same command reproduces the two findings verbatim:

./test/bufio.carp:27:4:  [unused-let-binding] let-do binding 'first-line' is never used
./test/bufio.carp:145:6: [unused-let-binding] let-do binding 'first-line' is never used

The dates line up too: the rule landed in 00d1f8b on 2026-08-05, and bufio's last green run on main was 2026-06-19 (8549872). Since the Lint step clones angler --depth 1 and builds it fresh on every run, nothing in this repo had to change for it to go red.

The underscore is the supported exemption, not a trick. angler.carp:1353-1360binding-name returns Maybe.Nothing for any single-segment symbol where (starts-with-bytes? s "_"), so the pair is skipped entirely. angler's own suite pins it: test/angler.carp:507 asserts "an underscore-prefixed binding is not reported", separately from the bare-_ case at :504.

Behaviour-neutral, confirmed independently. I built both trees with carp -b test/bufio.carp and diffed the generated main.c. Both are 15475 lines and differ in exactly four, all of them the variable's name:

14754c14754  <  Result__String_String first_MINUS_line = _77;
             >  Result__String_String _first_MINUS_line = _77;
14780c14780  <  Result_delete__String_String(first_MINUS_line);
             >  Result_delete__String_String(_first_MINUS_line);
15245c15245  <  Result__String_String first_MINUS_line = _683;
             >  Result__String_String _first_MINUS_line = _683;
15278c15278  <  Result_delete__String_String(first_MINUS_line);
             >  Result_delete__String_String(_first_MINUS_line);

Same call sites, same order, same drop points — the rename does not move ownership.

The calls really are load-bearing, and the tests notice. Deleting the two bindings outright — the fix the linter's wording invites — gives exit 2, 15 passed / 2 failed, and it is exactly the two assertions that depend on the reader having advanced:

Test 'read-line returns second line' failed:
Test 'clear-read discards buffered data' failed:

That is the one way to get this change wrong, and the body identifies it correctly.

On the alternative the body raises: the (ignore …) shape does not fit site 1 without restructuring, and that is not a stylistic preference — let-do bindings are evaluated in order, so a body form cannot be interleaved between the discarded read and r's binding. Getting the ordering right would mean an extra nested let-do, i.e. rewriting the test rather than clearing a lint. The rename is the smaller change and I would keep it.

Verdict: merge

Two characters, and they take the repo from red-on-next-push to green. The premise, the exemption, the C-level neutrality and the load-bearing-call argument all reproduce exactly as described. Nothing user-visible changed, so no changelog entry is correct — and bufio has none anyway. Worth noting for a follow-up: strbuf/test/strbuf.carp:164 is the other repo in the org with the same shape.

@hellerve
hellerve merged commit fc49035 into main Aug 17, 2026
2 checks passed
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