Skip to content

Add the 'awaited' type operator #35998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Mar 20, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
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
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ scripts/processDiagnosticMessages.js
scripts/produceLKG.js
scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js
scripts/generateLocalizedDiagnosticMessages.js
scripts/request-pr-review.js
scripts/*.js.map
scripts/typings/
coverage/
Expand Down
3 changes: 3 additions & 0 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const copyright = "CopyrightNotice.txt";
const cleanTasks = [];

const buildScripts = () => buildProject("scripts");
task("scripts", buildScripts);
task("scripts").description = "Builds files in the 'scripts' folder.";

const cleanScripts = () => cleanProject("scripts");
cleanTasks.push(cleanScripts);

Expand Down
50 changes: 50 additions & 0 deletions scripts/request-pr-review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference lib="esnext.asynciterable" />
/// <reference lib="es2015.promise" />
import octokit = require("@octokit/rest");
import Octokit = octokit.Octokit;
import minimist = require("minimist");

const options = minimist(process.argv.slice(2), {
string: ["token", "pull", "reviewer", "owner", "repo"],
alias: {
"pr": "pull",
},
default: {
token: process.env.GH_TOKEN,
pull: process.env.GH_PULL_NUMBER,
reviewer: process.env.REQUESTED_REVIEWER,
owner: "microsoft",
repo: "TypeScript"
}
});

if (!options.token || !options.pull || !options.reviewer || !options.owner || !options.repo) {
console.error("Invalid arguments");
process.exit();
}

const pull_number = +options.pull;
if (!isFinite(pull_number)) {
console.error("Invalid arguments");
process.exit();
}

const reviewers = Array.isArray(options.reviewer) ? options.reviewer : [options.reviewer];

async function main() {
const gh = new Octokit({ auth: options.token });
const response = await gh.pulls.createReviewRequest({
owner: options.owner,
repo: options.repo,
pull_number,
reviewers,
});
if (response.status === 201) {
console.log(`Added ${reviewers.join(", ")} to ${response.data.url}`);
}
else {
console.log(`Failed to add ${reviewers.join(", ")} to the pull request.`);
}
}

main().catch(console.error);
3 changes: 2 additions & 1 deletion scripts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"configurePrerelease.ts",
"buildProtocol.ts",
"produceLKG.ts",
"word2md.ts"
"word2md.ts",
"request-pr-review.ts"
]
}
154 changes: 134 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ namespace ts {
const literalTypes = createMap<LiteralType>();
const indexedAccessTypes = createMap<IndexedAccessType>();
const substitutionTypes = createMap<SubstitutionType>();
const awaitedTypes = createMap<AwaitedType>();
const evolvingArrayTypes: EvolvingArrayType[] = [];
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;

Expand Down Expand Up @@ -4225,6 +4226,11 @@ namespace ts {
context.approximateLength += 2;
return createIndexedAccessTypeNode(objectTypeNode, indexTypeNode);
}
if (type.flags & TypeFlags.Awaited) {
const awaitedTypeNode = typeToTypeNodeHelper((<AwaitedType>type).awaitedType, 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 @@ -10021,6 +10027,10 @@ namespace ts {
constraintDepth--;
return result;
}
if (t.flags & TypeFlags.Awaited) {
const basePromiseType = getBaseConstraint((<AwaitedType>t).awaitedType);
return basePromiseType ? getAwaitedType(basePromiseType) : undefined;
}
if (t.flags & TypeFlags.Substitution) {
return getBaseConstraint((<SubstitutionType>t).substitute);
}
Expand Down Expand Up @@ -12298,6 +12308,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 @@ -13817,6 +13830,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).awaitedType, mapper)) ?? unknownType;
}
if (flags & TypeFlags.Substitution) {
const maybeVariable = instantiateType((<SubstitutionType>type).typeVariable, mapper);
if (maybeVariable.flags & TypeFlags.TypeVariable) {
Expand Down Expand Up @@ -15715,6 +15731,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).awaitedType, (<AwaitedType>target).awaitedType, 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 @@ -15829,6 +15854,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).awaitedType);
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 @@ -18074,13 +18114,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).awaitedType, (<AwaitedType>target).awaitedType);
}
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).awaitedType, createPromiseLikeType((<AwaitedType>target).awaitedType)];
inferToMultipleTypes(source, targetTypes, target.flags);
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
inferToMultipleTypes(source, (<UnionOrIntersectionType>target).types, target.flags);
}
Expand Down Expand Up @@ -21479,7 +21526,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).awaitedType;
}
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
}
return contextualReturnType; // Regular function
Expand All @@ -21491,7 +21541,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).awaitedType;
}
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
}
return undefined;
Expand Down Expand Up @@ -29668,9 +29721,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 @@ -29679,20 +29732,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 @@ -29733,32 +29787,92 @@ namespace ts {
return awaitedType || errorType;
}

/**
* Gets or creates an `awaited T` type for a generic type.
*
* The "awaited type" of a generic type cannot be determined until it is instantiated. As
* a result, an `AwaitedType` for the generic type is created that can be instantiated
* or related later.
*/
function getAwaitedTypeForGenericType(type: InstantiableType) {
const typeId = "" + type.id;
let awaitedType = awaitedTypes.get(typeId);
if (!awaitedType) {
awaitedType = <AwaitedType>createType(TypeFlags.Awaited);
awaitedType.awaitedType = type;
awaitedTypes.set(typeId, awaitedType);
}
return awaitedType;
}

/**
* 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 (isGenericObjectType(type)) {
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 @@ -939,8 +939,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 @@ -3239,7 +3239,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 @@ -3262,6 +3262,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
Loading