Skip to content

Commit

Permalink
Defer generic awaited type
Browse files Browse the repository at this point in the history
  • Loading branch information
jablko committed Nov 27, 2019
1 parent 485b07e commit 2af06c9
Show file tree
Hide file tree
Showing 23 changed files with 261 additions and 225 deletions.
102 changes: 15 additions & 87 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ namespace ts {
let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined;
let deferredGlobalESSymbolType: ObjectType;
let deferredGlobalTypedPropertyDescriptorType: GenericType;
let deferredGlobalAwaitedSymbol: Symbol | undefined;
let deferredGlobalPromiseType: GenericType;
let deferredGlobalPromiseLikeType: GenericType;
let deferredGlobalPromiseConstructorSymbol: Symbol | undefined;
Expand Down Expand Up @@ -853,7 +854,6 @@ namespace ts {
const flowNodeReachable: (boolean | undefined)[] = [];
const potentialThisCollisions: Node[] = [];
const potentialNewTargetCollisions: Node[] = [];
const awaitedTypeStack: number[] = [];

const diagnostics = createDiagnosticCollection();
const suggestionDiagnostics = createDiagnosticCollection();
Expand Down Expand Up @@ -11069,6 +11069,10 @@ namespace ts {
return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}

function getGlobalAwaitedSymbol(reportErrors: boolean) {
return deferredGlobalAwaitedSymbol || (deferredGlobalAwaitedSymbol = getGlobalTypeSymbol("Awaited" as __String, reportErrors));
}

function getGlobalPromiseType(reportErrors: boolean) {
return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType;
}
Expand Down Expand Up @@ -29012,98 +29016,22 @@ namespace ts {
return typeAsAwaitable.awaitedTypeOfType = type;
}

if (type.flags & TypeFlags.Union) {
let types: Type[] | undefined;
for (const constituentType of (<UnionType>type).types) {
types = append<Type>(types, getAwaitedType(constituentType, errorNode, diagnosticMessage, arg0));
}

if (!types) {
return undefined;
}

return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
const symbol = getGlobalAwaitedSymbol(/*reportErrors*/ false);
if (!symbol) {
return typeAsAwaitable.awaitedTypeOfType = type;
}

const promisedType = getPromisedTypeOfPromise(type);
if (promisedType) {
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
// Verify that we don't have a bad actor in the form of a promise whose
// promised type is the same as the promise type, or a mutually recursive
// promise. If so, we return undefined as we cannot guess the shape. If this
// were the actual case in the JavaScript, this Promise would never resolve.
//
// An example of a bad actor with a singly-recursive promise type might
// be:
//
// interface BadPromise {
// then(
// onfulfilled: (value: BadPromise) => any,
// onrejected: (error: any) => any): BadPromise;
// }
// The above interface will pass the PromiseLike check, and return a
// promised type of `BadPromise`. Since this is a self reference, we
// don't want to keep recursing ad infinitum.
//
// An example of a bad actor in the form of a mutually-recursive
// promise type might be:
//
// interface BadPromiseA {
// then(
// onfulfilled: (value: BadPromiseB) => any,
// onrejected: (error: any) => any): BadPromiseB;
// }
//
// interface BadPromiseB {
// then(
// onfulfilled: (value: BadPromiseA) => any,
// onrejected: (error: any) => any): BadPromiseA;
// }
//
if (errorNode) {
error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method);
}
return undefined;
}

// Keep track of the type we're about to unwrap to avoid bad recursive promise types.
// See the comments above for more information.
awaitedTypeStack.push(type.id);
const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0);
awaitedTypeStack.pop();

if (!awaitedType) {
return undefined;
}

return typeAsAwaitable.awaitedTypeOfType = awaitedType;
const result = getTypeAliasInstantiation(symbol, [type]);
if (result !== unknownType || type === unknownType || getPromisedTypeOfPromise(type) === unknownType) {
return typeAsAwaitable.awaitedTypeOfType = (result as PromiseOrAwaitableType).awaitedTypeOfType = result;
}

// The type was not a promise, so it could not be unwrapped any further.
// As long as the type does not have a callable "then" property, it is
// safe to return the type; otherwise, an error will be reported in
// the call to getNonThenableType and we will return undefined.
//
// An example of a non-promise "thenable" might be:
//
// await { then(): void {} }
//
// The "thenable" does not match the minimal definition for a promise. When
// a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise
// will never settle. We treat this as an error to help flag an early indicator
// of a runtime problem. If the user wants to return this value from an async
// function, they would need to wrap it in some other value. If they want it to
// be treated as a promise, they can cast to <any>.
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String);
if (thenFunction && getSignaturesOfType(thenFunction, SignatureKind.Call).length > 0) {
if (errorNode) {
if (!diagnosticMessage) return Debug.fail();
error(errorNode, diagnosticMessage, arg0);
}
return undefined;
if (errorNode) {
if (!diagnosticMessage) return Debug.fail();
error(errorNode, diagnosticMessage, arg0);
}

return typeAsAwaitable.awaitedTypeOfType = type;
return undefined;
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4747,6 +4747,7 @@ namespace FourSlashInterface {
typeEntry("PropertyDecorator"),
typeEntry("MethodDecorator"),
typeEntry("ParameterDecorator"),
typeEntry("Awaited"),
typeEntry("PromiseConstructorLike"),
interfaceEntry("PromiseLike"),
interfaceEntry("Promise"),
Expand Down
5 changes: 5 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,11 @@ declare type PropertyDecorator = (target: Object, propertyKey: string | symbol)
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

// The undefined case is for strictNullChecks false, in which case
// undefined extends PromiseLike<infer U> is true, which would otherwise
// make Awaited<undefined> -> unknown.
type Awaited<T> = T extends undefined ? T : T extends PromiseLike<infer U> ? U : T extends { then(...args: any[]): any } ? unknown : T;

declare type PromiseConstructorLike = new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) => PromiseLike<T>;

interface PromiseLike<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<Awaited<this>>
>async () => await this : () => Promise<Awaited<this>>
>await this : Awaited<this>
>this : this
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<Awaited<this>>
>async () => await this : () => Promise<Awaited<this>>
>await this : Awaited<this>
>this : this
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ class C {
>method : () => void

var fn = async () => await this;
>fn : () => Promise<this>
>async () => await this : () => Promise<this>
>await this : this
>fn : () => Promise<Awaited<this>>
>async () => await this : () => Promise<Awaited<this>>
>await this : Awaited<this>
>this : this
}
}
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/asyncFunctionReturnType.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async function fGenericIndexedTypeForExplicitPromiseOfAnyProp<TObj extends Obj>(
return Promise.resolve<TObj["anyProp"]>(obj.anyProp);
}

async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<Awaited<TObj[K]>> {
return obj[key];
}

Expand All @@ -73,7 +73,8 @@ async function fGenericIndexedTypeForPromiseOfKProp<TObj extends Obj, K extends

async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
return Promise.resolve<TObj[K]>(obj[key]);
}
}


//// [asyncFunctionReturnType.js]
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
Expand Down
4 changes: 3 additions & 1 deletion tests/baselines/reference/asyncFunctionReturnType.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ async function fGenericIndexedTypeForExplicitPromiseOfAnyProp<TObj extends Obj>(
>anyProp : Symbol(Obj.anyProp, Decl(asyncFunctionReturnType.ts, 12, 23))
}

async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<Awaited<TObj[K]>> {
>fGenericIndexedTypeForKProp : Symbol(fGenericIndexedTypeForKProp, Decl(asyncFunctionReturnType.ts, 62, 1))
>TObj : Symbol(TObj, Decl(asyncFunctionReturnType.ts, 64, 43))
>Obj : Symbol(Obj, Decl(asyncFunctionReturnType.ts, 8, 1))
Expand All @@ -232,6 +232,7 @@ async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TOb
>key : Symbol(key, Decl(asyncFunctionReturnType.ts, 64, 93))
>K : Symbol(K, Decl(asyncFunctionReturnType.ts, 64, 60))
>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, --, --))
>Awaited : Symbol(Awaited, Decl(lib.es5.d.ts, --, --))
>TObj : Symbol(TObj, Decl(asyncFunctionReturnType.ts, 64, 43))
>K : Symbol(K, Decl(asyncFunctionReturnType.ts, 64, 60))

Expand Down Expand Up @@ -285,3 +286,4 @@ async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K
>obj : Symbol(obj, Decl(asyncFunctionReturnType.ts, 72, 100))
>key : Symbol(key, Decl(asyncFunctionReturnType.ts, 72, 110))
}

5 changes: 3 additions & 2 deletions tests/baselines/reference/asyncFunctionReturnType.types
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ async function fGenericIndexedTypeForExplicitPromiseOfAnyProp<TObj extends Obj>(
>anyProp : any
}

async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<TObj[K]> {
>fGenericIndexedTypeForKProp : <TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K) => Promise<TObj[K]>
async function fGenericIndexedTypeForKProp<TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K): Promise<Awaited<TObj[K]>> {
>fGenericIndexedTypeForKProp : <TObj extends Obj, K extends keyof TObj>(obj: TObj, key: K) => Promise<Awaited<TObj[K]>>
>obj : TObj
>key : K

Expand Down Expand Up @@ -220,3 +220,4 @@ async function fGenericIndexedTypeForExplicitPromiseOfKProp<TObj extends Obj, K
>obj : TObj
>key : K
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ tests/cases/compiler/compareTypeParameterConstrainedByLiteralToLiteral.ts(5,5):
t === "x"; // Should be error
~~~~~~~~~
!!! error TS2367: This condition will always return 'false' since the types 'T' and '"x"' have no overlap.
!!! related TS2773 tests/cases/compiler/compareTypeParameterConstrainedByLiteralToLiteral.ts:5:5: Did you forget to use 'await'?
}

Loading

0 comments on commit 2af06c9

Please sign in to comment.