From 9c2596c981d148c371aafa121389117c5319be3d Mon Sep 17 00:00:00 2001 From: Nate Bracy Date: Sun, 25 Oct 2026 15:31:48 -0400 Subject: [PATCH 1/2] fix(64954): exclude PromiseLike from contextual this in object literal methods --- tsc/internal/checker/checker.go | 13 ++++ .../thisInObjectLiteralMethodAsyncReturn.js | 46 ++++++++++++++ ...isInObjectLiteralMethodAsyncReturn.symbols | 51 ++++++++++++++++ ...thisInObjectLiteralMethodAsyncReturn.types | 61 +++++++++++++++++++ .../thisInObjectLiteralMethodAsyncReturn.ts | 24 ++++++++ 5 files changed, 195 insertions(+) create mode 100644 tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js create mode 100644 tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types create mode 100644 tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 148cf5fde47dd..2b3570bbd1669 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -12287,6 +12287,19 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { // There was no contextual ThisType for the containing object literal, so the contextual type // for 'this' is the non-null form of the contextual type for the containing object literal or // the type of the object literal itself. + // A `return` in an async function is contextually `Awaited | PromiseLike>` + // (see getContextualTypeForReturnExpression), but `this` is always the literal itself. + // See https://github.com/microsoft/TypeScript/issues/64954 + if contextualType != nil { + filtered := c.filterType(contextualType, func(t *Type) bool { + return c.getAwaitedTypeOfPromise(t) == nil + }) + if filtered.flags&TypeFlagsNever == 0 { + contextualType = filtered + } else { + contextualType = nil + } + } if contextualType != nil { thisType = c.GetNonNullableType(contextualType) } else { diff --git a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js new file mode 100644 index 0000000000000..079579b692f43 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js @@ -0,0 +1,46 @@ +//// [tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts] //// + +//// [thisInObjectLiteralMethodAsyncReturn.ts] +// https://github.com/microsoft/TypeScript/issues/64954 + +const test1 = async () => { + return { + value: 5, + method() { + this satisfies { value: number, method(): void }; + }, + }; +}; + +const test2: () => Promise<{ + value: number; + method(): void; +}> = async () => { + return { + value: 5, + method() { + this satisfies { value: number, method(): void }; + }, + }; +}; + + +//// [thisInObjectLiteralMethodAsyncReturn.js] +"use strict"; +// https://github.com/microsoft/TypeScript/issues/64954 +const test1 = async () => { + return { + value: 5, + method() { + this; + }, + }; +}; +const test2 = async () => { + return { + value: 5, + method() { + this; + }, + }; +}; diff --git a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols new file mode 100644 index 0000000000000..e3bd0da34261d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts] //// + +=== thisInObjectLiteralMethodAsyncReturn.ts === +// https://github.com/microsoft/TypeScript/issues/64954 + +const test1 = async () => { +>test1 : Symbol(test1, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 2, 5)) + + return { + value: 5, +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 3, 12)) + + method() { +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 4, 17)) + + this satisfies { value: number, method(): void }; +>this : Symbol(__object, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 3, 10)) +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 6, 28)) +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 6, 43)) + + }, + }; +}; + +const test2: () => Promise<{ +>test2 : Symbol(test2, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 11, 5)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + + value: number; +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 11, 28)) + + method(): void; +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 12, 18)) + +}> = async () => { + return { + value: 5, +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 15, 12)) + + method() { +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 16, 17)) + + this satisfies { value: number, method(): void }; +>this : Symbol(__type, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 11, 27)) +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 18, 28)) +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 18, 43)) + + }, + }; +}; + diff --git a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types new file mode 100644 index 0000000000000..ee6da9ddced99 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types @@ -0,0 +1,61 @@ +//// [tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts] //// + +=== thisInObjectLiteralMethodAsyncReturn.ts === +// https://github.com/microsoft/TypeScript/issues/64954 + +const test1 = async () => { +>test1 : () => Promise<{ value: number; method(): void; }> +>async () => { return { value: 5, method() { this satisfies { value: number, method(): void }; }, };} : () => Promise<{ value: number; method(): void; }> + + return { +>{ value: 5, method() { this satisfies { value: number, method(): void }; }, } : { value: number; method(): void; } + + value: 5, +>value : number +>5 : 5 + + method() { +>method : () => void + + this satisfies { value: number, method(): void }; +>this satisfies { value: number, method(): void } : { value: number; method(): void; } +>this : { value: number; method(): void; } +>value : number +>method : () => void + + }, + }; +}; + +const test2: () => Promise<{ +>test2 : () => Promise<{ value: number; method(): void; }> + + value: number; +>value : number + + method(): void; +>method : () => void + +}> = async () => { +>async () => { return { value: 5, method() { this satisfies { value: number, method(): void }; }, };} : () => Promise<{ value: number; method(): void; }> + + return { +>{ value: 5, method() { this satisfies { value: number, method(): void }; }, } : { value: number; method(): void; } + + value: 5, +>value : number +>5 : 5 + + method() { +>method : () => void + + this satisfies { value: number, method(): void }; +>this satisfies { value: number, method(): void } : { value: number; method(): void; } +>this : { value: number; method(): void; } +>value : number +>method : () => void + + }, + }; +}; + diff --git a/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts b/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts new file mode 100644 index 0000000000000..78791f80936e5 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts @@ -0,0 +1,24 @@ +// @strict: true +// @target: es2017 +// https://github.com/microsoft/TypeScript/issues/64954 + +const test1 = async () => { + return { + value: 5, + method() { + this satisfies { value: number, method(): void }; + }, + }; +}; + +const test2: () => Promise<{ + value: number; + method(): void; +}> = async () => { + return { + value: 5, + method() { + this satisfies { value: number, method(): void }; + }, + }; +}; From addf07f5b16a76a86ba417aec0120239bac96320 Mon Sep 17 00:00:00 2001 From: Nate Bracy Date: Tue, 27 Oct 2026 00:54:11 -0400 Subject: [PATCH 2/2] fix(64954): restrict PromiseLike filtering on contextual this to async returns --- tsc/internal/checker/checker.go | 50 ++++++++++++++++++- .../thisInObjectLiteralMethodAsyncReturn.js | 20 ++++++++ ...isInObjectLiteralMethodAsyncReturn.symbols | 35 +++++++++++++ ...thisInObjectLiteralMethodAsyncReturn.types | 37 ++++++++++++++ .../thisInObjectLiteralMethodAsyncReturn.ts | 14 ++++++ 5 files changed, 154 insertions(+), 2 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index e0b81bafc342c..d7acb43f5fab5 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -12259,6 +12259,50 @@ func getThisParameterFromNodeContext(node *ast.Node) *ast.Node { return nil } +func isAsyncReturnExpression(node *ast.Node) bool { + expr := node + for expr != nil && expr.Parent != nil { + parent := expr.Parent + if ast.IsParenthesizedExpression(parent) || ast.IsNonNullExpression(parent) { + expr = parent + continue + } + if ast.IsConditionalExpression(parent) { + cond := parent.AsConditionalExpression() + if expr == cond.WhenTrue || expr == cond.WhenFalse { + expr = parent + continue + } + return false + } + if ast.IsBinaryExpression(parent) { + bin := parent.AsBinaryExpression() + switch bin.OperatorToken.Kind { + case ast.KindBarBarToken, ast.KindQuestionQuestionToken: + expr = parent + continue + case ast.KindCommaToken: + if expr == bin.Right { + expr = parent + continue + } + return false + default: + return false + } + } + if ast.IsReturnStatement(parent) { + fn := ast.GetContainingFunction(parent) + return fn != nil && (ast.GetFunctionFlags(fn)&ast.FunctionFlagsAsync) != 0 + } + if ast.IsArrowFunction(parent) { + return expr == parent.AsArrowFunction().Body && (ast.GetFunctionFlags(parent)&ast.FunctionFlagsAsync) != 0 + } + return false + } + return false +} + func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { if ast.IsArrowFunction(fn) { return nil @@ -12289,10 +12333,12 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { // the type of the object literal itself. // A `return` in an async function is contextually `Awaited | PromiseLike>` // (see getContextualTypeForReturnExpression), but `this` is always the literal itself. + // Restrict filtering of `PromiseLike` to the synthetic async-return context to avoid + // removing user-provided thenable types. // See https://github.com/microsoft/TypeScript/issues/64954 - if contextualType != nil { + if contextualType != nil && isAsyncReturnExpression(containingLiteral) { filtered := c.filterType(contextualType, func(t *Type) bool { - return c.getAwaitedTypeOfPromise(t) == nil + return !c.isReferenceToType(t, c.getGlobalPromiseLikeType()) }) if filtered.flags&TypeFlagsNever == 0 { contextualType = filtered diff --git a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js index 079579b692f43..fae0e1633a0a0 100644 --- a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js @@ -23,6 +23,20 @@ const test2: () => Promise<{ }, }; }; + +// Ordinary, non-async object literal with thenable contextual type +type T = { + then(cb: (n: number) => void): void; + value?: number; + method(): void; +}; +const x: T = { + then() {}, + method() { + this.value = 1; + }, +}; + //// [thisInObjectLiteralMethodAsyncReturn.js] @@ -44,3 +58,9 @@ const test2 = async () => { }, }; }; +const x = { + then() { }, + method() { + this.value = 1; + }, +}; diff --git a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols index e3bd0da34261d..de656407eccdb 100644 --- a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols @@ -49,3 +49,38 @@ const test2: () => Promise<{ }; }; +// Ordinary, non-async object literal with thenable contextual type +type T = { +>T : Symbol(T, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 21, 2)) + + then(cb: (n: number) => void): void; +>then : Symbol(then, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 24, 10)) +>cb : Symbol(cb, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 25, 9)) +>n : Symbol(n, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 25, 14)) + + value?: number; +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 25, 40)) + + method(): void; +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 26, 19)) + +}; +const x: T = { +>x : Symbol(x, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 29, 5)) +>T : Symbol(T, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 21, 2)) + + then() {}, +>then : Symbol(then, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 29, 14)) + + method() { +>method : Symbol(method, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 30, 14)) + + this.value = 1; +>this.value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 25, 40)) +>this : Symbol(__type, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 24, 8)) +>value : Symbol(value, Decl(thisInObjectLiteralMethodAsyncReturn.ts, 25, 40)) + + }, +}; + + diff --git a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types index ee6da9ddced99..f533c8072087d 100644 --- a/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types @@ -59,3 +59,40 @@ const test2: () => Promise<{ }; }; +// Ordinary, non-async object literal with thenable contextual type +type T = { +>T : T + + then(cb: (n: number) => void): void; +>then : (cb: (n: number) => void) => void +>cb : (n: number) => void +>n : number + + value?: number; +>value : number | undefined + + method(): void; +>method : () => void + +}; +const x: T = { +>x : T +>{ then() {}, method() { this.value = 1; },} : { then(): void; method(): void; } + + then() {}, +>then : () => void + + method() { +>method : () => void + + this.value = 1; +>this.value = 1 : 1 +>this.value : number | undefined +>this : T +>value : number | undefined +>1 : 1 + + }, +}; + + diff --git a/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts b/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts index 78791f80936e5..891acd3265443 100644 --- a/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts +++ b/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts @@ -22,3 +22,17 @@ const test2: () => Promise<{ }, }; }; + +// Ordinary, non-async object literal with thenable contextual type +type T = { + then(cb: (n: number) => void): void; + value?: number; + method(): void; +}; +const x: T = { + then() {}, + method() { + this.value = 1; + }, +}; +