Skip to content

gen-c: a string's .len is strlen, in both spellings - #3490

Merged
gHashTag merged 1 commit into
masterfrom
c/string-len
Sep 8, 2026
Merged

gen-c: a string's .len is strlen, in both spellings#3490
gHashTag merged 1 commit into
masterfrom
c/string-len

Conversation

@gHashTag

@gHashTag gHashTag commented Sep 8, 2026

Copy link
Copy Markdown
Owner

gen-c: a string's .len is strlen, in both spellings

Closes #3489

Five passes named the len family and moved on, each for a stated
reason. Splitting it by WHAT THE BASE ACTUALLY IS shows that a third of
it needs no decision at all: string lowers to const char*, and C's
answer for its length is strlen.

THREE SPELLINGS, NOT ONE:
s.len() ONE ExprCall named s.len 1322 in the specs
s.len an ExprFieldAccess 687
len(s) a call to a free function 142 diagnostics

The fourth time in this campaign that one rule had several spellings and
only one was taught. The first two now go through ONE helper, so a
change to the rule cannot reach one and miss the other.

AND THE BASE DECIDES WHETHER AN ANSWER EXISTS:
string 223 sites -> strlen(s)
slice []T 449 sites -> NEEDS A REPRESENTATION (#3464)

Rust's .len() on &str, Zig's .len on []const u8 and C's strlen
all count bytes, so the three agree and nothing is invented.

MEASURED, whole corpus, -ferror-limit=0:
errors 11 642 -> 11 401 (-241)
files better/worse 14 / 0
strlen( emitted 0 -> 247, in 15 files
igla_coder_eval 178 -> 103

#include <string.h> IS DECIDED BY THE SAME TWO HELPERS THE EMITTER
USES. A missing include is an undeclared function -- the very family
this repair shrinks -- and deciding it in a second place is how that
happens. The mutant that drops it dies on five tests.

THE THIRD SPELLING WAS MEASURED AND NOT HANDLED. Of the 302 argument
shapes of len(x), ZERO are a string parameter: 171 identifiers that
are not parameters, 89 not plain identifiers, 19 slices, 14 u32. A
branch for it would be unreachable today.

A MUTANT THAT DROPPED THE PER-ITEM RESET SURVIVED, AND THE FIXTURE WAS
THE REASON. The leak test declared an i32 named s in a test block
and asserted s == 3 -- but a leaked set only shows up where the rule
would FIRE. Rewritten to assert(s.len() == 0), which becomes
strlen(s) on an i32 if the set leaks; the mutant dies.

1337 .len sites remain (886 calls, 451 fields), left loud on purpose:
a slice lowers to a bare pointer, and strlen on one would read past
the end of anything that is not NUL-terminated bytes.

Tests: 6 new, full suite 3543 passed 0 failed. Six mutants, all killed.
14 stale seals refreshed.

🤖 Generated with Claude Code

Closes #3489

Five passes named the `len` family and moved on, each for a stated
reason. Splitting it by WHAT THE BASE ACTUALLY IS shows that a third of
it needs no decision at all: `string` lowers to `const char*`, and C's
answer for its length is `strlen`.

THREE SPELLINGS, NOT ONE:
  `s.len()`  ONE `ExprCall` named `s.len`      1322 in the specs
  `s.len`    an `ExprFieldAccess`               687
  `len(s)`   a call to a free function          142 diagnostics

The fourth time in this campaign that one rule had several spellings and
only one was taught. The first two now go through ONE helper, so a
change to the rule cannot reach one and miss the other.

AND THE BASE DECIDES WHETHER AN ANSWER EXISTS:
  `string`      223 sites   ->  strlen(s)
  slice `[]T`   449 sites   ->  NEEDS A REPRESENTATION (#3464)

Rust's `.len()` on `&str`, Zig's `.len` on `[]const u8` and C's `strlen`
all count bytes, so the three agree and nothing is invented.

MEASURED, whole corpus, -ferror-limit=0:
  errors            11 642 -> 11 401   (-241)
  files better/worse    14 / 0
  `strlen(` emitted      0 -> 247, in 15 files
  igla_coder_eval      178 -> 103

`#include <string.h>` IS DECIDED BY THE SAME TWO HELPERS THE EMITTER
USES. A missing include is an undeclared function -- the very family
this repair shrinks -- and deciding it in a second place is how that
happens. The mutant that drops it dies on five tests.

THE THIRD SPELLING WAS MEASURED AND NOT HANDLED. Of the 302 argument
shapes of `len(x)`, ZERO are a `string` parameter: 171 identifiers that
are not parameters, 89 not plain identifiers, 19 slices, 14 `u32`. A
branch for it would be unreachable today.

A MUTANT THAT DROPPED THE PER-ITEM RESET SURVIVED, AND THE FIXTURE WAS
THE REASON. The leak test declared an `i32` named `s` in a test block
and asserted `s == 3` -- but a leaked set only shows up where the rule
would FIRE. Rewritten to `assert(s.len() == 0)`, which becomes
`strlen(s)` on an `i32` if the set leaks; the mutant dies.

1337 `.len` sites remain (886 calls, 451 fields), left loud on purpose:
a slice lowers to a bare pointer, and `strlen` on one would read past
the end of anything that is not NUL-terminated bytes.

Tests: 6 new, full suite 3543 passed 0 failed. Six mutants, all killed.
14 stale seals refreshed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag enabled auto-merge (squash) September 8, 2026 12:15
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 NotebookLM Notebook linked to this PR

This notebook contains session context, decisions, and artifacts for this work.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-09-08 12:15:57 UTC

Summary

Status Count
Total Open PRs 17
PRs with Failing Checks 13
PRs with All Checks Green 4
READY 2
FAILING 13
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 13 + 0 + 0 = 15, and there are 17 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=d958bcfcb5e4 != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

@gHashTag
gHashTag merged commit c5b92ba into master Sep 8, 2026
30 of 33 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.

gen-c: s.len on a string is strlen(s) -- the decidable third of the len family

1 participant