microsoft / microsoft/TypeScript

keyof becoming union of string literal in emitted type definitions

Open
#27,171 13 comments 14 reactions 1 assignee View on GitHub

@weswigham is already working on this.

Since Sep 18, 2018.

In Discussion Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

TypeScript Version: 3.0.1

Search Terms: declaration emit keyof as union type

Code

We're seeing this problem in LitElement: https://github.com/Polymer/lit-element/blob/master/src/lib/decorators.ts#L43

export const customElement = (tagName: keyof HTMLElementTagNameMap) =>
    (clazz: Constructor<HTMLElement>) => {
      window.customElements.define(tagName, clazz);
      // Cast as any because TS doesn't recognize the return type as being a
      // subtype of the decorated class when clazz is typed as
      // `Constructor<HTMLElement>` for some reason. `Constructor<HTMLElement>`
      // is helpful to make sure the decorator is applied to elements however.
      return clazz as any;
    };

We do this so that users are forced to add their elements to the HTMLElementTagNameMap:

@customElement('my-element')
class MyElement extends HTMLElement {}

declare global {
  interface HTMLElementTagNameMap {
    'my-element': MyElement;
  }
}

Expected behavior:

The declaration for customElement is emitted as:

export declare const customElement: (tagName: keyof HTMLElementTagNameMap) => (clazz: Constructor<HTMLElement>) => any;

And the example user code has no errors.

Actual behavior:

This is the declaration emit for customElement (see https://unpkg.com/@polymer/lit-element@0.6.1/lib/decorators.d.ts):

export declare const customElement: (tagName: "object" | "a" | "abbr" | "acronym" | "address" | "applet" | "area" | "article" | "aside" | "audio" | "b" | "base" | "basefont" | "bdo" | "big" | "blockquote" | "body" | "br" | "button" | "canvas" | "caption" | "center" | "cite" | "code" | "col" | "colgroup" | "data" | "datalist" | "dd" | "del" | "dfn" | "dir" | "div" | "dl" | "dt" | "em" | "embed" | "fieldset" | "figcaption" | "figure" | "font" | "footer" | "form" | "frame" | "frameset" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "head" | "header" | "hgroup" | "hr" | "html" | "i" | "iframe" | "img" | "input" | "ins" | "isindex" | "kbd" | "keygen" | "label" | "legend" | "li" | "link" | "listing" | "map" | "mark" | "marquee" | "menu" | "meta" | "meter" | "nav" | "nextid" | "nobr" | "noframes" | "noscript" | "ol" | "optgroup" | "option" | "output" | "p" | "param" | "picture" | "plaintext" | "pre" | "progress" | "q" | "rt" | "ruby" | "s" | "samp" | "script" | "section" | "select" | "slot" | "small" | "source" | "span" | "strike" | "strong" | "style" | "sub" | "sup" | "table" | "tbody" | "td" | "template" | "textarea" | "tfoot" | "th" | "thead" | "time" | "title" | "tr" | "track" | "tt" | "u" | "ul" | "var" | "video" | "wbr" | "xmp") => (clazz: Constructor<HTMLElement>) => any;

And the above user code has an error, because extending HTMLElementTagNameMap has no effect.

Playground Link: The Playground doesn't show declaration files.

Related Issues: This is exactly the issue, but it was closed and locked: https://github.com/Microsoft/TypeScript/issues/21445

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.