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
59 changes: 59 additions & 0 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -12287,6 +12331,21 @@ func (c *Checker) getContextualThisParameterType(fn *ast.Node) *Type {
// There was no contextual ThisType<T> 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<T> | PromiseLike<Awaited<T>>`
// (see getContextualTypeForReturnExpression), but `this` is always the literal itself.
// Restrict filtering of `PromiseLike<T>` 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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
},
};
Original file line number Diff line number Diff line change
@@ -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))

},
};


Original file line number Diff line number Diff line change
@@ -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

},
};


Original file line number Diff line number Diff line change
@@ -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;
},
};