From 858ac40d158a6eece410c92d6cc49377d9693e0b Mon Sep 17 00:00:00 2001 From: Avik Chaudhuri Date: Tue, 2 Aug 2016 01:59:11 -0700 Subject: [PATCH] simple disjoint union perf optimization Summary: Previously, an object type O1 was checked against a disjoint union by picking each object type O2 in the union and checking *all* properties of O2 in O1. Unfortunately, this approach was quite wasteful. (1) If the name of the sentinel property (whose type would be the main distinguishing factor across the union) came alphabetically towards the end, there would be a ton of work done before getting to that point, often including *more* union checks for recursively defined datatypes. (2) Usually, all but one case would fail simply by checking the sentinel property first, which means that most of the time the additional work done was actually useless! This diff guesses sentinel properties and ensures that they are checked first. This causes drastic perf improvements for even mid-sized recursive disjoint unions: e.g., for the linked issue below, check time goes from ~15s to ~0.5s. Fixes https://github.com/facebook/flow/issues/2153 Reviewed By: bhosmer Differential Revision: D3646993 fbshipit-source-id: 65dbc9665e3b7757cc5c8537992fbf755818d463 --- src/typing/flow_js.ml | 128 +++- src/typing/flow_js.mli | 3 +- tests/disjoint-union-perf/.flowconfig | 7 + tests/disjoint-union-perf/ast.js | 80 +++ .../disjoint-union-perf.exp | 1 + tests/disjoint-union-perf/emit.js | 65 ++ tests/disjoint-union-perf/jsAst.js | 636 ++++++++++++++++++ 7 files changed, 908 insertions(+), 12 deletions(-) create mode 100644 tests/disjoint-union-perf/.flowconfig create mode 100644 tests/disjoint-union-perf/ast.js create mode 100644 tests/disjoint-union-perf/disjoint-union-perf.exp create mode 100644 tests/disjoint-union-perf/emit.js create mode 100644 tests/disjoint-union-perf/jsAst.js diff --git a/src/typing/flow_js.ml b/src/typing/flow_js.ml index 29e63862152..d1b709e1988 100644 --- a/src/typing/flow_js.ml +++ b/src/typing/flow_js.ml @@ -309,11 +309,6 @@ let iter_props cx id f = find_props cx id |> SMap.iter f -let iter_real_props cx id f = - find_props cx id - |> SMap.filter (fun x _ -> not (is_internal_name x)) - |> SMap.iter f - (* visit an optional evaluated type at an evaluation id *) let visit_eval_id cx id f = match IMap.get id (Context.evaluated cx) with @@ -741,16 +736,65 @@ module Cache = struct let repos_cache = ref Repos_cache.empty + (* Cache that records sentinel properties for objects. Cache entries are + populated before checking against a union of object types, and are used + while checking against each object type in the union. *) + module SentinelProp = struct + let cache = ref IMap.empty + + let add id more_keys = + match IMap.get id !cache with + | Some keys -> + cache := IMap.add id (SSet.union keys more_keys) !cache + | None -> + cache := IMap.add id more_keys !cache + + let ordered_iter id f map = + let map = match IMap.get id !cache with + | Some keys -> + SSet.fold (fun s map -> + match SMap.get s map with + | Some t -> f s t; SMap.remove s map + | None -> map + ) keys map + | _ -> map in + SMap.iter f map + + end + let clear () = FlowConstraint.cache := TypePairSet.empty; Hashtbl.clear PolyInstantiation.cache; - repos_cache := Repos_cache.empty + repos_cache := Repos_cache.empty; + SentinelProp.cache := IMap.empty - let stats () = + let stats_poly_instantiation () = Hashtbl.stats PolyInstantiation.cache + (* debug util: please don't dead-code-eliminate *) + (* Summarize flow constraints in cache as ctor/reason pairs, and return counts + for each group. *) + let summarize_flow_constraint () = + let group_counts = TypePairSet.fold (fun (l,u) map -> + let key = spf "[%s] %s => [%s] %s" + (string_of_ctor l) (string_of_reason (reason_of_t l)) + (string_of_use_ctor u) (string_of_reason (reason_of_use_t u)) in + match SMap.get key map with + | None -> SMap.add key 0 map + | Some i -> SMap.add key (i+1) map + ) !FlowConstraint.cache SMap.empty in + SMap.elements group_counts |> List.sort + (fun (_,i1) (_,i2) -> Pervasives.compare i1 i2) + end +(* Iterate over properties of an object, prioritizing sentinel properties (if + any) and ignoring shadow properties (if any). *) +let iter_real_props cx id f = + find_props cx id + |> SMap.filter (fun x _ -> not (is_internal_name x)) + |> Cache.SentinelProp.ordered_iter id f + (* Helper module for full type resolution as needed to check union and intersection types. @@ -5580,6 +5624,8 @@ and speculative_match cx trace branch l u = choice-making. *) and speculative_matches cx trace r speculation_id spec = Speculation.Case.( + (* explore optimization opportunities *) + optimize_spec cx spec; (* extract stuff to ignore while considering actions *) let ignore = ignore_of_spec spec in (* split spec into a list of pairs of types to try speculative matching on *) @@ -5735,15 +5781,75 @@ and trials_of_spec = function | IntersectionCases (ls, u) -> List.mapi (fun i l -> (i, reason_of_use_t u, l, u)) ls -and ignore_of_spec = function - | IntersectionCases (_, CallT (_, callt)) -> Some (callt.return_t) - | _ -> None - and choices_of_spec = function | UnionCases (_, ts) | IntersectionCases (ts, _) -> ts +and ignore_of_spec = function + | IntersectionCases (_, CallT (_, callt)) -> Some (callt.return_t) + | _ -> None + +(* spec optimization *) +(* Currently, the only optimization we do is for disjoint unions. Specifically, + when an object type is checked against an union of object types, we try to + guess and record sentinel properties across object types in the union. By + checking sentinel properties first, we force immediate match failures in the + vast majority of cases without having to do any useless additional work. *) +and optimize_spec cx = function + | UnionCases (l, ts) -> begin match l with + | ObjT _ -> guess_and_record_sentinel_prop cx ts + | _ -> () + end + | IntersectionCases _ -> () + +and guess_and_record_sentinel_prop cx ts = + + let props_of_object = function + | AnnotT (OpenT (_, id), _) -> + let constraints = find_graph cx id in + begin match constraints with + | Resolved (ObjT (_, { props_tmap; _ })) -> find_props cx props_tmap + | _ -> SMap.empty + end + | ObjT (_, { props_tmap; _ }) -> find_props cx props_tmap + | _ -> SMap.empty in + + let is_singleton_type = function + | AnnotT (OpenT (_, id), _) -> + let constraints = find_graph cx id in + begin match constraints with + | Resolved (SingletonStrT _ | SingletonNumT _ | SingletonBoolT _) -> true + | _ -> false + end + | SingletonStrT _ | SingletonNumT _ | SingletonBoolT _ -> true + | _ -> false in + + (* Compute the intersection of properties of objects *) + let prop_maps = List.map props_of_object ts in + let acc = List.fold_left (fun acc map -> + SMap.filter (fun s _ -> SMap.mem s map) acc + ) (List.hd prop_maps) (List.tl prop_maps) in + + (* Keep only those that have singleton types *) + let acc = SMap.filter (fun _ -> is_singleton_type) acc in + + if not (SMap.is_empty acc) then + (* Record the guessed sentinel properties for each object *) + let keys = SMap.fold (fun s _ keys -> SSet.add s keys) acc SSet.empty in + List.iter (function + | AnnotT (OpenT (_, id), _) -> + let constraints = find_graph cx id in + begin match constraints with + | Resolved (ObjT (_, { props_tmap; _ })) -> + Cache.SentinelProp.add props_tmap keys + | _ -> () + end + | ObjT (_, { props_tmap; _ }) -> + Cache.SentinelProp.add props_tmap keys + | _ -> () + ) ts + and fire_actions cx trace = List.iter (function | _, Speculation.Action.Flow (l, u) -> rec_flow cx trace (l, u) | _, Speculation.Action.Unify (t1, t2) -> rec_unify cx trace t1 t2 diff --git a/src/typing/flow_js.mli b/src/typing/flow_js.mli index ec9d6ab8ebf..0b98d2f94cf 100644 --- a/src/typing/flow_js.mli +++ b/src/typing/flow_js.mli @@ -32,7 +32,8 @@ val filter_optional: Context.t -> ?trace:Trace.t -> reason -> Type.t -> Type.t module Cache: sig val clear: unit -> unit - val stats: unit -> Hashtbl.statistics + val stats_poly_instantiation: unit -> Hashtbl.statistics + val summarize_flow_constraint: unit -> (string * int) list end val mk_tvar: Context.t -> reason -> Type.t diff --git a/tests/disjoint-union-perf/.flowconfig b/tests/disjoint-union-perf/.flowconfig new file mode 100644 index 00000000000..4a58bdcdef3 --- /dev/null +++ b/tests/disjoint-union-perf/.flowconfig @@ -0,0 +1,7 @@ +[ignore] + +[include] + +[libs] + +[options] diff --git a/tests/disjoint-union-perf/ast.js b/tests/disjoint-union-perf/ast.js new file mode 100644 index 00000000000..8705bde0341 --- /dev/null +++ b/tests/disjoint-union-perf/ast.js @@ -0,0 +1,80 @@ +/** + * @flow + */ + +export type InferredType = + | 'unknown' + | 'gender' + | 'enum' + | 'number-or-string' + | 'number' + | 'string' + | 'error' +; + +export type Pos = { + firstLine: number, + firstColumn: number, + lastLine: number, + lastColumn: number, +}; + +export type TypedBinaryOpNode = { + exprNodeType: 'binary_op', + binaryOp: 'plus' | 'multiply' | 'divide' | 'minus', + lhs: TypedNode, + rhs: TypedNode, + pos: Pos, + exprType: InferredType, + typed: true, +} + +export type TypedUnaryMinusNode = { + exprNodeType: 'unary_minus', + op: TypedNode, + pos: Pos, + exprType: InferredType, + typed: true, +} + +export type TypedNumberNode = { + exprNodeType: 'number', + value: number, + pos: Pos, + exprType: 'number', + typed: true, +} + +export type TypedStringLiteralNode = { + exprNodeType: 'string_literal', + value: string, + pos: Pos, + exprType: 'string', + typed: true, +} + +export type TypedVariableNode = { + exprNodeType: 'variable', + name: string, + pos: Pos, + exprType: InferredType, + typed: true, +}; + +export type TypedFunctionInvocationNode = { + exprNodeType: 'function_invocation', + name: string, + parameters: TypedNode[], + pos: Pos, + exprType: 'error' | 'string', + typed: true, +} + +export type TypedNode = + | TypedBinaryOpNode + | TypedUnaryMinusNode + | TypedNumberNode + | TypedStringLiteralNode + | TypedVariableNode + | TypedFunctionInvocationNode +; diff --git a/tests/disjoint-union-perf/disjoint-union-perf.exp b/tests/disjoint-union-perf/disjoint-union-perf.exp new file mode 100644 index 00000000000..2829d581f51 --- /dev/null +++ b/tests/disjoint-union-perf/disjoint-union-perf.exp @@ -0,0 +1 @@ +Found 0 errors diff --git a/tests/disjoint-union-perf/emit.js b/tests/disjoint-union-perf/emit.js new file mode 100644 index 00000000000..49bef648e63 --- /dev/null +++ b/tests/disjoint-union-perf/emit.js @@ -0,0 +1,65 @@ +/** + * @flow + */ +import * as t from './jsAst'; + +const b = t.builders; + +import type { + TypedNode +} from './ast'; + +function getBinaryOp(op: 'plus' | 'minus' | 'divide' | 'multiply') : '+' | '-' | '*' | '/' { + switch (op) { + case 'plus': + return '+'; + case 'minus': + return '-'; + case 'divide': + return '/'; + case 'multiply': + return '*'; + default: + throw new Error('Invalid binary operator: ' + op); + } +} + +export function emitExpression(node: TypedNode) : t.Expression { + switch (node.exprNodeType) { + case 'string_literal': // FALLTHROUGH + case 'number': + return b.literal(node.value); + case 'variable': + return b.memberExpression( + b.identifier('vars'), + b.identifier(node.name), + false + ); + case 'binary_op': { + const lhs = emitExpression(node.lhs); + const rhs = emitExpression(node.rhs); + + const op = getBinaryOp(node.binaryOp); + return b.binaryExpression(op, lhs, rhs); + } + case 'unary_minus': { + const operand = emitExpression(node.op); + return b.unaryExpression('-', operand, true); + } + case 'function_invocation': { + const callee = b.memberExpression( + b.identifier('fns'), + b.identifier(node.name), + false + ); + + const args = node.parameters.map( + (n) => emitExpression(n) + ); + + return b.callExpression(callee, args); + } + default: + throw new Error('Unknown expression type: ' + node.type); + } +} diff --git a/tests/disjoint-union-perf/jsAst.js b/tests/disjoint-union-perf/jsAst.js new file mode 100644 index 00000000000..4ca78a03e62 --- /dev/null +++ b/tests/disjoint-union-perf/jsAst.js @@ -0,0 +1,636 @@ +/** + * @flow + */ +export type Comment = { + loc: ?SourceLocation, + value: string, + leading: boolean, + trailing: boolean, +}; + +export type SourceLocation = { + start: SourcePosition, + end: SourcePosition, + source: ?string, +}; + +export type SourcePosition = { + line: number, + column: number, +}; + +export type File = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'File', + program: Program, +} + +export type Program = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'Program', + body: Statement[], +} + +export type BinaryOperator = + |'==' + | '!=' + | '===' + | '!==' + | '<' + | '<=' + | '>' + | '>=' + | '<<' + | '>>' + | '>>>' + | '+' + | '-' + | '*' + | '/' + | '%' + | '&' // TODO Missing from the Parser API. + | '|' + | '^' + | 'in' + | 'instanceof' + | '..' +; + +export type UnaryOperator = + | '-' + | '+' + | '!' + | '~' + | 'typeof' + | 'void' + | 'delete' +; + +export type AssignmentOperator = + | '=' + | '+=' + | '-=' + | '*=' + | '/=' + | '%=' + | '<<=' + | '>>=' + | '>>>=' + | '|=' + | '^=' + | '&=' +; + +export type UpdateOperator = + | '++' + | '--' +; + +export type LogicalOperator = + | '&&' + | '||' +; + +export type Node = + | EmptyStatement + | BlockStatement + | ExpressionStatement + | IfStatement + | BreakStatement + | ContinueStatement + | ReturnStatement + | ThrowStatement + | WhileStatement + | ForStatement + | ForInStatement + | TryStatement + | CatchClause + | Identifier + | Literal + | ThisExpression + | ArrayExpression + | ObjectExpreession + | Property + | FunctionExpression + | BinaryExpression + | UnaryExpression + | AssignmentExpression + | UpdateExpression + | LogicalExpression + | ConditionalExpression + | NewExpression + | CallExpression + | MemberExpression + | VariableDeclaration + | FunctionDeclaration + | VariableDeclarator +; + +export type Statement = + | BlockStatement + | EmptyStatement + | ExpressionStatement + | IfStatement + | BreakStatement + | ContinueStatement + | ReturnStatement + | ThrowStatement + | WhileStatement + | ForStatement + | ForInStatement + | TryStatement + | Declaration +; + +export type EmptyStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'EmptyStatement', +} + +export type BlockStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'BlockStatement', + body: Statement[], +} + +export type ExpressionStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ExpressionStatement', + expression: Expression, +} + +export type IfStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'IfStatement', + test: Expression, + consequent: Statement, + alternate: ?Statement, +} + +export type BreakStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'BreakStatement', + label: ?Identifier, +} + +export type ContinueStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ContinueStatement', + label: ?Identifier, +} + +export type ReturnStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ReturnStatement', + argument: ?Expression, +} + +export type ThrowStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ThrowStatement', + argument: ?Expression, +} + +export type WhileStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'WhileStatement', + test: Expression, + body: Statement, +} + +export type ForStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ForStatement', + init: ?(VariableDeclaration | Expression), + test: ?Expression, + update: ?Expression, + body: Statement, +} + +export type ForInStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ForInStatement', + left: VariableDeclaration | Expression, + right: Expression, + body: Statement, +} + +export type TryStatement = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'TryStatement', + block: BlockStatement, + handler: ?CatchClause, + handlers: CatchClause[], + finalizer: ?BlockStatement, +}; + +export type CatchClause = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'CatchClause', + param: Pattern, + guard: ?Expression, + body: BlockStatement, +}; + +export type Expression = + | Identifier + | ThisExpression + | Literal + | FunctionExpression + | BinaryExpression + | UnaryExpression + | AssignmentExpression + | UpdateExpression + | LogicalExpression + | ConditionalExpression + | NewExpression + | CallExpression + | MemberExpression + | ArrayExpression + | ObjectExpreession +; + +export type Identifier = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'Identifier', + name: string, +} + +export type Literal = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'Literal', + value: ?(string | boolean | number | RegExp), + regex: ?{ pattern: string, flags: string }, +} + +export type ThisExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ThisExpression', +} + +export type ArrayExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ArrayExpression', + elements: Expression[], +} + +export type ObjectExpreession = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ObjectExpression', + properties: Property[], +} + +export type Property = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'Property', + kind: 'init' | 'get' | 'set', + key: Literal | Identifier, + value: Expression, +}; + +export type FunctionExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'FunctionExpression', + id: ?Identifier, + params: Pattern[], + body: BlockStatement, +} + +export type BinaryExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'BinaryExpression', + operator: BinaryOperator, + left: Expression, + right: Expression, +} + +export type UnaryExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'UnaryExpression', + operator: UnaryOperator, + argument: Expression, + prefix: boolean, +}; + +export type AssignmentExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'AssignmentExpression', + operator: AssignmentOperator, + left: Pattern, + right: Expression, +}; + +export type UpdateExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'UpdateExpression', + operator: UpdateOperator, + argument: Expression, + prefix: boolean, +}; + +export type LogicalExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'LogicalExpression', + operator: LogicalOperator, + left: Expression, + right: Expression, +}; + +export type ConditionalExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'ConditionalExpression', + test: Expression, + consequent: Expression, + alternate: Expression, +}; + +export type NewExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'NewExpression', + callee: Expression, + arguments: Expression[], +}; + +export type CallExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'CallExpression', + callee: Expression, + arguments: Expression[], +}; + +export type MemberExpression = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'MemberExpression', + object: Expression, + property: Identifier | Expression, + computed: bool, +} +// ast-types exports all expressions as patterns. +// That seems not like it was intended. +export type Pattern = + | Identifier +; + +export type Declaration = + | VariableDeclaration + | FunctionDeclaration +; + +export type VariableDeclaration = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'VariableDeclaration', + kind: 'var' | 'let' | 'const', + declarations: VariableDeclarator[], +} + +export type FunctionDeclaration = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'FunctionDeclaration', + id: Identifier, + body: BlockStatement, + params: Pattern[], +} + +export type VariableDeclarator = { + source: ?string, + start: SourcePosition, + end: SourcePosition, + comments: ?Array, + type: 'VariableDeclarator', + id: Pattern, + init: ?Expression, +} + +const a : any = null; + +export const builders : { + emptyStatement() : EmptyStatement, + blockStatement( + body: Statement[] + ) : BlockStatement, + expressionStatement( + expression: Expression + ) : ExpressionStatement, + ifStatement( + test: Expression, + consequent: Statement, + alternate?: Statement + ) : IfStatement, + breakStatement( + label?: Identifier + ) : BreakStatement, + continueStatement( + label?: Identifier + ) : ContinueStatement, + returnStatement( + argument: ?Expression + ) : ReturnStatement, + throwStatement( + argument: ?Expression + ) : ThrowStatement, + whileStatement( + test: Expression, + body: Statement + ) : WhileStatement, + forStatement( + init: ?(VariableDeclaration | Expression), + test: ?Expression, + update: ?Expression, + body: Statement + ) : ForStatement, + forInStatement( + left: VariableDeclaration | Expression, + right: Expression, + body: Statement + ) : ForInStatement, + tryStatement( + block: BlockStatement, + handler: ?CatchClause, + handlers: CatchClause[], + finalizer?: BlockStatement + ) : TryStatement, + catchClause( + param: Pattern, + guard: ?Expression, + body: BlockStatement + ) : CatchClause, + identifier( + name: string + ) : Identifier, + literal( + value: ?(string | boolean | number | RegExp), + regex?: { pattern: string, flags: string } + ) : Literal, + thisExpression() : ThisExpression, + arrayExpression( + elements: Expression[] + ) : ArrayExpression, + objectExpreession( + properties: Property[] + ) : ObjectExpreession, + property( + kind: 'init' | 'get' | 'set', + key: Literal | Identifier, + value: Expression + ) : Property, + functionExpression( + id: ?Identifier, + params: Pattern[], + body: BlockStatement + ) : FunctionExpression, + binaryExpression( + operator: BinaryOperator, + left: Expression, + right: Expression + ) : BinaryExpression, + unaryExpression( + operator: UnaryOperator, + argument: Expression, + prefix: boolean + ) : UnaryExpression, + assignmentExpression( + operator: AssignmentOperator, + left: Pattern, + right: Expression + ) : AssignmentExpression, + updateExpression( + operator: UpdateOperator, + argument: Expression, + prefix: boolean + ) : UpdateExpression, + logicalExpression( + operator: LogicalOperator, + left: Expression, + right: Expression + ) : LogicalExpression, + conditionalExpression( + test: Expression, + consequent: Expression, + alternate: Expression + ) : ConditionalExpression, + newExpression( + callee: Expression, + arguments: Expression[] + ) : NewExpression, + callExpression( + callee: Expression, + arguments: Expression[] + ) : CallExpression, + memberExpression( + object: Expression, + property: Identifier | Expression, + computed: bool + ) : MemberExpression, + variableDeclaration( + kind: 'var' | 'let' | 'const', + declarations: VariableDeclarator[] + ) : VariableDeclaration, + functionDeclaration( + id: Identifier, + body: BlockStatement, + params: Pattern[] + ) : FunctionDeclaration, + variableDeclarator( + id: Pattern, + init?: Expression + ) : VariableDeclarator, +} = a;