Merge pull request #282 from pulumi/asyncawait

Support try/catch in Lumi and async/await in Node.js
This commit is contained in:
Luke Hoban 2017-07-07 14:44:59 -07:00 committed by GitHub
commit 1c8ad139f1
8 changed files with 139 additions and 25 deletions

View file

@ -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) {

View file

@ -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),

View file

@ -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"),

View file

@ -21,3 +21,5 @@ export class Number {
}
}
export class Promise<T> {
}

View file

@ -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)

View file

@ -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)

View file

@ -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) {

View file

@ -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 {