Skip to content

"Unique symbol" returned from the function becomes "symbol" #24506

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

Closed
Lodin opened this issue May 30, 2018 · 9 comments
Closed

"Unique symbol" returned from the function becomes "symbol" #24506

Lodin opened this issue May 30, 2018 · 9 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@Lodin
Copy link

Lodin commented May 30, 2018

TypeScript Version: 3.0.0-dev.20180530

Search Terms:
unique symbol, property, dynamic class creation

Code

const create = () => { 
  const prop = Symbol();

  return {
    cls: class A {
      public [prop]: string = 'str'; // works as expected
    },
    prop,
  };
}

const { cls, prop } = create();

class B extends cls { 
  public [prop]: string = 'test'; // Error: A computed property name in a class property declaration must refer to an expression whose type is a literal type or a 'unique symbol' type.
}

Expected behavior:
No error.

Actual behavior:
When unique symbol is returned from function, it becames symbol that forbids working with class properties defined inside the function as well as class itself.

Playground Link:
Link

Related Issues:
Didn't find any

@mhegazy
Copy link
Contributor

mhegazy commented May 30, 2018

there are two issues here. first a mutable location (e.g. property assignment) is always widened and does not get the literal type.. e.g.:

const s = "str"; // "str"
const x = {
    prop: s;
};

x.prop; // string

Unless the properties that are declared as readonly (or contextually typed by a readonly property), it will not get the literal type..

For the second issue, for the compiler to verify the relationship, the compiler needs to have access to the unique symbol declaration, otherwise, no grantees can be made that the function is not called multiple times for instance.. e.g.

const cls = create().cls;
const prop = create().prop;

prop here can not be assumed to be the same unique symbol that was used to declare cls

@mhegazy mhegazy added the Design Limitation Constraints of the existing architecture prevent this from being fixed label May 30, 2018
@Lodin
Copy link
Author

Lodin commented May 31, 2018

Is there any workaround to disable this compiler check except for @ts-ignore? I cannot cast it to unique symbol, cannot force function return to have unique symbol, cannot cast it to any, because class property requires string or unique symbol... Only @ts-ignore can disable this check, but I cannot force
a user of my library to use it at any function overriding. I suppose if compiler cannot guarantee it, my library should be able to do it at least, but now I'm completely run out of options.

@mhegazy
Copy link
Contributor

mhegazy commented May 31, 2018

you can always cast your way out:

const create = () => {
    const prop = Symbol();

    return {
        cls: class A {
            public [prop]: string = 'str'; 
        },
        prop: prop as typeof prop,  // make sure the unique symbol survives the winding
    };
}

const result = create();
const prop: ReturnType<typeof create>["prop"] = result.prop; // explicitly type it to the type of the unique symbol.

class B extends result.cls {
    public [prop]: string = 'test'; 
}

@Lodin
Copy link
Author

Lodin commented May 31, 2018

@mhegazy Thanks for the idea.

I'm thinking on your words, and there is something that I cannot get intuitively. Actually, I don't quite understand why symbol widens on destructuring. If I replace symbol with string and follow your example, it will work without casting to ReturnType<typeof create>["prop"]:

const create = () => {
    const prop = 'value';

    return {
        cls: class A {
            public [prop]: string = 'str'; 
        },
        prop: prop as typeof prop,
    };
}

const { cls, prop } = create();

class B extends cls {
    public [prop]: string = 'test'; // works without explicit cast
}

Aren't they the same? String literal saves the type for some reason without widening, but symbol loses it.

@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.

@aleclarson
Copy link

@mhegazy Is the following behavior intended?

function test<T>(fn: () => T): T {
  return fn()
}

declare const foo: unique symbol

// Type 'symbol' is not assignable to type 'unique symbol'
let result: typeof foo = test(() => foo)

Playground

@ExE-Boss
Copy link
Contributor

ExE-Boss commented Jan 14, 2021

@aleclarson
That seems to have been fixed in TypeScript 3.6, as T is now correctly inferred as typeof foo.

@pastelmind
Copy link

This may be related, but when I return a unique symbol in an async function, TypeScript infers its return type as Promise<symbol> instead of Promise<unique symbol>.

const VALUE = Symbol('VALUE')
async function foo() { return VALUE }

const result = foo() // Promise<symbol>

This can be problematic when I accept as argument a callback that may return a unique symbol or a promise:

function useCallback(callback: () => typeof VALUE | Promise<typeof VALUE>) {
  // ...
}

// OK
useCallback(() => VALUE)

// Type 'Promise<symbol>' is not assignable to type 'unique symbol | Promise<unique symbol>'.
//   Type 'Promise<symbol>' is not assignable to type 'Promise<unique symbol>'.
//     Type 'symbol' is not assignable to type 'unique symbol'.
useCallback(async () => VALUE)

TypeScript playground link: https://www.typescriptlang.org/play?#code/LAKAxg9gdgzgLgAgGoEEAyBVAoggvAgZQE8BbAIwgBsAKAclUy1oEpQBDGIqMBAMwFducAJbQ+ECNWYIA3ggBOAUzj95UZOmwIAvqFCRYiJTH6VE+XhKkIA9DYQAFeRBLCYigDydyVAHx6QASFRdX53AGE2SkoyNjAAa2owKJi4+IAuBGtcXwQ4IgAHRQheDUYEAB9HZ1d3D3yikrLsX2kZXRBQOwQAeQBpUDDFSOjYhOps3IZsVk6QboAVQsUERXlneUGIlLHEji4eSeasWaA

@aayla-secura
Copy link

This may be related, but when I return a unique symbol in an async function, TypeScript infers its return type as Promise<symbol> instead of Promise<unique symbol>.

const VALUE = Symbol('VALUE')
async function foo() { return VALUE }

const result = foo() // Promise<symbol>

This can be problematic when I accept as argument a callback that may return a unique symbol or a promise:

function useCallback(callback: () => typeof VALUE | Promise<typeof VALUE>) {
  // ...
}

// OK
useCallback(() => VALUE)

// Type 'Promise<symbol>' is not assignable to type 'unique symbol | Promise<unique symbol>'.
//   Type 'Promise<symbol>' is not assignable to type 'Promise<unique symbol>'.
//     Type 'symbol' is not assignable to type 'unique symbol'.
useCallback(async () => VALUE)

TypeScript playground link: https://www.typescriptlang.org/play?#code/LAKAxg9gdgzgLgAgGoEEAyBVAoggvAgZQE8BbAIwgBsAKAclUy1oEpQBDGIqMBAMwFducAJbQ+ECNWYIA3ggBOAUzj95UZOmwIAvqFCRYiJTH6VE+XhKkIA9DYQAFeRBLCYigDydyVAHx6QASFRdX53AGE2SkoyNjAAa2owKJi4+IAuBGtcXwQ4IgAHRQheDUYEAB9HZ1d3D3yikrLsX2kZXRBQOwQAeQBpUDDFSOjYhOps3IZsVk6QboAVQsUERXlneUGIlLHEji4eSeasWaA

I'm struggling with the same issue. In case someone else is wondering how to get around this:

Doing async () => VALUE as typeof VALUE does not make the error go away because the symbol is still widened when the Promise is resolved; however doing async (): Promise<typeof VALUE> => VALUE does work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

7 participants