Skip to content
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

Inconsistent tsc behavior on TS 5.4 #58175

Closed
olegafx opened this issue Apr 12, 2024 · 6 comments · Fixed by #58677
Closed

Inconsistent tsc behavior on TS 5.4 #58175

olegafx opened this issue Apr 12, 2024 · 6 comments · Fixed by #58677
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@olegafx
Copy link

olegafx commented Apr 12, 2024

🔎 Search Terms

"tsc inconsistent behavior", "tsc 0 errors", "ts 5.4 bug", "5.4.0-dev.20240219"

🕗 Version & Regression Information

I wanted to update TS in our project from v5.3.3 to v5.4.3. And I noticed several weird things:

  • When I was switching TS version in VS Code, it wasn't showing any errors. But tsc --noEmit was showing 2 errors. I thought that VS Code was just not ready to support v5.4.3 yet. But the new version comes with v5.4.3 and it still doesn't work correctly.
  • I run tsc --noEmit. It shows 2 errors. If I open a problematic file and put // @ts-expect-error, it shows only 1 error. But if I remove that line, it instantly shows 0 errors.

This changed between versions 5.4.0-dev.20240218 and 5.4.0-dev.20240219.

5.4.0-dev.20240219 is the first version where I see this problem. I checked @next version which is 5.5.0-dev.20240412 and the error is still there.

Unfortunately, I wasn't able to make a reproducible example due complexity of the code. But both errors are about unknown type where it should be detected just fine.

⏯ Playground Link

No response

💻 Code

No response

🙁 Actual behavior

  • It doesn't show errors in VS Code
  • It shows errors in a console. But after applying // @ts-expect-error and removing that line, it shows 0 errors.

🙂 Expected behavior

  • Errors in VS Code are detected
  • It shows errors in a console. After applying // @ts-expect-error and removing that line, it shows all previous errors.

Additional information about the issue

No response

@RyanCavanaugh RyanCavanaugh added the Needs More Info The issue still hasn't been fully clarified label Apr 12, 2024
@RyanCavanaugh
Copy link
Member

There's really not much we can do without a way to reproduce the problem, unfortunately

@olegafx
Copy link
Author

olegafx commented Apr 15, 2024

Ok, I was able to reproduce it. Here is a link to StackBlitz. There are 2 main cases:

  1. If you'll open fail.tsx, you won't see any errors in the editor. But if you will run yarn tsc --noEmit in the terminal, you'll see src/fail/fail.tsx:18:22 - error TS2322: Type 'unknown[]' is not assignable to type 'FailedItem[]'.
  2. Start tsc in watch mode: yarn tsc --noEmit --watch. Open fail.tsx and add // @ts-expect-error in any place of the code. Remove this comment. It will show 0 errors in the console.

Also you could notice that there is import '@total-typescript/ts-reset/is-array'; in reset.d.ts. The only thing it's doing is changing any to unknown for Array.isArray. Source code.

React version doesn't really matter. It works the same on v16 and v18.

@RyanCavanaugh RyanCavanaugh removed the Needs More Info The issue still hasn't been fully clarified label Apr 15, 2024
@DanielRosenwasser DanielRosenwasser added the Bug A bug in TypeScript label Apr 19, 2024
@DanielRosenwasser
Copy link
Member

For what it's worth, the only thing that was merged in between those dates was #57122

@ahejlsberg
Copy link
Member

This one is truly bizarre and took some time to figure out. It isn't directly related to #57122, but apparently that PR causes types to be resolved in a different order that triggers the deeper issue.

Here's a shorter repro:

type Includes<T, Keys extends string> = T extends object
  ? {
      [K in keyof T]-?:
          NonNullable<T[K]> extends object
            ? NonNullable<T[K]> extends any[]
              ? Includes<NonNullable<T[K]>[number], Keys>[]
              : Includes<NonNullable<T[K]>, Keys>
            : T[K];
    }
  : T;

type BaseEntity = {
  id: number;
  name: string;
};

type T0<T extends any[] & object> = T;
type T1 = T0<BaseEntity[]>;  // Error!

There obviously shouldn't be an error, and the error message is confounding:

Type 'BaseEntity[]' does not satisfy the constraint 'any[] & object'.
  The types returned by 'sort(...)' are incompatible between these types.
    Type 'BaseEntity[]' is not assignable to type 'NonNullable<T[K]>'.
      Type 'BaseEntity[]' is not assignable to type 'T[K]'.(2344)```

And, strangely, the error goes away when the Includes type is commented out.

It turns out to be a caching problem. Specifically, our caching of apparent types of intersection types. From checker.ts:

function getApparentTypeOfIntersectionType(type: IntersectionType, thisArgument: Type) {
    return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, thisArgument, /*needApparentType*/ true));
}

This logic fails to account for the thisArgument: We simply cache the result of the first invocation regardless of the thisArgument type. Oridinarily, the thisArgument is the same as type, but not for generic types with intersection type constraints. And the most deeply nested NonNullable<T[K]> type reference in the Includes type has the intersection type constraint any[] & object because of the surrounding conditional types.

The fix isn't hard. We simply need to account for both arguments to getApparentTypeOfIntersectionType in the caching logic. I will put up a PR to that affect,

@ahejlsberg
Copy link
Member

ahejlsberg commented May 28, 2024

Even shorter repro:

type TX<T extends any[] & object> = T["length"];  // Comment this out and error goes away
type T0<U extends any[] & object> = U;
type T1 = T0<string[]>;  // Error!

This reports the error:

Type 'string[]' does not satisfy the constraint 'any[] & object'.
  The types returned by 'sort(...)' are incompatible between these types.
    Type 'string[]' is not assignable to type 'T'.
      'T' could be instantiated with an arbitrary type which could be unrelated to 'string[]'.

which obviously is crazy since T isn't even in scope in T0.

@olegafx
Copy link
Author

olegafx commented Jun 24, 2024

Just update our project to TS 5.5. And everything works great! Thanks a lot, guys! 💪🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants