Skip to content

keyof any incorrectly maps (only) to type string #21983

Description

TypeScript Version: 2.7.1 and 2.8.0-dev.20180215

Search Terms: "keyof any"

Code

function foo<T>(bar: T, baz: keyof T) {
        console.log(bar[baz]);
}

const sym = Symbol();
const quirk = { [sym]: "thing" };

foo<any>(quirk, sym);

Expected behavior:
keyof any should be equivalent to PropertyKey (that is string | number | symbol)

The example code should compile without error.

Actual behavior:
keyof any is equivalent to string

The example code does not compile and produces error:
error TS2345: Argument of type 'unique symbol' is not assignable to parameter of type 'string'.

Playground Link:
https://www.typescriptlang.org/play/#src=function%20foo%3CT%3E(bar%3A%20T%2C%20baz%3A%20keyof%20T)%20%7B%0D%0A%20%20%20%20%20%20%20%20console.log(bar%5Bbaz%5D)%3B%0D%0A%7D%0D%0A%0D%0Aconst%20sym%20%3D%20Symbol()%3B%0D%0Aconst%20quirk%20%3D%20%7B%20%5Bsym%5D%3A%20%22thing%22%20%7D%3B%0D%0A%0D%0Afoo%3Cany%3E(quirk%2C%20sym)%3B

Activity

  1. yortus commented on Feb 16, 2018

    @yortus
    Contributor

    If symbolof (#20721) was supported, you could write it like this:

    function foo<T>(bar: T, baz: keyof T | symbolof T) {
            console.log(bar[baz]);
    }
    
    const sym = Symbol();
    const quirk = { [sym]: "thing" };
    
    foo(quirk, sym);
  2. Jessidhia commented on Feb 16, 2018

    @Jessidhia

    (There are no number property keys. All non-symbol property keys are either strings or numeric strings.)

  3. Jessidhia commented on Feb 16, 2018

    @Jessidhia

    I just think it's not appropriate as a widened type for the result of keyof.

  4. yortus commented on Feb 16, 2018

    @yortus
    Contributor

    As for making keyof return symbols.... Object.keys only returns string keys at runtime, and many other JS constructs only operate over string keys. I think we may do symbolof operator to maintain compatibility and keep a distinction between the two namespaces.

    ogwh that was in Wesley Wigham (@weswigham)'s comment for why keyof should be just strings.

    As for keyof any, since this is not referring to the keys of any particular object, why not just use the PropertyKey type in your annotation instead?

  5. dinoboff commented on Feb 16, 2018

    @dinoboff

    I don't mind having keyOf and symbolOf for backward compatibility but can we also have anyKeyOf which would return both type of keys because I can't think of a use case where I would want to use symbolOf on its own.

  6. robbiespeed commented on Feb 16, 2018

    @robbiespeed

    Having keyof only refer to string keys can be useful, introducing symbolof seems to be the right way to go. My guess is most of the time people are going to need keyof more than symbolof anyway. If someone consistently needs both they can just create a type alias type SymbolKeyOf<T> = keyof T | symbolof T.

  7. robbiespeed commented on Feb 18, 2018

    @robbiespeed
    1. Symbols are usually used to store hidden values on an object, and are used far less than string keys.
    2. keyof as is can be useful when using Object.keys or Object.entries which do not return symbols as keys.

    keyof has already been declared as string keys, and is being used as such, why break it? It also makes sense seeing as Object.keys refers only to strings.

    Can you provide a use case where you would need to use keyof T | symbolof T many times throughout? Is there a instance where an alias wouldn't be enough?

  8. 1 remaining item

  9. yortus commented on Feb 20, 2018

    @yortus
    Contributor

    ogwh that could be fixed either way: either by (a) adding symbols to keyof, or by (b) adding symbolof and redefining Readonly<T> to use keyof T | symbolof T.

    So yes, that is a problem with the current definitions, but its not necessary an argument for one approach over the other.

  10. robbiespeed commented on Feb 20, 2018

    @robbiespeed

    ogwh Here's a simple example of how expanding keyof to include symbols could break existing code

    const sym = Symbol();
    
    function values <T, K extends keyof T>(obj: T) {
      return Object.keys(obj).map((key: K) => {
        return obj[key];
      });
    }
    
    const exampleObj = {
      a: 1,
      b: 2,
      [sym]: '',
    };
    
    const numbers: number[] = values(exampleObj);
    

    Currently works perfectly, but if symbols were included you'd get the resulting type error

    Type '(string | number)[]' is not assignable to type 'number[]'.
    

    When in reality that would not be the correct type to return.

    In response to some of your summary:

    1. I think I've shown an example that proves this is not something based merely on wording. For a long time there were no symbols in JS, hence why when talking about objects, a key generally refers to a string property (even if technically the spec defines it as either string or symbol).
    2. (+ 7, 8) I'm afraid I don't get what your point is here. Though you are correct that Object.keys does not directly correlate to keyof, in most cases though usage of keyof as the return type of Object.keys does work (and is useful).

    1. My example above shows your proposal would be a breaking change.
    2. symbolof would not break anything, and allow us to do everything we would need to

    One last note about expanding keyof
    If one were to try and separate keys into 2 types of symbol and string:

    interface O {
      a; b; c;
      [sA]; [sB];
    }
    type OStringKeys = keyof O & string;
    type OSymbolKeys = keyof O & symbol;
    

    it would result in this mess:

    type OStringKeys = ("a" & string) | ("b" & string) | ("c" & string) | (unique symbol & string) | (unique symbol & string)
    type OSymbolKeys = ("a" & symbol) | ("b" & symbol) | ("c" & symbol) | (unique symbol & symbol) | (unique symbol & symbol)
    

    if we left keyof alone and added symbolof, we'd get:

    type OStringKeys = keyof O = "a" | "b" | "c"
    type OSymbolKeys = symbolof O = unique symbol | unique symbol
    
  11. robbiespeed commented on Feb 20, 2018

    @robbiespeed

    ogwh it is producing an error because you've enabled strict function types, and is due to the fact that Object.keys does not return the type (keyof T)[], there was a proposal to have it do so, but that wouldn't be correct in all scenarios (discussion on that).
    Here's a updated version with strict function types enabled (Also tested locally with 2.7.2)

  12. typescript-bot commented on Mar 7, 2018

    @typescript-bot
    Contributor

    Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

  13. added
    FixedA PR has been merged for this issue
    and removed
    DuplicateAn existing issue was already created
    on Apr 23, 2018
  14. locked and limited conversation to collaborators on Jul 31, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixedA PR has been merged for this issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions