microsoft / microsoft/TypeScript
Type Conversion Error with Empty and Non-Empty Generic Types in Record Mapping
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Go
- Star
- 111k
- Fork
- 14.3k
- Merge trung bình
- 2 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 132
Mô tả
### 🔎 Search Terms
"empty object", "generic", "map", "infer", "two", "conversion error", "partial"
### 🕗 Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about generic type inference with empty object types.
### ⏯ Playground Link
[example-two-types-error](https://www.typescriptlang.org/play/?ssl=1&ssc=1&pln=46&pc=3#code/C4TwDgpgBAogHgQwLZgDYQLIHsAmFVQC8UA3gFBSVQBmAlvjgIIBcUAdgK5IBGEATgG4KVOgwBCrAM7A+tNgHMhAXyFkA9ACoNFDVADiENv1oBjKKEhQA7rWAALc1aznw0MAj7IIwfpIB0OlAAShBgfBCShsCSUAhQSAhgYHLyUNJ8HCbAHOE0WHyOzhYRUAAUACoAjLFsOFDlAEwAlAFQugCSbFDhCAQckVAmCJGSADRQAOQIbCAT1lgcqHW83aGoCCYQdTb2g1hsAG78PgXUHGxZtPsx1PlQEBsOxRM39EutGmpkxfWukhiJSB8AA8VXGjQAfERSMJKAkwFVWCQoABtADSUDkUAA1hAQFhqPVKgBdVjTEBQFSwqBqNTwxGkVEYrG4-GEgAKHmAtF6oMqENJNQpKkotKgAGE7NN5ClzM5OXxubyqlDwkgsEcYvZoPw+Hd9qgKbRCQhgFB0MMzftoPCgVApTE4mx9gBaCAoUBEsq2-gAdVsdhgHpAjUxMXOJiwSCQUS2TWp1PpDSRTMxXVZBPqDUF5MpAko1NpSZT6LTOLxmYVStQoIaArJMzzorUEqlCllwHlXJ5Nchq3VmvMdh1fD1BQNRpNZot0ig1vigP49uGsXYrvdYE9odKPr4-vsQc3IeqtHDFyjMbYPhw8ap6hbABEICZ1rlbSlJGQ8C+PNBI2xZ13fdA2DBlyj+AEkn4YESCUcZ4GQNBMFwfAISEb9Xz-a4zSAgND1ARpWHAyB-kXEEEJQdBsDwVBxlgtCv2fTC9gAnCyOAsQsHsYiIiIiCyOBCikOo-B4MQSjkJohiMN-FjAPYvDQL+PiSMgoEYLg0glAYsh-1nDBGHZdkYCCABlaFyCoGkWwAVXPaMoigHBjWofhHKGEY5TnbhIj4I4oF4KUDiuPhmELOkFPsTjuL+cYqDFQBQcigdlwiOK8Yl1O5Sl4IZ+mgbguIcKoajqUNZOdNg3WDeMrKLSKQKPHixnilskpSiA0uie5RyynKEDygLCq9aZSoaWJcg3UAaqoXCD1Ayo4qs6yoEAGXIJX6zzMoKbLnw26BitPe5gxKrMwzXYBprher8JDBpFtqls1vFPaMp67a+oG0NDsmikRq9Q7nQusglDSU1T1EGIQkjPgcGBdIUnBfioJBclxnJCEdLFeAZA2bkFBcEjvlcKA1P4NE8RiYgM0JYpMwMoyTNM1Qfk6Vy+CqYFyYpCA4B8WoYlJvguckKFiFpwl6eMsz0TxYl7l5wwcBiJrBeBOQ2aJcZ1aXPsAH4vVYIwjkEInLFZ-hGk5vF5b5pWSbI4XRYJiA6cMqXTJlkA5Z523laR9TtYKME0w1vXTsNjr+FUHmwHyM0fkFgAxPUkC5q3uYV-n7eRx3oRVgTzfZyp04hcZC8trmMdUMVE4jbl9i8jZNkkR0F2RgKKTYLxdOwqB5G8QWxBALnoXTm3FYFh2KYhUpWVYLmmlYJOU7TyuiChSyWtW35IFMkxZE3bqxznEwTByGJh1yKxhy6SXGZY4AEDkGIJ28gArZ8up2Bx5x+vvDGMGYH4f15wVSqkef+RhZBANcOFKASVyjDiPncZykhFweBiMaZK3ZlT8jnAUKsPZaxQkOnlOojAAByD5YjTgeLOecu5lytzAX-H4twCjaigO4RUPYuEeC8CcVoVBwjZD4LfN2jMUSsjliuZeUZV54gYneTQUAyCmR8GALUzgQhhFwJkaAnD2gtw4BAMKZBKh+CgAACQQMFfGr9ZoNQItUEaag7iOJuqGXKnlpjIL4LCF0EoLyOQWGxZGHFCpNROh4pSJEhFQECXZSMDkrxtyBMBTx1QJyWOpMECIixgCsDWjxPeB8zRbXiYE8UwTUkxMai42oUAIw1LCekxSjUxrZNUUtEIkgClFJ3hAUptBD4VICVAJJLTBq7Dqc46J11QINByT0-JqBCnbxKfvEZ5S3oBDIEs2Ab0zphEjleLY1gb6xBwM5fGjjop2Cie4hZjU-jjOqSkq0HAzS9AILuTB4iGZmUqRM+yl5Wl+gDPcpq4xtRdGaR8oUc5tQFGSdwOQYMG6Zj+WUCAfh5B+HGLM48hLnkEWaPEygvT+nwKgBQ5wmyyl+PGOEeQHgcDoBbnOQk19TA-2RWk3w41oDwrBVsYF7ywUCr3JCyJfwTrPx8AgOoIrHJEqassqyVK1msCSnSwZwzRlvWZRAVlMMOUv25XYXlSLL5SsdLkFVZycB7IAMyWN9PkbEHgFi1FYO1TqHYkFbSaZIWUhDcFQjuOG3sdZrABlCVKiJMUSL4MTe0giryqCBOosaCkPFBZeRMG2fuc42CGjbsVTs2CeERvFVMhNPy7VljvmZcYPNNiH3YWmua9SKV5L6dqjZrgDU7LHMCyZCK1VjR5egcsoRZRquqI668qbkmiudbkrV6zdX0uHVsw1Y5xgfQGFOphq4WHHSqMCkIxsBicKLdKUxqxpD5BtIkYqf0FUPDqA+hQb6ERjSrdG4hdaEUNtQL8si-ySYSNbfLDtZou1Tr7VugZDLtl+PHaC1VpLjwXNoLO3E87bm4dDMu85dw12xg3Ssgd27aW7t3vu0d+Qj27QGous9Tp1yXqWWQT4ZAgA)
[example-single-type-no-error](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogHgQwLZgDYQLIHsAmFVQC8UA3gFBSVQBmAlvjgIIBcUAdgK5IBGEATgG4KVOgwBCrAM7A+tNgHMhAXyFkA9ACoNFDVABKEMHwiSIbYJKgIoSBGDBz5UaXw4BjYB2M0sfK88d0KFBIADodKABJNihjBAIOUyg3BFNJABooAHIENhAsqAB3LA5UHCheWMNUBDcIcsLaYAALfzcsNgA3fmB+Gg42D1oO8KgNNTIQ6AAVcEw7SD4AHmmAPiJSYUpbMFYSKABtAGkoOSgAawgQLGooaYBdVlyQKBUyN-U1KAARCDca7w7BwKSRkPD-BDedpsaQ2Bb8ADqTWaMBQoFYs0gGHhyxISlWQnBAOg0NhQMRyPgyDQmFw+Axc2x9n4SypKHQ2DwqAJZDIpOAUAwjAACsKYHoAMobchUKBqL4AVUGWCQSDMApwtGo1H46uSqRMwSwyQ6dD4SGCzQQArYxsxEAlblkYAF-D4vigWDcbi8kmYWzhzL4SJabJpnPwmTlX0AoORQAByxrdvgD5ODlLRICjsqo8qgccTUGTfHeVks-Oc1toklElgM7T4OCWLkcmXtTMWS2eqx5n1gcBktWAjmCc1BU0FOKOV0sxEu11uUxugpFYslqgn0R1fG+1oQS2nLwgA7MOEsHf4h8k62IS9uQtF4olxyu9yLJ7YZ7ujJxSzk27udYAH47igVg2AgbpBF5Y8wF8AUJwvPgADF3SQQ8Dyud9ek-c8pxnG9vyxX8t34XdgH3Q8e1UPNkIGIYOiNKxvRMSxrDTCoXjYZAID5DpYXkCBgCQsQQEPDZMKPD8vyQq9VgACnnVhDwASlYJDUJVDCqKIdYZVzWMEyTPh3T8ZpdQDYxPD4GIHzXZ95zfVJJyDTT0KuSSeRUIA)
### 💻 Code
```ts
type ExampleModel = {
fieldA: number;
fieldB: string;
};
/**
* Generic type with two type parameters.
* Represents a mapping structure for two types (T1 and T2).
* In real use cases, 'any' would be replaced with converter functions for each type's field.
*/
type TypesMapper = {
mapT1: { [K in keyof T1]: any };
//mapT1: { [K in keyof Partial]: any }; // Changing to Partial removes the error only if at least one mapper has a non-empty T1 (mapperWithEmptyT2 is uncommented)
mapT2: { [K in keyof T2]: any };
//mapT2: { [K in keyof Partial]: any }; // Changing to Partial removes the error only if at least one mapper has a non-empty T2 (mapperWithEmptyT1 is uncommented)
};
// Declare mappings
declare const mapperWithEmptyT1: TypesMapper<{}, ExampleModel>;
declare const mapperWithEmptyT2: TypesMapper;
declare const mapperWithBothTypes: TypesMapper;
declare const mapperWithEmptyTypes: TypesMapper<{}, {}>;
const MAPPERS = {
// Uncomment different cases to observe behavior:
//mapperWithBothTypes, // ✅ Prevents error (because both T1 and T2 are non-empty)
//mapperWithEmptyTypes, // ✅ Prevents error (because both T1 and T2 are empty)
mapperWithEmptyT1, // ❌ Causes error (because T1 is empty and T2 is not)
mapperWithEmptyT2, // ❌ Causes error (because T2 is empty and T1 is not)
} satisfies Record>;
// Extracting types
type MapperKeys = keyof typeof MAPPERS;
type InferT1 = typeof MAPPERS[Key] extends TypesMapper ? T1 : never;
type InferT2 = typeof MAPPERS[Key] extends TypesMapper ? T2 : never;
export type MapperFromKey = TypesMapper, InferT2>;
// Function to access a mapper by name
const getMapperByKey = (key: Key): MapperFromKey => {
// ❌ TypeScript error occurs here when MAPPERS contains only objects with one empty generic type and one non-empty generic type
// ✅ The error disappears if Partial or Partial is used AND at least one mapper has a non-empty type for the partial parameter.
return MAPPERS[key] as MapperFromKey;
};
```
### 🙁 Actual behavior
When creating a type with two generic types, where each generic type has a property that is an object containing all the type’s fields with some assigned type, an issue arises when using a `Record` (as a map) that stores these types as values.
If all the mapped values have one generic type as an empty type (`{}`) and the other as a non-empty type, TypeScript produces a **type conversion error** when using a generic function to retrieve values from the map with inferred types, based on a key name type (as shown in the steps below).
The error disappears when:
- Adding a type where both generic types are empty (`mapperWithEmptyTypes`).
- Adding a type where both generic types are non-empty (`mapperWithBothTypes`).
- Applying the workaround described below using `Partial` or `Partial`.
When performing the steps below, the error occurs on **line 45**:
```ts
return MAPPERS[key] as MapperFromKey;
```
> Error Message:
Conversion of type 'TypesMapper<{}, ExampleModel> | TypesMapper' to type 'MapperFromKey' may be a mistake because neither type sufficiently overlaps with the other.
If this was intentional, convert the expression to 'unknown' first.
Type 'TypesMapper' is not comparable to type 'MapperFromKey'.
Types of property 'mapT2' are incompatible.
Type '{}' is not comparable to type '{ [K in keyof InferT2]: any; }'.(2352)
### Steps that Reproduce the Issue (with the Attached Code)
#### 1. Using only `mapperWithEmptyT1` and/or `mapperWithEmptyT2` causes an error
- Comment `mapperWithBothTypes` and `mapperWithEmptyTypes`.
- Uncomment `mapperWithEmptyT1` only.
- **Result:** ❌ TypeScript error.
- Comment `mapperWithEmptyT1` and uncomment `mapperWithEmptyT2` only.
- **Result:** ❌ TypeScript error.
- Uncomment both `mapperWithEmptyT1` and `mapperWithEmptyT2`.
- **Result:** ❌ TypeScript error.
#### 2. Error does not occur when adding `mapperWithBothTypes` or `mapperWithEmptyTypes`
- Comment all mappers in `MAPPERS`.
- Uncomment `mapperWithBothTypes`, then uncomment any other combination of mappers (e.g., `mapperWithEmptyT1`, `mapperWithEmptyT2`).
- **Result:** ✅ No TypeScript error, regardless of which other mappers are uncommented.
- Comment `mapperWithBothTypes` and instead uncomment `mapperWithEmptyTypes`.
- **Result:** ✅ No TypeScript error, regardless of which other mappers are uncommented.
#### 3. Workaround: Preventing the error using `Partial` or `Partial` without `mapperWithBothTypes` or `mapperWithEmptyTypes`
- Modify `TypesMapper` to change only `mapT1` to `Partial`.
- Comment all mappers in `MAPPERS`, except for `mapperWithEmptyT1`.
- **Result:** ❌ TypeScript error.
- Uncomment `mapperWithEmptyT2`, whether `mapperWithEmptyT1` is uncommented or commented.
- **Result:** ✅ No TypeScript error, because `mapperWithEmptyT2` has a non-empty `T1`.
- Reverse the change: restore `mapT1` and instead change `mapT2` to `Partial`.
- Comment all mappers in `MAPPERS`, except for `mapperWithEmptyT2`.
- **Result:** ❌ TypeScript error.
- Uncomment `mapperWithEmptyT1` while keeping `mapperWithEmptyT2` uncommented or commented.
- **Result:** ✅ No TypeScript error, because `mapperWithEmptyT1` has a non-empty `T2`.
#### Limitations of The Workaround
Using `Partial` or `Partial` as a workaround does not help when we want to enforce explicit converters for all fields. Since `Partial` allows missing fields, it weakens type safety and fails to enforce strict mapping of all fields.
### Additional Observation
This issue does not occur when using the same generic type with only one type parameter instead of two. In this case, TypeScript correctly infers and applies the expected type without any conversion errors.
[ Playground Link: a working example with only one generic type](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogHgQwLZgDYQLIHsAmFVQC8UA3gFBSVQBmAlvjgIIBcUAdgK5IBGEATgG4KVOgwBCrAM7A+tNgHMhAXyFkA9ACoNFDVABKEMHwiSIbYJKgIoSBGDBz5UaXw4BjYB2M0sfK88d0KFBIADodKABJNihjBAIOUyg3BFNJABooAHIENhAsqAB3LA5UHCheWMNUBDcIcsLaYAALfzcsNgA3fmB+Gg42D1oO8KgNNTIQ6AAVcEw7SD4AHmmAPiJSYUpbMFYSKABtAGkoOSgAawgQLGooaYBdVlyQKBUyN-U1KAARCDca7w7BwKSRkPD-BDedpsaQ2Bb8ADqTWaMBQoFYs0gGHhyxISlWQnBAOg0NhQMRyPgyDQmFw+Axc2x9n4SypKHQ2DwqAJZDIpOAUAwjAACsKYHoAMobchUKBqL4AVUGWCQSDMApwtGo1H46uSqRMwSwyQ6dD4SGCzQQArYxsxEAlblkYAF-D4vigWDcbi8kmYWzhzL4SJabJpnPwmTlX0AoORQAByxrdvgD5ODlLRICjsqo8qgccTUGTfHeVks-Oc1toklElgM7T4OCWLkcmXtTMWS2eqx5n1gcBktWAjmCc1BU0FOKOV0sxEu11uUxugpFYslqgn0R1fG+1oQS2nLwgA7MOEsHf4h8k62IS9uQtF4olxyu9yLJ7YZ7ujJxSzk27udYAH47igVg2AgbpBF5Y8wF8AUJwvPgADF3SQQ8Dyud9ek-c8pxnG9vyxX8t34XdgH3Q8e1UPNkIGIYOiNKxvRMSxrDTCoXjYZAID5DpYXkCBgCQsQQEPDZMKPD8vyQq9VgACnnVhDwASlYJDUJVDCqKIdYZVzWMEyTPh3T8ZpdQDYxPD4GIHzXZ95zfVJJyDTT0KuSSeRUIA)
### 🙂 Expected behavior
TypeScript should work consistently and always allow retrieving values from MAPPERS in the above generic function, without type conversion errors, regardless of the specific generic type combinations used.
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu với các ví dụ TypeScript Playground được liên kết và hàm getMapperByKey, đặc biệt là phép cast ở dòng 45 liên quan đến MapperFromKey. So sánh các tổ hợp mapper và các biến thể Partial/Partial để cô lập hành vi suy luận generic. Được coi là hoàn tất khi các tổ hợp được báo cáo không còn tạo ra TS2352 mà vẫn duy trì việc ánh xạ trường nghiêm ngặt.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- typescript
- Lĩnh vực
- compilers
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Đình trệ
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 45/100