Skip to content

Infer type in conditional cannot unify generics #22617

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

Open
ivogabe opened this issue Mar 15, 2018 · 6 comments
Open

Infer type in conditional cannot unify generics #22617

ivogabe opened this issue Mar 15, 2018 · 6 comments
Assignees
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@ivogabe
Copy link
Contributor

ivogabe commented Mar 15, 2018

TypeScript Version: 2.8.0-dev.20180315

Search Terms:
infer conditional unify generic function

Code

type Apply1<T, V> = T extends (item: V) => infer R ? R : never;
type A1 = Apply1< (item: string) => string[] , string>;
type B1 = Apply1< <U>(item: U) => U[], string>;

type Apply2<T, V> = T extends (item: V, obj: infer R) => any ? R : never;
type A2 = Apply2< (item: string, obj: string[]) => any , string>;
type B2 = Apply2< <U>(item: U, obj: U[]) => any , string>;

type Apply3<T, V> = T extends (item: V, obj: infer R) => infer R ? R : never;
type A3 = Apply3< (item: string, obj: string[]) => string[] , string>;
type B3 = Apply3< <U>(item: U, obj: U[]) => U[] , string>;
{
	"compilerOptions": {
		"allowJs": true,
		"target": "es6",
		"module": "commonjs",
		"outDir": "dest",
		"strictNullChecks": true,
		"jsx": "preserve",
		"strictFunctionTypes": true
	}
}

Expected behavior:
All A and B types are inferred to string[]. For types B, the type parameter U should be unified with string.

Actual behavior:
All B types are inferred to {}[].

Related Issues:
#22615 (different problem, but similar input)

@ahejlsberg
Copy link
Member

Repeating my comments from #22615 as they apply equally here:

It is effectively a design limitation. We have the concept of instantiating a generic function type in the context of a non-generic function type (a form of unification), but we currently don't do that in conditional types. Instead, function type parameters are erased to their constraints and we infer from those. In the example above, that causes us to infer {} for the infer R type parameter.

The proper fix for this would be have type inference perform instantiation of a source type in the context of a target type when the source type is a generic function type.

@ahejlsberg ahejlsberg added the Suggestion An idea for TypeScript label Mar 21, 2018
@ahejlsberg ahejlsberg self-assigned this Mar 21, 2018
@RyanCavanaugh RyanCavanaugh added the Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. label Mar 27, 2018
@SimonMeskens
Copy link

It's a design limitation, but Suggestion and Need proposal were added instead of closing. Does this mean there's a slim chance this could land eventually? This specific use case of a generic input type mapped onto the output is really common and this issue is stopping quite a few use cases from being typed properly.

@DuncanWalter
Copy link

Any new thoughts/insights? IMHO, now that v3.0 is out, this issue has become more interesting. It looks like this issue is the cause of this behavior:

type BindConsumer<T, M extends <A>(a: A) => any> = 
  M extends ((a: T) => infer U)
    ? U
    : never

const a: BindConsumer<number, <U>(a: U) => { boxed: U }> = {
  boxed: 2,  // infers the type: { boxed: {} }
}

If (strong if) this is a bug/potential feature, then fixing it will allow for all sorts of neat types like:

// Can be used for function declarations like zip and even conditional mapping.
// mapped types on tuples could also make this work.
type MapTuple<Tuple extends any[], Mapping extends <T>(a: T) => any> = never // long and tedious

// Good for all manner of composition/piping.
// Folds the tuple's types into one by inferring the return of Join.
// mapped types on tuples probably isn't good enough for this?
type FoldTuple<Tuple extends any[], Join extends <T, U>(a: T, b: U) => any, Seed> = never // longer and more tedious

Though, any type made this way can be expressed, albeit more verbosely.

@fazouane-marouane
Copy link

Hi,

I'll add a simple example related to this:

type GenericReturnType<T, TInput> = T extends (_: TInput) => infer TResult? TResult: never;

// Gives { x: {}; }
type Box = <T>(_: T)=> { value: T };
type _test1 = GenericReturnType<Box, number>;

// Gives {x: number; }
type AltBox<T> = (_: T)=> { value: T };
type _test2 = GenericReturnType<AltBox<number> | AltBox<string>, number>;

@thomasmikava
Copy link

thomasmikava commented Jul 18, 2020

@ahejlsberg The fix for this was planned to be in TypeScript 2.9 milestone, but then it has been removed (judging from #22615 history). Is there any plan to include this in the milestones of 4.1, 4.2 or higher versions? Since it might have many use cases for higher order functions

@thomasmikava
Copy link

One of the use cases that can be achieved by fixing this limitation:

const getRef = <T extends any>(val: T) => {
    return {
        current: val,
    };
};

const getWrapInValueFn = <Fn extends (arg: any) => any>(fn: Fn) => {
    return <A extends Parameters<Fn>[0]>(
        arg: A
    ): { value: Fn extends (arg: A) => infer R ? R : never } => {
        return {
            value: fn(arg),
        };
    };
};
const wrapInValue = getWrapInValueFn(getRef);
const data = wrapInValue(5); // infsers { value: { current: unknown; }; } instead of { value: { current: number; }; }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants