Repository navigation
Inference failing for conditional types in function parameters #33369
Description
Activity
This type,
type FooQueryLike<T> = T extends (foo: FooQueryType<T>, ...args: any[]) => boolean ? T : never
And your usage of it are very suspicious.
I've also noticed you don't constrain your type parameters and choose to leave them implicitly unknown.
You should constrain them as much as possible.
For example, this,
export type FooType<T> = T extends Foo<infer U> ? U : never
Can be rewritten as,
export type FooType<T extends Foo<any>> = T["bar"]
Saving you the need to use a conditional type. Conditional types should really be more of a last resort.
I'm on mobile now so I can't poke at it more.
Upon a little more inspection, your types can be greatly simplified but I'm not going to do that on my phone =x
Okay, I tried to simplify on mobile, lol,
export interface Foo<T=any> { bar: T } type Query = (foo: Foo, ...args: any[]) => boolean; export type QueryFoo<T extends Query> = T extends (foo: infer U, ...args: any[]) => boolean ? U : never export type QueryParameters<T extends Query> = T extends (foo: any, ...args: infer U) => boolean ? U : never export type Result<T extends Foo> = (foo: T) => boolean declare function match<T extends Query> (query : T, ...args : QueryParameters<T>) : ( Result<QueryFoo<T>> );
[Edit]
Removed unnecessary type param fromQueryI'm even tempted to say this should work,
export interface Foo<T=any> { bar: T } export type Result<FooT extends Foo> = (foo: FooT) => boolean declare function match< FooT extends Foo, ParamsT extends readonly any[] > ( query : (foo : FooT, ...args : ParamsT) => boolean, ...args : ParamsT ) : ( Result<FooT> );
However, I'm on mobile and can't test that
AnyhowStep thanks for your input on this. I agree with you in general, but actually, the
FooLikeinterface which I defined is necessary in my case, as I have interfaces extending fromFoo. The conditional types help preserve the extended interface type, which means that the type inference will correctly infer the extended type ofFoowhen usingFooLikeand notFoo. Also, the generic type parameter ofFoo(which you defaulted toany) is necessary in my case.I just tested and the type of
foois preserved,export interface Foo<T=any> { bar: T } export type Result<FooT extends Foo> = (foo: FooT) => boolean declare function match< FooT extends Foo, ParamsT extends readonly any[] > ( query : (foo : FooT, ...args : ParamsT) => boolean, ...args : ParamsT ) : ( Result<FooT> ); interface MyFooLike { bar : "haha", baz : "hehe", } declare function myQuery ( foo : MyFooLike, arg0 : number, arg1 : Date ) : boolean; //const result: Result<MyFooLike> const result = match(myQuery, 3.141, new Date()); //type resultParams = [MyFooLike] type resultParams = Parameters<typeof result>;
You can see
myQueryhasMyFooLikeand theresultalso hasMyFooLike.MyFooLikeextendsFooand is still inferred and preserved.- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Sep 13, 2019 OK, so the issue is due to contextual typing; specifically recent additions around generic signatures. The contextual type of
queryin the functioning case isT-Tis unconstrained and has no signatures, which means we forward along the type ofqueryunmodified into inference (which is then immediately matched againstT). In the "broken" case, the argument type isFooQueryLike<T>, which does have signatures, which causes us to go "ah, we may need to unify the type parameters inquerywith the arguments ofFooQueryLike. Let's defer inference to them until the next inference stage". We then produceunknownas the inference result forTduring that first stage, since we made no successful inferences. Before moving onto the next stage, we check that none of our inferences have already made the signature invalid, but, whatdoyouknow, thatunknown(from having made no inferences whatsoever), when fed through the arguments, becomesneverfor the first argument and triggers an error, causing us to bail on the second inference phase and immediately issue an error. Generally speaking we assume that when a type parameter is instantiated with it's constraint, a call should succeed, as the constraint it supposedly strictly "broader" than the type parameter itself; but conditionals throw a massive wrench into this, as the constraint of an unconstrained type parameter isn't going toextendanything in a conditional, which means those conditions are always going to evaluate to theirfalsebranches, which, more often than not, will produce aneverwhich in turn disallows any actual assignments.Reacted by Martin DonathReacted by AnyhowStep25 remaining items
- addedDuplicateAn existing issue was already createdAn existing issue was already createdand removedBugA bug in TypeScriptA bug in TypeScriptDomain: Conditional TypesThe issue relates to conditional typesThe issue relates to conditional typesDomain: check: Type InferenceRelated to type inference performed during signature resolution or `infer` type resolutionRelated to type inference performed during signature resolution or `infer` type resolutionRescheduledThis issue was previously scheduled to an earlier milestoneThis issue was previously scheduled to an earlier milestone
on Feb 14, 2023 RyanCavanaugh commented
on Feb 14, 2023 MemberMore actionsBased on the above explanation, I'm going to track this as a duplicate of #26933
- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
When using generics with conditionals in a function's signature, type inference seems to break down. I narrowed the problem down to the following reproducible case. Interestingly, the issues vanishes when another generic "helper" parameter is introduced, as can be seen in the example.
TypeScript Version: 3.6.3, 3.6.2, 3.5.x
Search Terms: parameter, inference, functions, generic, conditional types
Code
Expected behavior:
matchWithBrokenInferenceinfersquerycorrectly.Actual behavior:
matchWithBrokenInferenceinfersqueryincorrectly.Playground Link: reproducible example
Related Issues: None found