TypeScript-intersektionstyper av funktion
Jag använder TypeScript och stötte på några problem. Den enklaste demon skulle vara:
type g = 1 & 2 // never
type h = ((x: 1) => 0) & ((x: 2) => 0) // why h not never
type i = ((x: 1 & 2) => 0)// why x not never
Jag förstår inte varför typen h inte är never och parametern x i typen i inte är never
type e = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never; // why e is 2 not never or 1?
Förstod också inte varför typ e är 2 och inte never?
Svar #1
Observera: icke-primitiva typer skrivs konventionellt med UpperCamelCase; detta skiljer dem från primitiva typer och variabel-/egenskapsnamn. Jag kommer att använda denna konvention i det som följer.
Det finns en massa delar i den här frågan, och det kommer därför att finnas en massa delar i dess svar.
Låt oss ta de enkla först:
type G = 1 & 2 // never
Reduktionen till never av tomma snitt som 1 & 2 implementerades i microsoft/TypeScript#31838 och släpptes med TypeScript 3.6. Före detta behandlades 1 & 2 mycket likt never, eftersom du aldrig skulle kunna hitta ett värde som uppfyller det. Det finns egentligen ingen konceptuell skillnad mellan 1 & 2 och never, även om detaljer i kompilatorns implementation kan göra att den ena behandlas annorlunda än den andra.
Nästa:
type I = ((x: 1 & 2) => 0) // why x not never
x är aldrig, men reduktionen är uppskjuten tills du faktiskt använder den:
type IParam = Parameters<I>[0]; // never
Denna uppskov implementerades i microsoft/TypeScript#36696 och släpptes med TypeScript 3.9. Innan detta reducerades x ivrigt till never precis som G ovan.
Nu för några mer komplicerade exempel:
type H = ((x: 1) => 0) & ((x: 2) => 0) // why H not never
Det finns faktiskt ganska många anledningar till varför H inte är never:
Den TypeScript-specifika anledningen är att en intersection av funktionstyper betraktas som samma sak som en överlagrad funktion med flera anropssignaturer:
declare const h: H; // 1/2 h(x: 1): 0 // 2/2 h(x: 2): 0 h(1); // okay h(2); // okay h(3); // error, no overload matches this call h(Math.random() < 0.5 ? 1 : 2); // error, no overload matches this callObservera hur
hvisas som en funktion med två överlagringar; en som accepterar en1-parameter och en annan som accepterar en2-parameter. Den accepterar inte3, och den accepterar inte heller1 | 2även om den ur ett rent typ-systemperspektiv förmodligen borde göra det (se microsoft/TypeScript#14107 för en långvarig funktionsförfrågan om att stödja detta).Även om TypeScript inte tolkade funktionssnitt som överlagringar, borde
Hinte varanever. Funktions typer är kontravarianta i sina parametertyper (se mitt svar på den här frågan för en förklaring. Du kan också läsa om kontravarians av funktionsparametrar i TS-handbokens beskrivning av kompileringsflaggan--strictFunctionTypes). Kontravarians vänder saker till deras dual; OmF<T>är kontravariant iT, då ärF<T | U>ekvivalent medF<T> & F<U>, ochF<T & U>är ekvivalent medF<T> | F<U>. Så ett konsekvent typ-system, när det efterfrågas parametertypen av ett snitt av funktioner, skulle returnera unionen av funktionsparametrarnas typer. Och1 | 2är intenever.Även om
Hvore en helt obebodd typ, reducerar TypeScript för närvarande endast snitt tillneveri vissa situationer, och ett snitt av funktioner är inte en av dessa. Om du tittar i kompilatorns källkodsfilchecker.tsstår det "en snittyp betraktas som tom om den innehåller typennever, eller mer än en enhetstyp eller en objekttyp och en nullable-typ (nullellerundefined), eller enstring-lik typ och en typ som är känd för att vara icke-string-lik, eller ennumber-lik typ och en typ som är känd för att vara icke-number-lik, eller ensymbol-lik typ och en typ som är känd för att vara icke-symbol-lik, eller envoid-lik typ och en typ som är känd för att vara icke-void-lik, eller en icke-primitive typ och en typ som är känd för att vara primitiv. Ingen av det där säger "två inkompatibla funktionstyper", så det reduceras inte tillnever.
Sista:
type E = (((x: 1) => 0) & ((x: 2) => 0)) extends (x: infer L) => 0 ? L : never;
// why E is 2 not never or 1?
Kom ihåg från tidigare att ett snitt av funktioner betraktas som en överlagrad funktion. Det är en känd designbegränsning i TypeScript att när man använder typinferens på en överlagrad funktionstyp, försöker inte kompilatorn att lösa överlagringen genom att ta reda på vilken anropssignatur som bäst matchar den avsedda inferensen. Den använder bara den sista anropssignaturen och ignorerar alla de andra. I E betyder det att kompilatorn endast ser (x: 2) => 0 när den matchar mot (x: infer L) => 0, och därför infereras L som 2. Det "rätta" att göra skulle förmodligen vara unionen 1 | 2, men detta sker inte. Se microsoft/TypeScript#27027 för ett exempel på denna begränsning.
Playground-länk till kod
