diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 881affac249e2..09be73451771e 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -12291,6 +12291,9 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *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. if contextualType != nil { + if contextualType.flags&TypeFlagsUnion != 0 { + contextualType = c.removeSynthesizedPromiseLike(containingLiteral, contextualType) + } thisType = c.GetNonNullableType(contextualType) } else { thisType = c.checkExpressionCached(containingLiteral) @@ -12318,6 +12321,107 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type { return nil } +// The contextual type of an async function's return expression is `T | PromiseLike`, where `T` is the awaited +// contextual return type. For `this` in methods of a returned object literal, the `PromiseLike` member is an +// artifact of that wrapping, so remove exactly that synthesized member. It is kept if the literal has a `then` +// member, since the literal may then be intended as the thenable. +func (c *Checker) removeSynthesizedPromiseLike(literal *ast.Node, contextualType *Type) *Type { + fn := getAsyncFunctionReturningExpression(literal) + if fn == nil { + return contextualType + } + if c.objectLiteralMayBeThenable(literal) { + return contextualType + } + returnType := c.getContextualReturnType(fn, ContextFlagsNone) + if returnType == nil { + return contextualType + } + awaitedType := c.mapType(returnType, c.getAwaitedTypeNoAlias) + if awaitedType == nil { + return contextualType + } + synthesized := c.createPromiseLikeType(awaitedType) + return c.filterType(contextualType, func(t *Type) bool { return t != synthesized }) +} + +// Conservatively determines whether an object literal might be a thenable (i.e. have a callable `then` member). +// Spreads of non-literal expressions, property names that can't be statically resolved, and `then` members whose +// initializers aren't known to be non-callable are all treated as possibly thenable. Spreads of object literals are +// inspected recursively, and computed names are resolved through their (cached) computed-name type. +func (c *Checker) objectLiteralMayBeThenable(literal *ast.Node) bool { + for _, p := range literal.AsObjectLiteralExpression().Properties.Nodes { + if p.Kind == ast.KindSpreadAssignment { + expression := ast.SkipParentheses(p.Expression()) + if expression.Kind == ast.KindObjectLiteralExpression && !c.objectLiteralMayBeThenable(expression) { + continue + } + return true + } + name := p.Name() + if name == nil { + continue + } + text, ok := ast.TryGetTextOfPropertyName(name) + if !ok { + if name.Kind != ast.KindComputedPropertyName { + return true + } + nameType := c.checkComputedPropertyName(name) + if !isTypeUsableAsPropertyName(nameType) { + return true + } + text = getPropertyNameFromType(nameType) + } + if text != "then" { + continue + } + if p.Kind == ast.KindPropertyAssignment && isKnownNonCallableExpression(p.Initializer()) { + continue + } + return true + } + return false +} + +func isKnownNonCallableExpression(node *ast.Node) bool { + node = ast.SkipParentheses(node) + switch node.Kind { + case ast.KindNumericLiteral, ast.KindBigIntLiteral, ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral, + ast.KindTemplateExpression, ast.KindTrueKeyword, ast.KindFalseKeyword, ast.KindNullKeyword, + ast.KindObjectLiteralExpression, ast.KindArrayLiteralExpression, ast.KindRegularExpressionLiteral: + return true + } + return false +} + +// Returns the async, non-generator function whose return value (a `return` operand or concise arrow body) is node. +func getAsyncFunctionReturningExpression(node *ast.Node) *ast.Node { + parent := node.Parent + for parent != nil && ast.IsParenthesizedExpression(parent) { + node = parent + parent = parent.Parent + } + if parent == nil { + return nil + } + var fn *ast.Node + switch { + case ast.IsReturnStatement(parent): + fn = ast.GetContainingFunction(parent) + case ast.IsArrowFunction(parent) && parent.Body() == node: + fn = parent + } + if fn == nil { + return nil + } + flags := ast.GetFunctionFlags(fn) + if flags&ast.FunctionFlagsAsync != 0 && flags&ast.FunctionFlagsGenerator == 0 { + return fn + } + return nil +} + func (c *Checker) checkThisExpression(node *ast.Node) *Type { // Stop at the first arrow function so that we can // tell whether 'this' needs to be captured. diff --git a/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.errors.txt b/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.errors.txt new file mode 100644 index 0000000000000..f5c5b6aaf782a --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.errors.txt @@ -0,0 +1,73 @@ +asyncReturnObjectLiteralThisType.ts(12,45): error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member. +asyncReturnObjectLiteralThisType.ts(13,29): error TS2339: Property 'value' does not exist on type 'PromiseLike<{ value: number; }> | { value: number; }'. + Property 'value' does not exist on type 'PromiseLike<{ value: number; }>'. +asyncReturnObjectLiteralThisType.ts(28,41): error TS2339: Property 'value' does not exist on type 'PromiseLike | V'. + Property 'value' does not exist on type 'PromiseLike'. +asyncReturnObjectLiteralThisType.ts(32,18): error TS2339: Property 'value' does not exist on type 'PromiseLike | V'. + Property 'value' does not exist on type 'PromiseLike'. +asyncReturnObjectLiteralThisType.ts(43,18): error TS2339: Property 'value' does not exist on type 'PromiseLike | V'. + Property 'value' does not exist on type 'PromiseLike'. + + +==== asyncReturnObjectLiteralThisType.ts (5 errors) ==== + type X = { value: number; method(): void }; + declare function foo(cb: () => Promise): void; + foo(async () => ({ value: 5, method() { this satisfies X; } })); + foo(async () => { return { value: 5, method() { this satisfies X; } }; }); + foo(async function () { return { value: 5, method() { this satisfies X; } }; }); + async function f(): Promise { return { value: 1, method() { this satisfies X; } }; } + const test2: () => Promise = async () => { return { value: 5, method() { this satisfies X; } }; }; + // nullable awaited + const n1: () => Promise = async () => ({ value: 1, method() { this satisfies X; } }); + const n2: () => Promise = async () => { return { value: 1, method() { this satisfies X; } }; }; + // thenable must not get this: X + const t: () => Promise<{ value: number }> = async () => ({ + ~~~~~~~~~~~~~~ + then(resolve: any) { this.value.toFixed(); }, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~ +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike<{ value: number; }> | { value: number; }'. +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike<{ value: number; }>'. + }); + ~~ +!!! error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member. + // declared PromiseLike remains + declare function g(cb: () => X | PromiseLike): void; + g(async () => ({ value: 1, method() { this satisfies X; } })); + + // non-callable `then` is not a thenable + type Y = { value: number; then: number; method(): void }; + const nonCallableThen: () => Promise = async () => ({ value: 1, then: 0, method() { this.value.toFixed(); } }); + + // thenables supplied by a computed key or a spread keep the PromiseLike branch + type V = { value: number }; + const key = "then"; + declare const thenableSource: { then(resolve: (x: V) => void): void }; + const computedThen: () => Promise = async () => ({ + [key](resolve: (x: V) => void) { this.value.toFixed(); }, + ~~~~~ +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike | V'. +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike'. + }); + const spreadThen: () => Promise = async () => ({ + ...thenableSource, + extra() { this.value.toFixed(); }, + ~~~~~ +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike | V'. +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike'. + }); + + // spreads of object literals and statically known computed keys other than `then` are not thenables + const valueKey = "value"; + foo(async () => ({ ...{ value: 5 }, method() { this satisfies X; } })); + foo(async () => ({ ...({ value: 5 }), method() { this satisfies X; } })); + foo(async () => ({ [valueKey]: 5, method() { this satisfies X; } })); + // a spread object literal containing `then` is still a thenable + const nestedSpreadThen: () => Promise = async () => ({ + ...{ then(resolve: (x: V) => void) {} }, + extra() { this.value.toFixed(); }, + ~~~~~ +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike | V'. +!!! error TS2339: Property 'value' does not exist on type 'PromiseLike'. + }); + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.symbols b/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.symbols new file mode 100644 index 0000000000000..cb050df5d4e65 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.symbols @@ -0,0 +1,202 @@ +//// [tests/cases/compiler/asyncReturnObjectLiteralThisType.ts] //// + +=== asyncReturnObjectLiteralThisType.ts === +type X = { value: number; method(): void }; +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 0, 10)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 0, 25)) + +declare function foo(cb: () => Promise): void; +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>cb : Symbol(cb, Decl(asyncReturnObjectLiteralThisType.ts, 1, 21)) +>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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +foo(async () => ({ value: 5, method() { this satisfies X; } })); +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 2, 18)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 2, 28)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +foo(async () => { return { value: 5, method() { this satisfies X; } }; }); +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 3, 26)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 3, 36)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +foo(async function () { return { value: 5, method() { this satisfies X; } }; }); +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 4, 32)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 4, 42)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +async function f(): Promise { return { value: 1, method() { this satisfies X; } }; } +>f : Symbol(f, Decl(asyncReturnObjectLiteralThisType.ts, 4, 80)) +>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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 5, 41)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 5, 51)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +const test2: () => Promise = async () => { return { value: 5, method() { this satisfies X; } }; }; +>test2 : Symbol(test2, Decl(asyncReturnObjectLiteralThisType.ts, 6, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 6, 54)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 6, 64)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +// nullable awaited +const n1: () => Promise = async () => ({ value: 1, method() { this satisfies X; } }); +>n1 : Symbol(n1, Decl(asyncReturnObjectLiteralThisType.ts, 8, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 8, 50)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 8, 60)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +const n2: () => Promise = async () => { return { value: 1, method() { this satisfies X; } }; }; +>n2 : Symbol(n2, Decl(asyncReturnObjectLiteralThisType.ts, 9, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 9, 63)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 9, 73)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +// thenable must not get this: X +const t: () => Promise<{ value: number }> = async () => ({ +>t : Symbol(t, Decl(asyncReturnObjectLiteralThisType.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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 11, 24)) + + then(resolve: any) { this.value.toFixed(); }, +>then : Symbol(then, Decl(asyncReturnObjectLiteralThisType.ts, 11, 58)) +>resolve : Symbol(resolve, Decl(asyncReturnObjectLiteralThisType.ts, 12, 7)) + +}); +// declared PromiseLike remains +declare function g(cb: () => X | PromiseLike): void; +>g : Symbol(g, Decl(asyncReturnObjectLiteralThisType.ts, 13, 3)) +>cb : Symbol(cb, Decl(asyncReturnObjectLiteralThisType.ts, 15, 19)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) +>PromiseLike : Symbol(PromiseLike, Decl(lib.es5.d.ts, --, --)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +g(async () => ({ value: 1, method() { this satisfies X; } })); +>g : Symbol(g, Decl(asyncReturnObjectLiteralThisType.ts, 13, 3)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 16, 16)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 16, 26)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +// non-callable `then` is not a thenable +type Y = { value: number; then: number; method(): void }; +>Y : Symbol(Y, Decl(asyncReturnObjectLiteralThisType.ts, 16, 62)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 19, 10)) +>then : Symbol(then, Decl(asyncReturnObjectLiteralThisType.ts, 19, 25)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 19, 39)) + +const nonCallableThen: () => Promise = async () => ({ value: 1, then: 0, method() { this.value.toFixed(); } }); +>nonCallableThen : Symbol(nonCallableThen, Decl(asyncReturnObjectLiteralThisType.ts, 20, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>Y : Symbol(Y, Decl(asyncReturnObjectLiteralThisType.ts, 16, 62)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 20, 56)) +>then : Symbol(then, Decl(asyncReturnObjectLiteralThisType.ts, 20, 66)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 20, 75)) +>this.value.toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) +>this.value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 19, 10)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 19, 8)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 19, 10)) +>toFixed : Symbol(Number.toFixed, Decl(lib.es5.d.ts, --, --)) + +// thenables supplied by a computed key or a spread keep the PromiseLike branch +type V = { value: number }; +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 23, 10)) + +const key = "then"; +>key : Symbol(key, Decl(asyncReturnObjectLiteralThisType.ts, 24, 5)) + +declare const thenableSource: { then(resolve: (x: V) => void): void }; +>thenableSource : Symbol(thenableSource, Decl(asyncReturnObjectLiteralThisType.ts, 25, 13)) +>then : Symbol(then, Decl(asyncReturnObjectLiteralThisType.ts, 25, 31)) +>resolve : Symbol(resolve, Decl(asyncReturnObjectLiteralThisType.ts, 25, 37)) +>x : Symbol(x, Decl(asyncReturnObjectLiteralThisType.ts, 25, 47)) +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) + +const computedThen: () => Promise = async () => ({ +>computedThen : Symbol(computedThen, Decl(asyncReturnObjectLiteralThisType.ts, 26, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) + + [key](resolve: (x: V) => void) { this.value.toFixed(); }, +>[key] : Symbol([key], Decl(asyncReturnObjectLiteralThisType.ts, 26, 53)) +>key : Symbol(key, Decl(asyncReturnObjectLiteralThisType.ts, 24, 5)) +>resolve : Symbol(resolve, Decl(asyncReturnObjectLiteralThisType.ts, 27, 8)) +>x : Symbol(x, Decl(asyncReturnObjectLiteralThisType.ts, 27, 18)) +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) + +}); +const spreadThen: () => Promise = async () => ({ +>spreadThen : Symbol(spreadThen, Decl(asyncReturnObjectLiteralThisType.ts, 29, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) + + ...thenableSource, +>thenableSource : Symbol(thenableSource, Decl(asyncReturnObjectLiteralThisType.ts, 25, 13)) + + extra() { this.value.toFixed(); }, +>extra : Symbol(extra, Decl(asyncReturnObjectLiteralThisType.ts, 30, 20)) + +}); + +// spreads of object literals and statically known computed keys other than `then` are not thenables +const valueKey = "value"; +>valueKey : Symbol(valueKey, Decl(asyncReturnObjectLiteralThisType.ts, 35, 5)) + +foo(async () => ({ ...{ value: 5 }, method() { this satisfies X; } })); +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 36, 23)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 36, 35)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +foo(async () => ({ ...({ value: 5 }), method() { this satisfies X; } })); +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>value : Symbol(value, Decl(asyncReturnObjectLiteralThisType.ts, 37, 24)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 37, 37)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +foo(async () => ({ [valueKey]: 5, method() { this satisfies X; } })); +>foo : Symbol(foo, Decl(asyncReturnObjectLiteralThisType.ts, 0, 43)) +>[valueKey] : Symbol([valueKey], Decl(asyncReturnObjectLiteralThisType.ts, 38, 18)) +>valueKey : Symbol(valueKey, Decl(asyncReturnObjectLiteralThisType.ts, 35, 5)) +>method : Symbol(method, Decl(asyncReturnObjectLiteralThisType.ts, 38, 33)) +>this : Symbol(__type, Decl(asyncReturnObjectLiteralThisType.ts, 0, 8)) +>X : Symbol(X, Decl(asyncReturnObjectLiteralThisType.ts, 0, 0)) + +// a spread object literal containing `then` is still a thenable +const nestedSpreadThen: () => Promise = async () => ({ +>nestedSpreadThen : Symbol(nestedSpreadThen, Decl(asyncReturnObjectLiteralThisType.ts, 40, 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, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) + + ...{ then(resolve: (x: V) => void) {} }, +>then : Symbol(then, Decl(asyncReturnObjectLiteralThisType.ts, 41, 6)) +>resolve : Symbol(resolve, Decl(asyncReturnObjectLiteralThisType.ts, 41, 12)) +>x : Symbol(x, Decl(asyncReturnObjectLiteralThisType.ts, 41, 22)) +>V : Symbol(V, Decl(asyncReturnObjectLiteralThisType.ts, 20, 114)) + + extra() { this.value.toFixed(); }, +>extra : Symbol(extra, Decl(asyncReturnObjectLiteralThisType.ts, 41, 42)) + +}); + diff --git a/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.types b/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.types new file mode 100644 index 0000000000000..c49a06e803e47 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/asyncReturnObjectLiteralThisType.types @@ -0,0 +1,270 @@ +//// [tests/cases/compiler/asyncReturnObjectLiteralThisType.ts] //// + +=== asyncReturnObjectLiteralThisType.ts === +type X = { value: number; method(): void }; +>X : X +>value : number +>method : () => void + +declare function foo(cb: () => Promise): void; +>foo : (cb: () => Promise) => void +>cb : () => Promise + +foo(async () => ({ value: 5, method() { this satisfies X; } })); +>foo(async () => ({ value: 5, method() { this satisfies X; } })) : void +>foo : (cb: () => Promise) => void +>async () => ({ value: 5, method() { this satisfies X; } }) : () => Promise<{ value: number; method(): void; }> +>({ value: 5, method() { this satisfies X; } }) : { value: number; method(): void; } +>{ value: 5, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +foo(async () => { return { value: 5, method() { this satisfies X; } }; }); +>foo(async () => { return { value: 5, method() { this satisfies X; } }; }) : void +>foo : (cb: () => Promise) => void +>async () => { return { value: 5, method() { this satisfies X; } }; } : () => Promise<{ value: number; method(): void; }> +>{ value: 5, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +foo(async function () { return { value: 5, method() { this satisfies X; } }; }); +>foo(async function () { return { value: 5, method() { this satisfies X; } }; }) : void +>foo : (cb: () => Promise) => void +>async function () { return { value: 5, method() { this satisfies X; } }; } : () => Promise<{ value: number; method(): void; }> +>{ value: 5, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +async function f(): Promise { return { value: 1, method() { this satisfies X; } }; } +>f : () => Promise +>{ value: 1, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>1 : 1 +>method : () => void +>this satisfies X : X +>this : X + +const test2: () => Promise = async () => { return { value: 5, method() { this satisfies X; } }; }; +>test2 : () => Promise +>async () => { return { value: 5, method() { this satisfies X; } }; } : () => Promise<{ value: number; method(): void; }> +>{ value: 5, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +// nullable awaited +const n1: () => Promise = async () => ({ value: 1, method() { this satisfies X; } }); +>n1 : () => Promise +>async () => ({ value: 1, method() { this satisfies X; } }) : () => Promise<{ value: number; method(): void; }> +>({ value: 1, method() { this satisfies X; } }) : { value: number; method(): void; } +>{ value: 1, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>1 : 1 +>method : () => void +>this satisfies X : X +>this : X + +const n2: () => Promise = async () => { return { value: 1, method() { this satisfies X; } }; }; +>n2 : () => Promise +>async () => { return { value: 1, method() { this satisfies X; } }; } : () => Promise<{ value: number; method(): void; }> +>{ value: 1, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>1 : 1 +>method : () => void +>this satisfies X : X +>this : X + +// thenable must not get this: X +const t: () => Promise<{ value: number }> = async () => ({ +>t : () => Promise<{ value: number; }> +>value : number +>async () => ({ then(resolve: any) { this.value.toFixed(); },}) : () => Promise +>({ then(resolve: any) { this.value.toFixed(); },}) : { then(resolve: any): void; } +>{ then(resolve: any) { this.value.toFixed(); },} : { then(resolve: any): void; } + + then(resolve: any) { this.value.toFixed(); }, +>then : (resolve: any) => void +>resolve : any +>this.value.toFixed() : any +>this.value.toFixed : any +>this.value : any +>this : PromiseLike<{ value: number; }> | { value: number; } +>value : any +>toFixed : any + +}); +// declared PromiseLike remains +declare function g(cb: () => X | PromiseLike): void; +>g : (cb: () => X | PromiseLike) => void +>cb : () => X | PromiseLike + +g(async () => ({ value: 1, method() { this satisfies X; } })); +>g(async () => ({ value: 1, method() { this satisfies X; } })) : void +>g : (cb: () => X | PromiseLike) => void +>async () => ({ value: 1, method() { this satisfies X; } }) : () => Promise<{ value: number; method(): void; }> +>({ value: 1, method() { this satisfies X; } }) : { value: number; method(): void; } +>{ value: 1, method() { this satisfies X; } } : { value: number; method(): void; } +>value : number +>1 : 1 +>method : () => void +>this satisfies X : X +>this : X + +// non-callable `then` is not a thenable +type Y = { value: number; then: number; method(): void }; +>Y : Y +>value : number +>then : number +>method : () => void + +const nonCallableThen: () => Promise = async () => ({ value: 1, then: 0, method() { this.value.toFixed(); } }); +>nonCallableThen : () => Promise +>async () => ({ value: 1, then: 0, method() { this.value.toFixed(); } }) : () => Promise<{ value: number; then: number; method(): void; }> +>({ value: 1, then: 0, method() { this.value.toFixed(); } }) : { value: number; then: number; method(): void; } +>{ value: 1, then: 0, method() { this.value.toFixed(); } } : { value: number; then: number; method(): void; } +>value : number +>1 : 1 +>then : number +>0 : 0 +>method : () => void +>this.value.toFixed() : string +>this.value.toFixed : (fractionDigits?: number) => string +>this.value : number +>this : Y +>value : number +>toFixed : (fractionDigits?: number) => string + +// thenables supplied by a computed key or a spread keep the PromiseLike branch +type V = { value: number }; +>V : V +>value : number + +const key = "then"; +>key : "then" +>"then" : "then" + +declare const thenableSource: { then(resolve: (x: V) => void): void }; +>thenableSource : { then(resolve: (x: V) => void): void; } +>then : (resolve: (x: V) => void) => void +>resolve : (x: V) => void +>x : V + +const computedThen: () => Promise = async () => ({ +>computedThen : () => Promise +>async () => ({ [key](resolve: (x: V) => void) { this.value.toFixed(); },}) : () => Promise +>({ [key](resolve: (x: V) => void) { this.value.toFixed(); },}) : { then(resolve: (x: V) => void): void; } +>{ [key](resolve: (x: V) => void) { this.value.toFixed(); },} : { then(resolve: (x: V) => void): void; } + + [key](resolve: (x: V) => void) { this.value.toFixed(); }, +>[key] : (resolve: (x: V) => void) => void +>key : "then" +>resolve : (x: V) => void +>x : V +>this.value.toFixed() : any +>this.value.toFixed : any +>this.value : any +>this : PromiseLike | V +>value : any +>toFixed : any + +}); +const spreadThen: () => Promise = async () => ({ +>spreadThen : () => Promise +>async () => ({ ...thenableSource, extra() { this.value.toFixed(); },}) : () => Promise +>({ ...thenableSource, extra() { this.value.toFixed(); },}) : { then(resolve: (x: V) => void): void; extra(): void; } +>{ ...thenableSource, extra() { this.value.toFixed(); },} : { then(resolve: (x: V) => void): void; extra(): void; } + + ...thenableSource, +>thenableSource : { then(resolve: (x: V) => void): void; } + + extra() { this.value.toFixed(); }, +>extra : () => void +>this.value.toFixed() : any +>this.value.toFixed : any +>this.value : any +>this : PromiseLike | V +>value : any +>toFixed : any + +}); + +// spreads of object literals and statically known computed keys other than `then` are not thenables +const valueKey = "value"; +>valueKey : "value" +>"value" : "value" + +foo(async () => ({ ...{ value: 5 }, method() { this satisfies X; } })); +>foo(async () => ({ ...{ value: 5 }, method() { this satisfies X; } })) : void +>foo : (cb: () => Promise) => void +>async () => ({ ...{ value: 5 }, method() { this satisfies X; } }) : () => Promise<{ value: number; method(): void; }> +>({ ...{ value: 5 }, method() { this satisfies X; } }) : { value: number; method(): void; } +>{ ...{ value: 5 }, method() { this satisfies X; } } : { value: number; method(): void; } +>{ value: 5 } : { value: number; } +>value : number +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +foo(async () => ({ ...({ value: 5 }), method() { this satisfies X; } })); +>foo(async () => ({ ...({ value: 5 }), method() { this satisfies X; } })) : void +>foo : (cb: () => Promise) => void +>async () => ({ ...({ value: 5 }), method() { this satisfies X; } }) : () => Promise<{ value: number; method(): void; }> +>({ ...({ value: 5 }), method() { this satisfies X; } }) : { value: number; method(): void; } +>{ ...({ value: 5 }), method() { this satisfies X; } } : { value: number; method(): void; } +>({ value: 5 }) : { value: number; } +>{ value: 5 } : { value: number; } +>value : number +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +foo(async () => ({ [valueKey]: 5, method() { this satisfies X; } })); +>foo(async () => ({ [valueKey]: 5, method() { this satisfies X; } })) : void +>foo : (cb: () => Promise) => void +>async () => ({ [valueKey]: 5, method() { this satisfies X; } }) : () => Promise<{ value: number; method(): void; }> +>({ [valueKey]: 5, method() { this satisfies X; } }) : { value: number; method(): void; } +>{ [valueKey]: 5, method() { this satisfies X; } } : { value: number; method(): void; } +>[valueKey] : number +>valueKey : "value" +>5 : 5 +>method : () => void +>this satisfies X : X +>this : X + +// a spread object literal containing `then` is still a thenable +const nestedSpreadThen: () => Promise = async () => ({ +>nestedSpreadThen : () => Promise +>async () => ({ ...{ then(resolve: (x: V) => void) {} }, extra() { this.value.toFixed(); },}) : () => Promise +>({ ...{ then(resolve: (x: V) => void) {} }, extra() { this.value.toFixed(); },}) : { then(resolve: (x: V) => void): void; extra(): void; } +>{ ...{ then(resolve: (x: V) => void) {} }, extra() { this.value.toFixed(); },} : { then(resolve: (x: V) => void): void; extra(): void; } + + ...{ then(resolve: (x: V) => void) {} }, +>{ then(resolve: (x: V) => void) {} } : { then(resolve: (x: V) => void): void; } +>then : (resolve: (x: V) => void) => void +>resolve : (x: V) => void +>x : V + + extra() { this.value.toFixed(); }, +>extra : () => void +>this.value.toFixed() : any +>this.value.toFixed : any +>this.value : any +>this : PromiseLike | V +>value : any +>toFixed : any + +}); + diff --git a/tsc/testdata/tests/cases/compiler/asyncReturnObjectLiteralThisType.ts b/tsc/testdata/tests/cases/compiler/asyncReturnObjectLiteralThisType.ts new file mode 100644 index 0000000000000..250ed7fd92286 --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/asyncReturnObjectLiteralThisType.ts @@ -0,0 +1,48 @@ +// @strict: true +// @target: esnext +// @noEmit: true + +type X = { value: number; method(): void }; +declare function foo(cb: () => Promise): void; +foo(async () => ({ value: 5, method() { this satisfies X; } })); +foo(async () => { return { value: 5, method() { this satisfies X; } }; }); +foo(async function () { return { value: 5, method() { this satisfies X; } }; }); +async function f(): Promise { return { value: 1, method() { this satisfies X; } }; } +const test2: () => Promise = async () => { return { value: 5, method() { this satisfies X; } }; }; +// nullable awaited +const n1: () => Promise = async () => ({ value: 1, method() { this satisfies X; } }); +const n2: () => Promise = async () => { return { value: 1, method() { this satisfies X; } }; }; +// thenable must not get this: X +const t: () => Promise<{ value: number }> = async () => ({ + then(resolve: any) { this.value.toFixed(); }, +}); +// declared PromiseLike remains +declare function g(cb: () => X | PromiseLike): void; +g(async () => ({ value: 1, method() { this satisfies X; } })); + +// non-callable `then` is not a thenable +type Y = { value: number; then: number; method(): void }; +const nonCallableThen: () => Promise = async () => ({ value: 1, then: 0, method() { this.value.toFixed(); } }); + +// thenables supplied by a computed key or a spread keep the PromiseLike branch +type V = { value: number }; +const key = "then"; +declare const thenableSource: { then(resolve: (x: V) => void): void }; +const computedThen: () => Promise = async () => ({ + [key](resolve: (x: V) => void) { this.value.toFixed(); }, +}); +const spreadThen: () => Promise = async () => ({ + ...thenableSource, + extra() { this.value.toFixed(); }, +}); + +// spreads of object literals and statically known computed keys other than `then` are not thenables +const valueKey = "value"; +foo(async () => ({ ...{ value: 5 }, method() { this satisfies X; } })); +foo(async () => ({ ...({ value: 5 }), method() { this satisfies X; } })); +foo(async () => ({ [valueKey]: 5, method() { this satisfies X; } })); +// a spread object literal containing `then` is still a thenable +const nestedSpreadThen: () => Promise = async () => ({ + ...{ then(resolve: (x: V) => void) {} }, + extra() { this.value.toFixed(); }, +});