-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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 indexing with symbols #1863
Comments
That is part of the ES6 Symbol support we @JsonFreeman is working on. Your code sample should be supported in the next release. |
@wereHamster, with pull request #1978, this should become legal, and |
Will it be possible to specify the type of properties which are indexed by symbols? Something like the following: var theAnswer = Symbol('secret');
interface DeepThought {
[theAnswer]: number;
} |
Based on the comments in that PR, no: This does not cover symbol indexers, which allows an object to act as a map with arbitrary symbol keys. |
I think @wereHamster is talking about a stronger typing than @danquirk. There are 3 levels of support here. The most basic level is provided by my PR, but that is just for symbols that are properties of the global Symbol object, not user defined symbols. So, var theAnswer = Symbol('secret');
interface DeepThought {
[Symbol.toStringTag](): string; // Allowed
[theAnswer]: number; // not allowed
} The next level of support would be to allow a symbol indexer: var theAnswer = Symbol('secret');
interface DeepThought {
[s: symbol]: number;
}
var d: DeepThought;
d[theAnswer] = 42; // Typed as number This is on our radar, and can be implemented easily. The strongest level is what you're asking for, which is something like: var theAnswer = Symbol('secret');
var theQuestion = Symbol('secret');
interface DeepThought {
[theQuestion]: string;
[theAnswer]: number;
}
var d: DeepThought;
d[theQuesiton] = "why";
d[theAnswer] = 42; This would be really nice, but so far we have not come up with a sensible design for it. It ultimately seems to hinge on making the type depend on the runtime value of these symbols. We will continue to think about it, as it is clearly a useful thing to do. With my PR, you should at least be able to use a symbol to pull a value out of an object. It will be |
@wereHamster I did a little writeup #2012 that you may be interested in. |
I've merged request #1978, but I will leave this bug open, as it seems to ask for more than I provided with that change. However, with my change, the original error will go away. |
@wereHamster can you post an update of what more you'd like to see happen here? Wasn't immediately clear to me what we have implemented vs what you posted |
Any idea when |
We would take a PR for this. @JsonFreeman can provide details on some of the issues that you might run into. |
I actually think adding a symbol indexer would be pretty straightforward. It would work just like number and string, except that it wouldn't be compatible with either of them in assignability, type argument inference, etc. The main challenge is just making sure you remember to add logic in all the appropriate places. |
@RyanCavanaugh, it would be nice to eventually have the last example in #1863 (comment) typecheck. But if you prefer you can split this issue up into multiple smaller issues which build on top of each other. |
You're looking for Objet.assign(obj, { [theAnswer]: 42 });
|
For the love of God, please make this a priority. |
As pointed out by mellonis and MingweiSamuel, the workarounds using generic function are: var theAnswer: symbol = Symbol("secret");
var obj = {} as Record<symbol, number>;
obj[theAnswer] = 42; // Not allowed, but should be allowed
Object.assign(obj, { [theAnswer]: 42 }); // allowed
function get<T, K extends keyof T>(object: T, key: K): T[K] {
return object[key];
}
var value = obj[theAnswer]; // Not allowed, but should be allowed
var value = get(obj, theAnswer); // allowed |
Five years and Symbol as index still not allowed |
Found a work-around on this case, it not generic but work in some case: const SYMKEY = Symbol.for('my-key');
interface MyObject { // Original object interface
key: string
}
interface MyObjectExtended extends MyObject {
[SYMKEY]?: string
}
const myObj: MyObject = {
'key': 'value'
}
// myObj[SYMKEY] = '???' // Not allowed
function getValue(obj: MyObjectExtended, key: keyof MyObjectExtended): any {
return obj[key];
}
function setValue(obj: MyObjectExtended, key: keyof MyObjectExtended, value: any): void {
obj[key] = value
}
setValue(myObj, SYMKEY, 'Hello world');
console.log(getValue(myObj, SYMKEY)); |
@james4388 How is your example any different from the one from @beenotung? |
FYI: #26797 (Just found it - I'm not actually part of the TS team.) |
I agree that if this is safe:
then the analogous
is also safe |
My poor workaround for some cases : const bar: Record<any, string> = {};
const FOO = Symbol('foo');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const aFOO = FOO as any;
bar[aFOO] = 'sad'; |
@Gnucki It’s probably better to do, since the const bar: Record<any, string> = {};
const FOO = Symbol('foo');
bar[
// eslint-disable-next-line @typescript-eslint/no-explicit-any
FOO as any
] = 'sad'; which compiles to: const bar = {};
const FOO = Symbol('foo');
bar[FOO] = 'sad'; Whereas your code compiles to: const bar = {};
const FOO = Symbol('foo');
const aFoo = FOO as any;
bar[aFOO] = 'sad'; which causes the local DeclarativeEnvironmentRecord to have two |
Is there any explanation of this from typescript maintainers team? |
I'll reiterate my suggestion to make a TS language-level distinction between user-instantiated symbols and 'well-known' JS symbols. Quoting myself from earlier:
|
@riggs that would not fit a number of use cases; it's absolutely critical that everything for which |
Given that TypeScript is a superset of JS created for the explicit purpose of type safety, create two new types,
Symbol remains untouched, but maybe gets a flag warning about direct usage in a future version. Sentinels are, by definition, freed from the concerns relating to well-known symbols. Preserving this behavior at runtime does incur a small overhead, but would likely only occur when doing introspection, at which point extra care is likely warranted. I suspect most code doing runtime type checks for symbols are expecting to not be dealing with a |
Nit: TypeScript is not a superset of JS, nor it is a programming language; it specifies little in the way of behaviors. One can't "develop code in TS 4.2". One codes in eg. ES2015, with TS 4.2 specified type annotation syntax and ES2015 library types. Novice programmers can't learn to "code in TS". They learn to code in JS, with type annotations. No TS spec says anything about what TypeScript has no up to date specification, let alone standard, and Microsoft's own 2016 writeup hasn't been vetted by standards bodies and isn't nearly anything like the EcmaScript specification from which one can actually implement a complying and useful realization. This document claiming TS is "a superset of EcmaScript 2015" doesn't make it so. There has been a stated disinterest in a specification for the last 5 years. Everything is a tradeoff, and this can be a legit tradeoff, and something defined by its own implementation, rather than a spec, doesn't make it not a language, though the existence of an ES-like spec would help establish TS as a language. To quote from the 2016 document, "[besides some class and module notations] TypeScript also provides to JavaScript programmers a system of optional type annotations. These type annotations are like the JSDoc comments". It's an affordance for JavaScript programmers. TS is more of a type annotation overlay to assist code linting, which also happens to add minor shorthands to the JS syntax via transpilation, and in practice, remove significantly from JS capabilities too (because it makes certain patterns very hard or impossible to properly type annotate; eg. it allows extra properties). TS does not live independently of JS, it has no language spec. And sure, there's an associated toolchain with source code transformation (mostly, removes the type notations), linter, IDE plugins etc. So it's best to think of TS
|
We're working on an implementation, but the conversation here isn't useful nor is it contributing anything to the implementation. |
Implemented in #44512 which is now in the main branch. |
TypeScript now has a ES6 target mode which includes definitions
Symbol
. However when trying to index an object with a symbol, I get an error (An index expression argument must be of type 'string', 'number', or 'any').The text was updated successfully, but these errors were encountered: