Skip to content

Catalog the standard library, and recognize a sortedness check - #43

Merged
zmaril merged 1 commit into
mainfrom
pr3-std-catalog-is-sorted
Aug 25, 2026
Merged

Catalog the standard library, and recognize a sortedness check#43
zmaril merged 1 commit into
mainfrom
pr3-std-catalog-is-sorted

Conversation

@zmaril

@zmaril zmaril commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Stacked on #42.

The catalog builder was missing most of the standard library

It read trait declarations and free functions. A method written on a type
belongs to neither, so Vec::push and <[T]>::is_sorted fell between the two
passes and no catalog ever held them. In core alone that is 5,621 methods
most of what a caller of std actually calls, and the reason slice::is_sorted
could not be recommended at all.

Inherent implementations are now a third pass, named by the type they are
written on: a nominal type by the path its crate publishes, a built-in one the
way the language names it (slice, str, u32), and anything with no name a
caller could write left out rather than invented.

A sortedness check

Coverage::Adjacent is the walk the Pairwise doc said would arrive when
something needed it — a single loop, not a nested one, and the only coverage
where the two are neighbours, which makes it the one that says anything about
order. The bound must stop one short, because that is what keeps v[i + 1]
inside the sequence.

The second idiom cost three answers rather than a second recognizer: which pairs
the walk must reach, what it asks of a pair, and what must hold before the API
may stand in. Sortedness admits only the adjacent coverage, because a > b over
every pair decides something far stronger.

The conditions come out right without being told to. slice::is_sorted needs
T: PartialOrd and so does the loop, so the element bound is dropped rather
than restated; it allocates nothing, so a const fn may still have it; it makes
the same comparisons, so nothing is hidden. What is left is one real gap: the
loop refuses on a strict a > b and the API accepts only on a <= b, which
agree under a total order and not under a partial one. Two NaNs are sorted to
the loop and not to the API.

Measured

30 findings across 127,210 CodeNet files, all 30 read and confirmed. Zero
across 3,650 files of production Rust.
One file has two neighbour loops and
the recognizer took the right one — the other writes through the index.

Review notes — the catalog is not committed

core.json is 26 MB of generated JSON against itertools' 312 KB, and is
reproducible in two seconds from a rustup component. It follows the existing
.gitignore convention for generated packs rather than entering history;
infact-packs/rust-std/README.md is the command, and infact.toml already
points at where it lands.

Consequence: a fresh clone produces no is_sorted findings until that command
is run once.
The tests do not depend on it — a test that needs a real
signature reads a single-callable excerpt under
crates/infact-rust-behaviors/tests/fixtures/catalog/.

Only a nightly toolchain emits rustdoc JSON, so the version recorded is a
nightly version. That is what a finding is bound to, and it is honest: the
signature it was checked against came from that compiler and no other.

332 tests, clippy clean.

@zmaril
zmaril force-pushed the pr3-std-catalog-is-sorted branch from afb4600 to 2d2174e Compare August 25, 2026 18:50
Base automatically changed from pr2-idiom-all-different to main August 25, 2026 20:28
The catalog builder read trait declarations and free functions. A method written
on a type belongs to neither, so `Vec::push` and `<[T]>::is_sorted` fell between
the two passes and no catalog ever held them. For core that is 5,621 methods —
most of what a caller of the standard library actually calls, and the reason
`slice::is_sorted` could not be recommended at all. Inherent implementations are
now a third pass, named by the type they are written on: a nominal type by the
path its crate publishes, a built-in one the way the language names it, and
anything with no name a caller could write left out rather than invented.

`Coverage::Adjacent` is the walk the `Pairwise` doc said would arrive when
something needed it. A single loop, not a nested one, and the only coverage
where the two are neighbours — which makes it the one that says anything about
ORDER. The bound must stop one short, because that is what keeps `v[i + 1]`
inside the sequence, and the extent is therefore one past the bound.

`Idiom` now holds two, and the second cost three answers rather than a second
recognizer: which pairs the walk must reach, what it asks of a pair, and what
has to hold before the API may stand in. Sortedness admits only the adjacent
coverage, because `a > b` over EVERY pair decides something far stronger.

The conditions come out right without being told to. `slice::is_sorted` needs
`T: PartialOrd` and so does the loop, so the element bound is dropped rather
than restated; it allocates nothing, so a `const fn` may still have it; it makes
the same comparisons, so nothing is hidden. What is left is the one real gap,
and it is a subtle one: the loop refuses on a strict `a > b` and the API accepts
only on `a <= b`, which agree under a total order and not under a partial one.
Two NaNs are sorted to the loop and not to the API.

Measured: 30 findings across 127,210 CodeNet files, all 30 read and confirmed;
zero across 3,650 files of production Rust. One file has two neighbour loops and
the recognizer took the right one — the other writes through the index.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ghYbUJFcVfkKYacbTVJsR
@zmaril
zmaril force-pushed the pr3-std-catalog-is-sorted branch from 2d2174e to 28f1943 Compare August 25, 2026 20:28
@zmaril
zmaril merged commit df76873 into main Aug 25, 2026
1 check 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