diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 67a9ab08cfd17..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 @@ -12287,6 +12331,21 @@ 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. + // 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 && isAsyncReturnExpression(containingLiteral) { + filtered := c.filterType(contextualType, func(t *Type) bool { + return !c.isReferenceToType(t, c.getGlobalPromiseLikeType()) + }) + 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..fae0e1633a0a0 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.js @@ -0,0 +1,66 @@ +//// [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 }; + }, + }; +}; + +// 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] +"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; + }, + }; +}; +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 new file mode 100644 index 0000000000000..de656407eccdb --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.symbols @@ -0,0 +1,86 @@ +//// [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)) + + }, + }; +}; + +// 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 new file mode 100644 index 0000000000000..f533c8072087d --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/thisInObjectLiteralMethodAsyncReturn.types @@ -0,0 +1,98 @@ +//// [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 + + }, + }; +}; + +// 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 new file mode 100644 index 0000000000000..891acd3265443 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/thisInObjectLiteralMethodAsyncReturn.ts @@ -0,0 +1,38 @@ +// @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 }; + }, + }; +}; + +// 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; + }, +}; +