2

Is there any difference between the parameter transfer type of the ts generic function and the internal definition type of the generic function?

I know that keyof can generate string or numeric literal union of its key for object type

type Data = { a: string, b: number } keyof Data;// 'a' | 'b'

But if I apply it to literal types, I will get union types similar to all its attributes
type Test = keyof 'a';
// result
type Test = number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | ... 30 more ... | "padEnd"
I know that the "in" operator in typescript can be used to traverse the attribute names of the target type
type Test = {
    [P in keyof 'a']: '??'
};
type T = Test;
// result
type T = {
    [x: number]: "??";
    toString: "??";
    charAt: "??";
    charCodeAt: "??";
    concat: "??";
    indexOf: "??";
    lastIndexOf: "??";
    localeCompare: "??";
    match: "??";
    replace: "??";
    search: "??";
    slice: "??";
    split: "??";
    ... 30 more ...;
    [Symbol.iterator]: "??";
}

image

But I don't understand why the results are completely different once they are passed in through generics

type Test<V> = {
    [P in keyof V]: '??'
};
type T = Test<'a'>;
// result
type T = 'a'

image

Nullndr
  • 1,624
  • 1
  • 6
  • 24
Mishra
  • 25
  • 4
  • 1
    Please don't post code as screenshots. Paste the code into your question instead. And please don't link to images; if the images are no longer available, important information in the question will be lost forever. – Simon Lundberg Dec 07 '22 at 14:20
  • 1
    @Simon: I wonder if the screenshots are necessary in this case, since they have floating context boxes. I agree with your sentiment generally, but not sure how the type information should be displayed. – halfer Dec 07 '22 at 14:26
  • 1
    You can generally select and copy the info in those context boxes and paste them into a comment near the relevant part of the code. This is what I do, and while it is a bit tedious to do so, it is preferable to posting a screenshot. – jcalz Dec 07 '22 at 14:30
  • 1
    Presumably if someone reading the question pasted the code is pasted into their editor of choice, they will also get type information and such. But sure, the screenshots can come in handy. In that case: post the code _and_ the screenshots, and not as _links_ to screenshots. There's an image attach button right there in the UI. Just upload the image and put it directly in the question. – Simon Lundberg Dec 07 '22 at 14:33
  • 1
    Please don't use `
    ` markup in your question Mishra - it just makes it harder to re-edit.
    – halfer Dec 07 '22 at 14:36

1 Answers1

2

This has to do with whether or not the mapped type in question is perceived by the compiler as homomorphic (See What does "homomorphic mapped type" mean? for more detailed information about what "homomorphic" means in TypeScript). The answer is given in this comment on microsoft/TypeScript#41575 (slightly paraphrased):

The key issue here is that { [P in keyof T]: '??' }, where T is a naked type variable, is considered a homomorphic mapped type, whereas { [P in keyof 'a']: '??' } is not because keyof 'a' is eagerly resolved to a union of literal types. For homomorphic mapped types

  • if T is a union type we distribute the mapped type over the union,

  • if T is a primitive type no mapping is performed and the result is simply T,

  • if T is an array we map to an array where the element type has been transformed,

  • if T is a tuple we map to a tuple where the element types have been transformed, and

  • otherwise we map to an object type where the type of each property has been transformed.

We've had these distinctions in place for a very long time, not sure we can change it now without breaking lots of code.

So in { [P in keyof 'a']: '??' } the compiler eagerly computes keyof 'a' as a union of literal types such as "toString" | "charAt" (as well as number for the index signature and the relevant symbol key), and then dutifully maps over this union.

But in { [P in keyof T]: '??' } where T is generic, the compiler treats the whole thing as a homomorphic mapped type, and homomorphic mapped types on primitives like "a" just evaluate to their input directly, which is "a". At no point does the compiler even try to compute keyof "a" here.

jcalz
  • 264,269
  • 27
  • 359
  • 360