Skip to content

Commit e3d3857

Browse files
committed
fix(40617): handle uninitialized class member with computed key
1 parent d518bdb commit e3d3857

File tree

37 files changed

+845
-168
lines changed

37 files changed

+845
-168
lines changed

src/compiler/binder.ts

+1-1
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

+26-8
Original file line numberDiff line numberDiff line change
@@ -22396,11 +22396,27 @@ namespace ts {
2239622396
}
2239722397

2239822398
function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
22399-
let propertyName;
22400-
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
22401-
access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
22402-
access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) :
22403-
undefined;
22399+
if (isPropertyAccessExpression(access)) {
22400+
return access.name.escapedText;
22401+
}
22402+
if (isElementAccessExpression(access)) {
22403+
if (isStringOrNumericLiteralLike(access.argumentExpression)) {
22404+
return escapeLeadingUnderscores(access.argumentExpression.text);
22405+
}
22406+
if (isIdentifier(access.argumentExpression)) {
22407+
const symbol = getResolvedSymbol(access.argumentExpression);
22408+
const type = getSymbolLinks(symbol).type;
22409+
if (type === undefined) return undefined;
22410+
return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName :
22411+
type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined;
22412+
}
22413+
return undefined;
22414+
}
22415+
if (isBindingElement(access)) {
22416+
const name = getDestructuringPropertyName(access);
22417+
return name ? escapeLeadingUnderscores(name) : undefined;
22418+
}
22419+
return undefined;
2240422420
}
2240522421

2240622422
function containsMatchingReference(source: Node, target: Node) {
@@ -38598,7 +38614,7 @@ namespace ts {
3859838614
}
3859938615
if (!isStatic(member) && isPropertyWithoutInitializer(member)) {
3860038616
const propName = (member as PropertyDeclaration).name;
38601-
if (isIdentifier(propName) || isPrivateIdentifier(propName)) {
38617+
if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) {
3860238618
const type = getTypeOfSymbol(getSymbolOfNode(member));
3860338619
if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) {
3860438620
if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) {
@@ -38634,8 +38650,10 @@ namespace ts {
3863438650
return false;
3863538651
}
3863638652

38637-
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) {
38638-
const reference = factory.createPropertyAccessExpression(factory.createThis(), propName);
38653+
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) {
38654+
const reference = isComputedPropertyName(propName)
38655+
? factory.createElementAccessExpression(factory.createThis(), propName.expression)
38656+
: factory.createPropertyAccessExpression(factory.createThis(), propName);
3863938657
setParent(reference.expression, reference);
3864038658
setParent(reference, constructor);
3864138659
reference.flowNode = constructor.returnFlowNode;

src/compiler/commandLineParser.ts

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

tests/baselines/reference/controlFlowOptionalChain.errors.txt

+4-1
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

+2-2
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

+5-5
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

+2-4
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

+1-1
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

+15
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

+32
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

+37
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

+48
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/baselines/reference/typeGuardNarrowsIndexedAccessOfKnownProperty.js tests/baselines/reference/typeGuardNarrowsIndexedAccessOfKnownProperty1.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//// [typeGuardNarrowsIndexedAccessOfKnownProperty.ts]
1+
//// [typeGuardNarrowsIndexedAccessOfKnownProperty1.ts]
22
interface Square {
33
["dash-ok"]: "square";
44
["square-size"]: number;
@@ -80,7 +80,7 @@ export function g(pair: [number, string?]): string {
8080
}
8181

8282

83-
//// [typeGuardNarrowsIndexedAccessOfKnownProperty.js]
83+
//// [typeGuardNarrowsIndexedAccessOfKnownProperty1.js]
8484
"use strict";
8585
exports.__esModule = true;
8686
exports.g = void 0;

0 commit comments

Comments
 (0)