Skip to content

Commit

Permalink
Merge pull request #27157 from Microsoft/fixEmptyObjectFalsiness
Browse files Browse the repository at this point in the history
Fix empty object falsiness
  • Loading branch information
ahejlsberg committed Sep 18, 2018
2 parents e40ce24 + c0eb742 commit 6adb9d1
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 36 deletions.
53 changes: 31 additions & 22 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@ namespace ts {
FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy,
UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy,
NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy,
EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull),
EmptyObjectFacts = All,
}

const typeofEQFacts = createMapFromTemplate({
Expand Down Expand Up @@ -11836,8 +11838,12 @@ namespace ts {
const simplified = getSimplifiedType((<IndexType>target).type);
const constraint = simplified !== (<IndexType>target).type ? simplified : getConstraintOfType((<IndexType>target).type);
if (constraint) {
if (result = isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors)) {
return result;
// We require Ternary.True here such that circular constraints don't cause
// false positives. For example, given 'T extends { [K in keyof T]: string }',
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
// related to other types.
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
return Ternary.True;
}
}
}
Expand Down Expand Up @@ -14241,9 +14247,11 @@ namespace ts {
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
}
if (flags & TypeFlags.Object) {
return isFunctionObjectType(<ObjectType>type) ?
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(<ObjectType>type) ?
strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts :
isFunctionObjectType(<ObjectType>type) ?
strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts :
strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts;
}
if (flags & (TypeFlags.Void | TypeFlags.Undefined)) {
return TypeFacts.UndefinedFacts;
Expand Down Expand Up @@ -15163,23 +15171,24 @@ namespace ts {
return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);

function narrowTypeForTypeof(type: Type) {
if (assumeTrue && !(type.flags & TypeFlags.Union)) {
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
return getUnionType([nonPrimitiveType, nullType]);
}
// We narrow a non-union type to an exact primitive type if the non-union type
// is a supertype of that primitive type. For example, type 'any' can be narrowed
// to one of the primitive types.
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
if (targetType) {
if (isTypeSubtypeOf(targetType, type)) {
return isTypeAny(type) ? targetType : getIntersectionType([type, targetType]); // Intersection to handle `string` being a subtype of `keyof T`
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getBaseConstraintOfType(type) || anyType;
if (isTypeSubtypeOf(targetType, constraint)) {
return getIntersectionType([type, targetType]);
}
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
return getUnionType([nonPrimitiveType, nullType]);
}
// We narrow a non-union type to an exact primitive type if the non-union type
// is a supertype of that primitive type. For example, type 'any' can be narrowed
// to one of the primitive types.
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
if (targetType) {
if (isTypeSubtypeOf(type, targetType)) {
return type;
}
if (isTypeSubtypeOf(targetType, type)) {
return targetType;
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getBaseConstraintOfType(type) || anyType;
if (isTypeSubtypeOf(targetType, constraint)) {
return getIntersectionType([type, targetType]);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/importTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ namespace ts.FindAllReferences {
}

/** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T): T | undefined {
function forEachPossibleImportOrExportStatement<T>(sourceFileLike: SourceFileLike, action: (statement: Statement) => T) {
return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body!.statements, statement => // TODO: GH#18217
action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action)));
}
Expand Down
52 changes: 51 additions & 1 deletion tests/baselines/reference/controlFlowTruthiness.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,33 @@ function f6() {
y; // string | undefined
}
}


function f7(x: {}) {
if (x) {
x; // {}
}
else {
x; // {}
}
}

function f8<T>(x: T) {
if (x) {
x; // {}
}
else {
x; // {}
}
}

function f9<T extends object>(x: T) {
if (x) {
x; // {}
}
else {
x; // never
}
}

//// [controlFlowTruthiness.js]
function f1() {
Expand Down Expand Up @@ -131,3 +157,27 @@ function f6() {
y; // string | undefined
}
}
function f7(x) {
if (x) {
x; // {}
}
else {
x; // {}
}
}
function f8(x) {
if (x) {
x; // {}
}
else {
x; // {}
}
}
function f9(x) {
if (x) {
x; // {}
}
else {
x; // never
}
}
51 changes: 51 additions & 0 deletions tests/baselines/reference/controlFlowTruthiness.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,54 @@ function f6() {
}
}

function f7(x: {}) {
>f7 : Symbol(f7, Decl(controlFlowTruthiness.ts, 67, 1))
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))

if (x) {
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))

x; // {}
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))
}
else {
x; // {}
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 69, 12))
}
}

function f8<T>(x: T) {
>f8 : Symbol(f8, Decl(controlFlowTruthiness.ts, 76, 1))
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 78, 12))
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 78, 12))

if (x) {
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))

x; // {}
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
}
else {
x; // {}
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 78, 15))
}
}

function f9<T extends object>(x: T) {
>f9 : Symbol(f9, Decl(controlFlowTruthiness.ts, 85, 1))
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 87, 12))
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
>T : Symbol(T, Decl(controlFlowTruthiness.ts, 87, 12))

if (x) {
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))

x; // {}
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
}
else {
x; // never
>x : Symbol(x, Decl(controlFlowTruthiness.ts, 87, 30))
}
}
47 changes: 47 additions & 0 deletions tests/baselines/reference/controlFlowTruthiness.types
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,50 @@ function f6() {
}
}

function f7(x: {}) {
>f7 : (x: {}) => void
>x : {}

if (x) {
>x : {}

x; // {}
>x : {}
}
else {
x; // {}
>x : {}
}
}

function f8<T>(x: T) {
>f8 : <T>(x: T) => void
>x : T

if (x) {
>x : T

x; // {}
>x : T
}
else {
x; // {}
>x : T
}
}

function f9<T extends object>(x: T) {
>f9 : <T extends object>(x: T) => void
>x : T

if (x) {
>x : T

x; // {}
>x : T
}
else {
x; // never
>x : never
}
}
14 changes: 13 additions & 1 deletion tests/baselines/reference/keyofAndIndexedAccessErrors.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(114,5): error
Type 'string' is not assignable to type 'J'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(117,5): error TS2322: Type 'T[K]' is not assignable to type 'U[J]'.
Type 'T' is not assignable to type 'U'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(122,5): error TS2322: Type '42' is not assignable to type 'keyof T'.
tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(123,5): error TS2322: Type '"hello"' is not assignable to type 'keyof T'.


==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (36 errors) ====
==== tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts (38 errors) ====
class Shape {
name: string;
width: number;
Expand Down Expand Up @@ -281,4 +283,14 @@ tests/cases/conformance/types/keyof/keyofAndIndexedAccessErrors.ts(117,5): error
!!! error TS2322: Type 'T[K]' is not assignable to type 'U[J]'.
!!! error TS2322: Type 'T' is not assignable to type 'U'.
}

// The constraint of 'keyof T' is 'keyof T'
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
k = 42; // error
~
!!! error TS2322: Type '42' is not assignable to type 'keyof T'.
k = "hello"; // error
~
!!! error TS2322: Type '"hello"' is not assignable to type 'keyof T'.
}

11 changes: 11 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccessErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ function f3<T, K extends Extract<keyof T, string>, U extends T, J extends K>(
tk = uj;
uj = tk; // error
}

// The constraint of 'keyof T' is 'keyof T'
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
k = 42; // error
k = "hello"; // error
}


//// [keyofAndIndexedAccessErrors.js]
Expand Down Expand Up @@ -178,3 +184,8 @@ function f3(t, k, tk, u, j, uk, tj, uj) {
tk = uj;
uj = tk; // error
}
// The constraint of 'keyof T' is 'keyof T'
function f4(k) {
k = 42; // error
k = "hello"; // error
}
16 changes: 16 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccessErrors.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,19 @@ function f3<T, K extends Extract<keyof T, string>, U extends T, J extends K>(
>tk : Symbol(tk, Decl(keyofAndIndexedAccessErrors.ts, 99, 15))
}

// The constraint of 'keyof T' is 'keyof T'
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
>f4 : Symbol(f4, Decl(keyofAndIndexedAccessErrors.ts, 117, 1))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 120, 12))
>K : Symbol(K, Decl(keyofAndIndexedAccessErrors.ts, 120, 25))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 120, 12))
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 120, 50))
>T : Symbol(T, Decl(keyofAndIndexedAccessErrors.ts, 120, 12))

k = 42; // error
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 120, 50))

k = "hello"; // error
>k : Symbol(k, Decl(keyofAndIndexedAccessErrors.ts, 120, 50))
}

16 changes: 16 additions & 0 deletions tests/baselines/reference/keyofAndIndexedAccessErrors.types
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,19 @@ function f3<T, K extends Extract<keyof T, string>, U extends T, J extends K>(
>tk : T[K]
}

// The constraint of 'keyof T' is 'keyof T'
function f4<T extends { [K in keyof T]: string }>(k: keyof T) {
>f4 : <T extends { [K in keyof T]: string; }>(k: keyof T) => void
>k : keyof T

k = 42; // error
>k = 42 : 42
>k : keyof T
>42 : 42

k = "hello"; // error
>k = "hello" : "hello"
>k : keyof T
>"hello" : "hello"
}

2 changes: 1 addition & 1 deletion tests/baselines/reference/mappedTypes4.types
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function boxify<T>(obj: T): Boxified<T> {
}
return <any>obj;
><any>obj : any
>obj : never
>obj : T
}

type A = { a: string };
Expand Down
9 changes: 8 additions & 1 deletion tests/baselines/reference/recursiveTypeRelations.errors.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
tests/cases/compiler/recursiveTypeRelations.ts(8,5): error TS2391: Function implementation is missing or not immediately following the declaration.
tests/cases/compiler/recursiveTypeRelations.ts(27,38): error TS2304: Cannot find name 'ClassNameObject'.
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'.
Types of parameters 'key' and 'currentValue' are incompatible.
Type 'string' is not assignable to type 'keyof S'.
tests/cases/compiler/recursiveTypeRelations.ts(27,61): error TS2304: Cannot find name 'ClassNameObject'.


==== tests/cases/compiler/recursiveTypeRelations.ts (3 errors) ====
==== tests/cases/compiler/recursiveTypeRelations.ts (4 errors) ====
// Repro from #14896

type Attributes<Keys extends keyof any> = {
Expand Down Expand Up @@ -35,6 +38,10 @@ tests/cases/compiler/recursiveTypeRelations.ts(27,61): error TS2304: Cannot find
return Object.keys(arg).reduce<ClassNameObject>((obj: ClassNameObject, key: keyof S) => {
~~~~~~~~~~~~~~~
!!! error TS2304: Cannot find name 'ClassNameObject'.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!! 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'.
!!! error TS2345: Types of parameters 'key' and 'currentValue' are incompatible.
!!! error TS2345: Type 'string' is not assignable to type 'keyof S'.
~~~~~~~~~~~~~~~
!!! error TS2304: Cannot find name 'ClassNameObject'.
const exportedClassName = styles[key];
Expand Down

0 comments on commit 6adb9d1

Please sign in to comment.