]> git.kaiwu.me - quickjs.git/commitdiff
Fix template literal substitution (Iann29)
authorFabrice Bellard <fabrice@bellard.org>
Mon, 26 Oct 2026 13:33:01 +0000 (14:33 +0100)
committerFabrice Bellard <fabrice@bellard.org>
Mon, 26 Oct 2026 13:33:01 +0000 (14:33 +0100)
quickjs-atom.h
quickjs.c
tests/test_language.js

index 82f6981fce5e955924750f5605d4aa2b3550b90e..ea461ae5e73e898cd26d666b97f7cb29aba96162 100644 (file)
@@ -129,7 +129,6 @@ DEF(input, "input")
 DEF(defineProperties, "defineProperties")
 DEF(apply, "apply")
 DEF(join, "join")
-DEF(concat, "concat")
 DEF(split, "split")
 DEF(construct, "construct")
 DEF(getPrototypeOf, "getPrototypeOf")
index b2a69492eada0320abd7f81bbd8cff08881637ab..c564fdb3ec726570c45708de0bb41a15906b2d7f 100644 (file)
--- a/quickjs.c
+++ b/quickjs.c
@@ -25029,8 +25029,8 @@ static __exception int js_parse_template(JSParseState *s, int call, int *argc)
                 if (depth == 0) {
                     if (s->token.u.str.sep == '`')
                         goto done1;
-                    emit_op(s, OP_get_field2);
-                    emit_atom(s, JS_ATOM_concat);
+                } else {
+                    emit_op(s, OP_add);
                 }
                 depth++;
             } else {
@@ -25043,11 +25043,19 @@ static __exception int js_parse_template(JSParseState *s, int call, int *argc)
             return -1;
         if (js_parse_expr(s))
             return -1;
+        /* XXX: should instead add OP_to_string and OP_concat so that
+           all the strings can be concatenated at the end (faster) */
+        if (!call) {
+            /* ToString right away, before the next substitution is evaluated
+               (ECMA-262 13.2.8.6). OP_to_propkey keeps a symbol, which
+               OP_add then rejects, as ToString does. */
+            emit_op(s, OP_to_propkey);
+            emit_op(s, OP_add);
+        }
         depth++;
         if (s->token.val != '}') {
             return js_parse_error(s, "expected '}' after template expression");
         }
-        /* XXX: should convert to string at this stage? */
         free_token(s, &s->token);
         /* Resume TOK_TEMPLATE parsing (s->token.line_num and
          * s->token.ptr are OK) */
@@ -25063,9 +25071,6 @@ static __exception int js_parse_template(JSParseState *s, int call, int *argc)
         seal_template_obj(ctx, raw_array);
         seal_template_obj(ctx, template_object);
         *argc = depth + 1;
-    } else {
-        emit_op(s, OP_call_method);
-        emit_u16(s, depth - 1);
     }
  done1:
     return next_token(s);
index babece3db109d7368e021fac28b9f46d1857bece..aa96c223cd735ce2e6dbac4e1bc3493cd4ce8fc0 100644 (file)
@@ -375,6 +375,38 @@ function test_template_skip()
     assert(b, "BaraBarbaz");
 }
 
+function test_template_tostring_order()
+{
+    /* each substitution is converted with ToString right after it is
+       evaluated (ECMA-262 13.2.8.6), and String.prototype.concat is not
+       involved */
+    var log = [];
+    var o = { toString() { log.push("o"); return "O"; } };
+    assert(`${o}${(log.push("next"), "n")}`, "On");
+    assert(log.join(), "o,next");
+
+    var c = { n: 0, toString() { return "" + this.n; } };
+    assert(`${c} ${c.n++}`, "0 0");
+
+    var d = new Date(2024, 0, 15, 12);
+    assert(`${d}|${d.setFullYear(2000)}`.indexOf("2024"), 11);
+
+    var p = { [Symbol.toPrimitive](hint) { return hint; } };
+    assert(`${p}`, "string");
+    var v = { valueOf() { return 1; }, toString() { return "s"; } };
+    assert(`${v}`, "s");
+
+    var concat = String.prototype.concat;
+    String.prototype.concat = function() { return "concat"; };
+    try {
+        assert(`a${1}b${2}`, "a1b2");
+    } finally {
+        String.prototype.concat = concat;
+    }
+
+    assert_throws(TypeError, () => `${Symbol()}`);
+}
+
 function test_object_literal()
 {
     var x = 0, get = 1, set = 2, async = 3;
@@ -1004,6 +1036,7 @@ test_arguments();
 test_class();
 test_template();
 test_template_skip();
+test_template_tostring_order();
 test_object_literal();
 test_regexp_skip();
 test_labels();