From e1c8dc2768c958cf29dce8db1feac471c5e6cf33 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Mon, 24 Sep 2018 12:37:13 -0700 Subject: [PATCH] Limit the narrow-to-fresh rule added with boolean literals to only boolean literals (#27274) * Remove the narrow-to-fresh rule added with boolean literals * Revert "Remove the narrow-to-fresh rule added with boolean literals" This reverts commit 9f96fe5da33f9297157b326c37680a964b23d7eb. * Only apply freshness to booleans for now * Add largeish example from issue * Should be AND not OR * Add minor improvements suggested by @ahejelsberg * Reorder conditional a bit --- src/compiler/checker.ts | 8 +- ...FreshnessPropagationOnNarrowing.errors.txt | 72 +++++++ .../literalFreshnessPropagationOnNarrowing.js | 114 ++++++++++++ ...ralFreshnessPropagationOnNarrowing.symbols | 150 +++++++++++++++ ...teralFreshnessPropagationOnNarrowing.types | 175 ++++++++++++++++++ .../literalFreshnessPropagationOnNarrowing.ts | 61 ++++++ 6 files changed, 576 insertions(+), 4 deletions(-) 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/src/compiler/checker.ts b/src/compiler/checker.ts index 67dcac2834..691b994daa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8873,7 +8873,7 @@ namespace ts { } switch (unionReduction) { case UnionReduction.Literal: - if (includes & TypeFlags.StringOrNumberLiteralOrUnique) { + if (includes & TypeFlags.StringOrNumberLiteralOrUnique | TypeFlags.BooleanLiteral) { removeRedundantLiteralTypes(typeSet, includes); } break; @@ -12938,8 +12938,8 @@ 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 === 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 : @@ -14214,7 +14214,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 && 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 new file mode 100644 index 0000000000..63e68d79d8 --- /dev/null +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.errors.txt @@ -0,0 +1,72 @@ +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 (2 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'; + ~~ +!!! error TS2322: Type '"y"' is not assignable to type '"x"'. + + // 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 0000000000..a4ab83607c --- /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 0000000000..790def0be4 --- /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 0000000000..8fb03bc413 --- /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 : "x" +>x : "x" + + // Desired: OK (up for debate?) + // 3.0: Error + // 3.1 as-is: OK + x2 = 'y'; +>x2 = 'y' : "y" +>x2 : "x" +>'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 0000000000..56f14dc6b2 --- /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