Merge pull request #282 from pulumi/asyncawait
Support try/catch in Lumi and async/await in Node.js
This commit is contained in:
commit
1c8ad139f1
|
@ -1874,15 +1874,6 @@ export class Transformer {
|
|||
}
|
||||
}
|
||||
|
||||
private transformDeclarationIdentifier(node: ts.DeclarationName): ast.Identifier {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.Identifier:
|
||||
return this.transformIdentifier(node);
|
||||
default:
|
||||
return contract.fail(`Unrecognized declaration identifier: ${ts.SyntaxKind[node.kind]}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async transformFunctionLikeCommon(node: ts.FunctionLikeDeclaration): Promise<FunctionLikeDeclaration> {
|
||||
if (!!node.asteriskToken) {
|
||||
this.diagnostics.push(this.dctx.newGeneratorsNotSupportedError(node.asteriskToken));
|
||||
|
@ -1926,7 +1917,7 @@ export class Transformer {
|
|||
isLocal: boolean, body?: ast.Block): Promise<FunctionLikeDeclaration> {
|
||||
// Ensure we are dealing with the supported subset of functions.
|
||||
if (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Async) {
|
||||
this.diagnostics.push(this.dctx.newAsyncNotSupportedError(node));
|
||||
this.diagnostics.push(this.dctx.newAsyncNotSupportedWarning(node));
|
||||
}
|
||||
|
||||
// First transform the name into an identifier. In the absence of a name, we will proceed under the assumption
|
||||
|
@ -2217,7 +2208,7 @@ export class Transformer {
|
|||
private async transformVariableDeclaration(node: ts.VariableDeclaration): Promise<VariableLikeDeclaration> {
|
||||
// TODO[pulumi/lumi#43]: parameters can be any binding name, including destructuring patterns. For now,
|
||||
// however, we only support the identifier forms.
|
||||
let name: ast.Identifier = this.transformDeclarationIdentifier(node.name);
|
||||
let name: ast.Identifier = this.transformBindingIdentifier(node.name);
|
||||
let initializer: ast.Expression | undefined;
|
||||
if (node.initializer) {
|
||||
initializer = await this.transformExpression(node.initializer);
|
||||
|
@ -2229,6 +2220,23 @@ export class Transformer {
|
|||
};
|
||||
}
|
||||
|
||||
private async transformVariableDeclarationToLocalVariable(
|
||||
node: ts.VariableDeclaration): Promise<ast.LocalVariable> {
|
||||
// Pluck out any decorators and store them in the metadata as attributes.
|
||||
let attributes: ast.Attribute[] | undefined = await this.transformDecorators(node.decorators);
|
||||
|
||||
// TODO[pulumi/lumi#43]: parameters can be any binding name, including destructuring patterns. For now,
|
||||
// however, we only support the identifier forms.
|
||||
let name: ast.Identifier = this.transformBindingIdentifier(node.name);
|
||||
|
||||
return {
|
||||
kind: ast.localVariableKind,
|
||||
name: name,
|
||||
type: await this.resolveTypeTokenFromTypeLike(node),
|
||||
attributes: attributes,
|
||||
};
|
||||
}
|
||||
|
||||
private async transformVariableDeclarationList(
|
||||
node: ts.VariableDeclarationList): Promise<VariableLikeDeclaration[]> {
|
||||
let decls: VariableLikeDeclaration[] = [];
|
||||
|
@ -2387,8 +2395,12 @@ export class Transformer {
|
|||
return notYetImplemented(node);
|
||||
}
|
||||
|
||||
private transformCatchClause(node: ts.CatchClause): ast.Statement {
|
||||
return notYetImplemented(node);
|
||||
private async transformCatchClause(node: ts.CatchClause): Promise<ast.TryCatchClause> {
|
||||
return this.withLocation(node, <ast.TryCatchClause>{
|
||||
kind: ast.tryCatchClauseKind,
|
||||
body: await this.transformBlock(node.block),
|
||||
exception: await this.transformVariableDeclarationToLocalVariable(node.variableDeclaration),
|
||||
});
|
||||
}
|
||||
|
||||
private transformContinueStatement(node: ts.ContinueStatement): ast.ContinueStatement {
|
||||
|
@ -2544,8 +2556,21 @@ export class Transformer {
|
|||
});
|
||||
}
|
||||
|
||||
private transformTryStatement(node: ts.TryStatement): ast.TryCatchFinally {
|
||||
return notYetImplemented(node);
|
||||
private async transformTryStatement(node: ts.TryStatement): Promise<ast.TryCatchFinally> {
|
||||
let catchClauses: ast.TryCatchClause[] = [];
|
||||
if (!!node.catchClause) {
|
||||
catchClauses.push(await this.transformCatchClause(node.catchClause));
|
||||
}
|
||||
let finallyClause: ast.Block | undefined = undefined;
|
||||
if (!!node.finallyBlock) {
|
||||
finallyClause = await this.transformBlock(node.finallyBlock);
|
||||
}
|
||||
return this.withLocation(node, <ast.TryCatchFinally>{
|
||||
kind: ast.tryCatchFinallyKind,
|
||||
tryClause: await this.transformBlock(node.tryBlock),
|
||||
catchClauses: catchClauses,
|
||||
finallyClause: finallyClause,
|
||||
});
|
||||
}
|
||||
|
||||
private async transformWhileStatement(node: ts.WhileStatement): Promise<ast.WhileStatement> {
|
||||
|
@ -2629,6 +2654,8 @@ export class Transformer {
|
|||
return this.transformArrayLiteralExpression(<ts.ArrayLiteralExpression>node);
|
||||
case ts.SyntaxKind.ArrowFunction:
|
||||
return await this.transformArrowFunction(<ts.ArrowFunction>node);
|
||||
case ts.SyntaxKind.AwaitExpression:
|
||||
return await this.transformAwaitExpression(<ts.AwaitExpression>node);
|
||||
case ts.SyntaxKind.BinaryExpression:
|
||||
return this.transformBinaryExpression(<ts.BinaryExpression>node);
|
||||
case ts.SyntaxKind.CallExpression:
|
||||
|
@ -2742,9 +2769,10 @@ export class Transformer {
|
|||
|
||||
// Transpile the arrow function to get it's JavaScript source text to store on the AST
|
||||
let arrowText = this.printer.printNode(ts.EmitHint.Expression, node, this.currentSourceFile!);
|
||||
let result = ts.transpileModule(arrowText, {
|
||||
let result = ts.transpileModule("return " + arrowText, {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.ES2015,
|
||||
noEmitHelpers: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -2758,6 +2786,45 @@ export class Transformer {
|
|||
});
|
||||
}
|
||||
|
||||
private async transformAwaitExpression(node: ts.AwaitExpression): Promise<ast.Expression> {
|
||||
// Async/await is not yet implemented in Lumi, but we want to defer the error
|
||||
// until runtime, so that async/await can be used on code executed on the inside
|
||||
// by Node.js.
|
||||
return this.withLocation(node, <ast.CastExpression>{
|
||||
kind: ast.castExpressionKind,
|
||||
type: <ast.TypeToken>{
|
||||
kind: ast.typeTokenKind,
|
||||
tok: tokens.dynamicType,
|
||||
},
|
||||
expression: <ast.InvokeFunctionExpression>{
|
||||
kind: ast.invokeFunctionExpressionKind,
|
||||
function: <ast.LambdaExpression>{
|
||||
kind: ast.lambdaExpressionKind,
|
||||
sourceLanguage: ".js",
|
||||
sourceText: "(function() { throw 'Async/Await not yet implemented.'});\n",
|
||||
parameters: [],
|
||||
body: <ast.Block>{
|
||||
kind: ast.blockKind,
|
||||
statements: [
|
||||
<ast.ExpressionStatement>{
|
||||
kind: ast.expressionStatementKind,
|
||||
expression: await this.transformExpression(node.expression),
|
||||
},
|
||||
<ast.ThrowStatement>{
|
||||
kind: ast.throwStatementKind,
|
||||
expression: <ast.StringLiteral>{
|
||||
kind: ast.stringLiteralKind,
|
||||
value: "Async/await not yet implemented.",
|
||||
raw: "Async/await not yet implemented.",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async transformBinaryExpression(node: ts.BinaryExpression): Promise<ast.Expression> {
|
||||
let op: ts.SyntaxKind = node.operatorToken.kind;
|
||||
if (op === ts.SyntaxKind.CommaToken) {
|
||||
|
|
|
@ -237,9 +237,9 @@ export class Context {
|
|||
};
|
||||
}
|
||||
|
||||
public newAsyncNotSupportedError(node: ts.Node): Diagnostic {
|
||||
public newAsyncNotSupportedWarning(node: ts.Node): Diagnostic {
|
||||
return {
|
||||
category: DiagnosticCategory.Error,
|
||||
category: DiagnosticCategory.Warning,
|
||||
code: 100,
|
||||
message: "Async functions are not supported in the LumiJS subset",
|
||||
loc: this.locationFrom(node),
|
||||
|
|
|
@ -85,14 +85,54 @@ function createJavaScriptLambda(functionName: string, role: Role, closure: Closu
|
|||
"function " + name + "() {\n" +
|
||||
" let __env = JSON.parse(process.env.LUMI_ENV_" + name + ");\n" +
|
||||
" with(__env) {\n" +
|
||||
" let __f = " + funcs[name].code +
|
||||
" return __f.apply(null, arguments);\n" +
|
||||
" let __f = (() => {" + funcs[name].code + "})();\n" +
|
||||
" return __f.apply(this, arguments);\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"\n";
|
||||
envObj["LUMI_ENV_" + name] = funcs[name].env;
|
||||
}
|
||||
|
||||
/*tslint:disable: max-line-length */
|
||||
str += `
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
}
|
||||
|
||||
function __generator(thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (_) try {
|
||||
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [0, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let lambda = new LambdaFunction(functionName, {
|
||||
code: new AssetArchive({
|
||||
"node_modules": new File("node_modules"),
|
||||
|
|
|
@ -21,3 +21,5 @@ export class Number {
|
|||
}
|
||||
}
|
||||
|
||||
export class Promise<T> {
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ type TryCatchFinally struct {
|
|||
StatementNode
|
||||
TryClause Statement `json:"tryClause"`
|
||||
CatchClauses *[]*TryCatchClause `json:"catchClauses,omitempty"`
|
||||
FinallyClause Statement `json:"finallyClause"`
|
||||
FinallyClause *Statement `json:"finallyClause,omitempty"`
|
||||
}
|
||||
|
||||
var _ Node = (*TryCatchFinally)(nil)
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
package ast
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/pulumi/lumi/pkg/util/contract"
|
||||
)
|
||||
|
||||
|
@ -118,7 +119,7 @@ func WalkChildren(v Visitor, node Node) {
|
|||
}
|
||||
}
|
||||
if n.FinallyClause != nil {
|
||||
Walk(v, n.FinallyClause)
|
||||
Walk(v, *n.FinallyClause)
|
||||
}
|
||||
case *TryCatchClause:
|
||||
Walk(v, n.Exception)
|
||||
|
|
|
@ -95,7 +95,11 @@ func decodeLocalVariableDeclaration(m mapper.Mapper,
|
|||
}
|
||||
|
||||
func decodeTryCatchFinally(m mapper.Mapper, obj map[string]interface{}) (*ast.TryCatchFinally, error) {
|
||||
return nil, nil
|
||||
var stmt ast.TryCatchFinally
|
||||
if err := m.Decode(obj, &stmt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &stmt, nil
|
||||
}
|
||||
|
||||
func decodeBreakStatement(m mapper.Mapper, obj map[string]interface{}) (*ast.BreakStatement, error) {
|
||||
|
|
|
@ -924,7 +924,7 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
|
|||
|
||||
// No matter the rt.Unwind instructions, be sure to invoke the finally part.
|
||||
if node.FinallyClause != nil {
|
||||
uwf := e.evalStatement(node.FinallyClause)
|
||||
uwf := e.evalStatement(*node.FinallyClause)
|
||||
|
||||
// Any rt.Unwind information from the finally block overrides the try rt.Unwind that was in flight.
|
||||
if uwf != nil {
|
||||
|
|
Loading…
Reference in a new issue