diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b975b9848853d..a21693a55b4d9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22429,7 +22429,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const targetHasStringIndex = some(indexInfos, info => info.keyType === stringType); let result = Ternary.True; for (const targetInfo of indexInfos) { - const related = !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : + const related = relation !== strictSubtypeRelation && !sourceIsPrimitive && targetHasStringIndex && targetInfo.type.flags & TypeFlags.Any ? Ternary.True : isGenericMappedType(source) && targetHasStringIndex ? isRelatedTo(getTemplateTypeFromMappedType(source), targetInfo.type, RecursionFlags.Both, reportErrors) : typeRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); if (!related) { diff --git a/tests/baselines/reference/narrowingMutualSubtypes.errors.txt b/tests/baselines/reference/narrowingMutualSubtypes.errors.txt new file mode 100644 index 0000000000000..8f65deade7c12 --- /dev/null +++ b/tests/baselines/reference/narrowingMutualSubtypes.errors.txt @@ -0,0 +1,155 @@ +tests/cases/compiler/narrowingMutualSubtypes.ts(117,17): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record'. + No index signature with a parameter of type 'string' was found on type 'any[] | Record'. +tests/cases/compiler/narrowingMutualSubtypes.ts(118,29): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record'. + No index signature with a parameter of type 'string' was found on type 'any[] | Record'. + + +==== tests/cases/compiler/narrowingMutualSubtypes.ts (2 errors) ==== + // Check that `any` is a strict supertype of `unknown` + + declare const ru1: { [x: string]: unknown }; + declare const ra1: { [x: string]: any }; + + const a1a = [ru1, ra1]; // { [x: string]: any }[] + const a1b = [ra1, ru1]; // { [x: string]: any }[] + + declare const ra2: { [x: string]: any }; + declare const ru2: { [x: string]: unknown }; + + const a2a = [ru2, ra2]; // { [x: string]: any }[] + const a2b = [ra2, ru2]; // { [x: string]: any }[] + + // Check that `{}` is strict supertype of any non-empty object + + const c3 = {}; + declare const r3: { [x: string]: unknown } + + const a3a = [c3, r3]; // {}[] + const a3b = [r3, c3]; // {}[] + + declare const r4: { [x: string]: unknown } + const c4 = {}; + + const a4a = [c4, r4]; // {}[] + const a4b = [r4, c4]; // {}[] + + // Check that {} is a strict supertype of Record + + declare function isObject1(value: unknown): value is Record; + + function gg1(x: {}) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} + } + + declare function isObject2(value: unknown): value is {}; + + function gg2(x: Record) { + if (isObject2(x)) { + x; // Record + } + else { + x; // never + } + x; // Record + } + + // Check that {} is a strict supertype of Record + + declare function isObject3(value: unknown): value is Record; + + function gg3(x: {}) { + if (isObject3(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} + } + + declare function isObject4(value: unknown): value is {}; + + function gg4(x: Record) { + if (isObject4(x)) { + x; // Record + } + else { + x; // never + } + x; // Record + } + + // Repro from #50916 + + type Identity = {[K in keyof T]: T[K]}; + + type Self = T extends unknown ? Identity : never; + + function is(value: T): value is Self { + return true; + } + + type Union = {a: number} | {b: number} | {c: number}; + + function example(x: Union) { + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + x; // Union + } + + function checksArrayOrObject1(obj: Record | Record[]) { + // "accidentally" guards the first branch on the length + if (Array.isArray(obj) && obj.length) { + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { + if (obj[key] !== undefined) { + ~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type 'any[] | Record'. + console.log(obj[key]) + ~~~~~~~~ +!!! error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'any[] | Record'. +!!! error TS7053: No index signature with a parameter of type 'string' was found on type 'any[] | Record'. + } + } + } + } + + function checksArrayOrObject2(obj: Record | Record[]) { + if (Array.isArray(obj)) { + // obj should only be an array type here + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/narrowingMutualSubtypes.js b/tests/baselines/reference/narrowingMutualSubtypes.js index 0d5f36d2f9551..69faca4689f3d 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.js +++ b/tests/baselines/reference/narrowingMutualSubtypes.js @@ -27,11 +27,11 @@ const c4 = {}; const a4a = [c4, r4]; // {}[] const a4b = [r4, c4]; // {}[] -// Check that narrowing preserves original type in false branch for non-identical mutual subtypes +// Check that {} is a strict supertype of Record declare function isObject1(value: unknown): value is Record; -function gg(x: {}) { +function gg1(x: {}) { if (isObject1(x)) { x; // Record } @@ -45,14 +45,40 @@ declare function isObject2(value: unknown): value is {}; function gg2(x: Record) { if (isObject2(x)) { - x; // {} + x; // Record } else { - x; // Record + x; // never } x; // Record } +// Check that {} is a strict supertype of Record + +declare function isObject3(value: unknown): value is Record; + +function gg3(x: {}) { + if (isObject3(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} + +declare function isObject4(value: unknown): value is {}; + +function gg4(x: Record) { + if (isObject4(x)) { + x; // Record + } + else { + x; // never + } + x; // Record +} + // Repro from #50916 type Identity = {[K in keyof T]: T[K]}; @@ -76,6 +102,44 @@ function example(x: Union) { if (is(x)) {} x; // Union } + +function checksArrayOrObject1(obj: Record | Record[]) { + // "accidentally" guards the first branch on the length + if (Array.isArray(obj) && obj.length) { + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } +} + +function checksArrayOrObject2(obj: Record | Record[]) { + if (Array.isArray(obj)) { + // obj should only be an array type here + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } +} //// [narrowingMutualSubtypes.js] @@ -92,7 +156,7 @@ var a3b = [r3, c3]; // {}[] var c4 = {}; var a4a = [c4, r4]; // {}[] var a4b = [r4, c4]; // {}[] -function gg(x) { +function gg1(x) { if (isObject1(x)) { x; // Record } @@ -103,13 +167,31 @@ function gg(x) { } function gg2(x) { if (isObject2(x)) { - x; // {} + x; // Record } else { - x; // Record + x; // never } x; // Record } +function gg3(x) { + if (isObject3(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} +function gg4(x) { + if (isObject4(x)) { + x; // Record + } + else { + x; // never + } + x; // Record +} function is(value) { return true; } @@ -124,3 +206,39 @@ function example(x) { if (is(x)) { } x; // Union } +function checksArrayOrObject1(obj) { + // "accidentally" guards the first branch on the length + if (Array.isArray(obj) && obj.length) { + for (var key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]); + } + } + } + else { + // 'obj' should probably not include an array type here. + for (var key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]); + } + } + } +} +function checksArrayOrObject2(obj) { + if (Array.isArray(obj)) { + // obj should only be an array type here + for (var key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]); + } + } + } + else { + // 'obj' should probably not include an array type here. + for (var key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]); + } + } + } +} diff --git a/tests/baselines/reference/narrowingMutualSubtypes.symbols b/tests/baselines/reference/narrowingMutualSubtypes.symbols index 9229c0b6e2946..a08534ac2a31c 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.symbols +++ b/tests/baselines/reference/narrowingMutualSubtypes.symbols @@ -73,7 +73,7 @@ const a4b = [r4, c4]; // {}[] >r4 : Symbol(r4, Decl(narrowingMutualSubtypes.ts, 22, 13)) >c4 : Symbol(c4, Decl(narrowingMutualSubtypes.ts, 23, 5)) -// Check that narrowing preserves original type in false branch for non-identical mutual subtypes +// Check that {} is a strict supertype of Record declare function isObject1(value: unknown): value is Record; >isObject1 : Symbol(isObject1, Decl(narrowingMutualSubtypes.ts, 26, 21)) @@ -81,23 +81,23 @@ declare function isObject1(value: unknown): value is Record; >value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 30, 27)) >Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) -function gg(x: {}) { ->gg : Symbol(gg, Decl(narrowingMutualSubtypes.ts, 30, 77)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) +function gg1(x: {}) { +>gg1 : Symbol(gg1, Decl(narrowingMutualSubtypes.ts, 30, 77)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 13)) if (isObject1(x)) { >isObject1 : Symbol(isObject1, Decl(narrowingMutualSubtypes.ts, 26, 21)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 13)) x; // Record ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 13)) } else { x; // {} ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 13)) } x; // {} ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 13)) } declare function isObject2(value: unknown): value is {}; @@ -114,90 +114,251 @@ function gg2(x: Record) { >isObject2 : Symbol(isObject2, Decl(narrowingMutualSubtypes.ts, 40, 1)) >x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) - x; // {} + x; // Record >x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) } else { - x; // Record + x; // never >x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) } x; // Record >x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) } +// Check that {} is a strict supertype of Record + +declare function isObject3(value: unknown): value is Record; +>isObject3 : Symbol(isObject3, Decl(narrowingMutualSubtypes.ts, 52, 1)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 56, 27)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 56, 27)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +function gg3(x: {}) { +>gg3 : Symbol(gg3, Decl(narrowingMutualSubtypes.ts, 56, 73)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 58, 13)) + + if (isObject3(x)) { +>isObject3 : Symbol(isObject3, Decl(narrowingMutualSubtypes.ts, 52, 1)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 58, 13)) + + x; // Record +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 58, 13)) + } + else { + x; // {} +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 58, 13)) + } + x; // {} +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 58, 13)) +} + +declare function isObject4(value: unknown): value is {}; +>isObject4 : Symbol(isObject4, Decl(narrowingMutualSubtypes.ts, 66, 1)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 68, 27)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 68, 27)) + +function gg4(x: Record) { +>gg4 : Symbol(gg4, Decl(narrowingMutualSubtypes.ts, 68, 56)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 70, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + if (isObject4(x)) { +>isObject4 : Symbol(isObject4, Decl(narrowingMutualSubtypes.ts, 66, 1)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 70, 13)) + + x; // Record +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 70, 13)) + } + else { + x; // never +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 70, 13)) + } + x; // Record +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 70, 13)) +} + // Repro from #50916 type Identity = {[K in keyof T]: T[K]}; ->Identity : Symbol(Identity, Decl(narrowingMutualSubtypes.ts, 52, 1)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 56, 14)) ->K : Symbol(K, Decl(narrowingMutualSubtypes.ts, 56, 21)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 56, 14)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 56, 14)) ->K : Symbol(K, Decl(narrowingMutualSubtypes.ts, 56, 21)) +>Identity : Symbol(Identity, Decl(narrowingMutualSubtypes.ts, 78, 1)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 82, 14)) +>K : Symbol(K, Decl(narrowingMutualSubtypes.ts, 82, 21)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 82, 14)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 82, 14)) +>K : Symbol(K, Decl(narrowingMutualSubtypes.ts, 82, 21)) type Self = T extends unknown ? Identity : never; ->Self : Symbol(Self, Decl(narrowingMutualSubtypes.ts, 56, 42)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 58, 10)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 58, 10)) ->Identity : Symbol(Identity, Decl(narrowingMutualSubtypes.ts, 52, 1)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 58, 10)) +>Self : Symbol(Self, Decl(narrowingMutualSubtypes.ts, 82, 42)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 84, 10)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 84, 10)) +>Identity : Symbol(Identity, Decl(narrowingMutualSubtypes.ts, 78, 1)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 84, 10)) function is(value: T): value is Self { ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 60, 12)) ->value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 60, 15)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 60, 12)) ->value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 60, 15)) ->Self : Symbol(Self, Decl(narrowingMutualSubtypes.ts, 56, 42)) ->T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 60, 12)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 86, 12)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 86, 15)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 86, 12)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 86, 15)) +>Self : Symbol(Self, Decl(narrowingMutualSubtypes.ts, 82, 42)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 86, 12)) return true; } type Union = {a: number} | {b: number} | {c: number}; ->Union : Symbol(Union, Decl(narrowingMutualSubtypes.ts, 62, 1)) ->a : Symbol(a, Decl(narrowingMutualSubtypes.ts, 64, 15)) ->b : Symbol(b, Decl(narrowingMutualSubtypes.ts, 64, 29)) ->c : Symbol(c, Decl(narrowingMutualSubtypes.ts, 64, 43)) +>Union : Symbol(Union, Decl(narrowingMutualSubtypes.ts, 88, 1)) +>a : Symbol(a, Decl(narrowingMutualSubtypes.ts, 90, 15)) +>b : Symbol(b, Decl(narrowingMutualSubtypes.ts, 90, 29)) +>c : Symbol(c, Decl(narrowingMutualSubtypes.ts, 90, 43)) function example(x: Union) { ->example : Symbol(example, Decl(narrowingMutualSubtypes.ts, 64, 54)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) ->Union : Symbol(Union, Decl(narrowingMutualSubtypes.ts, 62, 1)) +>example : Symbol(example, Decl(narrowingMutualSubtypes.ts, 90, 54)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) +>Union : Symbol(Union, Decl(narrowingMutualSubtypes.ts, 88, 1)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) if (is(x)) {} ->is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 84, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) x; // Union ->x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 92, 17)) +} + +function checksArrayOrObject1(obj: Record | Record[]) { +>checksArrayOrObject1 : Symbol(checksArrayOrObject1, Decl(narrowingMutualSubtypes.ts, 102, 1)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + // "accidentally" guards the first branch on the length + if (Array.isArray(obj) && obj.length) { +>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, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>obj.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --)) + + for (let key in obj) { +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 107, 16)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) + + if (obj[key] !== undefined) { +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 107, 16)) +>undefined : Symbol(undefined) + + console.log(obj[key]) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 107, 16)) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 115, 16)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) + + if (obj[key] !== undefined) { +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 115, 16)) +>undefined : Symbol(undefined) + + console.log(obj[key]) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 104, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 115, 16)) + } + } + } +} + +function checksArrayOrObject2(obj: Record | Record[]) { +>checksArrayOrObject2 : Symbol(checksArrayOrObject2, Decl(narrowingMutualSubtypes.ts, 121, 1)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + if (Array.isArray(obj)) { +>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, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) + + // obj should only be an array type here + for (let key in obj) { +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 126, 16)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) + + if (obj[key] !== undefined) { +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 126, 16)) +>undefined : Symbol(undefined) + + console.log(obj[key]) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 126, 16)) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 134, 16)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) + + if (obj[key] !== undefined) { +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 134, 16)) +>undefined : Symbol(undefined) + + console.log(obj[key]) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>obj : Symbol(obj, Decl(narrowingMutualSubtypes.ts, 123, 30)) +>key : Symbol(key, Decl(narrowingMutualSubtypes.ts, 134, 16)) + } + } + } } diff --git a/tests/baselines/reference/narrowingMutualSubtypes.types b/tests/baselines/reference/narrowingMutualSubtypes.types index 7ed2f5955d678..c335387467d23 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.types +++ b/tests/baselines/reference/narrowingMutualSubtypes.types @@ -83,14 +83,14 @@ const a4b = [r4, c4]; // {}[] >r4 : { [x: string]: unknown; } >c4 : {} -// Check that narrowing preserves original type in false branch for non-identical mutual subtypes +// Check that {} is a strict supertype of Record declare function isObject1(value: unknown): value is Record; >isObject1 : (value: unknown) => value is Record >value : unknown -function gg(x: {}) { ->gg : (x: {}) => void +function gg1(x: {}) { +>gg1 : (x: {}) => void >x : {} if (isObject1(x)) { @@ -122,17 +122,67 @@ function gg2(x: Record) { >isObject2 : (value: unknown) => value is {} >x : Record - x; // {} + x; // Record >x : Record } else { - x; // Record + x; // never >x : never } x; // Record >x : Record } +// Check that {} is a strict supertype of Record + +declare function isObject3(value: unknown): value is Record; +>isObject3 : (value: unknown) => value is Record +>value : unknown + +function gg3(x: {}) { +>gg3 : (x: {}) => void +>x : {} + + if (isObject3(x)) { +>isObject3(x) : boolean +>isObject3 : (value: unknown) => value is Record +>x : {} + + x; // Record +>x : Record + } + else { + x; // {} +>x : {} + } + x; // {} +>x : {} +} + +declare function isObject4(value: unknown): value is {}; +>isObject4 : (value: unknown) => value is {} +>value : unknown + +function gg4(x: Record) { +>gg4 : (x: Record) => void +>x : Record + + if (isObject4(x)) { +>isObject4(x) : boolean +>isObject4 : (value: unknown) => value is {} +>x : Record + + x; // Record +>x : Record + } + else { + x; // never +>x : never + } + x; // Record +>x : Record +} + // Repro from #50916 type Identity = {[K in keyof T]: T[K]}; @@ -203,3 +253,127 @@ function example(x: Union) { >x : Union } +function checksArrayOrObject1(obj: Record | Record[]) { +>checksArrayOrObject1 : (obj: Record | Record[]) => void +>obj : Record | Record[] + + // "accidentally" guards the first branch on the length + if (Array.isArray(obj) && obj.length) { +>Array.isArray(obj) && obj.length : number | false +>Array.isArray(obj) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>obj : Record | Record[] +>obj.length : number +>obj : any[] | Record[] +>length : number + + for (let key in obj) { +>key : string +>obj : any[] | Record[] + + if (obj[key] !== undefined) { +>obj[key] !== undefined : boolean +>obj[key] : any +>obj : any[] | Record[] +>key : string +>undefined : undefined + + console.log(obj[key]) +>console.log(obj[key]) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>obj[key] : any +>obj : any[] | Record[] +>key : string + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { +>key : string +>obj : any[] | Record + + if (obj[key] !== undefined) { +>obj[key] !== undefined : boolean +>obj[key] : any +>obj : any[] | Record +>key : string +>undefined : undefined + + console.log(obj[key]) +>console.log(obj[key]) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>obj[key] : any +>obj : any[] | Record +>key : string + } + } + } +} + +function checksArrayOrObject2(obj: Record | Record[]) { +>checksArrayOrObject2 : (obj: Record | Record[]) => void +>obj : Record | Record[] + + if (Array.isArray(obj)) { +>Array.isArray(obj) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>obj : Record | Record[] + + // obj should only be an array type here + for (let key in obj) { +>key : string +>obj : any[] | Record[] + + if (obj[key] !== undefined) { +>obj[key] !== undefined : boolean +>obj[key] : any +>obj : any[] | Record[] +>key : string +>undefined : undefined + + console.log(obj[key]) +>console.log(obj[key]) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>obj[key] : any +>obj : any[] | Record[] +>key : string + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { +>key : string +>obj : Record + + if (obj[key] !== undefined) { +>obj[key] !== undefined : boolean +>obj[key] : any +>obj : Record +>key : string +>undefined : undefined + + console.log(obj[key]) +>console.log(obj[key]) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>obj[key] : any +>obj : Record +>key : string + } + } + } +} + diff --git a/tests/cases/compiler/narrowingMutualSubtypes.ts b/tests/cases/compiler/narrowingMutualSubtypes.ts index 1d666c256fa77..de026cb2066ad 100644 --- a/tests/cases/compiler/narrowingMutualSubtypes.ts +++ b/tests/cases/compiler/narrowingMutualSubtypes.ts @@ -28,11 +28,11 @@ const c4 = {}; const a4a = [c4, r4]; // {}[] const a4b = [r4, c4]; // {}[] -// Check that narrowing preserves original type in false branch for non-identical mutual subtypes +// Check that {} is a strict supertype of Record declare function isObject1(value: unknown): value is Record; -function gg(x: {}) { +function gg1(x: {}) { if (isObject1(x)) { x; // Record } @@ -46,14 +46,40 @@ declare function isObject2(value: unknown): value is {}; function gg2(x: Record) { if (isObject2(x)) { - x; // {} + x; // Record } else { - x; // Record + x; // never } x; // Record } +// Check that {} is a strict supertype of Record + +declare function isObject3(value: unknown): value is Record; + +function gg3(x: {}) { + if (isObject3(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} + +declare function isObject4(value: unknown): value is {}; + +function gg4(x: Record) { + if (isObject4(x)) { + x; // Record + } + else { + x; // never + } + x; // Record +} + // Repro from #50916 type Identity = {[K in keyof T]: T[K]}; @@ -77,3 +103,41 @@ function example(x: Union) { if (is(x)) {} x; // Union } + +function checksArrayOrObject1(obj: Record | Record[]) { + // "accidentally" guards the first branch on the length + if (Array.isArray(obj) && obj.length) { + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } +} + +function checksArrayOrObject2(obj: Record | Record[]) { + if (Array.isArray(obj)) { + // obj should only be an array type here + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } + else { + // 'obj' should probably not include an array type here. + for (let key in obj) { + if (obj[key] !== undefined) { + console.log(obj[key]) + } + } + } +}