1

Here is a piece of code i got:

interface Obj {
  a: number
  b: string
}

const obj: Obj = {
  a: 1,
  b: 'hi'
}

function fn(key: keyof Obj, value: Obj[keyof Obj]) {
  let foo = obj[key]
  obj[key] = value
}

fn("a", 2)

So what i want to do is, i want the function fn() to be able to update the object obj properties, the first argument of this function is any key that obj has(defined in Obj interface), and the second argument is the value you wanna give to that key.

However, the typescript popup with an error in the obj[key] = value this line, which is:

Type 'string | number' is not assignable to type 'never'.
  Type 'string' is not assignable to type 'never'.(2322)

Here is a screenshot:

error message

And a strange thing happens here, if you hover to the variable foo(which is line 13 in the picture), it says:

let foo: string | number

which means, obj[key]'s type is string | number, but the error says obj[key] type is never.

so my first question is: How come a string | number type magically becomes a never type? Is there any way to fix this?

Then i got another piece of code which solves this problem:

interface Obj {
  a: number
  b: string
}

const obj: Obj = {
  a: 1,
  b: 'hi'
}

function fn<K extends keyof Obj>(key: K, value: Obj[K]) {
  obj[key] = value
}

fn("a", 2)

Therefore my second question would be: why using Generics solve the problem and what the hack is the keyword extends here?

BTW, all the code are tested in typescript 3.7.5 version.

I am not a native English speaker, hope i explained my confusion clearly.

1 Answer 1

1

What about the error, it's because keyof Obj might be "a" or "b" which have type number or string. In the expression obj[key] the compiler doesn't know the property type, it might be number or string as well, so it disallows such assignment. Here is the same question. And you can find the explanation here, see Fixes to unsound writes to indexed access types.

In case of the generic function K extends keyof Obj means that K type can be "a" or "b" as well, but when you call the function fn("a", 2) you implicitly set K to "a", the compiler infers K type from the first argument. So now, inside the call context key has "a" type and Obj[K] is number, hence the assignment becomes correct.


I just tried to explain the difference to my wife, who isn't a programmer :) I think it might be helpfull too:

Usual function: Lets imagine you are eating a cake but your eyes are closed. You know that it might be a cherry cake or a banana cake. You like the taste but you cannot say "What a delicious banana cake!" because you are not sure that it's a banana cake.

Generic function: In this case you eyes are open and you can choose the cake you want to eat, but you still have two choices: cherry or banana. Now, if you've chosen the banana cake and tasted it, you can say "What a delicious banana cake!".

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for you explanation, however i just got one stupid question, as you said: "you implicitly set K to a", but why can't keyof Obj do the same? why can't implicitly set keyof Obj to 'a' as well? What really happens under the hood?
@Limboer Generics allow you to specify a type when you call a function. A usual function argument types are constant, like keyof Obj in your case. In case of the generic function you tell the compiler that the function accepts an argument of type K and the type should satisfy the constraint extends keyof Obj, in other words it must be "a" or "b". When you call the function you specify the type explicitly like fn<"a">("a", 2) or it can be implicitly inferred from the arguments you pass to the function, but now it's not the union type "a" | "b" but just "a". I hope it'll help you
@Limboer I've also added a real life explanation to the answer, I hope it might be helpful too

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.