From f45f7579fc33617b9595a441823dce22e2c0c32a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 11 May 2017 23:51:20 -0700 Subject: [PATCH 1/2] Adds CommaList to avoid large deeply nested comma expressions --- src/compiler/emitter.ts | 8 ++++++++ src/compiler/factory.ts | 16 +++++++++++++++- src/compiler/parser.ts | 2 ++ src/compiler/types.ts | 6 ++++++ src/compiler/utilities.ts | 4 ++++ src/compiler/visitor.ts | 8 ++++++++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 669c9f5dd36..c0d8cae861f 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -733,6 +733,9 @@ namespace ts { // Transformation nodes case SyntaxKind.PartiallyEmittedExpression: return emitPartiallyEmittedExpression(node); + + case SyntaxKind.CommaList: + return emitCommaList(node); } } @@ -2101,6 +2104,10 @@ namespace ts { emitExpression(node.expression); } + function emitCommaList(node: CommaList) { + emitExpressionList(node, node.elements, ListFormat.CommaListElements); + } + /** * Emits any prologue directives at the start of a Statement list, returning the * number of prologue directives written to the output. @@ -2951,6 +2958,7 @@ namespace ts { ArrayBindingPatternElements = SingleLine | AllowTrailingComma | CommaDelimited | SpaceBetweenSiblings, ObjectLiteralExpressionProperties = PreserveLines | CommaDelimited | SpaceBetweenSiblings | SpaceBetweenBraces | Indented | Braces, ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, + CommaListElements = CommaDelimited | SpaceBetweenSiblings | SingleLine, CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, TemplateExpressionSpans = SingleLine | NoInterveningComments, diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 2b781b6dbb3..7f84ffd534f 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2077,6 +2077,18 @@ namespace ts { return node; } + export function createCommaList(elements: Expression[]) { + const node = createSynthesizedNode(SyntaxKind.CommaList); + node.elements = createNodeArray(elements); + return node; + } + + export function updateCommaList(node: CommaList, elements: Expression[]) { + return node.elements !== elements + ? updateNode(createCommaList(elements), node) + : node; + } + export function createBundle(sourceFiles: SourceFile[]) { const node = createNode(SyntaxKind.Bundle); node.sourceFiles = sourceFiles; @@ -2865,7 +2877,9 @@ namespace ts { } export function inlineExpressions(expressions: Expression[]) { - return reduceLeft(expressions, createComma); + return expressions.length > 10 + ? createCommaList(expressions) + : reduceLeft(expressions, createComma); } export function createExpressionFromEntityName(node: EntityName | Expression): Expression { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index cc3fb24bdb8..1e8f7a1c904 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -362,6 +362,8 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.MissingDeclaration: return visitNodes(cbNodes, node.decorators); + case SyntaxKind.CommaList: + return visitNodes(cbNodes, (node).elements); case SyntaxKind.JsxElement: return visitNode(cbNode, (node).openingElement) || diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a36378ade1d..5ad89bc469e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -391,6 +391,7 @@ namespace ts { PartiallyEmittedExpression, MergeDeclarationMarker, EndOfDeclarationMarker, + CommaList, // Enum value count Count, @@ -1603,6 +1604,11 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } + export interface CommaList extends Expression { + kind: SyntaxKind.CommaList; + elements: NodeArray; + } + /** * Marks the beginning of a merged transformed declaration. */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 95b76286c59..ae54f9d3e76 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2327,6 +2327,9 @@ namespace ts { case SyntaxKind.SpreadElement: return 1; + case SyntaxKind.CommaList: + return 0; + default: return -1; } @@ -3915,6 +3918,7 @@ namespace ts { || kind === SyntaxKind.SpreadElement || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression + || kind === SyntaxKind.CommaList || isUnaryExpressionKind(kind); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index e434dadeaf7..7788c313139 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -876,6 +876,10 @@ namespace ts { return updatePartiallyEmittedExpression(node, visitNode((node).expression, visitor, isExpression)); + case SyntaxKind.CommaList: + return updateCommaList(node, + nodesVisitor((node).elements, visitor, isExpression)); + default: // No need to visit nodes with no children. return node; @@ -1389,6 +1393,10 @@ namespace ts { result = reduceNode((node).expression, cbNode, result); break; + case SyntaxKind.CommaList: + result = reduceNodes((node).elements, cbNodes, result); + break; + default: break; } From 22cf036ed91e638220520bb5ff5b167ee723180c Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 12 May 2017 09:57:39 -0700 Subject: [PATCH 2/2] Clean up naming, add documentation, flatten (some) nested commas --- src/compiler/core.ts | 29 +++++++++++++++++++++++++++++ src/compiler/emitter.ts | 6 +++--- src/compiler/factory.ts | 22 ++++++++++++++++++---- src/compiler/parser.ts | 4 ++-- src/compiler/types.ts | 9 ++++++--- src/compiler/utilities.ts | 4 ++-- src/compiler/visitor.ts | 10 +++++----- 7 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 321cc06f765..5e4afce98c8 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -490,6 +490,35 @@ namespace ts { return result; } + /** + * Maps an array. If the mapped value is an array, it is spread into the result. + * Avoids allocation if all elements map to themselves. + * + * @param array The array to map. + * @param mapfn The callback used to map the result into one or more values. + */ + export function sameFlatMap(array: T[], mapfn: (x: T, i: number) => T | T[]): T[] { + let result: T[]; + if (array) { + for (let i = 0; i < array.length; i++) { + const item = array[i]; + const mapped = mapfn(item, i); + if (result || item !== mapped || isArray(mapped)) { + if (!result) { + result = array.slice(0, i); + } + if (isArray(mapped)) { + addRange(result, mapped); + } + else { + result.push(mapped); + } + } + } + } + return result || array; + } + /** * Computes the first matching span of elements and returns a tuple of the first span * and the remaining elements. diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c0d8cae861f..76423e45bd9 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -734,8 +734,8 @@ namespace ts { case SyntaxKind.PartiallyEmittedExpression: return emitPartiallyEmittedExpression(node); - case SyntaxKind.CommaList: - return emitCommaList(node); + case SyntaxKind.CommaListExpression: + return emitCommaList(node); } } @@ -2104,7 +2104,7 @@ namespace ts { emitExpression(node.expression); } - function emitCommaList(node: CommaList) { + function emitCommaList(node: CommaListExpression) { emitExpressionList(node, node.elements, ListFormat.CommaListElements); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 7f84ffd534f..42f6a1267bc 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2077,13 +2077,25 @@ namespace ts { return node; } - export function createCommaList(elements: Expression[]) { - const node = createSynthesizedNode(SyntaxKind.CommaList); - node.elements = createNodeArray(elements); + function flattenCommaElements(node: Expression): Expression | Expression[] { + if (nodeIsSynthesized(node) && !isParseTreeNode(node) && !node.original && !node.emitNode && !node.id) { + if (node.kind === SyntaxKind.CommaListExpression) { + return (node).elements; + } + if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) { + return [node.left, node.right]; + } + } return node; } - export function updateCommaList(node: CommaList, elements: Expression[]) { + export function createCommaList(elements: Expression[]) { + const node = createSynthesizedNode(SyntaxKind.CommaListExpression); + node.elements = createNodeArray(sameFlatMap(elements, flattenCommaElements)); + return node; + } + + export function updateCommaList(node: CommaListExpression, elements: Expression[]) { return node.elements !== elements ? updateNode(createCommaList(elements), node) : node; @@ -2877,6 +2889,8 @@ namespace ts { } export function inlineExpressions(expressions: Expression[]) { + // Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call + // stack size exceeded" errors. return expressions.length > 10 ? createCommaList(expressions) : reduceLeft(expressions, createComma); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1e8f7a1c904..dd189784a17 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -362,8 +362,8 @@ namespace ts { return visitNode(cbNode, (node).expression); case SyntaxKind.MissingDeclaration: return visitNodes(cbNodes, node.decorators); - case SyntaxKind.CommaList: - return visitNodes(cbNodes, (node).elements); + case SyntaxKind.CommaListExpression: + return visitNodes(cbNodes, (node).elements); case SyntaxKind.JsxElement: return visitNode(cbNode, (node).openingElement) || diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5ad89bc469e..f38ec9abd9c 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -389,9 +389,9 @@ namespace ts { // Transformation nodes NotEmittedStatement, PartiallyEmittedExpression, + CommaListExpression, MergeDeclarationMarker, EndOfDeclarationMarker, - CommaList, // Enum value count Count, @@ -1604,8 +1604,11 @@ namespace ts { kind: SyntaxKind.EndOfDeclarationMarker; } - export interface CommaList extends Expression { - kind: SyntaxKind.CommaList; + /** + * A list of comma-seperated expressions. This node is only created by transformations. + */ + export interface CommaListExpression extends Expression { + kind: SyntaxKind.CommaListExpression; elements: NodeArray; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ae54f9d3e76..1a981a0844f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2327,7 +2327,7 @@ namespace ts { case SyntaxKind.SpreadElement: return 1; - case SyntaxKind.CommaList: + case SyntaxKind.CommaListExpression: return 0; default: @@ -3918,7 +3918,7 @@ namespace ts { || kind === SyntaxKind.SpreadElement || kind === SyntaxKind.AsExpression || kind === SyntaxKind.OmittedExpression - || kind === SyntaxKind.CommaList + || kind === SyntaxKind.CommaListExpression || isUnaryExpressionKind(kind); } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 7788c313139..517854e21ce 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -876,9 +876,9 @@ namespace ts { return updatePartiallyEmittedExpression(node, visitNode((node).expression, visitor, isExpression)); - case SyntaxKind.CommaList: - return updateCommaList(node, - nodesVisitor((node).elements, visitor, isExpression)); + case SyntaxKind.CommaListExpression: + return updateCommaList(node, + nodesVisitor((node).elements, visitor, isExpression)); default: // No need to visit nodes with no children. @@ -1393,8 +1393,8 @@ namespace ts { result = reduceNode((node).expression, cbNode, result); break; - case SyntaxKind.CommaList: - result = reduceNodes((node).elements, cbNodes, result); + case SyntaxKind.CommaListExpression: + result = reduceNodes((node).elements, cbNodes, result); break; default: