Repository: TypeScript
Issue: union of tuples + spread tricks control flow analysis into thinking a reachable branch is not reachable
Labels: Bug

# Bug Report

<!--
  Please fill in each section completely. Thank you!
-->

### 🔎 Search Terms

tuple spread control flow analysis type narrowing

<!--
  What search terms did you use when trying to find an existing bug report?
  List them here so people in the future can find this one more easily.
-->

### 🕗 Version & Regression Information

<!-- When did you start seeing this bug occur?

"Bugs" that have existed in TS for a long time are very likely to be FAQs; refer to
  https://github.com/Microsoft/TypeScript/wiki/FAQ#common-bugs-that-arent-bugs

If possible, please try testing the nightly version of TS to see if it's already been fixed.
For npm: `typescript@next`
This is also the 'Nightly' version in the playground: http://www.typescriptlang.org/play/?ts=Nightly

Note: The TypeScript Playground can be used to try older versions of TypeScript.

Please keep and fill in the line that best applies:
-->
- This exists since at least 4.0.5 and is still present in the current 4.4.0-beta.
- 3.9.7 correctly fails to compile the snippet, but for what appear to be different reasons.

### ⏯ Playground Link


```ts
let current:
  | []
  | [string]
  | [string, string] = [];

const values = ['foo', 'bar', 'baz'];

for (const v of values) {
  if (current.length === 0) {
    current = ['first'];
  } else if (current.length === 1) {
    current; // -> correctly narrows type to [string]
    current = [...current, 'second'];
    current; // -> corrently infers type [string, string]

    // if, instead, we construct the tuple without spreads, the compiler correctly realizes the case below is reachable
    // current = [current[0], 'second'];
  } else {
    assertNever(current); // this should not compile; because this case is actually reachable
    console.log('here!'); // this line is hit, as expected
  }
}

function assertNever(condition: never) {

}
```



<details><summary><b>Output</b></summary>

```ts
"use strict";
let current = [];
const values = ['foo', 'bar', 'baz'];
for (const v of values) {
    if (current.length === 0) {
        current = ['first'];
    }
    else if (current.length === 1) {
        current; // -> correctly narrows type to [string]
        current = [...current, 'second'];
        current; // -> corrently infers type [string, string]
        // if, instead, we construct the tuple without spreads, the compiler correctly realizes the case below is reachable
        // current = [current[0], 'second'];
    }
    else {
        assertNever(current); // this should not compile; because this case is actually reachable
        console.log('here!'); // this line is hit, as expected
    }
}
function assertNever(condition) {
}

```


</details>


<details><summary><b>Compiler Options</b></summary>

```json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "jsx": "react",
    "module": "ESNext",
    "moduleResolution": "node"
  }
}
```


</details>

**Playground Link:** [Provided](https://www.typescriptlang.org/play?ts=4.3.5#code/DYUwLgBAxgrgTnEA7MAuAUBCAfCBtAXUx3wGcw4BLJAcyK1z3KtoBoJnq6IBefAgNzp0UAPZJyEAG4BDYDBCle+AOQAzUaJXsVAIxlxtEPTIBeKwcI1wIACjETIUiKLXS5C0gEoIAb2KUbvbwiCgAdKC0YAAWvDx8AAw+-lhYsAjIkHx46pRw5BZCWAC+ECDApCAQgXbpoWARyDQxcXwAjMnEaSGZAhAA9P0QALQAfNCiGVBgwACeEEgGcKIA7kpgswAOVWCiZBRc9Kl1mcp4YRcnKDqVDgAmhV3QPSh9gyPjYhkoc9VIaiB8hANtt9iwaOxOLQiE93oF2NRyCAZHd2Csqg5mDBpsDojsYJtQBAVpQYqIYJBSJtECjSOwYhjRABbTaUUA2L6Iaa-GnASimRS4jEySoQXTlVbVJQ0qDRGS6UCwoZXLL4FV4BIEG4ge6PEplCpVFKpEWVOBgAByICkgOC3zAXjeQxilCUpGi5OAdwWokgYhZbJAfXFUBkMFFLqUodFrogMmmMDkPORsvlitSEwkolAEVENFsKjxiAAhCpHQNndFY3ykFVY1WwOwRWUAB7baYgO7EYroHvoNQwJDTSjiOOkM2W622+6kkdIVALKdwTq9oA)
      
      

### 🙁 Actual behavior

The compiler erroneously claims the last case where `current` is length 2 cannot be reached (as represented by `assertNever` compiling without error) when `current` is reassigned with tuple spread. Furthermore, the compiler correctly identifies that `current` is a 2-tuple after the spread statement. The declared types also specify that `current` can be a 2-tuple, which leads me to believe it's the control flow analyzer getting fooled into narrowing the types too far.

The compiler correctly claims the last case _can_ be reached when `current` is reassigned with each tuple member explicitly stated, which is equivalent to the spread in this case.

### 🙂 Expected behavior

The usage of spread/explicitly specifying tuple members should both fail to compile.