Pesquisa de site

Como encontrar e desambiguar um tipo de união a partir de uma lista de tipos de união em TypeScript?


Eu tenho um tipo de união básico;

    type A = {type: "A"}
    type B = {type: "B"}
    type X =  A | B

E eu tenho uma função que encontra um item em uma lista que tem o mesmo tipo:

    function find(x: X, list: Array<X>) {
        return list.find(item => item.type === x.type)
    }

Eu esperaria que o tipo de retorno desta função fosse o subtipo específico de X que corresponde ao input x. Ou seja, eu quero que find({type: "A"}, [{type: "A"}, {type: "B"}]) retorne um tipo A.

Alguma ideia de como eu poderia fazer isso?

Editar: Acontece que o que estou lidando é um pouco mais complicado. Eu tenho um conceito de lotes que estão na fila e quero adicionar um item a um lote se ele existir, caso contrário, quero enfileirar um novo lote:

    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)

O problema aqui está em enqueue porque resulta em uma união de ambos os tipos. Quando tento sobrecarregar os tipos, o resultado é resolvido corretamente, mas há um problema com o primeiro argumento de find e ao adicionar um novo lote à fila:

    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] })
        }
    }

Por favor, me avise se houver uma maneira melhor de esclarecer esta pergunta.

Dada a resposta do @artem, eu cheguei mais perto:

    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] })
        }
    }

Mas ainda há um problema com queue.push.

Talvez este seja um exemplo mais conciso de como demonstrar o problema atual:

    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])
    }

Resposta #1

Você pode fazer isso adicionando declarações de sobrecarga para 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

Se o tipo de retorno de find for sempre o mesmo que o tipo de seu primeiro argumento, você pode usar uma única declaração de sobrecarga genérica para evitar repetições:

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)
}