typescript - Varför härleder inte generisk typinferens en union från två platser?


Överväg

const pairer = <T,>(a: T, b:T) => ({a, b})
const mypair = pairer(3n, true)

Detta är ett fel i TypeScript 4.9, nämligen

Argument av typen boolean kan inte tilldelas parametern av typen bigint

Så tydligt har TS dragit slutsatsen att T är bigint i anropet, vilket gör anropet ogiltigt. Men det finns en helt bra typ för T som gör anropet lagligt, nämligen bigint | boolean:

const myotherpair = pairer<bigint | boolean>(3n, true)

kompilerar och körs utan problem.

Varför härleder inte TypeScript typen som gör koden den bearbetar giltig? Finns det en kortfattad beskrivning av härledningen av en generisk parameter från ett faktiskt anrop med tillräcklig detalj för att förstå varför det valde bigint i mypair-anropet? (TypeScript-handboken sida om generics täcker inte fall med två användningar av parametern.) Och kanske viktigast av allt, finns det ett sätt för mig att definiera min generiska så att det obeklädda anropet mypair(x,y) kommer att härleda typen (typeof x) | (typeof y)?

Svar #1

Jag antar att följande är huvudfrågan:

Finns det ett sätt för mig att definiera min generiska så att det odekorerade anropet mypair(x,y) kommer att härleda typen (typeof x) | (typeof y)

Som du såg, kan du inte göra detta med en enda generisk typparameter T. Kompilatorn gillar inte att syntetisera unioner från flera inferensplatser, som beskrivs i Varför härleds inte typargumentet som en unionstyp?.

Om du vill efterlikna detta beteende kan du ange flera typparametrar och sedan själv syntetisera deras förening:

const pairer = <T, U>(a: T, b: U): { a: T | U, b: T | U } => ({ a, b });

I ovanstående kan T härledas endast från a, och U kan härledas endast från b. De är oberoende. Returtypen, å andra sidan, är annoterad som { a: T | U, b: T | U }, för att ge samma output som du skulle få om både a och b användes för att härleda ett enda typargument.

Observera att om du inte annoterar returtypen kommer kompilatorn att härleda den mer specifika typen {a: T, b: U}. Antagligen föredrar du faktiskt den bredare versionen där både a och b är av samma typ, dock.

Du kan se det i aktion:

const mypair = pairer(3n, true);
/* const mypair: {
    a: bigint | boolean;
    b: bigint | boolean;
} */

Ser bra ut. Det finns ingen klagan vid anropsplatsen, och typen av mypair har bigint | boolean-medlemmar som önskat.

Playground-länk till kod

Svar #2

För mig är detta ganska enkelt, det är det minst överraskande beteendet:

Du skulle förvänta dig att kompilatorn varnar dig när du skickar två olika typer. Man kan se det som en funktion att du kan kringgå denna varning genom att ange typen som en union.

Detta fungerar som avsett enligt #37673, men du kan alltid öppna ett ärende (#44312) och begära den funktionen för att tillåta att inferensen kan utökas av parametrarna.

Relaterade artiklar