Skip to content

Commit a7c1836

Browse files
committed
Parse, bind and check rest elements
1 parent f65dd21 commit a7c1836

File tree

6 files changed

+161
-61
lines changed

6 files changed

+161
-61
lines changed

src/compiler/binder.ts

+61-11
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,9 @@ namespace ts {
19161916
return bindParameter(<ParameterDeclaration>node);
19171917
case SyntaxKind.VariableDeclaration:
19181918
case SyntaxKind.BindingElement:
1919+
if ((node as BindingElement).dotDotDotToken && node.parent.kind === SyntaxKind.ObjectBindingPattern) {
1920+
emitFlags |= NodeFlags.HasRestAttribute;
1921+
}
19191922
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>node);
19201923
case SyntaxKind.PropertyDeclaration:
19211924
case SyntaxKind.PropertySignature:
@@ -1931,7 +1934,11 @@ namespace ts {
19311934

19321935
case SyntaxKind.SpreadElementExpression:
19331936
case SyntaxKind.JsxSpreadAttribute:
1934-
emitFlags |= NodeFlags.HasSpreadAttribute;
1937+
let root = container;
1938+
while (root && root.kind !== SyntaxKind.BinaryExpression) {
1939+
root = root.parent;
1940+
}
1941+
emitFlags |= root && isDestructuringAssignment(root) ? NodeFlags.HasRestAttribute : NodeFlags.HasSpreadAttribute;
19351942
return;
19361943

19371944
case SyntaxKind.CallSignature:
@@ -2542,10 +2549,13 @@ namespace ts {
25422549
const operatorTokenKind = node.operatorToken.kind;
25432550
const leftKind = node.left.kind;
25442551

2545-
if (operatorTokenKind === SyntaxKind.EqualsToken
2546-
&& (leftKind === SyntaxKind.ObjectLiteralExpression
2547-
|| leftKind === SyntaxKind.ArrayLiteralExpression)) {
2548-
// Destructuring assignments are ES6 syntax.
2552+
if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
2553+
// Destructuring object assignments with are ES2015 syntax
2554+
// and possibly ESNext if they contain rest
2555+
transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment;
2556+
}
2557+
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ArrayLiteralExpression) {
2558+
// Destructuring assignments are ES2015 syntax.
25492559
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment;
25502560
}
25512561
else if (operatorTokenKind === SyntaxKind.AsteriskAsteriskToken
@@ -2579,6 +2589,11 @@ namespace ts {
25792589
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments;
25802590
}
25812591

2592+
// parameters with object rest destructuring are ES Next syntax
2593+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2594+
transformFlags |= TransformFlags.AssertESNext;
2595+
}
2596+
25822597
// If a parameter has an initializer, a binding pattern or a dotDotDot token, then
25832598
// it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel.
25842599
if (subtreeFlags & TransformFlags.ContainsBindingPattern || initializer || dotDotDotToken) {
@@ -2812,6 +2827,11 @@ namespace ts {
28122827
transformFlags |= TransformFlags.AssertES2017;
28132828
}
28142829

2830+
// function declarations with object rest destructuring are ES Next syntax
2831+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2832+
transformFlags |= TransformFlags.AssertESNext;
2833+
}
2834+
28152835
// If a FunctionDeclaration's subtree has marked the container as needing to capture the
28162836
// lexical this, or the function contains parameters with initializers, then this node is
28172837
// ES6 syntax.
@@ -2849,6 +2869,12 @@ namespace ts {
28492869
transformFlags |= TransformFlags.AssertES2017;
28502870
}
28512871

2872+
// function expressions with object rest destructuring are ES Next syntax
2873+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2874+
transformFlags |= TransformFlags.AssertESNext;
2875+
}
2876+
2877+
28522878
// If a FunctionExpression's subtree has marked the container as needing to capture the
28532879
// lexical this, or the function contains parameters with initializers, then this node is
28542880
// ES6 syntax.
@@ -2886,6 +2912,11 @@ namespace ts {
28862912
transformFlags |= TransformFlags.AssertES2017;
28872913
}
28882914

2915+
// arrow functions with object rest destructuring are ES Next syntax
2916+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
2917+
transformFlags |= TransformFlags.AssertESNext;
2918+
}
2919+
28892920
// If an ArrowFunction contains a lexical this, its container must capture the lexical this.
28902921
if (subtreeFlags & TransformFlags.ContainsLexicalThis) {
28912922
transformFlags |= TransformFlags.ContainsCapturedLexicalThis;
@@ -2914,8 +2945,13 @@ namespace ts {
29142945
let transformFlags = subtreeFlags;
29152946
const nameKind = node.name.kind;
29162947

2917-
// A VariableDeclaration with a binding pattern is ES6 syntax.
2918-
if (nameKind === SyntaxKind.ObjectBindingPattern || nameKind === SyntaxKind.ArrayBindingPattern) {
2948+
// A VariableDeclaration with an object binding pattern is ES2015 syntax
2949+
// and possibly ESNext syntax if it contains an object binding pattern
2950+
if (nameKind === SyntaxKind.ObjectBindingPattern) {
2951+
transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
2952+
}
2953+
// A VariableDeclaration with an object binding pattern is ES2015 syntax.
2954+
else if (nameKind === SyntaxKind.ArrayBindingPattern) {
29192955
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
29202956
}
29212957

@@ -3056,14 +3092,17 @@ namespace ts {
30563092
transformFlags |= TransformFlags.AssertJsx;
30573093
break;
30583094

3095+
case SyntaxKind.ForOfStatement:
3096+
// for-of might be ESNext if it has a rest destructuring
3097+
transformFlags |= TransformFlags.AssertESNext;
3098+
// FALLTHROUGH
30593099
case SyntaxKind.NoSubstitutionTemplateLiteral:
30603100
case SyntaxKind.TemplateHead:
30613101
case SyntaxKind.TemplateMiddle:
30623102
case SyntaxKind.TemplateTail:
30633103
case SyntaxKind.TemplateExpression:
30643104
case SyntaxKind.TaggedTemplateExpression:
30653105
case SyntaxKind.ShorthandPropertyAssignment:
3066-
case SyntaxKind.ForOfStatement:
30673106
case SyntaxKind.StaticKeyword:
30683107
// These nodes are ES6 syntax.
30693108
transformFlags |= TransformFlags.AssertES2015;
@@ -3129,10 +3168,16 @@ namespace ts {
31293168

31303169
case SyntaxKind.SpreadExpression:
31313170
case SyntaxKind.SpreadElementExpression:
3132-
// This node is ES6 or ES future syntax, but is handled by a containing node.
3171+
// This node is ES2015 or ES next syntax, but is handled by a containing node.
31333172
transformFlags |= TransformFlags.ContainsSpreadExpression;
31343173
break;
31353174

3175+
case SyntaxKind.BindingElement:
3176+
if ((node as BindingElement).dotDotDotToken) {
3177+
// this node is ES2015 or ES next syntax, but is handled by a containing node.
3178+
transformFlags |= TransformFlags.ContainsSpreadExpression;
3179+
}
3180+
31363181
case SyntaxKind.SuperKeyword:
31373182
// This node is ES6 syntax.
31383183
transformFlags |= TransformFlags.AssertES2015;
@@ -3145,8 +3190,13 @@ namespace ts {
31453190

31463191
case SyntaxKind.ObjectBindingPattern:
31473192
case SyntaxKind.ArrayBindingPattern:
3148-
// These nodes are ES6 syntax.
3149-
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
3193+
// These nodes are ES2015 or ES Next syntax.
3194+
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
3195+
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsBindingPattern;
3196+
}
3197+
else {
3198+
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
3199+
}
31503200
break;
31513201

31523202
case SyntaxKind.Decorator:

src/compiler/checker.ts

+59-39
Original file line numberDiff line numberDiff line change
@@ -2245,7 +2245,7 @@ namespace ts {
22452245
writeUnionOrIntersectionType(<UnionOrIntersectionType>type, nextFlags);
22462246
}
22472247
else if (type.flags & TypeFlags.Spread) {
2248-
writeSpreadType(<SpreadType>type);
2248+
writeSpreadType(type as SpreadType);
22492249
}
22502250
else if (getObjectFlags(type) & ObjectFlags.Anonymous) {
22512251
writeAnonymousType(<ObjectType>type, nextFlags);
@@ -3028,26 +3028,31 @@ namespace ts {
30283028
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false);
30293029
}
30303030

3031-
function getTextOfPropertyName(name: PropertyName): string {
3032-
switch (name.kind) {
3033-
case SyntaxKind.Identifier:
3034-
return (<Identifier>name).text;
3035-
case SyntaxKind.StringLiteral:
3036-
case SyntaxKind.NumericLiteral:
3037-
return (<LiteralExpression>name).text;
3038-
case SyntaxKind.ComputedPropertyName:
3039-
if (isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind)) {
3040-
return (<LiteralExpression>(<ComputedPropertyName>name).expression).text;
3041-
}
3042-
}
3043-
3044-
return undefined;
3045-
}
3046-
30473031
function isComputedNonLiteralName(name: PropertyName): boolean {
30483032
return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind);
30493033
}
30503034

3035+
function getRestType(source: Type, properties: PropertyName[], symbol: Symbol): Type {
3036+
Debug.assert(!!(source.flags & TypeFlags.Object), "Rest types only support object types right now.");
3037+
const members = createMap<Symbol>();
3038+
const names = createMap<true>();
3039+
for (const name of properties) {
3040+
names[getTextOfPropertyName(name)] = true;
3041+
}
3042+
for (const prop of getPropertiesOfType(source)) {
3043+
const inNamesToRemove = prop.name in names;
3044+
const isPrivate = getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected);
3045+
const isMethod = prop.flags & SymbolFlags.Method;
3046+
const isSetOnlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
3047+
if (!inNamesToRemove && !isPrivate && !isMethod && !isSetOnlyAccessor) {
3048+
members[prop.name] = prop;
3049+
}
3050+
}
3051+
const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String);
3052+
const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number);
3053+
return createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
3054+
}
3055+
30513056
/** Return the inferred type for a binding element */
30523057
function getTypeForBindingElement(declaration: BindingElement): Type {
30533058
const pattern = <BindingPattern>declaration.parent;
@@ -3068,26 +3073,41 @@ namespace ts {
30683073

30693074
let type: Type;
30703075
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
3071-
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
3072-
const name = declaration.propertyName || <Identifier>declaration.name;
3073-
if (isComputedNonLiteralName(name)) {
3074-
// computed properties with non-literal names are treated as 'any'
3075-
return anyType;
3076-
}
3077-
if (declaration.initializer) {
3078-
getContextualType(declaration.initializer);
3076+
if (declaration.dotDotDotToken) {
3077+
if (!(parentType.flags & TypeFlags.Object)) {
3078+
error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types);
3079+
return unknownType;
3080+
}
3081+
const literalMembers: PropertyName[] = [];
3082+
for (const element of pattern.elements) {
3083+
if (element.kind !== SyntaxKind.OmittedExpression && !(element as BindingElement).dotDotDotToken) {
3084+
literalMembers.push(element.propertyName || element.name as Identifier);
3085+
}
3086+
}
3087+
type = getRestType(parentType, literalMembers, declaration.symbol);
30793088
}
3089+
else {
3090+
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
3091+
const name = declaration.propertyName || <Identifier>declaration.name;
3092+
if (isComputedNonLiteralName(name)) {
3093+
// computed properties with non-literal names are treated as 'any'
3094+
return anyType;
3095+
}
3096+
if (declaration.initializer) {
3097+
getContextualType(declaration.initializer);
3098+
}
30803099

3081-
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
3082-
// or otherwise the type of the string index signature.
3083-
const text = getTextOfPropertyName(name);
3100+
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
3101+
// or otherwise the type of the string index signature.
3102+
const text = getTextOfPropertyName(name);
30843103

3085-
type = getTypeOfPropertyOfType(parentType, text) ||
3086-
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
3087-
getIndexTypeOfType(parentType, IndexKind.String);
3088-
if (!type) {
3089-
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
3090-
return unknownType;
3104+
type = getTypeOfPropertyOfType(parentType, text) ||
3105+
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
3106+
getIndexTypeOfType(parentType, IndexKind.String);
3107+
if (!type) {
3108+
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
3109+
return unknownType;
3110+
}
30913111
}
30923112
}
30933113
else {
@@ -3295,8 +3315,8 @@ namespace ts {
32953315
let hasComputedProperties = false;
32963316
forEach(pattern.elements, e => {
32973317
const name = e.propertyName || <Identifier>e.name;
3298-
if (isComputedNonLiteralName(name)) {
3299-
// do not include computed properties in the implied type
3318+
if (isComputedNonLiteralName(name) || e.dotDotDotToken) {
3319+
// do not include computed properties or rests in the implied type
33003320
hasComputedProperties = true;
33013321
return;
33023322
}
@@ -14097,7 +14117,7 @@ namespace ts {
1409714117
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(objectLiteralType), declarationNameToString(name));
1409814118
}
1409914119
}
14100-
else {
14120+
else if (property.kind !== SyntaxKind.SpreadElementExpression) {
1410114121
error(property, Diagnostics.Property_assignment_expected);
1410214122
}
1410314123
}
@@ -14143,7 +14163,7 @@ namespace ts {
1414314163
}
1414414164
else {
1414514165
if (elementIndex < elements.length - 1) {
14146-
error(element, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
14166+
error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
1414714167
}
1414814168
else {
1414914169
const restExpression = (<SpreadExpression>element).expression;
@@ -21058,7 +21078,7 @@ namespace ts {
2105821078
if (node.dotDotDotToken) {
2105921079
const elements = (<BindingPattern>node.parent).elements;
2106021080
if (node !== lastOrUndefined(elements)) {
21061-
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
21081+
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
2106221082
}
2106321083

2106421084
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
},
@@ -1991,6 +1991,10 @@
19911991
"category": "Error",
19921992
"code": 2699
19931993
},
1994+
"Rest types may only be created from object types.": {
1995+
"category": "Error",
1996+
"code": 2700
1997+
},
19941998

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

0 commit comments

Comments
 (0)