From e2aa5c6a3ba33428d1de553b6644b034b791d185 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Mar 2023 10:27:34 -0700 Subject: [PATCH 1/4] Exclude special index signature rule from strict subtype relation --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) { From ae63c730c1f97bebffb8be60eb0f9bd2af4b5104 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 20 Mar 2023 11:15:45 -0700 Subject: [PATCH 2/4] Add tests --- .../reference/narrowingMutualSubtypes.js | 58 ++++++- .../reference/narrowingMutualSubtypes.symbols | 156 ++++++++++++------ .../reference/narrowingMutualSubtypes.types | 60 ++++++- .../cases/compiler/narrowingMutualSubtypes.ts | 34 +++- 4 files changed, 240 insertions(+), 68 deletions(-) diff --git a/tests/baselines/reference/narrowingMutualSubtypes.js b/tests/baselines/reference/narrowingMutualSubtypes.js index 0d5f36d2f9551..2e7788c55ca1e 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]}; @@ -92,7 +118,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 +129,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; } diff --git a/tests/baselines/reference/narrowingMutualSubtypes.symbols b/tests/baselines/reference/narrowingMutualSubtypes.symbols index 9229c0b6e2946..d85aaef1acec1 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,142 @@ 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)) } diff --git a/tests/baselines/reference/narrowingMutualSubtypes.types b/tests/baselines/reference/narrowingMutualSubtypes.types index 7ed2f5955d678..f67901f07fe07 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]}; diff --git a/tests/cases/compiler/narrowingMutualSubtypes.ts b/tests/cases/compiler/narrowingMutualSubtypes.ts index 1d666c256fa77..04c088be81827 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]}; From 54703cd74f1d2add64c5165b15c27d03c4eb44c7 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 22 Mar 2023 15:42:28 -0700 Subject: [PATCH 3/4] Add more test cases. --- .../cases/compiler/narrowingMutualSubtypes.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/cases/compiler/narrowingMutualSubtypes.ts b/tests/cases/compiler/narrowingMutualSubtypes.ts index 04c088be81827..de026cb2066ad 100644 --- a/tests/cases/compiler/narrowingMutualSubtypes.ts +++ b/tests/cases/compiler/narrowingMutualSubtypes.ts @@ -103,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]) + } + } + } +} From 37911f80353320202a71b246f199e30ff94866ec Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 22 Mar 2023 23:54:13 +0000 Subject: [PATCH 4/4] Accepted baselines. --- .../narrowingMutualSubtypes.errors.txt | 155 ++++++++++++++++++ .../reference/narrowingMutualSubtypes.js | 74 +++++++++ .../reference/narrowingMutualSubtypes.symbols | 109 ++++++++++++ .../reference/narrowingMutualSubtypes.types | 124 ++++++++++++++ 4 files changed, 462 insertions(+) create mode 100644 tests/baselines/reference/narrowingMutualSubtypes.errors.txt 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 2e7788c55ca1e..69faca4689f3d 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.js +++ b/tests/baselines/reference/narrowingMutualSubtypes.js @@ -102,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] @@ -168,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 d85aaef1acec1..a08534ac2a31c 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.symbols +++ b/tests/baselines/reference/narrowingMutualSubtypes.symbols @@ -253,3 +253,112 @@ function example(x: Union) { >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 f67901f07fe07..c335387467d23 100644 --- a/tests/baselines/reference/narrowingMutualSubtypes.types +++ b/tests/baselines/reference/narrowingMutualSubtypes.types @@ -253,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 + } + } + } +} +