Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix empty object falsiness #27157

Merged
merged 6 commits into from
Sep 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading