@@ -755,6 +755,7 @@ namespace ts {
755
755
const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType);
756
756
const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic");
757
757
const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown");
758
+ const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown");
758
759
const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
759
760
const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType);
760
761
const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined");
@@ -14048,7 +14049,9 @@ namespace ts {
14048
14049
const includes = addTypesToUnion(typeSet, 0, types);
14049
14050
if (unionReduction !== UnionReduction.None) {
14050
14051
if (includes & TypeFlags.AnyOrUnknown) {
14051
- return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType;
14052
+ return includes & TypeFlags.Any ?
14053
+ includes & TypeFlags.IncludesWildcard ? wildcardType : anyType :
14054
+ includes & TypeFlags.Null || containsType(typeSet, unknownType) ? unknownType : nonNullUnknownType;
14052
14055
}
14053
14056
if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) {
14054
14057
const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues);
@@ -22220,13 +22223,6 @@ namespace ts {
22220
22223
return false;
22221
22224
}
22222
22225
22223
- // Given a source x, check if target matches x or is an && operation with an operand that matches x.
22224
- function containsTruthyCheck(source: Node, target: Node): boolean {
22225
- return isMatchingReference(source, target) ||
22226
- (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken &&
22227
- (containsTruthyCheck(source, (target as BinaryExpression).left) || containsTruthyCheck(source, (target as BinaryExpression).right)));
22228
- }
22229
-
22230
22226
function getPropertyAccess(expr: Expression) {
22231
22227
if (isAccessExpression(expr)) {
22232
22228
return expr;
@@ -23239,7 +23235,8 @@ namespace ts {
23239
23235
if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
23240
23236
return declaredType;
23241
23237
}
23242
- return resultType;
23238
+ // The non-null unknown type should never escape control flow analysis.
23239
+ return resultType === nonNullUnknownType ? unknownType : resultType;
23243
23240
23244
23241
function getOrSetCacheKey() {
23245
23242
if (isKeySet) {
@@ -23727,7 +23724,8 @@ namespace ts {
23727
23724
23728
23725
function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
23729
23726
if (isMatchingReference(reference, expr)) {
23730
- return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
23727
+ return type.flags & TypeFlags.Unknown && assumeTrue ? nonNullUnknownType :
23728
+ getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy);
23731
23729
}
23732
23730
if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
23733
23731
type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
@@ -23885,7 +23883,7 @@ namespace ts {
23885
23883
valueType.flags & TypeFlags.Null ?
23886
23884
assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
23887
23885
assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined;
23888
- return getTypeWithFacts(type, facts);
23886
+ return type.flags & TypeFlags.Unknown && facts & (TypeFacts.NENull | TypeFacts.NEUndefinedOrNull) ? nonNullUnknownType : getTypeWithFacts(type, facts);
23889
23887
}
23890
23888
if (assumeTrue) {
23891
23889
const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ?
@@ -23915,15 +23913,10 @@ namespace ts {
23915
23913
return type;
23916
23914
}
23917
23915
if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") {
23918
- // The pattern x && typeof x === 'object', where x is of type unknown, narrows x to type object. We don't
23919
- // need to check for the reverse typeof x === 'object' && x since that already narrows correctly.
23920
- if (typeOfExpr.parent.parent.kind === SyntaxKind.BinaryExpression) {
23921
- const expr = typeOfExpr.parent.parent as BinaryExpression;
23922
- if (expr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && expr.right === typeOfExpr.parent && containsTruthyCheck(reference, expr.left)) {
23923
- return nonPrimitiveType;
23924
- }
23925
- }
23926
- return getUnionType([nonPrimitiveType, nullType]);
23916
+ // The non-null unknown type is used to track whether a previous narrowing operation has removed the null type
23917
+ // from the unknown type. For example, the expression `x && typeof x === 'object'` first narrows x to the non-null
23918
+ // unknown type, and then narrows that to the non-primitive type.
23919
+ return type === nonNullUnknownType ? nonPrimitiveType : getUnionType([nonPrimitiveType, nullType]);
23927
23920
}
23928
23921
const facts = assumeTrue ?
23929
23922
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
0 commit comments