A template literal interpolating a Symbol silently stringifies it, where the spec requires a TypeError. String concatenation on the same value already throws correctly, so this is a gap in the template path specifically, not in ToString.
ToString(argument) (ECMA-262 7.1.17) throws for a Symbol, and template substitution reaches it through GetValue → ToString in §13.2.8.6. String(sym) and sym.toString() are the two legal ways to stringify one and must keep working.
Reproduction
const s = Symbol("x");
try { console.log(`${s as any}`); } catch (e: any) { console.log("template threw", e.constructor.name + ":", e.message); }
try { console.log("" + (s as any)); } catch (e: any) { console.log("concat threw", e.constructor.name + ":", e.message); }
console.log("String():", String(s));
console.log("toString():", s.toString());
try { console.log(`a${s as any}b`); } catch (e: any) { console.log("multi threw", e.constructor.name + ":", e.message); }
node 26.5.1
template threw TypeError: Cannot convert a Symbol value to a string
concat threw TypeError: Cannot convert a Symbol value to a string
String(): Symbol(x)
toString(): Symbol(x)
multi threw TypeError: Cannot convert a Symbol value to a string
perry (clean main, built at c8cf450563)
Symbol(x)
concat threw TypeError: Cannot convert a Symbol value to a string
String(): Symbol(x)
toString(): Symbol(x)
aSymbol(x)b
Lines 2, 3 and 4 match. Lines 1 and 5 — the two template forms — should throw and do not.
Where to look
The + path throws, so the Symbol check exists and is reachable; the template path does not consult it. Templates desugar to a concat chain, and js_string_concat_chain's classify loop falls back to js_jsvalue_to_string for a pointer-tagged value, which stringifies a Symbol rather than rejecting it. The pairwise + helper rejects first. Worth checking whether the single-substitution form (`${s}`, which lowers to a bare StringCoerce with no chain) and the multi-part form need the check in different places.
Why it matters beyond conformance
Silently stringifying is the worse failure direction: a Symbol used as a key or brand that lands in a template produces a plausible-looking "Symbol(x)" string instead of failing, so the mistake propagates into output, keys or comparisons rather than surfacing where it happened.
Provenance
Found while measuring template-literal interpolation for #10576. Present identically on that PR's before and after binaries, so it is pre-existing on main and unrelated to that change.
A template literal interpolating a
Symbolsilently stringifies it, where the spec requires aTypeError. String concatenation on the same value already throws correctly, so this is a gap in the template path specifically, not in ToString.ToString(argument)(ECMA-262 7.1.17) throws for a Symbol, and template substitution reaches it throughGetValue→ToStringin §13.2.8.6.String(sym)andsym.toString()are the two legal ways to stringify one and must keep working.Reproduction
node 26.5.1
perry (clean
main, built atc8cf450563)Lines 2, 3 and 4 match. Lines 1 and 5 — the two template forms — should throw and do not.
Where to look
The
+path throws, so the Symbol check exists and is reachable; the template path does not consult it. Templates desugar to a concat chain, andjs_string_concat_chain's classify loop falls back tojs_jsvalue_to_stringfor a pointer-tagged value, which stringifies a Symbol rather than rejecting it. The pairwise+helper rejects first. Worth checking whether the single-substitution form (`${s}`, which lowers to a bareStringCoercewith no chain) and the multi-part form need the check in different places.Why it matters beyond conformance
Silently stringifying is the worse failure direction: a Symbol used as a key or brand that lands in a template produces a plausible-looking
"Symbol(x)"string instead of failing, so the mistake propagates into output, keys or comparisons rather than surfacing where it happened.Provenance
Found while measuring template-literal interpolation for #10576. Present identically on that PR's before and after binaries, so it is pre-existing on
mainand unrelated to that change.