Reputation: 19839
I have a basic union type;
type A = {type: "A"}
type B = {type: "B"}
type X = A | B
And I have a function that finds an item in a list that has the same type:
function find(x: X, list: Array<X>) {
return list.find(item => item.type === x.type)
}
I would expect the return type of this function to be the specific subtype of X that matches the input x. That is, I want find({type: "A"}, [{type: "A"}, {type: "B"}])
to return a type of A.
Any ideas how I might do this?
Edit: It turns out what I'm dealing with is a little more complicated. I have a concept of batches that are queue and I want to add an item to a batch if it exists, otherwise I want to enqueue a new batch:
type A = { type: "A" }
type B = { type: "B" }
type X = A | B
type Batch<T extends X> = { type: T["type"]; batch: Array<T> }
type BatchA = Batch<A>
type BatchB = Batch<B>
type BatchTypes = BatchA | BatchB
function find(x: X, list: Array<BatchTypes>) {
return list.find(item => item.type === x.type)
}
function enqueue(x: X, queue: Array<BatchTypes>) {
const result = find(x, queue)
if (result) {
result.batch.push(x)
} else {
queue.push({type: x.type, batch:[x]})
}
}
let a: A
let b: B
let queue: Array<BatchTypes>
enqueue(a, queue)
enqueue(b, queue)
The problem here lies in enqueue
because result a union of both types. When I try to overload the types, result is resolved properly but there's an issue with the first argument of find
and pushing a new batch to the queue:
function find(x: A, list: Array<BatchTypes>): BatchA
function find(x: B, list: Array<BatchTypes>): BatchB
function find(x: X, list: Array<BatchTypes>) {
return list.find(item => item.type === x.type)
}
function enqueue(x: A, queue: Array<BatchTypes>)
function enqueue(x: B, queue: Array<BatchTypes>)
function enqueue(x: X, queue: Array<BatchTypes>) {
const result = find(x, queue)
if (result) {
result.batch.push(x)
} else {
queue.push({ type: x.type, batch: [x] })
}
}
Please let me know if there's a better way of clarifying this question.
Given @artem's answer, I've gotten closer:
function find<T extends X>(x: T, list: Array<BatchTypes>): Batch<T> {
return <Batch<T>>list.find(item => item.type === x.type)
}
function enqueue<T extends X>(x: T, queue: Array<BatchTypes>) {
const result = find(x, queue)
if (result) {
result.batch.push(x)
} else {
queue.push({ type: x.type, batch: [x] })
}
}
But there's still an issue with queue.push
.
Maybe this is a more concise example of demonstrating the current issue:
type A = { type: "A" }
type B = { type: "B" }
type X = A | B
let list: Array<Array<A> | Array<B>>
function append<T extends X>(x: T) {
list.push([x])
}
function append2(x: X) {
list.push([x])
}
Upvotes: 2
Views: 1228
Reputation: 51629
You can do that by adding overload declarations for find
:
type A = {type: "A"}
type B = {type: "B"}
type X = A | B
function find(a: A, list: Array<X>): A;
function find(a: B, list: Array<X>): B;
function find(x: X, list: Array<X>) {
return list.find(item => item.type === x.type)
}
let a: A;
let b: B;
let x = [a, b];
let a1 = find(a, x); // inferred as A
let b1 = find(b, x); // inferred as B
If find
return type will always be the same as the type of its first argument, you can use single generic overload declaration to avoid repeating:
function find<T extends X>(x: T, list: Array<X>): T;
function find(x: X, list: Array<X>) {
return list.find(item => item.type === x.type)
}
Upvotes: 1