Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -12318,6 +12321,61 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type {
return nil
}

// The contextual type of an async function's return expression is `T | PromiseLike<T>`, where `T` is the awaited
// contextual return type. For `this` in methods of a returned object literal, the `PromiseLike<T>` 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
}
for _, p := range literal.AsObjectLiteralExpression().Properties.Nodes {
if p.Name() != nil {
if name, ok := ast.TryGetTextOfPropertyName(p.Name()); ok && name == "then" {
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 })
}

// 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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 (2 errors) ====
type X = { value: number; method(): void };
declare function foo(cb: () => Promise<X>): 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<X> { return { value: 1, method() { this satisfies X; } }; }
const test2: () => Promise<X> = async () => { return { value: 5, method() { this satisfies X; } }; };
// nullable awaited
const n1: () => Promise<X | null> = async () => ({ value: 1, method() { this satisfies X; } });
const n2: () => Promise<X | undefined> = 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<X>): void;
g(async () => ({ value: 1, method() { this satisfies X; } }));

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//// [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<X>): 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<X> { 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<X> = 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<X | null> = 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<X | undefined> = 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<X>): 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))

Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//// [tests/cases/compiler/asyncReturnObjectLiteralThisType.ts] ////

=== asyncReturnObjectLiteralThisType.ts ===
type X = { value: number; method(): void };
>X : X
>value : number
>method : () => void

declare function foo(cb: () => Promise<X>): void;
>foo : (cb: () => Promise<X>) => void
>cb : () => Promise<X>

foo(async () => ({ value: 5, method() { this satisfies X; } }));
>foo(async () => ({ value: 5, method() { this satisfies X; } })) : void
>foo : (cb: () => Promise<X>) => 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<X>) => 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<X>) => 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<X> { return { value: 1, method() { this satisfies X; } }; }
>f : () => Promise<X>
>{ 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<X> = async () => { return { value: 5, method() { this satisfies X; } }; };
>test2 : () => Promise<X>
>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<X | null> = async () => ({ value: 1, method() { this satisfies X; } });
>n1 : () => Promise<X | null>
>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<X | undefined> = async () => { return { value: 1, method() { this satisfies X; } }; };
>n2 : () => Promise<X | undefined>
>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<any>
>({ then(resolve: any) { this.value.toFixed(); },}) : { then<TResult1 = { value: number; }, TResult2 = never>(resolve: any): void; }
>{ then(resolve: any) { this.value.toFixed(); },} : { then<TResult1 = { value: number; }, TResult2 = never>(resolve: any): void; }

then(resolve: any) { this.value.toFixed(); },
>then : <TResult1 = { value: number; }, TResult2 = never>(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<X>): void;
>g : (cb: () => X | PromiseLike<X>) => void
>cb : () => X | PromiseLike<X>

g(async () => ({ value: 1, method() { this satisfies X; } }));
>g(async () => ({ value: 1, method() { this satisfies X; } })) : void
>g : (cb: () => X | PromiseLike<X>) => 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

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @strict: true
// @target: esnext
// @noEmit: true

type X = { value: number; method(): void };
declare function foo(cb: () => Promise<X>): 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<X> { return { value: 1, method() { this satisfies X; } }; }
const test2: () => Promise<X> = async () => { return { value: 5, method() { this satisfies X; } }; };
// nullable awaited
const n1: () => Promise<X | null> = async () => ({ value: 1, method() { this satisfies X; } });
const n2: () => Promise<X | undefined> = 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<X>): void;
g(async () => ({ value: 1, method() { this satisfies X; } }));