Skip to content
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

Higher order function inference breaks with multiple overloads #30369

Closed
OliverJAsh opened this issue Mar 13, 2019 · 5 comments
Closed

Higher order function inference breaks with multiple overloads #30369

OliverJAsh opened this issue Mar 13, 2019 · 5 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Mar 13, 2019

TypeScript Version: 3.3.3333, 3.4.0-dev.20190313

Search Terms: higher order function generic param inference any reselect

Code

This is an issue I found whilst using the popular Reselect library, however I believe it is a bug with TypeScript rather than the type definitions. Here is a minimal repro.

type Selector<S, R> = (state: S) => R;
type ParametricSelector<S, P, R> = (state: S, props: P) => R;

// Comment out this overload and things work as expected
declare function createSelector<S, R1, R2>(
    selector1: Selector<S, R1>,
    selector2: Selector<S, R2>,
): void;
declare function createSelector<S, P, R1, R2>(
    selector1: ParametricSelector<S, P, R1>,
    selector2: ParametricSelector<S, P, R2>,
): void;

type Props = { foo: 1 };
createSelector(
    (
        // Expected `state` param type to be inferred as `{}` (fallback)
        // Actual type: `any`
        state,
        props: Props,
    ) => props.foo,

    (
        // Expected `state` param type to be inferred as `{}` (fallback)
        // Actual type: `any`
        state,

        // Expected `props` param type to be inferred as `Props`
        // Actual type: `any`
        props,
    ) => props.foo,
);

// Workaround: annotate first function's state param
type State = {};
createSelector(
    (state: State, props: Props) => props.foo,
    (state, props) => props.foo,
);
@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Mar 25, 2019
@RyanCavanaugh
Copy link
Member

In general we can't do overload resolution at the same time as inference and will usually be working with either the first or the last overload, depending on the situation

@OliverJAsh
Copy link
Contributor Author

Thanks for the explanation. How come any is used as the fallback instead of {} (which is the usual generic inference fallback)?

@jituanlin
Copy link

jituanlin commented Jun 6, 2019

Thanks for the explanation. How come any is used as the fallback instead of {} (which is the usual generic inference fallback)?

In your example code:

createSelector(
    (
        // Expected `state` param type to be inferred as `{}` (fallback)
        // Actual type: `any`
        state,
        props: Props,
    ) => props.foo,

The state param cannot be infer as other type except any(I test it in [email protected], the result is unknown), because your code doesn't provide any information about the state param.
And the following code:

Expected props param type to be inferred as Props

If you comment the overload type signature, it will work well, because the compiler can infer it's type from
the previous context:

    (
        // Expected `state` param type to be inferred as `{}` (fallback)
        // Actual type: `any`
        state,
        props: Props,
    ) => props.foo,

So, in your example code, the props and state can't be refer as you expect because two different reason.

@jituanlin
Copy link

jituanlin commented Jun 6, 2019

In general we can't do overload resolution at the same time as inference and will usually be working with either the first or the last overload, depending on the situation

It is true, however, is there any solution?
Or, this behavior is OK?
I try the following code:

const f = <I, R>(f: (x: I) => R): ((x: I) => R) => (x: I) => f(x);
interface Fn {
  (x: number): number;
  (x: string): string;
}

declare const fn: Fn;

// type of `r` is (x:string)=>string, the `union type` property is lost
const r = f(fn);

The result type is make me upset.


I found a solution for the above case:

const f = <I, R ,F extends (x: I) => R>(f: F): F => {
    return ((x: I) => f(x) )as unknown as F
};
interface Fn {
    (x: number): number;
    (x: string): string;
}

declare const fn: Fn;

// type of `r` is Fn, `union type` property is preserve
const r = f(fn);
// work 
const v = r('1')

But it work just for the above specify case, it not solve the problem:

When unpack union type of function to something like (x:T)=>R, the union type property is lost

@Avaq

This comment has been minimized.

arte-dev pushed a commit to eDatos/external-users that referenced this issue Mar 10, 2022
> Sobrecarga las funciones de response-utils para que solo haya que
llamar a una. El problema es que los tipos todavía no funcionan del todo
bien (véase microsoft/TypeScript#30369 (comment)).
> Añade tests para el response-utils.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants