TypeScript: Unable to assign Symbol.toStringTag to a class inheriting from Uint8Array

Bug Report

🔎 Search Terms

symbol.toStringTag, uint8array

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about extending builtins

⏯ Playground Link

Playground link with relevant code

💻 Code

class Bytes extends Uint8Array {
  get [Symbol.toStringTag]() {
    return 'Bytes';
  }
}

🙁 Actual behavior

Property '[Symbol.toStringTag]' in type 'Bytes' is not assignable to the same property in base type 'Uint8Array'.
  Type 'string' is not assignable to type '"Uint8Array"'

As far as I’m aware you can add Symbol.toStringTag to any object.

🙂 Expected behavior

No error

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 18 (6 by maintainers)

Most upvoted comments

I’m not a TS team member, but I doubt very much they’d want to add a type parameter and make the types generic just to specify the value of toStringTag. My guess is this issue gets closed as Design Limitation or Working as Intended, but you’ll probably have to wait until Monday for an official response.

This is the intended behavior; subclassing is subtyping and a class that returns a different value for [Symbol.toStringTag] is indeed not a subtype.

I would recommend writing

class Bytes extends Uint8Array {
  get [Symbol.toStringTag]() {
    return 'Bytes' as any;
  }
}

if you intend to make a new class with a subtyping violation (no judgment 😅).

If you’re trying to get the "Bytes" wired through the type system, there are ways of accomplishing that, but I won’t go into it unless you’re really interested since they’re rather involved.

To be clear, this is a bug and #48617 is a feature request. Normally you wouldn’t create a bug in order to work-around the lack of a feature. That’s why it’s not clear to me that this bug is intentional, as you are suggesting.

In any event, I wonder if this can be fixed by adding a type-param? That way the normal non-extended case would continue to work as desired, but if you needed to extend you could provide a type param for the toStringTag return. Something like:

class Bytes extends Uint8Array<'Bytes'> {
  get [Symbol.toStringTag]() {
    return 'Bytes';
  }
}

Or perhaps another interface to keep the regular Uint8Array use-case cleaner?

class Bytes extends ExtendedableTypedArray<Uint8Array, 'Bytes'> {
  get [Symbol.toStringTag]() {
    return 'Bytes';
  }
}