Skip to content

Commit e7172a2

Browse files
committed
Do not widen computed properties with template literal types
1 parent 5021dd8 commit e7172a2

6 files changed

+715
-12
lines changed

src/compiler/checker.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30099,18 +30099,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3009930099
isTypeAssignableToKind(checkComputedPropertyName(firstDecl.name), TypeFlags.ESSymbol));
3010030100
}
3010130101

30102-
function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], keyType: Type): IndexInfo {
30102+
function getObjectLiteralIndexInfo(offset: number, properties: Symbol[], keyType: Type, isReadonly: boolean): IndexInfo {
3010330103
const propTypes: Type[] = [];
3010430104
for (let i = offset; i < properties.length; i++) {
3010530105
const prop = properties[i];
30106-
if (keyType === stringType && !isSymbolWithSymbolName(prop) ||
30107-
keyType === numberType && isSymbolWithNumericName(prop) ||
30108-
keyType === esSymbolType && isSymbolWithSymbolName(prop)) {
30106+
if (keyType === numberType && isSymbolWithNumericName(prop) || keyType === esSymbolType && isSymbolWithSymbolName(prop)) {
3010930107
propTypes.push(getTypeOfSymbol(properties[i]));
3011030108
}
30109+
else if (!isSymbolWithSymbolName(prop)) {
30110+
if (keyType === stringType) {
30111+
propTypes.push(getTypeOfSymbol(properties[i]));
30112+
}
30113+
else {
30114+
const source = tryCast(prop, isTransientSymbol)?.links.computedNameType || getStringLiteralType(unescapeLeadingUnderscores(prop.escapedName));
30115+
if (isTypeAssignableTo(source, keyType)) {
30116+
propTypes.push(getTypeOfSymbol(properties[i]));
30117+
}
30118+
}
30119+
}
3011130120
}
3011230121
const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType;
30113-
return createIndexInfo(keyType, unionType, isConstContext(node));
30122+
return createIndexInfo(keyType, unionType, isReadonly);
3011430123
}
3011530124

3011630125
function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined {
@@ -30133,6 +30142,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3013330142
const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined;
3013430143
let propertiesTable = createSymbolTable();
3013530144
let propertiesArray: Symbol[] = [];
30145+
let computedNameTypes: Type[] = [];
3013630146
let spread: Type = emptyObjectType;
3013730147

3013830148
pushCachedContextualType(node);
@@ -30191,7 +30201,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3019130201
if (nameType) {
3019230202
prop.links.nameType = nameType;
3019330203
}
30194-
30204+
else if (computedNameType) {
30205+
prop.links.computedNameType = computedNameType;
30206+
}
3019530207
if (inDestructuringPattern) {
3019630208
// If object literal is an assignment pattern and if the assignment pattern specifies a default value
3019730209
// for the property, make the property optional.
@@ -30243,6 +30255,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3024330255
spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
3024430256
propertiesArray = [];
3024530257
propertiesTable = createSymbolTable();
30258+
computedNameTypes = [];
3024630259
hasComputedStringProperty = false;
3024730260
hasComputedNumberProperty = false;
3024830261
hasComputedSymbolProperty = false;
@@ -30275,8 +30288,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3027530288
checkNodeDeferred(memberDecl);
3027630289
}
3027730290

30278-
if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) {
30279-
if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) {
30291+
if (computedNameType && !isTypeUsableAsPropertyName(computedNameType)) {
30292+
if (isPatternLiteralType(computedNameType)) {
30293+
computedNameTypes.push(computedNameType);
30294+
}
30295+
else if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) {
3028030296
if (isTypeAssignableTo(computedNameType, numberType)) {
3028130297
hasComputedNumberProperty = true;
3028230298
}
@@ -30339,6 +30355,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3033930355
spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext);
3034030356
propertiesArray = [];
3034130357
propertiesTable = createSymbolTable();
30358+
computedNameTypes = [];
3034230359
hasComputedStringProperty = false;
3034330360
hasComputedNumberProperty = false;
3034430361
}
@@ -30349,10 +30366,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3034930366
return createObjectLiteralType();
3035030367

3035130368
function createObjectLiteralType() {
30352-
const indexInfos = [];
30353-
if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, stringType));
30354-
if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, numberType));
30355-
if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(node, offset, propertiesArray, esSymbolType));
30369+
let indexInfos: IndexInfo[] = [];
30370+
for (const computedNameType of computedNameTypes) {
30371+
const indexInfo = getObjectLiteralIndexInfo(offset, propertiesArray, computedNameType, /*isReadonly*/ inConstContext);
30372+
indexInfos = appendIndexInfo(indexInfos, indexInfo, /*union*/ true);
30373+
}
30374+
if (hasComputedStringProperty) indexInfos.push(getObjectLiteralIndexInfo(offset, propertiesArray, stringType, /*isReadonly*/ inConstContext));
30375+
if (hasComputedNumberProperty) indexInfos.push(getObjectLiteralIndexInfo(offset, propertiesArray, numberType, /*isReadonly*/ inConstContext));
30376+
if (hasComputedSymbolProperty) indexInfos.push(getObjectLiteralIndexInfo(offset, propertiesArray, esSymbolType, /*isReadonly*/ inConstContext));
3035630377
const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, indexInfos);
3035730378
result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral;
3035830379
if (isJSObjectLiteral) {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5839,6 +5839,7 @@ export interface SymbolLinks {
58395839
type?: Type; // Type of value symbol
58405840
writeType?: Type; // Type of value symbol in write contexts
58415841
nameType?: Type; // Type associated with a late-bound symbol
5842+
computedNameType?: Type; // Type of the computed property name not usable as property name
58425843
uniqueESSymbolType?: Type; // UniqueESSymbol type for a symbol
58435844
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
58445845
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//// [tests/cases/conformance/es6/computedProperties/computedPropertyNamesTemplateLiteralTypes.ts] ////
2+
3+
//// [computedPropertyNamesTemplateLiteralTypes.ts]
4+
declare const str1: string;
5+
declare const pattern1: `foo${string}`;
6+
declare const pattern2: `foobar${string}`;
7+
declare const samepattern1: `foo${string}`;
8+
9+
const obj1 = {
10+
[pattern1]: true
11+
}
12+
13+
const obj2 = {
14+
[pattern1]: true,
15+
[str1]: 100
16+
}
17+
18+
const obj3 = {
19+
[str1]: 100,
20+
[pattern1]: true,
21+
}
22+
23+
const obj4 = {
24+
[pattern1]: true,
25+
[pattern2]: 'hello',
26+
}
27+
28+
const obj5 = {
29+
[pattern2]: 'hello',
30+
[pattern1]: true,
31+
}
32+
33+
const obj6 = {
34+
[pattern1]: true,
35+
[pattern2]: 'hello',
36+
other: 100
37+
}
38+
39+
const obj7 = {
40+
[pattern1]: true,
41+
[pattern2]: 'hello',
42+
fooooooooo: 100
43+
}
44+
45+
const obj8 = {
46+
[pattern1]: true,
47+
[pattern2]: 'hello',
48+
foobarrrrr: 100
49+
}
50+
51+
const obj9 = {
52+
[pattern1]: true,
53+
[samepattern1]: 'hello',
54+
}
55+
56+
const obj10 = {
57+
[pattern1]: true
58+
} as const
59+
60+
const obj11 = {
61+
[pattern1]: 100,
62+
...obj9
63+
}
64+
65+
const obj12 = {
66+
...obj9,
67+
[pattern1]: 100,
68+
}
69+
70+
const obj13 = {
71+
[pattern1]: 100,
72+
...({
73+
[pattern2]: 'hello',
74+
})
75+
}
76+
77+
const obj14 = {
78+
[pattern1]: 100,
79+
...({
80+
[pattern1]: true,
81+
[pattern2]: 'hello',
82+
foobarrrrr: [1, 2, 3]
83+
})
84+
}
85+
86+
87+
//// [computedPropertyNamesTemplateLiteralTypes.js]
88+
"use strict";
89+
const obj1 = {
90+
[pattern1]: true
91+
};
92+
const obj2 = {
93+
[pattern1]: true,
94+
[str1]: 100
95+
};
96+
const obj3 = {
97+
[str1]: 100,
98+
[pattern1]: true,
99+
};
100+
const obj4 = {
101+
[pattern1]: true,
102+
[pattern2]: 'hello',
103+
};
104+
const obj5 = {
105+
[pattern2]: 'hello',
106+
[pattern1]: true,
107+
};
108+
const obj6 = {
109+
[pattern1]: true,
110+
[pattern2]: 'hello',
111+
other: 100
112+
};
113+
const obj7 = {
114+
[pattern1]: true,
115+
[pattern2]: 'hello',
116+
fooooooooo: 100
117+
};
118+
const obj8 = {
119+
[pattern1]: true,
120+
[pattern2]: 'hello',
121+
foobarrrrr: 100
122+
};
123+
const obj9 = {
124+
[pattern1]: true,
125+
[samepattern1]: 'hello',
126+
};
127+
const obj10 = {
128+
[pattern1]: true
129+
};
130+
const obj11 = {
131+
[pattern1]: 100,
132+
...obj9
133+
};
134+
const obj12 = {
135+
...obj9,
136+
[pattern1]: 100,
137+
};
138+
const obj13 = {
139+
[pattern1]: 100,
140+
...({
141+
[pattern2]: 'hello',
142+
})
143+
};
144+
const obj14 = {
145+
[pattern1]: 100,
146+
...({
147+
[pattern1]: true,
148+
[pattern2]: 'hello',
149+
foobarrrrr: [1, 2, 3]
150+
})
151+
};

0 commit comments

Comments
 (0)