From 01bd4e8cbebc5767ba2a0b1b9c3a4bb1aaebd34b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 13 May 2024 19:19:51 +0200 Subject: [PATCH 1/2] Narrow non-directly related weak types by intersecting them with candidates --- src/compiler/checker.ts | 4 +- .../reference/typeGuardIntersectionTypes.js | 35 +++++++- .../typeGuardIntersectionTypes.symbols | 70 +++++++++++++-- .../typeGuardIntersectionTypes.types | 90 ++++++++++++++++--- .../typeGuards/typeGuardIntersectionTypes.ts | 25 +++++- 5 files changed, 202 insertions(+), 22 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 84b111555df7e..9108ccf57ff30 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28787,9 +28787,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { t => isTypeStrictSubtypeOf(t, c) ? t : isTypeStrictSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : isTypeSubtypeOf(c, t) ? c : neverType, ); // If no constituents are directly related, create intersections for any generic constituents that - // are related by constraint. + // are related by constraint or when the type is weak. return directlyRelated.flags & TypeFlags.Never ? - mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) : + mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) || isWeakType(type) ? getIntersectionType([t, c]) : neverType) : directlyRelated; }); // If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.js b/tests/baselines/reference/typeGuardIntersectionTypes.js index e75701a240155..bf7438ae8e89d 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.js +++ b/tests/baselines/reference/typeGuardIntersectionTypes.js @@ -111,7 +111,31 @@ function beastFoo(beast: Object) { if (hasLegs(beast) && hasWings(beast)) { beast; // Legged & Winged } -} +} + +// https://github.com/microsoft/TypeScript/issues/58518 + +interface A_58518 { + label?: string; + a?: string; +} + +interface B_58518 { + label?: string; + b: boolean; +} + +function isB_58518(thing: object): thing is B_58518 { + return "b" in thing; +} + +function test_58518(thing: A_58518) { + thing.a; + if (isB_58518(thing)) { + thing.a; + } +} + //// [typeGuardIntersectionTypes.js] function f1(obj) { @@ -180,3 +204,12 @@ function beastFoo(beast) { beast; // Legged & Winged } } +function isB_58518(thing) { + return "b" in thing; +} +function test_58518(thing) { + thing.a; + if (isB_58518(thing)) { + thing.a; + } +} diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.symbols b/tests/baselines/reference/typeGuardIntersectionTypes.symbols index da72bc7752c1b..87c88e93d3125 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.symbols +++ b/tests/baselines/reference/typeGuardIntersectionTypes.symbols @@ -178,17 +178,17 @@ function identifyBeast(beast: Beast) { >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) if (beast.legs === 4) { ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) log(`pegasus - 4 legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) } else if (beast.legs === 2) { ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) log(`bird - 2 legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) @@ -196,9 +196,9 @@ function identifyBeast(beast: Beast) { else { log(`unknown - ${beast.legs} legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) } } @@ -206,9 +206,9 @@ function identifyBeast(beast: Beast) { else { log(`manbearpig - ${beast.legs} legs, no wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) } } @@ -257,3 +257,57 @@ function beastFoo(beast: Object) { >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 99, 18)) } } + +// https://github.com/microsoft/TypeScript/issues/58518 + +interface A_58518 { +>A_58518 : Symbol(A_58518, Decl(typeGuardIntersectionTypes.ts, 110, 1)) + + label?: string; +>label : Symbol(A_58518.label, Decl(typeGuardIntersectionTypes.ts, 114, 19)) + + a?: string; +>a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17)) +} + +interface B_58518 { +>B_58518 : Symbol(B_58518, Decl(typeGuardIntersectionTypes.ts, 117, 1)) + + label?: string; +>label : Symbol(B_58518.label, Decl(typeGuardIntersectionTypes.ts, 119, 19)) + + b: boolean; +>b : Symbol(B_58518.b, Decl(typeGuardIntersectionTypes.ts, 120, 17)) +} + +function isB_58518(thing: object): thing is B_58518 { +>isB_58518 : Symbol(isB_58518, Decl(typeGuardIntersectionTypes.ts, 122, 1)) +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 124, 19)) +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 124, 19)) +>B_58518 : Symbol(B_58518, Decl(typeGuardIntersectionTypes.ts, 117, 1)) + + return "b" in thing; +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 124, 19)) +} + +function test_58518(thing: A_58518) { +>test_58518 : Symbol(test_58518, Decl(typeGuardIntersectionTypes.ts, 126, 1)) +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20)) +>A_58518 : Symbol(A_58518, Decl(typeGuardIntersectionTypes.ts, 110, 1)) + + thing.a; +>thing.a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17)) +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20)) +>a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17)) + + if (isB_58518(thing)) { +>isB_58518 : Symbol(isB_58518, Decl(typeGuardIntersectionTypes.ts, 122, 1)) +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20)) + + thing.a; +>thing.a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17)) +>thing : Symbol(thing, Decl(typeGuardIntersectionTypes.ts, 128, 20)) +>a : Symbol(A_58518.a, Decl(typeGuardIntersectionTypes.ts, 115, 17)) + } +} + diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.types b/tests/baselines/reference/typeGuardIntersectionTypes.types index d674b18422e1a..283132c1e4e0d 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.types +++ b/tests/baselines/reference/typeGuardIntersectionTypes.types @@ -241,16 +241,16 @@ function identifyBeast(beast: Beast) { > : ^^^^^^^ >hasWings : (x: Beast) => x is Winged > : ^ ^^ ^^^^^^^^^^^^^^^^ ->beast : Legged -> : ^^^^^^ +>beast : Beast & Legged +> : ^^^^^^^^^^^^^^ if (beast.legs === 4) { >beast.legs === 4 : boolean > : ^^^^^^^ >beast.legs : number > : ^^^^^^ ->beast : Legged & Winged -> : ^^^^^^^^^^^^^^^ +>beast : Beast & Legged & Winged +> : ^^^^^^^^^^^^^^^^^^^^^^^ >legs : number > : ^^^^^^ >4 : 4 @@ -269,8 +269,8 @@ function identifyBeast(beast: Beast) { > : ^^^^^^^ >beast.legs : number > : ^^^^^^ ->beast : Legged & Winged -> : ^^^^^^^^^^^^^^^ +>beast : Beast & Legged & Winged +> : ^^^^^^^^^^^^^^^^^^^^^^^ >legs : number > : ^^^^^^ >2 : 2 @@ -294,8 +294,8 @@ function identifyBeast(beast: Beast) { > : ^^^^^^ >beast.legs : number > : ^^^^^^ ->beast : Legged & Winged -> : ^^^^^^^^^^^^^^^ +>beast : Beast & Legged & Winged +> : ^^^^^^^^^^^^^^^^^^^^^^^ >legs : number > : ^^^^^^ } @@ -312,8 +312,8 @@ function identifyBeast(beast: Beast) { > : ^^^^^^ >beast.legs : number > : ^^^^^^ ->beast : Legged -> : ^^^^^^ +>beast : Beast & Legged +> : ^^^^^^^^^^^^^^ >legs : number > : ^^^^^^ } @@ -402,3 +402,73 @@ function beastFoo(beast: Object) { > : ^^^^^^^^^^^^^^^ } } + +// https://github.com/microsoft/TypeScript/issues/58518 + +interface A_58518 { + label?: string; +>label : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + a?: string; +>a : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +} + +interface B_58518 { + label?: string; +>label : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + b: boolean; +>b : boolean +> : ^^^^^^^ +} + +function isB_58518(thing: object): thing is B_58518 { +>isB_58518 : (thing: object) => thing is B_58518 +> : ^ ^^ ^^^^^ +>thing : object +> : ^^^^^^ + + return "b" in thing; +>"b" in thing : boolean +> : ^^^^^^^ +>"b" : "b" +> : ^^^ +>thing : object +> : ^^^^^^ +} + +function test_58518(thing: A_58518) { +>test_58518 : (thing: A_58518) => void +> : ^ ^^ ^^^^^^^^^ +>thing : A_58518 +> : ^^^^^^^ + + thing.a; +>thing.a : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>thing : A_58518 +> : ^^^^^^^ +>a : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + if (isB_58518(thing)) { +>isB_58518(thing) : boolean +> : ^^^^^^^ +>isB_58518 : (thing: object) => thing is B_58518 +> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^ +>thing : A_58518 +> : ^^^^^^^ + + thing.a; +>thing.a : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>thing : A_58518 & B_58518 +> : ^^^^^^^^^^^^^^^^^ +>a : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + } +} + diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts index 85c003787fd38..3d8397a0007ee 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardIntersectionTypes.ts @@ -110,4 +110,27 @@ function beastFoo(beast: Object) { if (hasLegs(beast) && hasWings(beast)) { beast; // Legged & Winged } -} \ No newline at end of file +} + +// https://github.com/microsoft/TypeScript/issues/58518 + +interface A_58518 { + label?: string; + a?: string; +} + +interface B_58518 { + label?: string; + b: boolean; +} + +function isB_58518(thing: object): thing is B_58518 { + return "b" in thing; +} + +function test_58518(thing: A_58518) { + thing.a; + if (isB_58518(thing)) { + thing.a; + } +} From 73b5904939153a8d85eb7e141aed432fd0c9227c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 16 Jan 2025 11:28:09 +0100 Subject: [PATCH 2/2] add test case --- .../reference/typeGuardWeakTypes1.symbols | 117 +++++++++++ .../reference/typeGuardWeakTypes1.types | 183 ++++++++++++++++++ .../typeGuards/typeGuardWeakTypes1.ts | 50 +++++ 3 files changed, 350 insertions(+) create mode 100644 tests/baselines/reference/typeGuardWeakTypes1.symbols create mode 100644 tests/baselines/reference/typeGuardWeakTypes1.types create mode 100644 tests/cases/conformance/expressions/typeGuards/typeGuardWeakTypes1.ts diff --git a/tests/baselines/reference/typeGuardWeakTypes1.symbols b/tests/baselines/reference/typeGuardWeakTypes1.symbols new file mode 100644 index 0000000000000..ae39c2b2cb91c --- /dev/null +++ b/tests/baselines/reference/typeGuardWeakTypes1.symbols @@ -0,0 +1,117 @@ +//// [tests/cases/conformance/expressions/typeGuards/typeGuardWeakTypes1.ts] //// + +=== typeGuardWeakTypes1.ts === +// https://github.com/microsoft/TypeScript/issues/60979 + +export interface FilesCleanupCliFlags { +>FilesCleanupCliFlags : Symbol(FilesCleanupCliFlags, Decl(typeGuardWeakTypes1.ts, 0, 0)) + + readonly dryRun?: boolean; +>dryRun : Symbol(FilesCleanupCliFlags.dryRun, Decl(typeGuardWeakTypes1.ts, 2, 39)) + + readonly outputPath?: string; +>outputPath : Symbol(FilesCleanupCliFlags.outputPath, Decl(typeGuardWeakTypes1.ts, 3, 28)) +} + +function checkCliFlags( +>checkCliFlags : Symbol(checkCliFlags, Decl(typeGuardWeakTypes1.ts, 5, 1)) + + flags: FilesCleanupCliFlags, +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 7, 23)) +>FilesCleanupCliFlags : Symbol(FilesCleanupCliFlags, Decl(typeGuardWeakTypes1.ts, 0, 0)) + +): flags is +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 7, 23)) + + | { readonly dryRun: true; readonly outputPath: string } +>dryRun : Symbol(dryRun, Decl(typeGuardWeakTypes1.ts, 10, 5)) +>outputPath : Symbol(outputPath, Decl(typeGuardWeakTypes1.ts, 10, 28)) + + | { readonly dryRun?: false } { +>dryRun : Symbol(dryRun, Decl(typeGuardWeakTypes1.ts, 11, 5)) + + if (flags.dryRun && !flags.outputPath) { +>flags.dryRun : Symbol(FilesCleanupCliFlags.dryRun, Decl(typeGuardWeakTypes1.ts, 2, 39)) +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 7, 23)) +>dryRun : Symbol(FilesCleanupCliFlags.dryRun, Decl(typeGuardWeakTypes1.ts, 2, 39)) +>flags.outputPath : Symbol(FilesCleanupCliFlags.outputPath, Decl(typeGuardWeakTypes1.ts, 3, 28)) +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 7, 23)) +>outputPath : Symbol(FilesCleanupCliFlags.outputPath, Decl(typeGuardWeakTypes1.ts, 3, 28)) + + throw new Error( +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + "The --outputPath option is required in dry-run mode and must specify the full file path.", + ); + } + return true; +} + +function main() { +>main : Symbol(main, Decl(typeGuardWeakTypes1.ts, 18, 1)) + + const options: FilesCleanupCliFlags = {}; +>options : Symbol(options, Decl(typeGuardWeakTypes1.ts, 21, 7)) +>FilesCleanupCliFlags : Symbol(FilesCleanupCliFlags, Decl(typeGuardWeakTypes1.ts, 0, 0)) + + if (checkCliFlags(options)) { +>checkCliFlags : Symbol(checkCliFlags, Decl(typeGuardWeakTypes1.ts, 5, 1)) +>options : Symbol(options, Decl(typeGuardWeakTypes1.ts, 21, 7)) + + const { dryRun, outputPath } = options; +>dryRun : Symbol(dryRun, Decl(typeGuardWeakTypes1.ts, 24, 11)) +>outputPath : Symbol(outputPath, Decl(typeGuardWeakTypes1.ts, 24, 19)) +>options : Symbol(options, Decl(typeGuardWeakTypes1.ts, 21, 7)) + } +} + +function checkCliFlagsWithAsserts( +>checkCliFlagsWithAsserts : Symbol(checkCliFlagsWithAsserts, Decl(typeGuardWeakTypes1.ts, 26, 1)) + + flags: FilesCleanupCliFlags, +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 28, 34)) +>FilesCleanupCliFlags : Symbol(FilesCleanupCliFlags, Decl(typeGuardWeakTypes1.ts, 0, 0)) + +): asserts flags is +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 28, 34)) + + | { readonly dryRun: true; readonly outputPath: string } +>dryRun : Symbol(dryRun, Decl(typeGuardWeakTypes1.ts, 31, 5)) +>outputPath : Symbol(outputPath, Decl(typeGuardWeakTypes1.ts, 31, 28)) + + | { readonly dryRun?: false } { +>dryRun : Symbol(dryRun, Decl(typeGuardWeakTypes1.ts, 32, 5)) + + if (flags.dryRun && !flags.outputPath) { +>flags.dryRun : Symbol(FilesCleanupCliFlags.dryRun, Decl(typeGuardWeakTypes1.ts, 2, 39)) +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 28, 34)) +>dryRun : Symbol(FilesCleanupCliFlags.dryRun, Decl(typeGuardWeakTypes1.ts, 2, 39)) +>flags.outputPath : Symbol(FilesCleanupCliFlags.outputPath, Decl(typeGuardWeakTypes1.ts, 3, 28)) +>flags : Symbol(flags, Decl(typeGuardWeakTypes1.ts, 28, 34)) +>outputPath : Symbol(FilesCleanupCliFlags.outputPath, Decl(typeGuardWeakTypes1.ts, 3, 28)) + + throw new Error( +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + "The --outputPath option is required in dry-run mode and must specify the full file path.", + ); + } +} + +function mainWithAsserts() { +>mainWithAsserts : Symbol(mainWithAsserts, Decl(typeGuardWeakTypes1.ts, 38, 1)) + + const options: FilesCleanupCliFlags = {}; +>options : Symbol(options, Decl(typeGuardWeakTypes1.ts, 41, 7)) +>FilesCleanupCliFlags : Symbol(FilesCleanupCliFlags, Decl(typeGuardWeakTypes1.ts, 0, 0)) + + checkCliFlagsWithAsserts(options); +>checkCliFlagsWithAsserts : Symbol(checkCliFlagsWithAsserts, Decl(typeGuardWeakTypes1.ts, 26, 1)) +>options : Symbol(options, Decl(typeGuardWeakTypes1.ts, 41, 7)) + + const { dryRun, outputPath } = options; +>dryRun : Symbol(dryRun, Decl(typeGuardWeakTypes1.ts, 45, 9)) +>outputPath : Symbol(outputPath, Decl(typeGuardWeakTypes1.ts, 45, 17)) +>options : Symbol(options, Decl(typeGuardWeakTypes1.ts, 41, 7)) +} + diff --git a/tests/baselines/reference/typeGuardWeakTypes1.types b/tests/baselines/reference/typeGuardWeakTypes1.types new file mode 100644 index 0000000000000..e522ea725be31 --- /dev/null +++ b/tests/baselines/reference/typeGuardWeakTypes1.types @@ -0,0 +1,183 @@ +//// [tests/cases/conformance/expressions/typeGuards/typeGuardWeakTypes1.ts] //// + +=== typeGuardWeakTypes1.ts === +// https://github.com/microsoft/TypeScript/issues/60979 + +export interface FilesCleanupCliFlags { + readonly dryRun?: boolean; +>dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ + + readonly outputPath?: string; +>outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +} + +function checkCliFlags( +>checkCliFlags : (flags: FilesCleanupCliFlags) => flags is { readonly dryRun: true; readonly outputPath: string; } | { readonly dryRun?: false; } +> : ^ ^^ ^^^^^ + + flags: FilesCleanupCliFlags, +>flags : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ + +): flags is + | { readonly dryRun: true; readonly outputPath: string } +>dryRun : true +> : ^^^^ +>true : true +> : ^^^^ +>outputPath : string +> : ^^^^^^ + + | { readonly dryRun?: false } { +>dryRun : false | undefined +> : ^^^^^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (flags.dryRun && !flags.outputPath) { +>flags.dryRun && !flags.outputPath : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>flags.dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>flags : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ +>dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>!flags.outputPath : boolean +> : ^^^^^^^ +>flags.outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>flags : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ +>outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + throw new Error( +>new Error( "The --outputPath option is required in dry-run mode and must specify the full file path.", ) : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + + "The --outputPath option is required in dry-run mode and must specify the full file path.", +>"The --outputPath option is required in dry-run mode and must specify the full file path." : "The --outputPath option is required in dry-run mode and must specify the full file path." +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + ); + } + return true; +>true : true +> : ^^^^ +} + +function main() { +>main : () => void +> : ^^^^^^^^^^ + + const options: FilesCleanupCliFlags = {}; +>options : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + + if (checkCliFlags(options)) { +>checkCliFlags(options) : boolean +> : ^^^^^^^ +>checkCliFlags : (flags: FilesCleanupCliFlags) => flags is { readonly dryRun: true; readonly outputPath: string; } | { readonly dryRun?: false; } +> : ^ ^^ ^^^^^ +>options : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ + + const { dryRun, outputPath } = options; +>dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>options : { readonly dryRun: true; readonly outputPath: string; } | (FilesCleanupCliFlags & { readonly dryRun?: false; }) +> : ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ + } +} + +function checkCliFlagsWithAsserts( +>checkCliFlagsWithAsserts : (flags: FilesCleanupCliFlags) => asserts flags is { readonly dryRun: true; readonly outputPath: string; } | { readonly dryRun?: false; } +> : ^ ^^ ^^^^^ + + flags: FilesCleanupCliFlags, +>flags : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ + +): asserts flags is + | { readonly dryRun: true; readonly outputPath: string } +>dryRun : true +> : ^^^^ +>true : true +> : ^^^^ +>outputPath : string +> : ^^^^^^ + + | { readonly dryRun?: false } { +>dryRun : false | undefined +> : ^^^^^^^^^^^^^^^^^ +>false : false +> : ^^^^^ + + if (flags.dryRun && !flags.outputPath) { +>flags.dryRun && !flags.outputPath : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>flags.dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>flags : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ +>dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>!flags.outputPath : boolean +> : ^^^^^^^ +>flags.outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>flags : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ +>outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ + + throw new Error( +>new Error( "The --outputPath option is required in dry-run mode and must specify the full file path.", ) : Error +> : ^^^^^ +>Error : ErrorConstructor +> : ^^^^^^^^^^^^^^^^ + + "The --outputPath option is required in dry-run mode and must specify the full file path.", +>"The --outputPath option is required in dry-run mode and must specify the full file path." : "The --outputPath option is required in dry-run mode and must specify the full file path." +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + ); + } +} + +function mainWithAsserts() { +>mainWithAsserts : () => void +> : ^^^^^^^^^^ + + const options: FilesCleanupCliFlags = {}; +>options : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ +>{} : {} +> : ^^ + + checkCliFlagsWithAsserts(options); +>checkCliFlagsWithAsserts(options) : void +> : ^^^^ +>checkCliFlagsWithAsserts : (flags: FilesCleanupCliFlags) => asserts flags is { readonly dryRun: true; readonly outputPath: string; } | { readonly dryRun?: false; } +> : ^ ^^ ^^^^^ +>options : FilesCleanupCliFlags +> : ^^^^^^^^^^^^^^^^^^^^ + + const { dryRun, outputPath } = options; +>dryRun : boolean | undefined +> : ^^^^^^^^^^^^^^^^^^^ +>outputPath : string | undefined +> : ^^^^^^^^^^^^^^^^^^ +>options : { readonly dryRun: true; readonly outputPath: string; } | (FilesCleanupCliFlags & { readonly dryRun?: false; }) +> : ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ +} + diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardWeakTypes1.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardWeakTypes1.ts new file mode 100644 index 0000000000000..29cf56ee07dd7 --- /dev/null +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardWeakTypes1.ts @@ -0,0 +1,50 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/60979 + +export interface FilesCleanupCliFlags { + readonly dryRun?: boolean; + readonly outputPath?: string; +} + +function checkCliFlags( + flags: FilesCleanupCliFlags, +): flags is + | { readonly dryRun: true; readonly outputPath: string } + | { readonly dryRun?: false } { + if (flags.dryRun && !flags.outputPath) { + throw new Error( + "The --outputPath option is required in dry-run mode and must specify the full file path.", + ); + } + return true; +} + +function main() { + const options: FilesCleanupCliFlags = {}; + + if (checkCliFlags(options)) { + const { dryRun, outputPath } = options; + } +} + +function checkCliFlagsWithAsserts( + flags: FilesCleanupCliFlags, +): asserts flags is + | { readonly dryRun: true; readonly outputPath: string } + | { readonly dryRun?: false } { + if (flags.dryRun && !flags.outputPath) { + throw new Error( + "The --outputPath option is required in dry-run mode and must specify the full file path.", + ); + } +} + +function mainWithAsserts() { + const options: FilesCleanupCliFlags = {}; + + checkCliFlagsWithAsserts(options); + + const { dryRun, outputPath } = options; +}