@@ -215,7 +215,7 @@ namespace ts {
215
215
const flowLoopKeys: string[] = [];
216
216
const flowLoopTypes: Type[][] = [];
217
217
const visitedFlowNodes: FlowNode[] = [];
218
- const visitedFlowTypes: Type [] = [];
218
+ const visitedFlowTypes: FlowType [] = [];
219
219
const potentialThisCollisions: Node[] = [];
220
220
const awaitedTypeStack: number[] = [];
221
221
@@ -8086,21 +8086,33 @@ namespace ts {
8086
8086
f(type) ? type : neverType;
8087
8087
}
8088
8088
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
+
8089
8101
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
8090
8102
let key: string;
8091
8103
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
8092
8104
return declaredType;
8093
8105
}
8094
8106
const initialType = assumeInitialized ? declaredType : includeFalsyTypes(declaredType, TypeFlags.Undefined);
8095
8107
const visitedFlowStart = visitedFlowCount;
8096
- const result = getTypeAtFlowNode(reference.flowNode);
8108
+ const result = getTypeFromFlowType( getTypeAtFlowNode(reference.flowNode) );
8097
8109
visitedFlowCount = visitedFlowStart;
8098
8110
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
8099
8111
return declaredType;
8100
8112
}
8101
8113
return result;
8102
8114
8103
- function getTypeAtFlowNode(flow: FlowNode): Type {
8115
+ function getTypeAtFlowNode(flow: FlowNode): FlowType {
8104
8116
while (true) {
8105
8117
if (flow.flags & FlowFlags.Shared) {
8106
8118
// We cache results of flow type resolution for shared nodes that were previously visited in
@@ -8112,7 +8124,7 @@ namespace ts {
8112
8124
}
8113
8125
}
8114
8126
}
8115
- let type: Type ;
8127
+ let type: FlowType ;
8116
8128
if (flow.flags & FlowFlags.Assignment) {
8117
8129
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
8118
8130
if (!type) {
@@ -8180,41 +8192,44 @@ namespace ts {
8180
8192
return undefined;
8181
8193
}
8182
8194
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);
8185
8198
if (type !== neverType) {
8186
8199
// 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
8192
8204
// narrow that.
8193
8205
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
8194
8206
type = narrowType(type, flow.expression, assumeTrue);
8195
- if (type === neverType) {
8207
+ if (type === neverType && isIncomplete(flowType) ) {
8196
8208
type = narrowType(declaredType, flow.expression, assumeTrue);
8197
8209
}
8198
8210
}
8199
- return type;
8211
+ return createFlowType( type, isIncomplete(flowType)) ;
8200
8212
}
8201
8213
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);
8204
8217
const expr = flow.switchStatement.expression;
8205
8218
if (isMatchingReference(reference, expr)) {
8206
- return narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
8219
+ type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
8207
8220
}
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));
8210
8223
}
8211
- return type;
8224
+ return createFlowType( type, isIncomplete(flowType)) ;
8212
8225
}
8213
8226
8214
- function getTypeAtFlowBranchLabel(flow: FlowLabel) {
8227
+ function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
8215
8228
const antecedentTypes: Type[] = [];
8229
+ let seenIncomplete = false;
8216
8230
for (const antecedent of flow.antecedents) {
8217
- const type = getTypeAtFlowNode(antecedent);
8231
+ const flowType = getTypeAtFlowNode(antecedent);
8232
+ const type = getTypeFromFlowType(flowType);
8218
8233
// If the type at a particular antecedent path is the declared type and the
8219
8234
// reference is known to always be assigned (i.e. when declared and initial types
8220
8235
// are the same), there is no reason to process more antecedents since the only
@@ -8225,11 +8240,14 @@ namespace ts {
8225
8240
if (!contains(antecedentTypes, type)) {
8226
8241
antecedentTypes.push(type);
8227
8242
}
8243
+ if (isIncomplete(flowType)) {
8244
+ seenIncomplete = true;
8245
+ }
8228
8246
}
8229
- return getUnionType(antecedentTypes);
8247
+ return createFlowType( getUnionType(antecedentTypes), seenIncomplete );
8230
8248
}
8231
8249
8232
- function getTypeAtFlowLoopLabel(flow: FlowLabel) {
8250
+ function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
8233
8251
// If we have previously computed the control flow type for the reference at
8234
8252
// this flow loop junction, return the cached type.
8235
8253
const id = getFlowNodeId(flow);
@@ -8241,12 +8259,12 @@ namespace ts {
8241
8259
return cache[key];
8242
8260
}
8243
8261
// 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.
8247
8265
for (let i = flowLoopStart; i < flowLoopCount; i++) {
8248
8266
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) {
8249
- return getUnionType(flowLoopTypes[i]);
8267
+ return createFlowType( getUnionType(flowLoopTypes[i]), true );
8250
8268
}
8251
8269
}
8252
8270
// Add the flow loop junction and reference to the in-process stack and analyze
@@ -8257,7 +8275,7 @@ namespace ts {
8257
8275
flowLoopTypes[flowLoopCount] = antecedentTypes;
8258
8276
for (const antecedent of flow.antecedents) {
8259
8277
flowLoopCount++;
8260
- const type = getTypeAtFlowNode(antecedent);
8278
+ const type = getTypeFromFlowType( getTypeAtFlowNode(antecedent) );
8261
8279
flowLoopCount--;
8262
8280
// If we see a value appear in the cache it is a sign that control flow analysis
8263
8281
// was restarted and completed by checkExpressionCached. We can simply pick up
0 commit comments