Spread+rest fixes

1. Rename finds identifiers in spread assignment expressions.
2. Spreads with computed properties of type number or any no longer
crash the compiler.
3. Rest emit uses indexOf === -1 instead of !indexOf to filter properties.
4. Rest emit correctly refers to computed properties' generated temps.
This commit is contained in:
Nathan Shively-Sanders
2016-11-14 16:17:59 -08:00
parent f437c8f318
commit 6f274eaabe
4 changed files with 53 additions and 22 deletions

View File

@@ -11164,10 +11164,10 @@ namespace ts {
return links.resolvedType;
}
function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, properties: Symbol[], kind: IndexKind): IndexInfo {
function getObjectLiteralIndexInfo(propertyNodes: NodeArray<ObjectLiteralElementLike>, offset: number, properties: Symbol[], kind: IndexKind): IndexInfo {
const propTypes: Type[] = [];
for (let i = 0; i < properties.length; i++) {
if (kind === IndexKind.String || isNumericName(node.properties[i].name)) {
if (kind === IndexKind.String || isNumericName(propertyNodes[i + offset].name)) {
propTypes.push(getTypeOfSymbol(properties[i]));
}
}
@@ -11193,7 +11193,10 @@ namespace ts {
let hasComputedStringProperty = false;
let hasComputedNumberProperty = false;
for (const memberDecl of node.properties) {
let i = 0;
let offset = 0;
for (i = 0; i < node.properties.length; i++) {
const memberDecl = node.properties[i];
let member = memberDecl.symbol;
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ||
@@ -11262,6 +11265,7 @@ namespace ts {
return unknownType;
}
spread = getSpreadType(spread, type, /*isFromObjectLiteral*/ false);
offset = i + 1;
continue;
}
else {
@@ -11315,8 +11319,8 @@ namespace ts {
return createObjectLiteralType();
function createObjectLiteralType() {
const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.String) : undefined;
const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, propertiesArray, IndexKind.Number) : undefined;
const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node.properties, offset, propertiesArray, IndexKind.String) : undefined;
const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node.properties, offset, propertiesArray, IndexKind.Number) : undefined;
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshLiteral;
result.flags |= TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags);

View File

@@ -45,7 +45,7 @@ var __assign = (this && this.__assign) || Object.assign || function(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))
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) === -1)
t[p] = s[p];
return t;
};`;

View File

@@ -304,17 +304,19 @@ namespace ts {
if (properties.length !== 1) {
// For anything but a single element destructuring we need to generate a temporary
// to ensure value is evaluated exactly once.
// When doing so we want to hightlight the passed in source map node since thats the one needing this temp assignment
// When doing so we want to highlight the passed in source map node since that's the one needing this temp assignment
value = ensureIdentifier(value, /*reuseIdentifierExpressions*/ true, location, emitTempVariableAssignment);
}
let bindingElements: ObjectLiteralElementLike[] = [];
let computedTempVariables: Expression[];
for (let i = 0; i < properties.length; i++) {
const p = properties[i];
if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) {
if (!transformRest ||
p.transformFlags & TransformFlags.ContainsSpreadExpression ||
(p.kind === SyntaxKind.PropertyAssignment && p.initializer.transformFlags & TransformFlags.ContainsSpreadExpression)) {
(p.kind === SyntaxKind.PropertyAssignment && p.initializer.transformFlags & TransformFlags.ContainsSpreadExpression) ||
isComputedPropertyName(p.name)) {
if (bindingElements.length) {
emitRestAssignment(bindingElements, value, location, target);
bindingElements = [];
@@ -322,7 +324,11 @@ namespace ts {
const propName = <Identifier | LiteralExpression>(<PropertyAssignment>p).name;
const bindingTarget = p.kind === SyntaxKind.ShorthandPropertyAssignment ? <ShorthandPropertyAssignment>p : (<PropertyAssignment>p).initializer || propName;
// Assignment for bindingTarget = value.propName should highlight whole property, hence use p as source map node
emitDestructuringAssignment(bindingTarget, createDestructuringPropertyAccess(value, propName), p);
const propAccess = createDestructuringPropertyAccess(value, propName);
if (isComputedPropertyName(propName)) {
(computedTempVariables = computedTempVariables || []).push((propAccess as ElementAccessExpression).argumentExpression);
}
emitDestructuringAssignment(bindingTarget, propAccess, p);
}
else {
bindingElements.push(p);
@@ -336,7 +342,7 @@ namespace ts {
bindingElements = [];
}
const propName = (p as SpreadAssignment).expression as Identifier;
const restCall = createRestCall(value, target.properties, p => p.name, target);
const restCall = createRestCall(value, target.properties, p => p.name, target, computedTempVariables);
emitDestructuringAssignment(propName, restCall, p);
}
}
@@ -413,17 +419,28 @@ namespace ts {
/** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement
* `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);`*/
function createRestCall<T extends Node>(value: Expression, elements: T[], getPropertyName: (element: T) => PropertyName, location: TextRange): Expression {
const propertyNames: LiteralExpression[] = [];
function createRestCall<T extends Node>(value: Expression, elements: T[], getPropertyName: (element: T) => PropertyName, location: TextRange, computedTempVariables: Expression[]): Expression {
const propertyNames: Expression[] = [];
for (let i = 0; i < elements.length - 1; i++) {
if (isOmittedExpression(elements[i])) {
const element = elements[i];
if (isOmittedExpression(element)) {
continue;
}
const str = <StringLiteral>createSynthesizedNode(SyntaxKind.StringLiteral);
str.pos = location.pos;
str.end = location.end;
str.text = getTextOfPropertyName(getPropertyName(elements[i]));
propertyNames.push(str);
if (isComputedPropertyName(getPropertyName(element))) {
// get the temp name and put that in there instead, like `_tmp + ""`
const stringifiedTemp = <StringLiteral>createSynthesizedNode(SyntaxKind.StringLiteral);
stringifiedTemp.pos = location.pos;
stringifiedTemp.end = location.end;
stringifiedTemp.text = "";
propertyNames.push(createBinary(computedTempVariables.shift(), SyntaxKind.PlusToken, stringifiedTemp));
}
else {
const str = <StringLiteral>createSynthesizedNode(SyntaxKind.StringLiteral);
str.pos = location.pos;
str.end = location.end;
str.text = getTextOfPropertyName(getPropertyName(element));
propertyNames.push(str);
}
}
const args = createSynthesizedNodeArray([value, createArrayLiteral(propertyNames, location)]);
return createCall(createIdentifier("__rest"), undefined, args);
@@ -522,6 +539,7 @@ namespace ts {
const elements = name.elements;
const numElements = elements.length;
let bindingElements: BindingElement[] = [];
let computedTempVariables: Expression[];
for (let i = 0; i < numElements; i++) {
const element = elements[i];
if (isOmittedExpression(element)) {
@@ -533,12 +551,15 @@ namespace ts {
bindingElements = [];
}
const restCall = createRestCall(value,
name.elements,
elements, // name.elements,
element => (element as BindingElement).propertyName || <Identifier>(element as BindingElement).name,
name);
name,
computedTempVariables);
emitBindingElement(element, restCall);
}
else if (transformRest && !(element.transformFlags & TransformFlags.ContainsSpreadExpression)) {
else if (transformRest &&
!(element.transformFlags & TransformFlags.ContainsSpreadExpression) &&
!isComputedPropertyName(element.propertyName || element.name)) {
// do not emit until we have a complete bundle of ES2015 syntax
bindingElements.push(element);
}
@@ -548,8 +569,13 @@ namespace ts {
bindingElements = [];
}
// Rewrite element to a declaration with an initializer that fetches property
// TODO: Probably save the result of createDestructuringPropertyAccess if propName is a computed property
const propName = element.propertyName || <Identifier>element.name;
emitBindingElement(element, createDestructuringPropertyAccess(value, propName));
const propAccess = createDestructuringPropertyAccess(value, propName);
if (isComputedPropertyName(propName)) {
(computedTempVariables = computedTempVariables || []).push((propAccess as ElementAccessExpression).argumentExpression);
}
emitBindingElement(element, propAccess);
}
}
if (bindingElements.length) {

View File

@@ -1262,6 +1262,7 @@ namespace ts {
case SyntaxKind.Decorator:
case SyntaxKind.JsxExpression:
case SyntaxKind.JsxSpreadAttribute:
case SyntaxKind.SpreadAssignment:
return true;
case SyntaxKind.ExpressionWithTypeArguments:
return (<ExpressionWithTypeArguments>parent).expression === node && isExpressionWithTypeArgumentsInClassExtendsClause(parent);