Incorrect assignability check when using Extract (TS2322)
See original GitHub issueBug Report
🔎 Search Terms
extract assignable TS2322
🕗 Version & Regression Information
This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics, Assignability, and Extract.
Tested with 4.2.3, 4.3.5, 4.4.4, and 4.5.0-dev.20211018.
In 4.1.5, the example using a type alias also fails. Version 4.2 introduced Smarter Type Alias Preservation, which may explain why that example passes starting from 4.2.x.
⏯ Playground Link
Playground link with relevant code
💻 Code
I have attempted to minimize this example as much as possible. The use of Extract
to define a field type appears to be significant.
type A<T> = {
val: Extract<number | string, T>;
};
type A_number = A<number>;
function f1(x: A_number): A<number| string> {
// Passes type checking.
return x;
}
function f2(x: A<number>): A<number | string> {
// Fails type checking with error TS2322:
// Type 'A<number>' is not assignable to type 'A<string | number>'.
// Type 'string | number' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.
return x;
}
🙁 Actual behavior
The type checker reports error TS2322: A<number>
is not assignable to type A<string | number>
because string | number
is not assignable to number
. It appears to be checking assignability of a field in the wrong direction, because the assignability check should be whether number
is assignable to string | number
.
Using a type alias for A<number>
does not produce the same error. I was also not able to reproduce this error without using Extract
to define a field type.
🙂 Expected behavior
The type A<number>
should be assignable to type A<string | number>
because it is a simple object type and number
is assignable to string | number
.
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (2 by maintainers)
It thinks that
A
is invariant; this also fails:This makes sense for general conditional types (consider
type X<T> = {} extends T ? 1 : 2
;X<{}>
isn’t assignable to or fromX<{ x: string }>
);Extract
is just a special case where it’s only covariant.The real bug here afaict is that TS is doing a variance check here instead of doing structural comparison; the variance measure should probably be tagged with
VarianceFlags.Unreliable
(which means that variance checking may produce false negatives, but not false positives).Sorry, my mistake, I got
Extract
mixed up withExclude
. That said, the issue/bug appears to be that TS is measuringA<T>
as contravariant for some reason:string | number
indeed isn’t assignable tonumber
, but this is going in the opposite direction relative toA
. So for whatever reason it seems to thinkT
is contravariant. 🤔