@@ -245,7 +245,8 @@ namespace ts {
245
245
NEUndefinedOrNull = 1 << 19, // x != undefined / x != null
246
246
Truthy = 1 << 20, // x
247
247
Falsy = 1 << 21, // !x
248
- All = (1 << 22) - 1,
248
+ Discriminatable = 1 << 22, // May have discriminant property
249
+ All = (1 << 23) - 1,
249
250
// The following members encode facts about particular kinds of types for use in the getTypeFacts function.
250
251
// The presence of a particular fact means that the given test is true for some (and possibly all) values
251
252
// of that kind of type.
@@ -275,9 +276,9 @@ namespace ts {
275
276
TrueFacts = BaseBooleanFacts | Truthy,
276
277
SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy,
277
278
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 ,
279
280
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 ,
281
282
FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
282
283
UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy,
283
284
NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy,
@@ -5351,12 +5352,12 @@ namespace ts {
5351
5352
}
5352
5353
}
5353
5354
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
5357
5358
// types are known not to circularly reference themselves (as is the case with union types created by
5358
5359
// 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.
5360
5361
// For example, "type Item = string | (() => Item" is a named type that circularly references itself.
5361
5362
function getUnionType(types: Type[], subtypeReduction?: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
5362
5363
if (types.length === 0) {
@@ -5378,15 +5379,23 @@ namespace ts {
5378
5379
typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
5379
5380
neverType;
5380
5381
}
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;
5383
5389
}
5384
- const id = getTypeListId(typeSet);
5390
+ if (types.length === 1) {
5391
+ return types[0];
5392
+ }
5393
+ const id = getTypeListId(types);
5385
5394
let type = unionTypes[id];
5386
5395
if (!type) {
5387
- const propagatedFlags = getPropagatingFlagsOfTypes(typeSet , /*excludeKinds*/ TypeFlags.Nullable);
5396
+ const propagatedFlags = getPropagatingFlagsOfTypes(types , /*excludeKinds*/ TypeFlags.Nullable);
5388
5397
type = unionTypes[id] = <UnionType>createObjectType(TypeFlags.Union | propagatedFlags);
5389
- type.types = typeSet ;
5398
+ type.types = types ;
5390
5399
type.aliasSymbol = aliasSymbol;
5391
5400
type.aliasTypeArguments = aliasTypeArguments;
5392
5401
}
@@ -7848,17 +7857,24 @@ namespace ts {
7848
7857
}
7849
7858
7850
7859
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));
7861
7876
}
7877
+ return (<TransientSymbol>prop).isDiscriminantProperty;
7862
7878
}
7863
7879
}
7864
7880
return false;
@@ -7907,10 +7923,10 @@ namespace ts {
7907
7923
// For example, when a variable of type number | string | boolean is assigned a value of type number | boolean,
7908
7924
// we remove type string.
7909
7925
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 ;
7914
7930
}
7915
7931
}
7916
7932
return declaredType;
@@ -7924,6 +7940,14 @@ namespace ts {
7924
7940
return result;
7925
7941
}
7926
7942
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
+
7927
7951
function getTypeFacts(type: Type): TypeFacts {
7928
7952
const flags = type.flags;
7929
7953
if (flags & TypeFlags.String) {
@@ -7952,8 +7976,7 @@ namespace ts {
7952
7976
type === falseType ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
7953
7977
}
7954
7978
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) ?
7957
7980
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
7958
7981
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
7959
7982
}
@@ -7977,26 +8000,7 @@ namespace ts {
7977
8000
}
7978
8001
7979
8002
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);
8000
8004
}
8001
8005
8002
8006
function getTypeWithDefault(type: Type, defaultExpression: Expression) {
@@ -8172,9 +8176,12 @@ namespace ts {
8172
8176
}
8173
8177
8174
8178
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;
8178
8185
}
8179
8186
8180
8187
function isIncomplete(flowType: FlowType) {
@@ -8512,7 +8519,7 @@ namespace ts {
8512
8519
}
8513
8520
if (assumeTrue && !(type.flags & TypeFlags.Union)) {
8514
8521
// 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
8516
8523
// to one of the primitive types.
8517
8524
const targetType = getProperty(typeofTypesByName, literal.text);
8518
8525
if (targetType && isTypeSubtypeOf(targetType, type)) {
@@ -8601,9 +8608,9 @@ namespace ts {
8601
8608
// If the current type is a union type, remove all constituents that couldn't be instances of
8602
8609
// the candidate type. If one or more constituents remain, return a union of those.
8603
8610
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 ;
8607
8614
}
8608
8615
}
8609
8616
// If the candidate type is a subtype of the target type, narrow to the candidate type.
0 commit comments