Skip to content

Commit c2c5ae1

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

6 files changed

+950
-12
lines changed

src/compiler/checker.ts

+33-12
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

+1
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)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
// repro from https://github.com/microsoft/TypeScript/issues/46309
87+
88+
interface IDocument_46309 {
89+
name: string;
90+
[added_: `added_${string}`]: number[] | undefined;
91+
}
92+
93+
const tech1_46309 = {
94+
uuid: "70b26275-5096-4e4b-9d50-3c965c9e5073",
95+
};
96+
97+
const doc_46309: IDocument_46309 = {
98+
name: "",
99+
[`added_${tech1_46309.uuid}` as const]: [19700101],
100+
};
101+
102+
const doc2_46309: IDocument_46309 = {
103+
name: "",
104+
[`added_${tech1_46309.uuid}` as const]: [19700101],
105+
};
106+
107+
interface IDocument2_46309 {
108+
[added_: `added_${string}`]: number[] | undefined;
109+
}
110+
111+
const doc3_46309: IDocument2_46309 = {
112+
[`added_${tech1_46309.uuid}`]: [19700101],
113+
};
114+
115+
116+
//// [computedPropertyNamesTemplateLiteralTypes.js]
117+
"use strict";
118+
const obj1 = {
119+
[pattern1]: true,
120+
};
121+
const obj2 = {
122+
[pattern1]: true,
123+
[str1]: 100,
124+
};
125+
const obj3 = {
126+
[str1]: 100,
127+
[pattern1]: true,
128+
};
129+
const obj4 = {
130+
[pattern1]: true,
131+
[pattern2]: "hello",
132+
};
133+
const obj5 = {
134+
[pattern2]: "hello",
135+
[pattern1]: true,
136+
};
137+
const obj6 = {
138+
[pattern1]: true,
139+
[pattern2]: "hello",
140+
other: 100,
141+
};
142+
const obj7 = {
143+
[pattern1]: true,
144+
[pattern2]: "hello",
145+
fooooooooo: 100,
146+
};
147+
const obj8 = {
148+
[pattern1]: true,
149+
[pattern2]: "hello",
150+
foobarrrrr: 100,
151+
};
152+
const obj9 = {
153+
[pattern1]: true,
154+
[samepattern1]: "hello",
155+
};
156+
const obj10 = {
157+
[pattern1]: true,
158+
};
159+
const obj11 = {
160+
[pattern1]: 100,
161+
...obj9,
162+
};
163+
const obj12 = {
164+
...obj9,
165+
[pattern1]: 100,
166+
};
167+
const obj13 = {
168+
[pattern1]: 100,
169+
...{
170+
[pattern2]: "hello",
171+
},
172+
};
173+
const obj14 = {
174+
[pattern1]: 100,
175+
...{
176+
[pattern1]: true,
177+
[pattern2]: "hello",
178+
foobarrrrr: [1, 2, 3],
179+
},
180+
};
181+
const tech1_46309 = {
182+
uuid: "70b26275-5096-4e4b-9d50-3c965c9e5073",
183+
};
184+
const doc_46309 = {
185+
name: "",
186+
[`added_${tech1_46309.uuid}`]: [19700101],
187+
};
188+
const doc2_46309 = {
189+
name: "",
190+
[`added_${tech1_46309.uuid}`]: [19700101],
191+
};
192+
const doc3_46309 = {
193+
[`added_${tech1_46309.uuid}`]: [19700101],
194+
};

0 commit comments

Comments
 (0)