TypeScript: Allow unique symbols to be interchangeable with types. Also assignability bug with `Symbol()`.

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: typescript@2.7.0-dev.20171226, I’ve also tested this on 2.7.0-insiders.20171214, but the playground correctly gives me errors.

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 31 (12 by maintainers)

Most upvoted comments

@sylvanaar You can extend enums in TS 🤷‍♂️

enum Foo { a, b, c }
enum Foo { d = 3, e, f}

So I assume that you can do so across modules using module augmentation.

Not to jump in from a weaker understanding of the topics under discussion, but if we are proposing enhancing syntax in this way, would we also consider having an enum where the values are Symbols?

Please see #18408

@mhegazy I wasn’t trying to explain how typescript is implemented or how the typesystem works. I was just saying that as a developer, string and integer literals have the property of being their own type, but that there’s no such convenient literal syntax for a unique symbol. But because one of the main use cases for unique symbols is to define object properties with them, there’s a need to use typeof a lot or to always define a type type C = typeof C merging the type name with the constant. My suggestion is just to reduce boilerplate by allowing that to be done with a single statement.

You can reference the type of the constant with typeof (the type query operator) in the following way:

const Foo = Symbol("foo");
type Foo = typeof foo;