microsoft / microsoft/TypeScript

“Type instantiation is excessively deep and possibly infinite” but only in a large codebase

Abierto
#34,933 73 comentarios 156 reacciones 1 asignado Ver en GitHub

@weswigham ya está trabajando en esto.

Desde el 12/11/2019.

Bug Domain: check: Big Unions Rescheduled
Lenguaje dominante
Go
Estrellas
111k
Forks
14.4k
Merge medio
1 d 19 h
PR fusionados (30 d)
117

Descripción

TypeScript Version: 3.7.2, 3.8.0-dev.20191102 (worked in 3.6)

Search Terms:

  • Type instantiation is excessively deep and possibly infinite.ts(2589)
  • Mapped types
  • Generics
  • Conditional types

Code

Note: this issue manifests itself only in our codebase. When you run the same code in TypeScript Playground, it seems to be working fine.

The snippet is hardly minimal, but I reduced it as much as I could. I recorded a video where exactly the same code yields an error different than the one in TypeScript Playground. I tried with two versions of TypeScript: 3.7.2 and 3.8.0-dev.20191102. It worked correctly with 3.6.

Since @sheetalkamat and @DanielRosenwasser have access to our repository, you're welcome to have a look at this PR. Copy-paste the code below anywhere in the project to see the error.

The versions of types used:

  • @types/history@4.7.3
  • @types/react@16.9.11
  • @types/react-router-dom@5.1.0
  • @types/recompose@0.30.7

Note: Interestingly enough, if you change:

- declare const Button: React.FunctionComponent<Omit<Props, never>>;
+ declare const Button: React.FunctionComponent<Props>;

it works again despite the fact Omit<Props, never> should be the same as just Props.

Source code
import { History } from 'history'; // "4.7.3"
import * as React from 'react'; // "16.9.11"
import { LinkProps, RouteComponentProps, withRouter } from 'react-router-dom'; // "5.1.0"
import { getDisplayName } from 'recompose'; // "0.30.7"

declare function isDefined<T>(candidate: T | null | undefined): candidate is T;
declare function isString(value?: any): value is string;

type ObjectOmit<T extends K, K> = Omit<T, keyof K>;

type OnClick = NonNullable<React.ComponentProps<'button'>['onClick']>;

type OnClickProp = {
  /** If there is a custom click handler, we must preserve it. */
  onClick?: OnClick;
};

type ProvidedProps = OnClickProp;

type InputProps = OnClickProp & {
  /** Note: we want this helper to work with all sorts of modals, not just those backed by query
   * parameters (e.g. `/photo/:id/info`), which is why this must accept a full location instead of a
   * `Modal` type.
   * */
  to: Exclude<LinkProps['to'], Function>;
};

const buildClickHandler = ({
  to,
  onClick,
  history,
}: InputProps & {
  history: History;
}): OnClick => {
  const navigate = () => {
    // https://github.com/Microsoft/TypeScript/issues/14107
    isString(to) ? history.push(to) : history.push(to);
  };

  return event => {
    [onClick, navigate].filter(isDefined).forEach(callback => callback(event));
  };
};

/** See the test for an example of usage. */
export const enhance = <ComposedProps extends ProvidedProps>(
  ComposedComponent: React.ComponentType<ComposedProps>,
) => {
  type PassThroughComposedProps = ObjectOmit<ComposedProps, ProvidedProps>;
  type OwnProps = InputProps & RouteComponentProps<never> & PassThroughComposedProps;
  type Props = OwnProps;

  const displayName = `CreateModalLink(${getDisplayName(ComposedComponent)})`;

  const ModalLink: React.FunctionComponent<Props> = ({
    to,
    onClick,

    history,
    // We specify these just to omit them from rest props below
    location,
    match,
    staticContext,

    ...passThroughComposedProps
  }) => {
    const clickHandler = buildClickHandler({ to, onClick, history });

    const composedProps: ComposedProps = {
      // Note: this is technically unsafe, since the composed component may have props
      // with names matching the ones we're omitting.
      // https://github.com/microsoft/TypeScript/issues/28884#issuecomment-503540848
      ...((passThroughComposedProps as unknown) as PassThroughComposedProps),
      onClick: clickHandler,
    } as ComposedProps;

    return <ComposedComponent {...composedProps} />;
  };

  ModalLink.displayName = displayName;

  return withRouter(ModalLink);
};

type Props = React.ComponentPropsWithoutRef<'button'> &
  Required<Pick<React.ComponentPropsWithoutRef<'button'>, 'type'>>;

/**
 * This one errors.
 */
declare const Button: React.FunctionComponent<Omit<Props, never>>;

/**
 * This one works.
 */
// declare const Button: React.FunctionComponent<Props>;

const EnhancedButton = enhance(Button);

/**
 * Type instantiation is excessively deep and possibly infinite.ts(2589).
 */
() => <EnhancedButton></EnhancedButton>;


Expected behavior:

I should get a proper error about missing properties (not the one about type instantiation):

Type '{}' is missing the following properties from type 'Readonly<Pick<OwnProps, "form" | "style" | "title" | "onClick" | "to" | "key" | "autoFocus" | "disabled" | "formAction" | "formEncType" | "formMethod" | "formNoValidate" | "formTarget" | ... 252 more ... | "onTransitionEndCapture">>': to, type(2739)

Actual behavior:

I'm getting this:

Type instantiation is excessively deep and possibly infinite.ts(2589).

Playground Link:

Playground Link

Related Issues:

  • #32735
  • #34850

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.