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

New error: Excessive stack depth comparing types #37600

Closed
amcasey opened this issue Mar 25, 2020 · 2 comments · Fixed by #37851
Closed

New error: Excessive stack depth comparing types #37600

amcasey opened this issue Mar 25, 2020 · 2 comments · Fixed by #37851
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@amcasey
Copy link
Member

amcasey commented Mar 25, 2020

So many things went wrong that I'm just going to paste the list of errors here.

One location for context: https://github.com/react-page/react-page/blob/fb17a032bd6415080e468c0a43fd0f9c45a22cbe/packages/core/src/service/plugin/default.tsx#L51

error TS2321: Excessive stack depth comparing types '{ Component: SFC<ContentPluginProps<{ value: string; }>>; name: string; version: string; createInitialState: () => { value: string; }; }' and 'Pick<ContentPluginProps<{}>, "allowInlineNeighbours" | "isInlineable" | "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | ... 9 more ... | "createInitialState">'.

packages/core/src/service/plugin/default.tsx:51:3 - error TS2322: Type 'SFC<ContentPluginProps<{ value: string; }>>' is not assignable to type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<{}, ContentPluginExtraProps<{}>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
  Type 'FunctionComponent<ContentPluginProps<{ value: string; }>>' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any> & FunctionComponent<PluginProps<{}, ContentPluginExtraProps<{}>> & ContentPluginExtraProps<...>>'.
    Type 'FunctionComponent<ContentPluginProps<{ value: string; }>>' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any>'.
      The types of 'propTypes.Component' are incompatible between these types.
        Type 'Validator<(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<{ value: string; }, ContentPluginExtraProps<{ value: string; }>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & Fun...' is not assignable to type 'Validator<(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)>'.
          Type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<{ value: string; }, ContentPluginExtraProps<{ value: string; }>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionCompo...' is not assignable to type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
            Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<{ value: string; }, ContentPluginExtraProps<{ value: string; }>> & ContentPluginExtraProps<...>, any>' is not assignable to type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
              Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<{ value: string; }, ContentPluginExtraProps<{ value: string; }>> & ContentPluginExtraProps<...>, any>' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>'.
                Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<{ value: string; }, ContentPluginExtraProps<{ value: string; }>> & ContentPluginExtraProps<...>, any>' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any>'.
                  The types of 'propTypes.Component[nominalTypeHack].type' are incompatible between these types.
                    Type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any> & ComponentClass<...> & FunctionComponent<...>) | ... 14 more ... | (FunctionComponent<...> & ... 2 more ... & ComponentClass<...>)' is not assignable to type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
                      Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any> & ComponentClass<...> & FunctionComponent<...>' is not assignable to type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
                        Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any> & ComponentClass<...> & FunctionComponent<...>' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any> & FunctionComponent<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>>'.
                          Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any> & ComponentClass<...> & FunctionComponent<...>' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any>'.

51   Component: Default,
     ~~~~~~~~~

  packages/core/src/service/plugin/classes.ts:112:3
    112   Component?: PluginComponentType<ContentPluginProps<T>>;
          ~~~~~~~~~
    The expected type comes from property 'Component' which is declared here on type 'Pick<ContentPluginProps<{}>, "allowInlineNeighbours" | "isInlineable" | "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | ... 9 more ... | "createInitialState">'

error TS2321: Excessive stack depth comparing types '{ Component: (props: ContentPluginProps<{}>) => Element; name: string; version: string; }' and 'Pick<ContentPluginProps<any>, "allowInlineNeighbours" | "isInlineable" | "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | ... 9 more ... | "createInitialState">'.

packages/core/src/service/plugin/missing.tsx:51:3 - error TS2322: Type '(props: ContentPluginProps<{}>) => JSX.Element' is not assignable to type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
  Type '(props: ContentPluginProps<{}>) => JSX.Element' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any> & FunctionComponent<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>>'.
    Type '(props: ContentPluginProps<{}>) => JSX.Element' is not assignable to type 'ComponentClass<ContentPluginProps<any>, any>'.
      Type '(props: ContentPluginProps<{}>) => Element' provides no match for the signature 'new (props: ContentPluginProps<any>, context?: any): Component<ContentPluginProps<any>, any, any>'.

51   Component: ContentMissingComponent,
     ~~~~~~~~~

  packages/core/src/service/plugin/classes.ts:112:3
    112   Component?: PluginComponentType<ContentPluginProps<T>>;
          ~~~~~~~~~
    The expected type comes from property 'Component' which is declared here on type 'Pick<ContentPluginProps<any>, "allowInlineNeighbours" | "isInlineable" | "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | ... 9 more ... | "createInitialState">'

packages/core/src/reducer/editable/index.ts:53:17 - error TS2322: Type '{ layout: { plugin: any; state: any; }; id: any; rows: { id: string; cells: { [key: string]: any; id: string; content?: { plugin: ContentPlugin<any>; state?: object; }; layout?: { plugin: LayoutPlugin<any>; state?: object; }; size?: number; }[]; }[]; ... 10 more ...; levels?: Levels; }' is not assignable to type 'AbstractCell<Row>'.
  Types of property 'rows' are incompatible.
    Type '{ id: string; cells: { [key: string]: any; id: string; content?: { plugin: ContentPlugin<any>; state?: object; }; layout?: { plugin: LayoutPlugin<any>; state?: object; }; size?: number; }[]; }[]' is not assignable to type 'Row[]'.
      Type '{ id: string; cells: { [key: string]: any; id: string; content?: { plugin: ContentPlugin<any>; state?: object; }; layout?: { plugin: LayoutPlugin<any>; state?: object; }; size?: number; }[]; }' is not assignable to type 'Row'.
        Types of property 'cells' are incompatible.
          Type '{ [key: string]: any; id: string; content?: { plugin: ContentPlugin<any>; state?: object; }; layout?: { plugin: LayoutPlugin<any>; state?: object; }; size?: number; }[]' is not assignable to type 'AbstractCell<Row>[]'.
            Type '{ [key: string]: any; id: string; content?: { plugin: ContentPlugin<any>; state?: object; }; layout?: { plugin: LayoutPlugin<any>; state?: object; }; size?: number; }' is not assignable to type 'AbstractCell<Row>'.
              The types of 'layout.plugin.handleFocus' are incompatible between these types.
                Type '(props: ContentPluginProps<any>, focusSource: string, ref: HTMLElement) => void' is not assignable to type '(props: PluginProps<any, LayoutPluginExtraProps<any>> & LayoutPluginExtraProps<any>, focusSource: string, ref: HTMLElement) => void'.
                  Types of parameters 'props' and 'props' are incompatible.
                    Type 'PluginProps<any, LayoutPluginExtraProps<any>> & LayoutPluginExtraProps<any>' is not assignable to type 'ContentPluginProps<any>'.
                      Type 'PluginProps<any, LayoutPluginExtraProps<any>> & LayoutPluginExtraProps<any>' is missing the following properties from type 'ContentPluginExtraProps<any>': isEditMode, isResizeMode, isInsertMode, isPreviewMode, isLayoutMode

53           const c: Cell = {
                   ~

packages/core/src/components/HotKey/Decorator.tsx:194:50 - error TS2615: Type of property 'editable' circularly references itself in mapped type '{ isEditMode: Selector<unknown, boolean>; focus: Selector<unknown, string>; node: Selector<unknown, (id: string, _editable: string) => Row | AbstractCell<Row>>; searchNodeEverywhere: Selector<...>; editable: Selector<...>; editables: Selector<...>; }'.

194 const mapStateToProps = createStructuredSelector({
                                                     ~
195   isEditMode,
    ~~~~~~~~~~~~~
... 
204   editables,
    ~~~~~~~~~~~~
205 });
    ~

packages/core/src/components/Row/index.tsx:65:50 - error TS2615: Type of property 'rawNode' circularly references itself in mapped type '{ isLayoutMode: Selector<unknown, boolean>; config: Selector<unknown, Config>; isResizeMode: Selector<unknown, boolean>; isInsertMode: Selector<unknown, boolean>; isEditMode: Selector<...>; node: Selector<...>; rawNode: Selector<...>; }'.

 65 const mapStateToProps = createStructuredSelector({
                                                     ~
 66   isLayoutMode,
    ~~~~~~~~~~~~~~~
... 
 73     node(state, props),
    ~~~~~~~~~~~~~~~~~~~~~~~
 74 });
    ~

packages/core/src/components/Cell/index.tsx:121:50 - error TS2615: Type of property 'rawNode' circularly references itself in mapped type '{ isPreviewMode: Selector<unknown, boolean>; isEditMode: Selector<unknown, boolean>; isResizeMode: Selector<unknown, boolean>; isInsertMode: Selector<unknown, boolean>; isLayoutMode: Selector<...>; config: Selector<...>; node: Selector<...>; rawNode: Selector<...>; }'.

121 const mapStateToProps = createStructuredSelector({
                                                     ~
122   isPreviewMode,
    ~~~~~~~~~~~~~~~~
... 
130   rawNode: (state: RootState, props: NodeProps) => () => node(state, props),
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
131 });
    ~

packages/core/src/components/Editable/Inner/index.tsx:105:56 - error TS2345: Argument of type 'Pick<ContentPluginProps<any>, "allowInlineNeighbours" | "isInlineable" | "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | ... 10 more ... | "createInitialState"> | Pick<...>' is not assignable to parameter of type 'Pick<LayoutPluginProps<any>, "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | "unserialize" | "description" | "handleRemoveHotKey" | ... 7 more ... | "createInitialChildren">'.
  Type 'Pick<ContentPluginProps<any>, "allowInlineNeighbours" | "isInlineable" | "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | ... 9 more ... | "createInitialState">' is not assignable to type 'Pick<LayoutPluginProps<any>, "Component" | "name" | "version" | "IconComponent" | "hideInMenu" | "text" | "serialize" | "unserialize" | "description" | "handleRemoveHotKey" | ... 7 more ... | "createInitialChildren">'.
    Types of property 'Component' are incompatible.
      Type '(ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)' is not assignable to type '(ComponentClass<LayoutPluginProps<any>, any> & ComponentClass<PluginProps<any, LayoutPluginExtraProps<any>> & LayoutPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
        Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>' is not assignable to type '(ComponentClass<LayoutPluginProps<any>, any> & ComponentClass<PluginProps<any, LayoutPluginExtraProps<any>> & LayoutPluginExtraProps<...>, any>) | (ComponentClass<...> & FunctionComponent<...>) | (FunctionComponent<...> & ComponentClass<...>) | (FunctionComponent<...> & FunctionComponent<...>)'.
          Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>' is not assignable to type 'ComponentClass<LayoutPluginProps<any>, any> & ComponentClass<PluginProps<any, LayoutPluginExtraProps<any>> & LayoutPluginExtraProps<...>, any>'.
            Type 'ComponentClass<ContentPluginProps<any>, any> & ComponentClass<PluginProps<any, ContentPluginExtraProps<any>> & ContentPluginExtraProps<...>, any>' is not assignable to type 'ComponentClass<LayoutPluginProps<any>, any>'.

105         this.props.createFallbackCell(new LayoutPlugin(defaultPlugin), id);
                                                           ~~~~~~~~~~~~~

To repro:

  1. yarn
  2. tsc -b -f packages
@amcasey amcasey added this to the TypeScript 3.9.1 milestone Mar 25, 2020
@RyanCavanaugh RyanCavanaugh added the Bug A bug in TypeScript label Mar 25, 2020
@weswigham
Copy link
Member

OK, so, two seperate things going on here, probably. The first one, from the title: the change that introduced this definitely seems to be #37195. The structural comparison of the source intersection to the target is definitely what triggers the new errors. Now, what's going on is we have

export type ContentPluginProps<
  // tslint:disable-next-line:no-any
  T = any
> = ContentPluginExtraProps & PluginProps<T, ContentPluginExtraProps<T>>;

both ContentPluginExtraProps and PluginProps have a Component property, and they have differing types

{
  Component?: PluginComponentType<ContentPluginProps<T>>;
}

and

{
  Component?: PluginComponentType<
    PluginProps<StateT, ExtraPropsT> & ExtraPropsT
  >;
}

The comparison stack between the expression type and that intersection is maybe too long to reasonably explain thoroughly, but suffice to say that we end up pingponging around between the proprieties on a class/function component which refer to props (propTypes, defaultProps) and the props themselves which refer to a component (the Component field of the provided props), and we don't notice that we're essentially structurally comparing a deep type with self-augmenting back references (the back references are never identical because of the type intersected with it which is essentially a noop intersection member based on the current comparison cursor position in the type structure). Ultimately, we compare propTypes.Component[nominalTypeHack].type.propTypes.... (yes, it keeps looping for a bit) and the types we encounter as never quite the same, as on each iteration the definition of Component in PluginProps essentially intersects the current position's type into the next position, growing the next result's intersection. Do note that this does bottom out (in reasonable time, no less) - if the maximum depth in recursiveTypeRelatedTo is raised from 100 to 150ish. That's because the exploration continues only for every combination of {} and any default arguments multiplied by every combination of ClassComponent and FunctionComponent, plus every combination of ContentPluginExtraProps and PluginProps, after which point all the possible combinations have been made, tested, and shown to be sufficiently structurally similar.

There is a trivial fix/workaround/improvement which can be applied to react-page itself:

export type ContentPluginProps<
  // tslint:disable-next-line:no-any
  T = any
> = ContentPluginExtraProps<T> & PluginProps<T, ContentPluginExtraProps<T>>;

actually providing ContentPluginExtraProps's type parameter makes the props sufficiently similar that we actual find success quickly (as we're no longer comparing ContentPluginExtraProps<any> to ContentPluginExtraProps<{}> nearly as much), and avoids the stack depth limit.

@ahejlsberg because this is resolvable with an increase to maximal depth, we're not fundamentally doing anything we don't intend to, indeed, it simply seems like the conflicting property check, when applied on a self-referential structure, is simply extraordinarily costly to perform, as it crawls the entire structural matrix reachable from the inputs, which, in this case, is the powerset of a few inputs, resulting a a large number of recursive calls. You might want to try a perf analysis of react-page from before/after #37195, just in case - the material-ui perf suite might not contain any self referential structures which trigger this behavior, but, beyond that, it might make sense for the conflicting property check to be disabled if we're already doing a conflicting property check (I know excess checks are deep, but we know excess checks on object literals never contain self references!)

Now, the other issue, the circular reference in mapped type thing, I have yet to look at. I'll look into it next.

@weswigham
Copy link
Member

weswigham commented Apr 8, 2020

The circular mapped type properties are new (the error itself is new, the behavior leading to the circularity used to simply cache an any, rather than report an error), however the circular behavior leading to the error here is maybe undesirable - inside getReturnTypeFromBody we're looking for a contextual signature even in cases where we don't strictly need it (the function isn't a generator, so doesn't have a contextual nextType, doesn't have a literal-valued return so won't need literal widening, and , and doesn't have a return type affected by widening, so we don't need to decide if we should emit widening errors). Ultimately the circularity is triggered because the parameter is a mapped type, making the contextual type at an object member of that argument position an indexed access on a reverse mapped type, which in turn, causes a resolution of... the same property in the same mapped type. Essentially the function contextually types itself under our current rules, strange as that sounds... here's a minimal repro:

type Selector<S, R> = (state: S) => R;

declare function createStructuredSelector<S, T>(
  selectors: {[K in keyof T]: Selector<S, T[K]>},
): Selector<S, T>;

const editable = () => ({});

const mapStateToProps = createStructuredSelector({
  editable: (state: any, props: any) => editable(),
});

the circular reference error goes away if the arity perfectly matches, however in the real world example scenario, this is just one of two overloads for creatureStructuredSelector, the second of which definitely allows the extra parameter; so it throwing a circularity error because of an (unused) contextual type when checking the first overload is definitely undesirable.

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.

3 participants