Closed
Description
Bug Report
When modifying a variable with a type Union of objects, the inferred type should be the Union of the possible results.
🔎 Search Terms
- Union
- Narrow union
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about Union and narrow
⏯ Playground Link
Playground link with relevant code
💻 Code
type Source = {
type: 'ws'
reader: {state: 'connecting'}
} | {
type: 'file'
reader: {state: number}
}
let source: Source = {type: 'file', reader: {state: 12}}
/*
* Error: getSourceWithState return type is
* {
* type: "ws" | "file";
* state: "connecting" | number;
* }
* instead of
* {
* type: "file";
* state: number;
* } |
* {
* type: "ws";
* state: "connecting";
* }
*/
const getSourceWithState = () => {
return {
type: source.type,
state: source.reader.state
}
}
const sourceWithState = getSourceWithState()
if (sourceWithState.type === 'file') {
// Error
sourceWithState.state += 1
}
🙁 Actual behavior
The return type of getSourceWithState is
{
type: "ws" | "file";
state: "connecting" | number;
}
Then trying to narrow the type is impossible.
🙂 Expected behavior
The return type of getSourceWithState should be
{
type: "file";
state: number;
} |
{
type: "ws";
state: "connecting";
}
This would make it possible to narrow the type and is also the truth.
Activity
fatcerberus commentedon Apr 27, 2023
I think this falls under the same limitation I described here #54027 (comment) - the compiler doesn’t track where individual values come from (see #30581)
jcalz commentedon Apr 27, 2023
This feels a lot like #30581; TS doesn't do "distributive control flow analysis" (see #25051). Generally the refactoring needed to get behavior like this is to use generic indexes into mapped types as described in #47109. Doing that here gives me
Playground link
Which works but is much less ergonomic than just throwing a type assertion at the problem and moving on. I don't know if there will be anything better here.
yannbriancon commentedon Apr 27, 2023
Thanks for your replies @jcalz & @fatcerberus
@jcalz It is exactly what I do usually but I am tired of this complicated mapping and hoped something better would exist.
Hope someday this feature can be implemented.
fatcerberus commentedon Apr 27, 2023
@jcalz For what it's worth I'm not convinced going all-in on distributive CFA is strictly necessary - most of these cases feel like they could be solved by simply tracking the provenance of types. That would allow the compiler to recognize that
is an isomorphism regardless of the types involved, without having to distribute over any unions (except to construct the final type of the object literal, where it'd distribute over the type of
p
).