Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}