diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 025eeecc30624..b20e4dbbe254c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19224,6 +19224,21 @@ namespace ts { return result; } + function removeIntersectionMembersWithoutKeys(type: Type) { + if (!(type.flags & TypeFlags.Intersection)) { + return type; + } + return getIntersectionType(filter((type as IntersectionType).types, t => !isKeylessType(t))); + } + + function isKeylessType(type: Type) { + return !!(getIndexType(type).flags & TypeFlags.Never); + } + + function isConditionalFilteringKeylessTypes(type: Type) { + return !!(type.flags & TypeFlags.Conditional) && (type as ConditionalType).root.isDistributive && everyType((type as ConditionalType).extendsType, isKeylessType); + } + function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (intersectionState & IntersectionState.PropertyCheck) { return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); @@ -19366,9 +19381,34 @@ namespace ts { const targetType = (target as IndexType).type; // A keyof S is related to a keyof T if T is related to S. if (sourceFlags & TypeFlags.Index) { - if (result = isRelatedTo(targetType, (source as IndexType).type, RecursionFlags.Both, /*reportErrors*/ false)) { + let sourceType = (source as IndexType).type; + if (result = isRelatedTo(targetType, sourceType, RecursionFlags.Both, /*reportErrors*/ false)) { return result; } + // If the source is a filtering conditional that removes only keyless sources, eg, `NonNullable`, + // then it doesn't affect the `keyof` query, and we can unwrap the conditional and relate the unwrapped source and target. + // There may be multiple stacked conditionals, such as `T extends null ? never : T extends undefined ? never : T : T`, so + // we need to repeat the unwrapping process. + while (isConditionalFilteringKeylessTypes(sourceType)) { + const lastSource = sourceType; + sourceType = getDefaultConstraintOfConditionalType(sourceType as ConditionalType); + if (sourceType === lastSource) { + break; + } + if (result = isRelatedTo(targetType, sourceType, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + const simplifiedSource = sourceType; + // In addition, `keyof (T & U)` is equivalent to `keyof T | keyof U`, so if `keyof U` is always `never`, we can omit + // it from the relationship. This allows, eg, `keyof (T & object)` to be related to `keyof T`. + sourceType = removeIntersectionMembersWithoutKeys(sourceType); + if (sourceType === simplifiedSource) { + continue; + } + if (result = isRelatedTo(targetType, sourceType, RecursionFlags.Both, /*reportErrors*/ false)) { + return result; + } + } } if (isTupleType(targetType)) { // An index type can have a tuple type target when the tuple type contains variadic elements. @@ -21330,11 +21370,20 @@ namespace ts { return result; } + function getApparentIntersectionTypes(type: IntersectionType): Type[] { + const apparent = getApparentType(type); + if (apparent.flags & TypeFlags.Intersection) { + return (apparent as IntersectionType).types; + } + return [apparent]; + } + // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns // no flags for all other types (including non-falsy literal types). function getFalsyFlags(type: Type): TypeFlags { return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type as UnionType).types) : + type.flags & TypeFlags.Intersection ? reduceLeft(getApparentIntersectionTypes(type as IntersectionType), (memo, type) => memo | getFalsyFlags(type), 0 as TypeFacts) : type.flags & TypeFlags.StringLiteral ? (type as StringLiteralType).value === "" ? TypeFlags.StringLiteral : 0 : type.flags & TypeFlags.NumberLiteral ? (type as NumberLiteralType).value === 0 ? TypeFlags.NumberLiteral : 0 : type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type as BigIntLiteralType) ? TypeFlags.BigIntLiteral : 0 : @@ -25019,7 +25068,9 @@ namespace ts { case "function": return type.flags & TypeFlags.Any ? type : globalFunctionType; case "object": - return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type; + const isUnknownish = type.flags & TypeFlags.Unknown || + (type.flags & TypeFlags.InstantiableNonPrimitive && (getBaseConstraintOfType(type) || unknownType).flags & TypeFlags.Unknown); + return isUnknownish ? getUnionType([nonPrimitiveType, nullType]) : type; default: return typeofTypesByName.get(text); } diff --git a/tests/baselines/reference/keyofNonNullableAssignments.js b/tests/baselines/reference/keyofNonNullableAssignments.js new file mode 100644 index 0000000000000..6bb599cfcc6e8 --- /dev/null +++ b/tests/baselines/reference/keyofNonNullableAssignments.js @@ -0,0 +1,26 @@ +//// [keyofNonNullableAssignments.ts] +type MyNonNullable = T extends null ? never : T extends undefined ? never : T; + +function f(x: T) { + const a: keyof T = (null as any as keyof NonNullable); + const b: keyof T = (null as any as keyof NonNullable); + const c: keyof T = (null as any as keyof MyNonNullable); + const d: keyof T = (null as any as keyof MyNonNullable); + const e: keyof T = (null as any as keyof NonNullable); + const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>); + const g: keyof T = (null as any as keyof MyNonNullable); + const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>); +} + +//// [keyofNonNullableAssignments.js] +"use strict"; +function f(x) { + var a = null; + var b = null; + var c = null; + var d = null; + var e = null; + var f = null; + var g = null; + var h = null; +} diff --git a/tests/baselines/reference/keyofNonNullableAssignments.symbols b/tests/baselines/reference/keyofNonNullableAssignments.symbols new file mode 100644 index 0000000000000..5d111456e8a22 --- /dev/null +++ b/tests/baselines/reference/keyofNonNullableAssignments.symbols @@ -0,0 +1,62 @@ +=== tests/cases/compiler/keyofNonNullableAssignments.ts === +type MyNonNullable = T extends null ? never : T extends undefined ? never : T; +>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 0, 19)) + +function f(x: T) { +>f : Symbol(f, Decl(keyofNonNullableAssignments.ts, 0, 81)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>x : Symbol(x, Decl(keyofNonNullableAssignments.ts, 2, 14)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const a: keyof T = (null as any as keyof NonNullable); +>a : Symbol(a, Decl(keyofNonNullableAssignments.ts, 3, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const b: keyof T = (null as any as keyof NonNullable); +>b : Symbol(b, Decl(keyofNonNullableAssignments.ts, 4, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const c: keyof T = (null as any as keyof MyNonNullable); +>c : Symbol(c, Decl(keyofNonNullableAssignments.ts, 5, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const d: keyof T = (null as any as keyof MyNonNullable); +>d : Symbol(d, Decl(keyofNonNullableAssignments.ts, 6, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const e: keyof T = (null as any as keyof NonNullable); +>e : Symbol(e, Decl(keyofNonNullableAssignments.ts, 7, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>); +>f : Symbol(f, Decl(keyofNonNullableAssignments.ts, 8, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const g: keyof T = (null as any as keyof MyNonNullable); +>g : Symbol(g, Decl(keyofNonNullableAssignments.ts, 9, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) + + const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>); +>h : Symbol(h, Decl(keyofNonNullableAssignments.ts, 10, 9)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +>MyNonNullable : Symbol(MyNonNullable, Decl(keyofNonNullableAssignments.ts, 0, 0)) +>T : Symbol(T, Decl(keyofNonNullableAssignments.ts, 2, 11)) +} diff --git a/tests/baselines/reference/keyofNonNullableAssignments.types b/tests/baselines/reference/keyofNonNullableAssignments.types new file mode 100644 index 0000000000000..0626ddd7bdcad --- /dev/null +++ b/tests/baselines/reference/keyofNonNullableAssignments.types @@ -0,0 +1,65 @@ +=== tests/cases/compiler/keyofNonNullableAssignments.ts === +type MyNonNullable = T extends null ? never : T extends undefined ? never : T; +>MyNonNullable : MyNonNullable +>null : null + +function f(x: T) { +>f : (x: T) => void +>x : T + + const a: keyof T = (null as any as keyof NonNullable); +>a : keyof T +>(null as any as keyof NonNullable) : keyof NonNullable +>null as any as keyof NonNullable : keyof NonNullable +>null as any : any +>null : null + + const b: keyof T = (null as any as keyof NonNullable); +>b : keyof T +>(null as any as keyof NonNullable) : keyof NonNullable +>null as any as keyof NonNullable : keyof NonNullable +>null as any : any +>null : null + + const c: keyof T = (null as any as keyof MyNonNullable); +>c : keyof T +>(null as any as keyof MyNonNullable) : keyof MyNonNullable +>null as any as keyof MyNonNullable : keyof MyNonNullable +>null as any : any +>null : null + + const d: keyof T = (null as any as keyof MyNonNullable); +>d : keyof T +>(null as any as keyof MyNonNullable) : keyof MyNonNullable +>null as any as keyof MyNonNullable : keyof MyNonNullable +>null as any : any +>null : null + + const e: keyof T = (null as any as keyof NonNullable); +>e : keyof T +>(null as any as keyof NonNullable) : keyof NonNullable +>null as any as keyof NonNullable : keyof NonNullable +>null as any : any +>null : null + + const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>); +>f : keyof T +>(null as any as keyof NonNullable<(T | undefined) & object>) : keyof NonNullable +>null as any as keyof NonNullable<(T | undefined) & object> : keyof NonNullable +>null as any : any +>null : null + + const g: keyof T = (null as any as keyof MyNonNullable); +>g : keyof T +>(null as any as keyof MyNonNullable) : keyof MyNonNullable +>null as any as keyof MyNonNullable : keyof MyNonNullable +>null as any : any +>null : null + + const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>); +>h : keyof T +>(null as any as keyof MyNonNullable<(T | undefined) & object>) : keyof MyNonNullable +>null as any as keyof MyNonNullable<(T | undefined) & object> : keyof MyNonNullable +>null as any : any +>null : null +} diff --git a/tests/baselines/reference/mappedTypes4.types b/tests/baselines/reference/mappedTypes4.types index 85e714a4f0043..ee75303560e37 100644 --- a/tests/baselines/reference/mappedTypes4.types +++ b/tests/baselines/reference/mappedTypes4.types @@ -26,19 +26,19 @@ function boxify(obj: T): Boxified { >{} : {} for (let k in obj) { ->k : Extract ->obj : T +>k : Extract, string> +>obj : T & (object | null) result[k] = { value: obj[k] }; ->result[k] = { value: obj[k] } : { value: T[Extract]; } ->result[k] : Boxified[Extract] +>result[k] = { value: obj[k] } : { value: NonNullable[Extract, string>]; } +>result[k] : Boxified[Extract, string>] >result : Boxified ->k : Extract ->{ value: obj[k] } : { value: T[Extract]; } ->value : T[Extract] ->obj[k] : T[Extract] ->obj : T ->k : Extract +>k : Extract, string> +>{ value: obj[k] } : { value: NonNullable[Extract, string>]; } +>value : NonNullable[Extract, string>] +>obj[k] : NonNullable[Extract, string>] +>obj : NonNullable +>k : Extract, string> } return result; >result : Boxified diff --git a/tests/baselines/reference/unconstrainedTypeParameterNarrowing.js b/tests/baselines/reference/unconstrainedTypeParameterNarrowing.js new file mode 100644 index 0000000000000..9fbf368f42fdc --- /dev/null +++ b/tests/baselines/reference/unconstrainedTypeParameterNarrowing.js @@ -0,0 +1,55 @@ +//// [unconstrainedTypeParameterNarrowing.ts] +function f1(x: T) { + if (typeof x === "object" && x) { + g(x); + } +} + +function f2(x: T) { + if (typeof x === "object" && x) { + g(x); + } +} + +// #48468 but with an explicit constraint so as to not trigger the `{}` and unconstrained type parameter bug +function deepEquals(a: T, b: T) { + if (typeof a !== "object" || typeof b !== "object" || !a || !b) { + return false; + } + if (Array.isArray(a) || Array.isArray(b)) { + return false; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + return true; +} + +function g(x: object) {} + +//// [unconstrainedTypeParameterNarrowing.js] +"use strict"; +function f1(x) { + if (typeof x === "object" && x) { + g(x); + } +} +function f2(x) { + if (typeof x === "object" && x) { + g(x); + } +} +// #48468 but with an explicit constraint so as to not trigger the `{}` and unconstrained type parameter bug +function deepEquals(a, b) { + if (typeof a !== "object" || typeof b !== "object" || !a || !b) { + return false; + } + if (Array.isArray(a) || Array.isArray(b)) { + return false; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + return true; +} +function g(x) { } diff --git a/tests/baselines/reference/unconstrainedTypeParameterNarrowing.symbols b/tests/baselines/reference/unconstrainedTypeParameterNarrowing.symbols new file mode 100644 index 0000000000000..36d73b13f9f0a --- /dev/null +++ b/tests/baselines/reference/unconstrainedTypeParameterNarrowing.symbols @@ -0,0 +1,85 @@ +=== tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts === +function f1(x: T) { +>f1 : Symbol(f1, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 0)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 12)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 12)) + + if (typeof x === "object" && x) { +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15)) + + g(x); +>g : Symbol(g, Decl(unconstrainedTypeParameterNarrowing.ts, 24, 1)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 0, 15)) + } +} + +function f2(x: T) { +>f2 : Symbol(f2, Decl(unconstrainedTypeParameterNarrowing.ts, 4, 1)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 12)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 12)) + + if (typeof x === "object" && x) { +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31)) + + g(x); +>g : Symbol(g, Decl(unconstrainedTypeParameterNarrowing.ts, 24, 1)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 6, 31)) + } +} + +// #48468 but with an explicit constraint so as to not trigger the `{}` and unconstrained type parameter bug +function deepEquals(a: T, b: T) { +>deepEquals : Symbol(deepEquals, Decl(unconstrainedTypeParameterNarrowing.ts, 10, 1)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 20)) +>a : Symbol(a, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 39)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 20)) +>b : Symbol(b, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 44)) +>T : Symbol(T, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 20)) + + if (typeof a !== "object" || typeof b !== "object" || !a || !b) { +>a : Symbol(a, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 39)) +>b : Symbol(b, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 44)) +>a : Symbol(a, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 39)) +>b : Symbol(b, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 44)) + + return false; + } + if (Array.isArray(a) || Array.isArray(b)) { +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 39)) +>Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) +>b : Symbol(b, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 44)) + + return false; + } + if (Object.keys(a).length !== Object.keys(b).length) { +>Object.keys(a).length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) +>a : Symbol(a, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 39)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>Object.keys(b).length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>Object.keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>keys : Symbol(ObjectConstructor.keys, Decl(lib.es5.d.ts, --, --)) +>b : Symbol(b, Decl(unconstrainedTypeParameterNarrowing.ts, 13, 44)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) + + return false; + } + return true; +} + +function g(x: object) {} +>g : Symbol(g, Decl(unconstrainedTypeParameterNarrowing.ts, 24, 1)) +>x : Symbol(x, Decl(unconstrainedTypeParameterNarrowing.ts, 26, 11)) + diff --git a/tests/baselines/reference/unconstrainedTypeParameterNarrowing.types b/tests/baselines/reference/unconstrainedTypeParameterNarrowing.types new file mode 100644 index 0000000000000..e558c8f672298 --- /dev/null +++ b/tests/baselines/reference/unconstrainedTypeParameterNarrowing.types @@ -0,0 +1,109 @@ +=== tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts === +function f1(x: T) { +>f1 : (x: T) => void +>x : T + + if (typeof x === "object" && x) { +>typeof x === "object" && x : false | (T & (object | null)) +>typeof x === "object" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"object" : "object" +>x : T & (object | null) + + g(x); +>g(x) : void +>g : (x: object) => void +>x : T & object + } +} + +function f2(x: T) { +>f2 : (x: T) => void +>x : T + + if (typeof x === "object" && x) { +>typeof x === "object" && x : false | (T & (object | null)) +>typeof x === "object" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : T +>"object" : "object" +>x : T & (object | null) + + g(x); +>g(x) : void +>g : (x: object) => void +>x : T & object + } +} + +// #48468 but with an explicit constraint so as to not trigger the `{}` and unconstrained type parameter bug +function deepEquals(a: T, b: T) { +>deepEquals : (a: T, b: T) => boolean +>a : T +>b : T + + if (typeof a !== "object" || typeof b !== "object" || !a || !b) { +>typeof a !== "object" || typeof b !== "object" || !a || !b : boolean +>typeof a !== "object" || typeof b !== "object" || !a : boolean +>typeof a !== "object" || typeof b !== "object" : boolean +>typeof a !== "object" : boolean +>typeof a : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>a : T +>"object" : "object" +>typeof b !== "object" : boolean +>typeof b : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>b : T +>"object" : "object" +>!a : boolean +>a : T & (object | null) +>!b : boolean +>b : T & (object | null) + + return false; +>false : false + } + if (Array.isArray(a) || Array.isArray(b)) { +>Array.isArray(a) || Array.isArray(b) : boolean +>Array.isArray(a) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>a : T & object +>Array.isArray(b) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>b : T & object + + return false; +>false : false + } + if (Object.keys(a).length !== Object.keys(b).length) { +>Object.keys(a).length !== Object.keys(b).length : boolean +>Object.keys(a).length : number +>Object.keys(a) : string[] +>Object.keys : (o: object) => string[] +>Object : ObjectConstructor +>keys : (o: object) => string[] +>a : T & object +>length : number +>Object.keys(b).length : number +>Object.keys(b) : string[] +>Object.keys : (o: object) => string[] +>Object : ObjectConstructor +>keys : (o: object) => string[] +>b : T & object +>length : number + + return false; +>false : false + } + return true; +>true : true +} + +function g(x: object) {} +>g : (x: object) => void +>x : object + diff --git a/tests/cases/compiler/keyofNonNullableAssignments.ts b/tests/cases/compiler/keyofNonNullableAssignments.ts new file mode 100644 index 0000000000000..a3c3514f6942d --- /dev/null +++ b/tests/cases/compiler/keyofNonNullableAssignments.ts @@ -0,0 +1,13 @@ +// @strict: true +type MyNonNullable = T extends null ? never : T extends undefined ? never : T; + +function f(x: T) { + const a: keyof T = (null as any as keyof NonNullable); + const b: keyof T = (null as any as keyof NonNullable); + const c: keyof T = (null as any as keyof MyNonNullable); + const d: keyof T = (null as any as keyof MyNonNullable); + const e: keyof T = (null as any as keyof NonNullable); + const f: keyof T = (null as any as keyof NonNullable<(T | undefined) & object>); + const g: keyof T = (null as any as keyof MyNonNullable); + const h: keyof T = (null as any as keyof MyNonNullable<(T | undefined) & object>); +} \ No newline at end of file diff --git a/tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts b/tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts new file mode 100644 index 0000000000000..a08b2630a18c8 --- /dev/null +++ b/tests/cases/compiler/unconstrainedTypeParameterNarrowing.ts @@ -0,0 +1,28 @@ +// @strict: true +function f1(x: T) { + if (typeof x === "object" && x) { + g(x); + } +} + +function f2(x: T) { + if (typeof x === "object" && x) { + g(x); + } +} + +// #48468 but with an explicit constraint so as to not trigger the `{}` and unconstrained type parameter bug +function deepEquals(a: T, b: T) { + if (typeof a !== "object" || typeof b !== "object" || !a || !b) { + return false; + } + if (Array.isArray(a) || Array.isArray(b)) { + return false; + } + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + return true; +} + +function g(x: object) {} \ No newline at end of file