I'm trying to type a function parameter where the param accepts any type where the union contains a known subset.
For example:
type One = { a: "one"; b: 1 };
type Two = { a: "two"; b: 2 };
type U = One | Two;
// `CouldContain` is some kind of wrapper that says it MIGHT contain `Two`
function test(param: CouldContain<Two>) {}
So the following would work:
const pass: U = { a: "one", b: 1 };
test(pass); // `U` contains `Two`
And so would:
type Z = { } | Two
const pass: Z = { };
test(pass); // `Z` contains `Two`
But this wouldn't:
const fail = { a: "three", b: 3}
test(fail); // Not inferred as `Two`
Nor would:
const fail = { };
test(fail); // Valid branch of `Z` which contains `Two`, but no way to infer this as `Z` so it fails
The use-case I have is inferring whether dynamically generated GraphQL data types might contain a specific union type.
CodePudding user response:
What an odd question. Here is my approach.
type CouldContain<T, U> = Extract<T, U> extends never ? never : T
function test<T>(param: CouldContain<T, Two>) {}
This works by using the argument param
to infer the generic type T
and by using a conditional type to check if Two
is a member of T
.
When using this type, how have to note one important thing:
type U = One | Two;
const pass: U = { a: "one", b: 1 } as U;
test(pass); // ok
type Z = { } | Two
const pass2: Z = { } as Z;
test(pass2); // ok
const fail = { a: "three", b: 3}
test(fail); // fails
const fail2 = { };
test(fail2) // fails
The two type assertions as U
and as Z
are needed for this to work. Control flow analysis will otherwise use the literal type to attach invisible type information to pass
and pass2
. Without the type assertions the compiler slightly changes both types, so that Two
is not part of their union anymore. The type assertions will stop the compiler from doing these optimizations.
CodePudding user response:
I recommend you to check the following URL content out: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has