You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Consider adding symbolof type operator, like keyof but for unique symbol properties #20721
EDIT: The example code below mostly does work as per #20721 (comment). The only part remaining unsolved is that there is no equivelent of keyof for unique symbols. The suggestion is to add a symbolof type operator.
TypeScript Version: 2.7.0-dev.20171215
Thanks to #15473, unique symbols and string consts can be used as keys in types. But they don't work with indexed access types or index type queries (#11929), as demonstated below.
Code
constSYM=Symbol('A Symbol');constSTR='A String';interfaceFoo{'lit': string;[STR]: boolean;[SYM]: number;}declareletfoo: Foo;letv1=foo['lit'];// v1 is stringletv2=foo[STR];// v2 is booleanletv3=foo[SYM];// v3 is number// indexed access typestypeT1=Foo['lit'];// T1 = stringtypeT2=Foo[STR];// ERROR: Cannot find name 'STR'typeT3=Foo[SYM];// ERROR: Cannot find name 'SYM'// index type querytypeK=keyofFoo;// K = 'A string' | 'lit' (but no SYM)
Expected behavior:
No errors. T2 is boolean and T3 is number. keyof Foo is 'A string' | 'lit' | SYM
Actual behavior:
Errors for T2 and T3, and keyof Foo doesn't include SYM.
I know there are several overlapping features in play here, but would like to know if all of the actual behaviour above is by design. E.g., maybe not having symbols show up in keyof queries is by design, but what about the fact that we can't query types with Foo[STR] or Foo[SYM], even though the compiler knows the types and the syntax is consistent with Foo['lit']?
Troy Gerwien (@yortus) Indexes work, you're just accessing the type of a const incorrectly:
constSYM=Symbol('A Symbol');constSTR='A String';interfaceFoo{'lit': string;[STR]: boolean;[SYM]: number;}declareletfoo: Foo;letv1=foo['lit'];// v1 is stringletv2=foo[STR];// v2 is booleanletv3=foo[SYM];// v3 is number// indexed access typestypeT1=Foo['lit'];// T1 = stringtypeT2=Foo[typeofSTR];// T2 = boolean (note `typeof`)typeT3=Foo[typeofSYM];// T3 = number (note `typeof`)
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.
Reacted by Nathan Shively-Sanders, Robbie Speed and Aluan Haddad
changed the title [-]Can't use unique symbols or const strings in indexed access types or index type queries[/-][+]Consider adding `symbolof` type operator, like `keyof` but for unique symbol properties[/+]on Feb 15, 2018
Here's an example of where a symbolof operator could be used.
const sym = Symbol();
const obj = { num: 0, str: 's', [sym]: sym };
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K] {
return obj[key] = value;
}
const val = set(obj, 'str', '');
// string
const valB = set(obj, 'num', '');
// Expect type error
// Argument of type '""' is not assignable to parameter of type 'number'.
const valC = set(obj, sym, sym);
// Unexpected type error
// Argument of type 'unique symbol' is not assignable to parameter of type '"str" | "num"'.
If we had a symbolof we could do:
function set <T extends object, K extends keyof T | symbolof T> (obj: T, key: K, value: T[K]): T[K] {
return obj[key] = value;
}
const val = set(obj, 'str', '');
// string
const valB = set(obj, 'num', '');
// Expect type error
// Argument of type '""' is not assignable to parameter of type 'number'.
const valC = set(obj, sym, sym);
// symbol
Using overloads we can work around the issue somewhat, but we still have no type checking of symbol properties.
function set (obj: object, key: symbol, value: any): any
function set <T extends object, K extends keyof T> (obj: T, key: K, value: T[K]): T[K];
function set (obj, key, value) {
return obj[key] = value;
}
const val = set(obj, 'str', '');
// string
const valB = set(obj, 'num', '');
// Expect type error
// Argument of type '""' is not assignable to parameter of type 'number'.
const valC = set(obj, sym, sym);
// any
Wesley Wigham (@weswigham) It looks like you ended up changing keyof to return unique symbols in #22339? I tested before and after that PR and that's what changed the behavior. Wanted to point it out since I'm guessing it wasn't intentional based on your comments earlier in this issue and the fact that there is no mention of it in the PR.
You shouldn't rely on keyof returning numbers and unique symbols - it's definitely a bug because a keyof T is a subtype of string, and changing that would likely break a lot. We're going to look into fixing keyof's behavior (meaning it should omit symbols and map numbers to their string representation) while also adding a new operator that can correctly retrieve the declared keys of an object (we're toying with propkeyof T, but are open to suggestions).
Forgive me for being blunt, but I don't see how you can say changing it would likely break a lot when you already did change it to no longer always only be a subtype of string and it didn't break enough for anyone to even notice. Any (edit: Some) code that for some reason actually requires keyof to always be a string is already breaking in 2.8:
constsym=Symbol()constobj={num: 0,str: 's',[sym]: true}declareconstobjKeys: keyoftypeofobjconststrObjKeys: string=objKeys// error unique symbol is not assignable to string in 2.8
So whatever (Edit: some) damage there would be is already being done, yet as far as I can tell there have been zero issues reported against it almost a month after it was merged and a week after 2.8 was released. That makes it hard for me to believe it's really that problematic of a breaking change.
If you do have to change it back though and add another operator instead, I'd prefer propof over propkeyof so that it at least has a fighting chance of being used regularly instead of the shorter and already common keyof.
Edit: Clarified that I was wrong when I thought any potential breaking changes would've already been happening in 2.8.
Forgive me for being blunt, but I don't see how you can say changing it would likely break a lot when you already did change it to no longer always only be a subtype of string and it didn't break enough for anyone to even notice
still works (not may people have reason to call keyof on concrete types, methinks). If keyof can be a symbol or number, it should not, and that inconsistency is what's really a bug.
Fair enough I wasn't thinking of that. I still question how common that use case(trying to assign keyof T to a string for some reason) really is. Anywhere I'm declaring something to be a keyof T I'm doing it because I want to be able to do an indexed access with it and for that use case a symbol works just as well as a string.
If a propkeyof or propof operator is added how common will it be to still want plain keyof? The most basic use case for keyof is something like:
Which would be much better typed with K extends propkeyof T because symbols will work with that code just as well as strings.
I think that we'd end up in an unfortunate situation where accurate types should normally be using propkeyof but it'll end up being far more common for people to just use keyof because of inertia and being slightly shorter to type.
Not the end of the world but it'd be nice to avoid. Especially since the workaround for the (still hypothetical to me) cases where it'd be a breaking change seems to be as simple as:
I think that we'd end up in an unfortunate situation where accurate types should normally be using propkeyof but it'll end up being far more common for people to just use keyof because of inertia and being slightly shorter to type.
I was worried about keyof including symbols, because narrowing with & string returns some really awful types. (typeof sym & string) | ("a" & string) | ("b" & string) in the simple instance of only three keys.
However with the introduction of condition types and Extract you can easily write Extract<keyof T, string> to get a return type of only the string keys.
Activity
dinoboff commentedon Dec 17, 2017
I hope
keyofwill support symbols. Symbol keys are not enumerable but they are not hidden; you should be able to test a symbol is a key of an object.weswigham commentedon Jan 10, 2018
Troy Gerwien (@yortus) Indexes work, you're just accessing the type of a
constincorrectly:As for making
keyofreturn symbols....Object.keysonly returns string keys at runtime, and many other JS constructs only operate over string keys. I think we may dosymbolofoperator to maintain compatibility and keep a distinction between the two namespaces.yortus commentedon Jan 10, 2018
Thanks Wesley Wigham (@weswigham). So unique symbol values (and const strings) do not create same-named unit types. And this is by design as explained by Mohamed Hegazy (@mhegazy) in #20898 (comment).
I think
symbolofwould be a good addition if it means unique symbol keys can play a part in mapped types and other type inference scenarios.[-]Can't use unique symbols or const strings in indexed access types or index type queries[/-][+]Consider adding `symbolof` type operator, like `keyof` but for unique symbol properties[/+]yortus commentedon Feb 15, 2018
I updated the title and description to more clearly reflect what is being suggested here.
robbiespeed commentedon Feb 15, 2018
Here's an example of where a
symbolofoperator could be used.If we had a
symbolofwe could do:Using overloads we can work around the issue somewhat, but we still have no type checking of symbol properties.
5 remaining items
yortus commentedon Mar 30, 2018
That is weird. I wonder if this change to
keyofbehaviour was intentional.The change doesn't seem in line with Wesley Wigham (@weswigham)'s comment above, and as you point out breaking changes would normally be documented.
kpdonn commentedon Mar 30, 2018
Wesley Wigham (@weswigham) It looks like you ended up changing
keyofto return unique symbols in #22339? I tested before and after that PR and that's what changed the behavior. Wanted to point it out since I'm guessing it wasn't intentional based on your comments earlier in this issue and the fact that there is no mention of it in the PR.weswigham commentedon Apr 2, 2018
You shouldn't rely on
keyofreturningnumbers andunique symbols - it's definitely a bug because akeyof Tis a subtype ofstring, and changing that would likely break a lot. We're going to look into fixingkeyof's behavior (meaning it should omit symbols and map numbers to their string representation) while also adding a new operator that can correctly retrieve the declared keys of an object (we're toying withpropkeyof T, but are open to suggestions).kpdonn commentedon Apr 3, 2018
Forgive me for being blunt, but I don't see how you can say changing it would likely break a lot when you already did change it to no longer always only be a subtype of string and it didn't break enough for anyone to even notice.
Any(edit: Some) code that for some reason actually requireskeyofto always be astringis already breaking in 2.8:Playground link
So
whatever(Edit: some) damage there would be is already being done, yet as far as I can tell there have been zero issues reported against it almost a month after it was merged and a week after 2.8 was released. That makes it hard for me to believe it's really that problematic of a breaking change.If you do have to change it back though and add another operator instead, I'd prefer
propofoverpropkeyofso that it at least has a fighting chance of being used regularly instead of the shorter and already commonkeyof.Edit: Clarified that I was wrong when I thought any potential breaking changes would've already been happening in 2.8.
weswigham commentedon Apr 3, 2018
That's because
still works (not may people have reason to call
keyofon concrete types, methinks). Ifkeyofcan be a symbol or number, it should not, and that inconsistency is what's really a bug.kpdonn commentedon Apr 3, 2018
Fair enough I wasn't thinking of that. I still question how common that use case(trying to assign
keyof Tto astringfor some reason) really is. Anywhere I'm declaring something to be akeyof TI'm doing it because I want to be able to do an indexed access with it and for that use case a symbol works just as well as a string.If a
propkeyoforpropofoperator is added how common will it be to still want plainkeyof? The most basic use case forkeyofis something like:Which would be much better typed with
K extends propkeyof Tbecause symbols will work with that code just as well as strings.I think that we'd end up in an unfortunate situation where accurate types should normally be using
propkeyofbut it'll end up being far more common for people to just usekeyofbecause of inertia and being slightly shorter to type.Not the end of the world but it'd be nice to avoid. Especially since the workaround for the (still hypothetical to me) cases where it'd be a breaking change seems to be as simple as:
yortus commentedon Apr 3, 2018
Kevin Donnelly (@kpdonn) I think you make a valid point there. There's already a precedent for that happening with
any.I like your suggestion to have a single general operator (
keyof), and narrow it with& stringif you want just the string keys.ahejlsberg commentedon Apr 20, 2018
With #23592
keyofsupports numeric literals and unique symbols.robbiespeed commentedon Apr 21, 2018
I was worried about
keyofincluding symbols, because narrowing with& stringreturns some really awful types.(typeof sym & string) | ("a" & string) | ("b" & string)in the simple instance of only three keys.However with the introduction of condition types and
Extractyou can easily writeExtract<keyof T, string>to get a return type of only the string keys.