diff --git a/tests/baselines/reference/narrowingPastLastAssignment.errors.txt b/tests/baselines/reference/narrowingPastLastAssignment.errors.txt new file mode 100644 index 0000000000000..7590912410bde --- /dev/null +++ b/tests/baselines/reference/narrowingPastLastAssignment.errors.txt @@ -0,0 +1,123 @@ +narrowingPastLastAssignment.ts(67,9): error TS7034: Variable 'x' implicitly has type 'any' in some locations where its type cannot be determined. +narrowingPastLastAssignment.ts(69,20): error TS7005: Variable 'x' implicitly has an 'any' type. + + +==== narrowingPastLastAssignment.ts (2 errors) ==== + function action(f: Function) {} + + // Narrowings are preserved in closures created past last assignment + + function f1(x: string | number) { + x = "abc"; + action(() => { x /* string | number */ }); + x = 42; + action(() => { x /* number */ }); + } + + // Narrowings are not preserved in inner function and class declarations (due to hoisting) + + function f2() { + let x: string | number; + x = 42; + let a = () => { x /* number */ }; + let f = function() { x /* number */ }; + let C = class { + foo() { x /* number */ } + }; + let o = { + foo() { x /* number */ } + }; + function g() { x /* string | number */ } + class A { + foo() { x /* string | number */ } + } + } + + // Narrowings are not preserved when assignments occur in inner functions + + function f3(x: string | number) { + action(() => { x = "abc" }); + x = 42; + action(() => { x /* string | number */ }); + } + + // Assignment effects in compoud statements extend to the entire statement + + function f4(cond: () => boolean) { + let x: string | number = 0; + while (cond()) { + x = "abc"; + action(() => { x /* string | number */ }); + x = 42; + action(() => { x /* string | number */ }); + } + action(() => { x /* number */ }); + } + + function f5(x: string | number, cond: () => boolean) { + if (cond()) { + x = 1; + action(() => { x /* string | number */ }); + } + else { + x = 2; + action(() => { x /* string | number */ }); + } + action(() => { x /* number */ }); + } + + // Implicit any variables have a known type following last assignment + + function f6() { + let x; + ~ +!!! error TS7034: Variable 'x' implicitly has type 'any' in some locations where its type cannot be determined. + x = "abc"; + action(() => { x }); // Error + ~ +!!! error TS7005: Variable 'x' implicitly has an 'any' type. + x = 42; + action(() => { x /* number */ }); + } + + // Narrowings on catch variables are preserved past last assignment + + function f7() { + try { + } + catch (e) { + if (e instanceof Error) { + let f = () => { e /* Error */ } + } + } + } + + // Repros from #35124 + + function f10() { + let i: number | undefined; + i = 0; + return (k: number) => k === i + 1; + } + + function makeAdder(n?: number) { + n ??= 0; + return (m: number) => n + m; + } + + function f11() { + let r; + r = "b"; + () => r; + } + + // Repro from #52104 + + const fooMap: Map> = new Map() + const values = [1, 2, 3, 4, 5]; + let foo = fooMap.get("a"); + if (foo == null) { + foo = []; + } + values.forEach(v => foo.push(v)); + \ No newline at end of file diff --git a/tests/baselines/reference/narrowingPastLastAssignment.symbols b/tests/baselines/reference/narrowingPastLastAssignment.symbols new file mode 100644 index 0000000000000..006603f0d010d --- /dev/null +++ b/tests/baselines/reference/narrowingPastLastAssignment.symbols @@ -0,0 +1,274 @@ +//// [tests/cases/compiler/narrowingPastLastAssignment.ts] //// + +=== narrowingPastLastAssignment.ts === +function action(f: Function) {} +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>f : Symbol(f, Decl(narrowingPastLastAssignment.ts, 0, 16)) +>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.esnext.decorators.d.ts, --, --)) + +// Narrowings are preserved in closures created past last assignment + +function f1(x: string | number) { +>f1 : Symbol(f1, Decl(narrowingPastLastAssignment.ts, 0, 31)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12)) + + x = "abc"; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12)) + + action(() => { x /* string | number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12)) + + x = 42; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12)) + + action(() => { x /* number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 4, 12)) +} + +// Narrowings are not preserved in inner function and class declarations (due to hoisting) + +function f2() { +>f2 : Symbol(f2, Decl(narrowingPastLastAssignment.ts, 9, 1)) + + let x: string | number; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + x = 42; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + let a = () => { x /* number */ }; +>a : Symbol(a, Decl(narrowingPastLastAssignment.ts, 16, 7)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + let f = function() { x /* number */ }; +>f : Symbol(f, Decl(narrowingPastLastAssignment.ts, 17, 7)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + let C = class { +>C : Symbol(C, Decl(narrowingPastLastAssignment.ts, 18, 7)) + + foo() { x /* number */ } +>foo : Symbol(C.foo, Decl(narrowingPastLastAssignment.ts, 18, 19)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + }; + let o = { +>o : Symbol(o, Decl(narrowingPastLastAssignment.ts, 21, 7)) + + foo() { x /* number */ } +>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 21, 13)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + }; + function g() { x /* string | number */ } +>g : Symbol(g, Decl(narrowingPastLastAssignment.ts, 23, 6)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + + class A { +>A : Symbol(A, Decl(narrowingPastLastAssignment.ts, 24, 44)) + + foo() { x /* string | number */ } +>foo : Symbol(A.foo, Decl(narrowingPastLastAssignment.ts, 25, 13)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 14, 7)) + } +} + +// Narrowings are not preserved when assignments occur in inner functions + +function f3(x: string | number) { +>f3 : Symbol(f3, Decl(narrowingPastLastAssignment.ts, 28, 1)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12)) + + action(() => { x = "abc" }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12)) + + x = 42; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12)) + + action(() => { x /* string | number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 32, 12)) +} + +// Assignment effects in compoud statements extend to the entire statement + +function f4(cond: () => boolean) { +>f4 : Symbol(f4, Decl(narrowingPastLastAssignment.ts, 36, 1)) +>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 40, 12)) + + let x: string | number = 0; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7)) + + while (cond()) { +>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 40, 12)) + + x = "abc"; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7)) + + action(() => { x /* string | number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7)) + + x = 42; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7)) + + action(() => { x /* string | number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7)) + } + action(() => { x /* number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 41, 7)) +} + +function f5(x: string | number, cond: () => boolean) { +>f5 : Symbol(f5, Decl(narrowingPastLastAssignment.ts, 49, 1)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12)) +>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 51, 31)) + + if (cond()) { +>cond : Symbol(cond, Decl(narrowingPastLastAssignment.ts, 51, 31)) + + x = 1; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12)) + + action(() => { x /* string | number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12)) + } + else { + x = 2; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12)) + + action(() => { x /* string | number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12)) + } + action(() => { x /* number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 51, 12)) +} + +// Implicit any variables have a known type following last assignment + +function f6() { +>f6 : Symbol(f6, Decl(narrowingPastLastAssignment.ts, 61, 1)) + + let x; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 66, 7)) + + x = "abc"; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 66, 7)) + + action(() => { x }); // Error +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 66, 7)) + + x = 42; +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 66, 7)) + + action(() => { x /* number */ }); +>action : Symbol(action, Decl(narrowingPastLastAssignment.ts, 0, 0)) +>x : Symbol(x, Decl(narrowingPastLastAssignment.ts, 66, 7)) +} + +// Narrowings on catch variables are preserved past last assignment + +function f7() { +>f7 : Symbol(f7, Decl(narrowingPastLastAssignment.ts, 71, 1)) + + try { + } + catch (e) { +>e : Symbol(e, Decl(narrowingPastLastAssignment.ts, 78, 11)) + + if (e instanceof Error) { +>e : Symbol(e, Decl(narrowingPastLastAssignment.ts, 78, 11)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2022.error.d.ts, --, --)) + + let f = () => { e /* Error */ } +>f : Symbol(f, Decl(narrowingPastLastAssignment.ts, 80, 15)) +>e : Symbol(e, Decl(narrowingPastLastAssignment.ts, 78, 11)) + } + } +} + +// Repros from #35124 + +function f10() { +>f10 : Symbol(f10, Decl(narrowingPastLastAssignment.ts, 83, 1)) + + let i: number | undefined; +>i : Symbol(i, Decl(narrowingPastLastAssignment.ts, 88, 7)) + + i = 0; +>i : Symbol(i, Decl(narrowingPastLastAssignment.ts, 88, 7)) + + return (k: number) => k === i + 1; +>k : Symbol(k, Decl(narrowingPastLastAssignment.ts, 90, 12)) +>k : Symbol(k, Decl(narrowingPastLastAssignment.ts, 90, 12)) +>i : Symbol(i, Decl(narrowingPastLastAssignment.ts, 88, 7)) +} + +function makeAdder(n?: number) { +>makeAdder : Symbol(makeAdder, Decl(narrowingPastLastAssignment.ts, 91, 1)) +>n : Symbol(n, Decl(narrowingPastLastAssignment.ts, 93, 19)) + + n ??= 0; +>n : Symbol(n, Decl(narrowingPastLastAssignment.ts, 93, 19)) + + return (m: number) => n + m; +>m : Symbol(m, Decl(narrowingPastLastAssignment.ts, 95, 12)) +>n : Symbol(n, Decl(narrowingPastLastAssignment.ts, 93, 19)) +>m : Symbol(m, Decl(narrowingPastLastAssignment.ts, 95, 12)) +} + +function f11() { +>f11 : Symbol(f11, Decl(narrowingPastLastAssignment.ts, 96, 1)) + + let r; +>r : Symbol(r, Decl(narrowingPastLastAssignment.ts, 99, 7)) + + r = "b"; +>r : Symbol(r, Decl(narrowingPastLastAssignment.ts, 99, 7)) + + () => r; +>r : Symbol(r, Decl(narrowingPastLastAssignment.ts, 99, 7)) +} + +// Repro from #52104 + +const fooMap: Map> = new Map() +>fooMap : Symbol(fooMap, Decl(narrowingPastLastAssignment.ts, 106, 5)) +>Map : Symbol(Map, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --) ... and 4 more) +>Map : Symbol(Map, Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.collection.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --)) + +const values = [1, 2, 3, 4, 5]; +>values : Symbol(values, Decl(narrowingPastLastAssignment.ts, 107, 5)) + +let foo = fooMap.get("a"); +>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 108, 3)) +>fooMap.get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) +>fooMap : Symbol(fooMap, Decl(narrowingPastLastAssignment.ts, 106, 5)) +>get : Symbol(Map.get, Decl(lib.es2015.collection.d.ts, --, --)) + +if (foo == null) { +>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 108, 3)) + + foo = []; +>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 108, 3)) +} +values.forEach(v => foo.push(v)); +>values.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>values : Symbol(values, Decl(narrowingPastLastAssignment.ts, 107, 5)) +>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --)) +>v : Symbol(v, Decl(narrowingPastLastAssignment.ts, 112, 15)) +>foo.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(narrowingPastLastAssignment.ts, 108, 3)) +>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --)) +>v : Symbol(v, Decl(narrowingPastLastAssignment.ts, 112, 15)) + diff --git a/tests/baselines/reference/narrowingPastLastAssignment.types b/tests/baselines/reference/narrowingPastLastAssignment.types new file mode 100644 index 0000000000000..99cde6a0fd2e2 --- /dev/null +++ b/tests/baselines/reference/narrowingPastLastAssignment.types @@ -0,0 +1,354 @@ +//// [tests/cases/compiler/narrowingPastLastAssignment.ts] //// + +=== narrowingPastLastAssignment.ts === +function action(f: Function) {} +>action : (f: Function) => void +>f : Function + +// Narrowings are preserved in closures created past last assignment + +function f1(x: string | number) { +>f1 : (x: string | number) => void +>x : string | number + + x = "abc"; +>x = "abc" : "abc" +>x : string | number +>"abc" : "abc" + + action(() => { x /* string | number */ }); +>action(() => { x /* string | number */ }) : void +>action : (f: Function) => void +>() => { x /* string | number */ } : () => void +>x : string | number + + x = 42; +>x = 42 : 42 +>x : string | number +>42 : 42 + + action(() => { x /* number */ }); +>action(() => { x /* number */ }) : void +>action : (f: Function) => void +>() => { x /* number */ } : () => void +>x : number +} + +// Narrowings are not preserved in inner function and class declarations (due to hoisting) + +function f2() { +>f2 : () => void + + let x: string | number; +>x : string | number + + x = 42; +>x = 42 : 42 +>x : string | number +>42 : 42 + + let a = () => { x /* number */ }; +>a : () => void +>() => { x /* number */ } : () => void +>x : number + + let f = function() { x /* number */ }; +>f : () => void +>function() { x /* number */ } : () => void +>x : number + + let C = class { +>C : typeof C +>class { foo() { x /* number */ } } : typeof C + + foo() { x /* number */ } +>foo : () => void +>x : number + + }; + let o = { +>o : { foo(): void; } +>{ foo() { x /* number */ } } : { foo(): void; } + + foo() { x /* number */ } +>foo : () => void +>x : number + + }; + function g() { x /* string | number */ } +>g : () => void +>x : string | number + + class A { +>A : A + + foo() { x /* string | number */ } +>foo : () => void +>x : string | number + } +} + +// Narrowings are not preserved when assignments occur in inner functions + +function f3(x: string | number) { +>f3 : (x: string | number) => void +>x : string | number + + action(() => { x = "abc" }); +>action(() => { x = "abc" }) : void +>action : (f: Function) => void +>() => { x = "abc" } : () => void +>x = "abc" : "abc" +>x : string | number +>"abc" : "abc" + + x = 42; +>x = 42 : 42 +>x : string | number +>42 : 42 + + action(() => { x /* string | number */ }); +>action(() => { x /* string | number */ }) : void +>action : (f: Function) => void +>() => { x /* string | number */ } : () => void +>x : string | number +} + +// Assignment effects in compoud statements extend to the entire statement + +function f4(cond: () => boolean) { +>f4 : (cond: () => boolean) => void +>cond : () => boolean + + let x: string | number = 0; +>x : string | number +>0 : 0 + + while (cond()) { +>cond() : boolean +>cond : () => boolean + + x = "abc"; +>x = "abc" : "abc" +>x : string | number +>"abc" : "abc" + + action(() => { x /* string | number */ }); +>action(() => { x /* string | number */ }) : void +>action : (f: Function) => void +>() => { x /* string | number */ } : () => void +>x : string | number + + x = 42; +>x = 42 : 42 +>x : string | number +>42 : 42 + + action(() => { x /* string | number */ }); +>action(() => { x /* string | number */ }) : void +>action : (f: Function) => void +>() => { x /* string | number */ } : () => void +>x : string | number + } + action(() => { x /* number */ }); +>action(() => { x /* number */ }) : void +>action : (f: Function) => void +>() => { x /* number */ } : () => void +>x : number +} + +function f5(x: string | number, cond: () => boolean) { +>f5 : (x: string | number, cond: () => boolean) => void +>x : string | number +>cond : () => boolean + + if (cond()) { +>cond() : boolean +>cond : () => boolean + + x = 1; +>x = 1 : 1 +>x : string | number +>1 : 1 + + action(() => { x /* string | number */ }); +>action(() => { x /* string | number */ }) : void +>action : (f: Function) => void +>() => { x /* string | number */ } : () => void +>x : string | number + } + else { + x = 2; +>x = 2 : 2 +>x : string | number +>2 : 2 + + action(() => { x /* string | number */ }); +>action(() => { x /* string | number */ }) : void +>action : (f: Function) => void +>() => { x /* string | number */ } : () => void +>x : string | number + } + action(() => { x /* number */ }); +>action(() => { x /* number */ }) : void +>action : (f: Function) => void +>() => { x /* number */ } : () => void +>x : number +} + +// Implicit any variables have a known type following last assignment + +function f6() { +>f6 : () => void + + let x; +>x : any + + x = "abc"; +>x = "abc" : "abc" +>x : any +>"abc" : "abc" + + action(() => { x }); // Error +>action(() => { x }) : void +>action : (f: Function) => void +>() => { x } : () => void +>x : any + + x = 42; +>x = 42 : 42 +>x : any +>42 : 42 + + action(() => { x /* number */ }); +>action(() => { x /* number */ }) : void +>action : (f: Function) => void +>() => { x /* number */ } : () => void +>x : number +} + +// Narrowings on catch variables are preserved past last assignment + +function f7() { +>f7 : () => void + + try { + } + catch (e) { +>e : unknown + + if (e instanceof Error) { +>e instanceof Error : boolean +>e : unknown +>Error : ErrorConstructor + + let f = () => { e /* Error */ } +>f : () => void +>() => { e /* Error */ } : () => void +>e : Error + } + } +} + +// Repros from #35124 + +function f10() { +>f10 : () => (k: number) => boolean + + let i: number | undefined; +>i : number | undefined + + i = 0; +>i = 0 : 0 +>i : number | undefined +>0 : 0 + + return (k: number) => k === i + 1; +>(k: number) => k === i + 1 : (k: number) => boolean +>k : number +>k === i + 1 : boolean +>k : number +>i + 1 : number +>i : number +>1 : 1 +} + +function makeAdder(n?: number) { +>makeAdder : (n?: number) => (m: number) => number +>n : number | undefined + + n ??= 0; +>n ??= 0 : number +>n : number | undefined +>0 : 0 + + return (m: number) => n + m; +>(m: number) => n + m : (m: number) => number +>m : number +>n + m : number +>n : number +>m : number +} + +function f11() { +>f11 : () => void + + let r; +>r : any + + r = "b"; +>r = "b" : "b" +>r : any +>"b" : "b" + + () => r; +>() => r : () => string +>r : string +} + +// Repro from #52104 + +const fooMap: Map> = new Map() +>fooMap : Map +>new Map() : Map +>Map : MapConstructor + +const values = [1, 2, 3, 4, 5]; +>values : number[] +>[1, 2, 3, 4, 5] : number[] +>1 : 1 +>2 : 2 +>3 : 3 +>4 : 4 +>5 : 5 + +let foo = fooMap.get("a"); +>foo : number[] | undefined +>fooMap.get("a") : number[] | undefined +>fooMap.get : (key: string) => number[] | undefined +>fooMap : Map +>get : (key: string) => number[] | undefined +>"a" : "a" + +if (foo == null) { +>foo == null : boolean +>foo : number[] | undefined + + foo = []; +>foo = [] : never[] +>foo : number[] | undefined +>[] : never[] +} +values.forEach(v => foo.push(v)); +>values.forEach(v => foo.push(v)) : void +>values.forEach : (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void +>values : number[] +>forEach : (callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any) => void +>v => foo.push(v) : (v: number) => number +>v : number +>foo.push(v) : number +>foo.push : (...items: number[]) => number +>foo : number[] +>push : (...items: number[]) => number +>v : number + diff --git a/tests/cases/compiler/narrowingPastLastAssignment.ts b/tests/cases/compiler/narrowingPastLastAssignment.ts new file mode 100644 index 0000000000000..ef10a3abafa2a --- /dev/null +++ b/tests/cases/compiler/narrowingPastLastAssignment.ts @@ -0,0 +1,117 @@ +// @strict: true +// @noEmit: true +// @target: esnext + +function action(f: Function) {} + +// Narrowings are preserved in closures created past last assignment + +function f1(x: string | number) { + x = "abc"; + action(() => { x /* string | number */ }); + x = 42; + action(() => { x /* number */ }); +} + +// Narrowings are not preserved in inner function and class declarations (due to hoisting) + +function f2() { + let x: string | number; + x = 42; + let a = () => { x /* number */ }; + let f = function() { x /* number */ }; + let C = class { + foo() { x /* number */ } + }; + let o = { + foo() { x /* number */ } + }; + function g() { x /* string | number */ } + class A { + foo() { x /* string | number */ } + } +} + +// Narrowings are not preserved when assignments occur in inner functions + +function f3(x: string | number) { + action(() => { x = "abc" }); + x = 42; + action(() => { x /* string | number */ }); +} + +// Assignment effects in compoud statements extend to the entire statement + +function f4(cond: () => boolean) { + let x: string | number = 0; + while (cond()) { + x = "abc"; + action(() => { x /* string | number */ }); + x = 42; + action(() => { x /* string | number */ }); + } + action(() => { x /* number */ }); +} + +function f5(x: string | number, cond: () => boolean) { + if (cond()) { + x = 1; + action(() => { x /* string | number */ }); + } + else { + x = 2; + action(() => { x /* string | number */ }); + } + action(() => { x /* number */ }); +} + +// Implicit any variables have a known type following last assignment + +function f6() { + let x; + x = "abc"; + action(() => { x }); // Error + x = 42; + action(() => { x /* number */ }); +} + +// Narrowings on catch variables are preserved past last assignment + +function f7() { + try { + } + catch (e) { + if (e instanceof Error) { + let f = () => { e /* Error */ } + } + } +} + +// Repros from #35124 + +function f10() { + let i: number | undefined; + i = 0; + return (k: number) => k === i + 1; +} + +function makeAdder(n?: number) { + n ??= 0; + return (m: number) => n + m; +} + +function f11() { + let r; + r = "b"; + () => r; +} + +// Repro from #52104 + +const fooMap: Map> = new Map() +const values = [1, 2, 3, 4, 5]; +let foo = fooMap.get("a"); +if (foo == null) { + foo = []; +} +values.forEach(v => foo.push(v));