Skip to content

Commit 76a3be7

Browse files
authored
Merge pull request #19838 from Microsoft/narrow-index-signature-property-access
Narrow property access of undeclared properties from string index signatures
2 parents ca181a7 + 2548ace commit 76a3be7

File tree

5 files changed

+120
-9
lines changed

5 files changed

+120
-9
lines changed

src/compiler/checker.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15221,7 +15221,7 @@ namespace ts {
1522115221
if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) {
1522215222
error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType));
1522315223
}
15224-
return indexInfo.type;
15224+
return getFlowTypeOfPropertyAccess(node, /*prop*/ undefined, indexInfo.type, getAssignmentTargetKind(node));
1522515225
}
1522615226
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
1522715227
reportNonexistentProperty(right, type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType ? apparentType : type);
@@ -15246,16 +15246,21 @@ namespace ts {
1524615246
return unknownType;
1524715247
}
1524815248
}
15249+
return getFlowTypeOfPropertyAccess(node, prop, propType, assignmentKind);
15250+
}
1524915251

15250-
// Only compute control flow type if this is a property access expression that isn't an
15251-
// assignment target, and the referenced property was declared as a variable, property,
15252-
// accessor, or optional method.
15253-
if (node.kind !== SyntaxKind.PropertyAccessExpression || assignmentKind === AssignmentKind.Definite ||
15254-
!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) &&
15255-
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
15256-
return propType;
15252+
/**
15253+
* Only compute control flow type if this is a property access expression that isn't an
15254+
* assignment target, and the referenced property was declared as a variable, property,
15255+
* accessor, or optional method.
15256+
*/
15257+
function getFlowTypeOfPropertyAccess(node: PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, type: Type, assignmentKind: AssignmentKind) {
15258+
if (node.kind !== SyntaxKind.PropertyAccessExpression ||
15259+
assignmentKind === AssignmentKind.Definite ||
15260+
prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && type.flags & TypeFlags.Union)) {
15261+
return type;
1525715262
}
15258-
const flowType = getFlowTypeOfReference(node, propType);
15263+
const flowType = getFlowTypeOfReference(node, type);
1525915264
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
1526015265
}
1526115266

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [controlFlowStringIndex.ts]
2+
type A = {
3+
other: number | null;
4+
[index: string]: number | null
5+
};
6+
declare const value: A;
7+
if (value.foo !== null) {
8+
value.foo.toExponential()
9+
value.other // should still be number | null
10+
value.bar // should still be number | null
11+
}
12+
13+
14+
//// [controlFlowStringIndex.js]
15+
"use strict";
16+
if (value.foo !== null) {
17+
value.foo.toExponential();
18+
value.other; // should still be number | null
19+
value.bar; // should still be number | null
20+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowStringIndex.ts ===
2+
type A = {
3+
>A : Symbol(A, Decl(controlFlowStringIndex.ts, 0, 0))
4+
5+
other: number | null;
6+
>other : Symbol(other, Decl(controlFlowStringIndex.ts, 0, 10))
7+
8+
[index: string]: number | null
9+
>index : Symbol(index, Decl(controlFlowStringIndex.ts, 2, 5))
10+
11+
};
12+
declare const value: A;
13+
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
14+
>A : Symbol(A, Decl(controlFlowStringIndex.ts, 0, 0))
15+
16+
if (value.foo !== null) {
17+
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
18+
19+
value.foo.toExponential()
20+
>value.foo.toExponential : Symbol(Number.toExponential, Decl(lib.d.ts, --, --))
21+
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
22+
>toExponential : Symbol(Number.toExponential, Decl(lib.d.ts, --, --))
23+
24+
value.other // should still be number | null
25+
>value.other : Symbol(other, Decl(controlFlowStringIndex.ts, 0, 10))
26+
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
27+
>other : Symbol(other, Decl(controlFlowStringIndex.ts, 0, 10))
28+
29+
value.bar // should still be number | null
30+
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
31+
}
32+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
=== tests/cases/conformance/controlFlow/controlFlowStringIndex.ts ===
2+
type A = {
3+
>A : A
4+
5+
other: number | null;
6+
>other : number | null
7+
>null : null
8+
9+
[index: string]: number | null
10+
>index : string
11+
>null : null
12+
13+
};
14+
declare const value: A;
15+
>value : A
16+
>A : A
17+
18+
if (value.foo !== null) {
19+
>value.foo !== null : boolean
20+
>value.foo : number | null
21+
>value : A
22+
>foo : number | null
23+
>null : null
24+
25+
value.foo.toExponential()
26+
>value.foo.toExponential() : string
27+
>value.foo.toExponential : (fractionDigits?: number | undefined) => string
28+
>value.foo : number
29+
>value : A
30+
>foo : number
31+
>toExponential : (fractionDigits?: number | undefined) => string
32+
33+
value.other // should still be number | null
34+
>value.other : number | null
35+
>value : A
36+
>other : number | null
37+
38+
value.bar // should still be number | null
39+
>value.bar : number | null
40+
>value : A
41+
>bar : number | null
42+
}
43+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @strict: true
2+
type A = {
3+
other: number | null;
4+
[index: string]: number | null
5+
};
6+
declare const value: A;
7+
if (value.foo !== null) {
8+
value.foo.toExponential()
9+
value.other // should still be number | null
10+
value.bar // should still be number | null
11+
}

0 commit comments

Comments
 (0)