Skip to content

Commit 072e938

Browse files
committed
Merge pull request #5535 from Microsoft/computedPropertiesInDestructuringPR
allow computed properties in destructuring, treat computed properties…
2 parents ad61788 + 72723e9 commit 072e938

File tree

53 files changed

+822
-178
lines changed

Some content is hidden

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

53 files changed

+822
-178
lines changed

src/compiler/binder.ts

+5
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ namespace ts {
189189
}
190190
if (node.name.kind === SyntaxKind.ComputedPropertyName) {
191191
const nameExpression = (<ComputedPropertyName>node.name).expression;
192+
// treat computed property names where expression is string/numeric literal as just string/numeric literal
193+
if (isStringOrNumericLiteral(nameExpression.kind)) {
194+
return (<LiteralExpression>nameExpression).text;
195+
}
196+
192197
Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
193198
return getPropertyNameForKnownSymbolName((<PropertyAccessExpression>nameExpression).name.text);
194199
}

src/compiler/checker.ts

+77-16
Original file line numberDiff line numberDiff line change
@@ -2342,6 +2342,26 @@ namespace ts {
23422342
return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node);
23432343
}
23442344

2345+
function getTextOfPropertyName(name: PropertyName): string {
2346+
switch (name.kind) {
2347+
case SyntaxKind.Identifier:
2348+
return (<Identifier>name).text;
2349+
case SyntaxKind.StringLiteral:
2350+
case SyntaxKind.NumericLiteral:
2351+
return (<LiteralExpression>name).text;
2352+
case SyntaxKind.ComputedPropertyName:
2353+
if (isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind)) {
2354+
return (<LiteralExpression>(<ComputedPropertyName>name).expression).text;
2355+
}
2356+
}
2357+
2358+
return undefined;
2359+
}
2360+
2361+
function isComputedNonLiteralName(name: PropertyName): boolean {
2362+
return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind);
2363+
}
2364+
23452365
// Return the inferred type for a binding element
23462366
function getTypeForBindingElement(declaration: BindingElement): Type {
23472367
const pattern = <BindingPattern>declaration.parent;
@@ -2364,10 +2384,17 @@ namespace ts {
23642384
if (pattern.kind === SyntaxKind.ObjectBindingPattern) {
23652385
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
23662386
const name = declaration.propertyName || <Identifier>declaration.name;
2387+
if (isComputedNonLiteralName(name)) {
2388+
// computed properties with non-literal names are treated as 'any'
2389+
return anyType;
2390+
}
2391+
23672392
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
23682393
// or otherwise the type of the string index signature.
2369-
type = getTypeOfPropertyOfType(parentType, name.text) ||
2370-
isNumericLiteralName(name.text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
2394+
const text = getTextOfPropertyName(name);
2395+
2396+
type = getTypeOfPropertyOfType(parentType, text) ||
2397+
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
23712398
getIndexTypeOfType(parentType, IndexKind.String);
23722399
if (!type) {
23732400
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
@@ -2478,10 +2505,18 @@ namespace ts {
24782505
// Return the type implied by an object binding pattern
24792506
function getTypeFromObjectBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type {
24802507
const members: SymbolTable = {};
2508+
let hasComputedProperties = false;
24812509
forEach(pattern.elements, e => {
2482-
const flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0);
24832510
const name = e.propertyName || <Identifier>e.name;
2484-
const symbol = <TransientSymbol>createSymbol(flags, name.text);
2511+
if (isComputedNonLiteralName(name)) {
2512+
// do not include computed properties in the implied type
2513+
hasComputedProperties = true;
2514+
return;
2515+
}
2516+
2517+
const text = getTextOfPropertyName(name);
2518+
const flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0);
2519+
const symbol = <TransientSymbol>createSymbol(flags, text);
24852520
symbol.type = getTypeFromBindingElement(e, includePatternInType);
24862521
symbol.bindingElement = e;
24872522
members[symbol.name] = symbol;
@@ -2490,6 +2525,9 @@ namespace ts {
24902525
if (includePatternInType) {
24912526
result.pattern = pattern;
24922527
}
2528+
if (hasComputedProperties) {
2529+
result.flags |= TypeFlags.ObjectLiteralPatternWithComputedProperties;
2530+
}
24932531
return result;
24942532
}
24952533

@@ -5013,7 +5051,7 @@ namespace ts {
50135051
}
50145052

50155053
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
5016-
if (someConstituentTypeHasKind(target, TypeFlags.ObjectType)) {
5054+
if (!(target.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties) && someConstituentTypeHasKind(target, TypeFlags.ObjectType)) {
50175055
for (const prop of getPropertiesOfObjectType(source)) {
50185056
if (!isKnownProperty(target, prop.name)) {
50195057
if (reportErrors) {
@@ -7467,6 +7505,7 @@ namespace ts {
74677505
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
74687506
let typeFlags: TypeFlags = 0;
74697507

7508+
let patternWithComputedProperties = false;
74707509
for (const memberDecl of node.properties) {
74717510
let member = memberDecl.symbol;
74727511
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
@@ -7494,8 +7533,11 @@ namespace ts {
74947533
if (isOptional) {
74957534
prop.flags |= SymbolFlags.Optional;
74967535
}
7536+
if (hasDynamicName(memberDecl)) {
7537+
patternWithComputedProperties = true;
7538+
}
74977539
}
7498-
else if (contextualTypeHasPattern) {
7540+
else if (contextualTypeHasPattern && !(contextualType.flags & TypeFlags.ObjectLiteralPatternWithComputedProperties)) {
74997541
// If object literal is contextually typed by the implied type of a binding pattern, and if the
75007542
// binding pattern specifies a default value for the property, make the property optional.
75017543
const impliedProp = getPropertyOfType(contextualType, member.name);
@@ -7552,7 +7594,7 @@ namespace ts {
75527594
const numberIndexType = getIndexType(IndexKind.Number);
75537595
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexType, numberIndexType);
75547596
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshObjectLiteral;
7555-
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags);
7597+
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags) | (patternWithComputedProperties ? TypeFlags.ObjectLiteralPatternWithComputedProperties : 0);
75567598
if (inDestructuringPattern) {
75577599
result.pattern = node;
75587600
}
@@ -10082,19 +10124,27 @@ namespace ts {
1008210124
const properties = node.properties;
1008310125
for (const p of properties) {
1008410126
if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) {
10085-
// TODO(andersh): Computed property support
10086-
const name = <Identifier>(<PropertyAssignment>p).name;
10127+
const name = <PropertyName>(<PropertyAssignment>p).name;
10128+
if (name.kind === SyntaxKind.ComputedPropertyName) {
10129+
checkComputedPropertyName(<ComputedPropertyName>name);
10130+
}
10131+
if (isComputedNonLiteralName(name)) {
10132+
continue;
10133+
}
10134+
10135+
const text = getTextOfPropertyName(name);
1008710136
const type = isTypeAny(sourceType)
1008810137
? sourceType
10089-
: getTypeOfPropertyOfType(sourceType, name.text) ||
10090-
isNumericLiteralName(name.text) && getIndexTypeOfType(sourceType, IndexKind.Number) ||
10138+
: getTypeOfPropertyOfType(sourceType, text) ||
10139+
isNumericLiteralName(text) && getIndexTypeOfType(sourceType, IndexKind.Number) ||
1009110140
getIndexTypeOfType(sourceType, IndexKind.String);
1009210141
if (type) {
1009310142
if (p.kind === SyntaxKind.ShorthandPropertyAssignment) {
1009410143
checkDestructuringAssignment(<ShorthandPropertyAssignment>p, type);
1009510144
}
1009610145
else {
10097-
checkDestructuringAssignment((<PropertyAssignment>p).initializer || name, type);
10146+
// non-shorthand property assignments should always have initializers
10147+
checkDestructuringAssignment((<PropertyAssignment>p).initializer, type);
1009810148
}
1009910149
}
1010010150
else {
@@ -12227,6 +12277,14 @@ namespace ts {
1222712277
checkExpressionCached(node.initializer);
1222812278
}
1222912279
}
12280+
12281+
if (node.kind === SyntaxKind.BindingElement) {
12282+
// check computed properties inside property names of binding elements
12283+
if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) {
12284+
checkComputedPropertyName(<ComputedPropertyName>node.propertyName);
12285+
}
12286+
}
12287+
1223012288
// For a binding pattern, check contained binding elements
1223112289
if (isBindingPattern(node.name)) {
1223212290
forEach((<BindingPattern>node.name).elements, checkSourceElement);
@@ -13338,11 +13396,14 @@ namespace ts {
1333813396
const enumIsConst = isConst(node);
1333913397

1334013398
for (const member of node.members) {
13341-
if (member.name.kind === SyntaxKind.ComputedPropertyName) {
13399+
if (isComputedNonLiteralName(<PropertyName>member.name)) {
1334213400
error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums);
1334313401
}
13344-
else if (isNumericLiteralName((<Identifier>member.name).text)) {
13345-
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
13402+
else {
13403+
const text = getTextOfPropertyName(<PropertyName>member.name);
13404+
if (isNumericLiteralName(text)) {
13405+
error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name);
13406+
}
1334613407
}
1334713408

1334813409
const previousEnumMemberIsNonConstant = autoValue === undefined;
@@ -15800,7 +15861,7 @@ namespace ts {
1580015861
}
1580115862

1580215863
function checkGrammarForNonSymbolComputedProperty(node: DeclarationName, message: DiagnosticMessage) {
15803-
if (node.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically((<ComputedPropertyName>node).expression)) {
15864+
if (isDynamicName(node)) {
1580415865
return grammarErrorOnNode(node, message);
1580515866
}
1580615867
}

src/compiler/emitter.ts

+16-9
Original file line numberDiff line numberDiff line change
@@ -4244,15 +4244,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
42444244
return node;
42454245
}
42464246

4247-
function createPropertyAccessForDestructuringProperty(object: Expression, propName: Identifier | LiteralExpression): Expression {
4248-
// We create a synthetic copy of the identifier in order to avoid the rewriting that might
4249-
// otherwise occur when the identifier is emitted.
4250-
const syntheticName = <Identifier | LiteralExpression>createSynthesizedNode(propName.kind);
4251-
syntheticName.text = propName.text;
4252-
if (syntheticName.kind !== SyntaxKind.Identifier) {
4253-
return createElementAccessExpression(object, syntheticName);
4254-
}
4255-
return createPropertyAccessExpression(object, syntheticName);
4247+
function createPropertyAccessForDestructuringProperty(object: Expression, propName: PropertyName): Expression {
4248+
let index: Expression;
4249+
const nameIsComputed = propName.kind === SyntaxKind.ComputedPropertyName;
4250+
if (nameIsComputed) {
4251+
index = ensureIdentifier((<ComputedPropertyName>propName).expression, /* reuseIdentifierExpression */ false);
4252+
}
4253+
else {
4254+
// We create a synthetic copy of the identifier in order to avoid the rewriting that might
4255+
// otherwise occur when the identifier is emitted.
4256+
index = <Identifier | LiteralExpression>createSynthesizedNode(propName.kind);
4257+
(<Identifier | LiteralExpression>index).text = (<Identifier | LiteralExpression>propName).text;
4258+
}
4259+
4260+
return !nameIsComputed && index.kind === SyntaxKind.Identifier
4261+
? createPropertyAccessExpression(object, <Identifier>index)
4262+
: createElementAccessExpression(object, index);
42564263
}
42574264

42584265
function createSliceCall(value: Expression, sliceIndex: number): CallExpression {

src/compiler/parser.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,7 @@ namespace ts {
10871087
token === SyntaxKind.NumericLiteral;
10881088
}
10891089

1090-
function parsePropertyNameWorker(allowComputedPropertyNames: boolean): DeclarationName {
1090+
function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName {
10911091
if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral) {
10921092
return parseLiteralNode(/*internName*/ true);
10931093
}
@@ -1097,7 +1097,7 @@ namespace ts {
10971097
return parseIdentifierName();
10981098
}
10991099

1100-
function parsePropertyName(): DeclarationName {
1100+
function parsePropertyName(): PropertyName {
11011101
return parsePropertyNameWorker(/*allowComputedPropertyNames:*/ true);
11021102
}
11031103

@@ -1207,7 +1207,7 @@ namespace ts {
12071207
case ParsingContext.ObjectLiteralMembers:
12081208
return token === SyntaxKind.OpenBracketToken || token === SyntaxKind.AsteriskToken || isLiteralPropertyName();
12091209
case ParsingContext.ObjectBindingElements:
1210-
return isLiteralPropertyName();
1210+
return token === SyntaxKind.OpenBracketToken || isLiteralPropertyName();
12111211
case ParsingContext.HeritageClauseElement:
12121212
// If we see { } then only consume it as an expression if it is followed by , or {
12131213
// That way we won't consume the body of a class in its heritage clause.
@@ -4587,15 +4587,14 @@ namespace ts {
45874587

45884588
function parseObjectBindingElement(): BindingElement {
45894589
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
4590-
// TODO(andersh): Handle computed properties
45914590
const tokenIsIdentifier = isIdentifier();
45924591
const propertyName = parsePropertyName();
45934592
if (tokenIsIdentifier && token !== SyntaxKind.ColonToken) {
45944593
node.name = <Identifier>propertyName;
45954594
}
45964595
else {
45974596
parseExpected(SyntaxKind.ColonToken);
4598-
node.propertyName = <Identifier>propertyName;
4597+
node.propertyName = propertyName;
45994598
node.name = parseIdentifierOrPattern();
46004599
}
46014600
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);

src/compiler/types.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ namespace ts {
491491

492492
export type EntityName = Identifier | QualifiedName;
493493

494+
export type PropertyName = Identifier | LiteralExpression | ComputedPropertyName;
494495
export type DeclarationName = Identifier | LiteralExpression | ComputedPropertyName | BindingPattern;
495496

496497
export interface Declaration extends Node {
@@ -543,7 +544,7 @@ namespace ts {
543544

544545
// SyntaxKind.BindingElement
545546
export interface BindingElement extends Declaration {
546-
propertyName?: Identifier; // Binding property name (in object binding pattern)
547+
propertyName?: PropertyName; // Binding property name (in object binding pattern)
547548
dotDotDotToken?: Node; // Present on rest binding element
548549
name: Identifier | BindingPattern; // Declared binding element name
549550
initializer?: Expression; // Optional initializer
@@ -587,7 +588,7 @@ namespace ts {
587588
// SyntaxKind.ShorthandPropertyAssignment
588589
// SyntaxKind.EnumMember
589590
export interface VariableLikeDeclaration extends Declaration {
590-
propertyName?: Identifier;
591+
propertyName?: PropertyName;
591592
dotDotDotToken?: Node;
592593
name: DeclarationName;
593594
questionToken?: Node;
@@ -1824,6 +1825,7 @@ namespace ts {
18241825
ContainsAnyFunctionType = 0x00800000, // Type is or contains object literal type
18251826
ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6
18261827
ThisType = 0x02000000, // This type
1828+
ObjectLiteralPatternWithComputedProperties = 0x04000000, // Object literal type implied by binding pattern has computed properties
18271829

18281830
/* @internal */
18291831
Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null,

src/compiler/utilities.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,10 @@ namespace ts {
14891489
return isFunctionLike(node) && (node.flags & NodeFlags.Async) !== 0 && !isAccessor(node);
14901490
}
14911491

1492+
export function isStringOrNumericLiteral(kind: SyntaxKind): boolean {
1493+
return kind === SyntaxKind.StringLiteral || kind === SyntaxKind.NumericLiteral;
1494+
}
1495+
14921496
/**
14931497
* A declaration has a dynamic name if both of the following are true:
14941498
* 1. The declaration has a computed property name
@@ -1497,9 +1501,13 @@ namespace ts {
14971501
* Symbol.
14981502
*/
14991503
export function hasDynamicName(declaration: Declaration): boolean {
1500-
return declaration.name &&
1501-
declaration.name.kind === SyntaxKind.ComputedPropertyName &&
1502-
!isWellKnownSymbolSyntactically((<ComputedPropertyName>declaration.name).expression);
1504+
return declaration.name && isDynamicName(declaration.name);
1505+
}
1506+
1507+
export function isDynamicName(name: DeclarationName): boolean {
1508+
return name.kind === SyntaxKind.ComputedPropertyName &&
1509+
!isStringOrNumericLiteral((<ComputedPropertyName>name).expression.kind) &&
1510+
!isWellKnownSymbolSyntactically((<ComputedPropertyName>name).expression);
15031511
}
15041512

15051513
/**

0 commit comments

Comments
 (0)