Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failure to refine tuple union return type when destructuring #32639

Closed
jaredh159 opened this issue Jul 31, 2019 · 2 comments
Closed

Failure to refine tuple union return type when destructuring #32639

jaredh159 opened this issue Jul 31, 2019 · 2 comments

Comments

@jaredh159
Copy link

TypeScript Version: 3.5.1

Search Terms:
destructure tuple destructuring

Code

function tuple(): [null, string] | [string, null] {
    if (Math.random() < 0.5) {
        return [null, 'a string'];
    }
    return ['a string', null];
}

const arr = tuple();

if (arr[0] === null) {
    arr[1].toUpperCase(); // <-- 👍 Works! TS knows arr[1] is a string
}

const [a, b] = tuple();

if (a === null) {
    b.toUpperCase(); // <-- 🚨 Error! TS doesn't know b is a string
}

Expected behavior:
TS can refine the destructured variables based on the function union return type.

Actual behavior:
Error.

It makes sense that when I do

const [a, b] = tuple();

That at that point, a and b need to both have an inferred type of null | string.

But when inside the conditional check if (a === null) I was expecting type refinement. Maybe that's an impossibility, but TS has gotten so smart I was genuinely surprised it wasn't able to refer that b was a string at that point.

Playground Link:
https://www.typescriptlang.org/play/#code/GYVwdgxgLglg9mABFEAHANgUwBQEoBciA2mCOugDSIDOUATjGAOYC6iAPsbQ81aeWwDeAKERjEMYImwBZAIZQAFgDo6csABM4AWzyIAPIgAMygKy5EI8dcR1MKOkhJlKiAORya9RkzcsA3KLiAL5BYnYOTh5ePL58LgHCocIQCLSIcnR0iAC8yGhYeIHCktKZdERGbDk1iPzoFlbi5UQAjCzKUHAAqqiomHQAwnLUOLiByalg6URyVABG1fkYY8Wl2J41efWNYYjznT19A8OjRUlAA

Related Issues:
maybe #28311 ?

@fatcerberus
Copy link

Duplicate of #12184, but just for full context:

Basically the type system doesn't track any dependencies between different variables, destructured or otherwise. When you write a type guard such as if (a === null), the compiler can only narrow the variable(s) directly involved in the guard expression, in this case a. It doesn't have any way of knowing internally what that narrowing implies for b.

It's for the same reason that the following doesn't work:

const isNull = arr[0] === null;
if (isNull) {
    b.toUpperCase(); // <-- error here
}

As far as I understand, tracking this kind of thing would represent a lot of added complexity in the control-flow analyzer, so TS doesn't even try to do it.

@jaredh159
Copy link
Author

Ok, thanks @fatcerberus for the explanation and the link. Makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants