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

Use return type of a function #4002

Closed
nickmessing opened this issue May 22, 2017 · 22 comments
Closed

Use return type of a function #4002

nickmessing opened this issue May 22, 2017 · 22 comments

Comments

@nickmessing
Copy link

Flow finds return type of functions very well, can we use that type in other declarations in any way?

Example of what I would like to achieve:

const mapStateToProps =  (state: ReduxState) => ({
  val1: selector1(state),
  val2: selector2(state),
})

type Props = typeof mapStateToProps()

class Home extends Component {
  props: Props;
  render () {
    return <div />
  }
}

const connector = connect(mapStateToProps);

export default connector(Home);
@vkurchatkin
Copy link
Contributor

There are a couple of hacky ways to do this:

  1. Use "existential" (star) type:
type Return_<R, Fn: () => R> = R;
type Return<T> = Return_<*, T>;

function foo() {
  return 1;
}

const x: Return<typeof foo> = ''; // error
  1. Call the function inside of a Flow comment block:
function foo() {
  return 1;
}

/*::
const __result = foo();
*/

const x: typeof __result = '';

@futpib
Copy link

futpib commented May 23, 2017

@vkurchatkin Awesome example, I am now wondering why does the same trick not work for the function arguments:

type Return_<R, F: (...args: Array<any>) => R> = R;
type Return<T> = Return_<*, T>;

type Arguments_<A, F: (...args: A) => any> = A;
type Arguments<F> = Arguments_<*, F>;

function foo(n: number) {
  return n;
}

const r: Return<typeof foo> = ''; // error
const a: Arguments<typeof foo> = ['']; // no error :(

https://flow.org/try/#0C4TwDgpgBAShwFcBOA7A+gHhgGigMQC4oAKAOnIEMkBzAZyIEEkkKQMKUQA+ASigF4usIf1gBuAFChIseMhQYAKiNmJUmAFS5lkqeGhNqCALYQUwWpga5CJcqSp1GfQVA7cBUBpOkGaJswsMPBVDAPNLDC18Ll0AMwQUAGNgAEsAexQoOPT04hQiFBMAIwgkPgBvCSgoJDlUKBRJAF8JCSTM2mBaojg1BV90uOzclQByMbEoAHppqDKkdKR2zu6KRn9TCIxB4Zz0lQBtCYBdKdnG9PnmJagCYiA

@vkurchatkin
Copy link
Contributor

That's because type of arguments flows in the opposite direction. * captures whatever flows in, not what flows out. Simple example:

var x: * = 1; // number ~> *
declare var y: typeof x;
(y: string); // error

declare function fn(x: number): void;

declare var z: *;

fn(z); // * ~> number

declare var u: typeof z;
(u: string); // no error

The same happens when function flows into another function, like in your example.

@vovacodes
Copy link

vovacodes commented Aug 6, 2017

@vkurchatkin Do you think your Return utility type could be added to the language? Seems like a useful thing

@vkurchatkin
Copy link
Contributor

Not Return specifically, but Apply makes sense to me

@sibelius
Copy link

@nickmessing is this working for u? Do u have a repo example of this?

@nickmessing
Copy link
Author

@sibelius, yes, this helper works fine:

type Return_<R, F: (...args: Array<any>) => R> = R;
type Return<T> = Return_<*, T>;

@sibelius
Copy link

Add ExtractReturn helper

// https://hackernoon.com/redux-flow-type-getting-the-maximum-benefit-from-the-fewest-key-strokes-5c006c54ec87
// https://github.com/facebook/flow/issues/4002
// eslint-disable-next-line no-unused-vars
type _ExtractReturn<B, F: (...args: any[]) => B> = B;
export type ExtractReturn<F> = _ExtractReturn<*, F>;

Extract return of mapStateToProps and mapDispatchToProps

const mapStateToProps = state => ({
  user: state.user,
});
const mapDispatchToProps = dispatch => ({
  actions: {
    logout: () => dispatch(logout()),
  },
});

type ReduxProps = ExtractReturn<typeof mapStateToProps>;
type ReduxActions = ExtractReturn<typeof mapDispatchToProps>;

Add ReduxProps and ReduxActions to your Component props

type Props = {} & ReduxProps & ReduxState

class EntriaComp extends Component<Props> {}

I think we can use $Call to improve this as well

cc @calebmer

can we close this?

@iddan
Copy link

iddan commented Nov 29, 2017

Can ExtractReturn be added to the global Flow helpers as $Return?

@sibelius
Copy link

you can use $Call instead of ExtractReturn

@AlicanC
Copy link
Contributor

AlicanC commented Dec 26, 2017

Proposed solutions do not work for my case: Flow Try

I need to explicitly type returns of actionCreators methods to make these work: Flow Try

The dream is using $ObjMap to completely avoid duplication:

const actionCreators = {
  // ...
};

export type Action = $Values<$ObjMap<typeof actionCreators, ExtractReturnType>>;

Edit: I think my problem is that the inferred return types not being literal. I guess what I need from Flow is return type of a function like () => 'hey' being inferred as 'hey' instead of string.

@mgtitimoli
Copy link

mgtitimoli commented Apr 5, 2018

Hi everyone,

@vkurchatkin, so based on what you wrote on this comment, I assume there is no way to extract function arguments, or is any?

@kangax
Copy link

kangax commented Apr 26, 2018

I can't get it to work either. Here's a very simple example.

Does not throw:

type Props = ReduxProps & { user: string };
this.props.user.foo();

Does throw:

type Props = { user: string };
this.props.user.foo();

When using ExtractReturn helper, user type being "string" should be properly propagated from global state all the way to this.props. But it doesn't.

UPDATE

Finally got it to work using spread and exact helper!

type Props = {|
   ...ReduxProps,
  customProp: customType
|}

@TrySound
Copy link
Contributor

This probably can be closed.
/cc @mrkev

@mgtitimoli
Copy link

mgtitimoli commented Apr 26, 2018

Hi @kangax,

I've just seen your comment, would you mind to share ReduxProps type?

I'm asking you this as I believe the error you were having was caused by the fact that when intersecting types (using &) if any of the them share a property, the resulting type would hold on that property an union of all the values for it, does it make sense? was your error caused by this?

@sibelius
Copy link

this is the version using $Call, it is much faster than using *

export type ExtractReturn<Fn> = $Call<<T>((...Iterable<any>) => T) => T, Fn>;

Example of extracting ReduxProps:

type ReduxProps = ExtractReturn<typeof mapStateToProps>;

https://flow.org/try/#0C4TwDgpgBAogHsATgQwMbAEoWAV0QOwB4AxfAPigF4oASAYWQBtHDCAVMgCk4Do+BJYBBQAjRhELJ8IMgEoqFNvMqKANFFJkA3ACgoO0JChYAJjjgBlYMiFUoAbz1RUALigBnJAEt8Ac1U6AL56OgBmOPjoXgD2+FDInJ42EG6m5lbJ8o5QUIjYeHHZOVAibklCPKgBOcHBYRFRsSWcYMgoALbubvBIaJj5BISGENGh8XIOTi1tyJ08pR7efrK6OdMd7vNuItHR4lIrQUA

@sibelius
Copy link

can we close this?

@mrkev
Copy link
Contributor

mrkev commented Jun 15, 2018

Sure 👍

@ggregoire
Copy link

ggregoire commented Aug 27, 2018

I'm having some issues with typing mapDispatchToProps as an object of actions:

const mapActionsToProps = {
  logout
}

connect(null, mapActionsToProps)(Comp)

Since it's a simple object of functions, I should be able to use typeof, right?

type ReduxActions = typeof mapActionsToProps

type Props = {} & ReduxActions // or type Props = { ...ReduxActions }

class Comp extends Component<Props> {}

But it doesn't work, Flow loses the inferred type of the actions and see them as any.

I reported the bug (?) here: #6797

Does someone have a workaround? Or am I doing something wrong? (cc @sibelius )

Edit: so it appears only when the actions are imported from another file. 🤯

@punksta
Copy link

punksta commented Feb 11, 2019

Hello. I am trying to extract union type of "foo": type Foo = "a" | "b" | "c"
but I could get only type Foo = "string"|"string"|"string"

const getObject = () => {
  const r = {
      a: {foo: "a"},
      b: {foo: "b"},
      c: {foo: "c"}
  }
  return r;
 }

type ResultType = $Call<typeof getObject>;
type Values = $Values<ResultType>
type AUnion = $PropertyType<Values, "foo"> // should be union of literals, but it's union of strings
  
const a: AUnion = "a"
const b: AUnion = "b"

//should be an error here
const fooBar: AUnion = "bar"

https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAodBjOA7AzgFzAHMBTfAeQCMArEjQgXjAAoBKMBgPjAG9UwwWPIQBOHXvwFSAhgC5eUOHHkAiaSoC+AGklSwleT0XKwKypp16BGQ8dUZNuqcGBhpYeIThQwcfAAsSMQBrEgBPXDAEAEsAhSVVAhFo7CIPWKDpGE0wADcsgFcSSQ1JETICkWwwEQBuflLUfDCABxIwACUSXAKYfAAVVvamABIAYSyYAB5mtu9iMipaek562faANULu8RGtmCLcKa6evsG2ziahsABBAFVsaJxdgAURODaRZvOSKf3DrSmYwqbguMC4fxwXoAE307QKj2e8xgGSqWVwgMoBUIsQA5JEEU9qvMkikiLhJKghAQ3PJ7ojqkw1CoqTgaQZbg8ieIzCzUC4IVCYLDKO1ogBbFpwXDRSgwYrUwjGABC0hEdK5zyZxkoapZQA

@alexandrricov
Copy link

alexandrricov commented Jun 13, 2019

Hello, can't figure out how to use $Call<typeof myfunc>, in case myfunc returns promise.
Smth like:

function asd(): Promise<number> {
  return Promise.resolve(3);
}

(async () => {
  const num: $Call<typeof asd> = await asd();
})()

Getting error:

7:   const num: $Call<typeof asd> = await asd();
                                    ^ Cannot assign `await asd()` to `num` because number [1] is incompatible with `Promise` [2].
References:
1: function asd(): Promise<number> {
                           ^ [1]
1: function asd(): Promise<number> {
                   ^ [2]

Promise chain also not working as expected:

function asd(): Promise<number> {
  return Promise.resolve(3);
}

asd().then((num: $Call<typeof asd>) => {});

Getting error:

5: asd().then((num: $Call<typeof asd>) => {});
                    ^ Cannot call `asd().then` because `Promise` [1] is incompatible with number [2] in the first argument.
References:
1: function asd(): Promise<number> {
                   ^ [1]
1: function asd(): Promise<number> {
                           ^ [2]

https://flow.org/try/#0GYVwdgxgLglg9mABAQwM4BMAUBKAXIgBQCc4BbGVAUwB4wRSAjSogPkQG8AoASCMqhBEkxMhUoA6PqjgAbAG6VMAZmwBuTgF9O2zGgCekRDkQBeNl06JEEBKiiI6pfABIAwshkzqUPQAdKcMAoGGwmKADuyDD2aFhqmtg42kA

@jcready
Copy link
Contributor

jcready commented Jun 14, 2019

@alexandrricov

function asd(): Promise<number> {
  return Promise.resolve(3);
}

(async () => {
  const num: $Call<typeof $await, $Call<typeof asd>> = await asd();
})()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests