Skip to content

Commit 599e150

Browse files
committed
fix(no-unnecessary-type-assertion): avoid contextual generic inference for call expressions (#824)
For call-like expressions, the assersion was infering the actual type. This is fixed by asking the checker for the context-free expression type before comparing it withith the asserted type. fixes oxc-project/oxc#20656
1 parent b022296 commit 599e150

File tree

2 files changed

+53
-0
lines changed

2 files changed

+53
-0
lines changed

internal/rules/no_unnecessary_type_assertion/no_unnecessary_type_assertion.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,15 @@ var NoUnnecessaryTypeAssertionRule = rule.Rule{
245245
}
246246
}
247247

248+
// For call-like expressions, use the context-free expression type so
249+
// contextual typing from the assertion itself doesn't leak into generic
250+
// inference for the original expression.
251+
if ast.IsCallExpression(expression) || ast.IsNewExpression(expression) || ast.IsTaggedTemplateExpression(expression) {
252+
if t := checker.Checker_getContextFreeTypeOfExpression(ctx.TypeChecker, expression); t != nil {
253+
return t
254+
}
255+
}
256+
248257
return ctx.TypeChecker.GetTypeAtLocation(expression)
249258
}
250259

internal/rules/no_unnecessary_type_assertion/no_unnecessary_type_assertion_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,50 @@ function unwrap<T>(input: number | string | Wrapper<T>): number {
461461
`},
462462
{Code: `
463463
const value = ((<T>(input: T): T | undefined => input)(1)) as number;
464+
`},
465+
{
466+
// https://github.com/oxc-project/oxc/issues/20656
467+
Code: `
468+
interface Element {
469+
tagName: string;
470+
}
471+
472+
interface HTMLCanvasElement extends Element {
473+
getContext(contextId: string): unknown;
474+
}
475+
476+
interface HTMLElementTagNameMap {
477+
canvas: HTMLCanvasElement;
478+
}
479+
480+
declare const document: {
481+
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
482+
querySelector<E extends Element = Element>(selectors: string): E | null;
483+
};
484+
485+
export const a = document.querySelector('.foo') as HTMLCanvasElement | null;
486+
`},
487+
{
488+
Code: `
489+
interface Element { tagName: string; }
490+
491+
interface HTMLCanvasElement extends Element { getContext(contextId: string): unknown; }
492+
493+
interface Factory { new <E extends Element = Element>(): E | null; }
494+
495+
declare const CanvasFactory: Factory;
496+
497+
export const a = new CanvasFactory() as HTMLCanvasElement | null;
498+
`},
499+
{
500+
Code: `
501+
interface Element { tagName: string; }
502+
503+
interface HTMLCanvasElement extends Element { getContext(contextId: string): unknown; }
504+
505+
declare const query: { <E extends Element = Element>(strings: TemplateStringsArray): E | null; };
506+
507+
export const a = query` + "`" + `.foo` + "`" + ` as HTMLCanvasElement | null;
464508
`},
465509
{Code: `
466510
type NumberValueType = number | string;

0 commit comments

Comments
 (0)