Closed as not planned
Description
🔎 Search Terms
"generic narrowing", "union narrowing", "generic guard", "ts2345"
🕗 Version & Regression Information
- This is the behavior in every version I tried (from
v3.9
tov5.5.0-dev.20240316
), and I reviewed the FAQ for entries about type narrowing - I was unable to test this on prior versions because
Property 'values' does not exist on type 'ObjectConstructor'.(2339)
⏯ Playground Link
💻 Code
class Common {}
class A extends Common {
public readonly propA = "";
}
class B extends Common {
public readonly propB = "";
}
type CommonUtilFns<T extends Common> = {
isAorB: (val: Common) => val is T;
getProp: (val: T) => { prop: string };
};
const utils = {
A: {
isAorB: (version: Common): version is A => !!(version as A).propA,
getProp: (version: A) => ({ prop: version.propA }),
} satisfies CommonUtilFns<A>,
B: {
isAorB: (version: Common): version is B => !!(version as B).propB,
getProp: (version: B) => ({ prop: version.propB }),
} satisfies CommonUtilFns<B>,
};
const getProp = (val: Common) => {
for (const util of Object.values(utils)) {
if (util.isAorB(val)) return util.getProp(val);
}
return null;
};
🙁 Actual behavior
Invoking util.getProp
after guarding it with util.isAorB
results in a type error:
Argument of type 'A | B' is not assignable to parameter of type 'A & B'.
Type 'A' is not assignable to type 'A & B'.
Property 'propB' is missing in type 'A' but required in type 'B'.(2345)
The return value of Object.values
seems to be correctly typed, as this is the evaluated typing of util
:
const util: {
isAorB: (version: Common) => version is A;
getProp: (version: A) => {
prop: string;
};
} | {
isAorB: (version: Common) => version is B;
getProp: (version: B) => {
prop: string;
};
}
However, once val
is guarded, its type becomes A | B
🙂 Expected behavior
I would expect the type guard to correctly narrow the type as A | B
, given I'm using the same instance of CommonUtilFns
with the same generic.
This however works correctly with these examples:
const getProp = (val: Common, utils: CommonUtilFns<Common>[]) => {
for (const util of utils) if (util.isAorB(val)) return util.getProp(val);
return null;
};
const getProp = (val: Common, utils: CommonUtilFns<A | B>[]) => {
for (const util of utils) if (util.isAorB(val)) return util.getProp(val);
return null;
};
Additional information about the issue
This might be related to #17713, but I haven't found another issue specifically addressing this pattern, apologies if I missed it
Metadata
Metadata
Assignees
Labels
No labels