Analiza fluxului de control în TypeScript nu recunoaște un tip cunoscut


Încerc să înțeleg de ce compilatorului trebuie să i se spună în mod explicit care este tipul valorii din tipul Policies, chiar dacă poate fi doar unul dintre Types-urile mele.

type Types = 'baz' | 'bar';

// tagged union types
type Foo<T extends Types> = { type: T; }
type FooOne = { one: string } & Foo<'baz'>;
type FooAnother = { another: string } & Foo<'bar'>;

type Predicate<T extends Types> = (e: Foo<T>) => boolean;

type Policies = {
    [P in Types]: Predicate<P>
}

const policies: Policies = {
    baz: (e: FooOne) => e.one === 'neo',
    bar: (e: FooAnother) => e.another === 'morphieus'
}

// this method receives a union type
function verify(e: FooOne | FooAnother) {
    // these both work, understandably
    if (e.type === 'baz') {
        const policy1 = policies[e.type]; // ide says this type is a Predicate<'baz'>
        const result1 = policy1(e); // fine  
    } else {
        const policy2 = policies[e.type]; // and this is a Predicate<'bar'>
        const result2 = policy2(e); // fine
    }

    // but this doesn't work even though e.type is known to be 'baz' | 'bar', and the keys at policies can only be 'baz' | 'bar'
    const policy3 = policies[e.type]; // ide says this type is a Predicate<'bar'> | Predicate<'baz'>
    const result3 = policy3(e); // error Cannot invoke an expression whose type lacks a call signature  

    // this works, if i hint to the compiler what is at policies[T]
    const policy4: Predicate<Types> = policies[e.type]; // Predicate<'baz' | bar'>
    const result4 = policy4(e); // fine 
}

Răspuns #1

Nu există nicio soluție bună aici, va trebui să folosești un fel de aserțiune de tip pentru a face lucrurile să funcționeze.

Problema este că analiza fluxului nu urmărește de unde provin valorile. Deci, atunci când scrii:

const policy3 = policies[e.type]; 
const result3 = policy3(e);

`policy3` va fi `Predicate<"baz"> | Predicate<"bar">`, dar compilatorul nu știe care. Când apelezi `policy3`, deoarece nu știe ce tip de funcție există de fapt în variabilă, te va forța, în versiunile mai noi, să specifici un argument care poate funcționa cu oricare dintre funcțiile din uniune. Nu își amintește că `policy3` este de fapt legat de `e.type` și că `policy3(e)` va fi sigur din punct de vedere al tipului. (FYI: în versiunile mai vechi `policy3` nu ar fi fost apelabil deloc deoarece este o uniune)

Acest cod funcționează:

const policy4= policies[e.type] as Predicate<Types> ; // Predicate<'baz' | bar'>
const result4 = policy4(e); // fine 

Dar Predicate<"baz"> | Predicate<"bar"> nu este același lucru cu Predicate<Types>. Primul este fie o funcție care primește baz, fie o funcție care primește bar, în timp ce al doilea este o funcție care primește fie baz, fie bar. Așadar, apelul va funcționa pentru că ai afirmat că funcția poate accepta oricare tip de parametru (ceea ce nu este strict adevărat, dar, după cum am spus, nu există soluții bune).