Skip to content

Commit fed9253

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

29 files changed

+592
-168
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: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22287,11 +22287,27 @@ namespace ts {
2228722287
}
2228822288

2228922289
function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
22290-
let propertyName;
22291-
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
22292-
access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
22293-
access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) :
22294-
undefined;
22290+
if (isPropertyAccessExpression(access)) {
22291+
return access.name.escapedText;
22292+
}
22293+
if (isElementAccessExpression(access)) {
22294+
if (isStringOrNumericLiteralLike(access.argumentExpression)) {
22295+
return escapeLeadingUnderscores(access.argumentExpression.text);
22296+
}
22297+
if (isIdentifier(access.argumentExpression)) {
22298+
const symbol = getResolvedSymbol(access.argumentExpression);
22299+
const type = getSymbolLinks(symbol).type;
22300+
if (type === undefined) return undefined;
22301+
return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName :
22302+
type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined;
22303+
}
22304+
return undefined;
22305+
}
22306+
if (isBindingElement(access)) {
22307+
const name = getDestructuringPropertyName(access);
22308+
return name ? escapeLeadingUnderscores(name) : undefined;
22309+
}
22310+
return undefined;
2229522311
}
2229622312

2229722313
function containsMatchingReference(source: Node, target: Node) {
@@ -38383,7 +38399,7 @@ namespace ts {
3838338399
}
3838438400
if (!isStatic(member) && isPropertyWithoutInitializer(member)) {
3838538401
const propName = (member as PropertyDeclaration).name;
38386-
if (isIdentifier(propName) || isPrivateIdentifier(propName)) {
38402+
if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) {
3838738403
const type = getTypeOfSymbol(getSymbolOfNode(member));
3838838404
if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) {
3838938405
if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) {
@@ -38419,8 +38435,10 @@ namespace ts {
3841938435
return false;
3842038436
}
3842138437

38422-
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) {
38423-
const reference = factory.createPropertyAccessExpression(factory.createThis(), propName);
38438+
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) {
38439+
const reference = isComputedPropertyName(propName)
38440+
? factory.createElementAccessExpression(factory.createThis(), propName.expression)
38441+
: factory.createPropertyAccessExpression(factory.createThis(), propName);
3842438442
setParent(reference.expression, reference);
3842538443
setParent(reference, constructor);
3842638444
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+
}

0 commit comments

Comments
 (0)