Skip to content

Commit

Permalink
Support using and await using declarations (#54505)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton authored Jun 22, 2023
1 parent d90eb0d commit f9cf821
Show file tree
Hide file tree
Showing 674 changed files with 29,610 additions and 2,826 deletions.
2 changes: 2 additions & 0 deletions src/compiler/_namespaces/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export * from "../visitorPublic";
export * from "../sourcemap";
export * from "../transformers/utilities";
export * from "../transformers/destructuring";
export * from "../transformers/classThis";
export * from "../transformers/namedEvaluation";
export * from "../transformers/taggedTemplate";
export * from "../transformers/ts";
export * from "../transformers/classFields";
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3727,7 +3727,7 @@ function isExecutableStatement(s: Statement): boolean {
// Don't remove statements that can validly be used before they appear.
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) &&
// `var x;` may declare a variable used above
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer));
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.BlockScoped)) && s.declarationList.declarations.some(d => !d.initializer));
}

function isPurelyTypeDeclaration(s: Statement): boolean {
Expand Down
271 changes: 200 additions & 71 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ const libEntries: [string, string][] = [
["esnext.symbol", "lib.es2019.symbol.d.ts"],
["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"],
["esnext.intl", "lib.esnext.intl.d.ts"],
["esnext.disposable", "lib.esnext.disposable.d.ts"],
["esnext.bigint", "lib.es2020.bigint.d.ts"],
["esnext.string", "lib.es2022.string.d.ts"],
["esnext.promise", "lib.es2021.promise.d.ts"],
Expand Down
56 changes: 48 additions & 8 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,18 +451,14 @@
"category": "Error",
"code": 1149
},
"'const' declarations must be initialized.": {
"'{0}' declarations must be initialized.": {
"category": "Error",
"code": 1155
},
"'const' declarations can only be declared inside a block.": {
"'{0}' declarations can only be declared inside a block.": {
"category": "Error",
"code": 1156
},
"'let' declarations can only be declared inside a block.": {
"category": "Error",
"code": 1157
},
"Unterminated template literal.": {
"category": "Error",
"code": 1160
Expand Down Expand Up @@ -1601,6 +1597,26 @@
"category": "Error",
"code": 1490
},
"'{0}' modifier cannot appear on a 'using' declaration.": {
"category": "Error",
"code": 1491
},
"'{0}' declarations may not have binding patterns.": {
"category": "Error",
"code": 1492
},
"The left-hand side of a 'for...in' statement cannot be a 'using' declaration.": {
"category": "Error",
"code": 1493
},
"The left-hand side of a 'for...in' statement cannot be an 'await using' declaration.": {
"category": "Error",
"code": 1494
},
"'{0}' modifier cannot appear on an 'await using' declaration.": {
"category": "Error",
"code": 1495
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down Expand Up @@ -3623,6 +3639,26 @@
"category": "Error",
"code": 2849
},
"The initializer of a 'using' declaration must be either an object with a '[Symbol.dispose]()' method, or be 'null' or 'undefined'.": {
"category": "Error",
"code": 2850
},
"The initializer of an 'await using' declaration must be either an object with a '[Symbol.asyncDispose]()' or '[Symbol.dispose]()' method, or be 'null' or 'undefined'.": {
"category": "Error",
"code": 2851
},
"'await using' statements are only allowed within async functions and at the top levels of modules.": {
"category": "Error",
"code": 2852
},
"'await using' statements are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module.": {
"category": "Error",
"code": 2853
},
"Top-level 'await using' statements are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext', and the 'target' option is set to 'es2017' or higher.": {
"category": "Error",
"code": 2854
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down Expand Up @@ -7757,11 +7793,11 @@
"category": "Error",
"code": 18036
},
"Await expression cannot be used inside a class static block.": {
"'await' expression cannot be used inside a class static block.": {
"category": "Error",
"code": 18037
},
"'For await' loops cannot be used inside a class static block.": {
"'for await' loops cannot be used inside a class static block.": {
"category": "Error",
"code": 18038
},
Expand Down Expand Up @@ -7820,5 +7856,9 @@
"Its type '{0}' is not a valid JSX element type.": {
"category": "Error",
"code": 18053
},
"'await using' statements cannot be used inside a class static block.": {
"category": "Error",
"code": 18054
}
}
15 changes: 14 additions & 1 deletion src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,10 @@ import {
isUnparsedNode,
isUnparsedPrepend,
isUnparsedSource,
isVarAwaitUsing,
isVarConst,
isVariableStatement,
isVarUsing,
JSDoc,
JSDocAugmentsTag,
JSDocCallbackTag,
Expand Down Expand Up @@ -3685,7 +3687,18 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}

function emitVariableDeclarationList(node: VariableDeclarationList) {
writeKeyword(isLet(node) ? "let" : isVarConst(node) ? "const" : "var");
if (isVarAwaitUsing(node)) {
writeKeyword("await");
writeSpace();
writeKeyword("using");
}
else {
const head = isLet(node) ? "let" :
isVarConst(node) ? "const" :
isVarUsing(node) ? "using" :
"var";
writeKeyword(head);
}
writeSpace();
emitList(node, node.declarations, ListFormat.VariableDeclarationList);
}
Expand Down
91 changes: 89 additions & 2 deletions src/compiler/factory/emitHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ export interface EmitHelperFactory {
createClassPrivateFieldGetHelper(receiver: Expression, state: Identifier, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
createClassPrivateFieldSetHelper(receiver: Expression, state: Identifier, value: Expression, kind: PrivateIdentifierKind, f: Identifier | undefined): Expression;
createClassPrivateFieldInHelper(state: Identifier, receiver: Expression): Expression;
// 'using' helpers
createAddDisposableResourceHelper(envBinding: Expression, value: Expression, async: boolean): Expression;
createDisposeResourcesHelper(envBinding: Expression): Expression;
}

/** @internal */
Expand Down Expand Up @@ -183,7 +186,10 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
// Class Fields Helpers
createClassPrivateFieldGetHelper,
createClassPrivateFieldSetHelper,
createClassPrivateFieldInHelper
createClassPrivateFieldInHelper,
// 'using' helpers
createAddDisposableResourceHelper,
createDisposeResourcesHelper,
};

/**
Expand Down Expand Up @@ -666,6 +672,20 @@ export function createEmitHelperFactory(context: TransformationContext): EmitHel
context.requestEmitHelper(classPrivateFieldInHelper);
return factory.createCallExpression(getUnscopedHelperName("__classPrivateFieldIn"), /*typeArguments*/ undefined, [state, receiver]);
}

function createAddDisposableResourceHelper(envBinding: Expression, value: Expression, async: boolean): Expression {
context.requestEmitHelper(addDisposableResourceHelper);
return factory.createCallExpression(
getUnscopedHelperName("__addDisposableResource"),
/*typeArguments*/ undefined,
[envBinding, value, async ? factory.createTrue() : factory.createFalse()]
);
}

function createDisposeResourcesHelper(envBinding: Expression) {
context.requestEmitHelper(disposeResourcesHelper);
return factory.createCallExpression(getUnscopedHelperName("__disposeResources"), /*typeArguments*/ undefined, [envBinding]);
}
}

/** @internal */
Expand Down Expand Up @@ -1367,6 +1387,71 @@ export const classPrivateFieldInHelper: UnscopedEmitHelper = {
};`
};

/**
* @internal
*/
export const addDisposableResourceHelper: UnscopedEmitHelper = {
name: "typescript:addDisposableResource",
importName: "__addDisposableResource",
scoped: false,
text: `
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object") throw new TypeError("Object expected.");
var dispose;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};`
};

/**
* @internal
*/
export const disposeResourcesHelper: UnscopedEmitHelper = {
name: "typescript:disposeResources",
importName: "__disposeResources",
scoped: false,
text: `
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
function next() {
while (env.stack.length) {
var rec = env.stack.pop();
try {
var result = rec.dispose && rec.dispose.call(rec.value);
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
catch (e) {
fail(e);
}
}
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});`
};

let allUnscopedEmitHelpers: ReadonlyMap<string, UnscopedEmitHelper> | undefined;

/** @internal */
Expand Down Expand Up @@ -1399,7 +1484,9 @@ export function getAllUnscopedEmitHelpers() {
classPrivateFieldSetHelper,
classPrivateFieldInHelper,
createBindingHelper,
setModuleDefaultHelper
setModuleDefaultHelper,
addDisposableResourceHelper,
disposeResourcesHelper,
], helper => helper.name));
}

Expand Down
23 changes: 22 additions & 1 deletion src/compiler/factory/nodeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BindingOrAssignmentPattern,
Block,
cast,
ClassDeclaration,
ConciseBody,
Debug,
Expression,
Expand All @@ -17,6 +18,8 @@ import {
isBindingElement,
isBindingPattern,
isBlock,
isDefaultModifier,
isExportModifier,
isExpression,
isIdentifier,
isObjectBindingPattern,
Expand All @@ -39,6 +42,7 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
return {
convertToFunctionBlock,
convertToFunctionExpression,
convertToClassExpression,
convertToArrayAssignmentElement,
convertToObjectAssignmentElement,
convertToAssignmentPattern,
Expand All @@ -59,7 +63,7 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
function convertToFunctionExpression(node: FunctionDeclaration) {
if (!node.body) return Debug.fail(`Cannot convert a FunctionDeclaration without a body`);
const updated = factory.createFunctionExpression(
getModifiers(node),
getModifiers(node)?.filter(modifier => !isExportModifier(modifier) && !isDefaultModifier(modifier)),
node.asteriskToken,
node.name,
node.typeParameters,
Expand All @@ -75,6 +79,22 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
return updated;
}

function convertToClassExpression(node: ClassDeclaration) {
const updated = factory.createClassExpression(
node.modifiers?.filter(modifier => !isExportModifier(modifier) && !isDefaultModifier(modifier)),
node.name,
node.typeParameters,
node.heritageClauses,
node.members
);
setOriginalNode(updated, node);
setTextRange(updated, node);
if (getStartsOnNewLine(node)) {
setStartsOnNewLine(updated, /*newLine*/ true);
}
return updated;
}

function convertToArrayAssignmentElement(element: ArrayBindingOrAssignmentElement) {
if (isBindingElement(element)) {
if (element.dotDotDotToken) {
Expand Down Expand Up @@ -163,6 +183,7 @@ export function createNodeConverters(factory: NodeFactory): NodeConverters {
export const nullNodeConverters: NodeConverters = {
convertToFunctionBlock: notImplemented,
convertToFunctionExpression: notImplemented,
convertToClassExpression: notImplemented,
convertToArrayAssignmentElement: notImplemented,
convertToObjectAssignmentElement: notImplemented,
convertToAssignmentPattern: notImplemented,
Expand Down
Loading

0 comments on commit f9cf821

Please sign in to comment.