mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Control flow for element access expressions (#31478)
* 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
This commit is contained in:
parent
c7b8b2ae9b
commit
1de76cd605
@ -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;
|
||||
@ -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);
|
||||
@ -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;
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
25
tests/baselines/reference/controlFlowElementAccess2.js
Normal file
25
tests/baselines/reference/controlFlowElementAccess2.js
Normal file
@ -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
tests/baselines/reference/controlFlowElementAccess2.symbols
Normal file
37
tests/baselines/reference/controlFlowElementAccess2.symbols
Normal file
@ -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
tests/baselines/reference/controlFlowElementAccess2.types
Normal file
63
tests/baselines/reference/controlFlowElementAccess2.types
Normal file
@ -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"
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user