-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type inference in conditional types #21496
Conversation
# Conflicts: # src/compiler/checker.ts
# Conflicts: # src/compiler/checker.ts # src/compiler/types.ts # tests/baselines/reference/api/tsserverlibrary.d.ts # tests/baselines/reference/api/typescript.d.ts
@simonbuchan You don't need 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 |
The issue is more about how you would describe Basically: Here's another case, where the user is trying to emulate the flow // Must be a promise, but I can't be bothered writing what kind
let foo: Promise<infer T> = asyncMethod(); |
I actually suggested this more general use of |
@jcalz: I can't tell what JS function you're typing there, but now that type Everything = {
foo: (x: {
a: string;
}) => void;
bar: (x: {
b: number;
}) => void;
baz: (x: {
c: boolean;
}) => void;
} .. so apparently these |
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. |
|
@MeirionHughes |
@masaeedu Yeah, my apologies - I was confusing this one with 21316. |
Any updates on recursively handling this? |
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);
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? |
@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- |
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
As |
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:
Within the
extends
clause of a conditional type, it is now possible to haveinfer
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 multipleinfer
locations for the same type variable.A conditional type
T extends U ? X : Y
is either resolved toX
orY
, or deferred because the condition depends on one or more type variables. Whether to resolve or defer is determined as follows:T'
andU'
that are instantiations ofT
andU
where all occurrences of type parameters are replaced withany
, ifT'
is not assignable toU'
, the conditional type is resolved toY
. Intuitively, if the most permissive instantiation ofT
is not assignable to the most permissive instantiation ofU
, we know that no instantiation will be and we can just resolve toY
.infer
declaration withinU
collect a set of candidate types by inferring fromT
toU
(using the same inference algorithm as type inference for generic functions). For a giveninfer
type variableV
, if any candidates were inferred from co-variant positions, the type inferred forV
is a union of those candidates. Otherwise, if any candidates were inferred from contra-variant positions, the type inferred forV
is an intersection of those candidates. Otherwise, the type inferred forV
isnever
.T''
that is an instantiation ofT
where allinfer
type variables are replaced with the types inferred in the previous step, ifT''
is definitely assignable toU
, the conditional type is resolved toX
. 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.Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
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:
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
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).It is not possible to use
infer
declarations in constraint clauses for regular type parameters:However, much the same effect can be obtained by erasing the type variables in the constraint and instead specifying a conditional type: