@@ -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
@@ -8090,21 +8090,33 @@ namespace ts {
8090
8090
f(type) ? type : neverType;
8091
8091
}
8092
8092
8093
+ function isIncomplete(flowType: FlowType) {
8094
+ return flowType.flags === 0;
8095
+ }
8096
+
8097
+ function getTypeFromFlowType(flowType: FlowType) {
8098
+ return flowType.flags === 0 ? (<IncompleteType>flowType).type : <Type>flowType;
8099
+ }
8100
+
8101
+ function createFlowType(type: Type, incomplete: boolean): FlowType {
8102
+ return incomplete ? { flags: 0, type } : type;
8103
+ }
8104
+
8093
8105
function getFlowTypeOfReference(reference: Node, declaredType: Type, assumeInitialized: boolean, includeOuterFunctions: boolean) {
8094
8106
let key: string;
8095
8107
if (!reference.flowNode || assumeInitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
8096
8108
return declaredType;
8097
8109
}
8098
8110
const initialType = assumeInitialized ? declaredType : includeFalsyTypes(declaredType, TypeFlags.Undefined);
8099
8111
const visitedFlowStart = visitedFlowCount;
8100
- const result = getTypeAtFlowNode(reference.flowNode);
8112
+ const result = getTypeFromFlowType( getTypeAtFlowNode(reference.flowNode) );
8101
8113
visitedFlowCount = visitedFlowStart;
8102
8114
if (reference.parent.kind === SyntaxKind.NonNullExpression && getTypeWithFacts(result, TypeFacts.NEUndefinedOrNull) === neverType) {
8103
8115
return declaredType;
8104
8116
}
8105
8117
return result;
8106
8118
8107
- function getTypeAtFlowNode(flow: FlowNode): Type {
8119
+ function getTypeAtFlowNode(flow: FlowNode): FlowType {
8108
8120
while (true) {
8109
8121
if (flow.flags & FlowFlags.Shared) {
8110
8122
// We cache results of flow type resolution for shared nodes that were previously visited in
@@ -8116,7 +8128,7 @@ namespace ts {
8116
8128
}
8117
8129
}
8118
8130
}
8119
- let type: Type ;
8131
+ let type: FlowType ;
8120
8132
if (flow.flags & FlowFlags.Assignment) {
8121
8133
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
8122
8134
if (!type) {
@@ -8184,41 +8196,44 @@ namespace ts {
8184
8196
return undefined;
8185
8197
}
8186
8198
8187
- function getTypeAtFlowCondition(flow: FlowCondition) {
8188
- let type = getTypeAtFlowNode(flow.antecedent);
8199
+ function getTypeAtFlowCondition(flow: FlowCondition): FlowType {
8200
+ const flowType = getTypeAtFlowNode(flow.antecedent);
8201
+ let type = getTypeFromFlowType(flowType);
8189
8202
if (type !== neverType) {
8190
8203
// If we have an antecedent type (meaning we're reachable in some way), we first
8191
- // attempt to narrow the antecedent type. If that produces the nothing type, then
8192
- // we take the type guard as an indication that control could reach here in a
8193
- // manner not understood by the control flow analyzer (e.g. a function argument
8194
- // has an invalid type, or a nested function has possibly made an assignment to a
8195
- // captured variable). We proceed by reverting to the declared type and then
8204
+ // attempt to narrow the antecedent type. If that produces the never type, and if
8205
+ // the antecedent type is incomplete (i.e. a transient type in a loop), then we
8206
+ // take the type guard as an indication that control *could* reach here once we
8207
+ // have the complete type. We proceed by reverting to the declared type and then
8196
8208
// narrow that.
8197
8209
const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0;
8198
8210
type = narrowType(type, flow.expression, assumeTrue);
8199
- if (type === neverType) {
8211
+ if (type === neverType && isIncomplete(flowType) ) {
8200
8212
type = narrowType(declaredType, flow.expression, assumeTrue);
8201
8213
}
8202
8214
}
8203
- return type;
8215
+ return createFlowType( type, isIncomplete(flowType)) ;
8204
8216
}
8205
8217
8206
- function getTypeAtSwitchClause(flow: FlowSwitchClause) {
8207
- const type = getTypeAtFlowNode(flow.antecedent);
8218
+ function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType {
8219
+ const flowType = getTypeAtFlowNode(flow.antecedent);
8220
+ let type = getTypeFromFlowType(flowType);
8208
8221
const expr = flow.switchStatement.expression;
8209
8222
if (isMatchingReference(reference, expr)) {
8210
- return narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
8223
+ type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd);
8211
8224
}
8212
- if (isMatchingPropertyAccess(expr)) {
8213
- return narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
8225
+ else if (isMatchingPropertyAccess(expr)) {
8226
+ type = narrowTypeByDiscriminant(type, <PropertyAccessExpression>expr, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd));
8214
8227
}
8215
- return type;
8228
+ return createFlowType( type, isIncomplete(flowType)) ;
8216
8229
}
8217
8230
8218
- function getTypeAtFlowBranchLabel(flow: FlowLabel) {
8231
+ function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType {
8219
8232
const antecedentTypes: Type[] = [];
8233
+ let seenIncomplete = false;
8220
8234
for (const antecedent of flow.antecedents) {
8221
- const type = getTypeAtFlowNode(antecedent);
8235
+ const flowType = getTypeAtFlowNode(antecedent);
8236
+ const type = getTypeFromFlowType(flowType);
8222
8237
// If the type at a particular antecedent path is the declared type and the
8223
8238
// reference is known to always be assigned (i.e. when declared and initial types
8224
8239
// are the same), there is no reason to process more antecedents since the only
@@ -8229,11 +8244,14 @@ namespace ts {
8229
8244
if (!contains(antecedentTypes, type)) {
8230
8245
antecedentTypes.push(type);
8231
8246
}
8247
+ if (isIncomplete(flowType)) {
8248
+ seenIncomplete = true;
8249
+ }
8232
8250
}
8233
- return getUnionType(antecedentTypes);
8251
+ return createFlowType( getUnionType(antecedentTypes), seenIncomplete );
8234
8252
}
8235
8253
8236
- function getTypeAtFlowLoopLabel(flow: FlowLabel) {
8254
+ function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType {
8237
8255
// If we have previously computed the control flow type for the reference at
8238
8256
// this flow loop junction, return the cached type.
8239
8257
const id = getFlowNodeId(flow);
@@ -8245,12 +8263,12 @@ namespace ts {
8245
8263
return cache[key];
8246
8264
}
8247
8265
// If this flow loop junction and reference are already being processed, return
8248
- // the union of the types computed for each branch so far. We should never see
8249
- // an empty array here because the first antecedent of a loop junction is always
8250
- // the non-looping control flow path that leads to the top.
8266
+ // the union of the types computed for each branch so far, marked as incomplete.
8267
+ // We should never see an empty array here because the first antecedent of a loop
8268
+ // junction is always the non-looping control flow path that leads to the top.
8251
8269
for (let i = flowLoopStart; i < flowLoopCount; i++) {
8252
8270
if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key) {
8253
- return getUnionType(flowLoopTypes[i]);
8271
+ return createFlowType( getUnionType(flowLoopTypes[i]), /*incomplete*/ true );
8254
8272
}
8255
8273
}
8256
8274
// Add the flow loop junction and reference to the in-process stack and analyze
@@ -8261,7 +8279,7 @@ namespace ts {
8261
8279
flowLoopTypes[flowLoopCount] = antecedentTypes;
8262
8280
for (const antecedent of flow.antecedents) {
8263
8281
flowLoopCount++;
8264
- const type = getTypeAtFlowNode(antecedent);
8282
+ const type = getTypeFromFlowType( getTypeAtFlowNode(antecedent) );
8265
8283
flowLoopCount--;
8266
8284
// If we see a value appear in the cache it is a sign that control flow analysis
8267
8285
// was restarted and completed by checkExpressionCached. We can simply pick up
0 commit comments