microsoft / microsoft/TypeScript

Multiple keys being remapped to same key causes unstable modifier propagation

Open
#62,318 3 comments 0 reactions 0 assignees View on GitHub
Bug Domain: Mapped Types Help Wanted
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

### 🔎 Search Terms

Mapped Types, Key Remapping

### 🕗 Version & Regression Information

- This has been happening since first release of key remapping at 4.1 (tested with v4.1.5 at Playground) until nightly (tested with v6.0.0-dev.20250822 at Playground).

### ⏯ Playground Link

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAShC2BDMBpCIAqB7AkgOwEtgDEAbABUQCdgAeDAPigF4oBvAKCm6gG0UoBPFADW6LADMoGKIgDOUARAAewCHgAmCgAYASNkIkQqUAGIEqc4AF8AdPsPGoAfThXr2qAH4zFq1AAuRQBdILwAV1JSAG4Oa1iOUEhfS2AAeTBiLDwyFlgEZDRMXEJiMkoaWk4eKAB6WqgAPS8uHgAiCSwsWwAjajavIKsqIQBzWJqOrt7EAC82sPD4HuNY6wYEpOhzVLgAR3CLCA08uCRUdGx8IhIKajpqnnqmlsnO7r75xeXV1u4pj79QZQYZjNYbDhAA

### 💻 Code

```ts
type RemapKeyToInitialPart = {
[K in keyof T as K extends `${infer First}.${infer _Rest}` ? First : K]: null;
};

type FirstOptional = RemapKeyToInitialPart<{
// ^?
"foo.bar"?: string;
"foo.baz": number;
}>;

type FirstRequired = RemapKeyToInitialPart<{
// ^?
"foo.baz": number;
"foo.bar"?: string;
}>;
```

### 🙁 Actual behavior

`type FirstOptional` and `type FirstRequired` results in different types:
- `type FirstOptional` has *optional* property `foo`
- `type FirstRequired` has *required* property `foo`

### 🙂 Expected behavior

`type FirstOptional` and `type FirstRequired` results in same type. I would expect it to be same as ~~`type FirstRequired`~~ `type FirstOptional`.

### Additional information about the issue

My motivation was to implement a utility `type Expand` which transforms `{ "foo.bar": number }` to `{ foo: { bar: number } }` ([playground](https://www.typescriptlang.org/play/?exactOptionalPropertyTypes=true#code/C4TwDgpgBAogHmAhgOwCYB4AqA+KBeKAbwCgoyoBtAaSgEtkoBrCEAewDMpMpEBnKAGK0ATr2AAFRMOAB5duirYAugC4oAJQhiZAIwBWEAMbAsAGkEixk6XIXZsxAL7FioSBdESps+YvxQaCDhgCDR+AAMAEkJ6dghhQUcAOmixYXoAc0dwqAB+QSg1GgAyKDTMlzdoTW19IxNMc0Dg0NR+cuQM3AJuIJCwokpxOgYqJVy1WPioADUoRzzZwtgEFAwSciGRphYOLh5+Yb7WiOiqZOiphJrgbMWb5eQIADd41S4KcSUAbicHYlQRgANlJoIZWMgxFBgABGNTwJBodAbcgAIgAFqwMhAkuwAK4ZRBJMC0NioiZQZB4gC2Oniv02GKxOPYrFYSR0UlRag6GV+jmwv1hSUx2NyuIJiHFJLYZUQwFovHYtC0lJpdISAB8oHi0BBlU9UK4YSLmbi2RypHKFUqVe1gOlOr8AcDQVBwZDgNCAEzw1ZIlFkJnY83sznCbllB2ZBlo0UsyXE0msclqKm0+l-IXe01iiWE6XJ62K5Wq9MaqDa3WAg0QI3AHPx0OWhK8eUlu1Rx184hAA))

Contributor guide

Open the contributing guide

Research direction

Reproduce the two mapped-type examples using the linked TypeScript Playground and compare FirstOptional with FirstRequired. Investigate the compiler's key-remapping behavior, then add a regression test showing that both declaration orders produce the same property optionality; done means the test passes with identical results.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.