Skip to content

Inconsistent incompatible types error for a sparse array type declaration #50351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
upsuper opened this issue Aug 18, 2022 · 5 comments
Closed
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@upsuper
Copy link

upsuper commented Aug 18, 2022

Bug Report

🔎 Search Terms

incompatible types inconsistent

🕗 Version & Regression Information

The behavior changes between 4.4.4 and 4.5.5, but the previous state wasn't the expected result either.

⏯ Playground Link

Playground link with relevant code

💻 Code

export interface ReadonlySparseArray<T> {
  readonly length: number;
  slice(start?: number, end?: number): SparseArray<T>;
  every<S extends T>(
      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 | readonly U[] | ReadonlySparseArray<U>): U[];
}

export interface SparseArray<T> extends ReadonlySparseArray<T> {
  [n: number]: T | undefined;
}

// const _a: SparseArray<string> = ['a'];

export function foo<T>(): ReadonlySparseArray<T> {
    let a: T[] = [];
    return a;
}

🙁 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.

@upsuper
Copy link
Author

upsuper commented 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 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.

@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Feb 1, 2023
@andrewbranch
Copy link
Member

andrewbranch commented Apr 24, 2024

This is caused by a false positive of isDeeplyNestedType. @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> U | ReadonlyArray<U> | ReadonlySparseArray<U>
9 ReadonlyArray<U | ReadonlySparseArray<U>> ReadonlySparseArray<U>
10 ReadonlyArray<U | ReadonlySparseArray<U>>["slice"] ReadonlySparseArray<U>["slice"]
11 Array<U | ReadonlySparseArray<U>> SparseArray<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.

@andrewbranch
Copy link
Member

#58321 fixes this by changing the weird inference mentioned above, but I feel like it’s probably wrong

@andrewbranch andrewbranch added Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Bug A bug in TypeScript Rescheduled This issue was previously scheduled to an earlier milestone labels Jul 29, 2024
@andrewbranch andrewbranch removed their assignment Jul 29, 2024
@andrewbranch andrewbranch removed this from the TypeScript 5.7.0 milestone Jul 29, 2024
@andrewbranch
Copy link
Member

andrewbranch commented Jul 29, 2024

@ahejlsberg says possibly related to #56291 and #55666

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Design Limitation" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants