TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Two same types (mapped type of generic param) are not assignable to each other

Open csr632 opened this issue 3 years ago • 3 comments

Bug Report

🔎 Search Terms

Keywords: generic function, mapped types

Errors:

  1. Type 'T1' is not assignable to type 'T2'.
  2. Two different types with this name exist, but they are unrelated.

I got both error messages in my original code, but only the first error message is left in the minimal reproduce.

🕗 Version & Regression Information

4.7.2

All other versions also have this issue.

⏯ Playground Link

https://www.typescriptlang.org/play?#code/C4TwDgpgBAYgjFAvFAPAFQHwAoCGAnAcwC4o0BKJDKAJTnQwG4AoUSGuzJKAbyaigDaAaSgBLAHZQA1hBAB7AGakoOAM5QA5MAirgGqBAAe28QBN1IgPxRxEAG4Q8UEkIC6JAAzMAvsyYB6fwNDHABjYAAbEChgAAtoVRwAW2hWCBZwaBgAJi56XEISckoabPpmNNL6Ll5+YTFJGXklNBV1LR09YJNzKCsbe0dnPvcoLyZfJiZTCFCI-GgIiGAoOzgSeGYZuYWoJZW7bI3svzWuQ4YoQKgAUTw8OTwmQ-O4S+u7h6emIA

💻 Code

type F1 = <T>(arg: T) => R1<T>;
type R1<T> = {
  [K in keyof T as 'test' extends K ? never : K]: 0;
};

// exactly the same type
type F2 = <T>(arg: T) => R2<T>;
type R2<T> = {
  [K in keyof T as 'test' extends K ? never : K]: 0;
};

declare let v1: F1;
declare let v2: F2;

v1 = v2; // Error
v2 = v1; // Error

The problem appears when a conditional type is used in a key remapping of mapped types.

🙁 Actual behavior

The two same types are not assignable to each other.

🙂 Expected behavior

The two same types should be assignable to each other. Because they are written in the same way.

In my case, the two "same-written type declarations" exist because they come from the same package with two different versions.

csr632 avatar Jun 23 '22 13:06 csr632

Maybe related to https://github.com/microsoft/TypeScript/issues/48626#issuecomment-1098168620? I don't fully understand the comment.

csr632 avatar Jun 23 '22 13:06 csr632

The issue here is that we only consider two mapped types with as clauses related if the as clause types are identical. We could probably be a bit more flexible here. Meanwhile, the workaround is to use a shared as type:

type N<K> = 'test' extends K ? never : K;

type F1 = <T>(arg: T) => R1<T>;
type R1<T> = {
  [K in keyof T as N<K>]: 0;
};

type F2 = <T>(arg: T) => R2<T>;
type R2<T> = {
  [K in keyof T as N<K>]: 0;
};

ahejlsberg avatar Jul 01 '22 16:07 ahejlsberg

I am running into the same issue and found a slightly simpler example. Link to playground.

type R1 = <T extends PropertyKey>() => {
  [K in T as K extends unknown ? K : K]: 0
}

type R2 = <T extends PropertyKey>() => {
  [K in T as K extends unknown ? K : K]: 0
}

declare let v1: R1;
declare let v2: R2;

v1 = v2; // Error
v2 = v1; // Error
Type 'R2' is not assignable to type 'R1'.
  Type '{ [K in T as K extends unknown ? K : K]: 0; }' is not assignable to type '{ [K in T as K extends unknown ? K : K]: 0; }'. 
  Two different types with this name exist, but they are unrelated.

Removing as K extends unknown ? K : K (even though this clause is a noop) unexpectedly removes that error.

Unfortunately, in my case, the workaround described by @ahejlsberg is not possible because R1 and R2 are actually two instances of the same package installed in different places in node_modules. This leads to my package's types not being assignable with themselves (or more precisely, a copy of themselves).

ehmicky avatar Nov 03 '22 13:11 ehmicky