Typescript: Type 'P' is not assignable to type 'boolean'.

Created on 28 Apr 2019  路  6Comments  路  Source: microsoft/TypeScript

I am actively looking suggestion to better describe this issue. We discussed it over three days now in the Gitter channel. Most recent reference at the time of writing these lines is :point_up: April 28, 2019 10:26 PM


TypeScript Version: 3.5.4


Search Terms: Type 'T' is not assignable to type '

Code

// import { Action } from 'redux';
type Action<T> = { type: T }

const actions: ActionMap<Type> = {
    setIsPublic: ( payload: boolean ) => ({ type: 'z' as Type, payload }),
//  ^^^^^^^^^^^
};

interface ActionMap<T extends string>
{
    [ name: string ]: <P>( payload: P ) => Action<T> & { payload: P };
}

enum Type { SET_PUBLIC = 'SET_PUBLIC', SET_PRIVATE = 'SET_PRIVATE' };

Expected behavior: No error

Actual behavior: Error at setIsPublic

Type '(payload: boolean) => { type: Type; payload: boolean; }' is not assignable to type '<P>(payload: P) => Action<Type> & { payload: P; }'.
  Types of parameters 'payload' and 'payload' are incompatible.
    Type 'P' is not assignable to type 'boolean'.

Playground Link: Follow me

Related Issues: No

Question

Most helpful comment

@SalathielGenese I'm not sure why you would expect identity = lowercase; to work, if we try to use identity we are sure to get an error:

let identity = <I>( i: I ) => i;
let lowercase = (string: string) => string.toLowerCase();
identity = lowercase; // If this were allowed
identity(1) // this would ok, since identity is generic, so I is decided by the caller, but it would cause a runtime error

Generics give the caller (ie the call site) control over what I will be. As such any function that is an implementation for the generic function <I>( i: I ) => I must be able to handle any possible I passed into it. lowercase can only handle string. Typescript is right to scream at this.

@RyanCavanaugh had a fun tweet about this:

Why is this code illegal?

function func<T extends string>(x: T): T {
 return "";
}

Every drink in a bar comes in a glass (the constraint), but the waiter (the function) can't just bring you an empty glass (the return value) if you asked for a beer (T)

Same thing here, the bartender (identity) needs to serve you what you ordered (for example number) it can't just decide to serve you string.

All 6 comments

This is a correct error. ActionMap's definition says this is legal:

actions["setIsPublic"](10);

but that call would violate setIsPublic's call signature.

It sounds like what you want is a definition that says that every key in ActionMap points to a function where the return type of that function is related to its concrete parameter type as stated by its definition rather than at its call site. I don't think there's a way to do that in this context.

@SalathielGenese Might I suggest a workaround to get the desired behavior:

    type Action<T> = { type: T }

    const actions = createActionMap<Type>()({
        setIsPublic: ( payload: boolean ) => ({ type: Type.SET_PUBLIC, payload }),
    })

    function createActionMap<T extends string>(){
        return function<M>(o: ActionMap<T, M>) {
            return o;
        }
    }

    type ActionMap<T extends string, M> = 
    {
        [K in keyof M]: ( payload: M[K] ) => Action<T> & { payload: M[K] };
    }

    enum Type { SET_PUBLIC = 'SET_PUBLIC', SET_PRIVATE = 'SET_PRIVATE' };

The createActionMap will enforce that the payload has the same type as the parameter.

Tx for looking into this @RyanCavanaugh - But I'm not sure to get the difference from definition to call site.
= = =
Thank you too @dragomirtitian, that's quite impressive. I was able to complete my type to my flavour

type Action<T> = { type: T }

const actions = createActionMap<Type>()({
    setPublic: () => ({ type: Type.SET_PUBLIC }),
    setIsAge: ( payload: number ) => ({ type: Type.SET_PUBLIC, payload }),
    setIsPublic: ( payload: boolean ) => ({ type: Type.SET_PUBLIC, payload }),
})

function createActionMap<T extends string>(){
    return function<M>(o: ActionMap<T, M>) {
        return o;
    }
}

type ActionMap<T extends string, M> = 
{
    [K in keyof M]:
        { (): Action<T> } |
        { ( payload: M[K] ): Action<T> & { payload: M[K] } }
}

enum Type { SET_PUBLIC = 'SET_PUBLIC', SET_PRIVATE = 'SET_PRIVATE' };

Ok @RyanCavanaugh , I think the following excerpt is related to the initial issue/concern.

let identity = <I>( i: I ) => i;
let lowercase = (string: string) => string.toLowerCase();

    identity = lowercase;
//  ^^^^^^^^
//  Type '(string: string) => string' is not assignable to type '<I>(i: I) => I'.
//    Types of parameters 'string' and 'i' are incompatible.
//      Type 'I' is not assignable to type 'string'.

Why is TypeScript trying to compare my call site string to my definition generic I ?
Is it not kinda dummy as reasoning for TypeScript.

I argued that what I mean is :

antything, even a string

But in our discussion (maybe even few messages before), @G-Rath answered :

anything, not just a string

I'm not sure what this means in TypeScript compiler words.

@SalathielGenese I'm not sure why you would expect identity = lowercase; to work, if we try to use identity we are sure to get an error:

let identity = <I>( i: I ) => i;
let lowercase = (string: string) => string.toLowerCase();
identity = lowercase; // If this were allowed
identity(1) // this would ok, since identity is generic, so I is decided by the caller, but it would cause a runtime error

Generics give the caller (ie the call site) control over what I will be. As such any function that is an implementation for the generic function <I>( i: I ) => I must be able to handle any possible I passed into it. lowercase can only handle string. Typescript is right to scream at this.

@RyanCavanaugh had a fun tweet about this:

Why is this code illegal?

function func<T extends string>(x: T): T {
 return "";
}

Every drink in a bar comes in a glass (the constraint), but the waiter (the function) can't just bring you an empty glass (the return value) if you asked for a beer (T)

Same thing here, the bartender (identity) needs to serve you what you ordered (for example number) it can't just decide to serve you string.

Many thanks @dragomirtitian , your approach was fun, clear and concise.

I got the missing point. Thank you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

manekinekko picture manekinekko  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments

bgrieder picture bgrieder  路  3Comments

blendsdk picture blendsdk  路  3Comments

kyasbal-1994 picture kyasbal-1994  路  3Comments