Typescript: tsc, tsserver: hangs with large union type and object spread in React HOC (strict mode)

Created on 18 Feb 2019  路  19Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.4.0-dev.20190216


Search Terms: hang higher order union strictFunctionTypes

Code

Note that this code has compilation errors, but that's not important. When invoking tsc or loading in an editor that uses tsserver, the compilation hangs/never completes.

import * as React from "react";

const animated: {
  [Tag in keyof JSX.IntrinsicElements]: React.ForwardRefExoticComponent<
    React.ComponentPropsWithRef<Tag>
  >
} = {};

function makeAnimated<T extends React.ReactType>(
  comp: T
): React.ForwardRefExoticComponent<React.ComponentPropsWithRef<T>> {
  return null as any; // not important
}

export interface UpgradedProps {
  show: boolean;
}

export function test<P>(
  component: React.ComponentType<P> | keyof React.ReactHTML
): React.ComponentType<P & UpgradedProps> {
  // changing to `const Comp: any` un-hangs tsserver
  const Comp =
    typeof component === "string"
      ? animated[component]
      : makeAnimated(component);

  return React.forwardRef<any, P & UpgradeProps>((props, ref) => {
    const { show, ...ownProps } = props; // addition of this line causes the hang
    return show ? <Comp {...ownProps} ref={ref} /> : null;
  });
}

Expected behavior:

Compilation completes (optionally with errors).

Actual behavior:

Compilation hangs.

Repro Link: https://github.com/jgoz/typescript-bug

Related Issues: Didn't find anything recent

Bug Big Unions Rescheduled

Most helpful comment

@aleclarson oh! Is anyone else getting what I鈥檓 getting the ? Last couple months VSCode as been nearly unusable (with no extensions), autocomplete takes a couple seconds, cursor and text input lags often and sometimes they spike to a freeze. This is on a pretty powerful new laptop.

All 19 comments

With Version 3.4.0-dev.20190227:

D:\Throwaway\reactish>tsc a.tsx --diagnostics
a.tsx:3:7 - error TS2740: Type '{}' is missing the following properties from type '{ a: ForwardRefExoticComponent<Pick<DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "media" | "hidden" | "dir" | "slot" | "style" | "title" | "color" | "key" | "children" | ... 249 more ... | "onTransitionEndCapture"> & { ...; }>; ... 170 more ...; view: ForwardRefExoticComponent<...>; }': a, abbr, address, area, and 168 more.

3 const animated: {
        ~~~~~~~~

a.tsx:28:3 - error TS2322: Type 'ForwardRefExoticComponent<Pick<any, string | number | symbol> & RefAttributes<any>>' is not assignable to type 'ComponentType<P & UpgradedProps>'.
  Type 'ForwardRefExoticComponent<Pick<any, string | number | symbol> & RefAttributes<any>>' is not assignable to type 'FunctionComponent<P & UpgradedProps>'.
    Types of property 'defaultProps' are incompatible.
      Type 'Partial<Pick<any, string | number | symbol> & RefAttributes<any>>' has no properties in common with type 'Partial<P & UpgradedProps>'.

28   return React.forwardRef<any, P & UpgradeProps>((props, ref) => {
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29     const { show, ...ownProps } = props; // addition of this line causes the hang
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30     return show ? <Comp {...ownProps} ref={ref} /> : null;
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31   });
   ~~~~~

a.tsx:28:36 - error TS2304: Cannot find name 'UpgradeProps'.

28   return React.forwardRef<any, P & UpgradeProps>((props, ref) => {
                                      ~~~~~~~~~~~~

a.tsx:30:19 - error TS17004: Cannot use JSX unless the '--jsx' flag is provided.

30     return show ? <Comp {...ownProps} ref={ref} /> : null;
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 4 errors.

Files:            13
Lines:         65254
Nodes:        211336
Identifiers:   67355
Symbols:      148609
Types:         58118
Memory used: 174251K
I/O read:      0.02s
I/O write:     0.00s
Parse time:    0.50s
Bind time:     0.22s
Check time:    3.50s
Emit time:     0.03s
Total time:    4.25s

With --skipLibCheck (highly recommended), total time is 1.44s. Can you try with latest and see what you see?

@RyanCavanaugh With Version 3.4.0-dev.20190227, tsc does complete but tsserver still hangs. This can be observed in VS Code by hovering the mouse over animated or makeAnimated - it just shows Loading... and never changes.

I do have "skipLibCheck": true in my tsconfig.

I tweaked the example to remove compilation errors:

import * as React from "react";

const animated: {
  [Tag in keyof JSX.IntrinsicElements]: React.ForwardRefExoticComponent<
    React.ComponentPropsWithRef<Tag>
  >
} = {} as any;

function makeAnimated<T extends React.ReactType>(
  _comp: T
): React.ForwardRefExoticComponent<React.ComponentPropsWithRef<T>> {
  return null as any; // not important
}

export interface UpgradedProps {
  show: boolean;
}

export function test<P>(
  component: React.ComponentType<P> | keyof React.ReactHTML
): any {
  // --> changing to `const Comp: any` un-hangs tsserver <--
  const Comp =
    typeof component === "string"
      ? animated[component]
      : makeAnimated(component);

  return React.forwardRef<any, P & UpgradedProps>((props, ref) => {
    const { show, ...ownProps } = props;
    return show ? <Comp {...ownProps} ref={ref} /> : null;
  });
}

image

Not sure what's going on! If you can get a debugger attached to tsserver that may be helpful, though it's hard to say since this is a pretty small repro and we really should be seeing the same results.

Here is my tsconfig for reference:

{
  "compileOnSave": false,
  "compilerOptions": {
    "importHelpers": true,
    "jsx": "react",
    "lib": [
      "dom",
      "es5",
      "es2015.collection",
      "es2015.iterable",
      "es2015.promise",
      "es2015.symbol"
    ],
    "module": "commonjs",
    "moduleResolution": "node",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "preserveConstEnums": true,
    "preserveSymlinks": true,
    "removeComments": false,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "target": "es5"
  }
}

@RyanCavanaugh I just disabled all options and re-enabled one-by-one. The server will hang when strict: true is set.

{
  "compileOnSave": false,
  "compilerOptions": {
    "jsx": "react",
    "skipLibCheck": true,
    "strict": true
  }
}

There's the trick! Thank you

Pretty sure this is to do with type string genaration.

Looks like the specific strict flag that's in the mix here is strictFunctionTypes.

I created this a few days ago without doing much investigation, but just today discovered that it is caused by strictFunctionTypes. Perhaps it's the same problem?

Maybe - #30411 may also help.

Spent some time looking at this the other day, though (and talking about it in our design meeting) - ultimately the ref field in react, when materialized, currently necessitates making a union of 2^111 members. Naturally, we cannot actually do that. The reason for that is that the function type-ref and the object type-ref are not mutually exclusive - so when 111 union-pairs of them are intersected together, we have to form the powerset of alternatives that could be in the resulting union. If we could recognize {(ref: T): void; currentRef?: undefined} and {readonly currentRef: T} as mutually exclusive (we do not currently), and redefine the function ref to be along the lines of {(ref: T): void; currentRef?: undefined}, then we could simplify many of the alternatives as we go when we actually construct the type (and have a resulting union that _only_ has around 222 members).

@weswigham Function refs do not have a current property, so function and object refs _should_ be mutually exclusive. This is how they're defined in the react typings:

interface RefObject<T> {
    readonly current: T | null;
}

type Ref<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"] | RefObject<T> | null;

interface RefAttributes<T> extends Attributes {
    ref?: Ref<T>;
}

Or is there some other definition you are referring to where they do overlap?

Function refs do not have a current property

They don't define one, however as written a {(instance: T | null): void; readonly current: T | null} would actually be assignable to a { bivarianceHack(instance: T | null): void }["bivarianceHack"], _and_ to a RefObject<T>. However, were the function type defined as type FunctionRef<T> = { bivarianceHack(instance: T | null): void }["bivarianceHack"] & { current?: undefined } it would be impossible for a type to be both a RefObject and a RefFunction sans fields of type never, which would themselves imply the containing type can't actually exist (we don't currently detect this, which is why it'd also need some changes in TS).

Ah, makes sense, thank you for the explanation!

I ran into a similar memory issue which was causing tsc to get killed on CI due to lack of memory. I refactored the code to use the newer React.createRef() API and typed all my refs as RefObject<T>, whose type doesn't involve { bivarianceHack(instance: T | null): void }["bivarianceHack"] and the memory usage went down enough that my CI build passes again.

I'm posting this here in case this helps someone else who also didn't want to disable strictFunctionTypes.

@weswigham So, you think the best course of action would be to change the Ref type in @types/react?

@aleclarson is this the ref issue still? Maybe I can take a stab at it, care to just point me to the general area that causes it?

@natew It's allegedly an issue with the @types/react package. See here.

@aleclarson oh! Is anyone else getting what I鈥檓 getting the ? Last couple months VSCode as been nearly unusable (with no extensions), autocomplete takes a couple seconds, cursor and text input lags often and sometimes they spike to a freeze. This is on a pretty powerful new laptop.

is there any other solutions or workarounds for this?
for me turning off

"strict": false,
"strictNullChecks": false,
"strictPropertyInitialization": false

fixed it

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  路  3Comments

seanzer picture seanzer  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

blendsdk picture blendsdk  路  3Comments

dlaberge picture dlaberge  路  3Comments