GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Conditional types by ahejlsberg · Pull Request #21316 · microsoft/TypeScript

Conversation

@ahejlsberg

@ahejlsberg Anders Hejlsberg (ahejlsberg) commented Jan 20, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This PR introduces conditional types which add the ability to express non-uniform type mappings. A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

T extends U ? X : Y

The type above means when T is assignable to U the type is X, otherwise the type is Y. Evaluation of a conditional type is deferred when evaluation of the condition depends on type variables in T or U, but is resolved to either X or Y when the condition depends on no type variables.

An example:

type TypeName<T> =
    T extends string ? "string" :
    T extends number ? "number" :
    T extends boolean ? "boolean" :
    T extends undefined ? "undefined" :
    T extends Function ? "function" :
    "object";

type T0 = TypeName<string>;  // "string"
type T1 = TypeName<"a">;  // "string"
type T2 = TypeName<true>;  // "boolean"
type T3 = TypeName<() => void>;  // "function"
type T4 = TypeName<string[]>;  // "object"

Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

type T10 = TypeName<string | (() => void)>;  // "string" | "function"
type T12 = TypeName<string | string[] | undefined>;  // "string" | "object" | "undefined"
type T11 = TypeName<string[] | number[]>;  // "object"

In instantiations of a distributive conditional type T extends U ? X : Y, references to T within the conditional type are resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e. T is considered assignable to U within X).

type BoxedValue<T> = { value: T };
type BoxedArray<T> = { array: T[] };
type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;

type T20 = Boxed<string>;  // BoxedValue<string>;
type T21 = Boxed<number[]>;  // BoxedArray<number>;
type T22 = Boxed<string | number[]>;  // BoxedValue<string> | BoxedArray<number>;

Notice that T has the additional constraint any[] within the true branch of Boxed<T> and it is therefore possible to refer to the element type of the array as T[number]. Also, notice how the conditional type is distributed over the union type in the last example.

The distributive property of conditional types can conveniently be used to filter union types:

type Diff<T, U> = T extends U ? never : T;  // Remove types from T that are assignable to U
type Filter<T, U> = T extends U ? T : never;  // Remove types from T that are not assignable to U

type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"
type T32 = Diff<string | number | (() => void), Function>;  // string | number
type T33 = Filter<string | number | (() => void), Function>;  // () => void

type NonNullable<T> = Diff<T, null | undefined>;  // Remove null and undefined from T

type T34 = NonNullable<string | number | undefined>;  // string | number
type T35 = NonNullable<string | string[] | null | undefined>;  // string | string[]

function f1<T>(x: T, y: NonNullable<T>) {
    x = y;  // Ok
    y = x;  // Error
}

function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {
    x = y;  // Ok
    y = x;  // Error
    let s1: string = x;  // Error
    let s2: string = y;  // Ok
}

Conditional types are particularly useful when combined with mapped types:

type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

type NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;

interface Part {
    id: number;
    name: string;
    subparts: Part[];
    updatePart(newName: string): void;
}

type T40 = FunctionPropertyNames<Part>;  // "updatePart"
type T41 = NonFunctionPropertyNames<Part>;  // "id" | "name" | "subparts"
type T42 = FunctionProperties<Part>;  // { updatePart(newName: string): void }
type T43 = NonFunctionProperties<Part>;  // { id: number, name: string, subparts: Part[] }

Combining all of the above to create a DeepReadonly<T> type that recursively makes all properties of an object read-only and removes all function properties (i.e. methods):

type DeepReadonly<T> =
    T extends any[] ? DeepReadonlyArray<T[number]> :
    T extends object ? DeepReadonlyObject<T> :
    T;

interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}

type DeepReadonlyObject<T> = {
    readonly [P in NonFunctionPropertyNames<T>]: DeepReadonly<T[P]>;
};

function f10(part: DeepReadonly<Part>) {
    let name: string = part.name;
    let id: number = part.subparts[0].id;
    part.id = part.id;  // Error
    part.subparts[0] = part.subparts[0];  // Error
    part.subparts[0].id = part.subparts[0].id;  // Error
    part.updatePart("hello");  // Error
}

Similar to union and intersection types, conditional types are not permitted to reference themselves recursively (however, indirect references through interface types or object literal types are allowed, as illustrated by the DeepReadonly<T> example above). For example the following is an error:

type ElementType<T> = T extends any[] ? ElementType<T[number]> : T;  // Error

For further examples see the tests associated with the PR.

EDIT: See #21496 for type inference in conditional types.

Fixes #12215.
Fixes #12424.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ajafff, azhukaudev, joewood, Bnaya, astashov, olaf89, ErikSchierboom, Shinigami92, shairez, piotrwitek, and 271 more reacted with thumbs up emoji jonsterling reacted with thumbs down emoji Bnaya, olaf89, Shinigami92, shairez, piotrwitek, thgreasi, karol-majewski, houfio, j-oliveras, pelotom, and 111 more reacted with hooray emoji jonsterling, browsermage, and nagarsuresh reacted with confused emoji mohsen1, exyi, s-ve, olaf89, Shinigami92, shairez, piotrwitek, karol-majewski, thgreasi, alexrock, and 134 more reacted with heart emoji
All reactions
  • 👍 281 reactions
  • 👎 1 reaction
  • 🎉 121 reactions
  • 😕 3 reactions
  • ❤️ 144 reactions
Anders Hejlsberg (ahejlsberg) added 30 commits December 5, 2017 14:18
@ahejlsberg
Initial implementation of conditional type operator
57ca768
@ahejlsberg
Add type relationships and distribute over union types
063eed1
@ahejlsberg
Add 'T extends U' type operator
ec2bdfd
@ahejlsberg
Clean up isGenericXXXType functions
43e195d
@ahejlsberg
Introduce TypeFlags.Instatiable
61225cc
@ahejlsberg
Rename TypeVariable to InstantiableType
9f74a7a
@ahejlsberg
Inference for conditional and extends type operators
20434fa
@ahejlsberg
Fix typo
ddc631c
@ahejlsberg
Improve conditional type constraint checking
000f121
@ahejlsberg
Handle constraints for distributive conditional types
27b945b
@ahejlsberg
Accept new baselines
f59e2e6
@ahejlsberg
Move JsxAttributes and MarkerType from TypeFlags to ObjectFlags
14590f1
@ahejlsberg
Accept new baselines
100e4f6
@ahejlsberg
Parse xxx? as JSDoc type when not followed by token that starts type
c5fd2f1
@ahejlsberg
Accept new baselines
341c397
@ahejlsberg
Fix linting error
3f4911f
@ahejlsberg
Merge branch 'master' into conditionalTypes
abc8110
# Conflicts:
#	src/compiler/checker.ts
@ahejlsberg
Propagate both TypeFlags and ObjectFlags in getSpreadType
bb23bb2
@ahejlsberg
Eagerly evaluate S extends T when S is definitely or definitely not a…
c10a552
…ssignable to T
@ahejlsberg
Revert to extends check being part of conditional type
53b1572
@ahejlsberg
Remove 'T extends U' type constructor
5094f76
@ahejlsberg
Accept new baselines
925da86
@ahejlsberg
Introduce substitution types to use for constrained type parameters
e8d1740
@ahejlsberg
Accept new baselines
15baf0e
@ahejlsberg
Properly handle 'any' and 'never' as conditional check type
9598acd
@ahejlsberg
Erase substitution types in type references and type alias instantiat…
e96ec8c
…ions
@ahejlsberg
Optimize the sameMap function
d52fa71
@ahejlsberg
Merge branch 'master' into conditionalTypes
4ec6fdd
# Conflicts:
#	src/compiler/checker.ts
#	src/compiler/types.ts
#	tests/baselines/reference/api/tsserverlibrary.d.ts
#	tests/baselines/reference/api/typescript.d.ts
@ahejlsberg
Separate code path for conditional type instantiation
fd0dd6e
@ahejlsberg
Fix parsing
c360c24
@jack-williams Jack Williams (jack-williams) mentioned this pull request Mar 16, 2018
Conditional Types and Narrowing Type Fail to Return #22628
Closed
@dead-claudia Claudia Meadows (dead-claudia) mentioned this pull request Mar 17, 2018
Proposal: Add an "exclusive or" (^) operator #14094
Closed
Amin (marashiai) pushed a commit to marashiai/babel that referenced this pull request Mar 17, 2018
Andy @marashiai
TypeScript: Support conditional types syntax (babel#7404)
ead9c85
microsoft/TypeScript#21316 and microsoft/TypeScript#21496
@KiaraGrouwstra kiara (KiaraGrouwstra) mentioned this pull request Mar 18, 2018
Divide by zero Typechecking #21955
Open
@leonadler

Leon Adler (leonadler) commented Mar 19, 2018

Copy link
Copy Markdown

Sorry if there is a more appropriate place to post this, but thanks for the new (albeit sometimes complicated) ways to express behavior!

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type NumberOfArgs<T extends Function> = 
    T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => any ? (
        IsValidArg<J> extends true ? 10 :
        IsValidArg<I> extends true ? 9 :
        IsValidArg<H> extends true ? 8 :
        IsValidArg<G> extends true ? 7 :
        IsValidArg<F> extends true ? 6 :
        IsValidArg<E> extends true ? 5 :
        IsValidArg<D> extends true ? 4 :
        IsValidArg<C> extends true ? 3 :
        IsValidArg<B> extends true ? 2 :
        IsValidArg<A> extends true ? 1 : 0
    ) : 0;

function numArgs<T extends Function>(fn: T): NumberOfArgs<T> {
    return fn.length as any;
}
    
declare function exampleFunction(a: number, b: string, c?: any[]): void;
const test = numArgs(exampleFunction);

screenshot


type Promisified<T extends Function> =
    T extends (...args: any[]) => Promise<any> ? T : (
        T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
            IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => Promise<R> :
            IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => Promise<R> :
            IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => Promise<R> :
            IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => Promise<R> :
            IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => Promise<R> :
            IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => Promise<R> :
            IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => Promise<R> :
            IsValidArg<C> extends true ? (a: A, b: B, c: C) => Promise<R> :
            IsValidArg<B> extends true ? (a: A, b: B) => Promise<R> :
            IsValidArg<A> extends true ? (a: A) => Promise<R> :
            () => Promise<R>
        ) : never
    );

declare function promisify<T extends Function>(fn: T): Promisified<T>;

declare function exampleFunction2(a: number, b: string, c?: any[]): RegExp;

const test2 = promisify(exampleFunction2);

screenshot

niieani, donaldpipowitch, AriaMinaei, KiaraGrouwstra, greghornby, wand3r, and DCoderLT reacted with thumbs up emoji
All reactions
  • 👍 7 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Mar 19, 2018
Nested conditional generic types yields boolean not assignable to true | false | undefined. #22630
Closed
@ljani Jani (ljani) mentioned this pull request Mar 20, 2018
Add typings jrparish/tslint-webpack-plugin#11
Merged
@frenic Fredrik Nicol (frenic) mentioned this pull request Mar 21, 2018
Incompatibilities with @types/react frenic/csstype#4
Closed
@sirian

sirian commented Mar 27, 2018

Copy link
Copy Markdown
Contributor

#22899

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sirian

sirian commented Mar 28, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Leon Adler (@leonadler)

numArgs((...args: any[]) => {});  // 10
numArgs((a: string, ...args: any[]) => {}); //10
numArgs((a: object) => {}); // 0

image

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@RyanCavanaugh

Ryan Cavanaugh (RyanCavanaugh) commented Mar 28, 2018

Copy link
Copy Markdown
Member

sirian please post code-fenced blocks instead of screenshots; no one wants to have to type in a dozen lines of code to add comments or observe behavior

gcnew and leonadler reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@leonadler

Leon Adler (leonadler) commented Mar 29, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

sirian are you asking a specific question?

My example was not meant to be feature-complete, just a result of experimenting with 2.8 for a few minutes. It depends on typescripts infer keyword inferring unprovided parameter types as {}:

type TypeOfFirstArg<T extends Function> = T extends (a: infer FirstArg) => any ? FirstArg : never;

function functionWithOneParameter(a: number) { }
let exampleA: TypeOfFirstArg<typeof functionWithOneParameter>;
// exampleA is of type "number", as you would expect

function functionWithNoParameter() { }
let exampleB: TypeOfFirstArg<typeof functionWithNoParameter>;
// exampleB is "{}", although I would have expected "never"
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sirian

sirian commented Mar 29, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Leon Adler (@leonadler) It was just a notice, that IsValidArgs and NumberOfArgs is not correct (to prevent other users from using incorrect code).

Ryan Cavanaugh (@RyanCavanaugh) Mi fault, I was sure I attached code.

type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type Valid<T> = IsValidArg<T> extends true ? T : never

declare function isValid<T extends Valid<V>, V = T>(value: T):IsValidArg<T>;

isValid(3); // ok

isValid({}); // wrong, false === IsValidArg
isValid({} as Record<string, any>); // ok
isValid({} as {x?: any}); // ok

isValid(Function); //ok
isValid(class Foo{}); // ok
isValid(new class Foo{}); //wrong, false === IsValidArg
isValid(() => 1); // wrong, false === IsValidArg

isValid(0 as never); // maybe wrong, true === IsValidArg
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@sirian

sirian commented Mar 29, 2018

Copy link
Copy Markdown
Contributor

Leon Adler (@leonadler) I have an idea! We could use reverse extends check. Look at scratch:

type NumOfArgs<F extends Function> = F extends (a: infer A, b: infer B, c: infer C, ...args: (infer Z)[]) => infer R ? (
    ((a: A, b: B, c: C, ...args: Z[]) => R) extends F ? number :
    ((a: A, b: B, c: C) => R) extends F ? 3 :
    ((a: A, b: B) => R) extends F ? 2 :
    ((a: A) => R) extends F ? 1 :
    0
) : never;
sandangel reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ChuckJonas Charlie Jonas (ChuckJonas) mentioned this pull request Mar 29, 2018
March fun react ralphcallaway/bad-ass-salesforce-stack#1
Open
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Mar 29, 2018
Can't dereference array type in conditional type #22985
Closed
@sirian

sirian commented Apr 12, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Anders Hejlsberg (@ahejlsberg) I think many users will copy types from your examples from #21316 (comment).
So some notices about type TypeName<T>

  1. you missed symbol
  2. typeof Object === "function" but TypeName<Object> === "object"
  3. typeof class A{} === "function" but TypeName<A> === "object"
izatop reacted with thumbs up emoji leonadler reacted with thumbs down emoji
All reactions
  • 👍 1 reaction
  • 👎 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@leonadler

Leon Adler (leonadler) commented Apr 13, 2018

Copy link
Copy Markdown

sirian You seem to confuse "type A" with "object of type A".
The TypeName of an object with the type Object is "object".
Calling typeof Object retrieves the type of the constructor, which is "function".
When you call typeof a with an a that has the type Object, you will get "object", as expected.

Similarily, when you write let a: MyClass in TypeScript, it means a is an instance of the type MyClass, not a has the same type that the function MyClass has.

typeof Object === "function"
TypeName<typeof Object> === "function"
typeof Object.create(Object.prototype) === "object"
TypeName<Object> === "object"
class A { }
typeof A === "function"
TypeName<typeof A> === "function"
typeof (new A()) === "object"
TypeName<A> === "object"
SlurpTheo reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@KiaraGrouwstra kiara (KiaraGrouwstra) mentioned this pull request Apr 19, 2018
Subtraction types #4183
Closed
@ericanderson Eric Anderson (ericanderson) mentioned this pull request May 8, 2018
Adds DeepReadonly pelotom/type-zoo#17
Closed
@pleerock

Umed Khudoiberdiev (pleerock) commented May 10, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

type Boxed = T extends any[] ? BoxedArray<T[number]> : BoxedValue;

How is it possible to do following?

T extends R[] ? BoxedArray<R> : BoxedValue<T>
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@princemaple

Po Chen (princemaple) commented May 10, 2018

Copy link
Copy Markdown

Umed Khudoiberdiev (@pleerock) T extends (infer R)[]

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pleerock

Umed Khudoiberdiev (pleerock) commented May 10, 2018

Copy link
Copy Markdown

Po Chen (@princemaple) thanks it worked. Actually BoxedArray<T[number]> worked as well but such syntax wasn't obvious to me

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@thorn0 Georgii Dolzhykov (thorn0) mentioned this pull request May 17, 2018
Type alias circularly references itself #14174
Closed
@laughinghan Han Seoul-Oh (laughinghan) mentioned this pull request May 28, 2018
Add type declaration overloading #17636
Closed
@InExtremaRes Daniel Contreras (InExtremaRes) mentioned this pull request Jun 26, 2018
Parameter of type 'K extends FunctionPropertyNames<T>' is always a union type and not restricted to the string literal passed to function #25215
Closed
@kitsonk Kitson Kelly (kitsonk) mentioned this pull request Jul 12, 2018
Allow declaration declare within conditionals #25570
Closed
1 of 4 tasks
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Jul 16, 2018
"Did you mean" spelling suggestions can offer the same thing being complained about #25564
Closed
@sirian sirian mentioned this pull request Jul 16, 2018
ParamTypes issue pelotom/type-zoo#26
Open
@microsoft Microsoft (microsoft) locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@sandersn Nathan Shively-Sanders (sandersn) sandersn requested changes
@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) DanielRosenwasser left review comments
@weswigham Wesley Wigham (weswigham) weswigham left review comments
+2 more reviewers
@rbuckton Ron Buckton (rbuckton) rbuckton requested changes
@mhegazy Mohamed Hegazy (mhegazy) mhegazy approved these changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

TypeScript 2.8

Development

Successfully merging this pull request may close these issues.

Mapped conditional types Add support for literal type subtraction

Uh oh!

There was an error while loading. Please reload this page.

20 participants

@ahejlsberg @DanielRosenwasser @lyrachord @Retsam @dead-claudia @AlexGalays @jack-williams @AriaMinaei @neoncom @treybrisbane @falsandtru @mbrodersen @sandersn @vendethiel @mohsen1 @KiaraGrouwstra @jcalz @pelotom @zpdDG4gta8XKpMCd @MartinJohns
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.