question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

keyof becoming union of string literal in emitted type definitions

See original GitHub issue

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

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:12
  • Comments:13 (5 by maintainers)

github_iconTop GitHub Comments

3reactions
dragomirtitiancommented, Sep 19, 2019

@epeli Don’t know if it helps, but as mentioned in other issues #33117 and #17294 this issue does not impact function and class declarations, only function and class expressions. Maybe you could structure your code to use function/class declarations? It’s hard to propose a more concrete solution without actual code.

@justinfagnani The code in the original ticket also works as expected with a function declaration, not sure you came across the workaround:

export type Constructor<T> = new()=> T

export function customElement(tagName: keyof HTMLElementTagNameMap){
    return (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;
    }
};

Outputs in d.ts file:

export declare type Constructor<T> = new () => T;
export declare function customElement(tagName: keyof HTMLElementTagNameMap): (clazz: Constructor<HTMLElement>) => any;
1reaction
apexskiercommented, Oct 16, 2020

I’m seeing this issue as well in a function that accesses keys of a type from a peer dependency.

import { SomethingTypes } from "peer-dependency";

export default {
    foo<S extends keyof SomethingTypes>(bar: MyType<S>) { }
}

becomes

    foo<S extends "key1" | "key2" | "key3"...

This is an issue because it means my package needs to be updated in tandem with the peer dependency.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use keyof to extract a string literal union of only keys that have ...
I usually define KeysMatching like this type ... type StringKeys = KeysMatching<SomeType, string>; // type StringKeys = "aString" const v2: ...
Read more >
Derive union of string literal types with lookup types in Typescript
With the help of lookup types the union of property names/string literals is derived using keyof Post . The set of keys in...
Read more >
TypeScript | The Guide You Need to Learn the Omit Type
Omitting multiple keys. To omit multiple keys from an object, pass a union of string literals in K . In the next example,...
Read more >
Taming Strings with Template Literal Types - SitePen
How to use template literal types in TypeScript to enhance the ... a union type to ensure that the Key is both a...
Read more >
typescript-cheatsheet - GitHub Pages
This is where union types come in where we can assign two or more types (e.g. assign number and string ) to a...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found