It has been mentioned that achieving what you desire is not feasible.
During the compilation of TypeScript code, the static type system gets erased. Therefore, consider the following TypeScript code snippet:
const item: A = { foo: 123 };
if (is<B>(item)) { console.log(item.foo.toFixed(2)); }
if (is<C>(item)) { console.log(item.bar.toFixed(2)); }
The resulting JavaScript code after compilation could look like this:
const item: A = Math.random() < 0.5 ? { foo: 123 } : { bar: 456 };
if (is(item)) { console.log(item.foo.toFixed(2)); }
if (is(item)) { console.log(item.bar.toFixed(2)); }
Hence, at runtime, it becomes essential for `is(item)` to function as a `B` check in one instance and a `C` check in the next. This scenario requires uniqueness in results based on type information which isn't inherently present. It delves into the realm of supernatural capabilities beyond standard human-made software functionality.
To implement something achievable without resorting to magic, the solution lies in providing your function with runtime data to determine the appropriate checks. For example, tweaking `is()` to include a second parameter containing the type name to be checked:
const item: A = Math.random() < 0.5 ? { foo: 123 } : { bar: 456 };
if (is(item, "B")) { console.log(item.foo.toFixed(2)); }
if (is(item, "C")) { console.log(item.bar.toFixed(2)); }
This updated approach enables the `is()` implementation to utilize the second parameter to determine the property to validate at runtime. While the solution may seem convoluted, it's viable and comes with an appropriately defined type signature showcasing its integrity:
type PossibleTypes = B | C;
const props = {
B: "foo",
C: "bar"
} as const
type Props = typeof props;
function is<K extends keyof Props>(
item: A,
typeName: K
): item is Extract<PossibleTypes, Record<Props[K], number>> {
const key = props[typeName];
return ((key in item) && (typeof (item as any)[key] === "number"));
}
In this new setup, a precise union type `PossibleTypes` enumerates the types eligible for checking within `is()`. The `props` object maps type names "B" and "C" to their respective properties for verification. Also, `Props` represents the type of `props` in use.
The `is()` function employs generics via `K` for the type name, whereas the result constitutes a type predicate confirming if `item` aligns with either type `B` or `C`. Type extraction from the `PossibleTypes` union ensures accuracy by verifying a numeric property matching the key specified in `Props[K]`.
By mapping type names to valid keys and validating these keys within `item`, the `is()` mechanism adeptly performs the necessary property verifications.
While there exist alternate paths to transform `is()` into a manageable form, each alteration might yield similar levels of dissatisfaction due to minimal gains from consolidating diverse checks within a single type guard function. With only two types, namely `B` and `C`, opting for simplicity by creating distinct check functions might offer a clearer path forward:
function isB(item: A): item is B {
return ("foo" in item) && (typeof (item as any).foo === "number");
}
function isC(item: A): item is C {
return ("bar" in item) && (typeof (item as any).bar === "number");
}
const item: A = Math.random() < 0.5 ? { foo: 123 } : { bar: 456 };
if (isB(item)) { console.log(item.foo.toFixed(2)); }
if (isC(item)) { console.log(item.bar.toFixed(2)); }
Your decision ultimately hinges on specific usage scenarios and personal preferences.
Click here for Playground link to the code