From 0b2b9e01189d74c240c8bbe605dfd22841b291f5 Mon Sep 17 00:00:00 2001 From: DeepSeek V4 Pro agent Date: Thu, 9 Jul 2026 13:36:42 +0300 Subject: [PATCH 1/2] Fix inconsistent typeof this with contextual this parameter (#63616) The binder tracked seenThisKeyword only for ThisKeyword AST nodes, but typeof this in type queries uses an Identifier node with text "this". This caused NodeFlags.ContainsThis to not be set on functions that only reference this via typeof this, which in turn prevented hasContextSensitiveParameters from recognizing the function as context-sensitive, causing getContextualThisParameterType to skip the contextual this type from the calling signature. Fix: extend the seenThisKeyword check in the binder to also recognize Identifier nodes with escapedText === "this", which is how the parser represents this inside typeof type queries. Co-authored-by: atlarix-agent --- src/compiler/binder.ts | 8 +- .../typeofThisWithContextualThisParameter.js | 113 +++++++++++ ...eofThisWithContextualThisParameter.symbols | 123 ++++++++++++ ...ypeofThisWithContextualThisParameter.types | 178 ++++++++++++++++++ .../typeofThisWithContextualThisParameter.ts | 64 +++++++ 5 files changed, 482 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/typeofThisWithContextualThisParameter.js create mode 100644 tests/baselines/reference/typeofThisWithContextualThisParameter.symbols create mode 100644 tests/baselines/reference/typeofThisWithContextualThisParameter.types create mode 100644 tests/cases/compiler/typeofThisWithContextualThisParameter.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5980b113e7baf..6352845cbf900 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2859,10 +2859,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { break; } // falls through - case SyntaxKind.ThisKeyword: - if (node.kind === SyntaxKind.ThisKeyword) { - seenThisKeyword = true; - } + case SyntaxKind.ThisKeyword: + if (node.kind === SyntaxKind.ThisKeyword || (node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "this")) { + seenThisKeyword = true; + } // TODO: Why use `isExpression` here? both Identifier and ThisKeyword are expressions. if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { (node as Identifier | ThisExpression).flowNode = currentFlow; diff --git a/tests/baselines/reference/typeofThisWithContextualThisParameter.js b/tests/baselines/reference/typeofThisWithContextualThisParameter.js new file mode 100644 index 0000000000000..16383fc6f009e --- /dev/null +++ b/tests/baselines/reference/typeofThisWithContextualThisParameter.js @@ -0,0 +1,113 @@ +//// [tests/cases/compiler/typeofThisWithContextualThisParameter.ts] //// + +//// [typeofThisWithContextualThisParameter.ts] +// Reproduces #63616: typeof this inconsistent when this parameter comes from a contextual signature + +interface A { a: string } + +// ---- Object-literal method case ---- + +{ + interface B { g: (this: A) => void } + + function f(_: B): void {} + + // Case 1: typeof this without explicit this usage — should resolve to A + f({ + g() { + type X = typeof this; + // ^ should be A + } + }) + + // Case 2: this used before typeof this — should resolve to A + f({ + g() { + this; + type X = typeof this; + // ^ should be A + } + }) + + // Case 3: this used after typeof this — should resolve to A + f({ + g() { + type X = typeof this; + // ^ should be A + this; + } + }) +} + +// ---- Function expression case ---- + +{ + function f(_: (this: A) => void): void {} + + // Case 4: typeof this only — should resolve to A (not implicit any error for typeof this context) + f(function g() { + type X = typeof this; + }) + + // Case 5: this used before typeof this — should resolve to A + f(function g() { + this; + type X = typeof this; + }) + + // Case 6: this used after typeof this — should resolve to A + f(function g() { + type X = typeof this; + this; + }) +} + + +//// [typeofThisWithContextualThisParameter.js] +"use strict"; +// Reproduces #63616: typeof this inconsistent when this parameter comes from a contextual signature +// ---- Object-literal method case ---- +{ + function f(_) { } + // Case 1: typeof this without explicit this usage — should resolve to A + f({ + g() { + // ^ should be A + } + }); + // Case 2: this used before typeof this — should resolve to A + f({ + g() { + this; + // ^ should be A + } + }); + // Case 3: this used after typeof this — should resolve to A + f({ + g() { + // ^ should be A + this; + } + }); +} +// ---- Function expression case ---- +{ + function f(_) { } + // Case 4: typeof this only — should resolve to A (not implicit any error for typeof this context) + f(function g() { + }); + // Case 5: this used before typeof this — should resolve to A + f(function g() { + this; + }); + // Case 6: this used after typeof this — should resolve to A + f(function g() { + this; + }); +} + + +//// [typeofThisWithContextualThisParameter.d.ts] +interface A { + a: string; +} diff --git a/tests/baselines/reference/typeofThisWithContextualThisParameter.symbols b/tests/baselines/reference/typeofThisWithContextualThisParameter.symbols new file mode 100644 index 0000000000000..c4e44df1fb44a --- /dev/null +++ b/tests/baselines/reference/typeofThisWithContextualThisParameter.symbols @@ -0,0 +1,123 @@ +//// [tests/cases/compiler/typeofThisWithContextualThisParameter.ts] //// + +=== typeofThisWithContextualThisParameter.ts === +// Reproduces #63616: typeof this inconsistent when this parameter comes from a contextual signature + +interface A { a: string } +>A : Symbol(A, Decl(typeofThisWithContextualThisParameter.ts, 0, 0)) +>a : Symbol(A.a, Decl(typeofThisWithContextualThisParameter.ts, 2, 13)) + +// ---- Object-literal method case ---- + +{ + interface B { g: (this: A) => void } +>B : Symbol(B, Decl(typeofThisWithContextualThisParameter.ts, 6, 1)) +>g : Symbol(B.g, Decl(typeofThisWithContextualThisParameter.ts, 7, 17)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 7, 22)) +>A : Symbol(A, Decl(typeofThisWithContextualThisParameter.ts, 0, 0)) + + function f(_: B): void {} +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 7, 40)) +>_ : Symbol(_, Decl(typeofThisWithContextualThisParameter.ts, 9, 15)) +>B : Symbol(B, Decl(typeofThisWithContextualThisParameter.ts, 6, 1)) + + // Case 1: typeof this without explicit this usage — should resolve to A + f({ +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 7, 40)) + + g() { +>g : Symbol(g, Decl(typeofThisWithContextualThisParameter.ts, 12, 7)) + + type X = typeof this; +>X : Symbol(X, Decl(typeofThisWithContextualThisParameter.ts, 13, 13)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 7, 22)) + + // ^ should be A + } + }) + + // Case 2: this used before typeof this — should resolve to A + f({ +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 7, 40)) + + g() { +>g : Symbol(g, Decl(typeofThisWithContextualThisParameter.ts, 20, 7)) + + this; +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 7, 22)) + + type X = typeof this; +>X : Symbol(X, Decl(typeofThisWithContextualThisParameter.ts, 22, 17)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 7, 22)) + + // ^ should be A + } + }) + + // Case 3: this used after typeof this — should resolve to A + f({ +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 7, 40)) + + g() { +>g : Symbol(g, Decl(typeofThisWithContextualThisParameter.ts, 29, 7)) + + type X = typeof this; +>X : Symbol(X, Decl(typeofThisWithContextualThisParameter.ts, 30, 13)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 7, 22)) + + // ^ should be A + this; +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 7, 22)) + } + }) +} + +// ---- Function expression case ---- + +{ + function f(_: (this: A) => void): void {} +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 40, 1)) +>_ : Symbol(_, Decl(typeofThisWithContextualThisParameter.ts, 41, 15)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 41, 19)) +>A : Symbol(A, Decl(typeofThisWithContextualThisParameter.ts, 0, 0)) + + // Case 4: typeof this only — should resolve to A (not implicit any error for typeof this context) + f(function g() { +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 40, 1)) +>g : Symbol(g, Decl(typeofThisWithContextualThisParameter.ts, 44, 6)) + + type X = typeof this; +>X : Symbol(X, Decl(typeofThisWithContextualThisParameter.ts, 44, 20)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 41, 19)) + + }) + + // Case 5: this used before typeof this — should resolve to A + f(function g() { +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 40, 1)) +>g : Symbol(g, Decl(typeofThisWithContextualThisParameter.ts, 49, 6)) + + this; +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 41, 19)) + + type X = typeof this; +>X : Symbol(X, Decl(typeofThisWithContextualThisParameter.ts, 50, 13)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 41, 19)) + + }) + + // Case 6: this used after typeof this — should resolve to A + f(function g() { +>f : Symbol(f, Decl(typeofThisWithContextualThisParameter.ts, 40, 1)) +>g : Symbol(g, Decl(typeofThisWithContextualThisParameter.ts, 55, 6)) + + type X = typeof this; +>X : Symbol(X, Decl(typeofThisWithContextualThisParameter.ts, 55, 20)) +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 41, 19)) + + this; +>this : Symbol(this, Decl(typeofThisWithContextualThisParameter.ts, 41, 19)) + + }) +} + diff --git a/tests/baselines/reference/typeofThisWithContextualThisParameter.types b/tests/baselines/reference/typeofThisWithContextualThisParameter.types new file mode 100644 index 0000000000000..7d0ca011c06c0 --- /dev/null +++ b/tests/baselines/reference/typeofThisWithContextualThisParameter.types @@ -0,0 +1,178 @@ +//// [tests/cases/compiler/typeofThisWithContextualThisParameter.ts] //// + +=== typeofThisWithContextualThisParameter.ts === +// Reproduces #63616: typeof this inconsistent when this parameter comes from a contextual signature + +interface A { a: string } +>a : string +> : ^^^^^^ + +// ---- Object-literal method case ---- + +{ + interface B { g: (this: A) => void } +>g : (this: A) => void +> : ^ ^^ ^^^^^ +>this : A +> : ^ + + function f(_: B): void {} +>f : (_: B) => void +> : ^ ^^^^^^^^ +>_ : B +> : ^ + + // Case 1: typeof this without explicit this usage — should resolve to A + f({ +>f({ g() { type X = typeof this; // ^ should be A } }) : void +> : ^^^^ +>f : (_: B) => void +> : ^ ^^^^^^^^ +>{ g() { type X = typeof this; // ^ should be A } } : { g(this: A): void; } +> : ^^^^ ^^ ^^^^^^^^^^ + + g() { +>g : (this: A) => void +> : ^ ^^ ^^^^^^^^^ + + type X = typeof this; +>X : A +> : ^ +>this : A +> : ^ + + // ^ should be A + } + }) + + // Case 2: this used before typeof this — should resolve to A + f({ +>f({ g() { this; type X = typeof this; // ^ should be A } }) : void +> : ^^^^ +>f : (_: B) => void +> : ^ ^^^^^^^^ +>{ g() { this; type X = typeof this; // ^ should be A } } : { g(this: A): void; } +> : ^^^^ ^^ ^^^^^^^^^^ + + g() { +>g : (this: A) => void +> : ^ ^^ ^^^^^^^^^ + + this; +>this : A +> : ^ + + type X = typeof this; +>X : A +> : ^ +>this : A +> : ^ + + // ^ should be A + } + }) + + // Case 3: this used after typeof this — should resolve to A + f({ +>f({ g() { type X = typeof this; // ^ should be A this; } }) : void +> : ^^^^ +>f : (_: B) => void +> : ^ ^^^^^^^^ +>{ g() { type X = typeof this; // ^ should be A this; } } : { g(this: A): void; } +> : ^^^^ ^^ ^^^^^^^^^^ + + g() { +>g : (this: A) => void +> : ^ ^^ ^^^^^^^^^ + + type X = typeof this; +>X : A +> : ^ +>this : A +> : ^ + + // ^ should be A + this; +>this : A +> : ^ + } + }) +} + +// ---- Function expression case ---- + +{ + function f(_: (this: A) => void): void {} +>f : (_: (this: A) => void) => void +> : ^ ^^ ^^^^^ +>_ : (this: A) => void +> : ^ ^^ ^^^^^ +>this : A +> : ^ + + // Case 4: typeof this only — should resolve to A (not implicit any error for typeof this context) + f(function g() { +>f(function g() { type X = typeof this; }) : void +> : ^^^^ +>f : (_: (this: A) => void) => void +> : ^ ^^ ^^^^^ +>function g() { type X = typeof this; } : (this: A) => void +> : ^ ^^ ^^^^^^^^^ +>g : (this: A) => void +> : ^ ^^ ^^^^^^^^^ + + type X = typeof this; +>X : A +> : ^ +>this : A +> : ^ + + }) + + // Case 5: this used before typeof this — should resolve to A + f(function g() { +>f(function g() { this; type X = typeof this; }) : void +> : ^^^^ +>f : (_: (this: A) => void) => void +> : ^ ^^ ^^^^^ +>function g() { this; type X = typeof this; } : (this: A) => void +> : ^ ^^ ^^^^^^^^^ +>g : (this: A) => void +> : ^ ^^ ^^^^^^^^^ + + this; +>this : A +> : ^ + + type X = typeof this; +>X : A +> : ^ +>this : A +> : ^ + + }) + + // Case 6: this used after typeof this — should resolve to A + f(function g() { +>f(function g() { type X = typeof this; this; }) : void +> : ^^^^ +>f : (_: (this: A) => void) => void +> : ^ ^^ ^^^^^ +>function g() { type X = typeof this; this; } : (this: A) => void +> : ^ ^^ ^^^^^^^^^ +>g : (this: A) => void +> : ^ ^^ ^^^^^^^^^ + + type X = typeof this; +>X : A +> : ^ +>this : A +> : ^ + + this; +>this : A +> : ^ + + }) +} + diff --git a/tests/cases/compiler/typeofThisWithContextualThisParameter.ts b/tests/cases/compiler/typeofThisWithContextualThisParameter.ts new file mode 100644 index 0000000000000..a5f39e81aa0e3 --- /dev/null +++ b/tests/cases/compiler/typeofThisWithContextualThisParameter.ts @@ -0,0 +1,64 @@ +// @strict: true +// @target: es2015 +// @declaration: true + +// Reproduces #63616: typeof this inconsistent when this parameter comes from a contextual signature + +interface A { a: string } + +// ---- Object-literal method case ---- + +{ + interface B { g: (this: A) => void } + + function f(_: B): void {} + + // Case 1: typeof this without explicit this usage — should resolve to A + f({ + g() { + type X = typeof this; + // ^ should be A + } + }) + + // Case 2: this used before typeof this — should resolve to A + f({ + g() { + this; + type X = typeof this; + // ^ should be A + } + }) + + // Case 3: this used after typeof this — should resolve to A + f({ + g() { + type X = typeof this; + // ^ should be A + this; + } + }) +} + +// ---- Function expression case ---- + +{ + function f(_: (this: A) => void): void {} + + // Case 4: typeof this only — should resolve to A (not implicit any error for typeof this context) + f(function g() { + type X = typeof this; + }) + + // Case 5: this used before typeof this — should resolve to A + f(function g() { + this; + type X = typeof this; + }) + + // Case 6: this used after typeof this — should resolve to A + f(function g() { + type X = typeof this; + this; + }) +} From d5d168effb1e1e69959c13b1f2b73bfcd9a6ba81 Mon Sep 17 00:00:00 2001 From: PilgrimStack <110414493+AmariahAK@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:50:50 +0300 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/compiler/binder.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6352845cbf900..84b33efb1e4b6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2859,10 +2859,10 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void { break; } // falls through - case SyntaxKind.ThisKeyword: - if (node.kind === SyntaxKind.ThisKeyword || (node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "this")) { - seenThisKeyword = true; - } + case SyntaxKind.ThisKeyword: + if (node.kind === SyntaxKind.ThisKeyword || (node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "this" && isPartOfTypeQuery(node))) { + seenThisKeyword = true; + } // TODO: Why use `isExpression` here? both Identifier and ThisKeyword are expressions. if (currentFlow && (isExpression(node) || parent.kind === SyntaxKind.ShorthandPropertyAssignment)) { (node as Identifier | ThisExpression).flowNode = currentFlow;