mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Report error for invalid 'this' type during 'await' (#48946)
This commit is contained in:
+33
-4
@@ -36270,7 +36270,7 @@ namespace ts {
|
||||
* @param type The type of the promise.
|
||||
* @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback.
|
||||
*/
|
||||
function getPromisedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined {
|
||||
function getPromisedTypeOfPromise(type: Type, errorNode?: Node, thisTypeForErrorOut?: { value?: Type }): Type | undefined {
|
||||
//
|
||||
// { // type
|
||||
// then( // thenFunction
|
||||
@@ -36312,7 +36312,30 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull);
|
||||
let thisTypeForError: Type | undefined;
|
||||
let candidates: Signature[] | undefined;
|
||||
for (const thenSignature of thenSignatures) {
|
||||
const thisType = getThisTypeOfSignature(thenSignature);
|
||||
if (thisType && thisType !== voidType && !isTypeRelatedTo(type, thisType, subtypeRelation)) {
|
||||
thisTypeForError = thisType;
|
||||
}
|
||||
else {
|
||||
candidates = append(candidates, thenSignature);
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates) {
|
||||
Debug.assertIsDefined(thisTypeForError);
|
||||
if (thisTypeForErrorOut) {
|
||||
thisTypeForErrorOut.value = thisTypeForError;
|
||||
}
|
||||
if (errorNode) {
|
||||
error(errorNode, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForError));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(candidates, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull);
|
||||
if (isTypeAny(onfulfilledParameterType)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -36459,7 +36482,8 @@ namespace ts {
|
||||
return typeAsAwaitable.awaitedTypeOfType = mapType(type, mapper);
|
||||
}
|
||||
|
||||
const promisedType = getPromisedTypeOfPromise(type);
|
||||
const thisTypeForErrorOut: { value: Type | undefined } = { value: undefined };
|
||||
const promisedType = getPromisedTypeOfPromise(type, /*errorNode*/ undefined, thisTypeForErrorOut);
|
||||
if (promisedType) {
|
||||
if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) {
|
||||
// Verify that we don't have a bad actor in the form of a promise whose
|
||||
@@ -36532,7 +36556,12 @@ namespace ts {
|
||||
if (isThenableType(type)) {
|
||||
if (errorNode) {
|
||||
Debug.assertIsDefined(diagnosticMessage);
|
||||
error(errorNode, diagnosticMessage, arg0);
|
||||
let chain: DiagnosticMessageChain | undefined;
|
||||
if (thisTypeForErrorOut.value) {
|
||||
chain = chainDiagnosticMessages(chain, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForErrorOut.value));
|
||||
}
|
||||
chain = chainDiagnosticMessages(chain, diagnosticMessage, arg0);
|
||||
diagnostics.add(createDiagnosticForNodeFromMessageChain(errorNode, chain));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
tests/cases/conformance/async/es2017/await_incorrectThisType.ts(40,1): error TS2684: The 'this' context of type 'EPromise<number, string>' is not assignable to method's 'this' of type 'EPromise<never, string>'.
|
||||
Type 'number' is not assignable to type 'never'.
|
||||
tests/cases/conformance/async/es2017/await_incorrectThisType.ts(43,5): error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
|
||||
The 'this' context of type 'EPromise<number, string>' is not assignable to method's 'this' of type 'EPromise<never, string>'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/async/es2017/await_incorrectThisType.ts (2 errors) ====
|
||||
// https://github.com/microsoft/TypeScript/issues/47711
|
||||
type Either<E, A> = Left<E> | Right<A>;
|
||||
type Left<E> = { tag: 'Left', e: E };
|
||||
type Right<A> = { tag: 'Right', a: A };
|
||||
|
||||
const mkLeft = <E>(e: E): Either<E, never> => ({ tag: 'Left', e });
|
||||
const mkRight = <A>(a: A): Either<never, A> => ({ tag: 'Right', a });
|
||||
|
||||
class EPromise<E, A> implements PromiseLike<A> {
|
||||
static succeed<A>(a: A): EPromise<never, A> {
|
||||
return new EPromise(Promise.resolve(mkRight(a)));
|
||||
}
|
||||
|
||||
static fail<E>(e: E): EPromise<E, never> {
|
||||
return new EPromise(Promise.resolve(mkLeft(e)));
|
||||
}
|
||||
|
||||
constructor(readonly p: PromiseLike<Either<E, A>>) { }
|
||||
|
||||
then<B = A, B1 = never>(
|
||||
// EPromise can act as a Thenable only when `E` is `never`.
|
||||
this: EPromise<never, A>,
|
||||
onfulfilled?: ((value: A) => B | PromiseLike<B>) | null | undefined,
|
||||
onrejected?: ((reason: any) => B1 | PromiseLike<B1>) | null | undefined
|
||||
): PromiseLike<B | B1> {
|
||||
return this.p.then(
|
||||
// Casting to `Right<A>` is safe here because we've eliminated the possibility of `Left<E>`.
|
||||
either => onfulfilled?.((either as Right<A>).a) ?? (either as Right<A>).a as unknown as B,
|
||||
onrejected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const withTypedFailure: EPromise<number, string> = EPromise.fail(1);
|
||||
|
||||
// Errors as expected:
|
||||
//
|
||||
// "The 'this' context of type 'EPromise<number, string>' is not assignable to method's
|
||||
// 'this' of type 'EPromise<never, string>"
|
||||
withTypedFailure.then(s => s.toUpperCase()).then(console.log);
|
||||
~~~~~~~~~~~~~~~~
|
||||
!!! error TS2684: The 'this' context of type 'EPromise<number, string>' is not assignable to method's 'this' of type 'EPromise<never, string>'.
|
||||
!!! error TS2684: Type 'number' is not assignable to type 'never'.
|
||||
|
||||
async function test() {
|
||||
await withTypedFailure;
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
!!! error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member.
|
||||
!!! error TS1320: The 'this' context of type 'EPromise<number, string>' is not assignable to method's 'this' of type 'EPromise<never, string>'.
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// @target: esnext
|
||||
// @noEmit: true
|
||||
// @noTypesAndSymbols: true
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/47711
|
||||
type Either<E, A> = Left<E> | Right<A>;
|
||||
type Left<E> = { tag: 'Left', e: E };
|
||||
type Right<A> = { tag: 'Right', a: A };
|
||||
|
||||
const mkLeft = <E>(e: E): Either<E, never> => ({ tag: 'Left', e });
|
||||
const mkRight = <A>(a: A): Either<never, A> => ({ tag: 'Right', a });
|
||||
|
||||
class EPromise<E, A> implements PromiseLike<A> {
|
||||
static succeed<A>(a: A): EPromise<never, A> {
|
||||
return new EPromise(Promise.resolve(mkRight(a)));
|
||||
}
|
||||
|
||||
static fail<E>(e: E): EPromise<E, never> {
|
||||
return new EPromise(Promise.resolve(mkLeft(e)));
|
||||
}
|
||||
|
||||
constructor(readonly p: PromiseLike<Either<E, A>>) { }
|
||||
|
||||
then<B = A, B1 = never>(
|
||||
// EPromise can act as a Thenable only when `E` is `never`.
|
||||
this: EPromise<never, A>,
|
||||
onfulfilled?: ((value: A) => B | PromiseLike<B>) | null | undefined,
|
||||
onrejected?: ((reason: any) => B1 | PromiseLike<B1>) | null | undefined
|
||||
): PromiseLike<B | B1> {
|
||||
return this.p.then(
|
||||
// Casting to `Right<A>` is safe here because we've eliminated the possibility of `Left<E>`.
|
||||
either => onfulfilled?.((either as Right<A>).a) ?? (either as Right<A>).a as unknown as B,
|
||||
onrejected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const withTypedFailure: EPromise<number, string> = EPromise.fail(1);
|
||||
|
||||
// Errors as expected:
|
||||
//
|
||||
// "The 'this' context of type 'EPromise<number, string>' is not assignable to method's
|
||||
// 'this' of type 'EPromise<never, string>"
|
||||
withTypedFailure.then(s => s.toUpperCase()).then(console.log);
|
||||
|
||||
async function test() {
|
||||
await withTypedFailure;
|
||||
}
|
||||
Reference in New Issue
Block a user