Description
Bug Report
Applying the 'extract to constant' refactoring on an ?: operator can cause type narrowing to get lost, making subsequent calls in the true or false expressions of the ?: operator invalid from typescript's perspective, if the ?: operator is being used to detect null/undefined cases.
🔎 Search Terms
type narrowing, undefined, boolean, ? operator
See also #35260
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about "narrow"
⏯ Playground Link
Playground link with relevant code
💻 Code
export default function actOnDataOrUndefined(data?: string[]) {
// This is fine
const ok = data && data.length != 0 ? data.length : null;
// Refactor the condition to a local variable and typescript
// reports that data can be undefined in the true-case expression
// when it clearly(?) cannot be, since hasData is true only when data is not undefined.
const hasData = data && data.length != 0;
const not_ok = hasData ? data.length : null;
}
🙁 Actual behavior
In the 'ok' case, pre-refactoring, typescript compiles correctly since it determines that data is not undefined in the true expression. Selecting the boolean expression of the ?: operator, Visual Studio suggests that we can extract the expression into a local variable. Doing so, creates the 'not_ok' case, and then typescript believes "data" to be string[]|undefined
.
🙂 Expected behavior
The best case would be for typescript to correctly identify that in the true expression of the ?: operator type type of data
is only string[]
and not string[] | undefined
, since code analysis can prove that, when hasData
is true
, then data
is not undefined.
If, for some reason, that is not possible (or it is expensive), then perhaps the refactoring should not be available or it needs to suggest changes in other places in the code, since the expectation is that refactoring will keep resulting code working as before.
In my particular production code I simply "fixed" the issue by using data!
, but that still cost me a PR-build cycle since I didn't realize the mistake when I first did the refactoring (in my case, data
was being passed to a separate function instead of used directly, and vscode was not fast enough to show the error before the change was submitted as PR).