@@ -17491,110 +17491,28 @@ namespace ts {
17491
17491
return type;
17492
17492
}
17493
17493
17494
- function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
17495
- // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
17496
- const target = getReferenceCandidate(typeOfExpr.expression);
17497
- if (!isMatchingReference(reference, target)) {
17498
- // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
17499
- // narrowed type of 'y' to its declared type.
17500
- if (containsMatchingReference(reference, target)) {
17501
- return declaredType;
17502
- }
17503
- return type;
17504
- }
17505
- if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
17506
- assumeTrue = !assumeTrue;
17507
- }
17508
- if (type.flags & TypeFlags.Any && literal.text === "function") {
17509
- return type;
17510
- }
17511
- const facts = assumeTrue ?
17512
- typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
17513
- typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
17514
- return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
17515
-
17516
- function narrowTypeForTypeof(type: Type) {
17517
- if (type.flags & TypeFlags.Unknown && literal.text === "object") {
17518
- return getUnionType([nonPrimitiveType, nullType]);
17519
- }
17520
- // We narrow a non-union type to an exact primitive type if the non-union type
17521
- // is a supertype of that primitive type. For example, type 'any' can be narrowed
17522
- // to one of the primitive types.
17523
- const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
17524
- if (targetType) {
17525
- if (isTypeSubtypeOf(type, targetType)) {
17526
- return type;
17527
- }
17528
- if (isTypeSubtypeOf(targetType, type)) {
17529
- return targetType;
17530
- }
17531
- if (type.flags & TypeFlags.Instantiable) {
17532
- const constraint = getBaseConstraintOfType(type) || anyType;
17533
- if (isTypeSubtypeOf(targetType, constraint)) {
17534
- return getIntersectionType([type, targetType]);
17535
- }
17536
- }
17537
- }
17538
- return type;
17539
- }
17540
- }
17541
-
17542
- function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
17543
- // We only narrow if all case expressions specify
17544
- // values with unit types, except for the case where
17545
- // `type` is unknown. In this instance we map object
17546
- // types to the nonPrimitive type and narrow with that.
17547
- const switchTypes = getSwitchClauseTypes(switchStatement);
17548
- if (!switchTypes.length) {
17549
- return type;
17550
- }
17551
- const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
17552
- const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
17553
- if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) {
17554
- let groundClauseTypes: Type[] | undefined;
17555
- for (let i = 0; i < clauseTypes.length; i += 1) {
17556
- const t = clauseTypes[i];
17557
- if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
17558
- if (groundClauseTypes !== undefined) {
17559
- groundClauseTypes.push(t);
17560
- }
17561
- }
17562
- else if (t.flags & TypeFlags.Object) {
17563
- if (groundClauseTypes === undefined) {
17564
- groundClauseTypes = clauseTypes.slice(0, i);
17565
- }
17566
- groundClauseTypes.push(nonPrimitiveType);
17567
- }
17568
- else {
17569
- return type;
17570
- }
17571
- }
17572
- return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes);
17573
- }
17574
- const discriminantType = getUnionType(clauseTypes);
17575
- const caseType =
17576
- discriminantType.flags & TypeFlags.Never ? neverType :
17577
- replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType);
17578
- if (!hasDefaultClause) {
17579
- return caseType;
17580
- }
17581
- const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t))));
17582
- return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
17583
- }
17584
-
17585
- function getImpliedTypeFromTypeofCase(type: Type, text: string) {
17494
+ function getImpliedTypeFromTypeofGuard(type: Type, text: string) {
17586
17495
switch (text) {
17587
17496
case "function":
17588
17497
return type.flags & TypeFlags.Any ? type : globalFunctionType;
17589
17498
case "object":
17590
17499
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
17591
17500
default:
17592
- return typeofTypesByName.get(text) || type ;
17501
+ return typeofTypesByName.get(text);
17593
17502
}
17594
17503
}
17595
17504
17596
- function narrowTypeForTypeofSwitch(candidate: Type) {
17505
+ // When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are
17506
+ // super-types of the implied guard will be retained in the final type: this is because type-facts only
17507
+ // filter. Instead, we would like to replace those union constituents with the more precise type implied by
17508
+ // the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not
17509
+ // the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to
17510
+ // filtering by type-facts.
17511
+ function narrowUnionMemberByTypeof(candidate: Type) {
17597
17512
return (type: Type) => {
17513
+ if (isTypeSubtypeOf(type, candidate)) {
17514
+ return type;
17515
+ }
17598
17516
if (isTypeSubtypeOf(candidate, type)) {
17599
17517
return candidate;
17600
17518
}
@@ -17608,6 +17526,30 @@ namespace ts {
17608
17526
};
17609
17527
}
17610
17528
17529
+ function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type {
17530
+ // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands
17531
+ const target = getReferenceCandidate(typeOfExpr.expression);
17532
+ if (!isMatchingReference(reference, target)) {
17533
+ // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
17534
+ // narrowed type of 'y' to its declared type.
17535
+ if (containsMatchingReference(reference, target)) {
17536
+ return declaredType;
17537
+ }
17538
+ return type;
17539
+ }
17540
+ if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) {
17541
+ assumeTrue = !assumeTrue;
17542
+ }
17543
+ if (type.flags & TypeFlags.Any && literal.text === "function") {
17544
+ return type;
17545
+ }
17546
+ const facts = assumeTrue ?
17547
+ typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
17548
+ typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
17549
+ const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text);
17550
+ return getTypeWithFacts(assumeTrue && impliedType ? mapType(type, narrowUnionMemberByTypeof(impliedType)) : type, facts);
17551
+ }
17552
+
17611
17553
function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type {
17612
17554
const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement);
17613
17555
if (!switchWitnesses.length) {
@@ -17619,11 +17561,9 @@ namespace ts {
17619
17561
let clauseWitnesses: string[];
17620
17562
let switchFacts: TypeFacts;
17621
17563
if (defaultCaseLocation > -1) {
17622
- // We no longer need the undefined denoting an
17623
- // explicit default case. Remove the undefined and
17624
- // fix-up clauseStart and clauseEnd. This means
17625
- // that we don't have to worry about undefined
17626
- // in the witness array.
17564
+ // We no longer need the undefined denoting an explicit default case. Remove the undefined and
17565
+ // fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the
17566
+ // witness array.
17627
17567
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
17628
17568
// The adjusted clause start and end after removing the `default` statement.
17629
17569
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
@@ -17666,11 +17606,51 @@ namespace ts {
17666
17606
boolean. We know that number cannot be selected
17667
17607
because it is caught in the first clause.
17668
17608
*/
17669
- let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofCase(type, text))), switchFacts);
17670
- if (impliedType.flags & TypeFlags.Union) {
17671
- impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
17609
+ const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts);
17610
+ return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts);
17611
+ }
17612
+
17613
+ function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
17614
+ // We only narrow if all case expressions specify
17615
+ // values with unit types, except for the case where
17616
+ // `type` is unknown. In this instance we map object
17617
+ // types to the nonPrimitive type and narrow with that.
17618
+ const switchTypes = getSwitchClauseTypes(switchStatement);
17619
+ if (!switchTypes.length) {
17620
+ return type;
17621
+ }
17622
+ const clauseTypes = switchTypes.slice(clauseStart, clauseEnd);
17623
+ const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType);
17624
+ if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) {
17625
+ let groundClauseTypes: Type[] | undefined;
17626
+ for (let i = 0; i < clauseTypes.length; i += 1) {
17627
+ const t = clauseTypes[i];
17628
+ if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) {
17629
+ if (groundClauseTypes !== undefined) {
17630
+ groundClauseTypes.push(t);
17631
+ }
17632
+ }
17633
+ else if (t.flags & TypeFlags.Object) {
17634
+ if (groundClauseTypes === undefined) {
17635
+ groundClauseTypes = clauseTypes.slice(0, i);
17636
+ }
17637
+ groundClauseTypes.push(nonPrimitiveType);
17638
+ }
17639
+ else {
17640
+ return type;
17641
+ }
17642
+ }
17643
+ return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes);
17672
17644
}
17673
- return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
17645
+ const discriminantType = getUnionType(clauseTypes);
17646
+ const caseType =
17647
+ discriminantType.flags & TypeFlags.Never ? neverType :
17648
+ replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType);
17649
+ if (!hasDefaultClause) {
17650
+ return caseType;
17651
+ }
17652
+ const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t))));
17653
+ return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
17674
17654
}
17675
17655
17676
17656
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
0 commit comments