Skip to content

Commit acb8b1f

Browse files
committed
Correct falsiness for {} empty object type
1 parent 394ee31 commit acb8b1f

File tree

1 file changed

+31
-22
lines changed

1 file changed

+31
-22
lines changed

src/compiler/checker.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,8 @@ namespace ts {
601601
FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
602602
UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy,
603603
NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy,
604+
EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull),
605+
EmptyObjectFacts = All,
604606
}
605607

606608
const typeofEQFacts = createMapFromTemplate({
@@ -11774,8 +11776,12 @@ namespace ts {
1177411776
const simplified = getSimplifiedType((<IndexType>target).type);
1177511777
const constraint = simplified !== (<IndexType>target).type ? simplified : getConstraintOfType((<IndexType>target).type);
1177611778
if (constraint) {
11777-
if (result = isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors)) {
11778-
return result;
11779+
// We require Ternary.True here such that circular constraints don't cause
11780+
// false positives. For example, given 'T extends { [K in keyof T]: string }',
11781+
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
11782+
// related to other types.
11783+
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
11784+
return Ternary.True;
1177911785
}
1178011786
}
1178111787
}
@@ -14169,9 +14175,11 @@ namespace ts {
1416914175
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
1417014176
}
1417114177
if (flags & TypeFlags.Object) {
14172-
return isFunctionObjectType(<ObjectType>type) ?
14173-
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
14174-
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
14178+
return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(<ObjectType>type) ?
14179+
strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts :
14180+
isFunctionObjectType(<ObjectType>type) ?
14181+
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
14182+
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
1417514183
}
1417614184
if (flags & (TypeFlags.Void | TypeFlags.Undefined)) {
1417714185
return TypeFacts.UndefinedFacts;
@@ -15083,23 +15091,24 @@ namespace ts {
1508315091
return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
1508415092

1508515093
function narrowTypeForTypeof(type: Type) {
15086-
if (assumeTrue && !(type.flags & TypeFlags.Union)) {
15087-
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
15088-
return getUnionType([nonPrimitiveType, nullType]);
15089-
}
15090-
// We narrow a non-union type to an exact primitive type if the non-union type
15091-
// is a supertype of that primitive type. For example, type 'any' can be narrowed
15092-
// to one of the primitive types.
15093-
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
15094-
if (targetType) {
15095-
if (isTypeSubtypeOf(targetType, type)) {
15096-
return isTypeAny(type) ? targetType : getIntersectionType([type, targetType]); // Intersection to handle `string` being a subtype of `keyof T`
15097-
}
15098-
if (type.flags & TypeFlags.Instantiable) {
15099-
const constraint = getBaseConstraintOfType(type) || anyType;
15100-
if (isTypeSubtypeOf(targetType, constraint)) {
15101-
return getIntersectionType([type, targetType]);
15102-
}
15094+
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
15095+
return getUnionType([nonPrimitiveType, nullType]);
15096+
}
15097+
// We narrow a non-union type to an exact primitive type if the non-union type
15098+
// is a supertype of that primitive type. For example, type 'any' can be narrowed
15099+
// to one of the primitive types.
15100+
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
15101+
if (targetType) {
15102+
if (isTypeSubtypeOf(type, targetType)) {
15103+
return type;
15104+
}
15105+
if (isTypeSubtypeOf(targetType, type)) {
15106+
return targetType;
15107+
}
15108+
if (type.flags & TypeFlags.Instantiable) {
15109+
const constraint = getBaseConstraintOfType(type) || anyType;
15110+
if (isTypeSubtypeOf(targetType, constraint)) {
15111+
return getIntersectionType([type, targetType]);
1510315112
}
1510415113
}
1510515114
}

0 commit comments

Comments
 (0)