Skip to content

Commit 25462c9

Browse files
authored
Merge pull request #12028 from Microsoft/object-rest
Object rest
2 parents 9b1f43b + 0196947 commit 25462c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2968
-1347
lines changed

src/compiler/binder.ts

+69-11
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,9 @@ namespace ts {
19181918
return bindParameter(<ParameterDeclaration>node);
19191919
case SyntaxKind.VariableDeclaration:
19201920
case SyntaxKind.BindingElement:
1921+
if ((node as BindingElement).dotDotDotToken && node.parent.kind === SyntaxKind.ObjectBindingPattern) {
1922+
emitFlags |= NodeFlags.HasRestAttribute;
1923+
}
19211924
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>node);
19221925
case SyntaxKind.PropertyDeclaration:
19231926
case SyntaxKind.PropertySignature:
@@ -1933,7 +1936,19 @@ namespace ts {
19331936

19341937
case SyntaxKind.SpreadAssignment:
19351938
case SyntaxKind.JsxSpreadAttribute:
1936-
emitFlags |= NodeFlags.HasSpreadAttribute;
1939+
let root = container;
1940+
let hasRest = false;
1941+
while (root.parent) {
1942+
if (root.kind === SyntaxKind.ObjectLiteralExpression &&
1943+
root.parent.kind === SyntaxKind.BinaryExpression &&
1944+
(root.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
1945+
(root.parent as BinaryExpression).left === root) {
1946+
hasRest = true;
1947+
break;
1948+
}
1949+
root = root.parent;
1950+
}
1951+
emitFlags |= hasRest ? NodeFlags.HasRestAttribute : NodeFlags.HasSpreadAttribute;
19371952
return;
19381953

19391954
case SyntaxKind.CallSignature:
@@ -2544,10 +2559,13 @@ namespace ts {
25442559
const operatorTokenKind = node.operatorToken.kind;
25452560
const leftKind = node.left.kind;
25462561

2547-
if (operatorTokenKind === SyntaxKind.EqualsToken
2548-
&& (leftKind === SyntaxKind.ObjectLiteralExpression
2549-
|| leftKind === SyntaxKind.ArrayLiteralExpression)) {
2550-
// Destructuring assignments are ES6 syntax.
2562+
if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
2563+
// Destructuring object assignments with are ES2015 syntax
2564+
// and possibly ESNext if they contain rest
2565+
transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment;
2566+
}
2567+
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ArrayLiteralExpression) {
2568+
// Destructuring assignments are ES2015 syntax.
25512569
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment;
25522570
}
25532571
else if (operatorTokenKind === SyntaxKind.AsteriskAsteriskToken
@@ -2581,6 +2599,11 @@ namespace ts {
25812599
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments;
25822600
}
25832601

2602+
// parameters with object rest destructuring are ES Next syntax
2603+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2604+
transformFlags |= TransformFlags.AssertESNext;
2605+
}
2606+
25842607
// If a parameter has an initializer, a binding pattern or a dotDotDot token, then
25852608
// it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel.
25862609
if (subtreeFlags & TransformFlags.ContainsBindingPattern || initializer || dotDotDotToken) {
@@ -2814,6 +2837,11 @@ namespace ts {
28142837
transformFlags |= TransformFlags.AssertES2017;
28152838
}
28162839

2840+
// function declarations with object rest destructuring are ES Next syntax
2841+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2842+
transformFlags |= TransformFlags.AssertESNext;
2843+
}
2844+
28172845
// If a FunctionDeclaration's subtree has marked the container as needing to capture the
28182846
// lexical this, or the function contains parameters with initializers, then this node is
28192847
// ES6 syntax.
@@ -2851,6 +2879,12 @@ namespace ts {
28512879
transformFlags |= TransformFlags.AssertES2017;
28522880
}
28532881

2882+
// function expressions with object rest destructuring are ES Next syntax
2883+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2884+
transformFlags |= TransformFlags.AssertESNext;
2885+
}
2886+
2887+
28542888
// If a FunctionExpression's subtree has marked the container as needing to capture the
28552889
// lexical this, or the function contains parameters with initializers, then this node is
28562890
// ES6 syntax.
@@ -2888,6 +2922,11 @@ namespace ts {
28882922
transformFlags |= TransformFlags.AssertES2017;
28892923
}
28902924

2925+
// arrow functions with object rest destructuring are ES Next syntax
2926+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2927+
transformFlags |= TransformFlags.AssertESNext;
2928+
}
2929+
28912930
// If an ArrowFunction contains a lexical this, its container must capture the lexical this.
28922931
if (subtreeFlags & TransformFlags.ContainsLexicalThis) {
28932932
transformFlags |= TransformFlags.ContainsCapturedLexicalThis;
@@ -2916,8 +2955,13 @@ namespace ts {
29162955
let transformFlags = subtreeFlags;
29172956
const nameKind = node.name.kind;
29182957

2919-
// A VariableDeclaration with a binding pattern is ES6 syntax.
2920-
if (nameKind === SyntaxKind.ObjectBindingPattern || nameKind === SyntaxKind.ArrayBindingPattern) {
2958+
// A VariableDeclaration with an object binding pattern is ES2015 syntax
2959+
// and possibly ESNext syntax if it contains an object binding pattern
2960+
if (nameKind === SyntaxKind.ObjectBindingPattern) {
2961+
transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
2962+
}
2963+
// A VariableDeclaration with an object binding pattern is ES2015 syntax.
2964+
else if (nameKind === SyntaxKind.ArrayBindingPattern) {
29212965
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
29222966
}
29232967

@@ -3058,14 +3102,17 @@ namespace ts {
30583102
transformFlags |= TransformFlags.AssertJsx;
30593103
break;
30603104

3105+
case SyntaxKind.ForOfStatement:
3106+
// for-of might be ESNext if it has a rest destructuring
3107+
transformFlags |= TransformFlags.AssertESNext;
3108+
// FALLTHROUGH
30613109
case SyntaxKind.NoSubstitutionTemplateLiteral:
30623110
case SyntaxKind.TemplateHead:
30633111
case SyntaxKind.TemplateMiddle:
30643112
case SyntaxKind.TemplateTail:
30653113
case SyntaxKind.TemplateExpression:
30663114
case SyntaxKind.TaggedTemplateExpression:
30673115
case SyntaxKind.ShorthandPropertyAssignment:
3068-
case SyntaxKind.ForOfStatement:
30693116
case SyntaxKind.StaticKeyword:
30703117
// These nodes are ES6 syntax.
30713118
transformFlags |= TransformFlags.AssertES2015;
@@ -3131,10 +3178,16 @@ namespace ts {
31313178

31323179
case SyntaxKind.SpreadElement:
31333180
case SyntaxKind.SpreadAssignment:
3134-
// This node is ES6 or ES future syntax, but is handled by a containing node.
3181+
// This node is ES6 or ES next syntax, but is handled by a containing node.
31353182
transformFlags |= TransformFlags.ContainsSpreadExpression;
31363183
break;
31373184

3185+
case SyntaxKind.BindingElement:
3186+
if ((node as BindingElement).dotDotDotToken) {
3187+
// this node is ES2015 or ES next syntax, but is handled by a containing node.
3188+
transformFlags |= TransformFlags.ContainsSpreadExpression;
3189+
}
3190+
31383191
case SyntaxKind.SuperKeyword:
31393192
// This node is ES6 syntax.
31403193
transformFlags |= TransformFlags.AssertES2015;
@@ -3147,8 +3200,13 @@ namespace ts {
31473200

31483201
case SyntaxKind.ObjectBindingPattern:
31493202
case SyntaxKind.ArrayBindingPattern:
3150-
// These nodes are ES6 syntax.
3151-
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
3203+
// These nodes are ES2015 or ES Next syntax.
3204+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
3205+
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsBindingPattern;
3206+
}
3207+
else {
3208+
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
3209+
}
31523210
break;
31533211

31543212
case SyntaxKind.Decorator:

src/compiler/checker.ts

+58-38
Original file line numberDiff line numberDiff line change
@@ -2982,26 +2982,31 @@ namespace ts {
29822982
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false);
29832983
}
29842984

2985-
function getTextOfPropertyName(name: PropertyName): string {
2986-
switch (name.kind) {
2987-
case SyntaxKind.Identifier:
2988-
return (<Identifier>name).text;
2989-
case SyntaxKind.StringLiteral:
2990-
case SyntaxKind.NumericLiteral:
2991-
return (<LiteralExpression>name).text;
2992-
case SyntaxKind.ComputedPropertyName:
2993-
if (isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind)) {
2994-
return (<LiteralExpression>(<ComputedPropertyName>name).expression).text;
2995-
}
2996-
}
2997-
2998-
return undefined;
2999-
}
3000-
30012985
function isComputedNonLiteralName(name: PropertyName): boolean {
30022986
return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind);
30032987
}
30042988

2989+
function getRestType(source: Type, properties: PropertyName[], symbol: Symbol): Type {
2990+
Debug.assert(!!(source.flags & TypeFlags.Object), "Rest types only support object types right now.");
2991+
const members = createMap<Symbol>();
2992+
const names = createMap<true>();
2993+
for (const name of properties) {
2994+
names[getTextOfPropertyName(name)] = true;
2995+
}
2996+
for (const prop of getPropertiesOfType(source)) {
2997+
const inNamesToRemove = prop.name in names;
2998+
const isPrivate = getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected);
2999+
const isMethod = prop.flags & SymbolFlags.Method;
3000+
const isSetOnlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
3001+
if (!inNamesToRemove && !isPrivate && !isMethod && !isSetOnlyAccessor) {
3002+
members[prop.name] = prop;
3003+
}
3004+
}
3005+
const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String);
3006+
const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number);
3007+
return createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
3008+
}
3009+
30053010
/** Return the inferred type for a binding element */
30063011
function getTypeForBindingElement(declaration: BindingElement): Type {
30073012
const pattern = <BindingPattern>declaration.parent;
@@ -3022,26 +3027,41 @@ namespace ts {
30223027

30233028
let type: Type;
30243029
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
3025-
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
3026-
const name = declaration.propertyName || <Identifier>declaration.name;
3027-
if (isComputedNonLiteralName(name)) {
3028-
// computed properties with non-literal names are treated as 'any'
3029-
return anyType;
3030-
}
3031-
if (declaration.initializer) {
3032-
getContextualType(declaration.initializer);
3030+
if (declaration.dotDotDotToken) {
3031+
if (!(parentType.flags & TypeFlags.Object)) {
3032+
error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types);
3033+
return unknownType;
3034+
}
3035+
const literalMembers: PropertyName[] = [];
3036+
for (const element of pattern.elements) {
3037+
if (element.kind !== SyntaxKind.OmittedExpression && !(element as BindingElement).dotDotDotToken) {
3038+
literalMembers.push(element.propertyName || element.name as Identifier);
3039+
}
3040+
}
3041+
type = getRestType(parentType, literalMembers, declaration.symbol);
30333042
}
3043+
else {
3044+
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
3045+
const name = declaration.propertyName || <Identifier>declaration.name;
3046+
if (isComputedNonLiteralName(name)) {
3047+
// computed properties with non-literal names are treated as 'any'
3048+
return anyType;
3049+
}
3050+
if (declaration.initializer) {
3051+
getContextualType(declaration.initializer);
3052+
}
30343053

3035-
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
3036-
// or otherwise the type of the string index signature.
3037-
const text = getTextOfPropertyName(name);
3054+
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
3055+
// or otherwise the type of the string index signature.
3056+
const text = getTextOfPropertyName(name);
30383057

3039-
type = getTypeOfPropertyOfType(parentType, text) ||
3040-
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
3041-
getIndexTypeOfType(parentType, IndexKind.String);
3042-
if (!type) {
3043-
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
3044-
return unknownType;
3058+
type = getTypeOfPropertyOfType(parentType, text) ||
3059+
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
3060+
getIndexTypeOfType(parentType, IndexKind.String);
3061+
if (!type) {
3062+
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
3063+
return unknownType;
3064+
}
30453065
}
30463066
}
30473067
else {
@@ -3249,8 +3269,8 @@ namespace ts {
32493269
let hasComputedProperties = false;
32503270
forEach(pattern.elements, e => {
32513271
const name = e.propertyName || <Identifier>e.name;
3252-
if (isComputedNonLiteralName(name)) {
3253-
// do not include computed properties in the implied type
3272+
if (isComputedNonLiteralName(name) || e.dotDotDotToken) {
3273+
// do not include computed properties or rests in the implied type
32543274
hasComputedProperties = true;
32553275
return;
32563276
}
@@ -13889,7 +13909,7 @@ namespace ts {
1388913909
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(objectLiteralType), declarationNameToString(name));
1389013910
}
1389113911
}
13892-
else {
13912+
else if (property.kind !== SyntaxKind.SpreadAssignment) {
1389313913
error(property, Diagnostics.Property_assignment_expected);
1389413914
}
1389513915
}
@@ -13935,7 +13955,7 @@ namespace ts {
1393513955
}
1393613956
else {
1393713957
if (elementIndex < elements.length - 1) {
13938-
error(element, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
13958+
error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
1393913959
}
1394013960
else {
1394113961
const restExpression = (<SpreadElement>element).expression;
@@ -20832,7 +20852,7 @@ namespace ts {
2083220852
if (node.dotDotDotToken) {
2083320853
const elements = (<BindingPattern>node.parent).elements;
2083420854
if (node !== lastOrUndefined(elements)) {
20835-
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
20855+
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
2083620856
}
2083720857

2083820858
if (node.name.kind === SyntaxKind.ArrayBindingPattern || node.name.kind === SyntaxKind.ObjectBindingPattern) {

src/compiler/diagnosticMessages.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -1455,7 +1455,7 @@
14551455
"category": "Error",
14561456
"code": 2461
14571457
},
1458-
"A rest element must be last in an array destructuring pattern": {
1458+
"A rest element must be last in a destructuring pattern": {
14591459
"category": "Error",
14601460
"code": 2462
14611461
},
@@ -1987,6 +1987,10 @@
19871987
"category": "Error",
19881988
"code": 2698
19891989
},
1990+
"Rest types may only be created from object types.": {
1991+
"category": "Error",
1992+
"code": 2700
1993+
},
19901994

19911995
"Import declaration '{0}' is using private name '{1}'.": {
19921996
"category": "Error",

src/compiler/emitter.ts

+14
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ var __assign = (this && this.__assign) || Object.assign || function(t) {
4242
return t;
4343
};`;
4444

45+
const restHelper = `
46+
var __rest = (this && this.__rest) || function (s, e) {
47+
var t = {};
48+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && !e.indexOf(p))
49+
t[p] = s[p];
50+
return t;
51+
};`;
52+
4553
// emit output for the __decorate helper function
4654
const decorateHelper = `
4755
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
@@ -226,6 +234,7 @@ const _super = (function (geti, seti) {
226234
let currentFileIdentifiers: Map<string>;
227235
let extendsEmitted: boolean;
228236
let assignEmitted: boolean;
237+
let restEmitted: boolean;
229238
let decorateEmitted: boolean;
230239
let paramEmitted: boolean;
231240
let awaiterEmitted: boolean;
@@ -2222,6 +2231,11 @@ const _super = (function (geti, seti) {
22222231
assignEmitted = true;
22232232
}
22242233

2234+
if (languageVersion < ScriptTarget.ESNext && !restEmitted && node.flags & NodeFlags.HasRestAttribute) {
2235+
writeLines(restHelper);
2236+
restEmitted = true;
2237+
}
2238+
22252239
if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) {
22262240
writeLines(decorateHelper);
22272241
if (compilerOptions.emitDecoratorMetadata) {

0 commit comments

Comments
 (0)