Skip to content

Conversation

@dilbertKocik
Copy link
Contributor

Summary

This commit addresses ECMAScript specification compliance issues with template literal string conversion by introducing a dedicated STRING_CONCAT token and operation that properly converts objects to strings using toString() semantics instead of the default ToPrimitive behavior.

Problem

Template literals like ${Object(1n)} were incorrectly using the default ToPrimitive conversion (which calls valueOf() first) instead of string conversion semantics (which calls toString() first). This caused Test262 compliance failures where custom toString() methods on BigInt and Symbol wrapper objects were not being invoked correctly.

Before: Template literals were transformed into regular ADD operations, causing ${Object(1n)} to return "1" instead of the expected custom toString() result.

After: Template literals use dedicated STRING_CONCAT operations that properly call ScriptRuntime.toString(), ensuring ECMAScript compliance.

Changes Made

  1. New Token Type: Added Token.STRING_CONCAT to distinguish template literal string concatenation from arithmetic addition operations.

  2. IRFactory Enhancement: Modified transformTemplateLiteral() to use STRING_CONCAT instead of ADD tokens, ensuring template literal substitutions follow string conversion semantics.

  3. Interpreter Support: Implemented DoStringConcat instruction class that directly converts operands to strings using ScriptRuntime.toString() before concatenation.

  4. Optimizer Support: Added STRING_CONCAT handling in BodyCodegen and OptRuntime.concat() method for optimized execution paths.

  5. Test Improvements: Removed 2 failing Test262 tests from the exclusion list:

    • built-ins/BigInt/wrapper-object-ordinary-toprimitive.js
    • built-ins/Symbol/prototype/Symbol.toPrimitive/removed-symbol-wrapper-ordinary-toprimitive.js

Technical Details

  • Root Cause: Template literals were incorrectly sharing the same code path as arithmetic addition, causing wrong ToPrimitive hint usage
  • Solution: Separate template literal string concatenation from arithmetic operations at the token level
  • Performance: Direct string conversion eliminates unnecessary ToPrimitive calls and type checking overhead
  • Compliance: Ensures template literals follow ECMAScript specification for string conversion semantics

Impact

  • Resolves ES2015 Issue with Template Literal Object > string substitution #1406
  • ✅ Fixes ECMAScript compliance for template literal string conversion
  • ✅ Improves Test262 conformance (2 additional tests now pass)
  • ✅ Maintains backward compatibility for existing code
  • ✅ Optimizes template literal performance by avoiding unnecessary type conversions

This change brings Rhino's template literal implementation into full compliance with the ECMAScript specification while maintaining performance and backward compatibility.

@rbri
Copy link
Collaborator

rbri commented Sep 3, 2025

@dilbertKocik many thanks for this PR.
Even if only two more tests are successful, I think that's a real step forward.

The CI failures are because you have manually edited the test262.properties. The idea of this is to regenerate the file - see https://github.com/mozilla/rhino/tree/master/tests#updating-the-test262properties-file for more

It will be great if you can regenerate the file according to the docu - i'm sure the ci build will pass then and we can merge your great PR.

@rbri
Copy link
Collaborator

rbri commented Sep 3, 2025

@dilbertKocik maybe you have an idea about a good place in the code to document the idea behind the new token a bit. Hope this will help others in the future to understand why we have the two add tokens.

@dilbertKocik dilbertKocik force-pushed the template-literal-object branch from 5d3b169 to 61e07e3 Compare September 5, 2025 16:52
@rbri
Copy link
Collaborator

rbri commented Sep 5, 2025

@dilbertKocik can you please rebase and force push again, i see some conflicts here

@dilbertKocik
Copy link
Contributor Author

@rbri Yeah, I see it. I rebased my branch locally but I'm seeing a large diff in the test262.properties file (approximately 500 lines). I'm trying to figure out why that's happening. All the other changes look the same as the PR. I tried regenerating the file but it looks like the same large diff. I'll try to sort this out on Monday.

@dilbertKocik dilbertKocik force-pushed the template-literal-object branch from 61e07e3 to 0c1c5c5 Compare September 8, 2025 22:37
@dilbertKocik
Copy link
Contributor Author

@rbri Can you tell me if this test update makes sense? I don't understand how so many tests could be affected. But this is what I get when I run the generator per the documentation.

@rbri
Copy link
Collaborator

rbri commented Sep 9, 2025

@dilbertKocik will have a look

the ci fails because your new comments are violating the spotless rules

Run './gradlew :rhino:spotlessApply' to fix these violations.

@rbri
Copy link
Collaborator

rbri commented Sep 9, 2025

@dilbertKocik looks like you have reverted the update of the linked test262 project as part of your commits. Therefore you get this many differences. Not sure how to sort this out - maybe the easiest way is to start a new branch from head and copy your changed files and make a new pr out of this

image

@dilbertKocik dilbertKocik force-pushed the template-literal-object branch from 0c1c5c5 to 2734efd Compare September 9, 2025 17:52
@dilbertKocik
Copy link
Contributor Author

@rbri Thanks for the help. Starting from head and cherry picking was great advice.

Copy link
Collaborator

@gbrail gbrail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

I'd really like to see the actual "concat" logic not be duplicated between interpreted and compiled mode -- see the comments. Thanks!

lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop - 1]);
}

String rhsString = ScriptRuntime.toString(rhs);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below, but this is an opportunity to consolidate the "concat" logic into one call into ScriptRuntime rather than duplicating it in two places.

return ScriptRuntime.add(val1, val2, cx);
}

public static Object concat(Object lhs, Object rhs) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please put this function in "ScriptRuntime" and then use it in both the interpreter code (see above) and in the bytecode? That way we only have the logic in here once. Yes, it's only three lines of code but it still means that we can put them together in the same place to change them in the future if we want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea. Thanks for the suggestion.

@dilbertKocik dilbertKocik requested a review from gbrail September 12, 2025 16:03
@rbri rbri merged commit d6d38b3 into mozilla:master Sep 15, 2025
3 checks passed
@rbri
Copy link
Collaborator

rbri commented Sep 15, 2025

@dilbertKocik thanks a lot, hope you provide more geat stuff like this in the future

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ES2015 Issue with Template Literal Object > string substitution

4 participants