Skip to content

Commit

Permalink
Merge pull request microsoft#19838 from Microsoft/narrow-index-signat…
Browse files Browse the repository at this point in the history
…ure-property-access

Narrow property access of undeclared properties from string index signatures
  • Loading branch information
sandersn authored Nov 8, 2017
2 parents ca181a7 + 2548ace commit 76a3be7
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 9 deletions.
23 changes: 14 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15221,7 +15221,7 @@ namespace ts {
if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) {
error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType));
}
return indexInfo.type;
return getFlowTypeOfPropertyAccess(node, /*prop*/ undefined, indexInfo.type, getAssignmentTargetKind(node));
}
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
reportNonexistentProperty(right, type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType ? apparentType : type);
Expand All @@ -15246,16 +15246,21 @@ namespace ts {
return unknownType;
}
}
return getFlowTypeOfPropertyAccess(node, prop, propType, assignmentKind);
}

// Only compute control flow type if this is a property access expression that isn't an
// assignment target, and the referenced property was declared as a variable, property,
// accessor, or optional method.
if (node.kind !== SyntaxKind.PropertyAccessExpression || assignmentKind === AssignmentKind.Definite ||
!(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) &&
!(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
return propType;
/**
* Only compute control flow type if this is a property access expression that isn't an
* assignment target, and the referenced property was declared as a variable, property,
* accessor, or optional method.
*/
function getFlowTypeOfPropertyAccess(node: PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, type: Type, assignmentKind: AssignmentKind) {
if (node.kind !== SyntaxKind.PropertyAccessExpression ||
assignmentKind === AssignmentKind.Definite ||
prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && type.flags & TypeFlags.Union)) {
return type;
}
const flowType = getFlowTypeOfReference(node, propType);
const flowType = getFlowTypeOfReference(node, type);
return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType;
}

Expand Down
20 changes: 20 additions & 0 deletions tests/baselines/reference/controlFlowStringIndex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//// [controlFlowStringIndex.ts]
type A = {
other: number | null;
[index: string]: number | null
};
declare const value: A;
if (value.foo !== null) {
value.foo.toExponential()
value.other // should still be number | null
value.bar // should still be number | null
}


//// [controlFlowStringIndex.js]
"use strict";
if (value.foo !== null) {
value.foo.toExponential();
value.other; // should still be number | null
value.bar; // should still be number | null
}
32 changes: 32 additions & 0 deletions tests/baselines/reference/controlFlowStringIndex.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/conformance/controlFlow/controlFlowStringIndex.ts ===
type A = {
>A : Symbol(A, Decl(controlFlowStringIndex.ts, 0, 0))

other: number | null;
>other : Symbol(other, Decl(controlFlowStringIndex.ts, 0, 10))

[index: string]: number | null
>index : Symbol(index, Decl(controlFlowStringIndex.ts, 2, 5))

};
declare const value: A;
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
>A : Symbol(A, Decl(controlFlowStringIndex.ts, 0, 0))

if (value.foo !== null) {
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))

value.foo.toExponential()
>value.foo.toExponential : Symbol(Number.toExponential, Decl(lib.d.ts, --, --))
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
>toExponential : Symbol(Number.toExponential, Decl(lib.d.ts, --, --))

value.other // should still be number | null
>value.other : Symbol(other, Decl(controlFlowStringIndex.ts, 0, 10))
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
>other : Symbol(other, Decl(controlFlowStringIndex.ts, 0, 10))

value.bar // should still be number | null
>value : Symbol(value, Decl(controlFlowStringIndex.ts, 4, 13))
}

43 changes: 43 additions & 0 deletions tests/baselines/reference/controlFlowStringIndex.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
=== tests/cases/conformance/controlFlow/controlFlowStringIndex.ts ===
type A = {
>A : A

other: number | null;
>other : number | null
>null : null

[index: string]: number | null
>index : string
>null : null

};
declare const value: A;
>value : A
>A : A

if (value.foo !== null) {
>value.foo !== null : boolean
>value.foo : number | null
>value : A
>foo : number | null
>null : null

value.foo.toExponential()
>value.foo.toExponential() : string
>value.foo.toExponential : (fractionDigits?: number | undefined) => string
>value.foo : number
>value : A
>foo : number
>toExponential : (fractionDigits?: number | undefined) => string

value.other // should still be number | null
>value.other : number | null
>value : A
>other : number | null

value.bar // should still be number | null
>value.bar : number | null
>value : A
>bar : number | null
}

11 changes: 11 additions & 0 deletions tests/cases/conformance/controlFlow/controlFlowStringIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @strict: true
type A = {
other: number | null;
[index: string]: number | null
};
declare const value: A;
if (value.foo !== null) {
value.foo.toExponential()
value.other // should still be number | null
value.bar // should still be number | null
}

0 comments on commit 76a3be7

Please sign in to comment.