Skip to content

Commit d83915c

Browse files
committed
Further restrict 'Awaited<T>' auto-wrapping for 'await'
1 parent 657f0a9 commit d83915c

10 files changed

+933
-42
lines changed

src/compiler/checker.ts

+36-24
Original file line numberDiff line numberDiff line change
@@ -13469,7 +13469,6 @@ namespace ts {
1346913469
}
1347013470

1347113471
function getGlobalAwaitedSymbol(reportErrors: boolean): Symbol | undefined {
13472-
if (reportErrors) debugger;
1347313472
// Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once.
1347413473
deferredGlobalAwaitedSymbol ||= getGlobalTypeAliasSymbol("Awaited" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined);
1347513474
return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol;
@@ -32560,10 +32559,8 @@ namespace ts {
3256032559
let wouldWorkWithAwait = false;
3256132560
const errNode = errorNode || operatorToken;
3256232561
if (isRelated) {
32563-
let awaitedLeftType = getAwaitedType(leftType);
32564-
let awaitedRightType = getAwaitedType(rightType);
32565-
awaitedLeftType &&= unwrapAwaitedType(awaitedLeftType);
32566-
awaitedRightType &&= unwrapAwaitedType(awaitedRightType);
32562+
const awaitedLeftType = unwrapAwaitedType(getAwaitedType(leftType));
32563+
const awaitedRightType = unwrapAwaitedType(getAwaitedType(rightType));
3256732564
wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType)
3256832565
&& !!(awaitedLeftType && awaitedRightType)
3256932566
&& isRelated(awaitedLeftType, awaitedRightType);
@@ -34643,9 +34640,14 @@ namespace ts {
3464334640
}
3464434641

3464534642
/**
34646-
* Determines whether a type has a callable `then` member.
34643+
* Determines whether a type is an object with a callable `then` member.
3464734644
*/
3464834645
function isThenableType(type: Type): boolean {
34646+
if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) {
34647+
// primitive types cannot be considered "thenable" since they are not objects.
34648+
return false;
34649+
}
34650+
3464934651
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
3465034652
return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0;
3465134653
}
@@ -34667,13 +34669,24 @@ namespace ts {
3466734669
/**
3466834670
* For a generic `Awaited<T>`, gets `T`.
3466934671
*/
34670-
function unwrapAwaitedType(type: Type) {
34672+
function unwrapAwaitedType(type: Type): Type;
34673+
function unwrapAwaitedType(type: Type | undefined): Type | undefined;
34674+
function unwrapAwaitedType(type: Type | undefined) {
34675+
if (!type) return undefined;
3467134676
return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) :
3467234677
isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] :
3467334678
type;
3467434679
}
3467534680

3467634681
function createAwaitedTypeIfNeeded(type: Type): Type {
34682+
// We wrap type `T` in `Awaited<T>` based on the following conditions:
34683+
// - `T` is not already an `Awaited<U>`, and
34684+
// - `T` is generic, and
34685+
// - One of the following applies:
34686+
// - `T` has no base constraint, or
34687+
// - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or
34688+
// - The base constraint of `T` is an object type with a callable `then` method.
34689+
3467734690
if (isTypeAny(type)) {
3467834691
return type;
3467934692
}
@@ -34684,13 +34697,18 @@ namespace ts {
3468434697
}
3468534698

3468634699
// Only instantiate `Awaited<T>` if `T` contains possibly non-primitive types.
34687-
if (isGenericObjectType(type) && !allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) {
34688-
// Nothing to do if `Awaited<T>` doesn't exist
34689-
const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true);
34690-
if (awaitedSymbol) {
34691-
// Unwrap unions that may contain `Awaited<T>`, otherwise its possible to manufacture an `Awaited<Awaited<T> | U>` where
34692-
// an `Awaited<T | U>` would suffice.
34693-
return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]);
34700+
if (isGenericObjectType(type)) {
34701+
const baseConstraint = getBaseConstraintOfType(type);
34702+
// Only instantiate `Awaited<T>` if `T` has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`,
34703+
// or is promise-like.
34704+
if (!baseConstraint || (baseConstraint.flags & TypeFlags.AnyOrUnknown) || isEmptyObjectType(baseConstraint) || isThenableType(baseConstraint)) {
34705+
// Nothing to do if `Awaited<T>` doesn't exist
34706+
const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true);
34707+
if (awaitedSymbol) {
34708+
// Unwrap unions that may contain `Awaited<T>`, otherwise its possible to manufacture an `Awaited<Awaited<T> | U>` where
34709+
// an `Awaited<T | U>` would suffice.
34710+
return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]);
34711+
}
3469434712
}
3469534713
}
3469634714

@@ -34731,12 +34749,6 @@ namespace ts {
3473134749
return typeAsAwaitable.awaitedTypeOfType && createAwaitedTypeIfNeeded(typeAsAwaitable.awaitedTypeOfType);
3473234750
}
3473334751

34734-
// primitives with a `{ then() }` won't be unwrapped/adopted. This prevents `Awaited<T>` when `T extends string`
34735-
// (or another primitive), since the `Awaited<T>` type only unwraps `object` types.
34736-
if (allTypesAssignableToKind(type, TypeFlags.Primitive | TypeFlags.Never)) {
34737-
return type;
34738-
}
34739-
3474034752
const promisedType = getPromisedTypeOfPromise(type);
3474134753
if (promisedType) {
3474234754
if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) {
@@ -34865,7 +34877,7 @@ namespace ts {
3486534877
if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) {
3486634878
// The promise type was not a valid type reference to the global promise type, so we
3486734879
// report an error and return the unknown type.
34868-
error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(unwrapAwaitedType(getAwaitedType(returnType) || voidType)));
34880+
error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(unwrapAwaitedType(getAwaitedType(returnType)) || voidType));
3486934881
return;
3487034882
}
3487134883
}
@@ -36865,7 +36877,7 @@ namespace ts {
3686536877
// - `Generator<T, TReturn, TNext>` or `AsyncGenerator<T, TReturn, TNext>`
3686636878
if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) {
3686736879
const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType);
36868-
return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(yieldType, returnType, nextType));
36880+
return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType));
3686936881
}
3687036882
}
3687136883

@@ -37197,8 +37209,8 @@ namespace ts {
3719737209
function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) {
3719837210
const isGenerator = !!(functionFlags & FunctionFlags.Generator);
3719937211
const isAsync = !!(functionFlags & FunctionFlags.Async);
37200-
return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) ?? errorType :
37201-
isAsync ? unwrapAwaitedType(getAwaitedType(returnType) ?? errorType) :
37212+
return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType :
37213+
isAsync ? unwrapAwaitedType(getAwaitedType(returnType)) || errorType :
3720237214
returnType;
3720337215
}
3720437216

src/lib/es5.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1444,7 +1444,7 @@ interface Promise<T> {
14441444
* Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`.
14451445
*/
14461446
type Awaited<T> =
1447-
T extends null | undefined ? T : // special case for `null | undefined` when not in `--noImplicitAny` mode
1447+
T extends null | undefined ? T : // special case for `null | undefined` when not in `--strictNullChecks` mode
14481448
T extends object ? // `await` only unwraps object types with a callable then. Non-object types are not unwrapped.
14491449
T extends { then(onfulfilled: infer F): any } ? // thenable, extracts the first argument to `then()`
14501450
F extends ((value: infer V) => any) ? // if the argument to `then` is callable, extracts the argument

tests/baselines/reference/asyncArrowFunctionCapturesThis_es2017.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class C {
66
>method : () => void
77

88
var fn = async () => await this;
9-
>fn : () => Promise<Awaited<this>>
10-
>async () => await this : () => Promise<Awaited<this>>
11-
>await this : Awaited<this>
9+
>fn : () => Promise<this>
10+
>async () => await this : () => Promise<this>
11+
>await this : this
1212
>this : this
1313
}
1414
}

tests/baselines/reference/asyncArrowFunctionCapturesThis_es5.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class C {
66
>method : () => void
77

88
var fn = async () => await this;
9-
>fn : () => Promise<Awaited<this>>
10-
>async () => await this : () => Promise<Awaited<this>>
11-
>await this : Awaited<this>
9+
>fn : () => Promise<this>
10+
>async () => await this : () => Promise<this>
11+
>await this : this
1212
>this : this
1313
}
1414
}

tests/baselines/reference/asyncArrowFunctionCapturesThis_es6.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ class C {
66
>method : () => void
77

88
var fn = async () => await this;
9-
>fn : () => Promise<Awaited<this>>
10-
>async () => await this : () => Promise<Awaited<this>>
11-
>await this : Awaited<this>
9+
>fn : () => Promise<this>
10+
>async () => await this : () => Promise<this>
11+
>await this : this
1212
>this : this
1313
}
1414
}

tests/baselines/reference/awaitedType.errors.txt

+116
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,122 @@ tests/cases/compiler/awaitedType.ts(22,12): error TS2589: Type instantiation is
4747
])
4848
}
4949

50+
// non-generic
51+
async function f1(x: string) {
52+
// y: string
53+
const y = await x;
54+
}
55+
56+
async function f2(x: unknown) {
57+
// y: unknown
58+
const y = await x;
59+
}
60+
61+
async function f3(x: object) {
62+
// y: object
63+
const y = await x;
64+
}
65+
66+
async function f4(x: Promise<string>) {
67+
// y: string
68+
const y = await x;
69+
}
70+
71+
async function f5(x: Promise<unknown>) {
72+
// y: unknown
73+
const y = await x;
74+
}
75+
76+
async function f6(x: Promise<object>) {
77+
// y: object
78+
const y = await x;
79+
}
80+
81+
// generic
82+
83+
async function f7<T>(x: T) {
84+
// NOTE: T does not belong solely to the domain of primitive types and either does
85+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
86+
// or it has a non-primitive base constraint with a callable `then`.
87+
88+
// y: Awaited<T>
89+
const y = await x;
90+
}
91+
92+
async function f8<T extends any>(x: T) {
93+
// NOTE: T does not belong solely to the domain of primitive types and either does
94+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
95+
// or it has a non-primitive base constraint with a callable `then`.
96+
97+
// y: Awaited<T>
98+
const y = await x;
99+
}
100+
101+
async function f9<T extends unknown>(x: T) {
102+
// NOTE: T does not belong solely to the domain of primitive types and either does
103+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
104+
// or it has a non-primitive base constraint with a callable `then`.
105+
106+
// y: Awaited<T>
107+
const y = await x;
108+
}
109+
110+
async function f10<T extends {}>(x: T) {
111+
// NOTE: T does not belong solely to the domain of primitive types and either does
112+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
113+
// or it has a non-primitive base constraint with a callable `then`.
114+
115+
// y: Awaited<T>
116+
const y = await x;
117+
}
118+
119+
async function f11<T extends { then(onfulfilled: (value: unknown) => void): void }>(x: T) {
120+
// NOTE: T does not belong solely to the domain of primitive types and either does
121+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
122+
// or it has a non-primitive base constraint with a callable `then`.
123+
124+
// y: Awaited<T>
125+
const y = await x;
126+
}
127+
128+
async function f12<T extends string | object>(x: T) {
129+
// NOTE: T does not belong solely to the domain of primitive types and either does
130+
// not have a base constraint, its base constraint is `any`, `unknown`, `{}`, or `object`,
131+
// or it has a non-primitive base constraint with a callable `then`.
132+
133+
// y: Awaited<T>
134+
const y = await x;
135+
}
136+
137+
async function f13<T extends string>(x: T) {
138+
// NOTE: T belongs to the domain of primitive types
139+
140+
// y: T
141+
const y = await x;
142+
}
143+
144+
async function f14<T extends { x: number }>(x: T) {
145+
// NOTE: T has a non-primitive base constraint without a callable `then`.
146+
147+
// y: T
148+
const y = await x;
149+
}
150+
151+
async function f15<T extends { then: number }>(x: T) {
152+
// NOTE: T has a non-primitive base constraint without a callable `then`.
153+
154+
// y: T
155+
const y = await x;
156+
}
157+
158+
async function f16<T extends number & { then(): void }>(x: T) {
159+
// NOTE: T belongs to the domain of primitive types (regardless of `then`)
160+
161+
// y: T
162+
const y = await x;
163+
}
164+
165+
50166
// helps with tests where '.types' just prints out the type alias name
51167
type _Expect<TActual extends TExpected, TExpected> = TActual;
52168

0 commit comments

Comments
 (0)