@@ -755,6 +755,7 @@ namespace ts {
755755 const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType);
756756 const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic");
757757 const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown");
758+ const nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown");
758759 const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined");
759760 const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType);
760761 const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined");
@@ -14048,7 +14049,9 @@ namespace ts {
1404814049 const includes = addTypesToUnion(typeSet, 0, types);
1404914050 if (unionReduction !== UnionReduction.None) {
1405014051 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;
1405214055 }
1405314056 if (exactOptionalPropertyTypes && includes & TypeFlags.Undefined) {
1405414057 const missingIndex = binarySearch(typeSet, missingType, getTypeId, compareValues);
@@ -22220,13 +22223,6 @@ namespace ts {
2222022223 return false;
2222122224 }
2222222225
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-
2223022226 function getPropertyAccess(expr: Expression) {
2223122227 if (isAccessExpression(expr)) {
2223222228 return expr;
@@ -23239,7 +23235,8 @@ namespace ts {
2323923235 if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && !(resultType.flags & TypeFlags.Never) && getTypeWithFacts(resultType, TypeFacts.NEUndefinedOrNull).flags & TypeFlags.Never) {
2324023236 return declaredType;
2324123237 }
23242- return resultType;
23238+ // The non-null unknown type should never escape control flow analysis.
23239+ return resultType === nonNullUnknownType ? unknownType : resultType;
2324323240
2324423241 function getOrSetCacheKey() {
2324523242 if (isKeySet) {
@@ -23727,7 +23724,8 @@ namespace ts {
2372723724
2372823725 function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type {
2372923726 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);
2373123729 }
2373223730 if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) {
2373323731 type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull);
@@ -23885,7 +23883,7 @@ namespace ts {
2388523883 valueType.flags & TypeFlags.Null ?
2388623884 assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull :
2388723885 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);
2388923887 }
2389023888 if (assumeTrue) {
2389123889 const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ?
@@ -23915,15 +23913,10 @@ namespace ts {
2391523913 return type;
2391623914 }
2391723915 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]);
2392723920 }
2392823921 const facts = assumeTrue ?
2392923922 typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
0 commit comments