Skip to content

fix(no-unnecessary-type-assertion): keep assertions that supply a call's type argument - #1142

Open
mizchi wants to merge 2 commits into
oxc-project:mainfrom
mizchi:fix/nuta-contextual-generic-backfill
Open

mizchi wants to merge 2 commits into
oxc-project:mainfrom
mizchi:fix/nuta-contextual-generic-backfill

Conversation

@mizchi

@mizchi mizchi commented Aug 19, 2026

Copy link
Copy Markdown

Fixes #1141.

A type parameter that appears in none of a signature's parameters can only be inferred from the return position, or else fall back to its default. When such a call is the operand of a type assertion, the assertion is what the type argument gets inferred from, so the operand ends up with exactly the asserted type and the assertion looks like it changes nothing. It isn't — removing it drops the type argument back to its default and the code stops compiling, which is what the autofix did.

getUncastType already asks for the context-free type to keep that inference out of the comparison (#824), but the checker caches a call's resolved signature per node, so the guard only holds while nothing else has checked the call. Reporting type errors alongside lint diagnostics (ReportSemantic: true, oxlint's --type-check) checks every file first and defeats it every time; without it, whether the rule fired came down to concurrent scheduling.

Summary

  • don't report an assertion that is what a call's type argument gets inferred from: an (optionally awaited) call / new / tagged template with no type arguments written, whose resolved signature declares a type parameter that none of its parameters mention
  • read that shape off the signature declaration instead of the checker's cached state, so the result no longer depends on what has already been checked
  • add regression tests that drive the rule through linter.RunLinterOnProgram with ReportSemantic: true, since the existing rule tester runs with type errors off, where the bug does not reproduce

Tests

AI disclosure: this change was produced with AI assistance (Claude Code) and reviewed by me.

…l's type argument

A type parameter that appears in none of a signature's parameters can only be
inferred from the return position, or else fall back to its default. When such a
call is the operand of a type assertion, the assertion is what the type argument
gets inferred from, so the operand ends up with exactly the asserted type and the
assertion looks like it changes nothing.

It does. Dropping `query('k') as Derived` from

    declare function query<T extends Base = Base>(key: string): T;
    const v = query('k') as Derived;

falls back to `T = Base`, and the code stops compiling - but the rule reported it
and the autofix removed it.

`getUncastType` already asks for the context-free type to keep that inference out
of the comparison, but the checker caches a call's resolved signature per node,
so once anything has checked the call the context-free type *is* the contextual
one. Reporting TypeScript's type errors alongside lint diagnostics (oxlint's
`--type-check`) checks every file up front and made this reproduce every time;
without it, whether it reproduced depended on what else had already been checked.

Detect the shape instead of relying on checker state: an (optionally awaited)
call, new, or tagged template with no type arguments written, whose resolved
signature declares a type parameter that no parameter mentions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

Bug: [no-unnecessary-type-assertion] non-deterministic false positive whose autofix breaks the build

1 participant