Search code examples
typescript

Assign a value to a generic constraint that extends a primitive type


I have the following function

function fun<T extends boolean>(param: T): void {
    param = true
}

When I assign param to true, the compiler gives me the following error:

Type 'boolean' is not assignable to type 'T'.
  'boolean' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'boolean'.ts(2322)

Why can't I assign it? T is (or at least extends) boolean, so it should work.

There is a workaround to add <T> before true or as T after true, but that's just annoying to do all the time.

It's also more annoying when you try to add a default value to param:

function fun<T extends boolean>(param: T = true): void

Now there is an error on param: T = true which is the same exact one as the previous error.

Why can't I just assign to param without explicitly type-asserting (adding <T> or as T) the value?


Solution

  • Basically, just because you have two different sub types does not mean you can assign one to other.

    true and false are subtypes, but if a value explicitly expects the value false, you cannot assign true right.

    It is a little difficult to find practical examples with primitives, but with objects it does make a lot of sense. And, in JS anything can be an object :D.


    I hope the below snippet is helpful. Here we have both kinds of example. Please check the comments too:

    function fun<T extends boolean>(param: T): void {
        param = true //The error
    }
    
    const val: false = false; //val can only be false
    
    type PrimitiveWithRandomFunc = false & { func? : () => void };
    
    type ExtendsWrong = PrimitiveWithRandomFunc extends boolean ? true : false;
    
    let m :false & { func? : () => void }  = false; //m can only be false and have a func in addition
    
    m.func = () => {
      console.log("yolo");
    } 
    
    fun(val) //no error, although we are assigning true to val, which should be false
    
    fun(m) //no error
     
    

    If one is to create a type like PrimitiveWithRandomFunc, which is possible in JavaScipt, then the above error makes sense.

    ExtendsWrong is true type, which means the extension constraint is match. But just because it is matched does not mean that we can assign a different subtype to param.

    Notice how fun(m) does not give any error. I am using func as optional property to get rid of some TS errors, but I hope it gets the point across. Basically you would be assigning true to something that only expects fale and some other things.

    And for val I hope it is clearly apparent, we are simply able to assign true to something that should be false.

    Playground