Skip to content

Commit 6adb9d1

Browse files
authored
Merge pull request #27157 from Microsoft/fixEmptyObjectFalsiness
Fix empty object falsiness
2 parents e40ce24 + c0eb742 commit 6adb9d1

15 files changed

+288
-36
lines changed

Diff for: src/compiler/checker.ts

+31-22
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({
@@ -11836,8 +11838,12 @@ namespace ts {
1183611838
const simplified = getSimplifiedType((<IndexType>target).type);
1183711839
const constraint = simplified !== (<IndexType>target).type ? simplified : getConstraintOfType((<IndexType>target).type);
1183811840
if (constraint) {
11839-
if (result = isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors)) {
11840-
return result;
11841+
// We require Ternary.True here such that circular constraints don't cause
11842+
// false positives. For example, given 'T extends { [K in keyof T]: string }',
11843+
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
11844+
// related to other types.
11845+
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
11846+
return Ternary.True;
1184111847
}
1184211848
}
1184311849
}
@@ -14241,9 +14247,11 @@ namespace ts {
1424114247
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
1424214248
}
1424314249
if (flags & TypeFlags.Object) {
14244-
return isFunctionObjectType(<ObjectType>type) ?
14245-
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
14246-
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
14250+
return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(<ObjectType>type) ?
14251+
strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts :
14252+
isFunctionObjectType(<ObjectType>type) ?
14253+
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
14254+
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
1424714255
}
1424814256
if (flags & (TypeFlags.Void | TypeFlags.Undefined)) {
1424914257
return TypeFacts.UndefinedFacts;
@@ -15163,23 +15171,24 @@ namespace ts {
1516315171
return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
1516415172

1516515173
function narrowTypeForTypeof(type: Type) {
15166-
if (assumeTrue && !(type.flags & TypeFlags.Union)) {
15167-
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
15168-
return getUnionType([nonPrimitiveType, nullType]);
15169-
}
15170-
// We narrow a non-union type to an exact primitive type if the non-union type
15171-
// is a supertype of that primitive type. For example, type 'any' can be narrowed
15172-
// to one of the primitive types.
15173-
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
15174-
if (targetType) {
15175-
if (isTypeSubtypeOf(targetType, type)) {
15176-
return isTypeAny(type) ? targetType : getIntersectionType([type, targetType]); // Intersection to handle `string` being a subtype of `keyof T`
15177-
}
15178-
if (type.flags & TypeFlags.Instantiable) {
15179-
const constraint = getBaseConstraintOfType(type) || anyType;
15180-
if (isTypeSubtypeOf(targetType, constraint)) {
15181-
return getIntersectionType([type, targetType]);
15182-
}
15174+
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
15175+
return getUnionType([nonPrimitiveType, nullType]);
15176+
}
15177+
// We narrow a non-union type to an exact primitive type if the non-union type
15178+
// is a supertype of that primitive type. For example, type 'any' can be narrowed
15179+
// to one of the primitive types.
15180+
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
15181+
if (targetType) {
15182+
if (isTypeSubtypeOf(type, targetType)) {
15183+
return type;
15184+
}
15185+
if (isTypeSubtypeOf(targetType, type)) {
15186+
return targetType;
15187+
}
15188+
if (type.flags & TypeFlags.Instantiable) {
15189+
const constraint = getBaseConstraintOfType(type) || anyType;
15190+
if (isTypeSubtypeOf(targetType, constraint)) {
15191+
return getIntersectionType([type, targetType]);
1518315192
}
1518415193
}
1518515194
}

Diff for: src/services/importTracker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ namespace ts.FindAllReferences {
385385
}
386386

387387
/** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */
388-
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T): T | undefined {
388+
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) {
389389
return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217
390390
action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action)));
391391
}

Diff for: tests/baselines/reference/controlFlowTruthiness.js

+51-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,33 @@ function f6() {
6767
y; // string | undefined
6868
}
6969
}
70-
70+
71+
function f7(x: {}) {
72+
if (x) {
73+
x; // {}
74+
}
75+
else {
76+
x; // {}
77+
}
78+
}
79+
80+
function f8<T>(x: T) {
81+
if (x) {
82+
x; // {}
83+
}
84+
else {
85+
x; // {}
86+
}
87+
}
88+
89+
function f9<T extends object>(x: T) {
90+
if (x) {
91+
x; // {}
92+
}
93+
else {
94+
x; // never
95+
}
96+
}
7197

7298
//// [controlFlowTruthiness.js]
7399
function f1() {
@@ -131,3 +157,27 @@ function f6() {
131157
y; // string | undefined
132158
}
133159
}
160+
function f7(x) {
161+
if (x) {
162+
x; // {}
163+
}
164+
else {
165+
x; // {}
166+
}
167+
}
168+
function f8(x) {
169+
if (x) {
170+
x; // {}
171+
}
172+
else {
173+
x; // {}
174+
}
175+
}
176+
function f9(x) {
177+
if (x) {
178+
x; // {}
179+
}
180+
else {
181+
x; // never
182+
}
183+
}

Diff for: tests/baselines/reference/controlFlowTruthiness.symbols

+51
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,54 @@ function f6() {
140140
}
141141
}
142142

143+
function f7(x: {}) {
144+
>f7 : Symbol(f7, Decl(controlFlowTruthiness.ts, 67, 1))
145+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))
146+
147+
if (x) {
148+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))
149+
150+
x; // {}
151+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))
152+
}
153+
else {
154+
x; // {}
155+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))
156+
}
157+
}
158+
159+
function f8<T>(x: T) {
160+
>f8 : Symbol(f8, Decl(controlFlowTruthiness.ts, 76, 1))
161+
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 78, 12))
162+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
163+
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 78, 12))
164+
165+
if (x) {
166+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
167+
168+
x; // {}
169+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
170+
}
171+
else {
172+
x; // {}
173+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
174+
}
175+
}
176+
177+
function f9<T extends object>(x: T) {
178+
>f9 : Symbol(f9, Decl(controlFlowTruthiness.ts, 85, 1))
179+
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 87, 12))
180+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
181+
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 87, 12))
182+
183+
if (x) {
184+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
185+
186+
x; // {}
187+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
188+
}
189+
else {
190+
x; // never
191+
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
192+
}
193+
}

Diff for: tests/baselines/reference/controlFlowTruthiness.types

+47
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,50 @@ function f6() {
157157
}
158158
}
159159

160+
function f7(x: {}) {
161+
>f7 : (x: {}) => void
162+
>x : {}
163+
164+
if (x) {
165+
>x : {}
166+
167+
x; // {}
168+
>x : {}
169+
}
170+
else {
171+
x; // {}
172+
>x : {}
173+
}
174+
}
175+
176+
function f8<T>(x: T) {
177+
>f8 : <T>(x: T) => void
178+
>x : T
179+
180+
if (x) {
181+
>x : T
182+
183+
x; // {}
184+
>x : T
185+
}
186+
else {
187+
x; // {}
188+
>x : T
189+
}
190+
}
191+
192+
function f9<T extends object>(x: T) {
193+
>f9 : <T extends object>(x: T) => void
194+
>x : T
195+
196+
if (x) {
197+
>x : T
198+
199+
x; // {}
200+
>x : T
201+
}
202+
else {
203+
x; // never
204+
>x : never
205+
}
206+
}

Diff for: tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt

+13-1
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(114,5): error
6161
Type 'string' is not assignable to type 'J'.
6262
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(117,5): error TS2322: Type 'T[K]' is not assignable to type 'U[J]'.
6363
Type 'T' is not assignable to type 'U'.
64+
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(122,5): error TS2322: Type '42' is not assignable to type 'keyof T'.
65+
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(123,5): error TS2322: Type '"hello"' is not assignable to type 'keyof T'.
6466

6567

66-
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (36 errors) ====
68+
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (38 errors) ====
6769
class Shape {
6870
name: string;
6971
width: number;
@@ -281,4 +283,14 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(117,5): error
281283
!!! error TS2322: Type 'T[K]' is not assignable to type 'U[J]'.
282284
!!! error TS2322: Type 'T' is not assignable to type 'U'.
283285
}
286+
287+
// The constraint of 'keyof T' is 'keyof T'
288+
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
289+
k = 42; // error
290+
~
291+
!!! error TS2322: Type '42' is not assignable to type 'keyof T'.
292+
k = "hello"; // error
293+
~
294+
!!! error TS2322: Type '"hello"' is not assignable to type 'keyof T'.
295+
}
284296

Diff for: tests/baselines/reference/keyofAndIndexedAccessErrors.js

+11
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ function f3<T, K extends Extract<keyof T, string>, U extends T, J extends K>(
117117
tk = uj;
118118
uj = tk; // error
119119
}
120+
121+
// The constraint of 'keyof T' is 'keyof T'
122+
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
123+
k = 42; // error
124+
k = "hello"; // error
125+
}
120126

121127

122128
//// [keyofAndIndexedAccessErrors.js]
@@ -178,3 +184,8 @@ function f3(t, k, tk, u, j, uk, tj, uj) {
178184
tk = uj;
179185
uj = tk; // error
180186
}
187+
// The constraint of 'keyof T' is 'keyof T'
188+
function f4(k) {
189+
k = 42; // error
190+
k = "hello"; // error
191+
}

Diff for: tests/baselines/reference/keyofAndIndexedAccessErrors.symbols

+16
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,19 @@ function f3<T, K extends Extract<keyof T, string>, U extends T, J extends K>(
412412
>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 99, 15))
413413
}
414414

415+
// The constraint of 'keyof T' is 'keyof T'
416+
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
417+
>f4 : Symbol(f4, Decl(keyofAndIndexedAccessErrors.ts, 117, 1))
418+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 120, 12))
419+
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 120, 25))
420+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 120, 12))
421+
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 120, 50))
422+
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 120, 12))
423+
424+
k = 42; // error
425+
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 120, 50))
426+
427+
k = "hello"; // error
428+
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 120, 50))
429+
}
430+

Diff for: tests/baselines/reference/keyofAndIndexedAccessErrors.types

+16
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,19 @@ function f3<T, K extends Extract<keyof T, string>, U extends T, J extends K>(
396396
>tk : T[K]
397397
}
398398

399+
// The constraint of 'keyof T' is 'keyof T'
400+
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
401+
>f4 : <T extends { [K in keyof T]: string; }>(k: keyof T) => void
402+
>k : keyof T
403+
404+
k = 42; // error
405+
>k = 42 : 42
406+
>k : keyof T
407+
>42 : 42
408+
409+
k = "hello"; // error
410+
>k = "hello" : "hello"
411+
>k : keyof T
412+
>"hello" : "hello"
413+
}
414+

Diff for: tests/baselines/reference/mappedTypes4.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function boxify<T>(obj: T): Boxified<T> {
4545
}
4646
return <any>obj;
4747
><any>obj : any
48-
>obj : never
48+
>obj : T
4949
}
5050

5151
type A = { a: string };

Diff for: tests/baselines/reference/recursiveTypeRelations.errors.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
tests/cases/compiler/recursiveTypeRelations.ts(8,5): error TS2391: Function implementation is missing or not immediately following the declaration.
22
tests/cases/compiler/recursiveTypeRelations.ts(27,38): error TS2304: Cannot find name 'ClassNameObject'.
3+
tests/cases/compiler/recursiveTypeRelations.ts(27,55): error TS2345: Argument of type '(obj: any, key: keyof S) => any' is not assignable to parameter of type '(previousValue: any, currentValue: string, currentIndex: number, array: string[]) => any'.
4+
Types of parameters 'key' and 'currentValue' are incompatible.
5+
Type 'string' is not assignable to type 'keyof S'.
36
tests/cases/compiler/recursiveTypeRelations.ts(27,61): error TS2304: Cannot find name 'ClassNameObject'.
47

58

6-
==== tests/cases/compiler/recursiveTypeRelations.ts (3 errors) ====
9+
==== tests/cases/compiler/recursiveTypeRelations.ts (4 errors) ====
710
// Repro from #14896
811

912
type Attributes<Keys extends keyof any> = {
@@ -35,6 +38,10 @@ tests/cases/compiler/recursiveTypeRelations.ts(27,61): error TS2304: Cannot find
3538
return Object.keys(arg).reduce<ClassNameObject>((obj: ClassNameObject, key: keyof S) => {
3639
~~~~~~~~~~~~~~~
3740
!!! error TS2304: Cannot find name 'ClassNameObject'.
41+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
42+
!!! error TS2345: Argument of type '(obj: any, key: keyof S) => any' is not assignable to parameter of type '(previousValue: any, currentValue: string, currentIndex: number, array: string[]) => any'.
43+
!!! error TS2345: Types of parameters 'key' and 'currentValue' are incompatible.
44+
!!! error TS2345: Type 'string' is not assignable to type 'keyof S'.
3845
~~~~~~~~~~~~~~~
3946
!!! error TS2304: Cannot find name 'ClassNameObject'.
4047
const exportedClassName = styles[key];

0 commit comments

Comments
 (0)