TypeScript: [NewErrors] 5.4.0-dev.20240121 vs 5.3.3

The following errors were reported by 5.4.0-dev.20240121, but not by 5.3.3 Pipeline that generated this bug Logs for the pipeline run File that generated the pipeline

This run considered 200 popular TS repos from GH (after skipping the top 0).

Successfully analyzed 115 of 200 visited repos
Outcome Count
Detected interesting changes 12
Detected no interesting changes 103
Git clone failed 3
Package install failed 35
Project-graph error in old TS 5
Too many errors in old TS 38
Unknown failure 4

Investigation Status

Repo Errors Outcome
BuilderIO/qwik 2 #56004, #56598
chakra-ui/chakra-ui 1 Not a regression
drizzle-team/drizzle-orm 16 #56004
heyxyz/hey 1 Intentional isolatedModules correctness improvement
pixijs/pixijs 2 Routine DOM update #57027
prisma/prisma 10 Intentional isolatedModules correctness improvement
pubkey/rxdb 3 #54477, only showed up in editor for some reason
quilljs/quill 3 Unused ts-expect-error due to #56908, Intentional isolatedModules correctness improvement
react-hook-form/react-hook-form 1 #56004
reduxjs/react-redux 2 #56515
refined-github/refined-github 5 #56004
vuejs/core 3 #55015, #56004

About this issue

  • Original URL
  • State: closed
  • Created 5 months ago
  • Comments: 29 (16 by maintainers)

Commits related to this issue

Most upvoted comments

Ok, I now see what’s going on.

The repro checks whether EPlusFallback<Lowercase<T>> is assignable to EPlusFallback<Keys> (which is just {}).

With #56004, the constraint of EPlusFallback<Lowercase<T>> ends up correctly being type EMap["event" & Lowercase<T>] | "unrecognized event", where previously it would just be "unrecognized event".

The constraint of EMap["event" & Lowercase<T>] in turn is reduced to EMap["event" & Lowercase<string>].

This is where things go wrong. We currently lack the ability to reduce unions and intersections of string literal types (like "event") and string mapping types with non-generic placeholders (like Lowercase<string>). The intersection "event" & Lowercase<string> really should reduce to just "event", but it doesn’t. So, we fail to consider it a valid index type in EMap["event" & Lowercase<string>], which therefore ends up being unknown. And unknown isn’t assignable to {}, so therefore error.

So, long story short, we can fix this issue by doing a better job reducing unions and intersections of string literal types and string mapping types with non-generic placeholders. I will look at putting up a PR.

Ok, I see the same effects with your repro. The root cause is #56004, but the issue only shows up after #56598 because that PR fixes template literal placeholder matching such that `${string & Record<never, never>}` behaves the same as just `${string}` (which indeed it should). And apparently we use template literal matching when checking relations for two string mapping types (this happens in isMemberOfStringMapping). Crazy.

Regarding the new react-hook-form error, the issue is the Record<string, any> constraint on T. We have rule that says an object with an [x: string]: any index signature is a supertype of any object type, even if that object type doesn’t have a matching index signature. This includes function types, so in the example, the typeof options === "function" check can’t just narrow the type to AsyncDefaultValues<T>. Indeed, options could be any arbitrary function. To wit, this call is permitted:

f(() => "hello");

The reason we issue an implicit any error on the data parameter is that, since the function check can really only narrow to type Function, we end up with an untyped function call.

Things work fine when the constraint of T is changed to Record<string, unknown>. In that case we error on calls to f with arbitrary function types, and we narrow to AsyncDefaultValues<T> following the function check.

Other than adding a type assertion, I’m not sure what the best fix is in the react-hook-form project since new errors pop up if I just change the constraint to Record<string, unknown>. But, certainly, I think our is correct and points out a latent issue in the code.

Copying over my comment from #56972

Minimal repro of a “cannot be used as an index type” error:

type Type = {prop: 'a', otherProp: unknown}

type X = 'prop' extends keyof Type
    ? Type['prop']
           ^^^^^^ Type '"prop"' cannot be used as an index type.(2538)
    : never;