Skip to content

Commit 6fd4ca8

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

9 files changed

+917
-24
lines changed

src/compiler/checker.ts

+21-7
Original file line numberDiff line numberDiff line change
@@ -34643,9 +34643,10 @@ namespace ts {
3464334643
}
3464434644

3464534645
/**
34646-
* Determines whether a type has a callable `then` member.
34646+
* Determines whether a type is an object with a callable `then` member.
3464734647
*/
3464834648
function isThenableType(type: Type): boolean {
34649+
if (!isTypeAssignableToKind(type, TypeFlags.NonPrimitive)) return false;
3464934650
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
3465034651
return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0;
3465134652
}
@@ -34674,6 +34675,14 @@ namespace ts {
3467434675
}
3467534676

3467634677
function createAwaitedTypeIfNeeded(type: Type): Type {
34678+
// We wrap type `T` in `Awaited<T>` based on the following conditions:
34679+
// - `T` is not already an `Awaited<U>`, and
34680+
// - `T` is generic, and
34681+
// - One of the following applies:
34682+
// - `T` has no base constraint, or
34683+
// - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or
34684+
// - The base constraint of `T` is an object type with a callable `then` method.
34685+
3467734686
if (isTypeAny(type)) {
3467834687
return type;
3467934688
}
@@ -34685,12 +34694,17 @@ namespace ts {
3468534694

3468634695
// Only instantiate `Awaited<T>` if `T` contains possibly non-primitive types.
3468734696
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)]);
34697+
const baseConstraint = getBaseConstraintOfType(type);
34698+
// Only instantiate `Awaited<T>` if `T` has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`,
34699+
// or is promise-like.
34700+
if (!baseConstraint || (baseConstraint.flags & TypeFlags.AnyOrUnknown) || isEmptyObjectType(baseConstraint) || isThenableType(baseConstraint)) {
34701+
// Nothing to do if `Awaited<T>` doesn't exist
34702+
const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true);
34703+
if (awaitedSymbol) {
34704+
// Unwrap unions that may contain `Awaited<T>`, otherwise its possible to manufacture an `Awaited<Awaited<T> | U>` where
34705+
// an `Awaited<T | U>` would suffice.
34706+
return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]);
34707+
}
3469434708
}
3469534709
}
3469634710

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)