Typescript: Intersection of two generic params to form a third is not recognized as equivalent

Created on 13 Jan 2019  ·  3Comments  ·  Source: microsoft/TypeScript


TypeScript Version: 3.3.0-dev.201xxxxx


Search Terms:

Intersection of generics

Code

let connect = <R1,R2, R = R1&R2 >(mapState2Props: (state) => R1, mapDispatch2Props: (dispatch: any) => R2): R => {
  let state = {};
  let dispatch = () => { };
  return { ...mapState2Props(state), ...mapDispatch2Props(dispatch)}
}

Expected behavior:
I would expect that this would be valid because I have defined R = R1&R2 in the generic signature.

Actual behavior:

Get an error Type 'R1 & R2' is not assignable to type 'R'. on the return statement. I've also tried R extends R1&R2 and I get the same error.

Playground Link:

Link

Related Issues:

https://github.com/Microsoft/TypeScript/issues/5823

Question

Most helpful comment

I believe the error is correct here. Yes R1 & R2 is assignable to the default of R, but R might be instantiated with a _more_ specific type. Consider the following instantiation.

connect<{ x: number; }, { y: number; }, { x: number, y: number, z: number }>( ... );

This is a valid instantiation of R1, R2, and R, but R1 & R2 is _not_ assignable to R because it is missing the z parameter.

In your example the R parameter is not actually required (though your real use-case may be more complicated). This works:

let connect = <R1,R2>(mapState2Props: (state) => R1, mapDispatch2Props: (dispatch: any) => R2): R1 & R2 => {
  let state = {};
  let dispatch = () => { };
  return { ...mapState2Props(state), ...mapDispatch2Props(dispatch)}
}

All 3 comments

I believe the error is correct here. Yes R1 & R2 is assignable to the default of R, but R might be instantiated with a _more_ specific type. Consider the following instantiation.

connect<{ x: number; }, { y: number; }, { x: number, y: number, z: number }>( ... );

This is a valid instantiation of R1, R2, and R, but R1 & R2 is _not_ assignable to R because it is missing the z parameter.

In your example the R parameter is not actually required (though your real use-case may be more complicated). This works:

let connect = <R1,R2>(mapState2Props: (state) => R1, mapDispatch2Props: (dispatch: any) => R2): R1 & R2 => {
  let state = {};
  let dispatch = () => { };
  return { ...mapState2Props(state), ...mapDispatch2Props(dispatch)}
}

hmm ya that's a good point. The goal here was to eventually get to where you could just specify the return type R and get type safety that mapState2Props & mapDispatch2Props combined would result in a valid R Object.

EG:

interface IComponentProps{
  foo: string;
  doFoo: ()=>void
}
let props: IComponentProps = connect<IComponentProps>(
  (state) => { foo: state.foo },
  (dispatch) => {doFoo: () => dispatch('FOO')}
)

However... I'm not sure if that is even possible... Seem like you would need to instead define R1 & R2 as some subset of R, but they are dependent on each other...

Note that R could also be a completely unrelated type. Default type arguments in generics are not constraints.

connect<{ a: string }, { b: number }, { c: boolean[] }>(() => ({ a: "a" }), () => ({ b: 3 }))
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kimamula picture kimamula  ·  147Comments

blakeembrey picture blakeembrey  ·  171Comments

metaweta picture metaweta  ·  140Comments

rwyborn picture rwyborn  ·  210Comments

chanon picture chanon  ·  138Comments