Skip to content

Commit 7dc42e8

Browse files
committed
fix(40617): handle uninitialized class member with computed key
1 parent 285c0e2 commit 7dc42e8

13 files changed

+169
-19
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ namespace ts {
889889
return isDottedName(expr)
890890
|| (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression)
891891
|| isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right)
892-
|| isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression)
892+
|| isElementAccessExpression(expr) && (isStringOrNumericLiteralLike(expr.argumentExpression) || isIdentifier(expr.argumentExpression)) && isNarrowableReference(expr.expression)
893893
|| isAssignmentExpression(expr) && isNarrowableReference(expr.left);
894894
}
895895

src/compiler/checker.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38383,7 +38383,7 @@ namespace ts {
3838338383
}
3838438384
if (!isStatic(member) && isPropertyWithoutInitializer(member)) {
3838538385
const propName = (member as PropertyDeclaration).name;
38386-
if (isIdentifier(propName) || isPrivateIdentifier(propName)) {
38386+
if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) {
3838738387
const type = getTypeOfSymbol(getSymbolOfNode(member));
3838838388
if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) {
3838938389
if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) {
@@ -38419,8 +38419,10 @@ namespace ts {
3841938419
return false;
3842038420
}
3842138421

38422-
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) {
38423-
const reference = factory.createPropertyAccessExpression(factory.createThis(), propName);
38422+
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) {
38423+
const reference = isComputedPropertyName(propName)
38424+
? factory.createElementAccessExpression(factory.createThis(), propName.expression)
38425+
: factory.createPropertyAccessExpression(factory.createThis(), propName);
3842438426
setParent(reference.expression, reference);
3842538427
setParent(reference, constructor);
3842638428
reference.flowNode = constructor.returnFlowNode;

src/compiler/commandLineParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2684,7 +2684,7 @@ namespace ts {
26842684
function getPropFromRaw<T>(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw<T> {
26852685
if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) {
26862686
if (isArray(raw[prop])) {
2687-
const result = raw[prop];
2687+
const result = raw[prop] as T[];
26882688
if (!sourceFile && !every(result, validateElement)) {
26892689
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName));
26902690
}

tests/baselines/reference/controlFlowOptionalChain.errors.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(501,13): error T
5959
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(515,13): error TS2532: Object is possibly 'undefined'.
6060
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(518,13): error TS2532: Object is possibly 'undefined'.
6161
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error TS2532: Object is possibly 'undefined'.
62+
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(586,9): error TS2367: This condition will always return 'false' since the types '"left"' and '"right"' have no overlap.
6263

6364

64-
==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (61 errors) ====
65+
==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (62 errors) ====
6566
// assignments in shortcutting chain
6667
declare const o: undefined | {
6768
[key: string]: any;
@@ -770,6 +771,8 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error T
770771
while (arr[i]?.tag === "left") {
771772
i += 1;
772773
if (arr[i]?.tag === "right") {
774+
~~~~~~~~~~~~~~~~~~~~~~~
775+
!!! error TS2367: This condition will always return 'false' since the types '"left"' and '"right"' have no overlap.
773776
console.log("I should ALSO be reachable");
774777
}
775778
}

tests/baselines/reference/controlFlowOptionalChain.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,11 +2057,11 @@ while (arr[i]?.tag === "left") {
20572057

20582058
if (arr[i]?.tag === "right") {
20592059
>arr[i]?.tag === "right" : boolean
2060-
>arr[i]?.tag : "left" | "right"
2060+
>arr[i]?.tag : "left"
20612061
>arr[i] : { tag: "left" | "right"; }
20622062
>arr : { tag: "left" | "right"; }[]
20632063
>i : number
2064-
>tag : "left" | "right"
2064+
>tag : "left"
20652065
>"right" : "right"
20662066

20672067
console.log("I should ALSO be reachable");

tests/baselines/reference/incrementOnNullAssertion.types

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,21 @@ if (foo[x] === undefined) {
2727
}
2828
else {
2929
let nu = foo[x]
30-
>nu : number | undefined
31-
>foo[x] : number | undefined
30+
>nu : number
31+
>foo[x] : number
3232
>foo : Dictionary<number>
3333
>x : "bar"
3434

3535
let n = foo[x]
36-
>n : number | undefined
37-
>foo[x] : number | undefined
36+
>n : number
37+
>foo[x] : number
3838
>foo : Dictionary<number>
3939
>x : "bar"
4040

4141
foo[x]!++
4242
>foo[x]!++ : number
4343
>foo[x]! : number
44-
>foo[x] : number | undefined
44+
>foo[x] : number
4545
>foo : Dictionary<number>
4646
>x : "bar"
4747
}

tests/baselines/reference/noUncheckedIndexedAccess.errors.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(90,7): error TS2322
3232
Type 'undefined' is not assignable to type 'string'.
3333
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(98,5): error TS2322: Type 'undefined' is not assignable to type '{ [key: string]: string; a: string; b: string; }[Key]'.
3434
Type 'undefined' is not assignable to type 'string'.
35-
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
36-
Type 'undefined' is not assignable to type 'string'.
35+
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'undefined' is not assignable to type 'string'.
3736

3837

3938
==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (31 errors) ====
@@ -201,8 +200,7 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS232
201200
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
202201
const v: string = myRecord2[key]; // Should error
203202
~
204-
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
205-
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
203+
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
206204
};
207205

208206

tests/baselines/reference/noUncheckedIndexedAccess.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ const fn3 = <Key extends keyof typeof myRecord2>(key: Key) => {
412412

413413
const v: string = myRecord2[key]; // Should error
414414
>v : string
415-
>myRecord2[key] : string | undefined
415+
>myRecord2[key] : undefined
416416
>myRecord2 : { [key: string]: string; a: string; b: string; }
417417
>key : Key
418418

tests/baselines/reference/strictPropertyInitialization.errors.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,19 @@ tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitial
164164
this.#b = someValue();
165165
}
166166
}
167+
168+
const a = 'a';
169+
const b = Symbol();
170+
171+
class C12 {
172+
[a]: number;
173+
[b]: number;
174+
['c']: number;
175+
176+
constructor() {
177+
this[a] = 1;
178+
this[b] = 1;
179+
this['c'] = 1;
180+
}
181+
}
167182

tests/baselines/reference/strictPropertyInitialization.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,21 @@ class C11 {
132132
this.#b = someValue();
133133
}
134134
}
135+
136+
const a = 'a';
137+
const b = Symbol();
138+
139+
class C12 {
140+
[a]: number;
141+
[b]: number;
142+
['c']: number;
143+
144+
constructor() {
145+
this[a] = 1;
146+
this[b] = 1;
147+
this['c'] = 1;
148+
}
149+
}
135150

136151

137152
//// [strictPropertyInitialization.js]
@@ -235,6 +250,15 @@ class C11 {
235250
}
236251
}
237252
_C11_b = new WeakMap();
253+
const a = 'a';
254+
const b = Symbol();
255+
class C12 {
256+
constructor() {
257+
this[a] = 1;
258+
this[b] = 1;
259+
this['c'] = 1;
260+
}
261+
}
238262

239263

240264
//// [strictPropertyInitialization.d.ts]
@@ -303,3 +327,11 @@ declare class C11 {
303327
a: number;
304328
constructor();
305329
}
330+
declare const a = "a";
331+
declare const b: unique symbol;
332+
declare class C12 {
333+
[a]: number;
334+
[b]: number;
335+
['c']: number;
336+
constructor();
337+
}

tests/baselines/reference/strictPropertyInitialization.symbols

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,40 @@ class C11 {
311311
}
312312
}
313313

314+
const a = 'a';
315+
>a : Symbol(a, Decl(strictPropertyInitialization.ts, 134, 5))
316+
317+
const b = Symbol();
318+
>b : Symbol(b, Decl(strictPropertyInitialization.ts, 135, 5))
319+
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
320+
321+
class C12 {
322+
>C12 : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
323+
324+
[a]: number;
325+
>[a] : Symbol(C12[a], Decl(strictPropertyInitialization.ts, 137, 11))
326+
>a : Symbol(a, Decl(strictPropertyInitialization.ts, 134, 5))
327+
328+
[b]: number;
329+
>[b] : Symbol(C12[b], Decl(strictPropertyInitialization.ts, 138, 16))
330+
>b : Symbol(b, Decl(strictPropertyInitialization.ts, 135, 5))
331+
332+
['c']: number;
333+
>['c'] : Symbol(C12['c'], Decl(strictPropertyInitialization.ts, 139, 16))
334+
>'c' : Symbol(C12['c'], Decl(strictPropertyInitialization.ts, 139, 16))
335+
336+
constructor() {
337+
this[a] = 1;
338+
>this : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
339+
>a : Symbol(a, Decl(strictPropertyInitialization.ts, 134, 5))
340+
341+
this[b] = 1;
342+
>this : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
343+
>b : Symbol(b, Decl(strictPropertyInitialization.ts, 135, 5))
344+
345+
this['c'] = 1;
346+
>this : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
347+
>'c' : Symbol(C12['c'], Decl(strictPropertyInitialization.ts, 139, 16))
348+
}
349+
}
350+

tests/baselines/reference/strictPropertyInitialization.types

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,51 @@ class C11 {
347347
}
348348
}
349349

350+
const a = 'a';
351+
>a : "a"
352+
>'a' : "a"
353+
354+
const b = Symbol();
355+
>b : unique symbol
356+
>Symbol() : unique symbol
357+
>Symbol : SymbolConstructor
358+
359+
class C12 {
360+
>C12 : C12
361+
362+
[a]: number;
363+
>[a] : number
364+
>a : "a"
365+
366+
[b]: number;
367+
>[b] : number
368+
>b : unique symbol
369+
370+
['c']: number;
371+
>['c'] : number
372+
>'c' : "c"
373+
374+
constructor() {
375+
this[a] = 1;
376+
>this[a] = 1 : 1
377+
>this[a] : number
378+
>this : this
379+
>a : "a"
380+
>1 : 1
381+
382+
this[b] = 1;
383+
>this[b] = 1 : 1
384+
>this[b] : number
385+
>this : this
386+
>b : unique symbol
387+
>1 : 1
388+
389+
this['c'] = 1;
390+
>this['c'] = 1 : 1
391+
>this['c'] : number
392+
>this : this
393+
>'c' : "c"
394+
>1 : 1
395+
}
396+
}
397+

tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitialization.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// @strict: true
2-
// @target:es2015
2+
// @target: es2015
33
// @declaration: true
44

55
// Properties with non-undefined types require initialization
@@ -135,3 +135,18 @@ class C11 {
135135
this.#b = someValue();
136136
}
137137
}
138+
139+
const a = 'a';
140+
const b = Symbol();
141+
142+
class C12 {
143+
[a]: number;
144+
[b]: number;
145+
['c']: number;
146+
147+
constructor() {
148+
this[a] = 1;
149+
this[b] = 1;
150+
this['c'] = 1;
151+
}
152+
}

0 commit comments

Comments
 (0)