Skip to content

Commit 7ab6e11

Browse files
committed
Limit type guards as assertions to incomplete types in loops
1 parent f06413b commit 7ab6e11

File tree

2 files changed

+56
-28
lines changed

2 files changed

+56
-28
lines changed

src/compiler/checker.ts

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ namespace ts {
215215
const flowLoopKeys: string[] = [];
216216
const flowLoopTypes: Type[][] = [];
217217
const visitedFlowNodes: FlowNode[] = [];
218-
const visitedFlowTypes: Type[] = [];
218+
const visitedFlowTypes: FlowType[] = [];
219219
const potentialThisCollisions: Node[] = [];
220220
const awaitedTypeStack: number[] = [];
221221

@@ -8086,21 +8086,33 @@ namespace ts {
80868086
f(type) ? type : neverType;
80878087
}
80888088

8089+
function isIncomplete(flowType: FlowType) {
8090+
return flowType.flags === 0;
8091+
}
8092+
8093+
function getTypeFromFlowType(flowType: FlowType) {
8094+
return flowType.flags === 0 ? (<IncompleteType>flowType).type : <Type>flowType;
8095+
}
8096+
8097+
function createFlowType(type: Type, incomplete: boolean): FlowType {
8098+
return incomplete ? { flags: 0, type } : type;
8099+
}
8100+
80898101
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
80908102
let key: string;
80918103
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
80928104
return declaredType;
80938105
}
80948106
const initialType = assumeInitialized ? declaredType : includeFalsyTypes(declaredType, TypeFlags.Undefined);
80958107
const visitedFlowStart = visitedFlowCount;
8096-
const result = getTypeAtFlowNode(reference.flowNode);
8108+
const result = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
80978109
visitedFlowCount = visitedFlowStart;
80988110
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
80998111
return declaredType;
81008112
}
81018113
return result;
81028114

8103-
function getTypeAtFlowNode(flow: FlowNode): Type {
8115+
function getTypeAtFlowNode(flow: FlowNode): FlowType {
81048116
while (true) {
81058117
if (flow.flags & FlowFlags.Shared) {
81068118
// We cache results of flow type resolution for shared nodes that were previously visited in
@@ -8112,7 +8124,7 @@ namespace ts {
81128124
}
81138125
}
81148126
}
8115-
let type: Type;
8127+
let type: FlowType;
81168128
if (flow.flags & FlowFlags.Assignment) {
81178129
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
81188130
if (!type) {
@@ -8180,41 +8192,44 @@ namespace ts {
81808192
return undefined;
81818193
}
81828194

8183-
function getTypeAtFlowCondition(flow: FlowCondition) {
8184-
let type = getTypeAtFlowNode(flow.antecedent);
8195+
function getTypeAtFlowCondition(flow: FlowCondition): FlowType {
8196+
const flowType = getTypeAtFlowNode(flow.antecedent);
8197+
let type = getTypeFromFlowType(flowType);
81858198
if (type !== neverType) {
81868199
// If we have an antecedent type (meaning we're reachable in some way), we first
8187-
// attempt to narrow the antecedent type. If that produces the nothing type, then
8188-
// we take the type guard as an indication that control could reach here in a
8189-
// manner not understood by the control flow analyzer (e.g. a function argument
8190-
// has an invalid type, or a nested function has possibly made an assignment to a
8191-
// captured variable). We proceed by reverting to the declared type and then
8200+
// attempt to narrow the antecedent type. If that produces the never type, and if
8201+
// the antecedent type is incomplete (i.e. a transient type in a loop), then we
8202+
// take the type guard as an indication that control *could* reach here once we
8203+
// have the complete type. We proceed by reverting to the declared type and then
81928204
// narrow that.
81938205
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
81948206
type = narrowType(type, flow.expression, assumeTrue);
8195-
if (type === neverType) {
8207+
if (type === neverType && isIncomplete(flowType)) {
81968208
type = narrowType(declaredType, flow.expression, assumeTrue);
81978209
}
81988210
}
8199-
return type;
8211+
return createFlowType(type, isIncomplete(flowType));
82008212
}
82018213

8202-
function getTypeAtSwitchClause(flow: FlowSwitchClause) {
8203-
const type = getTypeAtFlowNode(flow.antecedent);
8214+
function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
8215+
const flowType = getTypeAtFlowNode(flow.antecedent);
8216+
let type = getTypeFromFlowType(flowType);
82048217
const expr = flow.switchStatement.expression;
82058218
if (isMatchingReference(reference, expr)) {
8206-
return narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
8219+
type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
82078220
}
8208-
if (isMatchingPropertyAccess(expr)) {
8209-
return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
8221+
else if (isMatchingPropertyAccess(expr)) {
8222+
type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
82108223
}
8211-
return type;
8224+
return createFlowType(type, isIncomplete(flowType));
82128225
}
82138226

8214-
function getTypeAtFlowBranchLabel(flow: FlowLabel) {
8227+
function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
82158228
const antecedentTypes: Type[] = [];
8229+
let seenIncomplete = false;
82168230
for (const antecedent of flow.antecedents) {
8217-
const type = getTypeAtFlowNode(antecedent);
8231+
const flowType = getTypeAtFlowNode(antecedent);
8232+
const type = getTypeFromFlowType(flowType);
82188233
// If the type at a particular antecedent path is the declared type and the
82198234
// reference is known to always be assigned (i.e. when declared and initial types
82208235
// are the same), there is no reason to process more antecedents since the only
@@ -8225,11 +8240,14 @@ namespace ts {
82258240
if (!contains(antecedentTypes, type)) {
82268241
antecedentTypes.push(type);
82278242
}
8243+
if (isIncomplete(flowType)) {
8244+
seenIncomplete = true;
8245+
}
82288246
}
8229-
return getUnionType(antecedentTypes);
8247+
return createFlowType(getUnionType(antecedentTypes), seenIncomplete);
82308248
}
82318249

8232-
function getTypeAtFlowLoopLabel(flow: FlowLabel) {
8250+
function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
82338251
// If we have previously computed the control flow type for the reference at
82348252
// this flow loop junction, return the cached type.
82358253
const id = getFlowNodeId(flow);
@@ -8241,12 +8259,12 @@ namespace ts {
82418259
return cache[key];
82428260
}
82438261
// If this flow loop junction and reference are already being processed, return
8244-
// the union of the types computed for each branch so far. We should never see
8245-
// an empty array here because the first antecedent of a loop junction is always
8246-
// the non-looping control flow path that leads to the top.
8262+
// the union of the types computed for each branch so far, marked as incomplete.
8263+
// We should never see an empty array here because the first antecedent of a loop
8264+
// junction is always the non-looping control flow path that leads to the top.
82478265
for (let i = flowLoopStart; i < flowLoopCount; i++) {
82488266
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) {
8249-
return getUnionType(flowLoopTypes[i]);
8267+
return createFlowType(getUnionType(flowLoopTypes[i]), true);
82508268
}
82518269
}
82528270
// Add the flow loop junction and reference to the in-process stack and analyze
@@ -8257,7 +8275,7 @@ namespace ts {
82578275
flowLoopTypes[flowLoopCount] = antecedentTypes;
82588276
for (const antecedent of flow.antecedents) {
82598277
flowLoopCount++;
8260-
const type = getTypeAtFlowNode(antecedent);
8278+
const type = getTypeFromFlowType(getTypeAtFlowNode(antecedent));
82618279
flowLoopCount--;
82628280
// If we see a value appear in the cache it is a sign that control flow analysis
82638281
// was restarted and completed by checkExpressionCached. We can simply pick up

src/compiler/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,16 @@ namespace ts {
16061606
antecedent: FlowNode;
16071607
}
16081608

1609+
export type FlowType = Type | IncompleteType;
1610+
1611+
// Incomplete types occur during control flow analysis of loops. An IncompleteType
1612+
// is distinguished from a regular type by a flags value of zero. Incomplete type
1613+
// objects are internal to the getFlowTypeOfRefecence function and never escape it.
1614+
export interface IncompleteType {
1615+
flags: TypeFlags; // No flags set
1616+
type: Type; // The type marked incomplete
1617+
}
1618+
16091619
export interface AmdDependency {
16101620
path: string;
16111621
name: string;

0 commit comments

Comments
 (0)