Skip to content

Commit

Permalink
Control flow for element access expressions (#31478)
Browse files Browse the repository at this point in the history
* Control flow for element access expressions

Draft version, just want to see how performance is

* Add baselines

* Fix cast lint

* Cleanup to share code path

* Fix errant diffs
  • Loading branch information
sandersn authored Jul 16, 2019
1 parent c7b8b2a commit 1de76cd
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 4 deletions.
13 changes: 9 additions & 4 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20573,10 +20573,15 @@ namespace ts {
}
propType = getConstraintForLocation(getTypeOfSymbol(prop), node);
}
return getFlowTypeOfAccessExpression(node, prop, propType, right);
}

function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) {
// 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 ||
const assignmentKind = getAssignmentTargetKind(node);
if (node.kind !== SyntaxKind.ElementAccessExpression && node.kind !== SyntaxKind.PropertyAccessExpression ||
assignmentKind === AssignmentKind.Definite ||
prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) {
return propType;
Expand All @@ -20586,7 +20591,7 @@ namespace ts {
// and if we are in a constructor of the same class as the property declaration, assume that
// the property is uninitialized at the top of the control flow.
let assumeUninitialized = false;
if (strictNullChecks && strictPropertyInitialization && left.kind === SyntaxKind.ThisKeyword) {
if (strictNullChecks && strictPropertyInitialization && node.expression.kind === SyntaxKind.ThisKeyword) {
const declaration = prop && prop.valueDeclaration;
if (declaration && isInstancePropertyWithoutInitializer(declaration)) {
const flowContainer = getControlFlowContainer(node);
Expand All @@ -20603,7 +20608,7 @@ namespace ts {
}
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
error(right, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
// Return the declared type to reduce follow-on errors
return propType;
}
Expand Down Expand Up @@ -20960,7 +20965,7 @@ namespace ts {
AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) :
AccessFlags.None;
const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType;
return checkIndexedAccessIndexType(indexedAccessType, node);
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
}

function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
Expand Down
25 changes: 25 additions & 0 deletions tests/baselines/reference/controlFlowElementAccess2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [controlFlowElementAccess2.ts]
declare const config: {
[key: string]: boolean | { prop: string };
};

if (typeof config['works'] !== 'boolean') {
config.works.prop = 'test'; // ok
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
}
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}


//// [controlFlowElementAccess2.js]
"use strict";
if (typeof config['works'] !== 'boolean') {
config.works.prop = 'test'; // ok
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
}
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}
37 changes: 37 additions & 0 deletions tests/baselines/reference/controlFlowElementAccess2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
=== tests/cases/conformance/controlFlow/controlFlowElementAccess2.ts ===
declare const config: {
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))

[key: string]: boolean | { prop: string };
>key : Symbol(key, Decl(controlFlowElementAccess2.ts, 1, 5))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))

};

if (typeof config['works'] !== 'boolean') {
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))

config.works.prop = 'test'; // ok
>config.works.prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))

config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
}
if (typeof config.works !== 'boolean') {
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))

config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))

config.works.prop = 'test'; // ok
>config.works.prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
>config : Symbol(config, Decl(controlFlowElementAccess2.ts, 0, 13))
>prop : Symbol(prop, Decl(controlFlowElementAccess2.ts, 1, 30))
}

63 changes: 63 additions & 0 deletions tests/baselines/reference/controlFlowElementAccess2.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
=== tests/cases/conformance/controlFlow/controlFlowElementAccess2.ts ===
declare const config: {
>config : { [key: string]: boolean | { prop: string; }; }

[key: string]: boolean | { prop: string };
>key : string
>prop : string

};

if (typeof config['works'] !== 'boolean') {
>typeof config['works'] !== 'boolean' : boolean
>typeof config['works'] : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>config['works'] : boolean | { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>'works' : "works"
>'boolean' : "boolean"

config.works.prop = 'test'; // ok
>config.works.prop = 'test' : "test"
>config.works.prop : string
>config.works : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>works : { prop: string; }
>prop : string
>'test' : "test"

config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop = 'test' : "test"
>config['works'].prop : string
>config['works'] : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>'works' : "works"
>prop : string
>'test' : "test"
}
if (typeof config.works !== 'boolean') {
>typeof config.works !== 'boolean' : boolean
>typeof config.works : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>config.works : boolean | { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>works : boolean | { prop: string; }
>'boolean' : "boolean"

config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
>config['works'].prop = 'test' : "test"
>config['works'].prop : string
>config['works'] : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>'works' : "works"
>prop : string
>'test' : "test"

config.works.prop = 'test'; // ok
>config.works.prop = 'test' : "test"
>config.works.prop : string
>config.works : { prop: string; }
>config : { [key: string]: boolean | { prop: string; }; }
>works : { prop: string; }
>prop : string
>'test' : "test"
}

13 changes: 13 additions & 0 deletions tests/cases/conformance/controlFlow/controlFlowElementAccess2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @strict: true
declare const config: {
[key: string]: boolean | { prop: string };
};

if (typeof config['works'] !== 'boolean') {
config.works.prop = 'test'; // ok
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
}
if (typeof config.works !== 'boolean') {
config['works'].prop = 'test'; // error, config['works']: boolean | { 'prop': string }
config.works.prop = 'test'; // ok
}

0 comments on commit 1de76cd

Please sign in to comment.