Skip to content

Commit 08b6e2e

Browse files
committed
CFA inlining of conditional expressions referenced by const variables
1 parent 6452cfb commit 08b6e2e

File tree

1 file changed

+91
-35
lines changed

1 file changed

+91
-35
lines changed

src/compiler/checker.ts

Lines changed: 91 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21998,9 +21998,35 @@ namespace ts {
2199821998
(containsTruthyCheck(source, (target as BinaryExpression).left) || containsTruthyCheck(source, (target as BinaryExpression).right)));
2199921999
}
2200022000

22001-
function getAccessedPropertyName(access: AccessExpression): __String | undefined {
22001+
function getPropertyAccess(expr: Expression) {
22002+
if (isAccessExpression(expr)) {
22003+
return expr;
22004+
}
22005+
if (isIdentifier(expr)) {
22006+
const symbol = getResolvedSymbol(expr);
22007+
if (isConstVariable(symbol)) {
22008+
const declaration = symbol.valueDeclaration!;
22009+
// Given 'const x = obj.kind', allow 'x' as an alias for 'obj.kind'
22010+
if (isVariableDeclaration(declaration) && !declaration.type && declaration.initializer && isAccessExpression(declaration.initializer)) {
22011+
return declaration.initializer;
22012+
}
22013+
// Given 'const { kind: x } = obj', allow 'x' as an alias for 'obj.kind'
22014+
if (isBindingElement(declaration) && !declaration.initializer) {
22015+
const parent = declaration.parent.parent;
22016+
if (isVariableDeclaration(parent) && !parent.type && parent.initializer && (isIdentifier(parent.initializer) || isAccessExpression(parent.initializer))) {
22017+
return declaration;
22018+
}
22019+
}
22020+
}
22021+
}
22022+
return undefined;
22023+
}
22024+
22025+
function getAccessedPropertyName(access: AccessExpression | BindingElement): __String | undefined {
22026+
let propertyName;
2200222027
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
22003-
isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
22028+
access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
22029+
access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) :
2200422030
undefined;
2200522031
}
2200622032

@@ -22948,14 +22974,15 @@ namespace ts {
2294822974
}
2294922975
}
2295022976

22951-
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
22977+
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, isConstant?: boolean, flowContainer?: Node) {
2295222978
let key: string | undefined;
2295322979
let isKeySet = false;
2295422980
let flowDepth = 0;
22981+
let inlineLevel = 0;
2295522982
if (flowAnalysisDisabled) {
2295622983
return errorType;
2295722984
}
22958-
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
22985+
if (!reference.flowNode) {
2295922986
return declaredType;
2296022987
}
2296122988
flowInvocationCount++;
@@ -23244,8 +23271,9 @@ namespace ts {
2324423271
t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t as StringLiteralType).value === "undefined"));
2324523272
}
2324623273
}
23247-
if (isMatchingReferenceDiscriminant(expr, type)) {
23248-
type = narrowTypeBySwitchOnDiscriminantProperty(type, expr as AccessExpression, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
23274+
const access = getDiscriminantPropertyAccess(expr, type);
23275+
if (access) {
23276+
type = narrowTypeBySwitchOnDiscriminantProperty(type, access, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
2324923277
}
2325023278
}
2325123279
return createFlowType(type, isIncomplete(flowType));
@@ -23402,19 +23430,16 @@ namespace ts {
2340223430
return result;
2340323431
}
2340423432

23405-
function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) {
23433+
function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) {
23434+
let access, name;
2340623435
const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType;
23407-
if (!(type.flags & TypeFlags.Union) || !isAccessExpression(expr)) {
23408-
return false;
23409-
}
23410-
const name = getAccessedPropertyName(expr);
23411-
if (name === undefined) {
23412-
return false;
23413-
}
23414-
return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(type, name);
23436+
return type.flags & TypeFlags.Union && (access = getPropertyAccess(expr)) && (name = getAccessedPropertyName(access)) &&
23437+
isMatchingReference(reference, isAccessExpression(access) ? access.expression : access.parent.parent.initializer!) &&
23438+
isDiscriminantProperty(type, name) ?
23439+
access : undefined;
2341523440
}
2341623441

23417-
function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type {
23442+
function narrowTypeByDiscriminant(type: Type, access: AccessExpression | BindingElement, narrowType: (t: Type) => Type): Type {
2341823443
const propName = getAccessedPropertyName(access);
2341923444
if (propName === undefined) {
2342023445
return type;
@@ -23432,7 +23457,7 @@ namespace ts {
2343223457
});
2343323458
}
2343423459

23435-
function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression, operator: SyntaxKind, value: Expression, assumeTrue: boolean) {
23460+
function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, operator: SyntaxKind, value: Expression, assumeTrue: boolean) {
2343623461
if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) {
2343723462
const keyPropertyName = getKeyPropertyName(type as UnionType);
2343823463
if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) {
@@ -23447,7 +23472,7 @@ namespace ts {
2344723472
return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue));
2344823473
}
2344923474

23450-
function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
23475+
function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
2345123476
if (clauseStart < clauseEnd && type.flags & TypeFlags.Union && getKeyPropertyName(type as UnionType) === getAccessedPropertyName(access)) {
2345223477
const clauseTypes = getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd);
2345323478
const candidate = getUnionType(map(clauseTypes, t => getConstituentTypeForKeyType(type as UnionType, t) || unknownType));
@@ -23465,8 +23490,9 @@ namespace ts {
2346523490
if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
2346623491
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2346723492
}
23468-
if (isMatchingReferenceDiscriminant(expr, type)) {
23469-
return narrowTypeByDiscriminant(type, expr as AccessExpression, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
23493+
const access = getDiscriminantPropertyAccess(expr, type);
23494+
if (access) {
23495+
return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy));
2347023496
}
2347123497
return type;
2347223498
}
@@ -23524,11 +23550,13 @@ namespace ts {
2352423550
type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue);
2352523551
}
2352623552
}
23527-
if (isMatchingReferenceDiscriminant(left, type)) {
23528-
return narrowTypeByDiscriminantProperty(type, left as AccessExpression, operator, right, assumeTrue);
23553+
const leftAccess = getDiscriminantPropertyAccess(left, type);
23554+
if (leftAccess) {
23555+
return narrowTypeByDiscriminantProperty(type, leftAccess, operator, right, assumeTrue);
2352923556
}
23530-
if (isMatchingReferenceDiscriminant(right, type)) {
23531-
return narrowTypeByDiscriminantProperty(type, right as AccessExpression, operator, left, assumeTrue);
23557+
const rightAccess = getDiscriminantPropertyAccess(right, type);
23558+
if (rightAccess) {
23559+
return narrowTypeByDiscriminantProperty(type, rightAccess, operator, left, assumeTrue);
2353223560
}
2353323561
if (isMatchingConstructorReference(left)) {
2353423562
return narrowTypeByConstructor(type, operator, right, assumeTrue);
@@ -23553,6 +23581,17 @@ namespace ts {
2355323581
break;
2355423582
case SyntaxKind.CommaToken:
2355523583
return narrowType(type, expr.right, assumeTrue);
23584+
// Ordinarily we won't see && and || expressions in control flow analysis because the Binder breaks those
23585+
// expressions down to individual conditional control flows. However, we may encounter them when analyzing
23586+
// aliased conditional expressions.
23587+
case SyntaxKind.AmpersandAmpersandToken:
23588+
return assumeTrue ?
23589+
narrowType(narrowType(type, expr.left, /*assumeTrue*/ true), expr.right, /*assumeTrue*/ true) :
23590+
getUnionType([narrowType(type, expr.left, /*assumeTrue*/ false), narrowType(type, expr.right, /*assumeTrue*/ false)]);
23591+
case SyntaxKind.BarBarToken:
23592+
return assumeTrue ?
23593+
getUnionType([narrowType(type, expr.left, /*assumeTrue*/ true), narrowType(type, expr.right, /*assumeTrue*/ true)]) :
23594+
narrowType(narrowType(type, expr.left, /*assumeTrue*/ false), expr.right, /*assumeTrue*/ false);
2355623595
}
2355723596
return type;
2355823597
}
@@ -23959,8 +23998,9 @@ namespace ts {
2395923998
!(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) {
2396023999
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
2396124000
}
23962-
if (isMatchingReferenceDiscriminant(predicateArgument, type)) {
23963-
return narrowTypeByDiscriminant(type, predicateArgument as AccessExpression, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf));
24001+
const access = getDiscriminantPropertyAccess(predicateArgument, type);
24002+
if (access) {
24003+
return narrowTypeByDiscriminant(type, access, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf));
2396424004
}
2396524005
}
2396624006
}
@@ -23977,6 +24017,21 @@ namespace ts {
2397724017
}
2397824018
switch (expr.kind) {
2397924019
case SyntaxKind.Identifier:
24020+
// When narrowing a reference to a const variable, non-assigned parameter, or readonly property, we inline
24021+
// up to two levels of aliased conditional expressions that are themselves declared as const variables.
24022+
if (isConstant && !isMatchingReference(reference, expr) && inlineLevel < 2) {
24023+
const symbol = getResolvedSymbol(expr as Identifier);
24024+
if (isConstVariable(symbol)) {
24025+
const declaration = symbol.valueDeclaration;
24026+
if (declaration && isVariableDeclaration(declaration) && !declaration.type && declaration.initializer) {
24027+
inlineLevel++;
24028+
const result = narrowType(type, declaration.initializer, assumeTrue);
24029+
inlineLevel--;
24030+
return result;
24031+
}
24032+
}
24033+
}
24034+
// falls through
2398024035
case SyntaxKind.ThisKeyword:
2398124036
case SyntaxKind.SuperKeyword:
2398224037
case SyntaxKind.PropertyAccessExpression:
@@ -24002,8 +24057,9 @@ namespace ts {
2400224057
if (isMatchingReference(reference, expr)) {
2400324058
return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull);
2400424059
}
24005-
if (isMatchingReferenceDiscriminant(expr, type)) {
24006-
return narrowTypeByDiscriminant(type, expr as AccessExpression, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull));
24060+
const access = getDiscriminantPropertyAccess(expr, type);
24061+
if (access) {
24062+
return narrowTypeByDiscriminant(type, access, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull));
2400724063
}
2400824064
return type;
2400924065
}
@@ -24081,7 +24137,7 @@ namespace ts {
2408124137
}
2408224138

2408324139
function isConstVariable(symbol: Symbol) {
24084-
return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType;
24140+
return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0;
2408524141
}
2408624142

2408724143
/** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */
@@ -24313,12 +24369,12 @@ namespace ts {
2431324369
const isOuterVariable = flowContainer !== declarationContainer;
2431424370
const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent);
2431524371
const isModuleExports = symbol.flags & SymbolFlags.ModuleExports;
24372+
const isConstant = isConstVariable(localOrExportSymbol) && getTypeOfSymbol(localOrExportSymbol) !== autoArrayType || isParameter && !isParameterAssigned(localOrExportSymbol);
2431624373
// When the control flow originates in a function expression or arrow function and we are referencing
2431724374
// a const variable or parameter from an outer function, we extend the origin of the control flow
2431824375
// analysis to include the immediately enclosing function.
24319-
while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
24320-
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) &&
24321-
(isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) {
24376+
while (isConstant && flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
24377+
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer))) {
2432224378
flowContainer = getControlFlowContainer(flowContainer);
2432324379
}
2432424380
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
@@ -24333,7 +24389,7 @@ namespace ts {
2433324389
const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) :
2433424390
type === autoType || type === autoArrayType ? undefinedType :
2433524391
getOptionalType(type);
24336-
const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized);
24392+
const flowType = getFlowTypeOfReference(node, type, initialType, isConstant, flowContainer);
2433724393
// A variable is considered uninitialized when it is possible to analyze the entire control flow graph
2433824394
// from declaration to use, and when the variable's declared type doesn't include undefined but the
2433924395
// control flow based type does include undefined.
@@ -27547,7 +27603,7 @@ namespace ts {
2754727603
getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) {
2754827604
assumeUninitialized = true;
2754927605
}
27550-
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
27606+
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType, prop && isReadonlySymbol(prop));
2755127607
if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
2755227608
error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
2755327609
// Return the declared type to reduce follow-on errors
@@ -37991,7 +38047,7 @@ namespace ts {
3799138047
error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context);
3799238048
}
3799338049

37994-
const isAmbientExternalModule = isAmbientModule(node);
38050+
const isAmbientExternalModule: boolean = isAmbientModule(node);
3799538051
const contextErrorMessage = isAmbientExternalModule
3799638052
? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file
3799738053
: Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module;

0 commit comments

Comments
 (0)