Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
zpdDG4gta8XKpMCd opened this issue Mar 23, 2016 · 14 comments
Labels
Duplicate An existing issue was already created Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript

Comments

@zpdDG4gta8XKpMCd
Copy link

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

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 };

@zpdDG4gta8XKpMCd
Copy link
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" [email protected] 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)

@malibuzios
Copy link

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?

@malibuzios
Copy link

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?

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Needs More Info The issue still hasn't been fully clarified labels Mar 23, 2016
@RyanCavanaugh
Copy link
Member

Can you explain what you expect this to do?

Is it equivalent to this?

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

@malibuzios
Copy link

@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... }

@RyanCavanaugh
Copy link
Member

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

@malibuzios
Copy link

@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)?

@malibuzios
Copy link

@RyanCavanaugh

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

@zpdDG4gta8XKpMCd
Copy link
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" [email protected] 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)

@malibuzios
Copy link

@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).

@malibuzios
Copy link

@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?

@malibuzios
Copy link

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.

@malibuzios
Copy link

@Aleksey-Bykov @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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants