Skip to content

allow string literals (unions of string literals?) as a type of an indexer #7656

Description

type Keyed<a> = { [key: 'this' | 'that']: a; } // expected to work, actual doesn't work

Activity

  1. malibuzios commented on Mar 23, 2016

    @malibuzios

    I also encountered this and considered opening an issue, though having a string literal union as a key type would have very similar semantics to:

    type Keyed<a> = { this?: a; that?: a }

    Which would appear a bit more verbose and require more typing though.

    However, the indexer approach does have the advantage that it allows aliasing the key type (the string literal union) separately, e.g:

    type RequestMethod = "GET" | "SET" | "PUT" | "UPDATE" | "DELETE";
    type HandlerObject = { [key: RequestMethod]: (requestObject: RequestObject) => boolean; };

    I think that based on the usefulness of this particular pattern it should definitely be considered to become valid syntax.

    Also, the same pattern may also prove useful when the numeric literal type is introduced:

    type ValidNumericKey = 1 | 2 | 4 | 8 | 16;
    type ValueObject = { [key: ValidNumericKey]: SomeObject };
  2. zpdDG4gta8XKpMCd commented on Mar 23, 2016

    @zpdDG4gta8XKpMCd
    Author

    it's not an equivalent, my Keyed can take from 0 through 1 to 2 values,
    whereas yours always needs exactly 2

    completely different semantics
    On Mar 23, 2016 12:47 PM, "malibuzios" notifications@github.com wrote:

    I also encountered this and considered opening an issue, though having a
    string literal union as a key type would have very similar semantics to:

    type Keyed = { 'this': a; 'that': a }

    Which would appear a bit more verbose and require more typing though.

    However, the indexer approach does have the advantage that it allows
    aliasing the key type (the string literal union) separately, e.g:

    type RequestMethod = "GET" | "SET" | "PUT" | "UPDATE" | "DELETE";type handlers = { [key: RequestMethod]: (requestObject: RequestObject) => boolean; };

    I think that based on the usefulness of this particular pattern it should
    definitely be considered to become valid syntax.

    Also, the same pattern may also prove useful when the numeric literal type
    #7480 is introduced:

    type ValidNumericKey = 1 | 2 | 4 | 8 | 16;type values = { [key: ValidNumericKey]: SomeObject };

    —
    You are receiving this because you authored the thread.
    Reply to this email directly or view it on GitHub
    #7656 (comment)

  3. malibuzios commented on Mar 23, 2016

    @malibuzios

    I was just about to fix it when you replied. I changed it to make all properties optional, though it may still not have the same exact semantics, but the difference may be subtle. Can you think of any other differences?

  4. malibuzios commented on Mar 23, 2016

    @malibuzios

    Well there's one difference (that's isn't really about semantics) that the interface style notation, based on current behavior, would allow using the dot (.) operator, but the indexer wouldn't:

    type Keyed1<a> = { this?: a; that?: a }
    type Keyed2<a> = { [key: 'this' | 'that']: a; }
    
    let x: Keyed1<number> = {};
    let y: Keyed2<number> = {};
    
    x['that'] = 34; // OK
    x.that = 34; // OK
    
    y['that'] = 34 // OK
    y.that = 34 // Error

    However that behavior could be changed in case the key is a string literal, I guess, perhaps, maybe?

  5. RyanCavanaugh commented on Mar 23, 2016

    @RyanCavanaugh
    Member

    Can you explain what you expect this to do?

    Is it equivalent to this?

    type Keyed<a> = { 'this': a } | { 'that': a };
  6. malibuzios commented on Mar 23, 2016

    @malibuzios

    Ryan Cavanaugh (@RyanCavanaugh)

    I believe (though of course I may be subtly wrong) the idea is that the string literal union constrains what keys can be used to a finite set, in this case {"this", "that"}. For simplicity let's assume a is a number

    type Keyed = { [key: 'this' | 'that']: number; }

    I believe the possible values for let x: Keyed are:

    x = {};
    // or
    x = { 'this': ...some number... }
    // or
    x = { 'that': ...some number... }
    //or 
    x = { 'this': ...some number...,  'that': ...some number... }
  7. RyanCavanaugh commented on Mar 23, 2016

    @RyanCavanaugh
    Member

    So { 'this': 4, 'something': 6 } would be invalid?

  8. malibuzios commented on Mar 23, 2016

    @malibuzios

    Ryan Cavanaugh (@RyanCavanaugh)

    Based on my understanding that would seem like the most natural interpretation of this combination of the indexer syntax and the concept of string literal union applied to the key.

    It also seems like this may mean that unlike the semantics of an "normal" interface this would be a strict type, i.e. not assignable from a wider type (having additional properties)?

  9. malibuzios commented on Mar 23, 2016

    @malibuzios

    Ryan Cavanaugh (@RyanCavanaugh)

    Or the fact it's still enclosed within the "brackets" {}, representing an interface, means it's still extensible?

  10. zpdDG4gta8XKpMCd commented on Mar 23, 2016

    @zpdDG4gta8XKpMCd
    Author

    why? to limit the possible set of keys, prevent unexpected keys from
    possibly being used
    On Mar 23, 2016 1:23 PM, "Ryan Cavanaugh" notifications@github.com wrote:

    Can you explain what you expect this to do?

    Is it equivalent to this?

    type Keyed = { 'this': a } | { 'that': a };

    —
    You are receiving this because you authored the thread.
    Reply to this email directly or view it on GitHub
    #7656 (comment)

  11. malibuzios commented on Mar 23, 2016

    @malibuzios

    Ryan Cavanaugh (@RyanCavanaugh)

    I'm sort of rethinking this. Perhaps we need to separate the two different aspects of semantics here:

    1. What can be indexed into this - which based on the natural interpretation of literal types I believe should only include the strings "this", "that" and no more.
    2. What can be assigned to this - Is this a "strict" interface that only takes the 4 variations I listed, or does this behave like an all optional interface? which very unfortunately today is even assignable from anything - personally I feel this is one of those anomalies where I'm amazed still happens and nothing has really been done about (though to be fair it seems like a pull request was open at some point).
  12. malibuzios commented on Mar 23, 2016

    @malibuzios

    Aleksey-Bykov

    I think we have a bigger problem here: it seems like indexer key types are not even checked for conventional types like string or number (there may be an open issue for this but I haven't searched yet):

    These two examples don't induce any errors whatsoever (tested on playground and 1.8.7):

    type A = {[key: number]: any};
    
    let a: A = {};
    a["abcd"] = 1; // No error?
    let x =  a["abcd"]; // No error?
    type B = {[key: string]: any};
    
    let b: B = {};
    b[123] = 1; // No error?
    let y = b[123]; // No error?
  13. malibuzios commented on Mar 23, 2016

    @malibuzios

    This issue is a duplicate. This conversation needs to be somehow merged with #5683 which seems to be the main tracking point this subject (there has been several duplicates so far).

    I opened a different issue (#7660) for the indexer key type checking.

  14. malibuzios commented on Mar 24, 2016

    @malibuzios

    Aleksey-Bykov Ryan Cavanaugh (@RyanCavanaugh)

    This comment in #7660 is a direct continuation of the discussion here, but applied to the more general issue of the how strictly should index signature keys be type-checked.

  15. locked and limited conversation to collaborators on Jun 19, 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

    DuplicateAn existing issue was already createdNeeds More InfoThe issue still hasn't been fully clarifiedSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions