Merge pull request #27157 from Microsoft/fixEmptyObjectFalsiness

Fix empty object falsiness
This commit is contained in:
Anders Hejlsberg
2018-09-18 09:26:24 -07:00
committed by GitHub
15 changed files with 287 additions and 35 deletions

View File

@@ -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({
@@ -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;
}
}
}
@@ -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;
@@ -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]);
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;
}
// 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 (isTypeSubtypeOf(targetType, type)) {
return targetType;
}
if (type.flags & TypeFlags.Instantiable) {
const constraint = getBaseConstraintOfType(type) || anyType;
if (isTypeSubtypeOf(targetType, constraint)) {
return getIntersectionType([type, targetType]);
}
}
}

View File

@@ -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)));
}

View File

@@ -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() {
@@ -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
}
}

View File

@@ -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))
}
}

View File

@@ -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
}
}

View File

@@ -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;
@@ -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'.
}

View File

@@ -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]
@@ -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
}

View File

@@ -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))
}

View File

@@ -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"
}

View File

@@ -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 };

View File

@@ -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> = {
@@ -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];

View File

@@ -12,7 +12,7 @@ function stringify1(anything: { toString(): string } | undefined): string {
>"string" : "string"
>anything.toUpperCase() : string
>anything.toUpperCase : () => string
>anything : { toString(): string; } & string
>anything : string
>toUpperCase : () => string
>"" : ""
}
@@ -29,7 +29,7 @@ function stringify2(anything: {} | undefined): string {
>"string" : "string"
>anything.toUpperCase() : string
>anything.toUpperCase : () => string
>anything : string & {}
>anything : string
>toUpperCase : () => string
>"" : ""
}
@@ -64,7 +64,7 @@ function stringify4(anything: { toString?(): string } | undefined): string {
>"string" : "string"
>anything.toUpperCase() : string
>anything.toUpperCase : () => string
>anything : {} & string
>anything : string
>toUpperCase : () => string
>"" : ""
}

View File

@@ -14,7 +14,7 @@ if (typeof a === "number") {
let c: number = a;
>c : number
>a : number & {}
>a : number
}
if (typeof a === "string") {
>typeof a === "string" : boolean
@@ -24,7 +24,7 @@ if (typeof a === "string") {
let c: string = a;
>c : string
>a : string & {}
>a : string
}
if (typeof a === "boolean") {
>typeof a === "boolean" : boolean
@@ -34,7 +34,7 @@ if (typeof a === "boolean") {
let c: boolean = a;
>c : boolean
>a : (false & {}) | (true & {})
>a : boolean
}
if (typeof b === "number") {
@@ -45,7 +45,7 @@ if (typeof b === "number") {
let c: number = b;
>c : number
>b : { toString(): string; } & number
>b : number
}
if (typeof b === "string") {
>typeof b === "string" : boolean
@@ -55,7 +55,7 @@ if (typeof b === "string") {
let c: string = b;
>c : string
>b : { toString(): string; } & string
>b : string
}
if (typeof b === "boolean") {
>typeof b === "boolean" : boolean
@@ -65,6 +65,6 @@ if (typeof b === "boolean") {
let c: boolean = b;
>c : boolean
>b : ({ toString(): string; } & false) | ({ toString(): string; } & true)
>b : boolean
}

View File

@@ -68,3 +68,30 @@ 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
}
}

View File

@@ -116,3 +116,9 @@ 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
}