@@ -245,7 +245,8 @@ namespace ts {
245245 NEUndefinedOrNull = 1 << 19, // x != undefined / x != null
246246 Truthy = 1 << 20, // x
247247 Falsy = 1 << 21, // !x
248- All = (1 << 22) - 1,
248+ Discriminatable = 1 << 22, // May have discriminant property
249+ All = (1 << 23) - 1,
249250 // The following members encode facts about particular kinds of types for use in the getTypeFacts function.
250251 // The presence of a particular fact means that the given test is true for some (and possibly all) values
251252 // of that kind of type.
@@ -275,9 +276,9 @@ namespace ts {
275276 TrueFacts = BaseBooleanFacts | Truthy,
276277 SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
277278 SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
278- ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
279+ ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable ,
279280 ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
280- FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
281+ FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy | Discriminatable ,
281282 FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
282283 UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy,
283284 NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy,
@@ -5351,12 +5352,12 @@ namespace ts {
53515352 }
53525353 }
53535354
5354- // We deduplicate the constituent types based on object identity. If the subtypeReduction flag is
5355- // specified we also reduce the constituent type set to only include types that aren't subtypes of
5356- // other types. Subtype reduction is expensive for large union types and is possible only when union
5355+ // We sort and deduplicate the constituent types based on object identity. If the subtypeReduction
5356+ // flag is specified we also reduce the constituent type set to only include types that aren't subtypes
5357+ // of other types. Subtype reduction is expensive for large union types and is possible only when union
53575358 // types are known not to circularly reference themselves (as is the case with union types created by
53585359 // expression constructs such as array literals and the || and ?: operators). Named types can
5359- // circularly reference themselves and therefore cannot be deduplicated during their declaration.
5360+ // circularly reference themselves and therefore cannot be subtype reduced during their declaration.
53605361 // For example, "type Item = string | (() => Item" is a named type that circularly references itself.
53615362 function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
53625363 if (types.length === 0) {
@@ -5378,15 +5379,23 @@ namespace ts {
53785379 typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
53795380 neverType;
53805381 }
5381- else if (typeSet.length === 1) {
5382- return typeSet[0];
5382+ return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments);
5383+ }
5384+
5385+ // This function assumes the constituent type list is sorted and deduplicated.
5386+ function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
5387+ if (types.length === 0) {
5388+ return neverType;
53835389 }
5384- const id = getTypeListId(typeSet);
5390+ if (types.length === 1) {
5391+ return types[0];
5392+ }
5393+ const id = getTypeListId(types);
53855394 let type = unionTypes[id];
53865395 if (!type) {
5387- const propagatedFlags = getPropagatingFlagsOfTypes(typeSet , /*excludeKinds*/ TypeFlags.Nullable);
5396+ const propagatedFlags = getPropagatingFlagsOfTypes(types , /*excludeKinds*/ TypeFlags.Nullable);
53885397 type = unionTypes[id] = <UnionType>createObjectType(TypeFlags.Union | propagatedFlags);
5389- type.types = typeSet ;
5398+ type.types = types ;
53905399 type.aliasSymbol = aliasSymbol;
53915400 type.aliasTypeArguments = aliasTypeArguments;
53925401 }
@@ -7848,17 +7857,24 @@ namespace ts {
78487857 }
78497858
78507859 function isDiscriminantProperty(type: Type, name: string) {
7851- if (type) {
7852- const nonNullType = getNonNullableType(type);
7853- if (nonNullType.flags & TypeFlags.Union) {
7854- const prop = getPropertyOfType(nonNullType, name);
7855- if (prop && prop.flags & SymbolFlags.SyntheticProperty) {
7856- if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
7857- (<TransientSymbol>prop).isDiscriminantProperty = !(<TransientSymbol>prop).hasCommonType &&
7858- isUnitUnionType(getTypeOfSymbol(prop));
7859- }
7860- return (<TransientSymbol>prop).isDiscriminantProperty;
7860+ if (type && type.flags & TypeFlags.Union) {
7861+ let prop = getPropertyOfType(type, name);
7862+ if (!prop) {
7863+ // The type may be a union that includes nullable or primitive types. If filtering
7864+ // those out produces a different type, get the property from that type instead.
7865+ // Effectively, we're checking if this *could* be a discriminant property once nullable
7866+ // and primitive types are removed by other type guards.
7867+ const filteredType = getTypeWithFacts(type, TypeFacts.Discriminatable);
7868+ if (filteredType !== type && filteredType.flags & TypeFlags.Union) {
7869+ prop = getPropertyOfType(filteredType, name);
7870+ }
7871+ }
7872+ if (prop && prop.flags & SymbolFlags.SyntheticProperty) {
7873+ if ((<TransientSymbol>prop).isDiscriminantProperty === undefined) {
7874+ (<TransientSymbol>prop).isDiscriminantProperty = !(<TransientSymbol>prop).hasCommonType &&
7875+ isUnitUnionType(getTypeOfSymbol(prop));
78617876 }
7877+ return (<TransientSymbol>prop).isDiscriminantProperty;
78627878 }
78637879 }
78647880 return false;
@@ -7907,10 +7923,10 @@ namespace ts {
79077923 // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
79087924 // we remove type string.
79097925 function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) {
7910- if (declaredType !== assignedType && declaredType.flags & TypeFlags.Union ) {
7911- const reducedTypes = filter (declaredType.types , t => typeMaybeAssignableTo(assignedType, t));
7912- if (reducedTypes.length ) {
7913- return reducedTypes.length === 1 ? reducedTypes[0] : getUnionType(reducedTypes) ;
7926+ if (declaredType !== assignedType) {
7927+ const reducedType = filterType (declaredType, t => typeMaybeAssignableTo(assignedType, t));
7928+ if (reducedType !== neverType ) {
7929+ return reducedType ;
79147930 }
79157931 }
79167932 return declaredType;
@@ -7924,6 +7940,14 @@ namespace ts {
79247940 return result;
79257941 }
79267942
7943+ function isFunctionObjectType(type: ObjectType): boolean {
7944+ // We do a quick check for a "bind" property before performing the more expensive subtype
7945+ // check. This gives us a quicker out in the common case where an object type is not a function.
7946+ const resolved = resolveStructuredTypeMembers(type);
7947+ return !!(resolved.callSignatures.length || resolved.constructSignatures.length ||
7948+ hasProperty(resolved.members, "bind") && isTypeSubtypeOf(type, globalFunctionType));
7949+ }
7950+
79277951 function getTypeFacts(type: Type): TypeFacts {
79287952 const flags = type.flags;
79297953 if (flags & TypeFlags.String) {
@@ -7952,8 +7976,7 @@ namespace ts {
79527976 type === falseType ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
79537977 }
79547978 if (flags & TypeFlags.ObjectType) {
7955- const resolved = resolveStructuredTypeMembers(type);
7956- return resolved.callSignatures.length || resolved.constructSignatures.length || isTypeSubtypeOf(type, globalFunctionType) ?
7979+ return isFunctionObjectType(<ObjectType>type) ?
79577980 strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
79587981 strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
79597982 }
@@ -7977,26 +8000,7 @@ namespace ts {
79778000 }
79788001
79798002 function getTypeWithFacts(type: Type, include: TypeFacts) {
7980- if (!(type.flags & TypeFlags.Union)) {
7981- return getTypeFacts(type) & include ? type : neverType;
7982- }
7983- let firstType: Type;
7984- let types: Type[];
7985- for (const t of (type as UnionType).types) {
7986- if (getTypeFacts(t) & include) {
7987- if (!firstType) {
7988- firstType = t;
7989- }
7990- else {
7991- if (!types) {
7992- types = [firstType];
7993- }
7994- types.push(t);
7995- }
7996- }
7997- }
7998- return types ? getUnionType(types) :
7999- firstType ? firstType : neverType;
8003+ return filterType(type, t => (getTypeFacts(t) & include) !== 0);
80008004 }
80018005
80028006 function getTypeWithDefault(type: Type, defaultExpression: Expression) {
@@ -8172,9 +8176,12 @@ namespace ts {
81728176 }
81738177
81748178 function filterType(type: Type, f: (t: Type) => boolean): Type {
8175- return type.flags & TypeFlags.Union ?
8176- getUnionType(filter((<UnionType>type).types, f)) :
8177- f(type) ? type : neverType;
8179+ if (type.flags & TypeFlags.Union) {
8180+ const types = (<UnionType>type).types;
8181+ const filtered = filter(types, f);
8182+ return filtered === types ? type : getUnionTypeFromSortedList(filtered);
8183+ }
8184+ return f(type) ? type : neverType;
81788185 }
81798186
81808187 function isIncomplete(flowType: FlowType) {
@@ -8512,7 +8519,7 @@ namespace ts {
85128519 }
85138520 if (assumeTrue && !(type.flags & TypeFlags.Union)) {
85148521 // We narrow a non-union type to an exact primitive type if the non-union type
8515- // is a supertype of that primtive type. For example, type 'any' can be narrowed
8522+ // is a supertype of that primitive type. For example, type 'any' can be narrowed
85168523 // to one of the primitive types.
85178524 const targetType = getProperty(typeofTypesByName, literal.text);
85188525 if (targetType && isTypeSubtypeOf(targetType, type)) {
@@ -8601,9 +8608,9 @@ namespace ts {
86018608 // If the current type is a union type, remove all constituents that couldn't be instances of
86028609 // the candidate type. If one or more constituents remain, return a union of those.
86038610 if (type.flags & TypeFlags.Union) {
8604- const assignableConstituents = filter((<UnionType> type).types , t => isTypeInstanceOf(t, candidate));
8605- if (assignableConstituents.length ) {
8606- return getUnionType(assignableConstituents) ;
8611+ const assignableType = filterType( type, t => isTypeInstanceOf(t, candidate));
8612+ if (assignableType !== neverType ) {
8613+ return assignableType ;
86078614 }
86088615 }
86098616 // If the candidate type is a subtype of the target type, narrow to the candidate type.
0 commit comments