@@ -109,9 +109,9 @@ namespace ts {
109
109
const undefinedType = createIntrinsicType(TypeFlags.Undefined | TypeFlags.ContainsUndefinedOrNull, "undefined");
110
110
const nullType = createIntrinsicType(TypeFlags.Null | TypeFlags.ContainsUndefinedOrNull, "null");
111
111
const unknownType = createIntrinsicType(TypeFlags.Any, "unknown");
112
- const circularType = createIntrinsicType(TypeFlags.Any, "__circular__");
113
112
114
113
const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
114
+ const emptyUnionType = emptyObjectType;
115
115
const emptyGenericType = <GenericType><ObjectType>createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
116
116
emptyGenericType.instantiations = {};
117
117
@@ -4413,7 +4413,7 @@ namespace ts {
4413
4413
// a named type that circularly references itself.
4414
4414
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
4415
4415
if (types.length === 0) {
4416
- return emptyObjectType ;
4416
+ return emptyUnionType ;
4417
4417
}
4418
4418
const typeSet: Type[] = [];
4419
4419
addTypesToSet(typeSet, types, TypeFlags.Union);
@@ -6285,27 +6285,6 @@ namespace ts {
6285
6285
Debug.fail("should not get here");
6286
6286
}
6287
6287
6288
- // For a union type, remove all constituent types that are of the given type kind (when isOfTypeKind is true)
6289
- // or not of the given type kind (when isOfTypeKind is false)
6290
- function removeTypesFromUnionType(type: Type, typeKind: TypeFlags, isOfTypeKind: boolean, allowEmptyUnionResult: boolean): Type {
6291
- if (type.flags & TypeFlags.Union) {
6292
- const types = (<UnionType>type).types;
6293
- if (forEach(types, t => !!(t.flags & typeKind) === isOfTypeKind)) {
6294
- // Above we checked if we have anything to remove, now use the opposite test to do the removal
6295
- const narrowedType = getUnionType(filter(types, t => !(t.flags & typeKind) === isOfTypeKind));
6296
- if (allowEmptyUnionResult || narrowedType !== emptyObjectType) {
6297
- return narrowedType;
6298
- }
6299
- }
6300
- }
6301
- else if (allowEmptyUnionResult && !!(type.flags & typeKind) === isOfTypeKind) {
6302
- // Use getUnionType(emptyArray) instead of emptyObjectType in case the way empty union types
6303
- // are represented ever changes.
6304
- return getUnionType(emptyArray);
6305
- }
6306
- return type;
6307
- }
6308
-
6309
6288
function hasInitializer(node: VariableLikeDeclaration): boolean {
6310
6289
return !!(node.initializer || isBindingPattern(node.parent) && hasInitializer(<VariableLikeDeclaration>node.parent.parent));
6311
6290
}
@@ -6407,53 +6386,71 @@ namespace ts {
6407
6386
// Only narrow when symbol is variable of type any or an object, union, or type parameter type
6408
6387
if (node && symbol.flags & SymbolFlags.Variable) {
6409
6388
if (isTypeAny(type) || type.flags & (TypeFlags.ObjectType | TypeFlags.Union | TypeFlags.TypeParameter)) {
6389
+ const originalType = type;
6390
+ const nodeStack: {node: Node, child: Node}[] = [];
6410
6391
loop: while (node.parent) {
6411
6392
const child = node;
6412
6393
node = node.parent;
6413
- let narrowedType = type;
6394
+ switch (node.kind) {
6395
+ case SyntaxKind.IfStatement:
6396
+ case SyntaxKind.ConditionalExpression:
6397
+ case SyntaxKind.BinaryExpression:
6398
+ nodeStack.push({node, child});
6399
+ break;
6400
+ case SyntaxKind.SourceFile:
6401
+ case SyntaxKind.ModuleDeclaration:
6402
+ case SyntaxKind.FunctionDeclaration:
6403
+ case SyntaxKind.MethodDeclaration:
6404
+ case SyntaxKind.MethodSignature:
6405
+ case SyntaxKind.GetAccessor:
6406
+ case SyntaxKind.SetAccessor:
6407
+ case SyntaxKind.Constructor:
6408
+ // Stop at the first containing function or module declaration
6409
+ break loop;
6410
+ }
6411
+ }
6412
+
6413
+ let nodes: {node: Node, child: Node};
6414
+ while (nodes = nodeStack.pop()) {
6415
+ const {node, child} = nodes;
6414
6416
switch (node.kind) {
6415
6417
case SyntaxKind.IfStatement:
6416
6418
// In a branch of an if statement, narrow based on controlling expression
6417
6419
if (child !== (<IfStatement>node).expression) {
6418
- narrowedType = narrowType(type, (<IfStatement>node).expression, /*assumeTrue*/ child === (<IfStatement>node).thenStatement);
6420
+ type = narrowType(type, (<IfStatement>node).expression, /*assumeTrue*/ child === (<IfStatement>node).thenStatement);
6419
6421
}
6420
6422
break;
6421
6423
case SyntaxKind.ConditionalExpression:
6422
6424
// In a branch of a conditional expression, narrow based on controlling condition
6423
6425
if (child !== (<ConditionalExpression>node).condition) {
6424
- narrowedType = narrowType(type, (<ConditionalExpression>node).condition, /*assumeTrue*/ child === (<ConditionalExpression>node).whenTrue);
6426
+ type = narrowType(type, (<ConditionalExpression>node).condition, /*assumeTrue*/ child === (<ConditionalExpression>node).whenTrue);
6425
6427
}
6426
6428
break;
6427
6429
case SyntaxKind.BinaryExpression:
6428
6430
// In the right operand of an && or ||, narrow based on left operand
6429
6431
if (child === (<BinaryExpression>node).right) {
6430
6432
if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
6431
- narrowedType = narrowType(type, (<BinaryExpression>node).left, /*assumeTrue*/ true);
6433
+ type = narrowType(type, (<BinaryExpression>node).left, /*assumeTrue*/ true);
6432
6434
}
6433
6435
else if ((<BinaryExpression>node).operatorToken.kind === SyntaxKind.BarBarToken) {
6434
- narrowedType = narrowType(type, (<BinaryExpression>node).left, /*assumeTrue*/ false);
6436
+ type = narrowType(type, (<BinaryExpression>node).left, /*assumeTrue*/ false);
6435
6437
}
6436
6438
}
6437
6439
break;
6438
- case SyntaxKind.SourceFile:
6439
- case SyntaxKind.ModuleDeclaration:
6440
- case SyntaxKind.FunctionDeclaration:
6441
- case SyntaxKind.MethodDeclaration:
6442
- case SyntaxKind.MethodSignature:
6443
- case SyntaxKind.GetAccessor:
6444
- case SyntaxKind.SetAccessor:
6445
- case SyntaxKind.Constructor:
6446
- // Stop at the first containing function or module declaration
6447
- break loop;
6440
+ default:
6441
+ Debug.fail("Unreachable!");
6448
6442
}
6449
- // Use narrowed type if construct contains no assignments to variable
6450
- if (narrowedType !== type) {
6451
- if (isVariableAssignedWithin(symbol, node)) {
6452
- break;
6453
- }
6454
- type = narrowedType;
6443
+
6444
+ // Use original type if construct contains assignments to variable
6445
+ if (type !== originalType && isVariableAssignedWithin(symbol, node)) {
6446
+ type = originalType;
6455
6447
}
6456
6448
}
6449
+
6450
+ // Preserve old top-level behavior - if the branch is really an empty set, revert to prior type
6451
+ if (type === emptyUnionType) {
6452
+ type = originalType;
6453
+ }
6457
6454
}
6458
6455
}
6459
6456
@@ -6469,31 +6466,31 @@ namespace ts {
6469
6466
if (left.expression.kind !== SyntaxKind.Identifier || getResolvedSymbol(<Identifier>left.expression) !== symbol) {
6470
6467
return type;
6471
6468
}
6472
- const typeInfo = primitiveTypeInfo[right.text];
6473
6469
if (expr.operatorToken.kind === SyntaxKind.ExclamationEqualsEqualsToken) {
6474
6470
assumeTrue = !assumeTrue;
6475
6471
}
6476
- if (assumeTrue) {
6477
- // Assumed result is true. If check was not for a primitive type, remove all primitive types
6478
- if (!typeInfo) {
6479
- return removeTypesFromUnionType(type, /*typeKind*/ TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.Boolean | TypeFlags.ESSymbol,
6480
- /*isOfTypeKind*/ true, /*allowEmptyUnionResult*/ false);
6481
- }
6482
- // Check was for a primitive type, return that primitive type if it is a subtype
6483
- if (isTypeSubtypeOf(typeInfo.type, type)) {
6484
- return typeInfo.type;
6485
- }
6486
- // Otherwise, remove all types that aren't of the primitive type kind. This can happen when the type is
6487
- // union of enum types and other types.
6488
- return removeTypesFromUnionType(type, /*typeKind*/ typeInfo.flags, /*isOfTypeKind*/ false, /*allowEmptyUnionResult*/ false);
6472
+ const typeInfo = primitiveTypeInfo[right.text];
6473
+ // If the type to be narrowed is any and we're checking a primitive with assumeTrue=true, return the primitive
6474
+ if (!!(type.flags & TypeFlags.Any) && typeInfo && assumeTrue) {
6475
+ return typeInfo.type;
6476
+ }
6477
+ let flags: TypeFlags;
6478
+ if (typeInfo) {
6479
+ flags = typeInfo.flags;
6489
6480
}
6490
6481
else {
6491
- // Assumed result is false. If check was for a primitive type, remove that primitive type
6492
- if (typeInfo) {
6493
- return removeTypesFromUnionType(type, /*typeKind*/ typeInfo.flags, /*isOfTypeKind*/ true, /*allowEmptyUnionResult*/ false);
6494
- }
6495
- // Otherwise we don't have enough information to do anything.
6496
- return type;
6482
+ assumeTrue = !assumeTrue;
6483
+ flags = TypeFlags.NumberLike | TypeFlags.StringLike | TypeFlags.ESSymbol | TypeFlags.Boolean;
6484
+ }
6485
+ // At this point we can bail if it's not a union
6486
+ if (!(type.flags & TypeFlags.Union)) {
6487
+ // If the active non-union type would be removed from a union by this type guard, return an empty union
6488
+ return filterUnion(type) ? type : emptyUnionType;
6489
+ }
6490
+ return getUnionType(filter((type as UnionType).types, filterUnion), /*noSubtypeReduction*/ true);
6491
+
6492
+ function filterUnion(type: Type) {
6493
+ return assumeTrue === !!(type.flags & flags);
6497
6494
}
6498
6495
}
6499
6496
@@ -6507,7 +6504,7 @@ namespace ts {
6507
6504
// and the second operand was false. We narrow with those assumptions and union the two resulting types.
6508
6505
return getUnionType([
6509
6506
narrowType(type, expr.left, /*assumeTrue*/ false),
6510
- narrowType(narrowType( type, expr.left, /*assumeTrue*/ true) , expr.right, /*assumeTrue*/ false)
6507
+ narrowType(type, expr.right, /*assumeTrue*/ false)
6511
6508
]);
6512
6509
}
6513
6510
}
@@ -6518,7 +6515,7 @@ namespace ts {
6518
6515
// and the second operand was true. We narrow with those assumptions and union the two resulting types.
6519
6516
return getUnionType([
6520
6517
narrowType(type, expr.left, /*assumeTrue*/ true),
6521
- narrowType(narrowType( type, expr.left, /*assumeTrue*/ false) , expr.right, /*assumeTrue*/ true)
6518
+ narrowType(type, expr.right, /*assumeTrue*/ true)
6522
6519
]);
6523
6520
}
6524
6521
else {
@@ -12736,9 +12733,14 @@ namespace ts {
12736
12733
12737
12734
// After we remove all types that are StringLike, we will know if there was a string constituent
12738
12735
// based on whether the remaining type is the same as the initial type.
12739
- const arrayType = removeTypesFromUnionType(arrayOrStringType, TypeFlags.StringLike, /*isTypeOfKind*/ true, /*allowEmptyUnionResult*/ true);
12736
+ let arrayType = arrayOrStringType;
12737
+ if (arrayOrStringType.flags & TypeFlags.Union) {
12738
+ arrayType = getUnionType(filter((arrayOrStringType as UnionType).types, t => !(t.flags & TypeFlags.StringLike)));
12739
+ }
12740
+ else if (arrayOrStringType.flags & TypeFlags.StringLike) {
12741
+ arrayType = emptyUnionType;
12742
+ }
12740
12743
const hasStringConstituent = arrayOrStringType !== arrayType;
12741
-
12742
12744
let reportedError = false;
12743
12745
if (hasStringConstituent) {
12744
12746
if (languageVersion < ScriptTarget.ES5) {
0 commit comments