-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Link types in union with type in union of type guard #48998
Comments
There's not really a sound way of doing this without creating an entirely new kind of narrowing. From TypeScript's perspective, there's not a difference between the provided code (which is OK by construction) and this code, which is broken: function doWork<T extends WorkTypes>(workers: GuardedWorkers[], value: T) {
const worker1 = workers[0];
const worker2 = workers[1];
if (worker1.guard(value)) {
worker2.process(value); // Unsound call
}
}
declare const gwFoo: GuardedWorker<WorkTypes, Foo>;
declare const gwBar: GuardedWorker<WorkTypes, Bar>;
doWork([gwFoo, gwBar], { foo: "" }); // Fails So it's not enough / not correct to narrow |
Seems like another instance of #30581. If so, then the approach described in #47109 works here also, with the introduction of a helper mapping type, like this: interface WorkMapper {
foo: Foo;
bar: Bar;
}
type GuardedWorkers<K extends keyof WorkMapper> =
{ [P in K]: GuardedWorker<WorkTypes, WorkMapper[P]> }[K];
function doWork<K extends keyof WorkMapper>(workers: GuardedWorkers<K>[], value: WorkTypes) {
for (const worker of workers) {
if (worker.guard(value)) {
worker.process(value); // okay
}
}
}
declare const gwFoo: GuardedWorker<WorkTypes, Foo>;
declare const gwBar: GuardedWorker<WorkTypes, Bar>;
doWork([gwFoo, gwBar], { foo: "" }); It's also something that existential types would help ( #14466 ) and the various emulations of existential types also apply (with various runtime refactorings if you want type safety) const someGuardedWorker = <A, T extends A>(worker: GuardedWorker<A, T>) =>
(value: A) => { if (worker.guard(value)) worker.process(value) }
type SomeGuardedWorker = (value: WorkTypes) => void
function doWork(workers: SomeGuardedWorker[], value: WorkTypes) {
for (const worker of workers) {
worker(value)
}
}
declare const gwFoo: GuardedWorker<WorkTypes, Foo>;
declare const gwBar: GuardedWorker<WorkTypes, Bar>;
doWork([someGuardedWorker(gwFoo), someGuardedWorker(gwBar)], { foo: "" }) |
So if the workers themselves were to take the generic parameter and not the value it would work? I'll try that, thanks. |
This issue has been marked as "Too Complex" and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
Suggestion
π Search Terms
union of type guards
type guard union
list of type guards
β Viability Checklist
My suggestion meets these guidelines:
β Suggestion
Link the type of a type guard within a union with the type of the element, so they can be used with each other, even though narrowing is not possible.
π Motivating Example
π» Use Cases
The example above is a simplification of a real word example where this would be used.
Currently, we need to do a casting like this the example to work:
In our real use case, we don't need the
unknown
because the type containing the guard is generic a string literal, and that doesn't cause a conflict. This is the real world use case that prompted this feature request:The text was updated successfully, but these errors were encountered: