Description
🔎 Search Terms
map, union, generics, property does not exist on type
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about
Common "Bugs" That Aren't Bugs
andGenerics
.
⏯ Playground Link
💻 Code
If I try to modify a generic nested map-structure and add a property to an object deep in the hierarchy, the generic type cannot follow up and still thinks the object contains the old type. In the following examples, it's all about the type of semiStructuredCols
, as I show in the map-functions following the definition.
Example 1:
In this example, I do not assign a specific type, but let the system figure out what I do. It's pretty good, but it seems to keep the parameter cells
as in the original object and doesn't use Omit<>
to remove it before adding it with the new type. You can see it fail a bit further down in line 29 of the example because of the wrong type.
const foo = <
ECell extends { type: "element"; elementId: string },
FCell extends { type: "flow"; flowId: string },
ECol extends {
type: "element";
cells: (ECell | FCell)[];
padding: { left: number; right: number };
},
FCol extends {
type: "flow";
flowId: string;
},
>(cols: (ECol | FCol)[]) => {
const semiStructuredCols = cols.map((col) =>
col.type === "element"
? {
...col,
cells: col.cells.map((c) =>
c.type === "flow" ? { ...c, flows: new Array<{ flowId: string }>() } : c,
),
}
: col,
);
return semiStructuredCols.map((col) =>
col.type === "element"
? {
...col,
cells: col.cells.map((c) => c.type === "element" ? c : {...c, flows: c.flows.map(f => f)}),
}
: col,
);
};
Example 2:
In this example, I try to manually set the type on the variable, which fails miserably - even earlier, but still on line 29 of the example.
const foo = <
ECell extends { type: "element"; elementId: string },
FCell extends { type: "flow"; flowId: string },
ECol extends {
type: "element";
cells: (ECell | FCell)[];
padding: { left: number; right: number };
},
FCol extends {
type: "flow";
flowId: string;
},
>(cols: (ECol | FCol)[]) => {
const semiStructuredCols: ((Omit<ECol, "cells"> & { cells: (ECell | (FCell & { flows: { flowId: string }[] } ))[] }) | FCol)[] = cols.map((col) =>
col.type === "element"
? {
...col,
cells: col.cells.map((c) =>
c.type === "flow" ? { ...c, flows: new Array<{ flowId: string }>() } : c,
),
}
: col,
);
return semiStructuredCols.map((col) =>
col.type === "element"
? {
...col,
cells: col.cells.map((c) => c.type === "element" ? c : {...c, flows: c.flows.map(f => f)}),
}
: col,
);
};
Example 3:
I gave up on generic types and tried an explicit type - this seems to work quite well.
type ECell = { type: "element"; elementId: string };
type FCell = { type: "flow"; flowId: string };
type ECol = {
type: "element";
cells: (ECell | FCell)[];
padding: { left: number; right: number };
};
type FCol = {
type: "flow";
flowId: string;
};
const foo = (cols: (ECol | FCol)[]) => {
const semiStructuredCols = cols.map((col) =>
col.type === "element"
? {
...col,
cells: col.cells.map((c) =>
c.type === "flow" ? { ...c, flows: new Array<{ flowId: string }>() } : c,
),
}
: col,
);
return semiStructuredCols.map((col) =>
col.type === "element"
? {
...col,
cells: col.cells.map((c) => c.type === "element" ? c : {...c, flows: c.flows.map(f => f)}),
}
: col,
);
};
🙁 Actual behavior
Example 1 and example 2 fails, but example 3 passes.
Example 1:
Property 'flows' does not exist on type 'FCell'.
Parameter 'f' implicitly has an 'any' type.
Example 2:
Property 'cells' does not exist on type 'FCol | (Omit<ECol, "cells"> & { cells: (ECell | (FCell & { flows: { flowId: string; }[]; }))[]; })'.
Property 'cells' does not exist on type 'FCol'.
Parameter 'c' implicitly has an 'any' type.
Parameter 'f' implicitly has an 'any' type.
🙂 Expected behavior
Both example 1, example 2 and example 3 should pass.
Additional information about the issue
No response