@@ -20806,29 +20806,8 @@ namespace ts {
20806
20806
const facts = assumeTrue ?
20807
20807
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
20808
20808
typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
20809
- return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
20810
-
20811
- function narrowTypeForTypeof(type: Type) {
20812
- // We narrow a non-union type to an exact primitive type if the non-union type
20813
- // is a supertype of that primitive type. For example, type 'any' can be narrowed
20814
- // to one of the primitive types.
20815
- const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
20816
- if (targetType) {
20817
- if (isTypeSubtypeOf(type, targetType)) {
20818
- return type;
20819
- }
20820
- if (isTypeSubtypeOf(targetType, type)) {
20821
- return targetType;
20822
- }
20823
- if (type.flags & TypeFlags.Instantiable) {
20824
- const constraint = getBaseConstraintOfType(type) || anyType;
20825
- if (isTypeSubtypeOf(targetType, constraint)) {
20826
- return getIntersectionType([type, targetType]);
20827
- }
20828
- }
20829
- }
20830
- return type;
20831
- }
20809
+ const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text);
20810
+ return getTypeWithFacts(assumeTrue && impliedType ? mapType(type, narrowUnionMemberByTypeof(impliedType)) : type, facts);
20832
20811
}
20833
20812
20834
20813
function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) {
@@ -20879,19 +20858,28 @@ namespace ts {
20879
20858
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
20880
20859
}
20881
20860
20882
- function getImpliedTypeFromTypeofCase (type: Type, text: string) {
20861
+ function getImpliedTypeFromTypeofGuard (type: Type, text: string) {
20883
20862
switch (text) {
20884
20863
case "function":
20885
20864
return type.flags & TypeFlags.Any ? type : globalFunctionType;
20886
20865
case "object":
20887
20866
return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type;
20888
20867
default:
20889
- return typeofTypesByName.get(text) || type ;
20868
+ return typeofTypesByName.get(text);
20890
20869
}
20891
20870
}
20892
20871
20893
- function narrowTypeForTypeofSwitch(candidate: Type) {
20872
+ // When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are
20873
+ // super-types of the implied guard will be retained in the final type: this is because type-facts only
20874
+ // filter. Instead, we would like to replace those union constituents with the more precise type implied by
20875
+ // the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not
20876
+ // the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to
20877
+ // filtering by type-facts.
20878
+ function narrowUnionMemberByTypeof(candidate: Type) {
20894
20879
return (type: Type) => {
20880
+ if (isTypeSubtypeOf(type, candidate)) {
20881
+ return type;
20882
+ }
20895
20883
if (isTypeSubtypeOf(candidate, type)) {
20896
20884
return candidate;
20897
20885
}
@@ -20916,11 +20904,9 @@ namespace ts {
20916
20904
let clauseWitnesses: string[];
20917
20905
let switchFacts: TypeFacts;
20918
20906
if (defaultCaseLocation > -1) {
20919
- // We no longer need the undefined denoting an
20920
- // explicit default case. Remove the undefined and
20921
- // fix-up clauseStart and clauseEnd. This means
20922
- // that we don't have to worry about undefined
20923
- // in the witness array.
20907
+ // We no longer need the undefined denoting an explicit default case. Remove the undefined and
20908
+ // fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the
20909
+ // witness array.
20924
20910
const witnesses = <string[]>switchWitnesses.filter(witness => witness !== undefined);
20925
20911
// The adjusted clause start and end after removing the `default` statement.
20926
20912
const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart;
@@ -20963,11 +20949,8 @@ namespace ts {
20963
20949
boolean. We know that number cannot be selected
20964
20950
because it is caught in the first clause.
20965
20951
*/
20966
- let impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofCase(type, text))), switchFacts);
20967
- if (impliedType.flags & TypeFlags.Union) {
20968
- impliedType = getAssignmentReducedType(impliedType as UnionType, getBaseConstraintOrType(type));
20969
- }
20970
- return getTypeWithFacts(mapType(type, narrowTypeForTypeofSwitch(impliedType)), switchFacts);
20952
+ const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts);
20953
+ return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts);
20971
20954
}
20972
20955
20973
20956
function isMatchingConstructorReference(expr: Expression) {
0 commit comments