TypeScript: Enums, string/number types of interfaces/classes cannot be used as index signatures
Due to unresponsiveness and a highly unsatisfactory outcome on issue #1778, I want to raise a fresh issue so I can get a clear and final outcome. I don’t mind if it’s another no (actually I would because it makes zero sense), but if it’s a no, I would like something that wasn’t provided in the referenced issue: a CLEAR solution to this problem.
So let’s recap with some basics:
- There are two types of
enum:number, orstring. - A type can be a
stringornumber, or contain a property that is astringornumber - There are two types of supported index signatures:
stringandnumber
It naturally follows that index signatures should be able to be aliased, purely by observing the above facts alone.
But let’s step into a practical scenario.
It’s common to make the equivalent of a hashmap or lookup map, whether it be for the developer’s convenience, or for performance reasons. Either way, it’s almost always the case in my experience that the indexes of these hash maps correspond to a type expressed elsewhere in the codebase, and aren’t just an arbitrary/random index value.
e.g.
public templateElementHashMap: { [s: string]: TemplateElement } = {};
This shows an incomplete context, as the string type is always going to be the hash property in the class below:
export class TemplateElement {
public hash?: string;
}
Therefore, the truest expression would be this:
public templateElementHashMap: { [s: TemplateElement['hash']]: TemplateElement } = {};
This provides full context to the developer, quick travel to the type of interest, and as long as TemplateElement['hash'] is string or number, the above statement is 100% correct, and does not break any rules regarding type consistency. It’s a win/win, and clearly a large improvement.
Instead right now, I have many lines where the index signature is just a string property, and it’s become a pain to remember which hash map relates to which data I’m using it for. And quite often, I’m wasting time poking around trying to remember which index derives from which type.
So I’m going to ask again: what is the solution? To me there are only two:
- Make a comment after each one (zero type security, comments can be out of date, it’s ugly, why bother using TypeScript if I’m trying to compensate for its shortcomings with comments everywhere)
- Get on Github and start making noise about it
I’ve poured a fair amount of my time providing feedback and input for TypeScript since its early days, and only ask this is treated with the promptness and attention that I have received in the past, and not closed prematurely like the other issue.
So please can I receive a clear and concise workaround, or (preferably) the acknowledgement that something actually is inadequate with the design around this and I’m not just going insane, along with the many others who have expressed the same problem.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 92
- Comments: 23 (3 by maintainers)
We, the compiler team, get hundreds of comments every day on this repos issues. It’s often much easier to let one person respond when they are closer to the domain. We already have to pick our battles in these issues to actually get work done, and mix that with people being less available from COVID19 and it’s difficult to feel any progress on the language. For example, I used about 45m on just writing this tiny 3 paragraph reply.
I get that this issue is something close to your heart, but the team has commented on this issue multiple times with the same consistent opinion through the years in the original thread.
In my own OSS, I usually recommend that if someone is this passionate about a feature they should look at implementing it to try and better understand the constraints of the system. Maybe try give this a shot and have a better chance of coming at it from. language maintainers perspective too?
As a workaround I’ve been using a
Recordinstead of an object type. Hopefully this is helpful to someone else.Using @jamietre’s case as an example:
can be
Indeed, this issue was fixed by #44512 which is included in TS 4.4. Also, in TS 4.4 and later it is possible to have index signatures for tagged primitive types, for example:
@Vinnl This is the exact point I made here https://github.com/microsoft/TypeScript/issues/1778#issuecomment-456896553
The official response to which was
But that point has never been addressed and describes exactly what is wanted here. This addition would infer no safety beyond exactly what the language already does. The argument that it increases confusion does not hold water because this addresses a language inconsistency.
I’ve also been wondering who the others in the plural “we’ve” are and why they have not spoken.
This is marked as
Awaiting More Feedback(which is described as: “This means we’d like to hear from more people who would be helped by this feature”) but there’s 5 years of community feedback in #1778 already.We use interfaces derived from
Stringextensively to nominally type strings that should not be interchangeable. This is very convenient and within the API boundary of our software we never have to do type conversions and have complete safety for use of these string types… EXCEPT as object keys. For example:Now we can write functions that consume these types, instead of plain strings, and we get a compile error if we mix them up. It would be amazing if I could say
… but I cannot. The workarounds are to convert these types back and forth to strings whenver using indexes, or to use
Mapobjects instead. The latter is not really a great option, since we’d be doing a lot of conversion of objects to maps, and you just get a lot of other stuff for free with objects in TypeScript. So we just end up with clumsy, non-typesafe casts when using these interfaces as keys.Anything that could add support for using any
Stringderived interface as an index key would be amazing.(Hmm, I’m seeing a comment in my email that is not present here in GitHub… I’m responding to that.)
Although I’d like to see type aliases being allowed as index signatures, it’s obviously up to the TypeScript’s team discretion, so please don’t take this comment as demanding that the feature be added.
However, there’s something I don’t understand yet, that someone might be willing to clear up.
The argument against type aliases as index signatures, as I understand it, comes down to “it looks like we’re enforcing a type there, but we’re not - any string is valid, so allowing type aliases as index signatures would be misleading”. I get that point.
However, what I don’t get, is: is that not an argument against having type aliases in general?
For example:
Does the argument not hold there as well? We pretend that
someFunctiononly acceptsSomeType, but it will take any string and typecheck just fine?This started working in TS 4.4:
Digging deeper, the new index signature features are in the 4.4 release notes.
The
Recordsolution is a great way to declare such a mapping - it’s a great first step. unfortunately it leads to implicitanys when you actually look something up in the record - see #40892I appreciate that.
I don’t have the skill for the time to contribute to this in a competent way, I wish I did. But this feels like a repeat of what occurred last issue. And it’s a frustrating experience because I don’t necessarily want an improvement to TypeScript on my command done overnight, but a concise and clear answer that directly addresses concerns being raised and acknowledges there’s an actual problem here.
I’m guessing at this point that the ‘official’ stance is that I have to make comments. I’ve already been doing that and will live with it… thanks for your time, I do appreciate it.
In the spirit of the “Awaiting More Feedback” tag -
I came across this exact situation and was very confused / surprised to find I wasn’t able to use an alias to describe the key. I now have comments in the code base explaining what could otherwise be more accurately described by a type alias. More cognitive overload for the next guy.
I think Ryan’s point here (from https://github.com/microsoft/TypeScript/issues/1778#issuecomment-582559328):
is missing the key ingredient - that, for future maintainers and reviewers, it is in fact MORE confusing that we can’t alias the type, when the rest of the language allows us to use aliases for virtually everything and the alternative is an extra comment linking to these issues.
Instead we have to read comments and GitHub issue threads that basically boil down to “in this specific instance we don’t want to improve the typing of your objects because javascript is the underlying and you could hypothetically monkeypatch it elsewhere” (which could likely be detected in
tsc, no?).By way of example, which is more confusing to the next person to come across this code?
or
Furthermore downstream consumers don’t know that this object’s keys actually refer to something specific and are not intended to be arbitrary. That’s pretty confusing.
Same problem here, I wish I could index from branded types (which are very useful 😇) but I’m not sure how to do it properly. (Not sure the problem belongs to this issue).
Just jumping on the bandwagon in support of this feature. It would add a great deal of clarity.
@jamesgpearce
Thanks for pointing this out. This does solve the initial issue raised in #1778. It’s unfortunate it took this 6 year road and had the responses it got.
@zacksinclair exceptional rebuttal 🍺
Looking forward to this issue being resolved!
I do not understand the reasoning from #1778 that this would “create the implication that you’re making different kinds of index signature”. If the type aliasing was resolved and checked by the compiler, there would be no implications, it would either compile and one would be able to hover over it in an IDE to check the resolved type, or error and let one know that the type did not resolve to an allowed type.
Much thanks to @alecf for the temporary workaround for the need to add a comment explaining the type relation.
I’d be interested if @ahejlsberg has any input on the topic.