From 5560e8e4de1910333bed5e35227af78b6cea891c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 21 Aug 2025 21:07:25 +0200 Subject: [PATCH] Narrow by constant element access callees --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 4 +- ...dicateFromElementAccessExpression1.symbols | 47 ++++++++++++ ...redicateFromElementAccessExpression1.types | 74 +++++++++++++++++++ ...pePredicateFromElementAccessExpression1.ts | 25 +++++++ 5 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.symbols create mode 100644 tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.types create mode 100644 tests/cases/compiler/narrowByTypePredicateFromElementAccessExpression1.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 27ec079f614ab..540799673102d 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1295,8 +1295,8 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { } } if ( - expr.expression.kind === SyntaxKind.PropertyAccessExpression && - containsNarrowableReference((expr.expression as PropertyAccessExpression).expression) + (expr.expression.kind === SyntaxKind.PropertyAccessExpression || expr.expression.kind === SyntaxKind.ElementAccessExpression) && + containsNarrowableReference((expr.expression as PropertyAccessExpression | ElementAccessExpression).expression) ) { return true; } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75be36474222f..5f2fa22d5dd93 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27970,8 +27970,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } if ( - expression.expression.kind === SyntaxKind.PropertyAccessExpression && - isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression).expression) + (expression.expression.kind === SyntaxKind.PropertyAccessExpression || expression.expression.kind === SyntaxKind.ElementAccessExpression && isConstantReference((expression.expression as ElementAccessExpression).argumentExpression)) && + isOrContainsMatchingReference(reference, (expression.expression as PropertyAccessExpression | ElementAccessExpression).expression) ) { return true; } diff --git a/tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.symbols b/tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.symbols new file mode 100644 index 0000000000000..0438824530cc0 --- /dev/null +++ b/tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.symbols @@ -0,0 +1,47 @@ +//// [tests/cases/compiler/narrowByTypePredicateFromElementAccessExpression1.ts] //// + +=== narrowByTypePredicateFromElementAccessExpression1.ts === +// https://github.com/microsoft/TypeScript/issues/62247 + +const isBar = Symbol("isBar"); +>isBar : Symbol(isBar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +abstract class Foo { +>Foo : Symbol(Foo, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 30)) + + abstract [isBar](): this is Bar; +>[isBar] : Symbol(Foo[isBar], Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 4, 20)) +>isBar : Symbol(isBar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 5)) +>Bar : Symbol(Bar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 12, 1)) + + method(): void { +>method : Symbol(Foo.method, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 5, 34)) + + if (this[isBar]()) { +>this : Symbol(Foo, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 30)) +>isBar : Symbol(isBar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 5)) + + this.barMethod(); // ok +>this.barMethod : Symbol(Bar.barMethod, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 17, 3)) +>barMethod : Symbol(Bar.barMethod, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 17, 3)) + } + } +} + +class Bar extends Foo { +>Bar : Symbol(Bar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 12, 1)) +>Foo : Symbol(Foo, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 30)) + + override [isBar](): this is Bar { +>[isBar] : Symbol(Bar[isBar], Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 14, 23)) +>isBar : Symbol(isBar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 2, 5)) +>Bar : Symbol(Bar, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 12, 1)) + + return true; + } + + barMethod(): void {} +>barMethod : Symbol(Bar.barMethod, Decl(narrowByTypePredicateFromElementAccessExpression1.ts, 17, 3)) +} + diff --git a/tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.types b/tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.types new file mode 100644 index 0000000000000..8c67e1fcede27 --- /dev/null +++ b/tests/baselines/reference/narrowByTypePredicateFromElementAccessExpression1.types @@ -0,0 +1,74 @@ +//// [tests/cases/compiler/narrowByTypePredicateFromElementAccessExpression1.ts] //// + +=== narrowByTypePredicateFromElementAccessExpression1.ts === +// https://github.com/microsoft/TypeScript/issues/62247 + +const isBar = Symbol("isBar"); +>isBar : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol("isBar") : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ +>"isBar" : "isBar" +> : ^^^^^^^ + +abstract class Foo { +>Foo : Foo +> : ^^^ + + abstract [isBar](): this is Bar; +>[isBar] : () => this is Bar +> : ^^^^^^ +>isBar : unique symbol +> : ^^^^^^^^^^^^^ + + method(): void { +>method : () => void +> : ^^^^^^ + + if (this[isBar]()) { +>this[isBar]() : boolean +> : ^^^^^^^ +>this[isBar] : () => this is Bar +> : ^^^^^^ +>this : this +> : ^^^^ +>isBar : unique symbol +> : ^^^^^^^^^^^^^ + + this.barMethod(); // ok +>this.barMethod() : void +> : ^^^^ +>this.barMethod : () => void +> : ^^^^^^ +>this : this & Bar +> : ^^^^^^^^^^ +>barMethod : () => void +> : ^^^^^^ + } + } +} + +class Bar extends Foo { +>Bar : Bar +> : ^^^ +>Foo : Foo +> : ^^^ + + override [isBar](): this is Bar { +>[isBar] : () => this is Bar +> : ^^^^^^ +>isBar : unique symbol +> : ^^^^^^^^^^^^^ + + return true; +>true : true +> : ^^^^ + } + + barMethod(): void {} +>barMethod : () => void +> : ^^^^^^ +} + diff --git a/tests/cases/compiler/narrowByTypePredicateFromElementAccessExpression1.ts b/tests/cases/compiler/narrowByTypePredicateFromElementAccessExpression1.ts new file mode 100644 index 0000000000000..625b7e0572b6e --- /dev/null +++ b/tests/cases/compiler/narrowByTypePredicateFromElementAccessExpression1.ts @@ -0,0 +1,25 @@ +// @strict: true +// @target: esnext +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/62247 + +const isBar = Symbol("isBar"); + +abstract class Foo { + abstract [isBar](): this is Bar; + + method(): void { + if (this[isBar]()) { + this.barMethod(); // ok + } + } +} + +class Bar extends Foo { + override [isBar](): this is Bar { + return true; + } + + barMethod(): void {} +}