typed-ember / glint

TypeScript powered tooling for Glimmer templates
https://typed-ember.gitbook.io/glint
MIT License
109 stars 51 forks source link

*Like-bug: (ModifierLike, ComponentLike, etc?) When using WithBoundArgs, Type instantiation is excessively deep and possibly infinite (requiring glint-ignore/expect-error to move forward in a project) #661

Open NullVoxPopuli opened 9 months ago

NullVoxPopuli commented 9 months ago

Repro:

Error:

app/components/limber/menu.gts:118:18 - error TS2589: Type instantiation is excessively deep and possibly infinite.

118           Button=(component PlainTrigger menu=menu trigger=p.hook)
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For this component, Menu, I've specified:

const Menu: TOC<{
  Element: HTMLDivElement;
  Args: {
    inline?: boolean;
  };
  Blocks: {
    trigger: [
      {
        menu: MenuTypes.Menu;
        isOpen: boolean;
        Default: WithBoundArgs<typeof DefaultTrigger, 'menu' | 'trigger'>;
        Button: WithBoundArgs<typeof PlainTrigger, 'menu' | 'trigger'>;
      },
    ];
    options: [button: WithBoundArgs<typeof Button, 'item'>];
  };
}> = <template>

and PlainTrigger is:

const PlainTrigger: TOC<{
  Element: HTMLButtonElement;
  Args: {
    menu: MenuTypes.Menu;
    trigger: ModifierLike<{ Element: HTMLButtonElement }>;
  };
  Blocks: {
    default: [MenuTypes.Menu];
  };
}> = <template>
  <@menu.Button {{@trigger}} ...attributes>
    {{yield @menu}}
  </@menu.Button>
</template>;

and the menu types are:

declare module 'ember-headlessui/components/menu' {
  import { ComponentLike } from '@glint/template';

  export type Element<T extends HTMLButtonElement | HTMLAnchorElement> = ComponentLike<{
    Element: T;
    Args: { tagName?: 'button' };
    Blocks: { default: [] };
  }>;

  export type Item = ComponentLike<{
    Element: HTMLDivElement;
    Blocks: { default: [{ Element: Element }] };
  }>;

  export type Items = ComponentLike<{
    Element: HTMLDivElement;
    Blocks: { default: [{ Item: Item }] };
  }>;

  export interface Menu {
    isOpen: boolean;
    Items: Items;
    Button: ComponentLike<{
      Element: HTMLButtonElement;
      Blocks: { default: [] };
    }>;
  }

  export default ComponentLike<{
    Blocks: { default: [Menu] };
  }>;
}

idk what the solution here is, but this feels undebuggable

NullVoxPopuli commented 9 months ago

I changed my args to my tiny components to be "any" and the issue went away.

Issue present

    menu: MenuTypes.Menu;
    trigger: ModifierLike;

Issue gone:

    menu: MenuTypes.Menu;
    trigger: any;

So.. that strongly looks like a bug in how the types for (component) interact with the ModifierLike type

NullVoxPopuli commented 8 months ago

Just ran in to this with a yielded component type, too

NullVoxPopuli commented 6 months ago

Another repro:

import Component from '@glimmer/component';
import { modifier } from 'ember-modifier';

import type { ModifierLike } from '@glint/template';

class Scroller extends Component<{ Element: HTMLDivElement; Blocks: { default: [scrollToBottom: ModifierLike<{ }>]}}> {
  declare withinElement: HTMLDivElement;
  ref = modifier(( el: HTMLDivElement ) => {
    this.withinElement = el;
  });

  <template>
    <div ...attributes {{this.ref}}>
      {{yield (modifier scrollToBottom this.withinElement)}}
    </div>
  </template>
}

Error:

Diagnostics:
1. glint: Type instantiation is excessively deep and possibly infinite. [2589]

I have another error because scrollToBottoms usage isn't correct (yet), but the use of a ModifierLike or any *Like should not cause infinite type evaluation, yeah? I also meant to not use ModifierLike in this situation, so ignore that.

boris-petrov commented 6 months ago

For some reason updating to 1.4.0 from 1.3.0 fixed the (only) case I have in my codebase of this issue. I'm not using template-imports (which is the only change according to the changelog) so I'm not sure how that might have had an effect... but that's that. :)

miguelcobain commented 6 months ago

I found this issue when simply trying to conditionally apply a modifier:

<button type="button" {{(if @onClick (modifier on "click" @onClick))}}>
  Click
</button>