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 — Type inference in conditional types by ahejlsberg · Pull Request #21496 · microsoft/TypeScript

Conversation

@ahejlsberg

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

Uh oh!

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

Copy link
Copy Markdown
Member

This PR introduces the ability to use type inference in conditional types (#21316), enabling pattern matching for types. For example, the following extracts the return type of a function type:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;

Within the extends clause of a conditional type, it is now possible to have infer declarations that introduce a type variable to be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have multiple infer locations for the same type variable.

A conditional type T extends U ? X : Y is either resolved to X or Y, or deferred because the condition depends on one or more type variables. Whether to resolve or defer is determined as follows:

  • First, given types T' and U' that are instantiations of T and U where all occurrences of type parameters are replaced with any, if T' is not assignable to U', the conditional type is resolved to Y. Intuitively, if the most permissive instantiation of T is not assignable to the most permissive instantiation of U, we know that no instantiation will be and we can just resolve to Y.
  • Next, for each type variable introduced by an infer declaration within U collect a set of candidate types by inferring from T to U (using the same inference algorithm as type inference for generic functions). For a given infer type variable V, if any candidates were inferred from co-variant positions, the type inferred for V is a union of those candidates. Otherwise, if any candidates were inferred from contra-variant positions, the type inferred for V is an intersection of those candidates. Otherwise, the type inferred for V is never.
  • Then, given a type T'' that is an instantiation of T where all infer type variables are replaced with the types inferred in the previous step, if T'' is definitely assignable to U, the conditional type is resolved to X. The definitely assignable relation is the same as the regular assignable relation, except that type variable constraints are not considered. Intuitively, when a type is definitely assignable to another type, we know that it will be assignable for all instantiations of those types.
  • Otherwise, the condition depends on one or more type variables and the conditional type is deferred.

Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:

type Unpacked<T> =
    T extends (infer U)[] ? U :
    T extends (...args: any[]) => infer U ? U :
    T extends Promise<infer U> ? U :
    T;

type T0 = Unpacked<string>;  // string
type T1 = Unpacked<string[]>;  // string
type T2 = Unpacked<() => string>;  // string
type T3 = Unpacked<Promise<string>>;  // string
type T4 = Unpacked<Promise<string>[]>;  // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string

Note that is not possible for a conditional type to recursively reference itself, as might be desired in the Unpacked<T> case above. We're still considering ways in which to implement this.

The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:

type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>;  // string
type T11 = Foo<{ a: string, b: number }>;  // string | number

Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:

type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // string
type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number

When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types (this would require us to support typeof for arbitrary expressions, as suggested in #6606, or something similar).

declare function foo(x: string): number;
declare function foo(x: number): string;
declare function foo(x: string | number): string | number;
type T30 = ReturnType<typeof foo>;  // string | number

It is not possible to use infer declarations in constraint clauses for regular type parameters:

type ReturnType<T extends (...args: any[]) => infer R> = R;  // Error, not supported

However, much the same effect can be obtained by erasing the type variables in the constraint and instead specifying a conditional type:

type AnyFunction = (...args: any[]) => any;
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : never;

Sorry, something went wrong.

Uh oh!

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

ajafff, yortus, jwbay, TylorS, nevir, pelotom, yuit, cartant, tvald, HerringtonDarkholme, and 56 more reacted with thumbs up emoji RyanCavanaugh, phiresky, piotrwitek, Retsam, niieani, kimjoar, jwbay, TylorS, nevir, yuit, and 46 more reacted with hooray emoji piotrwitek, alexrock, dsifford, niieani, jwbay, TylorS, nevir, yuit, treybrisbane, HerringtonDarkholme, and 48 more reacted with heart emoji
All reactions
  • 👍 66 reactions
  • 🎉 56 reactions
  • ❤️ 58 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
@MadaraUchiha

Madara Uchiha (MadaraUchiha) commented Feb 8, 2018

Copy link
Copy Markdown

Simon Buchan (@simonbuchan) You don't need infer for that,

declare function zip<A, B>(a: ReadonlyArray<A>, b: ReadonlyArray<B>): Array<[A, B]>;

You really only need infer when A is part of another type you don't particularly know about, for example, you have T which could be a U[], and you need U.

All reactions

Sorry, something went wrong.

Uh oh!

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

@simonbuchan

Simon Buchan (simonbuchan) commented Feb 8, 2018

Copy link
Copy Markdown

The issue is more about how you would describe infer so that users don't expect to be able to do that. (Or maybe decide that alternate syntax is fine and implement it, I dunno.)

Basically: infer T means something like "introduce a new type parameter that is inferred to the contextual type apon instantiation", but that definition is too wide.

Here's another case, where the user is trying to emulate the flow * type:

// Must be a promise, but I can't be bothered writing what kind
let foo: Promise<infer T> = asyncMethod();
piotrwitek 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.

This was referenced Feb 8, 2018
Extracting return types from functions while mapping types? #15763
Closed
Fix formatting of conditional types #21762
Merged
@benstevens48

benstevens48 commented Feb 10, 2018

Copy link
Copy Markdown

I actually suggested this more general use of infer (with the syntax =T instead of infer T) some time ago in #13949. It will be interesting to see if this is implemented at some point. The current implementation is already very useful though, thanks!

All reactions

Sorry, something went wrong.

Uh oh!

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

@masaeedu

Asad Saeeduddin (masaeedu) commented Feb 11, 2018

Copy link
Copy Markdown
Contributor

it's happening!

grahamc reacted with hooray 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 Feb 11, 2018
Optional Generic Type Inference #14400
Open
@KiaraGrouwstra

kiara (KiaraGrouwstra) commented Feb 11, 2018

Copy link
Copy Markdown
Contributor

Joe Calzaretta (@jcalz): I can't tell what JS function you're typing there, but now that infer is in, I tried to run your type. It doesn't seem to be working yet though. I'm getting this:

type Everything = {
    foo: (x: {
        a: string;
    }) => void;
    bar: (x: {
        b: number;
    }) => void;
    baz: (x: {
        c: boolean;
    }) => void;
}

.. so apparently these Everything instances don't directly have a / b / c.

All reactions

Sorry, something went wrong.

Uh oh!

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

@jcalz

Joe Calzaretta (jcalz) commented Feb 11, 2018

Copy link
Copy Markdown
Contributor

@tycho01

Hmm, I just checked 2.8.0-dev.20180211 and it gives:

type Everything = {
    a: string;
} & {
    b: number;
} & {
    c: boolean;
}

which is indeed the intersection type I was aiming for. Not sure why you see something else.

All reactions

Sorry, something went wrong.

Uh oh!

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

@KiaraGrouwstra kiara (KiaraGrouwstra) mentioned this pull request Feb 12, 2018
Proposal: Get the type of any expression with typeof #6606
Closed
@azu azu (azu) mentioned this pull request Feb 13, 2018
TypeScript: improve UseCase#execute typing almin/almin#107
Closed
@MeirionHughes

Meirion Hughes (MeirionHughes) commented Feb 13, 2018 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown

a welcome addition; but don't you think ReturnType<T> is confusing? I would expect it to return the actual typescript Type of some function T. Perhaps ReturnPrimitive<T> would allow for a future ReturnType that is a type evaluator can be used for currying? I guess in the future this current ReturnType implementation could be extended though?

All reactions

Sorry, something went wrong.

Uh oh!

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

@masaeedu

Asad Saeeduddin (masaeedu) commented Feb 13, 2018 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

Meirion Hughes (@MeirionHughes) ReturnType does "return" the return type of a TypeScript function type. Perhaps you're talking about a runtime representation of a type, which at least currently is against the stated goals of the project (see #1573).

All reactions

Sorry, something went wrong.

Uh oh!

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

@MeirionHughes

Meirion Hughes (MeirionHughes) commented Feb 13, 2018 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown

Asad Saeeduddin (@masaeedu) Yeah, my apologies - I was confusing this one with 21316.

All reactions

Sorry, something went wrong.

Uh oh!

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

@MeirionHughes Meirion Hughes (MeirionHughes) mentioned this pull request Feb 13, 2018
feat: restore typings + testing Level/levelup#499
Closed
@ericanderson

Eric Anderson (ericanderson) commented Feb 14, 2018

Copy link
Copy Markdown
Contributor

Any updates on recursively handling this?

All reactions

Sorry, something went wrong.

Uh oh!

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

@KiaraGrouwstra

kiara (KiaraGrouwstra) commented Feb 14, 2018

Copy link
Copy Markdown
Contributor

Eric Anderson (@ericanderson) #21613 (comment)

All reactions

Sorry, something went wrong.

Uh oh!

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

@ghost ghost mentioned this pull request Feb 16, 2018
ReturnType<ReturnType<>> in type alias results in never #21997
Closed
@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) mentioned this pull request Feb 16, 2018
TypeScript 2.8 meta-issue babel/babel#7389
Closed
3 tasks done
@MeirionHughes

Meirion Hughes (MeirionHughes) commented Feb 18, 2018 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown

I'm trying to do a promisify and it seems to die if you give it an overloaded method.

The single case works perfectly fine:

type Promisify<T> =
  T extends (cb: (v: infer V) => void) => void ? () => Promise<V> :
  T extends (a: infer A, cb: (v: infer V) => void) => void ? (a: A) => Promise<V> :
  T extends (a: infer A, b: infer B, cb: (v: infer V) => void) => void ? (a: A, b: B) => Promise<V> :
  T;

type Foo = ((a: string, cb: (v) => void) => void)

let bar: Promisify<Foo>;

overload it and it dies completely:

type Foo =
  ((a: string, cb: (v) => void) => void) &
  ((a: string, b: number, cb: (v) => void) => void);

bug? already reported?

never mind - its mentioned it isn't supported - still, it shows nothing (first post says it should be picking the last signature). So maybe it is a bug?

KiaraGrouwstra 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.

@ghost ghost mentioned this pull request Feb 20, 2018
TypeScript: Support conditional types syntax babel/babel#7404
Merged
@MeirionHughes

Meirion Hughes (MeirionHughes) commented Feb 23, 2018 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown

Anders Hejlsberg (@ahejlsberg) this limitation on the function overloads is super annoying (not to sound ungrateful: this pr is awesome) - I can't seem to find a way to "mask" the input function (pick from the overloads) either.

You mention you choose the last function from the available ones. Would it not be possible to try each until you arrive at a non-never result?

All reactions

Sorry, something went wrong.

Uh oh!

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

Nicolò Ribaudo (nicolo-ribaudo) pushed a commit to babel/babel that referenced this pull request Feb 24, 2018
Andy @nicolo-ribaudo
TypeScript: Support conditional types syntax (#7404)
6f6c8da
microsoft/TypeScript#21316 and microsoft/TypeScript#21496
Ben Newman (benjamn) added a commit to benjamn/recast that referenced this pull request Feb 24, 2018
@benjamn
Implement printer cases for TS{Conditional,Infer}Type.
eb043a4
Although (infer T) must frequently be parenthesized to avoid ambiguity,
Recast does not have to encode that logic into FastPath#needsParens,
because Babylon wraps such types with TSParenthesizedType. Cool!

babel/babel#7404
benjamn/ast-types@da0367b
microsoft/TypeScript#21316
microsoft/TypeScript#21496
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
@chrisgervang Chris Gervang (chrisgervang) mentioned this pull request Apr 6, 2018
TypeScript 2.7 and 2.8 features niieani/typescript-vs-flowtype#26
Open
@kpdonn Kevin Donnelly (kpdonn) mentioned this pull request Apr 25, 2018
Proposal: new "invalid" type to indicate custom invalid states #23689
Open
@donaldpipowitch

Donald Pipowitch (donaldpipowitch) commented May 16, 2018

Copy link
Copy Markdown
Contributor

As ReturnType was added as a built-in helper are there plans to add Unpacked as well?

All reactions

Sorry, something went wrong.

Uh oh!

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

@nicojs Nico Jansen (nicojs) mentioned this pull request Jul 30, 2018
feat(child process): Make all child processes silent stryker-mutator/stryker-js#1039
Merged
@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

@rbuckton Ron Buckton (rbuckton) Awaiting requested review from rbuckton
@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) Awaiting requested review from DanielRosenwasser
1 more reviewer
@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.

Uh oh!

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

18 participants

@ahejlsberg @phiresky @jack-williams @kastermester @rictic @mhegazy @Igorbek @rbuckton @jcalz @gcanti @simonbuchan @MadaraUchiha @benstevens48 @masaeedu @KiaraGrouwstra @MeirionHughes @ericanderson @donaldpipowitch
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.