Skip to content

Allow unique symbols to be interchangeable with types. Also assignability bug with Symbol(). #20898

Closed
@rozzzly

Description

@rozzzly

Now that we have #15473, when a symbolwhich is guaranteed to be unique by the ES2015 spec—is assigned to a const variable, it can be used (via computed property syntax) in place of a normal property name.

Quick contrived example:

export const FOO = Symbol('FOO');
export const BAR = Symbol('BAR');

export interface Thing {
    [ FOO ]: boolean;
    [ BAR ]: number;
}

const thing: Thing = undefined;
const foo = thing[ FOO ]; // ☝️ hover shows type of `foo` as `boolean`
const bar = thing[ BAR ]; // ☝️ hover shows type of `bar` as `number`

I love this, it allows a symbol to act as a unique identifier. But it truth, a symbol represents a unique value, I want to use that unique value to describe a type.

Another quick contrived example:

export const FOO = Symbol('FOO');
export const BAR = Symbol('BAR');

export function fooOrBar(value: FOO | BAR): string { // ❌ Cannot find name 'FOO'.
                                                     // ❌ Cannot find name 'BAR'.
    if (value === FOO) return 'You gave me FOO!';
    else if (value === BAR) return 'You gave me BAR!';
    else throw TypeError('What was that!?');
}

Is that something anyone else would want? A lot of us will use a symbol to define a unique constant, I think it just makes sense to be able to use that constant to... refer to that constant.


On a side note, I think I may have found a bug. So while typing up this issue, I found that I am able to achieve the desired behavior by using an intersection with a "tagged" object literal:

export type FOO = symbol & { FOO: true };
export const FOO: FOO = Symbol('FOO'); // ❓️ no error
export type BAR = symbol & { BAR: true };
export const BAR: BAR = Symbol('BAR'); // ❓ no error

While this is nice for me because it achieves what I want, I don't think it should work. Heres why:

So if you hover over the Symbol() constructor you'll see this:

var Symbol: SymbolConstructor
(description?: string | number) => symbol

That's what I expected to see. You (optionally) give it a string | number description and it gives you a symbol in return. So why doesn't the compiler freakout when I assign it to something that is symbol & { FOO: true }? If IRRC the spec says that no properties can be set on a symbol. I can't find where it said that, but really quickly in my DevTools Console, I did this which seems to affirm my belief:

$> const foo = Symbol('foo')
undefined
$> foo
Symbol(foo)
$> foo.foo = true
true
$> foo
Symbol(foo)
$> foo.foo
undefined

Perhaps there some special assignability feature for typescript primitive symbol that I'm overlooking? I don't know. But if you do:

export type FOO = string & { FOO: true };
export const FOO: FOO = String('FOO'); // ❌ Type 'string' is not assignable to type 'FOO'.
                                       // ❌ Type 'string' is not assignable to type '{ FOO: true; }'.

Looking at the type info for the String() constructor, it's nearly identical:

const String: StringConstructor
(value?: any) => string

so why does it behave differently?


Just out of curiosity, I tried:

export const FOO: boolean = Symbol('FOO');

And got no errors. Something is definitely broken because symbol is acting like any.

Because you will ask, I am currently running: [email protected], I've also tested this on 2.7.0-insiders.20171214, but the playground correctly gives me errors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs More InfoThe issue still hasn't been fully clarified

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions