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

String Literal types on es6 interfaces for [Symbol.toStringTag]? #19006

Closed
chriseppstein opened this issue Oct 7, 2017 · 7 comments · Fixed by #24396
Closed

String Literal types on es6 interfaces for [Symbol.toStringTag]? #19006

chriseppstein opened this issue Oct 7, 2017 · 7 comments · Fixed by #24396
Labels
Bug A bug in TypeScript Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript Help Wanted You can do this

Comments

@chriseppstein
Copy link

TypeScript Version: 2.5.3

Code

class MySpecialMap implements Map<Object, Object> {
   private _map;
   readonly [Symbol.toStringTag] = "MySpecialmap";
   constructor() { this._map = new Map(); }
   // declare all other methods of Map methods with some unique behaviors that wrap calls to this._map. 
}

Expected behavior:

Classes implementing an ES6 interface can declare a distinct value for Symbol.toStringTag.

The goal of this property is to provide a distinct value, using a string literal type for the interface is overly specific.

Actual behavior:

src/MySpecialMap.ts(1,7): error TS2420: Class 'MySpecialMap' incorrectly implements interface 'Map<Object, Object>'.
  Types of property '[Symbol.toStringTag]' are incompatible.
    Type '"MySpecialMap"' is not assignable to type '"Map"'.
@chriseppstein
Copy link
Author

chriseppstein commented Oct 7, 2017

Relevant commits:

cc: @DanielRosenwasser

@aluanhaddad
Copy link
Contributor

aluanhaddad commented Oct 7, 2017

There is actually no such thing as an es6 interface, interfaces are purely a type system feature and, as that system is structural, there is actually no need to declare that your class implements any interface at all. However, in this case, your class does not in fact implement the interface, structurally or otherwise, because the interface does indeed require that value for the tag. It sounds more like you want to extend map. Unfortunately that will only work reliably under --target es2015 or above.

@chriseppstein
Copy link
Author

@aluanhaddad yeah, I understand those things. sorry, if I wasn't clear or didn't use the correct nomenclature. I wanted to use the type system to ensure that the class I built implemented the Map interface. I didn't want to inherit the methods of that class -- I wanted to provide an implementation for them and I wanted to let the interface of that class guide that implementation and ensure compatibility if the type ever changes.

I'm not sure what you mean by "does indeed require that value for the tag" because this is typescript type, not something enforced by es6 and it would be incorrect for my class to declare that it is actually a"Map" when it's not according to the semantics of Symbol.toStringTag.

I tried to create an interface that extends map and provides a different value for toStringTag but this doesn't work.

interface MySpecialMap<Object, Object> extends Map<Object, Object> {
  readonly [Symbol.toStringTag] = "MySpecialMap";
}

yielding the same error:

src/MySpecialMap.ts(1,11): error TS2430: Interface 'MySpecialMap<Object>' incorrectly extends interface 'Map<Object, Object>'.
  Types of property '[Symbol.toStringTag]' are incompatible.
    Type '"MySpecialMap"' is not assignable to type '"Map"'.

If I declare that it's my class that extends Map instead I get different errors:

src/MySpecialMap.ts(5,7): error TS2415: Class 'MySpecialMap' incorrectly extends base class 'Map<Object, Object>'.
  Types of property '[Symbol.toStringTag]' are incompatible.
    Type '"MySpecialMap"' is not assignable to type '"Map"'.
src/MySpecialMap.ts(5,7): error TS2417: Class static side 'typeof MySpecialMap' incorrectly extends base class static side '{ readonly prototype: Map<any, any>; }'.
  Types of property 'prototype' are incompatible.
    Type 'MySpecialMap' is not assignable to type 'Map<any, any>'.
      Types of property '[Symbol.toStringTag]' are incompatible.
        Type '"MySpecialMap"' is not assignable to type '"Map"'.

I don't understand what benefit TypeScript is imbuing to the javascript developer by using string literal types for toStringTag values in its interface definitions of es6 classes. toStringTag shouldn't be used for case statements or instance checking. The goal is to make debugging easier, right? How does limiting the possible values of this property help debugging?

I think toStringTag should be of type string for all es6 interfaces so that developers can override it correctly.

(note: I am using --target es2015)

@ghost
Copy link

ghost commented Oct 7, 2017

Yeah, I'm not sure what value we're providing by statically typing Symbol.toStringTag as some particular string. We should come up with a better fix to #5388 (ref: #202).

@mhegazy mhegazy added Bug A bug in TypeScript Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript Help Wanted You can do this labels Oct 9, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Oct 9, 2017

we should revert #6361. we tried to be too clever with the toStringTag and assumed no one will override it.

@mhegazy mhegazy added this to the Community milestone Oct 9, 2017
@mhegazy
Copy link
Contributor

mhegazy commented Oct 9, 2017

PRs welcomed.

@spion
Copy link

spion commented Jun 13, 2018

This is also a problem for Bluebird: petkaantonov/bluebird#1421

The overly strict Symbol.toStringTag effectively introduces a nominal type system for standard libraries.

We are between a rock and a hard place here. If we implement Symbol.toStringTag to have the value 'Promise', libraries using it to differentiate between native and Bluebird promises will break. If we return something different, the .d.ts needs to lie to the type system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Domain: lib.d.ts The issue relates to the different libraries shipped with TypeScript Help Wanted You can do this
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants