Hey! I'm trying to work with some intersection types and using ES object spread syntax to update my data records. I'm not seeing the behaviour I would expect however.
In the following code I am seeing errors when I would expect it to work fine.
type Tri = boolean | null;
type Record = {
id?: string,
type: string
}
type Project = {
type: 'project',
accepted?: Tri
} & Record;
type PendingProject = {accepted: null} & Project;
type AcceptedProject = {accepted: true} & Project;
type RejectedProject = {accepted: false} & Project;
const pro: PendingProject = {
type: 'project',
id: '1',
accepted: null
}
function accept(proj: PendingProject): AcceptedProject {
return {...proj, accepted: true};
}
The errors I see are:
test.js:8
8: type Project = {
^ property `type`. Property not found in
24: return {...proj, accepted: true};
^^^^^^^^^^^^^^^^^^^^^^^^^ object literal
test.js:11
11: } & Record;
^^^^^^ property `type`. Property not found in
24: return {...proj, accepted: true};
^^^^^^^^^^^^^^^^^^^^^^^^^ object literal
test.js:13
13: type PendingProject = {accepted: null} & Project;
^^^^ null. This type is incompatible with
14: type AcceptedProject = {accepted: true} & Project;
^^^^ boolean literal `true`
With my naivety I would expect flow to be able to see I am merging {accepted: true}
with proj
so it could safely assume that the result of that will satisfy PendingProject
and {accepted: true}
(AcceptedProject
?)
Thanks in advance! :)
This doesn't have anything to do with intersections, but is instead a bug in how we process spreads.
Smaller repro without intersections:
type PendingProject = {
type: 'project',
accepted: null,
id: string,
type: string,
};
type AcceptedProject = {
type: 'project',
accepted: true,
id: string,
type: string,
};
const pro: PendingProject = {
type: 'project',
id: '1',
accepted: null
}
function accept(proj: PendingProject): AcceptedProject {
return {...proj, accepted: true};
}
Merging objects with Object.assign also seems to repro this.
Any news on this? Here is minimal repro:
type A = { a: number, b: number }
type B = { a: number, b: string }
const a:A = { a:1, b:2 }
const b:B = { ...a, b: 'should be ok but its not' }
Related: It seems that you cannot return a spread object typed as a disjoint union member, even when it (I think?) can only possibly return the correct value. For example, I would expect the following to pass flow checks:
type A = {
name: 'FOO',
values: Array<string>,
};
type B = {
name: 'BAR',
values: Array<string>,
};
function test<T: A | B>(arg: T): T {
if (arg.name === 'BAR') {
return {
...arg,
values: [...arg.values, 'test'],
};
}
return arg;
}
It instead fails with the following error:
15: return { ^ object literal. Could not decide which case to select
13: function test<T: A | B>(arg: T): T {
^ union type
^ That is not a solution. The problem still exists and I won't be using Flow until this is rectified.
Is there still no solution to this? Is there a workaround? Seems like a pretty big issue.
@rsolomo your type for test
is not precise enough since it lets flow believe that you can send a A
in and get a B
out. Instead of A|B => A|B
, using the more precise type A=>A | B=>B
works:
const test2: A => A | B => B = (arg) => {
if (arg.name === 'BAR') {
return {
...arg,
values: [...arg.values, 'test'],
};
}
return arg;
}
Check out my comments on https://github.com/facebook/flow/issues/5253 to see why I believe this is actually correct behaviour for flow.
(I'm not flow dev, so my beliefs are only mine…)
this fixes the issue: https://github.com/facebook/flow/pull/7298
This code no longer errors in master
Most helpful comment
Any news on this? Here is minimal repro: