1

TS beginner. Here

let test = <T,>(x:T, c:{[k in keyof T]:(p:T[k])=>void})=>{

} 

test({name:"john"}, {name:(functionParam)=>null})

The functionParam is resolved as string. But I used T[k] which refers to "john". So is it correct that it got inferred as string?
Where can I read more about this?

3
  • T[k] is an Indexed Access Type Commented Oct 11, 2022 at 19:53
  • @zapl it seems I am confused how typescript guessed that by T[k] I was referring to type string and not just value "john"? Commented Oct 11, 2022 at 19:55
  • Types never refer to values directly, they refer to their type and the type of "john" is either string when typescript assumes that it's variable or more narrowed down only the exact string 'john' Literal Types which happens when typescript assumes it can't change. But it's still a subtype of string and can therefore be assigned to the broader string type. Commented Oct 11, 2022 at 20:15

3 Answers 3

2

I'm not aware of a specific name of this features. It's the keyof operator you apply here.

The keyof operator takes an object type and produces a string or numeric literal union of its keys.

Your object type T is { name: "john" }. The result of keyof is hence "name" and T[k] returns therefore "john", which is widened to string.

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

Comments

1

You are observing type widening which happens in this case because there is no constraint on T and T also does not directly infer the string type. You can read more about the specifics in this Pull request.

To solve this, add an extra generic type S which extends string (or any other primitive type which may be valid here). T will be constrained to Record<string, S>.

let test = <
  T extends Record<string, S>, 
  S extends string
>(x: T, c:{ [K in keyof T] : (p:T[K]) => void })=> {} 

test({ name:"john" }, { name:(functionParam) => null })
//                             ^? "john"

Now, the parameter functionParam is correctly inferred to the string literal type "john".


Playground

1 Comment

I will accept this because I think there will be more info in pull request. I will read it later. But thanks to all.
1

Yes, this is correct. In TypeScript, you can index a type to get the type of the property of that key (see Indexed Access Types). Basing a type on the value of an object's property is impossible, as TypeScript runs at compile time and values are determined at runtime. If you had a type "john" then that is what would show up:

type John = {name: "john"};

let yourName: John["name"];

TypeScript Playground

You can see that the type of yourName is then "john". By default, TypeScript assumes that the object will change, so the type of

let me = {name: "john"};

is inferenced as {name: string} (TypeScript Playground). If it was {name: "john"} by default, then you couldn't change it to something else. Most of the time, when you declare an object, you're going to change it, so this is the safe assumption to make.

4 Comments

@john in my type John I've declared the property name to be of type "john". Look at the addition to my answer to understand why TypeScript infers the type string instead of the more specific type "john".
but T in my case similarly to John in your case is type right? Why property of John was not widened, while property of T was widened?
Hohn in my case is a type. I told TypeScript that name.will always be john. In your case.it is.a.value.
Since in my case it is value it inferred the type differently that is what you are trying to say?

Your Answer

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