Home > Enterprise >  Assigning between types with the same keys - "Type '...' is not assignable to type &#
Assigning between types with the same keys - "Type '...' is not assignable to type &#

Time:11-16

In my application I'm using values that can be either static values or functions returning those values.

For the typescript, I defined the static values with their types like this

type Static = {
  key1: number;
  key2: string;
};

and then derived a type for the dynamic values using keyof Static:

type DynamicValue<T> = () => T;
type Dynamic = {[key in keyof Static]: DynamicValue<Static[key]>};

which - in my understanding - should be exactly the same as writing this:

type DynamicValue<T> = () => T;
type Dynamic = {
  key1: DynamicValue<number>;
  key2: DynamicValue<string>;
};

(and in fact, both produce the same error in the code below)

Now I have a function that takes dynamic values and creates static values from them, and that is where the problem shows:

function dynamicToStatic(d: Dynamic): Static {
  const o: Static = {key1: 0, key2: ''};
  
  for (const [key, callback] of Object.entries(d))
    // the left-hand side of this assignment is marked with error:
    //   "Type 'string | number' is not assignable to type 'never'."
    o[key as keyof Static] = callback();

  return o;
}

(there's also a variant of the function that is supposed to take a Partial<Dynamic> as argument and return a Partial<Static>, and that shows a similar error: "Type 'string | number' is not assignable to type 'undefined'.")

It seems to be unrelated to Object.keys, since this version has the same error:

function dynamicToStatic(d: Dynamic): Static {
  const o: Static = {key1: 1, key2: ''};
  
  const keys = ['key1', 'key2'] as Array<keyof Static>;
  for (const key of keys)
    o[key] = d[key]();

  return o;
}

I know that I can silence that error by replacing the problematic line with

(o as any)[key as keyof Static] = callback();

but clearly typescript thinks that I've been doing something problematic, but I just can't figure out what it is that might be a problem here.

So, why am I seeing this error with the code above? What exactly has the type 'undefined' / 'never' from the error-message and why?

(full code in the typescript playground)

CodePudding user response:

Since o can contain values of string or number, in this assignment here

o[key as keyof Static] = callback();

callback must return string & number to be sound (since to be safe, you have to somehow satisfy both string and number). Otherwise, the you might be assigning a string to a number and vice versa! However, string & number is impossible, which is why it gets reduced to never (that's where never is coming from). Since you can't assign string | number to never, you get an error.

This error is correct and does hint at this potentially unsafe assignment, but we know that it isn't since the key and callback should have matching types. There are multiple ways to get around this.

One would be to cast to never. It looks odd and might need someone else a little time to digest, but it does work since never is assignable to never:

o[key as keyof Static] = callback() as never;

I like this one since it's the simplest... but you could also use Object.defineProperty (or even Reflect.defineProperty):

Object.defineProperty(o, key, { value: callback() });

Lastly, another way would be a helper function for assignments:

function assign<T, K extends keyof T>(o: T, key: K, value: T[K]) {
  o[key] = value;
}

and then you can just cast the key to keyof Static:

assign(o, key as keyof Static, callback());
  • Related