diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 52f99bcb8d7..6ffe3a7ef52 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -850,6 +850,9 @@ namespace ts { case SyntaxKind.BindingElement: bindBindingElementFlow(node as BindingElement); break; + case SyntaxKind.Parameter: + bindParameterFlow(node as ParameterDeclaration); + break; case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.PropertyAssignment: @@ -1655,20 +1658,40 @@ namespace ts { } function bindBindingElementFlow(node: BindingElement) { - if (isBindingPattern(node.name)) { - // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: - // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization - // - `BindingElement: BindingPattern Initializer?` - // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization - // - `BindingElement: BindingPattern Initializer?` - bind(node.dotDotDotToken); - bind(node.propertyName); - bind(node.initializer); - bind(node.name); + // When evaluating a binding pattern, the initializer is evaluated before the binding pattern, per: + // - https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + // - https://tc39.es/ecma262/#sec-runtime-semantics-keyedbindinginitialization + // - `BindingElement: BindingPattern Initializer?` + bind(node.dotDotDotToken); + bind(node.propertyName); + bindInitializer(node.initializer); + bind(node.name); + } + + function bindParameterFlow(node: ParameterDeclaration) { + bindEach(node.modifiers); + bind(node.dotDotDotToken); + bind(node.questionToken); + bind(node.type); + bindInitializer(node.initializer); + bind(node.name); + } + + // a BindingElement/Parameter does not have side effects if initializers are not evaluated and used. (see GH#49759) + function bindInitializer(node: Expression | undefined) { + if (!node) { + return; } - else { - bindEachChild(node); + const entryFlow = currentFlow; + bind(node); + if (entryFlow === unreachableFlow || entryFlow === currentFlow) { + return; } + const exitFlow = createBranchLabel(); + addAntecedent(exitFlow, entryFlow); + addAntecedent(exitFlow, currentFlow); + currentFlow = finishFlowLabel(exitFlow); } function bindJSDocTypeAlias(node: JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag) { diff --git a/tests/baselines/reference/controlFlowBindingElement.js b/tests/baselines/reference/controlFlowBindingElement.js new file mode 100644 index 00000000000..e08cb83372f --- /dev/null +++ b/tests/baselines/reference/controlFlowBindingElement.js @@ -0,0 +1,136 @@ +//// [controlFlowBindingElement.ts] +{ + const data = { param: 'value' }; + + const { + param = (() => { throw new Error('param is not defined') })(), + } = data; + + console.log(param); // should not trigger 'Unreachable code detected.' +} + + +{ + const data = { param: 'value' }; + + let foo: string | undefined = ""; + const { + param = (() => { throw new Error('param is not defined') })(), + } = data; + + foo; // should be string +} + +{ + const data = { param: 'value' }; + + let foo: string | undefined = ""; + const { + param = (() => { foo = undefined })(), + } = data; + + foo; // should be string | undefined +} + +{ + const data = { param: 'value' }; + + let foo: string | undefined = ""; + const { + param = (() => { return "" + 1 })(), + } = data; + + foo; // should be string +} + +{ + interface Window { + window: Window; + } + + let foo: string | undefined; + let window = {} as Window; + window.window = window; + + const { [(() => { foo = ""; return 'window' as const })()]: + { [(() => { return 'window' as const })()]: bar } } = window; + + foo; // should be string +} + +{ + interface Window { + window: Window; + } + + let foo: string | undefined; + let window = {} as Window; + window.window = window; + + const { [(() => { return 'window' as const })()]: + { [(() => { foo = ""; return 'window' as const })()]: bar } } = window; + + foo; // should be string +} + +{ + interface Window { + window: Window; + } + + let foo: string | undefined; + let window = {} as Window; + window.window = window; + + const { [(() => { return 'window' as const })()]: + { [(() => { return 'window' as const })()]: bar = (() => { foo = ""; return window; })() } } = window; + + foo; // should be string | undefined +} + + +//// [controlFlowBindingElement.js] +{ + var data = { param: 'value' }; + var _a = data.param, param = _a === void 0 ? (function () { throw new Error('param is not defined'); })() : _a; + console.log(param); // should not trigger 'Unreachable code detected.' +} +{ + var data = { param: 'value' }; + var foo = ""; + var _b = data.param, param = _b === void 0 ? (function () { throw new Error('param is not defined'); })() : _b; + foo; // should be string +} +{ + var data = { param: 'value' }; + var foo_1 = ""; + var _c = data.param, param = _c === void 0 ? (function () { foo_1 = undefined; })() : _c; + foo_1; // should be string | undefined +} +{ + var data = { param: 'value' }; + var foo = ""; + var _d = data.param, param = _d === void 0 ? (function () { return "" + 1; })() : _d; + foo; // should be string +} +{ + var foo_2; + var window_1 = {}; + window_1.window = window_1; + var _e = window_1, _f = (function () { foo_2 = ""; return 'window'; })(), _g = (function () { return 'window'; })(), bar = _e[_f][_g]; + foo_2; // should be string +} +{ + var foo_3; + var window_2 = {}; + window_2.window = window_2; + var _h = window_2, _j = (function () { return 'window'; })(), _k = (function () { foo_3 = ""; return 'window'; })(), bar = _h[_j][_k]; + foo_3; // should be string +} +{ + var foo_4; + var window_3 = {}; + window_3.window = window_3; + var _l = window_3, _m = (function () { return 'window'; })(), _o = (function () { return 'window'; })(), _p = _l[_m][_o], bar = _p === void 0 ? (function () { foo_4 = ""; return window_3; })() : _p; + foo_4; // should be string | undefined +} diff --git a/tests/baselines/reference/controlFlowBindingElement.symbols b/tests/baselines/reference/controlFlowBindingElement.symbols new file mode 100644 index 00000000000..683a8e2c7be --- /dev/null +++ b/tests/baselines/reference/controlFlowBindingElement.symbols @@ -0,0 +1,188 @@ +=== tests/cases/conformance/controlFlow/controlFlowBindingElement.ts === +{ + const data = { param: 'value' }; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 1, 9)) +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 1, 19)) + + const { + param = (() => { throw new Error('param is not defined') })(), +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 3, 11)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + } = data; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 1, 9)) + + console.log(param); // should not trigger 'Unreachable code detected.' +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 3, 11)) +} + + +{ + const data = { param: 'value' }; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 12, 9)) +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 12, 19)) + + let foo: string | undefined = ""; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 14, 7)) + + const { + param = (() => { throw new Error('param is not defined') })(), +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 15, 11)) +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + } = data; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 12, 9)) + + foo; // should be string +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 14, 7)) +} + +{ + const data = { param: 'value' }; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 23, 9)) +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 23, 19)) + + let foo: string | undefined = ""; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 25, 7)) + + const { + param = (() => { foo = undefined })(), +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 26, 11)) +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 25, 7)) +>undefined : Symbol(undefined) + + } = data; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 23, 9)) + + foo; // should be string | undefined +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 25, 7)) +} + +{ + const data = { param: 'value' }; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 34, 9)) +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 34, 19)) + + let foo: string | undefined = ""; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 36, 7)) + + const { + param = (() => { return "" + 1 })(), +>param : Symbol(param, Decl(controlFlowBindingElement.ts, 37, 11)) + + } = data; +>data : Symbol(data, Decl(controlFlowBindingElement.ts, 34, 9)) + + foo; // should be string +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 36, 7)) +} + +{ + interface Window { +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 44, 1)) + + window: Window; +>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 45, 22)) +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 44, 1)) + } + + let foo: string | undefined; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 49, 7)) + + let window = {} as Window; +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7)) +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 44, 1)) + + window.window = window; +>window.window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 45, 22)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7)) +>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 45, 22)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7)) + + const { [(() => { foo = ""; return 'window' as const })()]: +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 49, 7)) +>const : Symbol(const) + + { [(() => { return 'window' as const })()]: bar } } = window; +>const : Symbol(const) +>bar : Symbol(bar, Decl(controlFlowBindingElement.ts, 54, 9)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 50, 7)) + + foo; // should be string +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 49, 7)) +} + +{ + interface Window { +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 59, 1)) + + window: Window; +>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 60, 22)) +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 59, 1)) + } + + let foo: string | undefined; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 64, 7)) + + let window = {} as Window; +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7)) +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 59, 1)) + + window.window = window; +>window.window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 60, 22)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7)) +>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 60, 22)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7)) + + const { [(() => { return 'window' as const })()]: +>const : Symbol(const) + + { [(() => { foo = ""; return 'window' as const })()]: bar } } = window; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 64, 7)) +>const : Symbol(const) +>bar : Symbol(bar, Decl(controlFlowBindingElement.ts, 69, 9)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 65, 7)) + + foo; // should be string +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 64, 7)) +} + +{ + interface Window { +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 74, 1)) + + window: Window; +>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 75, 22)) +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 74, 1)) + } + + let foo: string | undefined; +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 79, 7)) + + let window = {} as Window; +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7)) +>Window : Symbol(Window, Decl(controlFlowBindingElement.ts, 74, 1)) + + window.window = window; +>window.window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 75, 22)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7)) +>window : Symbol(Window.window, Decl(controlFlowBindingElement.ts, 75, 22)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7)) + + const { [(() => { return 'window' as const })()]: +>const : Symbol(const) + + { [(() => { return 'window' as const })()]: bar = (() => { foo = ""; return window; })() } } = window; +>const : Symbol(const) +>bar : Symbol(bar, Decl(controlFlowBindingElement.ts, 84, 9)) +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 79, 7)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7)) +>window : Symbol(window, Decl(controlFlowBindingElement.ts, 80, 7)) + + foo; // should be string | undefined +>foo : Symbol(foo, Decl(controlFlowBindingElement.ts, 79, 7)) +} + diff --git a/tests/baselines/reference/controlFlowBindingElement.types b/tests/baselines/reference/controlFlowBindingElement.types new file mode 100644 index 00000000000..8c9c6ac79a5 --- /dev/null +++ b/tests/baselines/reference/controlFlowBindingElement.types @@ -0,0 +1,250 @@ +=== tests/cases/conformance/controlFlow/controlFlowBindingElement.ts === +{ + const data = { param: 'value' }; +>data : { param: string; } +>{ param: 'value' } : { param: string; } +>param : string +>'value' : "value" + + const { + param = (() => { throw new Error('param is not defined') })(), +>param : string +>(() => { throw new Error('param is not defined') })() : never +>(() => { throw new Error('param is not defined') }) : () => never +>() => { throw new Error('param is not defined') } : () => never +>new Error('param is not defined') : Error +>Error : ErrorConstructor +>'param is not defined' : "param is not defined" + + } = data; +>data : { param: string; } + + console.log(param); // should not trigger 'Unreachable code detected.' +>console.log(param) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>param : string +} + + +{ + const data = { param: 'value' }; +>data : { param: string; } +>{ param: 'value' } : { param: string; } +>param : string +>'value' : "value" + + let foo: string | undefined = ""; +>foo : string | undefined +>"" : "" + + const { + param = (() => { throw new Error('param is not defined') })(), +>param : string +>(() => { throw new Error('param is not defined') })() : never +>(() => { throw new Error('param is not defined') }) : () => never +>() => { throw new Error('param is not defined') } : () => never +>new Error('param is not defined') : Error +>Error : ErrorConstructor +>'param is not defined' : "param is not defined" + + } = data; +>data : { param: string; } + + foo; // should be string +>foo : string +} + +{ + const data = { param: 'value' }; +>data : { param: string; } +>{ param: 'value' } : { param: string; } +>param : string +>'value' : "value" + + let foo: string | undefined = ""; +>foo : string | undefined +>"" : "" + + const { + param = (() => { foo = undefined })(), +>param : string | void +>(() => { foo = undefined })() : void +>(() => { foo = undefined }) : () => void +>() => { foo = undefined } : () => void +>foo = undefined : undefined +>foo : string | undefined +>undefined : undefined + + } = data; +>data : { param: string; } + + foo; // should be string | undefined +>foo : string | undefined +} + +{ + const data = { param: 'value' }; +>data : { param: string; } +>{ param: 'value' } : { param: string; } +>param : string +>'value' : "value" + + let foo: string | undefined = ""; +>foo : string | undefined +>"" : "" + + const { + param = (() => { return "" + 1 })(), +>param : string +>(() => { return "" + 1 })() : string +>(() => { return "" + 1 }) : () => string +>() => { return "" + 1 } : () => string +>"" + 1 : string +>"" : "" +>1 : 1 + + } = data; +>data : { param: string; } + + foo; // should be string +>foo : string +} + +{ + interface Window { + window: Window; +>window : Window + } + + let foo: string | undefined; +>foo : string | undefined + + let window = {} as Window; +>window : Window +>{} as Window : Window +>{} : {} + + window.window = window; +>window.window = window : Window +>window.window : Window +>window : Window +>window : Window +>window : Window + + const { [(() => { foo = ""; return 'window' as const })()]: +>(() => { foo = ""; return 'window' as const })() : "window" +>(() => { foo = ""; return 'window' as const }) : () => "window" +>() => { foo = ""; return 'window' as const } : () => "window" +>foo = "" : "" +>foo : string | undefined +>"" : "" +>'window' as const : "window" +>'window' : "window" + + { [(() => { return 'window' as const })()]: bar } } = window; +>(() => { return 'window' as const })() : "window" +>(() => { return 'window' as const }) : () => "window" +>() => { return 'window' as const } : () => "window" +>'window' as const : "window" +>'window' : "window" +>bar : Window +>window : Window + + foo; // should be string +>foo : string +} + +{ + interface Window { + window: Window; +>window : Window + } + + let foo: string | undefined; +>foo : string | undefined + + let window = {} as Window; +>window : Window +>{} as Window : Window +>{} : {} + + window.window = window; +>window.window = window : Window +>window.window : Window +>window : Window +>window : Window +>window : Window + + const { [(() => { return 'window' as const })()]: +>(() => { return 'window' as const })() : "window" +>(() => { return 'window' as const }) : () => "window" +>() => { return 'window' as const } : () => "window" +>'window' as const : "window" +>'window' : "window" + + { [(() => { foo = ""; return 'window' as const })()]: bar } } = window; +>(() => { foo = ""; return 'window' as const })() : "window" +>(() => { foo = ""; return 'window' as const }) : () => "window" +>() => { foo = ""; return 'window' as const } : () => "window" +>foo = "" : "" +>foo : string | undefined +>"" : "" +>'window' as const : "window" +>'window' : "window" +>bar : Window +>window : Window + + foo; // should be string +>foo : string +} + +{ + interface Window { + window: Window; +>window : Window + } + + let foo: string | undefined; +>foo : string | undefined + + let window = {} as Window; +>window : Window +>{} as Window : Window +>{} : {} + + window.window = window; +>window.window = window : Window +>window.window : Window +>window : Window +>window : Window +>window : Window + + const { [(() => { return 'window' as const })()]: +>(() => { return 'window' as const })() : "window" +>(() => { return 'window' as const }) : () => "window" +>() => { return 'window' as const } : () => "window" +>'window' as const : "window" +>'window' : "window" + + { [(() => { return 'window' as const })()]: bar = (() => { foo = ""; return window; })() } } = window; +>(() => { return 'window' as const })() : "window" +>(() => { return 'window' as const }) : () => "window" +>() => { return 'window' as const } : () => "window" +>'window' as const : "window" +>'window' : "window" +>bar : Window +>(() => { foo = ""; return window; })() : Window +>(() => { foo = ""; return window; }) : () => Window +>() => { foo = ""; return window; } : () => Window +>foo = "" : "" +>foo : string | undefined +>"" : "" +>window : Window +>window : Window + + foo; // should be string | undefined +>foo : string | undefined +} + diff --git a/tests/baselines/reference/controlFlowBindingPatternOrder.errors.txt b/tests/baselines/reference/controlFlowBindingPatternOrder.errors.txt new file mode 100644 index 00000000000..37b608c3071 --- /dev/null +++ b/tests/baselines/reference/controlFlowBindingPatternOrder.errors.txt @@ -0,0 +1,39 @@ +tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts(10,11): error TS2322: Type '0 | 9' is not assignable to type '9'. + Type '0' is not assignable to type '9'. +tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts(25,11): error TS2322: Type '0 | 9 | 8' is not assignable to type '0 | 8'. + Type '9' is not assignable to type '0 | 8'. + + +==== tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts (2 errors) ==== + // https://github.com/microsoft/TypeScript/pull/41094#issuecomment-716044363 + { + let a: 0 | 1 = 0; + const [{ [(a = 1)]: b } = [9, a] as const] = []; + const bb: 0 = b; + } + { + let a: 0 | 1 = 1; + const [{ [a]: b } = [9, a = 0] as const] = []; + const bb: 9 = b; + ~~ +!!! error TS2322: Type '0 | 9' is not assignable to type '9'. +!!! error TS2322: Type '0' is not assignable to type '9'. + } + { + let a: 0 | 1 | 2 = 1; + const [{ [a]: b } = [9, a = 0, 5] as const] = []; + const bb: 0 | 9 = b; + } + { + let a: 0 | 1 = 0; + const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; + const bb: 0 | 8 = b; + } + { + let a: 0 | 1 = 1; + const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; + const bb: 0 | 8 = b; + ~~ +!!! error TS2322: Type '0 | 9 | 8' is not assignable to type '0 | 8'. +!!! error TS2322: Type '9' is not assignable to type '0 | 8'. + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowBindingPatternOrder.symbols b/tests/baselines/reference/controlFlowBindingPatternOrder.symbols index fea01b4d125..7791d4540e0 100644 --- a/tests/baselines/reference/controlFlowBindingPatternOrder.symbols +++ b/tests/baselines/reference/controlFlowBindingPatternOrder.symbols @@ -29,25 +29,24 @@ >b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 8, 12)) } { - let a: 0 | 1 = 0; + let a: 0 | 1 | 2 = 1; >a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7)) - const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; + const [{ [a]: b } = [9, a = 0, 5] as const] = []; >a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7)) >b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 13, 12)) >a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 12, 7)) ->const : Symbol(const) >const : Symbol(const) - const bb: 0 | 8 = b; + const bb: 0 | 9 = b; >bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 14, 9)) >b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 13, 12)) } { - let a: 0 | 1 = 1; + let a: 0 | 1 = 0; >a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7)) - const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; + const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; >a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7)) >b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 18, 12)) >a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 17, 7)) @@ -58,3 +57,18 @@ >bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 19, 9)) >b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 18, 12)) } +{ + let a: 0 | 1 = 1; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 22, 7)) + + const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 22, 7)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 23, 12)) +>a : Symbol(a, Decl(controlFlowBindingPatternOrder.ts, 22, 7)) +>const : Symbol(const) +>const : Symbol(const) + + const bb: 0 | 8 = b; +>bb : Symbol(bb, Decl(controlFlowBindingPatternOrder.ts, 24, 9)) +>b : Symbol(b, Decl(controlFlowBindingPatternOrder.ts, 23, 12)) +} diff --git a/tests/baselines/reference/controlFlowBindingPatternOrder.types b/tests/baselines/reference/controlFlowBindingPatternOrder.types index 5c9d426c23b..dd7e782885b 100644 --- a/tests/baselines/reference/controlFlowBindingPatternOrder.types +++ b/tests/baselines/reference/controlFlowBindingPatternOrder.types @@ -27,8 +27,8 @@ >1 : 1 const [{ [a]: b } = [9, a = 0] as const] = []; ->a : 0 ->b : 9 +>a : 0 | 1 +>b : 0 | 9 >[9, a = 0] as const : readonly [9, 0] >[9, a = 0] : readonly [9, 0] >9 : 9 @@ -39,7 +39,28 @@ const bb: 9 = b; >bb : 9 ->b : 9 +>b : 0 | 9 +} +{ + let a: 0 | 1 | 2 = 1; +>a : 0 | 1 | 2 +>1 : 1 + + const [{ [a]: b } = [9, a = 0, 5] as const] = []; +>a : 0 | 1 +>b : 0 | 9 +>[9, a = 0, 5] as const : readonly [9, 0, 5] +>[9, a = 0, 5] : readonly [9, 0, 5] +>9 : 9 +>a = 0 : 0 +>a : 0 | 1 | 2 +>0 : 0 +>5 : 5 +>[] : [] + + const bb: 0 | 9 = b; +>bb : 0 | 9 +>b : 0 | 9 } { let a: 0 | 1 = 0; @@ -72,8 +93,8 @@ >1 : 1 const [{ [a]: b } = [a = 0, 9] as const] = [[8, 9] as const]; ->a : 0 ->b : 0 | 8 +>a : 0 | 1 +>b : 0 | 9 | 8 >[a = 0, 9] as const : readonly [0, 9] >[a = 0, 9] : readonly [0, 9] >a = 0 : 0 @@ -88,5 +109,5 @@ const bb: 0 | 8 = b; >bb : 0 | 8 ->b : 0 | 8 +>b : 0 | 9 | 8 } diff --git a/tests/baselines/reference/controlFlowParameter.js b/tests/baselines/reference/controlFlowParameter.js new file mode 100644 index 00000000000..283061f6650 --- /dev/null +++ b/tests/baselines/reference/controlFlowParameter.js @@ -0,0 +1,60 @@ +//// [controlFlowParameter.ts] +function f1( + required: unknown = (() => { + throw new Error("bad"); + })() +) { + console.log("ok"); // should not trigger 'Unreachable code detected.' +} + +function f2( + a: number | string | undefined, + required: unknown = (() => { + a = 1; + })() +) { + a; // should be number | string | undefined +} + +function f3( + a: number | string | undefined = 1, + required: unknown = (() => { + a = ""; + })() +) { + a; // should be number | string +} + +function f4( + a: number | string | undefined = 1, + { [(a = "")]: b } = {} as any +) { + a; // should be string +} + + +//// [controlFlowParameter.js] +function f1(required) { + if (required === void 0) { required = (function () { + throw new Error("bad"); + })(); } + console.log("ok"); // should not trigger 'Unreachable code detected.' +} +function f2(a, required) { + if (required === void 0) { required = (function () { + a = 1; + })(); } + a; // should be number | string | undefined +} +function f3(a, required) { + if (a === void 0) { a = 1; } + if (required === void 0) { required = (function () { + a = ""; + })(); } + a; // should be number | string +} +function f4(a, _a) { + if (a === void 0) { a = 1; } + var _b = _a === void 0 ? {} : _a, _c = (a = ""), b = _b[_c]; + a; // should be string +} diff --git a/tests/baselines/reference/controlFlowParameter.symbols b/tests/baselines/reference/controlFlowParameter.symbols new file mode 100644 index 00000000000..b2f58685a0c --- /dev/null +++ b/tests/baselines/reference/controlFlowParameter.symbols @@ -0,0 +1,69 @@ +=== tests/cases/conformance/controlFlow/controlFlowParameter.ts === +function f1( +>f1 : Symbol(f1, Decl(controlFlowParameter.ts, 0, 0)) + + required: unknown = (() => { +>required : Symbol(required, Decl(controlFlowParameter.ts, 0, 12)) + + throw new Error("bad"); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + })() +) { + console.log("ok"); // should not trigger 'Unreachable code detected.' +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + +function f2( +>f2 : Symbol(f2, Decl(controlFlowParameter.ts, 6, 1)) + + a: number | string | undefined, +>a : Symbol(a, Decl(controlFlowParameter.ts, 8, 12)) + + required: unknown = (() => { +>required : Symbol(required, Decl(controlFlowParameter.ts, 9, 33)) + + a = 1; +>a : Symbol(a, Decl(controlFlowParameter.ts, 8, 12)) + + })() +) { + a; // should be number | string | undefined +>a : Symbol(a, Decl(controlFlowParameter.ts, 8, 12)) +} + +function f3( +>f3 : Symbol(f3, Decl(controlFlowParameter.ts, 15, 1)) + + a: number | string | undefined = 1, +>a : Symbol(a, Decl(controlFlowParameter.ts, 17, 12)) + + required: unknown = (() => { +>required : Symbol(required, Decl(controlFlowParameter.ts, 18, 37)) + + a = ""; +>a : Symbol(a, Decl(controlFlowParameter.ts, 17, 12)) + + })() +) { + a; // should be number | string +>a : Symbol(a, Decl(controlFlowParameter.ts, 17, 12)) +} + +function f4( +>f4 : Symbol(f4, Decl(controlFlowParameter.ts, 24, 1)) + + a: number | string | undefined = 1, +>a : Symbol(a, Decl(controlFlowParameter.ts, 26, 12)) + + { [(a = "")]: b } = {} as any +>a : Symbol(a, Decl(controlFlowParameter.ts, 26, 12)) +>b : Symbol(b, Decl(controlFlowParameter.ts, 28, 3)) + +) { + a; // should be string +>a : Symbol(a, Decl(controlFlowParameter.ts, 26, 12)) +} + diff --git a/tests/baselines/reference/controlFlowParameter.types b/tests/baselines/reference/controlFlowParameter.types new file mode 100644 index 00000000000..0a77f532f1b --- /dev/null +++ b/tests/baselines/reference/controlFlowParameter.types @@ -0,0 +1,93 @@ +=== tests/cases/conformance/controlFlow/controlFlowParameter.ts === +function f1( +>f1 : (required?: unknown) => void + + required: unknown = (() => { +>required : unknown +>(() => { throw new Error("bad"); })() : never +>(() => { throw new Error("bad"); }) : () => never +>() => { throw new Error("bad"); } : () => never + + throw new Error("bad"); +>new Error("bad") : Error +>Error : ErrorConstructor +>"bad" : "bad" + + })() +) { + console.log("ok"); // should not trigger 'Unreachable code detected.' +>console.log("ok") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"ok" : "ok" +} + +function f2( +>f2 : (a: number | string | undefined, required?: unknown) => void + + a: number | string | undefined, +>a : string | number | undefined + + required: unknown = (() => { +>required : unknown +>(() => { a = 1; })() : void +>(() => { a = 1; }) : () => void +>() => { a = 1; } : () => void + + a = 1; +>a = 1 : 1 +>a : string | number | undefined +>1 : 1 + + })() +) { + a; // should be number | string | undefined +>a : string | number | undefined +} + +function f3( +>f3 : (a?: number | string | undefined, required?: unknown) => void + + a: number | string | undefined = 1, +>a : string | number | undefined +>1 : 1 + + required: unknown = (() => { +>required : unknown +>(() => { a = ""; })() : void +>(() => { a = ""; }) : () => void +>() => { a = ""; } : () => void + + a = ""; +>a = "" : "" +>a : string | number | undefined +>"" : "" + + })() +) { + a; // should be number | string +>a : string | number +} + +function f4( +>f4 : (a?: number | string | undefined, { [(a = "")]: b }?: any) => void + + a: number | string | undefined = 1, +>a : string | number | undefined +>1 : 1 + + { [(a = "")]: b } = {} as any +>(a = "") : "" +>a = "" : "" +>a : string | number | undefined +>"" : "" +>b : any +>{} as any : any +>{} : {} + +) { + a; // should be string +>a : string +} + diff --git a/tests/cases/conformance/controlFlow/controlFlowBindingElement.ts b/tests/cases/conformance/controlFlow/controlFlowBindingElement.ts new file mode 100644 index 00000000000..153666f9433 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowBindingElement.ts @@ -0,0 +1,90 @@ +// @strictNullChecks: true +// @allowUnreachableCode: false +{ + const data = { param: 'value' }; + + const { + param = (() => { throw new Error('param is not defined') })(), + } = data; + + console.log(param); // should not trigger 'Unreachable code detected.' +} + + +{ + const data = { param: 'value' }; + + let foo: string | undefined = ""; + const { + param = (() => { throw new Error('param is not defined') })(), + } = data; + + foo; // should be string +} + +{ + const data = { param: 'value' }; + + let foo: string | undefined = ""; + const { + param = (() => { foo = undefined })(), + } = data; + + foo; // should be string | undefined +} + +{ + const data = { param: 'value' }; + + let foo: string | undefined = ""; + const { + param = (() => { return "" + 1 })(), + } = data; + + foo; // should be string +} + +{ + interface Window { + window: Window; + } + + let foo: string | undefined; + let window = {} as Window; + window.window = window; + + const { [(() => { foo = ""; return 'window' as const })()]: + { [(() => { return 'window' as const })()]: bar } } = window; + + foo; // should be string +} + +{ + interface Window { + window: Window; + } + + let foo: string | undefined; + let window = {} as Window; + window.window = window; + + const { [(() => { return 'window' as const })()]: + { [(() => { foo = ""; return 'window' as const })()]: bar } } = window; + + foo; // should be string +} + +{ + interface Window { + window: Window; + } + + let foo: string | undefined; + let window = {} as Window; + window.window = window; + + const { [(() => { return 'window' as const })()]: + { [(() => { return 'window' as const })()]: bar = (() => { foo = ""; return window; })() } } = window; + + foo; // should be string | undefined +} diff --git a/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts b/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts index 804947c9b70..9bacc6b7eb4 100644 --- a/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts +++ b/tests/cases/conformance/controlFlow/controlFlowBindingPatternOrder.ts @@ -12,6 +12,11 @@ const [{ [a]: b } = [9, a = 0] as const] = []; const bb: 9 = b; } +{ + let a: 0 | 1 | 2 = 1; + const [{ [a]: b } = [9, a = 0, 5] as const] = []; + const bb: 0 | 9 = b; +} { let a: 0 | 1 = 0; const [{ [(a = 1)]: b } = [9, a] as const] = [[9, 8] as const]; diff --git a/tests/cases/conformance/controlFlow/controlFlowParameter.ts b/tests/cases/conformance/controlFlow/controlFlowParameter.ts new file mode 100644 index 00000000000..09972b5f6e0 --- /dev/null +++ b/tests/cases/conformance/controlFlow/controlFlowParameter.ts @@ -0,0 +1,34 @@ +// @strictNullChecks: true +// @allowUnreachableCode: false +function f1( + required: unknown = (() => { + throw new Error("bad"); + })() +) { + console.log("ok"); // should not trigger 'Unreachable code detected.' +} + +function f2( + a: number | string | undefined, + required: unknown = (() => { + a = 1; + })() +) { + a; // should be number | string | undefined +} + +function f3( + a: number | string | undefined = 1, + required: unknown = (() => { + a = ""; + })() +) { + a; // should be number | string +} + +function f4( + a: number | string | undefined = 1, + { [(a = "")]: b } = {} as any +) { + a; // should be string +}