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

Type 'symbol' cannot be used as an index type. #24587

Closed
praihan opened this issue Jun 1, 2018 · 16 comments
Closed

Type 'symbol' cannot be used as an index type. #24587

praihan opened this issue Jun 1, 2018 · 16 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@praihan
Copy link

praihan commented Jun 1, 2018

TypeScript Version: 3.0.0-dev.20180601

Code

const x: Record<keyof any, string> = {};
x['hello'] = 'world';
const sym: symbol = Symbol('sym');
x[sym] = 'symbol string';

Expected behavior:
No errors. tsc --target es6 symbol-index.ts works with 2.8.4.

Actual behavior:
symbol-index.ts(4,3): error TS2538: Type 'symbol' cannot be used as an index type.
when running tsc --pretty false --target es6 symbol-index.ts with 2.9.1 or next.

Playground Link: http://www.typescriptlang.org/play/#src=const%20x%3A%20Record%3Ckeyof%20any%2C%20string%3E%20%3D%20%7B%7D%3B%0D%0Ax%5B'hello'%5D%20%3D%20'world'%3B%0D%0Aconst%20sym%3A%20symbol%20%3D%20Symbol('sym')%3B%0D%0Ax%5Bsym%5D%20%3D%20'symbol%20string'%

@weswigham
Copy link
Member

@prshreshtha just write

const sym = Symbol('sym');

or if you must type annotate, write

const sym: unique symbol = Symbol('sym');

we forbid symbols as indexers because we have no symbol index signatures (this was a hole we patched in 2.9 - previously it would assume the string indexer type, which was very incorrect), however unique symbols - a type associated with exactly one symbol are fine, since they refer to exactly one property.

@praihan
Copy link
Author

praihan commented Jun 1, 2018

@weswigham both of your suggestions produce symbol-index.ts(4,3): error TS2538: Type 'unique symbol' cannot be used as an index type.

Playground Link: http://www.typescriptlang.org/play/#src=const%20x%3A%20Record%3Ckeyof%20any%2C%20string%3E%20%3D%20%7B%7D%3B%0D%0Ax%5B'hello'%5D%20%3D%20'world'%3B%0D%0Aconst%20sym%20%3D%20Symbol('sym')%3B%0D%0Ax%5Bsym%5D%20%3D%20'symbol%20string'%3B%0D%0A%0D%0Aconst%20sym2%3A%20unique%20symbol%20%3D%20Symbol('sym')%3B%0D%0Ax%5Bsym2%5D%20%3D%20'symbol%20string'%3B

@weswigham
Copy link
Member

Yeah.... We don't have symbol index signatures, and a Record<whatever, string> isn't going to have any specific symbol named properties. Suffice to say, right now there's no way in the typesystem to make a type which can have arbitrary symbol keys. There's an issue for it open... #1863 I think.

@praihan
Copy link
Author

praihan commented Jun 1, 2018

Hmm. So this code working pre 2.9 is a bug?

Shouldn't you always be able to index a property by PropertyKey (by definition)? Given that PropertyKey is string | number | symbol, I think it should follow that indexing by a symbol should always be possible.

Oddly enough, the following works:

let x: { [key in symbol]: string }; // typeof x is {}
x[Symbol()] = new Date();
x['hello'] = 'world';

But this doesn't:

let x: { [key in symbol | string]: string }; // typeof x is { [key: string]: string } 
x[Symbol()] = 'value';
x['hello'] = 'world';

@weswigham
Copy link
Member

weswigham commented Jun 1, 2018

Hmm. So this code working pre 2.9 is a bug?

Conceptually yes - the actual machinery to make it work was never in place - the symbols were just being mistaken for strings.

Oddly enough, the following works:

Yeah, that's because square bracket accesses to {} are unchecked:

const sym: unique symbol = Symbol('sym');

const x: {} = {};
x[sym] = 42;
x["no"] = 12;

I don't know how to feel about it, personally, but it's been "a way out" of the type checker for awhile. Usages are an error under noImplicitAny, so it doesn't come up too often.

But this doesn't

Yeah, since the mapped type maps over string, it manufactures a string index signature in the resulting type. Since symbols can't have index signatures, it quietly drops them (for now, at least).

@praihan
Copy link
Author

praihan commented Jun 1, 2018

Got it. Thanks for the detailed explanation.

@mhegazy mhegazy added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 3, 2018
@simon-scherzinger
Copy link

simon-scherzinger commented Jun 6, 2018

@weswigham

I have a question which is similar to this issue, I believe.

I author a library containig a similar class like MyObject you will find below. One main feature is that the content of MyObject can be different depending on the content. To enable users to write something like myObject.test I added the property [key: string]: any; to the interface.

With typescript 2.9.x this will result in Type 'unique symbol' cannot be used as an index type. error, but wenn I remove this line users will get Property 'test' does not exist on type 'MyObject'. when trying to access anything "dynamically added".

Is there any way of doing this properly?

edit:
It seems I found a workaround. If i declare the symbol const sbl: any = Symbol.for('content') typescript will accept it.

const sbl: any = Symbol.for('content'); // With `any` it will work

interface MyObject {
    someProperty: string;
    another: number;

    [key: string]: any;
}

class MyObject { 
    constructor(content: any) {
        this[sbl] = content; // Type 'unique symbol' cannot be used as an index type.

        Object.keys(content).forEach((key) => {
            // This is in fact more complex ;-)
            Object.defineProperty(this, key, {
                get: () => this[sbl][key],
                set: (val: any) => this[sbl][key] = val,
            });
        });
    }
}

export default MyObject;

const obj = new MyObject({ test: 'content' });
console.log(obj.test); // Property 'test' does not exist on type 'MyObject'.

@typescript-bot
Copy link
Collaborator

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@weswigham
Copy link
Member

@simon-scherzinger You probably want something more along the lines of

const sbl = Symbol('content');

interface MyObject<T> {
    someProperty: string;
    another: number;
    [sbl]: T;
    [key: string]: any;
}

class MyObject<T> { 
    constructor(content: T) {
        this[sbl] = content;

        Object.keys(content).forEach((key) => {
            // This is in fact more complex ;-)
            Object.defineProperty(this, key, {
                get: () => this[sbl][key],
                set: (val: any) => this[sbl][key] = val,
            });
        });
    }
}

export default MyObject;

const obj = new MyObject({ test: 'content' });
console.log(obj.test); // `any`

cletusw referenced this issue in elix/elix Jul 12, 2018
…rious error messages. (The problem has been fixed, but doesn't appear to have landed in TS yet.)
@AlexeiGontarCyber
Copy link

@weswigham @mhegazy Do we have any spec that describes that this behavior is intended?
For now, the main point that I get from #24587 (comment) is that we forbid symbols as indexers because we have no symbol index signatures and for me it's sounds like bug in compiler because:

  1. https://www.typescriptlang.org/docs/handbook/symbols.html says that Just like strings, symbols can be used as keys for object properties.
  2. https://developer.mozilla.org/uk/docs/Web/JavaScript/Reference/Global_Objects/Symbol says that A symbol value may be used as an identifier for object properties

According to refs above consider following use case. I am implementing DI container where i would like to provide library users ability to use Symbols as keys(to avoid components collision) for their components. In my code I have something like:
interface ComponentsContainer { [key: PropertyKey]: Component; }

Could someone provide strong argumentation why it's not a bug in compiler and this code shouldn't work?

@fenying
Copy link

fenying commented Aug 11, 2018

For the unbearable change, I gotta write a ugly line like:

const DEFAULT_LEVEL: string = Symbol("__default__") as any;

What a stupid shit...

@davalapar
Copy link

davalapar commented Feb 5, 2019

Can't believe that shit works.

Had to use as unknown as string though since linter dislikes straight-up any.

const ItemId: string = Symbol('Item.Id') as unknown as string;
type Item = Record<string, string>;
const shoes: Item = {
  name: 'whatever',
}
shoes[ItemId] = 'randomlygeneratedstring'; // no error
{ name: 'whatever', [Symbol(Item.Id)]: 'randomlygeneratedstring' }

@evg656e
Copy link

evg656e commented Dec 17, 2019

Can't imagine how it could be marked as "work as intended", since we have this "TypeScript is a typed superset of JavaScript" on main page. This is definitely a bug in a type system and should be fixed somehow.

@artalar
Copy link

artalar commented Dec 21, 2019

@mhegazy why you labling it "Working as Intended"?
Please answer to the comment

@slikts
Copy link

slikts commented Dec 21, 2019

This issue seems mislabeled, but the main issue is #1863 and it's still open (and stalled apparently).

@klesun
Copy link

klesun commented Jul 31, 2021

Seems to be fixed by #44512 !

Available in 4.4.0-dev.20210627

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests