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 9f96fe5da3.

* 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
This commit is contained in:
Wesley Wigham 2018-09-24 12:37:13 -07:00 committed by GitHub
parent b7fc092404
commit e1c8dc2768
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 576 additions and 4 deletions

View file

@ -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 && (<LiteralType>type).value === "" ||
type.flags & TypeFlags.NumberLiteral && (<LiteralType>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.

View file

@ -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'.
}

View file

@ -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'];
}

View file

@ -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))
}

View file

@ -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"
}

View file

@ -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']];
}