Skip to content

allow computed properties in destructuring, treat computed properties… #5535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ namespace ts {
}
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
const nameExpression = (<ComputedPropertyName>node.name).expression;
// treat computed property names where expression is string/numeric literal as just string/numeric literal
if (isStringOrNumericLiteral(nameExpression.kind)) {
return (<LiteralExpression>nameExpression).text;
}

Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
return getPropertyNameForKnownSymbolName((<PropertyAccessExpression>nameExpression).name.text);
}
Expand Down
93 changes: 77 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2337,6 +2337,26 @@ namespace ts {
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node);
}

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);
}

// Return the inferred type for a binding element
function getTypeForBindingElement(declaration: BindingElement): Type {
const pattern = <BindingPattern>declaration.parent;
Expand All @@ -2359,10 +2379,17 @@ namespace ts {
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;
}

// 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.
type = getTypeOfPropertyOfType(parentType, name.text) ||
isNumericLiteralName(name.text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
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));
Expand Down Expand Up @@ -2473,10 +2500,18 @@ namespace ts {
// Return the type implied by an object binding pattern
function getTypeFromObjectBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type {
const members: SymbolTable = {};
let hasComputedProperties = false;
forEach(pattern.elements, e => {
const flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0);
const name = e.propertyName || <Identifier>e.name;
const symbol = <TransientSymbol>createSymbol(flags, name.text);
if (isComputedNonLiteralName(name)) {
// do not include computed properties in the implied type
hasComputedProperties = true;
return;
}

const text = getTextOfPropertyName(name);
const flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0);
const symbol = <TransientSymbol>createSymbol(flags, text);
symbol.type = getTypeFromBindingElement(e, includePatternInType);
symbol.bindingElement = e;
members[symbol.name] = symbol;
Expand All @@ -2485,6 +2520,9 @@ namespace ts {
if (includePatternInType) {
result.pattern = pattern;
}
if (hasComputedProperties) {
result.flags |= TypeFlags.ObjectLiteralPatternWithComputedProperties;
}
return result;
}

Expand Down Expand Up @@ -4977,7 +5015,7 @@ namespace ts {
}

function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
if (someConstituentTypeHasKind(target, TypeFlags.ObjectType)) {
if (!(target.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties) && someConstituentTypeHasKind(target, TypeFlags.ObjectType)) {
for (const prop of getPropertiesOfObjectType(source)) {
if (!isKnownProperty(target, prop.name)) {
if (reportErrors) {
Expand Down Expand Up @@ -7395,6 +7433,7 @@ namespace ts {
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
let typeFlags: TypeFlags = 0;

let patternWithComputedProperties = false;
for (const memberDecl of node.properties) {
let member = memberDecl.symbol;
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
Expand Down Expand Up @@ -7422,8 +7461,11 @@ namespace ts {
if (isOptional) {
prop.flags |= SymbolFlags.Optional;
}
if (hasDynamicName(memberDecl)) {
patternWithComputedProperties = true;
}
}
else if (contextualTypeHasPattern) {
else if (contextualTypeHasPattern && !(contextualType.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties)) {
// If object literal is contextually typed by the implied type of a binding pattern, and if the
// binding pattern specifies a default value for the property, make the property optional.
const impliedProp = getPropertyOfType(contextualType, member.name);
Expand Down Expand Up @@ -7480,7 +7522,7 @@ namespace ts {
const numberIndexType = getIndexType(IndexKind.Number);
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexType, numberIndexType);
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshObjectLiteral;
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags);
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags) | (patternWithComputedProperties ? TypeFlags.ObjectLiteralPatternWithComputedProperties : 0);
if (inDestructuringPattern) {
result.pattern = node;
}
Expand Down Expand Up @@ -9999,19 +10041,27 @@ namespace ts {
const properties = node.properties;
for (const p of properties) {
if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) {
// TODO(andersh): Computed property support
const name = <Identifier>(<PropertyAssignment>p).name;
const name = <PropertyName>(<PropertyAssignment>p).name;
if (name.kind === SyntaxKind.ComputedPropertyName) {
checkComputedPropertyName(<ComputedPropertyName>name);
}
if (isComputedNonLiteralName(name)) {
continue;
}

const text = getTextOfPropertyName(name);
const type = isTypeAny(sourceType)
? sourceType
: getTypeOfPropertyOfType(sourceType, name.text) ||
isNumericLiteralName(name.text) && getIndexTypeOfType(sourceType, IndexKind.Number) ||
: getTypeOfPropertyOfType(sourceType, text) ||
isNumericLiteralName(text) && getIndexTypeOfType(sourceType, IndexKind.Number) ||
getIndexTypeOfType(sourceType, IndexKind.String);
if (type) {
if (p.kind === SyntaxKind.ShorthandPropertyAssignment) {
checkDestructuringAssignment(<ShorthandPropertyAssignment>p, type);
}
else {
checkDestructuringAssignment((<PropertyAssignment>p).initializer || name, type);
// non-shorthand property assignments should always have initializers
checkDestructuringAssignment((<PropertyAssignment>p).initializer, type);
}
}
else {
Expand Down Expand Up @@ -12130,6 +12180,14 @@ namespace ts {
checkExpressionCached(node.initializer);
}
}

if (node.kind === SyntaxKind.BindingElement) {
// check computed properties inside property names of binding elements
if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) {
checkComputedPropertyName(<ComputedPropertyName>node.propertyName);
}
}

// For a binding pattern, check contained binding elements
if (isBindingPattern(node.name)) {
forEach((<BindingPattern>node.name).elements, checkSourceElement);
Expand Down Expand Up @@ -13234,11 +13292,14 @@ namespace ts {
const enumIsConst = isConst(node);

for (const member of node.members) {
if (member.name.kind === SyntaxKind.ComputedPropertyName) {
if (isComputedNonLiteralName(<PropertyName>member.name)) {
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
}
else if (isNumericLiteralName((<Identifier>member.name).text)) {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
else {
const text = getTextOfPropertyName(<PropertyName>member.name);
if (isNumericLiteralName(text)) {
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
}
}

const previousEnumMemberIsNonConstant = autoValue === undefined;
Expand Down Expand Up @@ -15682,7 +15743,7 @@ namespace ts {
}

function checkGrammarForNonSymbolComputedProperty(node: DeclarationName, message: DiagnosticMessage) {
if (node.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically((<ComputedPropertyName>node).expression)) {
if (isDynamicName(node)) {
return grammarErrorOnNode(node, message);
}
}
Expand Down
25 changes: 16 additions & 9 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4167,15 +4167,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
return node;
}

function createPropertyAccessForDestructuringProperty(object: Expression, propName: Identifier | LiteralExpression): Expression {
// We create a synthetic copy of the identifier in order to avoid the rewriting that might
// otherwise occur when the identifier is emitted.
const syntheticName = <Identifier | LiteralExpression>createSynthesizedNode(propName.kind);
syntheticName.text = propName.text;
if (syntheticName.kind !== SyntaxKind.Identifier) {
return createElementAccessExpression(object, syntheticName);
}
return createPropertyAccessExpression(object, syntheticName);
function createPropertyAccessForDestructuringProperty(object: Expression, propName: PropertyName): Expression {
let index: Expression;
const nameIsComputed = propName.kind === SyntaxKind.ComputedPropertyName;
if (nameIsComputed) {
index = ensureIdentifier((<ComputedPropertyName>propName).expression, /* reuseIdentifierExpression */ false);
}
else {
// We create a synthetic copy of the identifier in order to avoid the rewriting that might
// otherwise occur when the identifier is emitted.
index = <Identifier | LiteralExpression>createSynthesizedNode(propName.kind);
(<Identifier | LiteralExpression>index).text = (<Identifier | LiteralExpression>propName).text;
}

return !nameIsComputed && index.kind === SyntaxKind.Identifier
? createPropertyAccessExpression(object, <Identifier>index)
: createElementAccessExpression(object, index);
}

function createSliceCall(value: Expression, sliceIndex: number): CallExpression {
Expand Down
9 changes: 4 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1069,7 +1069,7 @@ namespace ts {
token === SyntaxKind.NumericLiteral;
}

function parsePropertyNameWorker(allowComputedPropertyNames: boolean): DeclarationName {
function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName {
if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral) {
return parseLiteralNode(/*internName*/ true);
}
Expand All @@ -1079,7 +1079,7 @@ namespace ts {
return parseIdentifierName();
}

function parsePropertyName(): DeclarationName {
function parsePropertyName(): PropertyName {
return parsePropertyNameWorker(/*allowComputedPropertyNames:*/ true);
}

Expand Down Expand Up @@ -1189,7 +1189,7 @@ namespace ts {
case ParsingContext.ObjectLiteralMembers:
return token === SyntaxKind.OpenBracketToken || token === SyntaxKind.AsteriskToken || isLiteralPropertyName();
case ParsingContext.ObjectBindingElements:
return isLiteralPropertyName();
return token === SyntaxKind.OpenBracketToken || isLiteralPropertyName();
case ParsingContext.HeritageClauseElement:
// If we see { } then only consume it as an expression if it is followed by , or {
// That way we won't consume the body of a class in its heritage clause.
Expand Down Expand Up @@ -4568,15 +4568,14 @@ namespace ts {

function parseObjectBindingElement(): BindingElement {
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
// TODO(andersh): Handle computed properties
const tokenIsIdentifier = isIdentifier();
const propertyName = parsePropertyName();
if (tokenIsIdentifier && token !== SyntaxKind.ColonToken) {
node.name = <Identifier>propertyName;
}
else {
parseExpected(SyntaxKind.ColonToken);
node.propertyName = <Identifier>propertyName;
node.propertyName = propertyName;
node.name = parseIdentifierOrPattern();
}
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ namespace ts {

export type EntityName = Identifier | QualifiedName;

export type PropertyName = Identifier | LiteralExpression | ComputedPropertyName;
export type DeclarationName = Identifier | LiteralExpression | ComputedPropertyName | BindingPattern;

export interface Declaration extends Node {
Expand Down Expand Up @@ -543,7 +544,7 @@ namespace ts {

// SyntaxKind.BindingElement
export interface BindingElement extends Declaration {
propertyName?: Identifier; // Binding property name (in object binding pattern)
propertyName?: PropertyName; // Binding property name (in object binding pattern)
dotDotDotToken?: Node; // Present on rest binding element
name: Identifier | BindingPattern; // Declared binding element name
initializer?: Expression; // Optional initializer
Expand Down Expand Up @@ -587,7 +588,7 @@ namespace ts {
// SyntaxKind.ShorthandPropertyAssignment
// SyntaxKind.EnumMember
export interface VariableLikeDeclaration extends Declaration {
propertyName?: Identifier;
propertyName?: PropertyName;
dotDotDotToken?: Node;
name: DeclarationName;
questionToken?: Node;
Expand Down Expand Up @@ -1821,6 +1822,7 @@ namespace ts {
ContainsAnyFunctionType = 0x00800000, // Type is or contains object literal type
ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6
ThisType = 0x02000000, // This type
ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties

/* @internal */
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,
Expand Down
14 changes: 11 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,10 @@ namespace ts {
return isFunctionLike(node) && (node.flags & NodeFlags.Async) !== 0 && !isAccessor(node);
}

export function isStringOrNumericLiteral(kind: SyntaxKind): boolean {
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral;
}

/**
* A declaration has a dynamic name if both of the following are true:
* 1. The declaration has a computed property name
Expand All @@ -1435,9 +1439,13 @@ namespace ts {
* Symbol.
*/
export function hasDynamicName(declaration: Declaration): boolean {
return declaration.name &&
declaration.name.kind === SyntaxKind.ComputedPropertyName &&
!isWellKnownSymbolSyntactically((<ComputedPropertyName>declaration.name).expression);
return declaration.name && isDynamicName(declaration.name);
}

export function isDynamicName(name: DeclarationName): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i find the name a bit vague.

return name.kind === SyntaxKind.ComputedPropertyName &&
!isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind) &&
!isWellKnownSymbolSyntactically((<ComputedPropertyName>name).expression);
}

/**
Expand Down
Loading