From 9f96fe5da33f9297157b326c37680a964b23d7eb Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 21 Sep 2018 11:04:52 -0700 Subject: [PATCH 1/7] Remove the narrow-to-fresh rule added with boolean literals --- src/compiler/checker.ts | 5 +---- ...OperatorWithSecondOperandBooleanType.types | 4 ++-- ...onEmitDestructuringObjectLiteralPattern.js | 6 +++--- ...mitDestructuringObjectLiteralPattern.types | 18 ++++++++--------- ...nEmitDestructuringObjectLiteralPattern2.js | 6 +++--- ...itDestructuringObjectLiteralPattern2.types | 18 ++++++++--------- .../declarationsAndAssignments.types | 16 +++++++-------- .../reference/literalFreshnessNarrowing.js | 14 +++++++++++++ .../literalFreshnessNarrowing.symbols | 19 ++++++++++++++++++ .../reference/literalFreshnessNarrowing.types | 20 +++++++++++++++++++ .../reference/typeGuardOfFormThisMember.types | 2 +- .../compiler/literalFreshnessNarrowing.ts | 6 ++++++ 12 files changed, 95 insertions(+), 39 deletions(-) create mode 100644 tests/baselines/reference/literalFreshnessNarrowing.js create mode 100644 tests/baselines/reference/literalFreshnessNarrowing.symbols create mode 100644 tests/baselines/reference/literalFreshnessNarrowing.types create mode 100644 tests/cases/compiler/literalFreshnessNarrowing.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dc2faa2e298e2..87188ad9bbc9f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14208,10 +14208,7 @@ namespace ts { if (assignedType.flags & TypeFlags.Never) { return assignedType; } - let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); - if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.Literal)) { - reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types - } + const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); // Our crude heuristic produces an invalid result in some cases: see GH#26130. // For now, when that happens, we give up and don't narrow at all. (This also // means we'll never narrow for erroneous assignments where the assigned type diff --git a/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types b/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types index 3880059e5cc99..c5820232b0fc1 100644 --- a/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types +++ b/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types @@ -122,14 +122,14 @@ OBJECT = [1, 2, 3], BOOLEAN = false; >false : false var resultIsBoolean6 = (null, BOOLEAN); ->resultIsBoolean6 : boolean +>resultIsBoolean6 : false >(null, BOOLEAN) : false >null, BOOLEAN : false >null : null >BOOLEAN : false var resultIsBoolean7 = (ANY = undefined, BOOLEAN); ->resultIsBoolean7 : boolean +>resultIsBoolean7 : false >(ANY = undefined, BOOLEAN) : false >ANY = undefined, BOOLEAN : false >ANY = undefined : undefined diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js index 10d7427388a5e..2353206d9b3a6 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js @@ -55,9 +55,9 @@ declare var x11: number, y11: string, z11: boolean; declare function f15(): { a4: string; b4: number; - c4: boolean; + c4: true; }; -declare var a4: string, b4: number, c4: boolean; +declare var a4: string, b4: number, c4: true; declare module m { - var a4: string, b4: number, c4: boolean; + var a4: string, b4: number, c4: true; } diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types index 7e6efdf426439..38ddd8e086c56 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types @@ -82,7 +82,7 @@ var { a: x11, b: { a: y11, b: { a: z11 }}} = { a: 1, b: { a: "hello", b: { a: tr >true : true function f15() { ->f15 : () => { a4: string; b4: number; c4: boolean; } +>f15 : () => { a4: string; b4: number; c4: true; } var a4 = "hello"; >a4 : string @@ -97,17 +97,17 @@ function f15() { >true : true return { a4, b4, c4 }; ->{ a4, b4, c4 } : { a4: string; b4: number; c4: boolean; } +>{ a4, b4, c4 } : { a4: string; b4: number; c4: true; } >a4 : string >b4 : number ->c4 : boolean +>c4 : true } var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : boolean ->f15() : { a4: string; b4: number; c4: boolean; } ->f15 : () => { a4: string; b4: number; c4: boolean; } +>c4 : true +>f15() : { a4: string; b4: number; c4: true; } +>f15 : () => { a4: string; b4: number; c4: true; } module m { >m : typeof m @@ -115,7 +115,7 @@ module m { export var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : boolean ->f15() : { a4: string; b4: number; c4: boolean; } ->f15 : () => { a4: string; b4: number; c4: boolean; } +>c4 : true +>f15() : { a4: string; b4: number; c4: true; } +>f15 : () => { a4: string; b4: number; c4: true; } } diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js index 66dfe225e83c9..914cbbd153702 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js @@ -34,9 +34,9 @@ declare var x11: number, y11: string, z11: boolean; declare function f15(): { a4: string; b4: number; - c4: boolean; + c4: true; }; -declare var a4: string, b4: number, c4: boolean; +declare var a4: string, b4: number, c4: true; declare module m { - var a4: string, b4: number, c4: boolean; + var a4: string, b4: number, c4: true; } diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types index c73afd0b9cff9..6633a9dafeaa9 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types @@ -21,7 +21,7 @@ var { a: x11, b: { a: y11, b: { a: z11 }}} = { a: 1, b: { a: "hello", b: { a: tr >true : true function f15() { ->f15 : () => { a4: string; b4: number; c4: boolean; } +>f15 : () => { a4: string; b4: number; c4: true; } var a4 = "hello"; >a4 : string @@ -36,17 +36,17 @@ function f15() { >true : true return { a4, b4, c4 }; ->{ a4, b4, c4 } : { a4: string; b4: number; c4: boolean; } +>{ a4, b4, c4 } : { a4: string; b4: number; c4: true; } >a4 : string >b4 : number ->c4 : boolean +>c4 : true } var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : boolean ->f15() : { a4: string; b4: number; c4: boolean; } ->f15 : () => { a4: string; b4: number; c4: boolean; } +>c4 : true +>f15() : { a4: string; b4: number; c4: true; } +>f15 : () => { a4: string; b4: number; c4: true; } module m { >m : typeof m @@ -54,7 +54,7 @@ module m { export var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : boolean ->f15() : { a4: string; b4: number; c4: boolean; } ->f15 : () => { a4: string; b4: number; c4: boolean; } +>c4 : true +>f15() : { a4: string; b4: number; c4: true; } +>f15 : () => { a4: string; b4: number; c4: true; } } diff --git a/tests/baselines/reference/declarationsAndAssignments.types b/tests/baselines/reference/declarationsAndAssignments.types index 602ffbd76eb0d..5b9da633d2a85 100644 --- a/tests/baselines/reference/declarationsAndAssignments.types +++ b/tests/baselines/reference/declarationsAndAssignments.types @@ -483,7 +483,7 @@ module M { } function f15() { ->f15 : () => { a: string; b: number; c: boolean; } +>f15 : () => { a: string; b: number; c: true; } var a = "hello"; >a : string @@ -498,10 +498,10 @@ function f15() { >true : true return { a, b, c }; ->{ a, b, c } : { a: string; b: number; c: boolean; } +>{ a, b, c } : { a: string; b: number; c: true; } >a : string >b : number ->c : boolean +>c : true } function f16() { @@ -510,9 +510,9 @@ function f16() { var { a, b, c } = f15(); >a : string >b : number ->c : boolean ->f15() : { a: string; b: number; c: boolean; } ->f15 : () => { a: string; b: number; c: boolean; } +>c : true +>f15() : { a: string; b: number; c: true; } +>f15 : () => { a: string; b: number; c: true; } } function f17({ a = "", b = 0, c = false }) { @@ -547,8 +547,8 @@ f17({ c: true }); f17(f15()); >f17(f15()) : void >f17 : ({ a, b, c }: { a?: string; b?: number; c?: boolean; }) => void ->f15() : { a: string; b: number; c: boolean; } ->f15 : () => { a: string; b: number; c: boolean; } +>f15() : { a: string; b: number; c: true; } +>f15 : () => { a: string; b: number; c: true; } function f18() { >f18 : () => void diff --git a/tests/baselines/reference/literalFreshnessNarrowing.js b/tests/baselines/reference/literalFreshnessNarrowing.js new file mode 100644 index 0000000000000..fd665b8c5238b --- /dev/null +++ b/tests/baselines/reference/literalFreshnessNarrowing.js @@ -0,0 +1,14 @@ +//// [literalFreshnessNarrowing.ts] +type XY = 'x' | 'y'; +const x: XY = 'x'; +let x2 = x; // Shouldn't have type string + +const y: boolean = true; +let y2 = y; // Shouldn't type boolean + + +//// [literalFreshnessNarrowing.js] +var x = 'x'; +var x2 = x; // Shouldn't have type string +var y = true; +var y2 = y; // Shouldn't type boolean diff --git a/tests/baselines/reference/literalFreshnessNarrowing.symbols b/tests/baselines/reference/literalFreshnessNarrowing.symbols new file mode 100644 index 0000000000000..96ceebf3667d6 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessNarrowing.symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/literalFreshnessNarrowing.ts === +type XY = 'x' | 'y'; +>XY : Symbol(XY, Decl(literalFreshnessNarrowing.ts, 0, 0)) + +const x: XY = 'x'; +>x : Symbol(x, Decl(literalFreshnessNarrowing.ts, 1, 5)) +>XY : Symbol(XY, Decl(literalFreshnessNarrowing.ts, 0, 0)) + +let x2 = x; // Shouldn't have type string +>x2 : Symbol(x2, Decl(literalFreshnessNarrowing.ts, 2, 3)) +>x : Symbol(x, Decl(literalFreshnessNarrowing.ts, 1, 5)) + +const y: boolean = true; +>y : Symbol(y, Decl(literalFreshnessNarrowing.ts, 4, 5)) + +let y2 = y; // Shouldn't type boolean +>y2 : Symbol(y2, Decl(literalFreshnessNarrowing.ts, 5, 3)) +>y : Symbol(y, Decl(literalFreshnessNarrowing.ts, 4, 5)) + diff --git a/tests/baselines/reference/literalFreshnessNarrowing.types b/tests/baselines/reference/literalFreshnessNarrowing.types new file mode 100644 index 0000000000000..07bf8cb01edb9 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessNarrowing.types @@ -0,0 +1,20 @@ +=== tests/cases/compiler/literalFreshnessNarrowing.ts === +type XY = 'x' | 'y'; +>XY : XY + +const x: XY = 'x'; +>x : XY +>'x' : "x" + +let x2 = x; // Shouldn't have type string +>x2 : "x" +>x : "x" + +const y: boolean = true; +>y : boolean +>true : true + +let y2 = y; // Shouldn't type boolean +>y2 : true +>y : true + diff --git a/tests/baselines/reference/typeGuardOfFormThisMember.types b/tests/baselines/reference/typeGuardOfFormThisMember.types index d887f43478121..ed0499ed84969 100644 --- a/tests/baselines/reference/typeGuardOfFormThisMember.types +++ b/tests/baselines/reference/typeGuardOfFormThisMember.types @@ -92,7 +92,7 @@ namespace Test { >true : true let x = file.isFile; ->x : boolean +>x : true >file.isFile : true >file : FileSystemObject >isFile : true diff --git a/tests/cases/compiler/literalFreshnessNarrowing.ts b/tests/cases/compiler/literalFreshnessNarrowing.ts new file mode 100644 index 0000000000000..5255631ce567a --- /dev/null +++ b/tests/cases/compiler/literalFreshnessNarrowing.ts @@ -0,0 +1,6 @@ +type XY = 'x' | 'y'; +const x: XY = 'x'; +let x2 = x; // Shouldn't have type string + +const y: boolean = true; +let y2 = y; // Shouldn't type boolean From 357a5ac2273bf87f256d9f064b244d87eec051cc Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 21 Sep 2018 13:32:35 -0700 Subject: [PATCH 2/7] Revert "Remove the narrow-to-fresh rule added with boolean literals" This reverts commit 9f96fe5da33f9297157b326c37680a964b23d7eb. --- src/compiler/checker.ts | 5 ++++- ...OperatorWithSecondOperandBooleanType.types | 4 ++-- ...onEmitDestructuringObjectLiteralPattern.js | 6 +++--- ...mitDestructuringObjectLiteralPattern.types | 18 ++++++++--------- ...nEmitDestructuringObjectLiteralPattern2.js | 6 +++--- ...itDestructuringObjectLiteralPattern2.types | 18 ++++++++--------- .../declarationsAndAssignments.types | 16 +++++++-------- .../reference/literalFreshnessNarrowing.js | 14 ------------- .../literalFreshnessNarrowing.symbols | 19 ------------------ .../reference/literalFreshnessNarrowing.types | 20 ------------------- .../reference/typeGuardOfFormThisMember.types | 2 +- .../compiler/literalFreshnessNarrowing.ts | 6 ------ 12 files changed, 39 insertions(+), 95 deletions(-) delete mode 100644 tests/baselines/reference/literalFreshnessNarrowing.js delete mode 100644 tests/baselines/reference/literalFreshnessNarrowing.symbols delete mode 100644 tests/baselines/reference/literalFreshnessNarrowing.types delete mode 100644 tests/cases/compiler/literalFreshnessNarrowing.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 87188ad9bbc9f..dc2faa2e298e2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14208,7 +14208,10 @@ namespace ts { if (assignedType.flags & TypeFlags.Never) { return assignedType; } - const reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); + if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.Literal)) { + reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types + } // Our crude heuristic produces an invalid result in some cases: see GH#26130. // For now, when that happens, we give up and don't narrow at all. (This also // means we'll never narrow for erroneous assignments where the assigned type diff --git a/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types b/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types index c5820232b0fc1..3880059e5cc99 100644 --- a/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types +++ b/tests/baselines/reference/commaOperatorWithSecondOperandBooleanType.types @@ -122,14 +122,14 @@ OBJECT = [1, 2, 3], BOOLEAN = false; >false : false var resultIsBoolean6 = (null, BOOLEAN); ->resultIsBoolean6 : false +>resultIsBoolean6 : boolean >(null, BOOLEAN) : false >null, BOOLEAN : false >null : null >BOOLEAN : false var resultIsBoolean7 = (ANY = undefined, BOOLEAN); ->resultIsBoolean7 : false +>resultIsBoolean7 : boolean >(ANY = undefined, BOOLEAN) : false >ANY = undefined, BOOLEAN : false >ANY = undefined : undefined diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js index 2353206d9b3a6..10d7427388a5e 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.js @@ -55,9 +55,9 @@ declare var x11: number, y11: string, z11: boolean; declare function f15(): { a4: string; b4: number; - c4: true; + c4: boolean; }; -declare var a4: string, b4: number, c4: true; +declare var a4: string, b4: number, c4: boolean; declare module m { - var a4: string, b4: number, c4: true; + var a4: string, b4: number, c4: boolean; } diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types index 38ddd8e086c56..7e6efdf426439 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern.types @@ -82,7 +82,7 @@ var { a: x11, b: { a: y11, b: { a: z11 }}} = { a: 1, b: { a: "hello", b: { a: tr >true : true function f15() { ->f15 : () => { a4: string; b4: number; c4: true; } +>f15 : () => { a4: string; b4: number; c4: boolean; } var a4 = "hello"; >a4 : string @@ -97,17 +97,17 @@ function f15() { >true : true return { a4, b4, c4 }; ->{ a4, b4, c4 } : { a4: string; b4: number; c4: true; } +>{ a4, b4, c4 } : { a4: string; b4: number; c4: boolean; } >a4 : string >b4 : number ->c4 : true +>c4 : boolean } var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : true ->f15() : { a4: string; b4: number; c4: true; } ->f15 : () => { a4: string; b4: number; c4: true; } +>c4 : boolean +>f15() : { a4: string; b4: number; c4: boolean; } +>f15 : () => { a4: string; b4: number; c4: boolean; } module m { >m : typeof m @@ -115,7 +115,7 @@ module m { export var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : true ->f15() : { a4: string; b4: number; c4: true; } ->f15 : () => { a4: string; b4: number; c4: true; } +>c4 : boolean +>f15() : { a4: string; b4: number; c4: boolean; } +>f15 : () => { a4: string; b4: number; c4: boolean; } } diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js index 914cbbd153702..66dfe225e83c9 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.js @@ -34,9 +34,9 @@ declare var x11: number, y11: string, z11: boolean; declare function f15(): { a4: string; b4: number; - c4: true; + c4: boolean; }; -declare var a4: string, b4: number, c4: true; +declare var a4: string, b4: number, c4: boolean; declare module m { - var a4: string, b4: number, c4: true; + var a4: string, b4: number, c4: boolean; } diff --git a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types index 6633a9dafeaa9..c73afd0b9cff9 100644 --- a/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types +++ b/tests/baselines/reference/declarationEmitDestructuringObjectLiteralPattern2.types @@ -21,7 +21,7 @@ var { a: x11, b: { a: y11, b: { a: z11 }}} = { a: 1, b: { a: "hello", b: { a: tr >true : true function f15() { ->f15 : () => { a4: string; b4: number; c4: true; } +>f15 : () => { a4: string; b4: number; c4: boolean; } var a4 = "hello"; >a4 : string @@ -36,17 +36,17 @@ function f15() { >true : true return { a4, b4, c4 }; ->{ a4, b4, c4 } : { a4: string; b4: number; c4: true; } +>{ a4, b4, c4 } : { a4: string; b4: number; c4: boolean; } >a4 : string >b4 : number ->c4 : true +>c4 : boolean } var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : true ->f15() : { a4: string; b4: number; c4: true; } ->f15 : () => { a4: string; b4: number; c4: true; } +>c4 : boolean +>f15() : { a4: string; b4: number; c4: boolean; } +>f15 : () => { a4: string; b4: number; c4: boolean; } module m { >m : typeof m @@ -54,7 +54,7 @@ module m { export var { a4, b4, c4 } = f15(); >a4 : string >b4 : number ->c4 : true ->f15() : { a4: string; b4: number; c4: true; } ->f15 : () => { a4: string; b4: number; c4: true; } +>c4 : boolean +>f15() : { a4: string; b4: number; c4: boolean; } +>f15 : () => { a4: string; b4: number; c4: boolean; } } diff --git a/tests/baselines/reference/declarationsAndAssignments.types b/tests/baselines/reference/declarationsAndAssignments.types index 5b9da633d2a85..602ffbd76eb0d 100644 --- a/tests/baselines/reference/declarationsAndAssignments.types +++ b/tests/baselines/reference/declarationsAndAssignments.types @@ -483,7 +483,7 @@ module M { } function f15() { ->f15 : () => { a: string; b: number; c: true; } +>f15 : () => { a: string; b: number; c: boolean; } var a = "hello"; >a : string @@ -498,10 +498,10 @@ function f15() { >true : true return { a, b, c }; ->{ a, b, c } : { a: string; b: number; c: true; } +>{ a, b, c } : { a: string; b: number; c: boolean; } >a : string >b : number ->c : true +>c : boolean } function f16() { @@ -510,9 +510,9 @@ function f16() { var { a, b, c } = f15(); >a : string >b : number ->c : true ->f15() : { a: string; b: number; c: true; } ->f15 : () => { a: string; b: number; c: true; } +>c : boolean +>f15() : { a: string; b: number; c: boolean; } +>f15 : () => { a: string; b: number; c: boolean; } } function f17({ a = "", b = 0, c = false }) { @@ -547,8 +547,8 @@ f17({ c: true }); f17(f15()); >f17(f15()) : void >f17 : ({ a, b, c }: { a?: string; b?: number; c?: boolean; }) => void ->f15() : { a: string; b: number; c: true; } ->f15 : () => { a: string; b: number; c: true; } +>f15() : { a: string; b: number; c: boolean; } +>f15 : () => { a: string; b: number; c: boolean; } function f18() { >f18 : () => void diff --git a/tests/baselines/reference/literalFreshnessNarrowing.js b/tests/baselines/reference/literalFreshnessNarrowing.js deleted file mode 100644 index fd665b8c5238b..0000000000000 --- a/tests/baselines/reference/literalFreshnessNarrowing.js +++ /dev/null @@ -1,14 +0,0 @@ -//// [literalFreshnessNarrowing.ts] -type XY = 'x' | 'y'; -const x: XY = 'x'; -let x2 = x; // Shouldn't have type string - -const y: boolean = true; -let y2 = y; // Shouldn't type boolean - - -//// [literalFreshnessNarrowing.js] -var x = 'x'; -var x2 = x; // Shouldn't have type string -var y = true; -var y2 = y; // Shouldn't type boolean diff --git a/tests/baselines/reference/literalFreshnessNarrowing.symbols b/tests/baselines/reference/literalFreshnessNarrowing.symbols deleted file mode 100644 index 96ceebf3667d6..0000000000000 --- a/tests/baselines/reference/literalFreshnessNarrowing.symbols +++ /dev/null @@ -1,19 +0,0 @@ -=== tests/cases/compiler/literalFreshnessNarrowing.ts === -type XY = 'x' | 'y'; ->XY : Symbol(XY, Decl(literalFreshnessNarrowing.ts, 0, 0)) - -const x: XY = 'x'; ->x : Symbol(x, Decl(literalFreshnessNarrowing.ts, 1, 5)) ->XY : Symbol(XY, Decl(literalFreshnessNarrowing.ts, 0, 0)) - -let x2 = x; // Shouldn't have type string ->x2 : Symbol(x2, Decl(literalFreshnessNarrowing.ts, 2, 3)) ->x : Symbol(x, Decl(literalFreshnessNarrowing.ts, 1, 5)) - -const y: boolean = true; ->y : Symbol(y, Decl(literalFreshnessNarrowing.ts, 4, 5)) - -let y2 = y; // Shouldn't type boolean ->y2 : Symbol(y2, Decl(literalFreshnessNarrowing.ts, 5, 3)) ->y : Symbol(y, Decl(literalFreshnessNarrowing.ts, 4, 5)) - diff --git a/tests/baselines/reference/literalFreshnessNarrowing.types b/tests/baselines/reference/literalFreshnessNarrowing.types deleted file mode 100644 index 07bf8cb01edb9..0000000000000 --- a/tests/baselines/reference/literalFreshnessNarrowing.types +++ /dev/null @@ -1,20 +0,0 @@ -=== tests/cases/compiler/literalFreshnessNarrowing.ts === -type XY = 'x' | 'y'; ->XY : XY - -const x: XY = 'x'; ->x : XY ->'x' : "x" - -let x2 = x; // Shouldn't have type string ->x2 : "x" ->x : "x" - -const y: boolean = true; ->y : boolean ->true : true - -let y2 = y; // Shouldn't type boolean ->y2 : true ->y : true - diff --git a/tests/baselines/reference/typeGuardOfFormThisMember.types b/tests/baselines/reference/typeGuardOfFormThisMember.types index ed0499ed84969..d887f43478121 100644 --- a/tests/baselines/reference/typeGuardOfFormThisMember.types +++ b/tests/baselines/reference/typeGuardOfFormThisMember.types @@ -92,7 +92,7 @@ namespace Test { >true : true let x = file.isFile; ->x : true +>x : boolean >file.isFile : true >file : FileSystemObject >isFile : true diff --git a/tests/cases/compiler/literalFreshnessNarrowing.ts b/tests/cases/compiler/literalFreshnessNarrowing.ts deleted file mode 100644 index 5255631ce567a..0000000000000 --- a/tests/cases/compiler/literalFreshnessNarrowing.ts +++ /dev/null @@ -1,6 +0,0 @@ -type XY = 'x' | 'y'; -const x: XY = 'x'; -let x2 = x; // Shouldn't have type string - -const y: boolean = true; -let y2 = y; // Shouldn't type boolean From 1981a8f02609634764da69d4a7f809f4998f3de5 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 21 Sep 2018 14:11:27 -0700 Subject: [PATCH 3/7] Only apply freshness to booleans for now --- 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 dc2faa2e298e2..a54d0e4d2a6e9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14209,7 +14209,7 @@ namespace ts { return assignedType; } let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); - if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.Literal)) { + if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.BooleanLiteral)) { reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types } // Our crude heuristic produces an invalid result in some cases: see GH#26130. From 2c56f49f88fdaba4c3d357fafc48d80c455fdb84 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 21 Sep 2018 14:16:23 -0700 Subject: [PATCH 4/7] Add largeish example from issue --- ...FreshnessPropagationOnNarrowing.errors.txt | 69 +++++++ .../literalFreshnessPropagationOnNarrowing.js | 114 ++++++++++++ ...ralFreshnessPropagationOnNarrowing.symbols | 150 +++++++++++++++ ...teralFreshnessPropagationOnNarrowing.types | 175 ++++++++++++++++++ .../literalFreshnessPropagationOnNarrowing.ts | 61 ++++++ 5 files changed, 569 insertions(+) create mode 100644 tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt create mode 100644 tests/baselines/reference/literalFreshnessPropagationOnNarrowing.js create mode 100644 tests/baselines/reference/literalFreshnessPropagationOnNarrowing.symbols create mode 100644 tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types create mode 100644 tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt new file mode 100644 index 0000000000000..26f8b1395f008 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt @@ -0,0 +1,69 @@ +tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS2322: Type 'string[]' is not assignable to type 'XY[]'. + Type 'string' is not assignable to type 'XY'. + + +==== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts (1 errors) ==== + function f1() { + let b = true; + let obj = { b }; + // Desired: OK + // 3.0: OK + // 3.1 as-is: OK + // 3.1 minus widening propagation: error + obj.b = false; + } + + function f2() { + type Element = (string | false); + type ElementOrArray = Element | Element[]; + let el: Element = null as any; + let arr: Element[] = null as any; + let elOrA: ElementOrArray = null as any; + + // Desired/actual: All OK + let a1: ElementOrArray = el; + let a2: ElementOrArray = arr; + let a3: ElementOrArray = [el]; + let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA]; + + // Desired: OK + // 3.0: Error + // 3.1: OK + let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]]; + } + + function f3() { + type XY = 'x' | 'y'; + const x: XY = 'x'; + let x2 = x; + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; + + // Desired/actual: All OK + let x3: XY = x; + x3 = 'y'; + } + + function f4() { + const x: boolean = true; + let x1 = x; + // Desired: OK + // 3.0: OK + // 3.1: OK + // 3.1 minus widening propagation: error + x1 = false; + } + + function f5() { + type XY = 'x' | 'y'; + let arr: XY[] = ['x']; + arr = ['y']; + // Desired: OK + // Error in all extant branches + arr = [...['y']]; + ~~~ +!!! error TS2322: Type 'string[]' is not assignable to type 'XY[]'. +!!! error TS2322: Type 'string' is not assignable to type 'XY'. + } \ No newline at end of file diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.js b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.js new file mode 100644 index 0000000000000..a4ab83607cfb6 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.js @@ -0,0 +1,114 @@ +//// [literalFreshnessPropagationOnNarrowing.ts] +function f1() { + let b = true; + let obj = { b }; + // Desired: OK + // 3.0: OK + // 3.1 as-is: OK + // 3.1 minus widening propagation: error + obj.b = false; +} + +function f2() { + type Element = (string | false); + type ElementOrArray = Element | Element[]; + let el: Element = null as any; + let arr: Element[] = null as any; + let elOrA: ElementOrArray = null as any; + + // Desired/actual: All OK + let a1: ElementOrArray = el; + let a2: ElementOrArray = arr; + let a3: ElementOrArray = [el]; + let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA]; + + // Desired: OK + // 3.0: Error + // 3.1: OK + let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]]; +} + +function f3() { + type XY = 'x' | 'y'; + const x: XY = 'x'; + let x2 = x; + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; + + // Desired/actual: All OK + let x3: XY = x; + x3 = 'y'; +} + +function f4() { + const x: boolean = true; + let x1 = x; + // Desired: OK + // 3.0: OK + // 3.1: OK + // 3.1 minus widening propagation: error + x1 = false; +} + +function f5() { + type XY = 'x' | 'y'; + let arr: XY[] = ['x']; + arr = ['y']; + // Desired: OK + // Error in all extant branches + arr = [...['y']]; +} + +//// [literalFreshnessPropagationOnNarrowing.js] +function f1() { + var b = true; + var obj = { b: b }; + // Desired: OK + // 3.0: OK + // 3.1 as-is: OK + // 3.1 minus widening propagation: error + obj.b = false; +} +function f2() { + var el = null; + var arr = null; + var elOrA = null; + // Desired/actual: All OK + var a1 = el; + var a2 = arr; + var a3 = [el]; + var a4 = Array.isArray(elOrA) ? elOrA : [elOrA]; + // Desired: OK + // 3.0: Error + // 3.1: OK + var a5 = (Array.isArray(elOrA) ? elOrA : [elOrA]).slice(); +} +function f3() { + var x = 'x'; + var x2 = x; + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; + // Desired/actual: All OK + var x3 = x; + x3 = 'y'; +} +function f4() { + var x = true; + var x1 = x; + // Desired: OK + // 3.0: OK + // 3.1: OK + // 3.1 minus widening propagation: error + x1 = false; +} +function f5() { + var arr = ['x']; + arr = ['y']; + // Desired: OK + // Error in all extant branches + arr = ['y']; +} diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.symbols b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.symbols new file mode 100644 index 0000000000000..790def0be4318 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.symbols @@ -0,0 +1,150 @@ +=== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts === +function f1() { +>f1 : Symbol(f1, Decl(literalFreshnessPropagationOnNarrowing.ts, 0, 0)) + + let b = true; +>b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 1, 7)) + + let obj = { b }; +>obj : Symbol(obj, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 7)) +>b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 15)) + + // Desired: OK + // 3.0: OK + // 3.1 as-is: OK + // 3.1 minus widening propagation: error + obj.b = false; +>obj.b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 15)) +>obj : Symbol(obj, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 7)) +>b : Symbol(b, Decl(literalFreshnessPropagationOnNarrowing.ts, 2, 15)) +} + +function f2() { +>f2 : Symbol(f2, Decl(literalFreshnessPropagationOnNarrowing.ts, 8, 1)) + + type Element = (string | false); +>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15)) + + type ElementOrArray = Element | Element[]; +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) +>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15)) +>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15)) + + let el: Element = null as any; +>el : Symbol(el, Decl(literalFreshnessPropagationOnNarrowing.ts, 13, 7)) +>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15)) + + let arr: Element[] = null as any; +>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 14, 7)) +>Element : Symbol(Element, Decl(literalFreshnessPropagationOnNarrowing.ts, 10, 15)) + + let elOrA: ElementOrArray = null as any; +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) + + // Desired/actual: All OK + let a1: ElementOrArray = el; +>a1 : Symbol(a1, Decl(literalFreshnessPropagationOnNarrowing.ts, 18, 7)) +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) +>el : Symbol(el, Decl(literalFreshnessPropagationOnNarrowing.ts, 13, 7)) + + let a2: ElementOrArray = arr; +>a2 : Symbol(a2, Decl(literalFreshnessPropagationOnNarrowing.ts, 19, 7)) +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) +>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 14, 7)) + + let a3: ElementOrArray = [el]; +>a3 : Symbol(a3, Decl(literalFreshnessPropagationOnNarrowing.ts, 20, 7)) +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) +>el : Symbol(el, Decl(literalFreshnessPropagationOnNarrowing.ts, 13, 7)) + + let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA]; +>a4 : Symbol(a4, Decl(literalFreshnessPropagationOnNarrowing.ts, 21, 7)) +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) +>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, --, --)) +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) + + // Desired: OK + // 3.0: Error + // 3.1: OK + let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]]; +>a5 : Symbol(a5, Decl(literalFreshnessPropagationOnNarrowing.ts, 26, 7)) +>ElementOrArray : Symbol(ElementOrArray, Decl(literalFreshnessPropagationOnNarrowing.ts, 11, 36)) +>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, --, --)) +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) +>elOrA : Symbol(elOrA, Decl(literalFreshnessPropagationOnNarrowing.ts, 15, 7)) +} + +function f3() { +>f3 : Symbol(f3, Decl(literalFreshnessPropagationOnNarrowing.ts, 27, 1)) + + type XY = 'x' | 'y'; +>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 29, 15)) + + const x: XY = 'x'; +>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 31, 9)) +>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 29, 15)) + + let x2 = x; +>x2 : Symbol(x2, Decl(literalFreshnessPropagationOnNarrowing.ts, 32, 7)) +>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 31, 9)) + + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; +>x2 : Symbol(x2, Decl(literalFreshnessPropagationOnNarrowing.ts, 32, 7)) + + // Desired/actual: All OK + let x3: XY = x; +>x3 : Symbol(x3, Decl(literalFreshnessPropagationOnNarrowing.ts, 39, 7)) +>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 29, 15)) +>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 31, 9)) + + x3 = 'y'; +>x3 : Symbol(x3, Decl(literalFreshnessPropagationOnNarrowing.ts, 39, 7)) +} + +function f4() { +>f4 : Symbol(f4, Decl(literalFreshnessPropagationOnNarrowing.ts, 41, 1)) + + const x: boolean = true; +>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 44, 9)) + + let x1 = x; +>x1 : Symbol(x1, Decl(literalFreshnessPropagationOnNarrowing.ts, 45, 7)) +>x : Symbol(x, Decl(literalFreshnessPropagationOnNarrowing.ts, 44, 9)) + + // Desired: OK + // 3.0: OK + // 3.1: OK + // 3.1 minus widening propagation: error + x1 = false; +>x1 : Symbol(x1, Decl(literalFreshnessPropagationOnNarrowing.ts, 45, 7)) +} + +function f5() { +>f5 : Symbol(f5, Decl(literalFreshnessPropagationOnNarrowing.ts, 51, 1)) + + type XY = 'x' | 'y'; +>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 53, 15)) + + let arr: XY[] = ['x']; +>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 55, 7)) +>XY : Symbol(XY, Decl(literalFreshnessPropagationOnNarrowing.ts, 53, 15)) + + arr = ['y']; +>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 55, 7)) + + // Desired: OK + // Error in all extant branches + arr = [...['y']]; +>arr : Symbol(arr, Decl(literalFreshnessPropagationOnNarrowing.ts, 55, 7)) +} diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types new file mode 100644 index 0000000000000..477294f9f6782 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types @@ -0,0 +1,175 @@ +=== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts === +function f1() { +>f1 : () => void + + let b = true; +>b : boolean +>true : true + + let obj = { b }; +>obj : { b: boolean; } +>{ b } : { b: boolean; } +>b : boolean + + // Desired: OK + // 3.0: OK + // 3.1 as-is: OK + // 3.1 minus widening propagation: error + obj.b = false; +>obj.b = false : false +>obj.b : boolean +>obj : { b: boolean; } +>b : boolean +>false : false +} + +function f2() { +>f2 : () => void + + type Element = (string | false); +>Element : string | false +>false : false + + type ElementOrArray = Element | Element[]; +>ElementOrArray : string | false | (string | false)[] + + let el: Element = null as any; +>el : string | false +>null as any : any +>null : null + + let arr: Element[] = null as any; +>arr : (string | false)[] +>null as any : any +>null : null + + let elOrA: ElementOrArray = null as any; +>elOrA : string | false | (string | false)[] +>null as any : any +>null : null + + // Desired/actual: All OK + let a1: ElementOrArray = el; +>a1 : string | false | (string | false)[] +>el : string | false + + let a2: ElementOrArray = arr; +>a2 : string | false | (string | false)[] +>arr : (string | false)[] + + let a3: ElementOrArray = [el]; +>a3 : string | false | (string | false)[] +>[el] : (string | false)[] +>el : string | false + + let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA]; +>a4 : string | false | (string | false)[] +>Array.isArray(elOrA) ? elOrA : [elOrA] : (string | false)[] +>Array.isArray(elOrA) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>elOrA : string | false | (string | false)[] +>elOrA : (string | false)[] +>[elOrA] : (string | false)[] +>elOrA : string | false + + // Desired: OK + // 3.0: Error + // 3.1: OK + let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]]; +>a5 : string | false | (string | false)[] +>[...Array.isArray(elOrA) ? elOrA : [elOrA]] : (string | false)[] +>...Array.isArray(elOrA) ? elOrA : [elOrA] : string | false +>Array.isArray(elOrA) ? elOrA : [elOrA] : (string | false)[] +>Array.isArray(elOrA) : boolean +>Array.isArray : (arg: any) => arg is any[] +>Array : ArrayConstructor +>isArray : (arg: any) => arg is any[] +>elOrA : string | false | (string | false)[] +>elOrA : (string | false)[] +>[elOrA] : (string | false)[] +>elOrA : string | false +} + +function f3() { +>f3 : () => void + + type XY = 'x' | 'y'; +>XY : "x" | "y" + + const x: XY = 'x'; +>x : "x" | "y" +>'x' : "x" + + let x2 = x; +>x2 : string +>x : "x" + + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; +>x2 = 'y' : "y" +>x2 : string +>'y' : "y" + + // Desired/actual: All OK + let x3: XY = x; +>x3 : "x" | "y" +>x : "x" + + x3 = 'y'; +>x3 = 'y' : "y" +>x3 : "x" | "y" +>'y' : "y" +} + +function f4() { +>f4 : () => void + + const x: boolean = true; +>x : boolean +>true : true + + let x1 = x; +>x1 : boolean +>x : true + + // Desired: OK + // 3.0: OK + // 3.1: OK + // 3.1 minus widening propagation: error + x1 = false; +>x1 = false : false +>x1 : boolean +>false : false +} + +function f5() { +>f5 : () => void + + type XY = 'x' | 'y'; +>XY : "x" | "y" + + let arr: XY[] = ['x']; +>arr : ("x" | "y")[] +>['x'] : "x"[] +>'x' : "x" + + arr = ['y']; +>arr = ['y'] : "y"[] +>arr : ("x" | "y")[] +>['y'] : "y"[] +>'y' : "y" + + // Desired: OK + // Error in all extant branches + arr = [...['y']]; +>arr = [...['y']] : string[] +>arr : ("x" | "y")[] +>[...['y']] : string[] +>...['y'] : string +>['y'] : string[] +>'y' : "y" +} diff --git a/tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts b/tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts new file mode 100644 index 0000000000000..56f14dc6b2b53 --- /dev/null +++ b/tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts @@ -0,0 +1,61 @@ +function f1() { + let b = true; + let obj = { b }; + // Desired: OK + // 3.0: OK + // 3.1 as-is: OK + // 3.1 minus widening propagation: error + obj.b = false; +} + +function f2() { + type Element = (string | false); + type ElementOrArray = Element | Element[]; + let el: Element = null as any; + let arr: Element[] = null as any; + let elOrA: ElementOrArray = null as any; + + // Desired/actual: All OK + let a1: ElementOrArray = el; + let a2: ElementOrArray = arr; + let a3: ElementOrArray = [el]; + let a4: ElementOrArray = Array.isArray(elOrA) ? elOrA : [elOrA]; + + // Desired: OK + // 3.0: Error + // 3.1: OK + let a5: ElementOrArray = [...Array.isArray(elOrA) ? elOrA : [elOrA]]; +} + +function f3() { + type XY = 'x' | 'y'; + const x: XY = 'x'; + let x2 = x; + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; + + // Desired/actual: All OK + let x3: XY = x; + x3 = 'y'; +} + +function f4() { + const x: boolean = true; + let x1 = x; + // Desired: OK + // 3.0: OK + // 3.1: OK + // 3.1 minus widening propagation: error + x1 = false; +} + +function f5() { + type XY = 'x' | 'y'; + let arr: XY[] = ['x']; + arr = ['y']; + // Desired: OK + // Error in all extant branches + arr = [...['y']]; +} \ No newline at end of file From a71431f9f6df14003e83552b743095855c33b07e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 24 Sep 2018 11:09:07 -0700 Subject: [PATCH 5/7] Should be AND not OR --- src/compiler/checker.ts | 2 +- .../literalFreshnessPropagationOnNarrowing.errors.txt | 5 ++++- .../reference/literalFreshnessPropagationOnNarrowing.types | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a54d0e4d2a6e9..8805807b2fdbe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14209,7 +14209,7 @@ namespace ts { return assignedType; } let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); - if (assignedType.flags & (TypeFlags.FreshLiteral | TypeFlags.BooleanLiteral)) { + if (assignedType.flags & TypeFlags.FreshLiteral && assignedType.flags & TypeFlags.BooleanLiteral) { reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types } // Our crude heuristic produces an invalid result in some cases: see GH#26130. diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt index 26f8b1395f008..63e68d79d89bc 100644 --- a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt @@ -1,8 +1,9 @@ +tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(37,5): error TS2322: Type '"y"' is not assignable to type '"x"'. tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS2322: Type 'string[]' is not assignable to type 'XY[]'. Type 'string' is not assignable to type 'XY'. -==== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts (1 errors) ==== +==== tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts (2 errors) ==== function f1() { let b = true; let obj = { b }; @@ -40,6 +41,8 @@ tests/cases/compiler/literalFreshnessPropagationOnNarrowing.ts(60,5): error TS23 // 3.0: Error // 3.1 as-is: OK x2 = 'y'; + ~~ +!!! error TS2322: Type '"y"' is not assignable to type '"x"'. // Desired/actual: All OK let x3: XY = x; diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types index 477294f9f6782..8fb03bc413f30 100644 --- a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types @@ -103,7 +103,7 @@ function f3() { >'x' : "x" let x2 = x; ->x2 : string +>x2 : "x" >x : "x" // Desired: OK (up for debate?) @@ -111,7 +111,7 @@ function f3() { // 3.1 as-is: OK x2 = 'y'; >x2 = 'y' : "y" ->x2 : string +>x2 : "x" >'y' : "y" // Desired/actual: All OK From 43fc4bcfd0d13248bdfe5bb1c434f85d33952be5 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 24 Sep 2018 11:12:22 -0700 Subject: [PATCH 6/7] Add minor improvements suggested by @ahejelsberg --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8805807b2fdbe..2cfba0633749f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8868,7 +8868,7 @@ namespace ts { } switch (unionReduction) { case UnionReduction.Literal: - if (includes & TypeFlags.StringOrNumberLiteralOrUnique) { + if (includes & TypeFlags.StringOrNumberLiteralOrUnique | TypeFlags.BooleanLiteral) { removeRedundantLiteralTypes(typeSet, includes); } break; @@ -12933,7 +12933,7 @@ namespace ts { function getDefinitelyFalsyPartOfType(type: Type): Type { return type.flags & TypeFlags.String ? emptyStringType : type.flags & TypeFlags.Number ? zeroType : - type.flags & TypeFlags.Boolean || type === regularFalseType ? regularFalseType : + type === regularFalseType ? regularFalseType : type === falseType ? falseType : type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) || type.flags & TypeFlags.StringLiteral && (type).value === "" || From caa0896c21b273f9104feb4548f4101177de0caa Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 24 Sep 2018 11:15:16 -0700 Subject: [PATCH 7/7] Reorder conditional a bit --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2cfba0633749f..9103507451c13 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12933,8 +12933,8 @@ namespace ts { function getDefinitelyFalsyPartOfType(type: Type): Type { return type.flags & TypeFlags.String ? emptyStringType : type.flags & TypeFlags.Number ? zeroType : - type === regularFalseType ? regularFalseType : - type === falseType ? falseType : + type === regularFalseType || + type === falseType || type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null) || type.flags & TypeFlags.StringLiteral && (type).value === "" || type.flags & TypeFlags.NumberLiteral && (type).value === 0 ? type :