You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Inconsistent incompatible types error for a sparse array type declaration #50351
exportinterfaceReadonlySparseArray<T>{readonlylength: number;slice(start?: number,end?: number): SparseArray<T>;every<SextendsT>(predicate: (value: T,index: number)=>value is S,): this is ReadonlySparseArray<S>;readonly[n: number]: T|undefined;flatMap<U>(callback: (value: T,index: number)=>U|readonlyU[]|ReadonlySparseArray<U>): U[];}exportinterfaceSparseArray<T>extendsReadonlySparseArray<T>{[n: number]: T|undefined;}// const _a: SparseArray<string> = ['a'];exportfunctionfoo<T>(): ReadonlySparseArray<T>{leta: T[]=[];returna;}
🙁 Actual behavior
The code above reports:
Type 'T[]' is not assignable to type 'ReadonlySparseArray<T>'.
The types returned by 'slice(...).flatMap(...)' are incompatible between these types.
Type '(U | ReadonlySparseArray<U>)[]' is not assignable to type 'U[]'.
Type 'U | ReadonlySparseArray<U>' is not assignable to type 'U'.
'U' could be instantiated with an arbitrary type which could be unrelated to 'U | ReadonlySparseArray<U>'.
on return a;.
However, if the const _a line is uncommented, the error goes away. If you move that line to after the generic function, both places report this error.
(Weirdly, if you remove the every method from the ReadonlySparseArray, both places would be reporting the error regardless of the place of the const _a line.)
🙂 Expected behavior
It should not report error, since SparseArray and ReadonlySparseArray here should be compatible with array type. It's just a declaration of some of its methods.
Rethought about it, I now believe that the correct behavior is that TypeScript should consistently reject it. Because for flatMap,
U | readonly U[] is a subtype of U | readonly U[] | ReadonlySparseArray<T> (and not the reverse, since sparse array is not supposed to be assignable to dense array), so
(...) => U | readonly U[] is a subtype of (...) => U | readonly U[] | ReadonlySparseArray<T>, thus
(callback: (...) => U | readonly U[] | ReadonlySparseArray<T>) => U[] is a subtype of (callback: (...) => U | readonly U[]) => U[], which means
ReadonlyArray.flatMap should not be assignable to ReadonlySparseArray.flatMap here.
This is caused by a false positive of isDeeplyNestedType. Xidorn Quan (@upsuper) is correct that flatMap should consistently cause an assignability error. However, because of the interconnected return types in the methods of ReadonlySparseArray and SparseArray and the order they’re written, we end up bailing out of comparing flatMap just barely too early. The comparison stack at the point of bailing looks like:
Source
Target
0
(literal) Array<string>
SparseArray<string>
1
(literal) Array<string>["slice"]
SparseArray<string>["slice"]
2
Array<string>
SparseArray<string>
3
Array<string>["every"]
SparseArray<string>["every"]
4
Array<any>
ReadonlySparseArray<any>
5
Array<any>["slice"]
ReadonlySparseArray<any>["slice"]
6
Array<any>
SparseArray["any"]
7
Array<any>["flatMap"]
SparseArray<any>["flatMap"]
8
U | ReadonlyArray<U | ReadonlySparseArray<U>> | ReadonlySparseArray<U>
While this looks pretty chaotic, it does finish with the correct results if you bump the maxDepth parameter of isDeeplyNestedType from 3 to 4, with the following error elaboration:
const _a: SparseArray<string> = ['a'];
~~
!!! error TS2322: Type 'string[]' is not assignable to type 'SparseArray<string>'.
!!! error TS2322: The types of 'slice(...).every' are incompatible between these types.
!!! error TS2322: Type '{ <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; }' is not assignable to type '<S extends string>(predicate: (value: string, index: number) => value is S) => this is ReadonlySparseArray<S>'.
!!! error TS2322: Type predicate 'this is any[]' is not assignable to 'this is ReadonlySparseArray<any>'.
!!! error TS2322: Type 'any[]' is not assignable to type 'ReadonlySparseArray<any>'.
!!! error TS2322: The types returned by 'slice(...).flatMap(...)' are incompatible between these types.
!!! error TS2322: Type '(U | ReadonlySparseArray<U>)[]' is not assignable to type 'U[]'.
!!! error TS2322: Type 'U | ReadonlySparseArray<U>' is not assignable to type 'U'.
!!! error TS2322: 'U' could be instantiated with an arbitrary type which could be unrelated to 'U | ReadonlySparseArray<U>'.
Note that the comparison at index 8 is the result of contextual signature instantiation after a potentially questionable inference explained here. That’s a separate bug, but fixing it would probably prevent this particular example from triggering the depth limiter.
Activity
upsuper commentedon Aug 20, 2022
Rethought about it, I now believe that the correct behavior is that TypeScript should consistently reject it. Because for
flatMap,U | readonly U[]is a subtype ofU | readonly U[] | ReadonlySparseArray<T>(and not the reverse, since sparse array is not supposed to be assignable to dense array), so(...) => U | readonly U[]is a subtype of(...) => U | readonly U[] | ReadonlySparseArray<T>, thus(callback: (...) => U | readonly U[] | ReadonlySparseArray<T>) => U[]is a subtype of(callback: (...) => U | readonly U[]) => U[], which meansReadonlyArray.flatMapshould not be assignable toReadonlySparseArray.flatMaphere.andrewbranch commentedon Apr 24, 2024
This is caused by a false positive of
isDeeplyNestedType. Xidorn Quan (@upsuper) is correct thatflatMapshould consistently cause an assignability error. However, because of the interconnected return types in the methods ofReadonlySparseArrayandSparseArrayand the order they’re written, we end up bailing out of comparingflatMapjust barely too early. The comparison stack at the point of bailing looks like:(literal) Array<string>SparseArray<string>(literal) Array<string>["slice"]SparseArray<string>["slice"]Array<string>SparseArray<string>Array<string>["every"]SparseArray<string>["every"]Array<any>ReadonlySparseArray<any>Array<any>["slice"]ReadonlySparseArray<any>["slice"]Array<any>SparseArray["any"]Array<any>["flatMap"]SparseArray<any>["flatMap"]U | ReadonlyArray<U | ReadonlySparseArray<U>> | ReadonlySparseArray<U>U | ReadonlyArray<U> | ReadonlySparseArray<U>ReadonlyArray<U | ReadonlySparseArray<U>>ReadonlySparseArray<U>ReadonlyArray<U | ReadonlySparseArray<U>>["slice"]ReadonlySparseArray<U>["slice"]Array<U | ReadonlySparseArray<U>>SparseArray<U>While this looks pretty chaotic, it does finish with the correct results if you bump the
maxDepthparameter ofisDeeplyNestedTypefrom3to4, with the following error elaboration:Note that the comparison at index 8 is the result of contextual signature instantiation after a potentially questionable inference explained here. That’s a separate bug, but fixing it would probably prevent this particular example from triggering the depth limiter.
andrewbranch commentedon Apr 26, 2024
#58321 fixes this by changing the weird inference mentioned above, but I feel like it’s probably wrong
andrewbranch commentedon Jul 29, 2024
Anders Hejlsberg (@ahejlsberg) says possibly related to #56291 and #55666
typescript-bot commentedon Aug 1, 2024
This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.