Skip to content

Problem with generics in Signatures #610

@gossi

Description

@gossi

I'm providing glint support for ember-element-helper tildeio/ember-element-helper#107

Here are the types based on the ones from Dan (in tildeio/ember-element-helper#102):

export type ElementFromTagName<T extends string> = T extends keyof HTMLElementTagNameMap
  ? HTMLElementTagNameMap[T]
  : Element;

type Positional<T extends string> = [name: T];
type Return<T extends string> = typeof EmberComponent<{
  Element: ElementFromTagName<T>;
  Blocks: { default: [] };
}>;

export interface ElementSignature<T extends string> {
  Args: {
    Positional: Positional<T>;
  };
  Return: Return<T> | undefined;
}

export default class ElementHelper<T extends string> extends Helper<ElementSignature<T>> {}

Using the helper works straight away:

<template>
  {{#let (element 'div') as |Tag|}}
    <Tag id="lalala" ...attributes>Hello there!</Tag>
  {{/let}}
</template>

Going a bit more dynamic and allowing a @tag to be passed in, works when the type is made explicit:

import { type ElementSignature } from 'ember-element-helper';

interface ElementReceiverSignature{
  Element: HTMLDivElement; // 1: explicit element here
  Args: {
    tag: ElementSignature<'div'>['Return']; // 2: explicit tag name here
  };
  Blocks: {
    default: [];
  };
}

export default class ElementReceiver extends Component<ElementReceiverSignature> {
  <template>
    {{#let @tag as |Tag|}}
      <Tag id="content" ...attributes>{{yield}}</Tag>
    {{/let}}
  </template>
}

... of course those explicit types do not make sense, when we want to have any element being passed in. Making it generic makes the problem visible:

import {
  type ElementFromTagName,
  type ElementSignature
} from 'ember-element-helper';

interface ElementReceiverSignature<T extends string> {
  Element: ElementFromTagName<T>;
  Args: {
    tag: ElementSignature<T>['Return'];
  };
  Blocks: {
    default: [];
  };
}

export default class ElementReceiver<T extends string> extends Component<
  ElementReceiverSignature<T>
> {
  <template>
    {{#let @tag as |Tag|}}
      <Tag id="content" ...attributes>{{yield}}</Tag>
    {{/let}}
  </template>
}

This reveals two locations where glint throws both times the same error:

  1. <Tag and
  2. ...attributes

Wit the error message being:

Argument of type 'NonNullable<ElementFromTagName<T>> extends never ? unknown :
ElementFromTagName<T>' is not assignable to parameter of type 'Element'.

  Type 'unknown' is not assignable to type 'Element'.glint(2345)

As to my understanding, the types for the signature are correct, but the error message is wrong. The ElementFromTagName will always return a valid type, in either explicit HTMLElementTagNameMap[T] or generic Element which should be accurate inside the component (typing the unknownigly character of @tag).

Is this a valid problem with glint? Or are my typings wrong?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions