Skip to content

Commit bd8c625

Browse files
authored
Merge pull request #29422 from Microsoft/fix29006
Fix crash in getTextOfPropertyName
2 parents ebf9087 + 469ab3f commit bd8c625

18 files changed

+402
-43
lines changed

src/compiler/checker.ts

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5280,17 +5280,18 @@ namespace ts {
52805280
let objectFlags = ObjectFlags.ObjectLiteral;
52815281
forEach(pattern.elements, e => {
52825282
const name = e.propertyName || <Identifier>e.name;
5283-
if (isComputedNonLiteralName(name)) {
5284-
// do not include computed properties in the implied type
5285-
objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties;
5286-
return;
5287-
}
52885283
if (e.dotDotDotToken) {
52895284
stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
52905285
return;
52915286
}
52925287

5293-
const text = getTextOfPropertyName(name);
5288+
const exprType = getLiteralTypeFromPropertyName(name);
5289+
if (!isTypeUsableAsPropertyName(exprType)) {
5290+
// do not include computed properties in the implied type
5291+
objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties;
5292+
return;
5293+
}
5294+
const text = getPropertyNameFromType(exprType);
52945295
const flags = SymbolFlags.Property | (e.initializer ? SymbolFlags.Optional : 0);
52955296
const symbol = createSymbol(flags, text);
52965297
symbol.type = getTypeFromBindingElement(e, includePatternInType, reportErrors);
@@ -6399,9 +6400,9 @@ namespace ts {
63996400
}
64006401

64016402
/**
6402-
* Indicates whether a type can be used as a late-bound name.
6403+
* Indicates whether a type can be used as a property name.
64036404
*/
6404-
function isTypeUsableAsLateBoundName(type: Type): type is LiteralType | UniqueESSymbolType {
6405+
function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType {
64056406
return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique);
64066407
}
64076408

@@ -6416,7 +6417,7 @@ namespace ts {
64166417
function isLateBindableName(node: DeclarationName): node is LateBoundName {
64176418
return isComputedPropertyName(node)
64186419
&& isEntityNameExpression(node.expression)
6419-
&& isTypeUsableAsLateBoundName(checkComputedPropertyName(node));
6420+
&& isTypeUsableAsPropertyName(checkComputedPropertyName(node));
64206421
}
64216422

64226423
function isLateBoundName(name: __String): boolean {
@@ -6448,11 +6449,11 @@ namespace ts {
64486449
}
64496450

64506451
/**
6451-
* Gets the symbolic name for a late-bound member from its type.
6452+
* Gets the symbolic name for a member from its type.
64526453
*/
6453-
function getLateBoundNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String {
6454+
function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String {
64546455
if (type.flags & TypeFlags.UniqueESSymbol) {
6455-
return `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
6456+
return (<UniqueESSymbolType>type).escapedName;
64566457
}
64576458
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
64586459
return escapeLeadingUnderscores("" + (<StringLiteralType | NumberLiteralType>type).value);
@@ -6518,8 +6519,8 @@ namespace ts {
65186519
// fall back to the early-bound name of this member.
65196520
links.resolvedSymbol = decl.symbol;
65206521
const type = checkComputedPropertyName(decl.name);
6521-
if (isTypeUsableAsLateBoundName(type)) {
6522-
const memberName = getLateBoundNameFromType(type);
6522+
if (isTypeUsableAsPropertyName(type)) {
6523+
const memberName = getPropertyNameFromType(type);
65236524
const symbolFlags = decl.symbol.flags;
65246525

65256526
// Get or add a late-bound symbol for the member. This allows us to merge late-bound accessor declarations.
@@ -7168,8 +7169,8 @@ namespace ts {
71687169
const propType = instantiateType(templateType, templateMapper);
71697170
// If the current iteration type constituent is a string literal type, create a property.
71707171
// Otherwise, for type string create a string index signature.
7171-
if (t.flags & TypeFlags.StringOrNumberLiteralOrUnique) {
7172-
const propName = getLateBoundNameFromType(t as LiteralType);
7172+
if (isTypeUsableAsPropertyName(t)) {
7173+
const propName = getPropertyNameFromType(t);
71737174
const modifiersProp = getPropertyOfType(modifiersType, propName);
71747175
const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional ||
71757176
!(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
@@ -7354,7 +7355,8 @@ namespace ts {
73547355
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
73557356
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
73567357
return list.some(property => {
7357-
const name = property.name && getTextOfPropertyName(property.name);
7358+
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
7359+
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
73587360
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
73597361
return !!expected && isLiteralType(expected) && !isTypeIdenticalTo(getTypeOfNode(property), expected);
73607362
});
@@ -9722,8 +9724,8 @@ namespace ts {
97229724

97239725
function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, cacheSymbol: boolean, missingType: Type) {
97249726
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
9725-
const propName = isTypeUsableAsLateBoundName(indexType) ?
9726-
getLateBoundNameFromType(indexType) :
9727+
const propName = isTypeUsableAsPropertyName(indexType) ?
9728+
getPropertyNameFromType(indexType) :
97279729
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
97289730
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
97299731
accessNode && isPropertyName(accessNode) ?
@@ -10413,6 +10415,7 @@ namespace ts {
1041310415
function createUniqueESSymbolType(symbol: Symbol) {
1041410416
const type = <UniqueESSymbolType>createType(TypeFlags.UniqueESSymbol);
1041510417
type.symbol = symbol;
10418+
type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String;
1041610419
return type;
1041710420
}
1041810421

@@ -11300,7 +11303,7 @@ namespace ts {
1130011303
}
1130111304
if (resultObj.error) {
1130211305
const reportedDiag = resultObj.error;
11303-
const propertyName = isTypeUsableAsLateBoundName(nameType) ? getLateBoundNameFromType(nameType) : undefined;
11306+
const propertyName = isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1130411307
const targetProp = propertyName !== undefined ? getPropertyOfType(target, propertyName) : undefined;
1130511308

1130611309
let issuedElaboration = false;
@@ -15172,7 +15175,9 @@ namespace ts {
1517215175
}
1517315176

1517415177
function getTypeOfDestructuredProperty(type: Type, name: PropertyName) {
15175-
const text = getTextOfPropertyName(name);
15178+
const nameType = getLiteralTypeFromPropertyName(name);
15179+
if (!isTypeUsableAsPropertyName(nameType)) return errorType;
15180+
const text = getPropertyNameFromType(nameType);
1517615181
return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) ||
1517715182
isNumericLiteralName(text) && getIndexTypeOfType(type, IndexKind.Number) ||
1517815183
getIndexTypeOfType(type, IndexKind.String) ||
@@ -17304,9 +17309,10 @@ namespace ts {
1730417309
const parentDeclaration = declaration.parent.parent;
1730517310
const name = declaration.propertyName || declaration.name;
1730617311
const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration);
17307-
if (parentType && !isBindingPattern(name)) {
17308-
const text = getTextOfPropertyName(name);
17309-
if (text !== undefined) {
17312+
if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) {
17313+
const nameType = getLiteralTypeFromPropertyName(name);
17314+
if (isTypeUsableAsPropertyName(nameType)) {
17315+
const text = getPropertyNameFromType(nameType);
1731017316
return getTypeOfPropertyOfType(parentType, text);
1731117317
}
1731217318
}
@@ -18263,10 +18269,9 @@ namespace ts {
1826318269
}
1826418270
}
1826518271
typeFlags |= type.flags;
18266-
const nameType = computedNameType && computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique ?
18267-
<LiteralType | UniqueESSymbolType>computedNameType : undefined;
18272+
const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined;
1826818273
const prop = nameType ?
18269-
createSymbol(SymbolFlags.Property | member.flags, getLateBoundNameFromType(nameType), CheckFlags.Late) :
18274+
createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), CheckFlags.Late) :
1827018275
createSymbol(SymbolFlags.Property | member.flags, member.escapedName);
1827118276
if (nameType) {
1827218277
prop.nameType = nameType;
@@ -22315,15 +22320,15 @@ namespace ts {
2231522320
function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray<ObjectLiteralElementLike>, rightIsThis = false) {
2231622321
if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) {
2231722322
const name = property.name;
22318-
const text = getTextOfPropertyName(name);
22319-
if (text) {
22323+
const exprType = getLiteralTypeFromPropertyName(name);
22324+
if (isTypeUsableAsPropertyName(exprType)) {
22325+
const text = getPropertyNameFromType(exprType);
2232022326
const prop = getPropertyOfType(objectLiteralType, text);
2232122327
if (prop) {
2232222328
markPropertyAsReferenced(prop, property, rightIsThis);
2232322329
checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop);
2232422330
}
2232522331
}
22326-
const exprType = getLiteralTypeFromPropertyName(name);
2232722332
const elementType = getIndexedAccessType(objectLiteralType, exprType, name);
2232822333
const type = getFlowTypeOfDestructuring(property, elementType);
2232922334
return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type);
@@ -25638,13 +25643,14 @@ namespace ts {
2563825643
const parent = node.parent.parent;
2563925644
const parentType = getTypeForBindingElementParent(parent);
2564025645
const name = node.propertyName || node.name;
25641-
if (!isBindingPattern(name)) {
25642-
const nameText = getTextOfPropertyName(name);
25643-
if (nameText) {
25644-
const property = getPropertyOfType(parentType!, nameText); // TODO: GH#18217
25646+
if (!isBindingPattern(name) && parentType) {
25647+
const exprType = getLiteralTypeFromPropertyName(name);
25648+
if (isTypeUsableAsPropertyName(exprType)) {
25649+
const nameText = getPropertyNameFromType(exprType);
25650+
const property = getPropertyOfType(parentType, nameText);
2564525651
if (property) {
2564625652
markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference.
25647-
checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType!, property);
25653+
checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType, property);
2564825654
}
2564925655
}
2565025656
}

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3955,6 +3955,7 @@ namespace ts {
39553955
// Unique symbol types (TypeFlags.UniqueESSymbol)
39563956
export interface UniqueESSymbolType extends Type {
39573957
symbol: Symbol;
3958+
escapedName: __String;
39583959
}
39593960

39603961
export interface StringLiteralType extends LiteralType {

src/compiler/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,8 @@ namespace ts {
778778
case SyntaxKind.NoSubstitutionTemplateLiteral:
779779
return escapeLeadingUnderscores(name.text);
780780
case SyntaxKind.ComputedPropertyName:
781-
return isStringOrNumericLiteralLike(name.expression) ? escapeLeadingUnderscores(name.expression.text) : undefined!; // TODO: GH#18217 Almost all uses of this assume the result to be defined!
781+
if (isStringOrNumericLiteralLike(name.expression)) return escapeLeadingUnderscores(name.expression.text);
782+
return Debug.fail("Text of property name cannot be read from non-literal-valued ComputedPropertyNames");
782783
default:
783784
return Debug.assertNever(name);
784785
}

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,7 @@ declare namespace ts {
22172217
}
22182218
interface UniqueESSymbolType extends Type {
22192219
symbol: Symbol;
2220+
escapedName: __String;
22202221
}
22212222
interface StringLiteralType extends LiteralType {
22222223
value: string;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,7 @@ declare namespace ts {
22172217
}
22182218
interface UniqueESSymbolType extends Type {
22192219
symbol: Symbol;
2220+
escapedName: __String;
22202221
}
22212222
interface StringLiteralType extends LiteralType {
22222223
value: string;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts(23,24): error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
2+
3+
4+
==== tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts (1 errors) ====
5+
// https://github.com/Microsoft/TypeScript/issues/29006
6+
export interface A { type: 'a' }
7+
export interface B { type: 'b' }
8+
export type AB = A | B
9+
10+
const itemId = 'some-id'
11+
12+
// --- test on first level ---
13+
const items: { [id: string]: AB } = {}
14+
const { [itemId]: itemOk1 } = items
15+
typeof itemOk1 // pass
16+
17+
// --- test on second level ---
18+
interface ObjWithItems {
19+
items: {[s: string]: AB}
20+
}
21+
const objWithItems: ObjWithItems = { items: {}}
22+
23+
const itemOk2 = objWithItems.items[itemId]
24+
typeof itemOk2 // pass
25+
26+
const {
27+
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
28+
~~~~~~~~~~~~~~~
29+
!!! error TS2525: Initializer provides no value for this binding element and the binding element has no default value.
30+
} = objWithItems
31+
32+
// in order to re-produce the error, uncomment next line:
33+
typeof itemWithTSError // :(
34+
35+
// will result in:
36+
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [crashInGetTextOfComputedPropertyName.ts]
2+
// https://github.com/Microsoft/TypeScript/issues/29006
3+
export interface A { type: 'a' }
4+
export interface B { type: 'b' }
5+
export type AB = A | B
6+
7+
const itemId = 'some-id'
8+
9+
// --- test on first level ---
10+
const items: { [id: string]: AB } = {}
11+
const { [itemId]: itemOk1 } = items
12+
typeof itemOk1 // pass
13+
14+
// --- test on second level ---
15+
interface ObjWithItems {
16+
items: {[s: string]: AB}
17+
}
18+
const objWithItems: ObjWithItems = { items: {}}
19+
20+
const itemOk2 = objWithItems.items[itemId]
21+
typeof itemOk2 // pass
22+
23+
const {
24+
items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/
25+
} = objWithItems
26+
27+
// in order to re-produce the error, uncomment next line:
28+
typeof itemWithTSError // :(
29+
30+
// will result in:
31+
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined
32+
33+
//// [crashInGetTextOfComputedPropertyName.js]
34+
"use strict";
35+
exports.__esModule = true;
36+
var itemId = 'some-id';
37+
// --- test on first level ---
38+
var items = {};
39+
var _a = itemId, itemOk1 = items[_a];
40+
typeof itemOk1; // pass
41+
var objWithItems = { items: {} };
42+
var itemOk2 = objWithItems.items[itemId];
43+
typeof itemOk2; // pass
44+
var _b = objWithItems.items /*happens when default value is provided*/, _c = itemId, itemWithTSError = (_b === void 0 ? {} /*happens when default value is provided*/ : _b)[_c];
45+
// in order to re-produce the error, uncomment next line:
46+
typeof itemWithTSError; // :(
47+
// will result in:
48+
// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined

0 commit comments

Comments
 (0)