Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1ffda9f
Add the 'awaited' type operator
rbuckton Jan 4, 2020
a981148
Merge branch 'master' into awaitedType2
rbuckton Feb 17, 2020
e7174b1
Add script to manually add reviewers to a PR when GH 'Suggested Revie…
rbuckton Feb 17, 2020
65c7377
Fix lint error in review script
rbuckton Feb 24, 2020
7e57fed
Merge branch 'master' into awaitedType2
rbuckton Feb 28, 2020
8ccb983
Only defer generic awaited type for possible thenable
rbuckton Mar 2, 2020
7ea8940
Merge branch 'master' into awaitedType2
rbuckton Mar 2, 2020
624db27
Add variance-like behavior for awaited
rbuckton Mar 4, 2020
95d7316
Merge branch 'master' into awaitedType2
rbuckton Mar 10, 2020
d9ff357
Switch awaited type params to 'unreliable' variance
rbuckton Mar 10, 2020
a980ffa
fix typo in inferTypes
rbuckton Mar 11, 2020
eaff435
Merge branch 'master' into awaitedType2
rbuckton Mar 16, 2020
17d7981
LKG without syntax in lib
rbuckton Mar 16, 2020
53e0aaa
LKG with new syntax in lib
rbuckton Mar 16, 2020
3164a0c
Add 'strictAwaitedTypes' flag
rbuckton Mar 17, 2020
8a5ed7f
Merge branch 'master' into awaitedType2
rbuckton Mar 17, 2020
ec879a5
Treat strictAwaitedTypes as strict-mode flag
rbuckton Mar 17, 2020
76ae7c8
Rename TAll, remove duplicate definition of 'race'
rbuckton Mar 17, 2020
bc83e41
Apply suggestions from code review
rbuckton Mar 18, 2020
a9aa2fd
Merge branch 'master' into awaitedType2
rbuckton Mar 18, 2020
a005b9c
Fix inference priority
rbuckton Mar 19, 2020
8bbcefd
Update comment to isGenericAwaitableType
rbuckton Mar 19, 2020
04c3b6c
Add overloads for then/catch to Promise
rbuckton Mar 20, 2020
5abdee7
Add inference heuristic for T | PromiseLike<T> (for any PromiseLike)
rbuckton Mar 20, 2020
b9f29c7
Remove strictAwaitedTypes flag
rbuckton Mar 20, 2020
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
Next Next commit
Add the 'awaited' type operator
  • Loading branch information
rbuckton committed Jan 4, 2020
commit 1ffda9fcdc837cf862a726ab0a1e747052f55ec7
7 changes: 6 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/built/local/**
/tests/**
/lib/**
/src/lib/*.generated.d.ts
/src/lib/*.generated.d.ts

# TODO: Remove the following once typescript-eslint supports `awaited`:
/src/lib/es5.d.ts
/src/lib/es2015.iterable.d.ts
/src/lib/es2015.promise.d.ts
150 changes: 130 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3938,6 +3938,11 @@ namespace ts {
context.approximateLength += 2;
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
}
if (type.flags & TypeFlags.Awaited) {
const awaitedTypeNode = typeToTypeNodeHelper((<AwaitedType>type).typeVariable, context);
context.approximateLength += 9;
return createTypeOperatorNode(SyntaxKind.AwaitedKeyword, awaitedTypeNode);
}
if (type.flags & TypeFlags.Conditional) {
const checkTypeNode = typeToTypeNodeHelper((<ConditionalType>type).checkType, context);
const saveInferTypeParameters = context.inferTypeParameters;
Expand Down Expand Up @@ -9739,6 +9744,10 @@ namespace ts {
constraintDepth--;
return result;
}
if (t.flags & TypeFlags.Awaited) {
const basePromiseType = getBaseConstraint((<AwaitedType>t).typeVariable);
return basePromiseType ? getAwaitedType(basePromiseType) : undefined;
}
if (t.flags & TypeFlags.Substitution) {
return getBaseConstraint((<SubstitutionType>t).substitute);
}
Expand Down Expand Up @@ -11967,6 +11976,9 @@ namespace ts {
case SyntaxKind.ReadonlyKeyword:
links.resolvedType = getTypeFromTypeNode(node.type);
break;
case SyntaxKind.AwaitedKeyword:
links.resolvedType = getAwaitedType(getTypeFromTypeNode(node.type)) ?? unknownType;
break;
default:
throw Debug.assertNever(node.operator);
}
Expand Down Expand Up @@ -13452,6 +13464,9 @@ namespace ts {
if (flags & TypeFlags.Conditional) {
return getConditionalTypeInstantiation(<ConditionalType>type, combineTypeMappers((<ConditionalType>type).mapper, mapper));
}
if (flags & TypeFlags.Awaited) {
return getAwaitedType(instantiateType((<AwaitedType>type).typeVariable, mapper)) ?? unknownType;
}
if (flags & TypeFlags.Substitution) {
const maybeVariable = instantiateType((<SubstitutionType>type).typeVariable, mapper);
if (maybeVariable.flags & TypeFlags.TypeVariable) {
Expand Down Expand Up @@ -15342,6 +15357,15 @@ namespace ts {
}
}
}
else if (target.flags & TypeFlags.Awaited && source.flags & TypeFlags.Awaited) {
// An `awaited S` is related to an `awaited T` if `S` is related to `T`:
//
// S <: T ⇒ awaited S <: awaited T
//
if (result = isRelatedTo((<AwaitedType>source).typeVariable, (<AwaitedType>target).typeVariable, reportErrors)) {
return result;
}
}
else if (isGenericMappedType(target)) {
// A source type T is related to a target type { [P in X]: T[P] }
const template = getTemplateTypeFromMappedType(target);
Expand Down Expand Up @@ -15456,6 +15480,21 @@ namespace ts {
}
}
}
else if (source.flags & TypeFlags.Awaited) {
// An `awaited S` is related to `T` if `awaited C` is related to `T`, where `C` is the
// constraint of `S`:
//
// S <: C ^ awaited C <: T ⇒ awaited S <: T
//
// For example `awaited Promise<number>` is assignable to `number`.
const constraint = getConstraintOfType((<AwaitedType>source).typeVariable);
const awaitedConstraint = constraint && getAwaitedType(constraint);
if (awaitedConstraint) {
if (result = isRelatedTo(awaitedConstraint, target, reportErrors)) {
return result;
}
}
}
else {
// An empty object type is related to any mapped type that includes a '?' modifier.
if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) {
Expand Down Expand Up @@ -17686,13 +17725,20 @@ namespace ts {
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target));
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
}
else if (source.flags & TypeFlags.Awaited && target.flags && TypeFlags.Awaited) {
inferFromTypes((<AwaitedType>source).typeVariable, (<AwaitedType>target).typeVariable);
}
else if (target.flags & TypeFlags.Conditional) {
const savePriority = priority;
priority |= contravariant ? InferencePriority.ContravariantConditional : 0;
const targetTypes = [getTrueTypeFromConditionalType(<ConditionalType>target), getFalseTypeFromConditionalType(<ConditionalType>target)];
inferToMultipleTypes(source, targetTypes, target.flags);
priority = savePriority;
}
else if (target.flags & TypeFlags.Awaited) {
const targetTypes = [(<AwaitedType>target).typeVariable, createPromiseLikeType((<AwaitedType>target).typeVariable)];
inferToMultipleTypes(source, targetTypes, target.flags);
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, target.flags);
}
Expand Down Expand Up @@ -21138,7 +21184,10 @@ namespace ts {
const contextualReturnType = getContextualReturnType(func);
if (contextualReturnType) {
if (functionFlags & FunctionFlags.Async) { // Async function
const contextualAwaitedType = getAwaitedTypeOfPromise(contextualReturnType);
let contextualAwaitedType = getAwaitedTypeOfPromise(contextualReturnType);
if (contextualAwaitedType && contextualAwaitedType.flags & TypeFlags.Awaited) {
contextualAwaitedType = (<AwaitedType>contextualAwaitedType).typeVariable;
}
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
}
return contextualReturnType; // Regular function
Expand All @@ -21150,7 +21199,10 @@ namespace ts {
function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined {
const contextualType = getContextualType(node);
if (contextualType) {
const contextualAwaitedType = getAwaitedType(contextualType);
let contextualAwaitedType = getAwaitedType(contextualType);
if (contextualAwaitedType && contextualAwaitedType.flags & TypeFlags.Awaited) {
contextualAwaitedType = (<AwaitedType>contextualAwaitedType).typeVariable;
}
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
}
return undefined;
Expand Down Expand Up @@ -29207,9 +29259,9 @@ 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(promise: Type, errorNode?: Node): Type | undefined {
function getPromisedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined {
//
// { // promise
// { // type
// then( // thenFunction
// onfulfilled: ( // onfulfilledParameterType
// value: T // valueParameterType
Expand All @@ -29218,20 +29270,21 @@ namespace ts {
// }
//

if (isTypeAny(promise)) {
if (isTypeAny(type)) {
return undefined;
}

const typeAsPromise = <PromiseOrAwaitableType>promise;
const typeAsPromise = <PromiseOrAwaitableType>type;
if (typeAsPromise.promisedTypeOfPromise) {
return typeAsPromise.promisedTypeOfPromise;
}

if (isReferenceToType(promise, getGlobalPromiseType(/*reportErrors*/ false))) {
return typeAsPromise.promisedTypeOfPromise = getTypeArguments(<GenericType>promise)[0];
if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalPromiseLikeType(/*reportErrors*/ false))) {
return typeAsPromise.promisedTypeOfPromise = getTypeArguments(<GenericType>type)[0];
}

const thenFunction = getTypeOfPropertyOfType(promise, "then" as __String)!; // TODO: GH#18217
const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217
if (isTypeAny(thenFunction)) {
return undefined;
}
Expand Down Expand Up @@ -29272,32 +29325,89 @@ namespace ts {
return awaitedType || errorType;
}

/**
* Gets or creates an `awaited T` type for a type variable.
*
* The "awaited type" of a type variable cannot be determined until it is instantiated. As
* a result, an `AwaitedType` for the type variable is created that can be instantiated
* or related later.
*/
function getAwaitedTypeForGenericType(type: TypeVariable) {
if (!type.resolvedAwaitedType) {
type.resolvedAwaitedType = <AwaitedType>createType(TypeFlags.Awaited);
type.resolvedAwaitedType.typeVariable = type;
}
return type.resolvedAwaitedType;
}

/**
* Gets the "awaited type" of a type.
*
* The "awaited type" of an expression is its "promised type" if the expression is a
* Promise-like type; otherwise, it is the type of the expression. If the "promised
* type" is itself a Promise-like, the "promised type" is recursively unwrapped until a
* non-promise type is found.
*
* This is used to reflect the runtime behavior of the `await` keyword and the `awaited T`
* type.
*/
function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined {
const typeAsAwaitable = <PromiseOrAwaitableType>type;
if (typeAsAwaitable.awaitedTypeOfType) {
return typeAsAwaitable.awaitedTypeOfType;
}

if (isTypeAny(type)) {
return typeAsAwaitable.awaitedTypeOfType = type;
return 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));
}
// For a union, get a union of the awaited types of each constituent.
//
// For example:
//
// awaited (number | string) -> number | string
// awaited (number | Promise<string>) -> number | string
// awaited (T | string) -> awaited T | string
// awaited (T | Promise<string>) -> awaited T | string
// awaited (T | Promise<never>) -> awaited T
// awaited (T | U) -> awaited T | awaited U
//
return typeAsAwaitable.awaitedTypeOfType =
mapType(type, errorNode ? constituentType => getAwaitedTypeWorker(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeWorker);
}

if (!types) {
return undefined;
}
function getAwaitedTypeWorker(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined {
// If the type is already an awaited type, return it.
//
// For example:
//
// awaited T -> awaited T
//
if (type.flags & TypeFlags.Awaited) {
return type;
}

return typeAsAwaitable.awaitedTypeOfType = getUnionType(types);
// We cannot resolve the awaited type for a type variable until it is instantiated. As
// such, we create an `awaited T` type that can either be instantiated or related later.
//
// For example:
//
// T -> awaited T
//
if (maybeTypeOfKind(type, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) {
return getAwaitedTypeForGenericType(<TypeVariable>type);
}

const typeAsAwaitable = <PromiseOrAwaitableType>type;

// Use the cached type if already computed.
if (typeAsAwaitable.awaitedTypeOfType) {
return typeAsAwaitable.awaitedTypeOfType;
}

const promisedType = getPromisedTypeOfPromise(type);
if (promisedType) {
if (type.id === promisedType.id || awaitedTypeStack.indexOf(promisedType.id) >= 0) {
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
// 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
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/factoryPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -936,8 +936,8 @@ namespace ts {
}

export function createTypeOperatorNode(type: TypeNode): TypeOperatorNode;
export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword, type: TypeNode): TypeOperatorNode;
export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | TypeNode, type?: TypeNode) {
export function createTypeOperatorNode(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.AwaitedKeyword, type: TypeNode): TypeOperatorNode;
export function createTypeOperatorNode(operatorOrType: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.AwaitedKeyword | TypeNode, type?: TypeNode) {
const node = createSynthesizedNode(SyntaxKind.TypeOperator) as TypeOperatorNode;
node.operator = typeof operatorOrType === "number" ? operatorOrType : SyntaxKind.KeyOfKeyword;
node.type = parenthesizeElementTypeMember(typeof operatorOrType === "number" ? type! : operatorOrType);
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3205,7 +3205,7 @@ namespace ts {
return finishNode(postfix);
}

function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) {
function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.AwaitedKeyword) {
const node = <TypeOperatorNode>createNode(SyntaxKind.TypeOperator);
parseExpected(operator);
node.operator = operator;
Expand All @@ -3228,6 +3228,7 @@ namespace ts {
case SyntaxKind.KeyOfKeyword:
case SyntaxKind.UniqueKeyword:
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.AwaitedKeyword:
return parseTypeOperator(operator);
case SyntaxKind.InferKeyword:
return parseInferType();
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ namespace ts {
yield: SyntaxKind.YieldKeyword,
async: SyntaxKind.AsyncKeyword,
await: SyntaxKind.AwaitKeyword,
awaited: SyntaxKind.AwaitedKeyword,
of: SyntaxKind.OfKeyword,
};

Expand Down
16 changes: 14 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ namespace ts {
| SyntaxKind.YieldKeyword
| SyntaxKind.AsyncKeyword
| SyntaxKind.AwaitKeyword
| SyntaxKind.AwaitedKeyword
| SyntaxKind.OfKeyword;

export type JsxTokenSyntaxKind =
Expand Down Expand Up @@ -258,6 +259,7 @@ namespace ts {
AnyKeyword,
AsyncKeyword,
AwaitKeyword,
AwaitedKeyword,
BooleanKeyword,
ConstructorKeyword,
DeclareKeyword,
Expand Down Expand Up @@ -1314,7 +1316,7 @@ namespace ts {

export interface TypeOperatorNode extends TypeNode {
kind: SyntaxKind.TypeOperator;
operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword;
operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword | SyntaxKind.AwaitedKeyword;
type: TypeNode;
}

Expand Down Expand Up @@ -4265,6 +4267,7 @@ namespace ts {
Conditional = 1 << 24, // T extends U ? X : Y
Substitution = 1 << 25, // Type parameter substitution
NonPrimitive = 1 << 26, // intrinsic object type
Awaited = 1 << 27, // awaited T

/* @internal */
AnyOrUnknown = Any | Unknown,
Expand Down Expand Up @@ -4294,7 +4297,7 @@ namespace ts {
UnionOrIntersection = Union | Intersection,
StructuredType = Object | Union | Intersection,
TypeVariable = TypeParameter | IndexedAccess,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution,
InstantiableNonPrimitive = TypeVariable | Conditional | Substitution | Awaited,
InstantiablePrimitive = Index,
Instantiable = InstantiableNonPrimitive | InstantiablePrimitive,
StructuredOrInstantiable = StructuredType | Instantiable,
Expand Down Expand Up @@ -4546,6 +4549,8 @@ namespace ts {
resolvedBaseConstraint: Type;
/* @internal */
couldContainTypeVariables: boolean;
/* @internal */
resolvedAwaitedType?: AwaitedType;
}

export interface UnionType extends UnionOrIntersectionType {
Expand Down Expand Up @@ -4642,6 +4647,8 @@ namespace ts {
resolvedIndexType?: IndexType;
/* @internal */
resolvedStringIndexType?: IndexType;
/* @internal */
resolvedAwaitedType?: AwaitedType;
}

// Type parameters (TypeFlags.TypeParameter)
Expand Down Expand Up @@ -4722,6 +4729,11 @@ namespace ts {
substitute: Type; // Type to substitute for type parameter
}

// awaited T (TypeFlags.Awaited)
export interface AwaitedType extends InstantiableType {
typeVariable: TypeVariable;
}

/* @internal */
export const enum JsxReferenceKind {
Component,
Expand Down
Loading