From fcd52d104e788c7eabf2af954695beb11ebf8adf Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Aug 2022 13:59:22 -0700 Subject: [PATCH 1/5] feat: add isAsync and isAsterisk to function expressions and isOptional to ElementAccessExpr --- src/compile.ts | 32 ++++++++++++++++++++++----- src/declaration.ts | 54 +++++++++++++++++++++++++++++++++++++++++++-- src/expression.ts | 55 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 131 insertions(+), 10 deletions(-) diff --git a/src/compile.ts b/src/compile.ts index c732d087..330ecd2d 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -309,6 +309,29 @@ export function compile( ) ), body, + // isAsync for Functions, Arrows and Methods + ...(ts.isFunctionDeclaration(impl) || + ts.isFunctionExpression(impl) || + ts.isArrowFunction(impl) || + ts.isMethodDeclaration(impl) + ? [ + impl.modifiers?.find( + (mod) => mod.kind === ts.SyntaxKind.AsyncKeyword + ) + ? ts.factory.createTrue() + : ts.factory.createFalse(), + ] + : []), + // isAsterisk for Functions and Methods + ...(ts.isFunctionDeclaration(impl) || + ts.isFunctionExpression(impl) || + ts.isMethodDeclaration(impl) + ? [ + impl.asteriskToken + ? ts.factory.createTrue() + : ts.factory.createFalse(), + ] + : []), ]); }); @@ -425,14 +448,13 @@ export function compile( ? ts.factory.createTrue() : ts.factory.createFalse(), ]); - } else if (ts.isElementAccessExpression(node)) { - const type = checker.getTypeAtLocation(node.argumentExpression); + } else if (ts.isElementAccessChain(node)) { return newExpr(NodeKind.ElementAccessExpr, [ toExpr(node.expression, scope), toExpr(node.argumentExpression, scope), - type - ? ts.factory.createStringLiteral(checker.typeToString(type)) - : ts.factory.createIdentifier("undefined"), + node.questionDotToken + ? ts.factory.createTrue() + : ts.factory.createFalse(), ]); } else if (ts.isVariableStatement(node)) { return newExpr(NodeKind.VariableStmt, [ diff --git a/src/declaration.ts b/src/declaration.ts index a67c2a0b..55d7903d 100644 --- a/src/declaration.ts +++ b/src/declaration.ts @@ -79,12 +79,40 @@ export class MethodDecl extends BaseDecl { constructor( readonly name: PropName, readonly parameters: ParameterDecl[], - readonly body: BlockStmt + readonly body: BlockStmt, + + /** + * true if this function has an `async` modifier + * ```ts + * class Foo { + * async foo() {} + * + * // asterisk can co-exist + * async *foo() {} + * } + * ``` + */ + readonly isAsync: boolean, + /** + * true if this function has an `*` modifier + * + * ```ts + * class Foo { + * foo*() {} + * + * // async can co-exist + * async *foo() {} + * } + * ``` + */ + readonly isAsterisk: boolean ) { super(NodeKind.MethodDecl, arguments); this.ensure(name, "name", NodeKind.PropName); this.ensureArrayOf(parameters, "parameters", [NodeKind.ParameterDecl]); this.ensure(body, "body", [NodeKind.BlockStmt]); + this.ensure(isAsync, "isAsync", ["boolean"]); + this.ensure(isAsterisk, "isAsterisk", ["boolean"]); } } @@ -142,12 +170,34 @@ export class FunctionDecl< // according to the spec, name is mandatory on a FunctionDecl and FunctionExpr readonly name: string | undefined, readonly parameters: ParameterDecl[], - readonly body: BlockStmt + readonly body: BlockStmt, + /** + * true if this function has an `async` modifier + * ```ts + * async function foo() {} + * // asterisk can co-exist + * async function *foo() {} + * ``` + */ + readonly isAsync: boolean, + /** + * true if this function has an `*` modifier + * + * ```ts + * function foo*() {} + * + * // async can co-exist + * async function *foo() {} + * ``` + */ + readonly isAsterisk: boolean ) { super(NodeKind.FunctionDecl, arguments); this.ensure(name, "name", ["undefined", "string"]); this.ensureArrayOf(parameters, "parameters", [NodeKind.ParameterDecl]); this.ensure(body, "body", [NodeKind.BlockStmt]); + this.ensure(isAsync, "isAsync", ["boolean"]); + this.ensure(isAsterisk, "isAsterisk", ["boolean"]); } } diff --git a/src/expression.ts b/src/expression.ts index c49dad11..883ecce2 100644 --- a/src/expression.ts +++ b/src/expression.ts @@ -82,10 +82,21 @@ export class ArrowFunctionExpr< F extends AnyFunction = AnyFunction > extends BaseExpr { readonly _functionBrand?: F; - constructor(readonly parameters: ParameterDecl[], readonly body: BlockStmt) { + constructor( + readonly parameters: ParameterDecl[], + readonly body: BlockStmt, + /** + * true if this function has an `async` modifier + * ```ts + * async () => {} + * ``` + */ + readonly isAsync: boolean + ) { super(NodeKind.ArrowFunctionExpr, arguments); this.ensure(body, "body", [NodeKind.BlockStmt]); this.ensureArrayOf(parameters, "parameters", [NodeKind.ParameterDecl]); + this.ensure(isAsync, "isAsync", ["boolean"]); } } @@ -96,12 +107,34 @@ export class FunctionExpr< constructor( readonly name: string | undefined, readonly parameters: ParameterDecl[], - readonly body: BlockStmt + readonly body: BlockStmt, + /** + * true if this function has an `async` modifier + * ```ts + * async function foo() {} + * // asterisk can co-exist + * async function *foo() {} + * ``` + */ + readonly isAsync: boolean, + /** + * true if this function has an `*` modifier + * + * ```ts + * function foo*() {} + * + * // async can co-exist + * async function *foo() {} + * ``` + */ + readonly isAsterisk: boolean ) { super(NodeKind.FunctionExpr, arguments); this.ensure(name, "name", ["undefined", "string"]); this.ensureArrayOf(parameters, "parameters", [NodeKind.ParameterDecl]); this.ensure(body, "body", [NodeKind.BlockStmt]); + this.ensure(isAsync, "isAsync", ["boolean"]); + this.ensure(isAsterisk, "isAsterisk", ["boolean"]); } } @@ -160,6 +193,12 @@ export class PropAccessExpr extends BaseExpr { constructor( readonly expr: Expr, readonly name: Identifier | PrivateIdentifier, + /** + * Whether this is using optional chaining. + * ```ts + * a?.prop + * ``` + */ readonly isOptional: boolean ) { super(NodeKind.PropAccessExpr, arguments); @@ -169,7 +208,17 @@ export class PropAccessExpr extends BaseExpr { } export class ElementAccessExpr extends BaseExpr { - constructor(readonly expr: Expr, readonly element: Expr) { + constructor( + readonly expr: Expr, + readonly element: Expr, + /** + * Whether this is using optional chaining. + * ```ts + * a?.[element] + * ``` + */ + readonly isOptional: boolean + ) { super(NodeKind.ElementAccessExpr, arguments); this.ensure(expr, "expr", ["Expr"]); this.ensure(element, "element", ["Expr"]); From 66a5761cd5e6c8402daa79799f326d4d700b7713 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Aug 2022 16:09:50 -0700 Subject: [PATCH 2/5] fix: build --- src/asl.ts | 11 +++---- src/assert.ts | 24 +++++++++------ src/compile.ts | 64 +++++++++++++++++++++++++++------------ src/ensure.ts | 2 +- src/event-bridge/utils.ts | 8 +++-- src/node-kind.ts | 17 ++++++++--- src/node.ts | 2 +- src/step-function.ts | 9 +++--- src/vtl.ts | 9 ++++-- test/node.test.ts | 2 +- 10 files changed, 95 insertions(+), 53 deletions(-) diff --git a/src/asl.ts b/src/asl.ts index 5faf982f..1ea801e2 100644 --- a/src/asl.ts +++ b/src/asl.ts @@ -51,7 +51,7 @@ import { isForInStmt, isForOfStmt, isFunctionDecl, - isFunctionExpr, + isFunctionLike, isIdentifier, isIfStmt, isLabelledStmt, @@ -106,7 +106,6 @@ import { isGetAccessorDecl, isTaggedTemplateExpr, isOmittedExpr, - isFunctionLike, isQuasiString, } from "./guards"; import { @@ -1711,7 +1710,7 @@ export class ASL { const throwTransition = this.throw(expr); const callbackfn = expr.args[0].expr; - if (callbackfn !== undefined && isFunctionExpr(callbackfn)) { + if (callbackfn !== undefined && isFunctionLike(callbackfn)) { const callbackStates = this.evalStmt(callbackfn.body); return this.evalExpr( @@ -2480,7 +2479,7 @@ export class ASL { // detect the immediate for-loop closure surrounding this throw statement // because of how step function's Catch feature works, we need to check if the try // is inside or outside the closure - const mapOrParallelClosure = node.findParent(isFunctionExpr); + const mapOrParallelClosure = node.findParent(isFunctionLike); // catchClause or finallyBlock that will run upon throwing this error const catchOrFinally = node.throw(); @@ -2741,7 +2740,7 @@ export class ASL { expr: CallExpr & { expr: PropAccessExpr } ): ASLGraph.NodeResults { const predicate = expr.args[0]?.expr; - if (!(isFunctionExpr(predicate) || isArrowFunctionExpr(predicate))) { + if (!(isFunctionLike(predicate) || isArrowFunctionExpr(predicate))) { throw new SynthError( ErrorCodes.StepFunction_invalid_filter_syntax, `the 'predicate' argument of slice must be a function or arrow expression, found: ${predicate?.kindName}` @@ -4794,7 +4793,7 @@ function nodeToString( return `[${nodeToString(expr.expr)}]`; } else if (isElementAccessExpr(expr)) { return `${nodeToString(expr.expr)}[${nodeToString(expr.element)}]`; - } else if (isFunctionExpr(expr) || isArrowFunctionExpr(expr)) { + } else if (isFunctionLike(expr) || isArrowFunctionExpr(expr)) { return `function(${expr.parameters.map(nodeToString).join(", ")})`; } else if (isIdentifier(expr)) { return expr.name; diff --git a/src/assert.ts b/src/assert.ts index 17aa7ee2..1aaf8ede 100644 --- a/src/assert.ts +++ b/src/assert.ts @@ -69,18 +69,22 @@ export function assertConstantValue(val: any, message?: string): ConstantValue { ); } -export function assertNodeKind( +export function assertNodeKind( node: FunctionlessNode | undefined, - kind: Kind -): NodeInstance { - if (node?.kind !== kind) { - throw Error( - `Expected node of type ${getNodeKindName(kind)} and found ${ - node ? getNodeKindName(node.kind) : "undefined" - }` - ); + ...kinds: Kind +): NodeInstance { + if (node) { + for (const kind of kinds) { + if (node?.kind === kind) { + return >node; + } + } } - return >node; + throw Error( + `Expected node of type ${kinds.map(getNodeKindName).join(", ")} and found ${ + node ? getNodeKindName(node.kind) : "undefined" + }` + ); } // to prevent the closure serializer from trying to import all of functionless. diff --git a/src/compile.ts b/src/compile.ts index 330ecd2d..790616f0 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -296,21 +296,8 @@ export function compile( ]), ]); - return newExpr(type, [ - ...resolveFunctionName(), - ts.factory.createArrayLiteralExpression( - impl.parameters.map((param) => - newExpr(NodeKind.ParameterDecl, [ - toExpr(param.name, scope ?? impl), - ...(param.initializer - ? [toExpr(param.initializer, scope ?? impl)] - : []), - ]) - ) - ), - body, - // isAsync for Functions, Arrows and Methods - ...(ts.isFunctionDeclaration(impl) || + const isAsync = + ts.isFunctionDeclaration(impl) || ts.isFunctionExpression(impl) || ts.isArrowFunction(impl) || ts.isMethodDeclaration(impl) @@ -321,9 +308,10 @@ export function compile( ? ts.factory.createTrue() : ts.factory.createFalse(), ] - : []), - // isAsterisk for Functions and Methods - ...(ts.isFunctionDeclaration(impl) || + : []; + + const isAsterisk = + ts.isFunctionDeclaration(impl) || ts.isFunctionExpression(impl) || ts.isMethodDeclaration(impl) ? [ @@ -331,7 +319,25 @@ export function compile( ? ts.factory.createTrue() : ts.factory.createFalse(), ] - : []), + : []; + + return newExpr(type, [ + ...resolveFunctionName(), + ts.factory.createArrayLiteralExpression( + impl.parameters.map((param) => + newExpr(NodeKind.ParameterDecl, [ + toExpr(param.name, scope ?? impl), + ...(param.initializer + ? [toExpr(param.initializer, scope ?? impl)] + : []), + ]) + ) + ), + body, + // isAsync for Functions, Arrows and Methods + ...isAsync, + // isAsterisk for Functions and Methods + ...isAsterisk, ]); }); @@ -363,7 +369,9 @@ export function compile( ): ts.Expression { if (node === undefined) { return ts.factory.createIdentifier("undefined"); - } else if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) { + } else if (ts.isArrowFunction(node)) { + return toFunction(NodeKind.ArrowFunctionExpr, node, scope); + } else if (ts.isFunctionExpression(node)) { return toFunction(NodeKind.FunctionExpr, node, scope); } else if (ts.isExpressionStatement(node)) { return newExpr(NodeKind.ExprStmt, [toExpr(node.expression, scope)]); @@ -448,6 +456,14 @@ export function compile( ? ts.factory.createTrue() : ts.factory.createFalse(), ]); + } else if (ts.isPropertyAccessChain(node)) { + return newExpr(NodeKind.PropAccessExpr, [ + toExpr(node.expression, scope), + toExpr(node.name, scope), + node.questionDotToken + ? ts.factory.createTrue() + : ts.factory.createFalse(), + ]); } else if (ts.isElementAccessChain(node)) { return newExpr(NodeKind.ElementAccessExpr, [ toExpr(node.expression, scope), @@ -456,6 +472,14 @@ export function compile( ? ts.factory.createTrue() : ts.factory.createFalse(), ]); + } else if (ts.isElementAccessExpression(node)) { + return newExpr(NodeKind.ElementAccessExpr, [ + toExpr(node.expression, scope), + toExpr(node.argumentExpression, scope), + node.questionDotToken + ? ts.factory.createTrue() + : ts.factory.createFalse(), + ]); } else if (ts.isVariableStatement(node)) { return newExpr(NodeKind.VariableStmt, [ toExpr(node.declarationList, scope), diff --git a/src/ensure.ts b/src/ensure.ts index 0b028e4f..f708e139 100644 --- a/src/ensure.ts +++ b/src/ensure.ts @@ -59,7 +59,7 @@ export function ensure( nodeKind: NodeKind, item: any, fieldName: string, - assertions: Assert[] + assertions: Assert[] | readonly Assert[] ): asserts item is AssertionToInstance { if (!is(item, assertions)) { throw new Error( diff --git a/src/event-bridge/utils.ts b/src/event-bridge/utils.ts index 93692fa5..a69899fb 100644 --- a/src/event-bridge/utils.ts +++ b/src/event-bridge/utils.ts @@ -79,7 +79,11 @@ export const getPropertyAccessKeyFlatten = ( ): string | number => { if (isElementAccessExpr(expr)) { return getPropertyAccessKey( - new ElementAccessExpr(expr.expr, flattenExpression(expr.element, scope)) + new ElementAccessExpr( + expr.expr, + flattenExpression(expr.element, scope), + expr.isOptional + ) ); } return getPropertyAccessKey(expr); @@ -186,7 +190,7 @@ export const flattenExpression = (expr: Expr, scope: EventScope): Expr => { } return typeof key === "string" ? new PropAccessExpr(parent, new Identifier(key), false) - : new ElementAccessExpr(parent, new NumberLiteralExpr(key)); + : new ElementAccessExpr(parent, new NumberLiteralExpr(key), false); } else if (isComputedPropertyNameExpr(expr)) { return flattenExpression(expr.expr, scope); } else if (isArrayLiteralExpr(expr)) { diff --git a/src/node-kind.ts b/src/node-kind.ts index 0811bf14..6fbd085b 100644 --- a/src/node-kind.ts +++ b/src/node-kind.ts @@ -91,7 +91,7 @@ export namespace NodeKind { NodeKind.Identifier, NodeKind.ReferenceExpr, ...NodeKind.BindingPattern, - ]; + ] as const; export const ClassMember = [ NodeKind.ClassStaticBlockDecl, @@ -108,7 +108,7 @@ export namespace NodeKind { NodeKind.PropAssignExpr, NodeKind.SetAccessorDecl, NodeKind.SpreadAssignExpr, - ]; + ] as const; export const PropName = [ NodeKind.Identifier, @@ -116,9 +116,18 @@ export namespace NodeKind { NodeKind.ComputedPropertyNameExpr, NodeKind.StringLiteralExpr, NodeKind.NumberLiteralExpr, - ]; + ] as const; - export const SwitchClause = [NodeKind.CaseClause, NodeKind.DefaultClause]; + export const SwitchClause = [ + NodeKind.CaseClause, + NodeKind.DefaultClause, + ] as const; + + export const FunctionLike = [ + NodeKind.FunctionDecl, + NodeKind.FunctionExpr, + NodeKind.ArrowFunctionExpr, + ] as const; } export type NodeKindName = typeof NodeKindNames[Kind]; diff --git a/src/node.ts b/src/node.ts index 0a013ac3..0e3b6a61 100644 --- a/src/node.ts +++ b/src/node.ts @@ -134,7 +134,7 @@ export abstract class BaseNode< protected ensure( item: any, fieldName: string, - assertion: Assert[] + assertion: Assert[] | readonly Assert[] ): asserts item is AssertionToInstance { return ensure(this.kind, item, fieldName, assertion); } diff --git a/src/step-function.ts b/src/step-function.ts index 3f48e8d2..7f66d643 100644 --- a/src/step-function.ts +++ b/src/step-function.ts @@ -20,14 +20,13 @@ import { makeEventBusIntegration, } from "./event-bridge/event-bus"; import { Event } from "./event-bridge/types"; -import { CallExpr, FunctionExpr } from "./expression"; +import { CallExpr } from "./expression"; import { NativeIntegration } from "./function"; import { PrewarmClients } from "./function-prewarm"; import { isBindingPattern, isComputedPropertyNameExpr, isErr, - isFunctionExpr, isFunctionLike, isGetAccessorDecl, isIdentifier, @@ -319,7 +318,7 @@ export namespace $SFN { function mapOrForEach(call: CallExpr, context: ASL) { const callbackfn = call.args.length === 3 ? call.args[2]?.expr : call.args[1]?.expr; - if (callbackfn === undefined || !isFunctionExpr(callbackfn)) { + if (callbackfn === undefined || !isFunctionLike(callbackfn)) { throw new Error("missing callbackfn in $SFN.map"); } const callbackStates = context.evalStmt(callbackfn.body); @@ -434,8 +433,8 @@ export namespace $SFN { }> >("parallel", { asl(call, context) { - const paths = call.args.map((arg): FunctionExpr => { - if (isFunctionExpr(arg.expr)) { + const paths = call.args.map((arg): FunctionLike => { + if (isFunctionLike(arg.expr)) { return arg.expr; } else { throw new Error("each parallel path must be an inline FunctionExpr"); diff --git a/src/vtl.ts b/src/vtl.ts index 45581ba9..364171f7 100644 --- a/src/vtl.ts +++ b/src/vtl.ts @@ -459,7 +459,10 @@ export abstract class VTL { // list.reduce((result: string[], next) => [...result, next], []); // list.reduce((result, next) => [...result, next]); - const fn = assertNodeKind(node.args[0]?.expr, NodeKind.FunctionExpr); + const fn = assertNodeKind( + node.args[0]?.expr, + ...NodeKind.FunctionLike + ); const initialValue = node.args[1]; // (previousValue: string[], currentValue: string, currentIndex: number, array: string[]) @@ -1024,7 +1027,7 @@ export abstract class VTL { this.evalDecl(array, list); } - const fn = assertNodeKind(call.args[0]?.expr, NodeKind.FunctionExpr); + const fn = assertNodeKind(call.args[0]?.expr, ...NodeKind.FunctionLike); const tmp = returnVariable ? returnVariable : this.newLocalVarName(); @@ -1099,7 +1102,7 @@ export abstract class VTL { * Returns the [value, index, array] arguments if this CallExpr is a `forEach` or `map` call. */ const getMapForEachArgs = (call: CallExpr) => { - return assertNodeKind(call.args[0].expr, NodeKind.FunctionExpr).parameters; + return assertNodeKind(call.args[0].expr, ...NodeKind.FunctionLike).parameters; }; // to prevent the closure serializer from trying to import all of functionless. diff --git a/test/node.test.ts b/test/node.test.ts index 37ac144e..71fc14eb 100644 --- a/test/node.test.ts +++ b/test/node.test.ts @@ -24,7 +24,7 @@ test("node.exit() from catch surrounded by while", () => { new BlockStmt([new TryStmt(new BlockStmt([]), catchClause)]) ); - new FunctionDecl("name", [], new BlockStmt([whileStmt])); + new FunctionDecl("name", [], new BlockStmt([whileStmt]), false, false); const exit = catchClause.exit(); From 88dd00e5fd24ad08c26732a480ecb31a01eb463c Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Aug 2022 19:03:05 -0700 Subject: [PATCH 3/5] chore: feedback --- src/asl.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/asl.ts b/src/asl.ts index 7f0d2c89..a43a2bce 100644 --- a/src/asl.ts +++ b/src/asl.ts @@ -23,7 +23,6 @@ import { isArgument, isArrayBinding, isArrayLiteralExpr, - isArrowFunctionExpr, isAwaitExpr, isBinaryExpr, isBindingElem, @@ -2708,7 +2707,7 @@ export class ASL { expr: CallExpr & { expr: PropAccessExpr } ): ASLGraph.NodeResults { const predicate = expr.args[0]?.expr; - if (!(isFunctionLike(predicate) || isArrowFunctionExpr(predicate))) { + if (!isFunctionLike(predicate)) { throw new SynthError( ErrorCodes.StepFunction_invalid_filter_syntax, `the 'predicate' argument of slice must be a function or arrow expression, found: ${predicate?.kindName}` @@ -4738,7 +4737,7 @@ function nodeToString( return `[${nodeToString(expr.expr)}]`; } else if (isElementAccessExpr(expr)) { return `${nodeToString(expr.expr)}[${nodeToString(expr.element)}]`; - } else if (isFunctionLike(expr) || isArrowFunctionExpr(expr)) { + } else if (isFunctionLike(expr)) { return `function(${expr.parameters.map(nodeToString).join(", ")})`; } else if (isIdentifier(expr)) { return expr.name; From c234a75b3b741786b646b3c532783c89e2742869 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Aug 2022 19:23:51 -0700 Subject: [PATCH 4/5] chore: dummy commit --- src/asl.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/asl.ts b/src/asl.ts index a43a2bce..cfb6d878 100644 --- a/src/asl.ts +++ b/src/asl.ts @@ -45,6 +45,7 @@ import { isElementAccessExpr, isEmptyStmt, isErr, + // isExpr, isExprStmt, isForInStmt, From c793f30151d1302df55280619221391ceff8c5d9 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 3 Aug 2022 19:24:33 -0700 Subject: [PATCH 5/5] chore: remove dummy commit --- src/asl.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/asl.ts b/src/asl.ts index cfb6d878..a43a2bce 100644 --- a/src/asl.ts +++ b/src/asl.ts @@ -45,7 +45,6 @@ import { isElementAccessExpr, isEmptyStmt, isErr, - // isExpr, isExprStmt, isForInStmt,