@@ -242,6 +242,21 @@ namespace ts {
242
242
FunctionSubtreeExcludes = NewTarget | CapturedLexicalThis ,
243
243
}
244
244
245
+ const enum SpreadSegmentKind {
246
+ None , // Not a spread segment
247
+ UnpackedSpread , // A spread segment that must be packed (i.e., converting `[...[1, , 2]]` into `[1, undefined, 2]`)
248
+ PackedSpread , // A spread segment that is known to already be packed (i.e., `[...[1, 2]]` or `[...__read(a)]`)
249
+ }
250
+
251
+ interface SpreadSegment {
252
+ kind : SpreadSegmentKind ;
253
+ expression : Expression ;
254
+ }
255
+
256
+ function createSpreadSegment ( kind : SpreadSegmentKind , expression : Expression ) : SpreadSegment {
257
+ return { kind, expression } ;
258
+ }
259
+
245
260
export function transformES2015 ( context : TransformationContext ) {
246
261
const {
247
262
factory,
@@ -3599,7 +3614,7 @@ namespace ts {
3599
3614
function visitArrayLiteralExpression ( node : ArrayLiteralExpression ) : Expression {
3600
3615
if ( some ( node . elements , isSpreadElement ) ) {
3601
3616
// We are here because we contain a SpreadElementExpression.
3602
- return transformAndSpreadElements ( node . elements , /*needsUniqueCopy */ true , ! ! node . multiLine , /*hasTrailingComma*/ ! ! node . elements . hasTrailingComma ) ;
3617
+ return transformAndSpreadElements ( node . elements , /*isArgumentList */ false , ! ! node . multiLine , /*hasTrailingComma*/ ! ! node . elements . hasTrailingComma ) ;
3603
3618
}
3604
3619
return visitEachChild ( node , visitor , context ) ;
3605
3620
}
@@ -3820,7 +3835,7 @@ namespace ts {
3820
3835
resultingCall = factory . createFunctionApplyCall (
3821
3836
visitNode ( target , callExpressionVisitor , isExpression ) ,
3822
3837
node . expression . kind === SyntaxKind . SuperKeyword ? thisArg : visitNode ( thisArg , visitor , isExpression ) ,
3823
- transformAndSpreadElements ( node . arguments , /*needsUniqueCopy */ false , /*multiLine*/ false , /*hasTrailingComma*/ false )
3838
+ transformAndSpreadElements ( node . arguments , /*isArgumentList */ true , /*multiLine*/ false , /*hasTrailingComma*/ false )
3824
3839
) ;
3825
3840
}
3826
3841
else {
@@ -3878,7 +3893,7 @@ namespace ts {
3878
3893
factory . createFunctionApplyCall (
3879
3894
visitNode ( target , visitor , isExpression ) ,
3880
3895
thisArg ,
3881
- transformAndSpreadElements ( factory . createNodeArray ( [ factory . createVoidZero ( ) , ...node . arguments ! ] ) , /*needsUniqueCopy */ false , /*multiLine*/ false , /*hasTrailingComma*/ false )
3896
+ transformAndSpreadElements ( factory . createNodeArray ( [ factory . createVoidZero ( ) , ...node . arguments ! ] ) , /*isArgumentList */ true , /*multiLine*/ false , /*hasTrailingComma*/ false )
3882
3897
) ,
3883
3898
/*typeArguments*/ undefined ,
3884
3899
[ ]
@@ -3891,12 +3906,12 @@ namespace ts {
3891
3906
* Transforms an array of Expression nodes that contains a SpreadExpression.
3892
3907
*
3893
3908
* @param elements The array of Expression nodes.
3894
- * @param needsUniqueCopy A value indicating whether to ensure that the result is a fresh array.
3895
- * This should be `true ` when spreading into an `ArrayLiteral`, and `false ` when spreading into an
3909
+ * @param isArgumentList A value indicating whether to ensure that the result is a fresh array.
3910
+ * This should be `false ` when spreading into an `ArrayLiteral`, and `true ` when spreading into an
3896
3911
* argument list.
3897
3912
* @param multiLine A value indicating whether the result should be emitted on multiple lines.
3898
3913
*/
3899
- function transformAndSpreadElements ( elements : NodeArray < Expression > , needsUniqueCopy : boolean , multiLine : boolean , hasTrailingComma : boolean ) : Expression {
3914
+ function transformAndSpreadElements ( elements : NodeArray < Expression > , isArgumentList : boolean , multiLine : boolean , hasTrailingComma : boolean ) : Expression {
3900
3915
// When there is no leading SpreadElement:
3901
3916
//
3902
3917
// [source]
@@ -3931,7 +3946,10 @@ namespace ts {
3931
3946
// Map spans of spread expressions into their expressions and spans of other
3932
3947
// expressions into an array literal.
3933
3948
const numElements = elements . length ;
3934
- const segments = flatten < Expression > (
3949
+ const segments = flatten < SpreadSegment > (
3950
+ // As we visit each element, we return one of two functions to use as the "key":
3951
+ // - `visitSpanOfSpreads` for one or more contiguous `...` spread expressions, i.e. `...a, ...b` in `[1, 2, ...a, ...b]`
3952
+ // - `visitSpanOfNonSpreads` for one or more contiguous non-spread elements, i.e. `1, 2`, in `[1, 2, ...a, ...b]`
3935
3953
spanMap ( elements , partitionSpread , ( partition , visitPartition , _start , end ) =>
3936
3954
visitPartition ( partition , multiLine , hasTrailingComma && end === numElements )
3937
3955
)
@@ -3943,26 +3961,26 @@ namespace ts {
3943
3961
// a CallExpression or NewExpression. When using `--downlevelIteration`, we need
3944
3962
// to coerce this into an array for use with `apply`, so we will use the code path
3945
3963
// that follows instead.
3946
- if ( ! needsUniqueCopy && ! compilerOptions . downlevelIteration
3947
- || isPackedArrayLiteral ( firstSegment ) // see NOTE (above)
3948
- || isCallToHelper ( firstSegment , "___spreadArray" as __String ) ) {
3949
- return segments [ 0 ] ;
3964
+ if ( isArgumentList && ! compilerOptions . downlevelIteration
3965
+ || isPackedArrayLiteral ( firstSegment . expression ) // see NOTE (above)
3966
+ || isCallToHelper ( firstSegment . expression , "___spreadArray" as __String ) ) {
3967
+ return firstSegment . expression ;
3950
3968
}
3951
3969
}
3952
3970
3953
3971
const helpers = emitHelpers ( ) ;
3954
- const startsWithSpread = isSpreadElement ( elements [ 0 ] ) ;
3972
+ const startsWithSpread = segments [ 0 ] . kind !== SpreadSegmentKind . None ;
3955
3973
let expression : Expression =
3956
3974
startsWithSpread ? factory . createArrayLiteralExpression ( ) :
3957
- segments [ 0 ] ;
3975
+ segments [ 0 ] . expression ;
3958
3976
for ( let i = startsWithSpread ? 0 : 1 ; i < segments . length ; i ++ ) {
3977
+ const segment = segments [ i ] ;
3978
+ // If this is for an argument list, it doesn't matter if the array is packed or sparse
3959
3979
expression = helpers . createSpreadArrayHelper (
3960
3980
expression ,
3961
- compilerOptions . downlevelIteration && ! isPackedArrayLiteral ( segments [ i ] ) ? // see NOTE (above)
3962
- helpers . createReadHelper ( segments [ i ] , /*count*/ undefined ) :
3963
- segments [ i ] ) ;
3981
+ segment . expression ,
3982
+ segment . kind === SpreadSegmentKind . UnpackedSpread && ! isArgumentList ) ;
3964
3983
}
3965
-
3966
3984
return expression ;
3967
3985
}
3968
3986
@@ -3972,27 +3990,38 @@ namespace ts {
3972
3990
: visitSpanOfNonSpreads ;
3973
3991
}
3974
3992
3975
- function visitSpanOfSpreads ( chunk : Expression [ ] ) : VisitResult < Expression > {
3993
+ function visitSpanOfSpreads ( chunk : Expression [ ] ) : SpreadSegment [ ] {
3976
3994
return map ( chunk , visitExpressionOfSpread ) ;
3977
3995
}
3978
3996
3979
- function visitSpanOfNonSpreads ( chunk : Expression [ ] , multiLine : boolean , hasTrailingComma : boolean ) : VisitResult < Expression > {
3980
- return factory . createArrayLiteralExpression (
3981
- visitNodes ( factory . createNodeArray ( chunk , hasTrailingComma ) , visitor , isExpression ) ,
3982
- multiLine
3983
- ) ;
3997
+ function visitExpressionOfSpread ( node : SpreadElement ) : SpreadSegment {
3998
+ let expression = visitNode ( node . expression , visitor , isExpression ) ;
3999
+
4000
+ // We don't need to pack already packed array literals, or existing calls to the `__read` helper.
4001
+ const isCallToReadHelper = isCallToHelper ( expression , "___read" as __String ) ;
4002
+ let kind = isCallToReadHelper || isPackedArrayLiteral ( expression ) ? SpreadSegmentKind . PackedSpread : SpreadSegmentKind . UnpackedSpread ;
4003
+
4004
+ // We don't need the `__read` helper for array literals. Array packing will be performed by `__spreadArray`.
4005
+ if ( compilerOptions . downlevelIteration && kind === SpreadSegmentKind . UnpackedSpread && ! isArrayLiteralExpression ( expression ) && ! isCallToReadHelper ) {
4006
+ expression = emitHelpers ( ) . createReadHelper ( expression , /*count*/ undefined ) ;
4007
+ // the `__read` helper returns a packed array, so we don't need to ensure a packed array
4008
+ kind = SpreadSegmentKind . PackedSpread ;
4009
+ }
4010
+
4011
+ return createSpreadSegment ( kind , expression ) ;
3984
4012
}
3985
4013
3986
- function visitSpreadElement ( node : SpreadElement ) {
3987
- return visitNode ( node . expression , visitor , isExpression ) ;
4014
+ function visitSpanOfNonSpreads ( chunk : Expression [ ] , multiLine : boolean , hasTrailingComma : boolean ) : SpreadSegment {
4015
+ const expression = factory . createArrayLiteralExpression (
4016
+ visitNodes ( factory . createNodeArray ( chunk , hasTrailingComma ) , visitor , isExpression ) ,
4017
+ multiLine ) ;
4018
+
4019
+ // We do not pack non-spread segments, this is so that `[1, , ...[2, , 3], , 4]` is properly downleveled to
4020
+ // `[1, , 2, undefined, 3, , 4]`. See the NOTE in `transformAndSpreadElements`
4021
+ return createSpreadSegment ( SpreadSegmentKind . None , expression ) ;
3988
4022
}
3989
4023
3990
- /**
3991
- * Transforms the expression of a SpreadExpression node.
3992
- *
3993
- * @param node A SpreadExpression node.
3994
- */
3995
- function visitExpressionOfSpread ( node : SpreadElement ) {
4024
+ function visitSpreadElement ( node : SpreadElement ) {
3996
4025
return visitNode ( node . expression , visitor , isExpression ) ;
3997
4026
}
3998
4027
0 commit comments