Skip to content

unique symbols are downgraded simple symbols when they pass through destructuring #53753

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
lovasoa opened this issue Apr 12, 2023 · 8 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@lovasoa
Copy link

lovasoa commented Apr 12, 2023

Bug Report

unique symbol seems to lose its uniqueness when it goes through Promise.race

πŸ”Ž Search Terms

Awaited unique symbol typescript

πŸ•— Version & Regression Information

typescript version 5.0.4

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

const s: unique symbol = Symbol();
const t: Promise<typeof s> = Promise.race([s]);

πŸ™ Actual behavior

The code above should type check

πŸ™‚ Expected behavior

t should end up with type Promise<unique symbol> instead of Promise<symbol>

@lovasoa lovasoa changed the title unique symbols are downgraded simple symbols when they pass through Promise.race unique symbols are downgraded simple symbols when they pass through destructuring Apr 12, 2023
@lovasoa
Copy link
Author

lovasoa commented Apr 12, 2023

Investigating a bit, the problem seems to come from destructuring. Any intermediary assignation seems to fail

const s: unique symbol = Symbol();
const t = s;
const ss: typeof s = t;

@nmain
Copy link

nmain commented Apr 13, 2023

This can be inferred correctly with a const context

    const s: unique symbol = Symbol();
    const t: Promise<typeof s> = Promise.race([s] as const);

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Apr 13, 2023
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Apr 13, 2023

Unique symbols consistently follow the same widening rules as literals, which includes widening in places like non-consted arrays

@lovasoa
Copy link
Author

lovasoa commented Apr 13, 2023

@RyanCavanaugh : I can see that. But don't you think this is a problem ? This leads to a surprising behavior that is probably not what the user wants in most cases.

const x : unique symbol = Symbol("x");
const y = x; // is there any case in which someone wants to have a generic symbol instead of the unique symbol "x" here ?

Are there pieces of code in which the default behavior here makes sense ?

@lovasoa
Copy link
Author

lovasoa commented Apr 13, 2023

Plus, the rules don't seem to be exactly the same, because the following works as intended:

const s: 1 = 1;
const t: Promise<typeof s> = Promise.race([s]);

@RyanCavanaugh
Copy link
Member

This has been the behavior since we first shipped unique symbols over five years ago and I've seen at most a handful of complaints that this is unintuitive.

This leads to a surprising behavior

It's impossible for zero people to be surprised by something.

Are there pieces of code in which the default behavior here makes sense?

Sure, for example if you have

const s: unique symbol = Symbol();
export function fn() {
    return s;
}

the inferred type of fn is Symbol, not typeof s, since you (probably) don't want to leak implementation details.

@lovasoa
Copy link
Author

lovasoa commented Apr 13, 2023

I didn't want to be provocative, sorry if my tone was off.

const s: unique symbol = Symbol();
export function fn(): typeof s {
    return s;
}

How would that leak an implementation detail ? The symbol itself is returned to the user and leaked anyway. I am struggling to see an example where leaking the value would be fine but leaking its type would be an issue.

Is there any real-world code where it would be a problem ?

With the default behavior, APIs like this become broken, without it being obvious to the user:

export function fn() { return s }
export function accept(x: typeof s) { } // This function becomes impossible to use

const s: unique symbol = Symbol(); // replacing "unique symbol" by a literal type and Symbol() by a literal value results in a different behavior

If the behavior is set in stone because it was released like that, then maybe that should be highlighted in the docs, since it is (at least to simple users like me) surprising and unlike other parts of typescript.

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

# for free to join this conversation on GitHub. Already have an account? # 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

4 participants