diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b48ba6bbcd6..48232a2e026 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6447,6 +6447,13 @@ namespace ts { return isDynamicName(node) && !isLateBindableName(node); } + /** + * Gets the late-bound name for a computed property name. + */ + function getLateBoundName(node: LateBoundName) { + return getLateBoundNameFromType(checkComputedPropertyName(node)); + } + /** * Gets the symbolic name for a late-bound member from its type. */ @@ -7354,7 +7361,7 @@ namespace ts { function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { const list = obj.properties as NodeArray; return list.some(property => { - const name = property.name && getTextOfPropertyName(property.name); + const name = property.name && !isComputedNonLiteralName(property.name) ? getTextOfPropertyName(property.name) : undefined; const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name); return !!expected && isLiteralType(expected) && !isTypeIdenticalTo(getTypeOfNode(property), expected); }); @@ -15059,7 +15066,10 @@ namespace ts { } function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { - const text = getTextOfPropertyName(name); + const text = !isComputedNonLiteralName(name) ? getTextOfPropertyName(name) : + isLateBindableName(name) ? getLateBoundName(name) : + undefined; + if (text === undefined) return errorType; return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) || isNumericLiteralName(text) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || @@ -17191,11 +17201,9 @@ namespace ts { const parentDeclaration = declaration.parent.parent; const name = declaration.propertyName || declaration.name; const parentType = getContextualTypeForVariableLikeDeclaration(parentDeclaration); - if (parentType && !isBindingPattern(name)) { + if (parentType && !isBindingPattern(name) && !isComputedNonLiteralName(name)) { const text = getTextOfPropertyName(name); - if (text !== undefined) { - return getTypeOfPropertyOfType(parentType, text); - } + return getTypeOfPropertyOfType(parentType, text); } } @@ -22201,8 +22209,8 @@ namespace ts { function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray, rightIsThis = false) { if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { const name = property.name; - const text = getTextOfPropertyName(name); - if (text) { + if (!isComputedNonLiteralName(name)) { + const text = getTextOfPropertyName(name); const prop = getPropertyOfType(objectLiteralType, text); if (prop) { markPropertyAsReferenced(prop, property, rightIsThis); @@ -25524,14 +25532,12 @@ namespace ts { const parent = node.parent.parent; const parentType = getTypeForBindingElementParent(parent); const name = node.propertyName || node.name; - if (!isBindingPattern(name)) { + if (!isBindingPattern(name) && !isComputedNonLiteralName(name)) { const nameText = getTextOfPropertyName(name); - if (nameText) { - const property = getPropertyOfType(parentType!, nameText); // TODO: GH#18217 - if (property) { - markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference. - checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType!, property); - } + const property = getPropertyOfType(parentType!, nameText); // TODO: GH#18217 + if (property) { + markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference. + checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType!, property); } } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9c6b27de13c..3964c487fc2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -778,7 +778,8 @@ namespace ts { case SyntaxKind.NoSubstitutionTemplateLiteral: return escapeLeadingUnderscores(name.text); case SyntaxKind.ComputedPropertyName: - return isStringOrNumericLiteralLike(name.expression) ? escapeLeadingUnderscores(name.expression.text) : undefined!; // TODO: GH#18217 Almost all uses of this assume the result to be defined! + if (isStringOrNumericLiteralLike(name.expression)) return escapeLeadingUnderscores(name.expression.text); + return Debug.fail("Text of property name cannot be read from non-literal-valued ComputedPropertyNames"); default: return Debug.assertNever(name); } diff --git a/tests/baselines/reference/crashInGetTextOfComputedPropertyName.js b/tests/baselines/reference/crashInGetTextOfComputedPropertyName.js new file mode 100644 index 00000000000..71d5df00e2a --- /dev/null +++ b/tests/baselines/reference/crashInGetTextOfComputedPropertyName.js @@ -0,0 +1,48 @@ +//// [crashInGetTextOfComputedPropertyName.ts] +// https://github.com/Microsoft/TypeScript/issues/29006 +export interface A { type: 'a' } +export interface B { type: 'b' } +export type AB = A | B + +const itemId = 'some-id' + +// --- test on first level --- +const items: { [id: string]: AB } = {} +const { [itemId]: itemOk1 } = items +typeof itemOk1 // pass + +// --- test on second level --- +interface ObjWithItems { + items: {[s: string]: AB} +} +const objWithItems: ObjWithItems = { items: {}} + +const itemOk2 = objWithItems.items[itemId] +typeof itemOk2 // pass + +const { + items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/ +} = objWithItems + +// in order to re-produce the error, uncomment next line: +typeof itemWithTSError // :( + +// will result in: +// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined + +//// [crashInGetTextOfComputedPropertyName.js] +"use strict"; +exports.__esModule = true; +var itemId = 'some-id'; +// --- test on first level --- +var items = {}; +var _a = itemId, itemOk1 = items[_a]; +typeof itemOk1; // pass +var objWithItems = { items: {} }; +var itemOk2 = objWithItems.items[itemId]; +typeof itemOk2; // pass +var _b = objWithItems.items /*happens when default value is provided*/, _c = itemId, itemWithTSError = (_b === void 0 ? {} /*happens when default value is provided*/ : _b)[_c]; +// in order to re-produce the error, uncomment next line: +typeof itemWithTSError; // :( +// will result in: +// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined diff --git a/tests/baselines/reference/crashInGetTextOfComputedPropertyName.symbols b/tests/baselines/reference/crashInGetTextOfComputedPropertyName.symbols new file mode 100644 index 00000000000..d0f1721258c --- /dev/null +++ b/tests/baselines/reference/crashInGetTextOfComputedPropertyName.symbols @@ -0,0 +1,71 @@ +=== tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts === +// https://github.com/Microsoft/TypeScript/issues/29006 +export interface A { type: 'a' } +>A : Symbol(A, Decl(crashInGetTextOfComputedPropertyName.ts, 0, 0)) +>type : Symbol(A.type, Decl(crashInGetTextOfComputedPropertyName.ts, 1, 20)) + +export interface B { type: 'b' } +>B : Symbol(B, Decl(crashInGetTextOfComputedPropertyName.ts, 1, 32)) +>type : Symbol(B.type, Decl(crashInGetTextOfComputedPropertyName.ts, 2, 20)) + +export type AB = A | B +>AB : Symbol(AB, Decl(crashInGetTextOfComputedPropertyName.ts, 2, 32)) +>A : Symbol(A, Decl(crashInGetTextOfComputedPropertyName.ts, 0, 0)) +>B : Symbol(B, Decl(crashInGetTextOfComputedPropertyName.ts, 1, 32)) + +const itemId = 'some-id' +>itemId : Symbol(itemId, Decl(crashInGetTextOfComputedPropertyName.ts, 5, 5)) + +// --- test on first level --- +const items: { [id: string]: AB } = {} +>items : Symbol(items, Decl(crashInGetTextOfComputedPropertyName.ts, 8, 5)) +>id : Symbol(id, Decl(crashInGetTextOfComputedPropertyName.ts, 8, 16)) +>AB : Symbol(AB, Decl(crashInGetTextOfComputedPropertyName.ts, 2, 32)) + +const { [itemId]: itemOk1 } = items +>itemId : Symbol(itemId, Decl(crashInGetTextOfComputedPropertyName.ts, 5, 5)) +>itemOk1 : Symbol(itemOk1, Decl(crashInGetTextOfComputedPropertyName.ts, 9, 7)) +>items : Symbol(items, Decl(crashInGetTextOfComputedPropertyName.ts, 8, 5)) + +typeof itemOk1 // pass +>itemOk1 : Symbol(itemOk1, Decl(crashInGetTextOfComputedPropertyName.ts, 9, 7)) + +// --- test on second level --- +interface ObjWithItems { +>ObjWithItems : Symbol(ObjWithItems, Decl(crashInGetTextOfComputedPropertyName.ts, 10, 14)) + + items: {[s: string]: AB} +>items : Symbol(ObjWithItems.items, Decl(crashInGetTextOfComputedPropertyName.ts, 13, 24)) +>s : Symbol(s, Decl(crashInGetTextOfComputedPropertyName.ts, 14, 13)) +>AB : Symbol(AB, Decl(crashInGetTextOfComputedPropertyName.ts, 2, 32)) +} +const objWithItems: ObjWithItems = { items: {}} +>objWithItems : Symbol(objWithItems, Decl(crashInGetTextOfComputedPropertyName.ts, 16, 5)) +>ObjWithItems : Symbol(ObjWithItems, Decl(crashInGetTextOfComputedPropertyName.ts, 10, 14)) +>items : Symbol(items, Decl(crashInGetTextOfComputedPropertyName.ts, 16, 36)) + +const itemOk2 = objWithItems.items[itemId] +>itemOk2 : Symbol(itemOk2, Decl(crashInGetTextOfComputedPropertyName.ts, 18, 5)) +>objWithItems.items : Symbol(ObjWithItems.items, Decl(crashInGetTextOfComputedPropertyName.ts, 13, 24)) +>objWithItems : Symbol(objWithItems, Decl(crashInGetTextOfComputedPropertyName.ts, 16, 5)) +>items : Symbol(ObjWithItems.items, Decl(crashInGetTextOfComputedPropertyName.ts, 13, 24)) +>itemId : Symbol(itemId, Decl(crashInGetTextOfComputedPropertyName.ts, 5, 5)) + +typeof itemOk2 // pass +>itemOk2 : Symbol(itemOk2, Decl(crashInGetTextOfComputedPropertyName.ts, 18, 5)) + +const { + items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/ +>items : Symbol(ObjWithItems.items, Decl(crashInGetTextOfComputedPropertyName.ts, 13, 24)) +>itemId : Symbol(itemId, Decl(crashInGetTextOfComputedPropertyName.ts, 5, 5)) +>itemWithTSError : Symbol(itemWithTSError, Decl(crashInGetTextOfComputedPropertyName.ts, 22, 12)) + +} = objWithItems +>objWithItems : Symbol(objWithItems, Decl(crashInGetTextOfComputedPropertyName.ts, 16, 5)) + +// in order to re-produce the error, uncomment next line: +typeof itemWithTSError // :( +>itemWithTSError : Symbol(itemWithTSError, Decl(crashInGetTextOfComputedPropertyName.ts, 22, 12)) + +// will result in: +// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined diff --git a/tests/baselines/reference/crashInGetTextOfComputedPropertyName.types b/tests/baselines/reference/crashInGetTextOfComputedPropertyName.types new file mode 100644 index 00000000000..7d9ce4aa34b --- /dev/null +++ b/tests/baselines/reference/crashInGetTextOfComputedPropertyName.types @@ -0,0 +1,71 @@ +=== tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts === +// https://github.com/Microsoft/TypeScript/issues/29006 +export interface A { type: 'a' } +>type : "a" + +export interface B { type: 'b' } +>type : "b" + +export type AB = A | B +>AB : AB + +const itemId = 'some-id' +>itemId : "some-id" +>'some-id' : "some-id" + +// --- test on first level --- +const items: { [id: string]: AB } = {} +>items : { [id: string]: AB; } +>id : string +>{} : {} + +const { [itemId]: itemOk1 } = items +>itemId : "some-id" +>itemOk1 : AB +>items : { [id: string]: AB; } + +typeof itemOk1 // pass +>typeof itemOk1 : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>itemOk1 : AB + +// --- test on second level --- +interface ObjWithItems { + items: {[s: string]: AB} +>items : { [s: string]: AB; } +>s : string +} +const objWithItems: ObjWithItems = { items: {}} +>objWithItems : ObjWithItems +>{ items: {}} : { items: {}; } +>items : {} +>{} : {} + +const itemOk2 = objWithItems.items[itemId] +>itemOk2 : AB +>objWithItems.items[itemId] : AB +>objWithItems.items : { [s: string]: AB; } +>objWithItems : ObjWithItems +>items : { [s: string]: AB; } +>itemId : "some-id" + +typeof itemOk2 // pass +>typeof itemOk2 : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>itemOk2 : AB + +const { + items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/ +>items : any +>itemId : "some-id" +>itemWithTSError : AB +>{} : {} + +} = objWithItems +>objWithItems : ObjWithItems + +// in order to re-produce the error, uncomment next line: +typeof itemWithTSError // :( +>typeof itemWithTSError : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>itemWithTSError : AB + +// will result in: +// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined diff --git a/tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts b/tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts new file mode 100644 index 00000000000..d7ad08dd812 --- /dev/null +++ b/tests/cases/compiler/crashInGetTextOfComputedPropertyName.ts @@ -0,0 +1,30 @@ +// https://github.com/Microsoft/TypeScript/issues/29006 +export interface A { type: 'a' } +export interface B { type: 'b' } +export type AB = A | B + +const itemId = 'some-id' + +// --- test on first level --- +const items: { [id: string]: AB } = {} +const { [itemId]: itemOk1 } = items +typeof itemOk1 // pass + +// --- test on second level --- +interface ObjWithItems { + items: {[s: string]: AB} +} +const objWithItems: ObjWithItems = { items: {}} + +const itemOk2 = objWithItems.items[itemId] +typeof itemOk2 // pass + +const { + items: { [itemId]: itemWithTSError } = {} /*happens when default value is provided*/ +} = objWithItems + +// in order to re-produce the error, uncomment next line: +typeof itemWithTSError // :( + +// will result in: +// Error from compilation: TypeError: Cannot read property 'charCodeAt' of undefined TypeError: Cannot read property 'charCodeAt' of undefined \ No newline at end of file