Skip to content

Commit

Permalink
Merge pull request #12028 from Microsoft/object-rest
Browse files Browse the repository at this point in the history
Object rest
  • Loading branch information
sandersn authored Nov 10, 2016
2 parents 9b1f43b + 0196947 commit 25462c9
Show file tree
Hide file tree
Showing 43 changed files with 2,968 additions and 1,347 deletions.
80 changes: 69 additions & 11 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1918,6 +1918,9 @@ namespace ts {
return bindParameter(<ParameterDeclaration>node);
case SyntaxKind.VariableDeclaration:
case SyntaxKind.BindingElement:
if ((node as BindingElement).dotDotDotToken && node.parent.kind === SyntaxKind.ObjectBindingPattern) {
emitFlags |= NodeFlags.HasRestAttribute;
}
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>node);
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
Expand All @@ -1933,7 +1936,19 @@ namespace ts {

case SyntaxKind.SpreadAssignment:
case SyntaxKind.JsxSpreadAttribute:
emitFlags |= NodeFlags.HasSpreadAttribute;
let root = container;
let hasRest = false;
while (root.parent) {
if (root.kind === SyntaxKind.ObjectLiteralExpression &&
root.parent.kind === SyntaxKind.BinaryExpression &&
(root.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken &&
(root.parent as BinaryExpression).left === root) {
hasRest = true;
break;
}
root = root.parent;
}
emitFlags |= hasRest ? NodeFlags.HasRestAttribute : NodeFlags.HasSpreadAttribute;
return;

case SyntaxKind.CallSignature:
Expand Down Expand Up @@ -2544,10 +2559,13 @@ namespace ts {
const operatorTokenKind = node.operatorToken.kind;
const leftKind = node.left.kind;

if (operatorTokenKind === SyntaxKind.EqualsToken
&& (leftKind === SyntaxKind.ObjectLiteralExpression
|| leftKind === SyntaxKind.ArrayLiteralExpression)) {
// Destructuring assignments are ES6 syntax.
if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) {
// Destructuring object assignments with are ES2015 syntax
// and possibly ESNext if they contain rest
transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment;
}
else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ArrayLiteralExpression) {
// Destructuring assignments are ES2015 syntax.
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment;
}
else if (operatorTokenKind === SyntaxKind.AsteriskAsteriskToken
Expand Down Expand Up @@ -2581,6 +2599,11 @@ namespace ts {
transformFlags |= TransformFlags.AssertTypeScript | TransformFlags.ContainsParameterPropertyAssignments;
}

// parameters with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
transformFlags |= TransformFlags.AssertESNext;
}

// If a parameter has an initializer, a binding pattern or a dotDotDot token, then
// it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel.
if (subtreeFlags & TransformFlags.ContainsBindingPattern || initializer || dotDotDotToken) {
Expand Down Expand Up @@ -2814,6 +2837,11 @@ namespace ts {
transformFlags |= TransformFlags.AssertES2017;
}

// function declarations with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
transformFlags |= TransformFlags.AssertESNext;
}

// If a FunctionDeclaration's subtree has marked the container as needing to capture the
// lexical this, or the function contains parameters with initializers, then this node is
// ES6 syntax.
Expand Down Expand Up @@ -2851,6 +2879,12 @@ namespace ts {
transformFlags |= TransformFlags.AssertES2017;
}

// function expressions with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
transformFlags |= TransformFlags.AssertESNext;
}


// If a FunctionExpression's subtree has marked the container as needing to capture the
// lexical this, or the function contains parameters with initializers, then this node is
// ES6 syntax.
Expand Down Expand Up @@ -2888,6 +2922,11 @@ namespace ts {
transformFlags |= TransformFlags.AssertES2017;
}

// arrow functions with object rest destructuring are ES Next syntax
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
transformFlags |= TransformFlags.AssertESNext;
}

// If an ArrowFunction contains a lexical this, its container must capture the lexical this.
if (subtreeFlags & TransformFlags.ContainsLexicalThis) {
transformFlags |= TransformFlags.ContainsCapturedLexicalThis;
Expand Down Expand Up @@ -2916,8 +2955,13 @@ namespace ts {
let transformFlags = subtreeFlags;
const nameKind = node.name.kind;

// A VariableDeclaration with a binding pattern is ES6 syntax.
if (nameKind === SyntaxKind.ObjectBindingPattern || nameKind === SyntaxKind.ArrayBindingPattern) {
// A VariableDeclaration with an object binding pattern is ES2015 syntax
// and possibly ESNext syntax if it contains an object binding pattern
if (nameKind === SyntaxKind.ObjectBindingPattern) {
transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
}
// A VariableDeclaration with an object binding pattern is ES2015 syntax.
else if (nameKind === SyntaxKind.ArrayBindingPattern) {
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
}

Expand Down Expand Up @@ -3058,14 +3102,17 @@ namespace ts {
transformFlags |= TransformFlags.AssertJsx;
break;

case SyntaxKind.ForOfStatement:
// for-of might be ESNext if it has a rest destructuring
transformFlags |= TransformFlags.AssertESNext;
// FALLTHROUGH
case SyntaxKind.NoSubstitutionTemplateLiteral:
case SyntaxKind.TemplateHead:
case SyntaxKind.TemplateMiddle:
case SyntaxKind.TemplateTail:
case SyntaxKind.TemplateExpression:
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.ForOfStatement:
case SyntaxKind.StaticKeyword:
// These nodes are ES6 syntax.
transformFlags |= TransformFlags.AssertES2015;
Expand Down Expand Up @@ -3131,10 +3178,16 @@ namespace ts {

case SyntaxKind.SpreadElement:
case SyntaxKind.SpreadAssignment:
// This node is ES6 or ES future syntax, but is handled by a containing node.
// This node is ES6 or ES next syntax, but is handled by a containing node.
transformFlags |= TransformFlags.ContainsSpreadExpression;
break;

case SyntaxKind.BindingElement:
if ((node as BindingElement).dotDotDotToken) {
// this node is ES2015 or ES next syntax, but is handled by a containing node.
transformFlags |= TransformFlags.ContainsSpreadExpression;
}

case SyntaxKind.SuperKeyword:
// This node is ES6 syntax.
transformFlags |= TransformFlags.AssertES2015;
Expand All @@ -3147,8 +3200,13 @@ namespace ts {

case SyntaxKind.ObjectBindingPattern:
case SyntaxKind.ArrayBindingPattern:
// These nodes are ES6 syntax.
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
// These nodes are ES2015 or ES Next syntax.
if (subtreeFlags & TransformFlags.ContainsSpreadExpression) {
transformFlags |= TransformFlags.AssertESNext | TransformFlags.ContainsBindingPattern;
}
else {
transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern;
}
break;

case SyntaxKind.Decorator:
Expand Down
96 changes: 58 additions & 38 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2982,26 +2982,31 @@ namespace ts {
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false);
}

function getTextOfPropertyName(name: PropertyName): string {
switch (name.kind) {
case SyntaxKind.Identifier:
return (<Identifier>name).text;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
return (<LiteralExpression>name).text;
case SyntaxKind.ComputedPropertyName:
if (isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind)) {
return (<LiteralExpression>(<ComputedPropertyName>name).expression).text;
}
}

return undefined;
}

function isComputedNonLiteralName(name: PropertyName): boolean {
return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind);
}

function getRestType(source: Type, properties: PropertyName[], symbol: Symbol): Type {
Debug.assert(!!(source.flags & TypeFlags.Object), "Rest types only support object types right now.");
const members = createMap<Symbol>();
const names = createMap<true>();
for (const name of properties) {
names[getTextOfPropertyName(name)] = true;
}
for (const prop of getPropertiesOfType(source)) {
const inNamesToRemove = prop.name in names;
const isPrivate = getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected);
const isMethod = prop.flags & SymbolFlags.Method;
const isSetOnlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
if (!inNamesToRemove && !isPrivate && !isMethod && !isSetOnlyAccessor) {
members[prop.name] = prop;
}
}
const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String);
const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number);
return createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
}

/** Return the inferred type for a binding element */
function getTypeForBindingElement(declaration: BindingElement): Type {
const pattern = <BindingPattern>declaration.parent;
Expand All @@ -3022,26 +3027,41 @@ namespace ts {

let type: Type;
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
const name = declaration.propertyName || <Identifier>declaration.name;
if (isComputedNonLiteralName(name)) {
// computed properties with non-literal names are treated as 'any'
return anyType;
}
if (declaration.initializer) {
getContextualType(declaration.initializer);
if (declaration.dotDotDotToken) {
if (!(parentType.flags & TypeFlags.Object)) {
error(declaration, Diagnostics.Rest_types_may_only_be_created_from_object_types);
return unknownType;
}
const literalMembers: PropertyName[] = [];
for (const element of pattern.elements) {
if (element.kind !== SyntaxKind.OmittedExpression && !(element as BindingElement).dotDotDotToken) {
literalMembers.push(element.propertyName || element.name as Identifier);
}
}
type = getRestType(parentType, literalMembers, declaration.symbol);
}
else {
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
const name = declaration.propertyName || <Identifier>declaration.name;
if (isComputedNonLiteralName(name)) {
// computed properties with non-literal names are treated as 'any'
return anyType;
}
if (declaration.initializer) {
getContextualType(declaration.initializer);
}

// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
// or otherwise the type of the string index signature.
const text = getTextOfPropertyName(name);
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
// or otherwise the type of the string index signature.
const text = getTextOfPropertyName(name);

type = getTypeOfPropertyOfType(parentType, text) ||
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
getIndexTypeOfType(parentType, IndexKind.String);
if (!type) {
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
return unknownType;
type = getTypeOfPropertyOfType(parentType, text) ||
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
getIndexTypeOfType(parentType, IndexKind.String);
if (!type) {
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
return unknownType;
}
}
}
else {
Expand Down Expand Up @@ -3249,8 +3269,8 @@ namespace ts {
let hasComputedProperties = false;
forEach(pattern.elements, e => {
const name = e.propertyName || <Identifier>e.name;
if (isComputedNonLiteralName(name)) {
// do not include computed properties in the implied type
if (isComputedNonLiteralName(name) || e.dotDotDotToken) {
// do not include computed properties or rests in the implied type
hasComputedProperties = true;
return;
}
Expand Down Expand Up @@ -13889,7 +13909,7 @@ namespace ts {
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(objectLiteralType), declarationNameToString(name));
}
}
else {
else if (property.kind !== SyntaxKind.SpreadAssignment) {
error(property, Diagnostics.Property_assignment_expected);
}
}
Expand Down Expand Up @@ -13935,7 +13955,7 @@ namespace ts {
}
else {
if (elementIndex < elements.length - 1) {
error(element, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
}
else {
const restExpression = (<SpreadElement>element).expression;
Expand Down Expand Up @@ -20832,7 +20852,7 @@ namespace ts {
if (node.dotDotDotToken) {
const elements = (<BindingPattern>node.parent).elements;
if (node !== lastOrUndefined(elements)) {
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_an_array_destructuring_pattern);
return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern);
}

if (node.name.kind === SyntaxKind.ArrayBindingPattern || node.name.kind === SyntaxKind.ObjectBindingPattern) {
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1455,7 +1455,7 @@
"category": "Error",
"code": 2461
},
"A rest element must be last in an array destructuring pattern": {
"A rest element must be last in a destructuring pattern": {
"category": "Error",
"code": 2462
},
Expand Down Expand Up @@ -1987,6 +1987,10 @@
"category": "Error",
"code": 2698
},
"Rest types may only be created from object types.": {
"category": "Error",
"code": 2700
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ var __assign = (this && this.__assign) || Object.assign || function(t) {
return t;
};`;

const restHelper = `
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && !e.indexOf(p))
t[p] = s[p];
return t;
};`;

// emit output for the __decorate helper function
const decorateHelper = `
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
Expand Down Expand Up @@ -226,6 +234,7 @@ const _super = (function (geti, seti) {
let currentFileIdentifiers: Map<string>;
let extendsEmitted: boolean;
let assignEmitted: boolean;
let restEmitted: boolean;
let decorateEmitted: boolean;
let paramEmitted: boolean;
let awaiterEmitted: boolean;
Expand Down Expand Up @@ -2222,6 +2231,11 @@ const _super = (function (geti, seti) {
assignEmitted = true;
}

if (languageVersion < ScriptTarget.ESNext && !restEmitted && node.flags & NodeFlags.HasRestAttribute) {
writeLines(restHelper);
restEmitted = true;
}

if (!decorateEmitted && node.flags & NodeFlags.HasDecorators) {
writeLines(decorateHelper);
if (compilerOptions.emitDecoratorMetadata) {
Expand Down
Loading

0 comments on commit 25462c9

Please sign in to comment.