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

Can't use alias of string as an index signature parameter type #7374

Closed
mullens opened this issue Mar 3, 2016 · 9 comments
Closed

Can't use alias of string as an index signature parameter type #7374

mullens opened this issue Mar 3, 2016 · 9 comments
Labels
Duplicate An existing issue was already created

Comments

@mullens
Copy link

mullens commented Mar 3, 2016

The compiler requires an index signature parameter to be "string" or "number" specifically, and doesn't allow type aliases.

type CustomId = string;

interface Works {
    [customId: string]: string;
}
interface DoesNotWork {
    [customId: CustomId]: string;
}
// An index signature parameter type must be 'string' or 'number'.

Shouldn't CustomId be allowed there?

@mhegazy
Copy link
Contributor

mhegazy commented Mar 3, 2016

Duplicate of #5683, and #5683

@mhegazy mhegazy added the Duplicate An existing issue was already created label Mar 3, 2016
@mhegazy
Copy link
Contributor

mhegazy commented Mar 3, 2016

meant to link to #2491 as well.

@steadicat
Copy link

@mhegazy both of those tasks you referenced as duplicates are actually quite different. #2491 is about enums (which are internally ints, not strings), and #5683 is about string literals (not aliases of string).

#2491 was fixed and apparently did not fix this issue. And #5683 was solved with mapped types, which also don't apply in this case.

Is there an actual issue which tracks this? Or can you link me to a thread that explains why this is a wontfix?

@mhegazy
Copy link
Contributor

mhegazy commented Feb 6, 2017

There are two issues here.

  1. using an alias to string or number, there is no issue tracking this. the rational is the compiler will do nothing special here, it is just a different name to string or number, and thus it is better for the user, and the maintainer to make it clear that there is not extra checks here, and any string would be allowed.

  2. using a subtype of string or number to index. the example here is an enum (subtype of number), but you cal also think of tagged strings (e.g. type myString = string & { __myTag: any } ). Again it is not clear what it means to have the compiler enforce this, since all objects are indexable using string.

For enums, our recommendation was to use mapped types, this was tracked by #2491, was fixed when keyof implementation was merged, then later on broken again by a change to keyof design. Fixing it again is tracked by #13042.

@estaub
Copy link

estaub commented Mar 14, 2017

@mhegazy I think your logic on aliases in point 1, above, is inconsistent with the rest of Typescript.

Aliases for string and number are commonly used, and it's well-understood that typechecking of them beyond typeof, in any context, is neither implemented nor possible. They are used as a powerful tool for communicating semantics between human beings - even between the writer and their future self. IMO, this context is no different from any other. Aliases' value (or lack of, as you feel) is the same as elsewhere, and thus they should be allowed, for consistency even if you don't share the opinion that they are valuable.

Please reconsider.

@RyanCavanaugh
Copy link
Member

There's already a place to put a name to communicate semantics:
[whateverYouWant: string]: T

But type aliases -- You can alias string only because you can alias any type. Very few type aliases are of the form type T = U where U is a bare identifier - that is not their primary usage.

And arguably it's strictly worse to allow aliases here, because if you see the type

[k: CoolTypeAlias]: T

this type has very different behavior depending on whether CoolTypeAlias points to string or number. You usually need to know which it is in order to use the type effectively.

@estaub
Copy link

estaub commented Mar 14, 2017

@RyanCavanaugh

I'm strongly getting that y'all aren't going to do anything with this - but I thought I should push back regardless, in large part because of what the arguments imply for the future of the language.

Our mileage differs. Unchecked typing of simple values is a common language feature, from C/C++ to Scala. I use type aliases wherever there's a domain type that I need to express. For example, in my domain most interfaces include a string ID; I type these with SomeInterfaceNameId. Even worse, some interfaces have multiple ID types; I type them all to avoid confusion. Sure, I can and do use name suffixes, but It's a lot of baggage to carry a type declaration around everywhere the name is used.

If I were working with physical numeric units, I'd definitely type those, having learned from all the wrong-unit megafails of the past (e.g. the Mars Orbiter).
type FootPound = number

whateverYouWant is the same argument for not using Typescript at all. Let's all use Hungarian notation! For me, language clarity and expressiveness is far more important than ensuring that every usage can be validated; many usages can't. I'm more concerned about me or someone else not being confused in a month or a year. Typescript isn't just some kind of hopped-up linter - it's a language, whose added-value above Javascript lies as much in facilitating communication between humans as in facilitating communication with the machine.

Typing of simple values makes searching for places where a given domain type is used much easier. It would be nice if the compiler could check for correctness, but typing of numbers and strings by usage is of high value even without checking.

As for not understanding whether a typed value a number or string, that's far less frequent and usually more easily detected problem than confusing the wrong domain-type, and addressable by simply clicking through to the type definition. In most cases, getting number/string wrong wouldn't even get past the compiler.

@RyanCavanaugh
Copy link
Member

Your argument for (e.g. units of measure are important to keep right) is essentially our argument against -- allowing type aliases as key types would create the appearance that there was nominal checking of the indexing type. In other words, it would look like you could write type PersonName = string, type AnimalName = string and then get type safety indexing into a PersonMap or an AnimalMap.

On the one hand you're saying "All I want is to name this key type, checking is not required", but on the other hand you're rejecting the mechanism (key names) that already does this. The phrase "unchecked typing" is sort of an oxymoron - either it's typed and some checking occurs, or it isn't (and may as well be a code comment).

If you really want to name your indexing things for the purposes of finding them, you can write:

interface KeyTypeAlpha<T> {
  [k: string]: T;
}
interface KeyTypeBeta<T> {
  [k: string]: T;
}

// Usage
interface One extends KeyTypeBeta<number> { }
interface Two extends KeyTypeAlpha<number> { }

@alexfoxgill
Copy link

Your argument for (e.g. units of measure are important to keep right) is essentially our argument against -- allowing type aliases as key types would create the appearance that there was nominal checking of the indexing type. In other words, it would look like you could write type PersonName = string, type AnimalName = string and then get type safety indexing into a PersonMap or an AnimalMap.

I don't think people would expect that behaviour. TS is structurally typed, right? You don't get that kind of typechecking with a function call so what's the difference with a key type?

type AnimalId = string
type PersonId = string

const foo = (x: AnimalId) => x.slice(0, 1);

const bar: PersonId = "bob";
foo(bar);

@microsoft microsoft locked and limited conversation to collaborators Jul 18, 2018
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
Projects
None yet
Development

No branches or pull requests

7 participants