Error when trying to explicitly convert symbol wrapper object to string

Viewed 80

I tried the following in firefox web console.

    var sow = (function(){ return this; }).call(Symbol());

    console.log(typeof (sow)); // object

    console.log(sow.toString()); // Works: Symbol()

    String(sow); // Does not work: Throws "TypeError: can't convert symbol to string"

For some strange reason the explicit conversion in the last statement does not work (throws error).

Why is that the case?

Note that sow is an object and that is has a toString function returning a string in it's prototype chain, so it should work by using that function.

1 Answers

First note that your definition of sow can be simplified to:

var sow = Object(Symbol());

which does the same thing as your code, when in sloppy mode.

When you call String with an argument of value, per the specification:

a. If NewTarget is undefined and Type(value) is Symbol, return SymbolDescriptiveString(value).

b. Let s be ? ToString(value).

Here, since you've wrapped the symbol in an object, its type is not a symbol; rather, it's an object, so ToString is called. This, when called with an object, will first coerce the object to a primitive, then call ToString on it:

  1. Let primValue be ? ToPrimitive(argument, hint String).
  2. Return ? ToString(primValue).

But symbols cannot have ToString called on them; as you can see in the same table there, when passed a Symbol, ToString will throw a TypeError.

That's why String(sow); works. In contrast, when you do sow.toString(), you're calling Symbol.prototype.toString with a calling context (a this) of the object-wrapped symbol:

var sow = Object(Symbol());
console.log(sow.toString === Symbol.prototype.toString);

Which does something somewhat different. Symbol.prototype.toString retrieves the underlying Symbol from the object, and then calls SymbolDescriptiveString with the Symbol, resulting in a string with the symbol's descriptive value (if any).

Note that a native Symbol not wrapped in an object does not throw when passed to String because the String constructor will not call the problematic ToString abstract method when the argument is a symbol.

const sym = Symbol();
console.log(String(sym));
console.log(sym.toString());

Related