checker: knock down the remaining TS7 conformance MISSes - #251
Merged
Merged
Conversation
TS2708 through an `import a = A` alias, TS2403 inside a function scope,
and the polymorphic `this` type. Plus an unbounded recursion that ends
the process, reachable at HEAD.
`importStatementsInterfaces` (TS2708) needed two things, and the
recorded blocker was only half of one. The note said whether an
`import a = A` alias binds a VALUE "depends on the TARGET, which is not
resolved at parse time" — true of what the target MEANS and false of
what it SPELLS, and the spelling is all the rule needs, since an alias
binds a value exactly when its target does. The parser records the whole
dotted target beside the `<import-eq-root>` marker it already wrote, and
the four exclusions then decide against the TARGET. Probed one cell at a
time: a type-only namespace target reports at top level, nested
(`A.inA`) and through a chained alias, while an INSTANTIATED target, an
enum, a class and a function stay silent — the last three because tsc
gives TS2702 / TS2503 / TS2693 there, codes this rule does not claim.
The second was `namespace_is_instantiated` counting a nested namespace's
own value entry. `parse_namespace_decl_with_mode` builds that value
before it has parsed the body, so `namespace A { export namespace inA {
export interface P {} } }` read as existing at runtime. It does not, and
tsc reports `typeof A` as TS2708 — which a test in the SILENT list said
in its own comment ("tsc reports TS2708 there"), the fourteenth test here
found asserting a gap rather than a behaviour. An EMPTY inner namespace
is the same answer, probed.
TS2403 is the applied-in-some-places family at a scope boundary: the
rule and its identity comparison ran over `module_.top_level_stmts` and
nowhere else, so `function f() { var b: number; var b: string }` was
silent, and so was every class method, constructor, arrow and function
expression. The comparison is extracted and shared rather than copied.
`var` is function-scoped, so a nested block, an `if` arm, a `for` head, a
`switch` case and a `try` block are one group while a nested function is
not — collected by a STATEMENT walk, which is complete by construction
because only a declaration statement can introduce a `var`. Parameters
participate, as they do in tsc, including `b?: number` against the
`number | undefined` our parser already builds; a DESTRUCTURED parameter
is excluded because `p.name` is only the pattern's first name while
`p.type_` is the whole pattern's type, so comparing it would report legal
code.
`typeOfThisGeneral` then falls out of one more fact: `this` in an
INSTANCE member is the polymorphic `this` type, not the enclosing class's
name. `identity_key` learns it (`this` is a reserved word, so no
declaration can claim the name), and `types_definitely_differ` gets the
`this`-against-a-named-type arm a class name needs, since
`structural_named_key` declines one. Probed in both directions and in
both rules that read it: tsc reports the var pair, and `interface D
extends A, B` with `A.m(): this` against `B.m(): A` is TS2320, while two
bases that BOTH say `this` are legal. A static method's `this` is
`typeof C` and a free function's is `any` — both real TS2403 sources,
both a MISS here rather than a guess.
The segfault is the batch's most useful finding and predates it.
`types_definitely_differ` recurses on types `unwrap` has already
resolved, with no depth bound, so a self-referential `typeof` closes the
loop: `witness.ts` crashed the binary the moment the function-scope sweep
fed it `function fnArg1(x: typeof fnArg1) { var x: (n: typeof fnArg1) =>
void }`. The module-level spelling could always reach it — measured by
stashing the branch, rebuilding HEAD and running it, not assumed — and no
corpus file had that shape at top level. Bounded at 16, matching
`identity_key`; running out of depth abstains, which is this function's
own fail direction.
TP 2665 -> 2667 | MISS in scope 50 -> 48 | FP 0 | PFLEGAL 0 | TN 1750.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
Zero conformance files by design (TP 2667 / MISS in scope 48 / FP 0,
unchanged), and two things the corpus cannot score: a capability at the
shape real code writes, and a false positive on legal code.
A generic FUNCTION's type arguments have been inferred from the
arguments for as long as `infer_call` existed. A generic METHOD's never
were — `declare function f<T>(x: T): T; const q: string = f(12)`
reported while `interface I { m<T>(x: T): T }` with `i.m(12)` came back
carrying an unresolved `T`, which every downstream check reads as
unknowable. Probed one spelling at a time, and FOUR of them were
involved, each losing the binder its own way:
- an interface records it in `method_type_params` and a class method in
`TsClassMethodDecl.type_params`, both already there and read only by
`scoped_bindings_for_method`, never at a call site;
- a member written as a function TYPE (`m: <T>(x: T) => T`) carries it
on the `GenericFunc` wrapper, and `lookup_method_sig` matched `Func`
and `CallableMeta(Func)` and not that — the eighth fail-open
wrapper-node arm in this repo's ledger, and it made the whole member
opaque rather than merely un-instantiated;
- `infer_call` had the same hole, so a generic callable arriving as a
VALUE fell to the `Any` catch-all;
- an inline object type's method signature threw the binders away in
the parser, under a comment saying it did ("recorded nowhere on an
anonymous object, so just skip them"). They ride the same
`GenericFunc` wrapper the type parser already builds for the PROPERTY
spelling of the same thing, so the two spellings stop disagreeing.
The solve is `solve_generic_bindings`, the function the `Call` arm
already uses, so a callee reached by NAME and one reached through a
member cannot answer differently.
A CONSTRUCT / CALL signature's binders are deliberately NOT carried, and
the reason is measured rather than argued: wrapping them cost a true
positive outright — `genericCallWithOverloadedConstructorTypedArguments2`
is all `new <T>(…)` members, and the wrapper is visible to every consumer
that matches the member's shape. Nothing reads those binders yet, which
is the same reason CLAUDE.md records for not walking them elsewhere.
The false positive is the optional-CALL form of a chain. Batch EH
removed the unconditional `| undefined` on a non-nullable receiver and
covered a property, an index and a method call; `x?.(args)` has no
receiver — what the `?.` guards is the CALLEE — so it took the "shape
this cannot read" default and was widened unconditionally. `declare
const c: (n: number) => number; const r: number = c?.(1)` is ACCEPTED by
tsc and was reported. No corpus file has the shape, which is why FP 0
never caught it; found by probing the legal neighbour of the generic
work above.
`infer_call`'s union arm also stopped treating a NULLISH member as a
reason to collapse the answer to `Any`: that member is not a callable
the union offers, it is why the call needs a `?.` at all, and invoking a
possibly-nullish value without one is TS2722 — a diagnostic of its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
… type
Zero conformance files, again by design: this is one of the two BLIND
rows `UNSUPPORTED.md`'s capability probe found by asking the COMMON
SHAPE rather than the corpus, which contains neither. TP 2667 / MISS in
scope 48 / FP 0, unchanged.
`infer_index`'s `Named` / `Applied` arm has resolved an index-signature
read for a long time — a string key, a numeric key and a variable key
each falling back to the signature when no named field matches — and the
ANONYMOUS spelling had no arm at all, so `declare let m: { [k: string]:
V }; m["a"].nope` yielded no type and every check downstream abstained.
`collect_index_sigs` had the same hole one level down, so the signature
was invisible to every consumer of that list while the INTERFACE
spelling of one declaration was not. Both now read the member the parser
records keyed by its KEY TYPE, which is exactly what
`is_index_signature_key` recognises.
Two measurements worth keeping. The WRITE direction that `UNSUPPORTED.md`
lists beside it as BLIND is not: `m["x"] = "s"` against `{ [k: string]:
number }` already reported, in the anonymous spelling, the dotted
spelling, the numeric-key spelling and through an interface — so that
row is stale and the doc is corrected. And a MAPPED type is NOT reduced
to an index signature, so `{ [P in string]: D }` still yields nothing
where the `{ [k: string]: D }` spelling of the same type now resolves;
that is a declared MISS in the test rather than a claim, because the
first draft asserted it as a fire and the run said otherwise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
…inite key set
`{ [P in string]: V }` IS `{ [k: string]: V }`, and so is the `keyof any`
spelling — TypeScript says so — but neither evaluator could ENUMERATE
that key set, so the type stayed an unresolved `MappedType`, read as
unmodelled, and every member check downstream abstained while the
`{ [k: string]: V }` spelling of the same declaration resolved. Reduced
in `unwrap`, next to the five computed-type forms it already reduces.
The guard is where the work is, and it cost TWO false positives on
TS7-accepted files before it was right. A HOMOMORPHIC mapped type over
`any` — `{ [P in keyof T]: Box<T[P]> }` at `T = any` — yields `any` and
not a shape, which is the whole subject of `mappedTypeWithAny`; rendering
it as an index signature produced `{ string: Box<any[string]> }` and
reported `mappedTypesArraysTuples` and `thisTypeInObjectLiterals2`. The
line TypeScript draws is whether the VALUE depends on the key, and the
first attempt at testing that was defeated by a shape rather than by an
argument: `type_references_any` walks the structural variants and has no
`IndexedAccess` arm, so `Box<T[P]>` reads through it as independent of
`P`. Answered by a separate predicate rather than by widening that walk,
whose other consumers ask a different question. `{ [P in string]: P }` is
the MISS the guard costs.
Two smaller facts fell out. The substituted value is REDUCED before it is
stored, because a homomorphic source leaves `T[P]` behind and `unwrap`
reduces `any[string]` to `any` only at the top level, never inside a type
ARGUMENT — the shape came out `PropDesc<any[string]>`. And only the
un-remapped form participates: an `as` clause can rename or drop keys, so
an infinite source says nothing about the result's shape.
TP 2667 -> 2668 | MISS in scope 48 -> 47 | FP 0 | PFLEGAL 0 | TN 1750.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
…n indexer
`check_expr_against` carried `(Intersection(_), _) => return` under a
comment saying the modelling was "still too coarse structurally", and
the abstention was TOTAL: an intersection-typed value was invisible in
every assignment, against every target — `declare let s: {a: string} &
{b: number}; let t: string; t = s` reported nothing. That is exactly
what batch EN went looking for and could not find. It measured
`is_assignable_to_inner`'s source-intersection merge arm as unreachable
for an intersection-typed VALUE, reverted a fix that "provably changes
nothing", and wrote that finding what abstains first "is the actual
work". This is it.
The rule is restricted to an INDEX-SIGNATURE target, and the
restriction is a measurement rather than caution. Asking
`is_assignable_to(merged, target)` for ANY target is the same +1
conformance file and **four false positives on hand-written legal
code** — `A & B` into `A`, into `B`, an intersection carrying an index
signature into `A`, and a recursive alias into `A` — because that entry
point is resolver-free and cannot expand a `Named` target to compare
structurally, which is what the old comment's "too coarse" actually
meant. The corpus contains none of those shapes, so FP 0 stayed 0 with
the wrong rule in: the fourth time in this series that the legal
neighbour caught what the gate structurally could not.
An index-signature target needs no such expansion. An indexer is
constrained by EVERY property the source has, so the merged shape is the
exact question, and it is the case the corpus and real code both hit:
`tt = ss` with `ss: { a: string } & { b: number }` and `tt: { [k:
string]: string }` is TS2322 on `b`. A component that is not object-like
does not merge and still abstains; `interface A & interface B` into an
indexer is a MISS, since interface members get no implicit index
signature and that is a fact about the SOURCE this rule does not model.
TP 2668 -> 2669 | MISS in scope 47 -> 46 | FP 0 | PFLEGAL 0 | TN 1750.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
No checker change. `TP 2669 / MISS in scope 46 / OUT OF SCOPE 19 / FP 0 / PFLEGAL 0 / TN 1750`, and the five documents describing the backlog were all four files behind it. - `src/checker/UNSUPPORTED.md` §1 lists the 46 by machinery, with the four files batches EU–EY flipped removed from their sections (`importStatementsInterfaces`, `typeOfThisGeneral`, `mappedTypeWithAny`, `intersectionWithIndexSignatures`) and the per-section counts summing to 46 again. §2 was RE-PROBED one file per row, and two rows were wrong in the direction that matters: the index-signature WRITE read BLIND for two revisions and had been handled in all four spellings the whole time, and the template-literal placeholder read BLIND while the error shape reports. A capability table nobody re-measures ranks the wrong work — the same defect, one level up, that retired `docs/checker-priority.md`. §3 gains four declared abstentions with their numbers, including `this` in an object-literal `function` property, which was instrumented rather than argued about and is filed with the two blockers that stopped it. - `docs/checker-triage.md`'s family table is re-counted; `classes/this/ mixins` is 7, `type-level machinery` 6, `name resolution` 3. The row that called `intersectionWithIndexSignatures` a recorded rejection now says what it turned out to be: batch EN had measured the deciding arm as unreachable and written that finding what abstains first "is the actual work", and it was an explicit early return with a stated reason. - `justfile`'s `--max-miss` is tightened 50 -> 46, which is the direction that budget only ever moves, and the gate passes at it. - `docs/mtsc.md`'s compatibility snapshot and `CLAUDE.md`'s headline numbers are re-measured; `CLAUDE.md`'s `src/checker` section records the batches, the segfault, the three false positives on legal code and the two rejections with their blockers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
… methods
`TP 2669 -> 2670 | MISS in scope 46 -> 45 | OUT OF SCOPE 19 | FP 0 |
PFLEGAL 0 | TN 1750`
One conformance file (`callChain.3`) carrying FOUR independent defects,
plus a false positive on legal code the corpus structurally cannot see.
The file is filed in the triage under "object literals, contextual
typing, widening", which is not what any of the four is.
1. An optional chain short-circuits for the WHOLE chain, and the parser
puts only the guarded link inside the `OptionalChain` node — `g?.p.q`
is `PropAccess(OptionalChain(PropAccess(g, p)), q)` — so that outer
`.q` pruned the nullish receiver and handed back a bare `number`.
The propagation reads the chain node's own RESULT rather than
re-deciding its guard. `is_nullable_type` is NOT that predicate: it
answers "is this type ENTIRELY nullish", so `{ q: number } |
undefined` reads false there and the first draft fired on nothing.
2. `m?<T>(x)` did not PARSE at either member parser. The grammar is
`PropertyName ?opt CallSignature` and a CallSignature BEGINS with its
type parameters, so the `?` comes first; both read the binders first.
The object-type parser's failure was TOTAL — the whole literal fell
back to `Any`, so every member became unknowable, not just the
generic one.
3. An OPTIONAL method is stored as `Union([callable, Undefined])`, so
every consumer matching the callable SHAPE read it as opaque and
`a.m?.(1)` came back `Any`. `lookup_callable_field` removes the
wrapper for the two callers asking "what CALLABLE is this member",
and returns `None` unless exactly one non-nullish member remains, so
an overload set still goes to the overload path.
4. `unwrap` PEELS a `GenericFunc` — it says so at the site — so a
generic member of a union callee reached `infer_call` with its
binders gone and `c?.m({ x: 12 })` came back with `T` unsolved.
Generic members are instantiated only after every concrete one has
failed, which is TypeScript's own overload order.
The false positive: `method_overload_signatures` had arms for a class,
an `Object` type literal and a `Struct` — the three shapes that are NOT
how a `.d.ts` declares an overload set. With no candidates,
`resolve_method_overload` fell through to `lookup_method_sig` (first
declaration only) AND the argument check's `> 1` abstention never fired,
so `interface O { m(x: string): number; m(x: number): string }` with
`p.m(1)` reported `expected string but got number` on a line tsc
accepts. Confirmed pre-existing by stashing the branch and rebuilding
HEAD; corpus-neutral when fixed.
One more found the same way: the IndexAccess arm passed its receiver to
`infer_index` raw, where the PropAccess arm has pruned a nullable
receiver and retried for a long time — `u?.a[0]` was `Any` while
`u?.a.length` resolved.
`--max-miss` lowered to 45. Gates: fmt, check --deny-warn (native and
js), moon info, every `*_wbtest.mbt` file (2,853 tests), the oracle,
verify-checker-scaling, verify-generated-fixtures, verify-scaffolds,
verify-examples, verify-mbti-dts, verify-bridge-runtime,
verify-bridge-enum-returns, verify-mangle-safety, verify-dce-coverage,
verify-graph-walk, verify-language-service, verify-cli-node.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
`TP 2670 | MISS in scope 45 | OUT OF SCOPE 19 | FP 0 | PFLEGAL 0 |
TN 1750` — corpus-neutral by design.
`unique symbol` is 183 occurrences across 3,000 real `.d.ts` files, the
third commonest feature in `docs/checker-triage.md`'s real-code column,
and it was the top BLIND row of `UNSUPPORTED.md` §2 with no conformance
file anywhere. Three defects and two false positives on legal code.
1. The ANONYMOUS spelling did not PARSE.
`try_parse_object_type_with_members`'s bracket arm falls back for
anything that is not an index signature, so `{ [k]: number; plain:
number }` came out `Any` and EVERY member of it was lost, not just
the computed one — batch EZ's `m?<T>` shape again. That fall-back is
load-bearing for a mapped type (`[K in keyof T]` reaches the same
arm), so the fix is a positive shape test — exactly `[ Ident ]` —
rather than a widened fallback.
2. The INTERFACE spelling named the member `<computed>`, a name no
lookup can match, while the sibling WELL-KNOWN spelling twenty lines
above has had a stable `@@name` for a long time. Both record
`@@unique:<k>` now.
3. Nothing translated the index EXPRESSION back, so `i[k]` was `Any`
even once the member had a name. Wired at BOTH `infer_index` arms,
gated on the index's TYPE being a `unique symbol`.
The false positives are pre-existing and were found by probing a legal
neighbour: a computed key can also be a string-literal `const`
(`const kk = "hello"; interface I { [kk]: number }`), which tsc names
`hello` and neither member parser can evaluate. Reading the undecidable
name as "no member called `hello`" reported `i.hello` on a line tsc
accepts. Confirmed pre-existing by stashing and rebuilding HEAD — the
`<computed>` half predates the `@@unique:` name. The abstention sits in
`member_recv_unmodeled` beside the index-signature one it already makes
for the same reason; a well-known key is excluded because it IS decided
statically, measured (`w.nope` still reports).
One approximation, stated: tsc keys a late-bound member by SYMBOL, this
keys it by spelling, so two `unique symbol` bindings sharing a name
across scopes resolve to one member — a wrong member rather than a
report on legal code, since tsc calls that program TS2339.
Gates: fmt, check --deny-warn (native and js), moon info, every
`*_wbtest.mbt` file (2,854 tests), the oracle, verify-checker-scaling,
verify-generated-fixtures, verify-scaffolds, verify-examples,
verify-bridge-runtime, verify-bridge-enum-returns, verify-mangle-safety,
verify-dce-coverage, verify-graph-walk, verify-language-service,
verify-cli-node.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
`UNSUPPORTED.md` §2 ranks work by what the checker HAS, and CLAUDE.md records twice that a table nobody re-measures ranks the wrong work. A table with MISSING rows is one level further out: `satisfies` (238) and `as const` (108) are the two commonest features in the triage's real-`.ts` column and neither had a row, nor did `infer` (156 in `.d.ts`). Probed, one file per row: - `satisfies` — CAUGHT at all three cells (excess property, a member of the wrong type, and the narrowed type surviving the read). - `infer` through a conditional alias — CAUGHT. - `as const` on an OBJECT LITERAL — **BLIND**, and the cause is the non-obvious part, so it is filed in §3 rather than left as a label. `is_widening_direction_mismatch` is RIGHT to suppress it: that arm exists because our inference sometimes widens where tsc keeps a literal, so a report there can be about our gap rather than the program (batch EL kept it for exactly that). It fires here because `as const` does not keep its members' literal types — `a.k` comes out `string` where tsc has `"hello"`. Localized by measurement rather than by reading: the DECLARED spelling reports, so the member READ is not what loses it, and `a.nope` proves the member is `string` and not `Any`. The fix belongs in `as const`. - `as const` elsewhere (`"hello" as const`, a tuple index, a declared literal member) — CAUGHT at the verdict, with the message naming `string` where tsc names `"hello"`: a single `Literal` widens and a UNION of literals does not. No behaviour change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
The row committed one commit earlier blamed `is_widening_direction_mismatch`
and was wrong twice over, which is the defect this repo's own notes warn
about: a stated reason that the code it names contradicts.
- Batch EL REMOVED that function's string arm. Only number, boolean,
bigint and void remain, so it cannot be what suppresses a string
source against a string-literal target.
- `as const` is not the discriminator either. The identical shape
without it is equally silent, and the parser DROPS the const
assertion outright (`parse_asserted_*` does not wrap with `As(...)`,
by design, because the transform passes want the raw expression), so
the checker never sees one at all.
What a six-cell matrix does say, measured:
declare const d: { k: "hello" } ; d.k against "other" -> reports
declare const d2: { k: string } ; d2.k against "other" -> SILENT
const i1 = { k: "hello" } ; i1.k against "other" -> SILENT
const i1 = { k: "hello" } ; i1.k against number -> reports
declare const d3: { k: string } ; d3.k against number -> reports
const i3: { k: string } = {...} ; i3.k against "other" -> SILENT
So it is the TARGET being a string literal that suppresses, whatever the
receiver's origin, and a LITERAL member is what makes the first cell
fire. `is_assignable_to(String_, Literal("foo"))` is correctly `false`
(asserted in `assignability_wbtest.mbt`) and the `(String_, Literal(_))`
arm at `expr_check.mbt:7950` is an OVERLAP predicate for `==`, correct
as written — so the suppression is a third site, the
applied-in-some-places family again. Filed with the matrix and NOT a
diagnosis; worth taking with the corpus as the gate, since a
`string`-to-literal report is the commonest real TS2322 there is.
No behaviour change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
The capability probe's `as const` row turned out to be a corner of a
much larger gap, and instrumenting is what found it after three
candidate causes were each refuted by reading the code they named.
Seven cells, all probed against tsc:
declare const s: string;
const a1: "other" = s; // TS2322 — SILENT
let a2: "other"; a2 = s; // TS2322 — SILENT
const a3: "a" | "b" = s; // TS2322 — SILENT
f(s) where f(x: "a" | "b") // TS2345 — SILENT
type Mode = "r" | "w"; const a4: Mode = m; // TS2322 — SILENT
declare const n: number; const a6: 1 = n; // TS2322 — SILENT
const a5: "other" = "lit"; // TS2322 — reports
So a PRIMITIVE source against a LITERAL or literal-union target is
accepted, at the binding, the assignment and the call argument, for
strings and numbers alike; only a syntactically literal source reports.
This is the commonest real TS2322 there is — every options-string and
discriminated-union API produces it.
Refuted along the way, each by reading the named code:
`is_widening_direction_mismatch` lost its string arm in batch EL;
`is_assignable_to(String_, Literal("foo"))` is correctly `false`
(asserted in `assignability_wbtest.mbt`); the `(String_, Literal(_))`
arm at `expr_check.mbt:7950` is an OVERLAP predicate for `==`, right as
written; and `as const` is not the discriminator (the parser drops the
assertion outright, and the identical shape without it is equally
silent).
Instrumenting settled it in one run: `check_expr_against` receives
exactly `src=String_ exp=Literal(other)` and emits nothing, and the
STRICT entry point reports the same three issues as the permissive one —
so the mismatch is never DECIDED and no filter is involved.
Filed rather than fixed, with the blocker named. Our inference widens a
literal to `string` where tsc keeps it, so reporting every `string` ->
literal would be about OUR widening rather than the program — the fact a
sound rule needs is "this type came from a WRITTEN annotation", the same
absent-versus-`: any` channel recorded for TS7031, TS7022, TS2729,
TS2448, TS2564 and TS2490. That is a batch with the corpus as its gate,
not a row edit.
No behaviour change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Working the remaining
MISS in scopebacklog down from the measured baseline of 50 (not 30 — the repo's own docs, thejustfilebudget and a fresh oracle run all said 50), atFP 0.Status
--max-misstightened 50 → 45, which is the direction that budget only ever moves.Five conformance files, six capabilities the corpus cannot score, twelve false positives on legal code, and a segfault. Five of the eight batches buy zero files each and were taken for the capability.
The segfault, first
types_definitely_differrecurses on typesunwraphas already resolved — that is its design, it proves rather than normalizes — and it had no depth bound. A self-referentialtypeofcloses the loop:witness.ts, a corpus file that had been a TP for as long as the oracle existed, ended the process the moment batch EU's new sweep handed it that pair. The module-level spelling could always reach it — measured by stashing the branch, rebuilding HEAD and running it, not assumed. No gate here watches for a crash: the oracle scored it as one lost TP.Batches
EU (+2) — TS2708 through an
import a = Aalias: the recorded blocker was true of what the target means and false of what it spells. Its other half wasnamespace_is_instantiatedcounting a nested namespace's own value entry; a test in the SILENT list said in its own comment that tsc reports it there — the 14th test in this repo found asserting a gap rather than a behaviour. Plus TS2403 inside a function scope (the rule ran overmodule_.top_level_stmtsand nowhere else) and the polymorphicthis.EV (0 files) — generic method call inference. Four spellings of one declaration, each losing the binder a different way: interface/class record it and no call site reads it; a function-type property carries it on the
GenericFuncwrapperlookup_method_sighad no arm for;infer_callhad the same hole; an inline object type's method signature threw it away in the parser under a comment saying so. Construct/call-signature binders are deliberately not carried — wrapping them cost a TP outright.EW (0 files) — index-signature reads through an anonymous object type, one of the two BLIND rows the capability probe found by asking the common shape. The other one, the WRITE, turns out not to be blind at all.
EX (+1) — a mapped type over an infinite key set. The guard cost two FPs first, and the first attempt to test "does the value depend on the key" was defeated by a shape:
type_references_anyhas noIndexedAccessarm.EY (+1) — an intersection source against an index signature.
check_expr_againstcarried(Intersection(_), _) => returnand the abstention was total. This is exactly what batch EN went looking for and could not find.EZ (+1) — one file,
callChain.3, carrying four independent defects. It is filed in the triage under "object literals, contextual typing, widening", which is not what any of the four is; each was found by probing the next thing the previous fix exposed.OptionalChainnode —g?.p.qisPropAccess(OptionalChain(PropAccess(g, p)), q)— so that outer.qpruned the nullish receiver and handed back a barenumber. The propagation reads the chain node's own result rather than re-deciding its guard.is_nullable_typeis not that predicate: it answers "is this type entirely nullish", so{ q: number } | undefined— the exact shape a short-circuiting chain produces — reads false there, and the first draft fired on nothing.m?<T>(x)did not parse, at either member parser. The grammar isPropertyName ?opt CallSignatureand a CallSignature begins with its type parameters, so the?comes first; both read the binders first. The object-type parser's failure was total — the whole literal fell back toAny, so every member became unknowable, not just the generic one.Union([callable, Undefined]), so every consumer matching the callable shape read it as opaque anda.m?.(1)came backAny.unwrappeels aGenericFunc— it says so at its own site — so a generic member of a union callee reachedinfer_callwith its binders gone andc?.m({ x: 12 })came back withTunsolved, where the same member reached directly is solved ten lines above.FA (0 files) — a late-bound
unique symbolmember key, the top BLIND row of the capability table at 183 occurrences across 3,000 real.d.tsfiles and no conformance file anywhere. Three more defects, the first of them batch EZ's shape again:try_parse_object_type_with_members's bracket arm falls back for anything that is not an index signature, so{ [k]: number; plain: number }came outAnyand every member of it was lost. That fall-back is load-bearing for a mapped type ([K in keyof T]reaches the same arm), so the fix is a positive shape test — exactly[ Ident ]— rather than a widened fallback, which would have broken one feature to fix another.<computed>, a name no lookup can match, while the sibling well-known spelling twenty lines above has had a stable@@namefor a long time. One question, two answers.i[k]wasAnyeven once the member had a name. Wired at bothinfer_indexarms, since writing it at one is how the two spellings came to disagree in the first place.FB (0 files) — a
stringsource against a LITERAL or literal-union target,UNSUPPORTED.md§3's widest row:declare const s: string; const a: "other" = sand four siblings were all silent, which is the commonest real TS2322 / TS2345 there is. This is the first batch here whose whole case rests on real packages — corpus-neutral on every metric, and on real code it both gains the diagnostic and removes six false positives.infer_exprerases the first three at the source (NumberLit(_) => Number) and keepsLiteral(s). Soconst c = "z"; const c2: "a" | "b" = calready reported while thestring-source form never could. Fifteen string spellings were probed before the arm came out, and every one tsc keeps narrow we keep narrow.as const, which the parser drops for a reason stated at its own site (the transform passes want the raw expression), so{ k: "a" } as constarrives as the plain object literal whose property genuinely does widen here. A file carrying one abstains wholesale, through a file-level marker read iningest_modulerather than beside the other directive flags at the check entry — that site reads only the root module's markers, and a namespace body re-parses with a fresh Parser.let s = "a"isstring,const s = "a"is"a") and did not here — only the composite half of that rule had ever been written. Invisible while nostringsource could be judged against a literal target, and five reports on legal lines the moment one could: zod'sbg.tshaslet invalid_adj = "Невалиден"reassigned five times.let a: "foo" = "foo"; let b = a || "foo"; let c: "foo" = b— a conformance file —a's literal came from an annotation and neither it nor the||over it widens, and tsc acceptsc. A test caught the second: a template literal with a constant substitution folds to a literal here (batch AU, for TS2367) while tsc calls itstring, and a pre-existing parity pin requires avarholding one to behave exactly like the same string written plainly.widen_literal_deep's union arm mapped its members without deduping them, so["a", "b"]'s widened element type wasstring | string— a message nobody can act on, and not equal to the base the abstention tests, soconst arr = ["a", "b"] as const; const y: "a" = arr[0]reported on a line tsc accepts.False positives on legal code the corpus cannot see
declare const c: (n: number) => number; const r: number = c?.(1)—x?.(args)has no receiver, so it took the "shape this cannot read" default.A & BintoA, and three siblings — the unrestricted intersection rule is the same +1 file with four FPs, becauseis_assignable_tois resolver-free.method_overload_signatures, which had arms for a class, anObjecttype literal and aStruct— the three shapes that are not how a.d.tsdeclares overloads. With no candidates, two consumers of that one count went wrong at once:resolve_method_overloadfell through tolookup_method_sig(first declaration only), and the argument check's> 1abstention never fired. Sointerface O { m(x: string): number; m(x: number): string }withp.m(1)reportedexpected string but got numberon a line tsc accepts. Confirmed pre-existing by stashing the branch and rebuilding HEAD; corpus-neutral when fixed.u?.a[0]came backAnywhileu?.a.lengthresolved — theIndexAccessarm passed its receiver toinfer_indexraw, wherePropAccesshas pruned a nullable receiver and retried for a long time with its own comment saying why.const.const kk = "hello"; interface I { [kk]: number }names a memberhellothat neither member parser can evaluate — the value lives in another declaration — and reading the undecidable name as "no member calledhello" reportedi.helloon a line tsc accepts, twice. Also confirmed pre-existing by rebuilding HEAD.effect, all confirmed pre-existing against the baseline binary.Batch FB's measurement method is new here and is the answer to a gate the corpus structurally cannot be:
zod118 → 113 diagnostics throughmtsc --noEmit --bundle, and a sweep ofmtsc checkover 4,085 real files (every.d.tsundernode_modules, pluseffect's 362.tssources) diffed against the baseline binary — byte-identical, no diagnostic added.Rejections, with their blockers
thisin an object-literalfunctionproperty was instrumented rather than argued about (the binding arrives as{ n: number; f: () => any };check_funcexpr_with_contextthen rebinds toAny, deliberately, andnoImplicitThisis not plumbed).typeof x === "object"narrowingunknownneeds two other narrowings first or it is two FPs in the file it would flip.a.m(1)on an optional method is TS2722 in tsc and stays a MISS. The class spelling of a late-bound key (class C { [k]: number }) is untouched. And batch FB's numeric / boolean / bigint half needsinfer_exprto stop erasing those literals at the source, which is a change at every consumer of a numeric literal's type rather than a gate.Why these three, when the corpus ranks them last
docs/checker-triage.mdhas a second axis the MISS count cannot see: how often each feature appears in 3,000 real.d.tsfiles and 2,697 real.tssources. Optional chaining is 144 real occurrences against one conformance file;unique symbolis 183 against zero; astringassigned into a literal union is what every options-string API produces and has no corpus file at all. Reading the corpus count alone ranks all three last.Gates run
moon check --deny-warn,moon check --deny-warn --target js,moon fmt --check,moon info, the fullmoon test --target native(3,074) plus every*_wbtest.mbtfile individually after the last change (2,863; bench files excluded),checker_conformance_oracle.shover all 4,484 files,verify-checker-scaling(12/12 axes within budget),verify-mangle-safety(186/186),verify-generated-fixtures,verify-examples,verify-scaffolds,verify-mbti-dts,verify-bridge-runtime(0 converter runtime failures),verify-bridge-enum-returns,verify-graph-walk,verify-dce-coverage(31 eliminated / 0 broken),verify-language-service(22/22),verify-language-service-types,verify-cli-node(21/21).🤖 Generated with Claude Code
https://claude.ai/code/session_01TgePD6JZrRmq4VEYrS4LLk