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

Can't evaluate equality of symbols made by Symbol.for #35909

Open
falsandtru opened this issue Dec 29, 2019 · 10 comments
Open

Can't evaluate equality of symbols made by Symbol.for #35909

falsandtru opened this issue Dec 29, 2019 · 10 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@falsandtru
Copy link
Contributor

falsandtru commented Dec 29, 2019

This is also an obstacle of branded types and ghost types. I think resolving this problem is easy because these types are just a new literal type similar to string literal types.

TypeScript Version: 3.7.x-dev.20191228

Search Terms:

Code

const a = Symbol.for('');
const b = Symbol.for('');
const c: typeof b = Symbol.for(''); // error
a === b; // error

Expected behavior:
no error
Actual behavior:
Type 'typeof b' is not assignable to type 'typeof a'.
This condition will always return 'false' since the types 'typeof a' and 'typeof b' have no overlap.
Playground Link: https://www.typescriptlang.org/play/index.html?ts=3.8.0-dev.20191228&ssl=1&ssc=1&pln=4&pc=18#code/MYewdgzgLgBAhjAvDAygTwLYCMQBsB0AZiAE4AUA5BQJQDcAUKJLFkqpjgceVXY+NBjAAXDChoADgFMQhGK2TpseIqUo1aMAPRaYUkiVL0EiU-M069B0kA

Related Issues: #35200

@falsandtru
Copy link
Contributor Author

falsandtru commented Dec 29, 2019

Example

// npm package A@v1
namespace A1 {
    // Structural hidden field across versions and packages.
    const tag = Symbol.for('TagName');
    export class A<T> {
        constructor(arg: T) { }
        [tag]: T;
    }
}
// npm package A@v2
namespace A2 {
    // Structural hidden field across versions and packages.
    const tag = Symbol.for('TagName');
    export class A<T> {
        constructor(arg: T) { }
        [tag]: T;
    }
    export declare function f(a: A<'a'>): void;
}
// npm package B
namespace B {
    A2.f(new A1.A('a')); // Should work.
}
// npm package A
namespace A {
    // Structural hidden field across versions and packages.
    const tag = Symbol.for('TagName');
    export class A<T> {
        constructor(arg: T) { }
        [tag]: T;
    }
}
// npm package B
namespace B {
    // Structural hidden field across versions and packages.
    const tag = Symbol.for('TagName');
    export class B<T> {
        constructor(arg: T) { }
        [tag]: T;
    }
}
// npm package C
namespace C {
    declare function f(a: A.A<'a'>): void;
    f(new B.B('a')); // Should work.
}

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jan 14, 2020
@ExE-Boss
Copy link
Contributor

ExE-Boss commented Feb 14, 2020

Like we have unique symbol, we should probably support:

const foo: global symbol "<name>";

This would allow declaring Node’s built-in symbols as:

declare module "util" {
	namespace inspect {
		// https://github.com/nodejs/node/issues/20821
		const custom: global symbol "nodejs.util.inspect.custom";
	}

	namespace promisify {
		// https://github.com/nodejs/node/issues/31647
		const custom: global symbol "nodejs.util.promisify.custom";
	}
}

@typeofweb
Copy link

It's a bug I just stumbled upon and it was really surprising.

@ExE-Boss
Copy link
Contributor

It’s the intended behaviour for unique symbol types, which global symbols actually aren’t, but they’re currently using the same code path.

@typeofweb
Copy link

Sounds like it's not expected, more like an unforeseen side effect of adding unique symbols.

@kurtharriger
Copy link

I was looking at code for library and noticed usage of Symbol and Symbol.for I was curious what the difference was and found this https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for

I then attempted to try it in typescript and erroneously get the error that the conditional will alwasy be false even when the value is actually true.

https://www.typescriptlang.org/play/?ssl=3&ssc=25&pln=1&pc=1#code/MYewdgzgLgBARjAvDAygTwLZxAGwHQBmIATgBQDkcAhseQJQDcAUKJLHAExKqbb5FlKNes1YRcAUzw4QAc1LwkiZJxiMmQA

image

@cowboyd
Copy link

cowboyd commented Jan 13, 2022

Is the problem that there is no concept of a "symbol literal" in TypeScript?

It would be really nice to have a literal syntax for symbols which would type just like any other primitive literal like 'string', 5, or true.

Syntax is arbitrary, but using@ as a placeholder.

const something = @something; //=> Symbol.for("something")
const somethingElse = @"something.else"; //=> Symbol.for("something.else")

const object: { [something]: string } = { @something: 'hello' };

Seems like this would ease a lot of pain in matching types that use well known symbols as keys. But even in the absence of a literal syntax, what would it take for TS to treat Symbol.for() expressions as literals?

@nicolo-ribaudo
Copy link

nicolo-ribaudo commented Jan 13, 2022

@cowboyd That would risk big conflicts with decorators, but a while ago I was thinking about the symbol "foo" syntax:

declare function Symbol.for<T extends string>(name: T): symbol T;
declare function Symbol.keyFor<T extends string>(sym: symbol T): T;
declare function Symbol.keyFor(sym: symbol): string | undefined;

let a = Symbol.for("foo"); // type is `symbol "foo"`
let b: symbol "foo" = Symbol.for("bar"); // error
let c = Symbol.for("baz" as string); // symbol string
let d: symbol = Symbol.for("foo"); // ok, symbol T is a subtype of symbol
let d: symbol "foo" = Symbol(); // error

let descA: "foo" = Symbol.keyFor(a); // ok
let descB: "bar" = Symbol.keyFor(a); // err, "foo" is not assignable to "bar"

EDIT: I just noticed that this was already suggested at #35909 (comment) 😬

@cowboyd
Copy link

cowboyd commented Jan 14, 2022

@nicolo-ribaudo you're definitely right, and the @ was just the first thing that popped into my head. And in fact, there isn't any need for a new literal syntax, just a new type that accurately represents the return value of Symbol.for().

I like your suggestions. Also need to consider the difference between Symbol() and Symbol.for(). Maybe something like:

let a = Symbol("foo") // type is `unique symbol "foo"`
let b = Symbol.for("foo") // type is `symbol for "foo"`

Anyhow, as you noted @ExE-Boss already suggested this.... wonder why this thread petered out? Is there something that we can do?

@pjeby
Copy link

pjeby commented Jun 19, 2022

Throwing in a use case here: I've got a library that exports a symbol for purposes of interop, that other code can declare methods with that symbol, without needing to depend on the library that defines the symbol. But then, in a project that uses both of those libraries, Typescript can't tell that module 2 implements the interface specified by module 1, even though it uses the same Symbol.for().

As it stands, the only way to get this kind of interop is with strings, not symbols, as there's no "global symbol" or "symbol for" syntax in Typescript. Using typeof can't work here because that would force the common dependency, and a shared type definitions file, since merely equivalent types are not sufficient for "unique symbol" to match.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

8 participants