diff --git a/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Libraries/LibJS/Bytecode/ASTCodegen.cpp index e09d1ba30f2..e1e74e87327 100644 --- a/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -2469,10 +2469,24 @@ Bytecode::CodeGenerationErrorOr> TemplateLiteral::genera return dst; } +struct TagAndThisValue { + ScopedOperand tag; + ScopedOperand this_value; +}; + Bytecode::CodeGenerationErrorOr> TaggedTemplateLiteral::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto tag = TRY(m_tag->generate_bytecode(generator)).value(); + auto [tag, this_value] = TRY([&]() -> CodeGenerationErrorOr { + if (is(*m_tag)) { + auto& member_expression = static_cast(*m_tag); + auto base_and_value = TRY(get_base_and_value_from_member_expression(generator, member_expression)); + return TagAndThisValue { .tag = base_and_value.value, .this_value = base_and_value.base }; + } + + auto tag = TRY(m_tag->generate_bytecode(generator)).value(); + return TagAndThisValue { .tag = tag, .this_value = generator.add_constant(js_undefined()) }; + }()); // FIXME: Follow // 13.2.8.3 GetTemplateObject ( templateLiteral ), https://tc39.es/ecma262/#sec-gettemplateobject @@ -2539,7 +2553,7 @@ Bytecode::CodeGenerationErrorOr> TaggedTemplateLiteral:: generator.emit(arguments); auto dst = choose_dst(generator, preferred_dst); - generator.emit(Bytecode::Op::CallType::Call, dst, tag, generator.add_constant(js_undefined()), arguments); + generator.emit(Bytecode::Op::CallType::Call, dst, tag, this_value, arguments); return dst; } diff --git a/Libraries/LibJS/Tests/tagged-template-literals.js b/Libraries/LibJS/Tests/tagged-template-literals.js index 031b5852609..dea4c5ec2b0 100644 --- a/Libraries/LibJS/Tests/tagged-template-literals.js +++ b/Libraries/LibJS/Tests/tagged-template-literals.js @@ -164,7 +164,7 @@ describe("tagged template literal functionality", () => { expect(firstResult).toBe(secondResult); }); - test.xfail("this value of call comes from reference", () => { + test("this value of call comes from reference for non-computed, non-super property", () => { let thisValue = null; const obj = { func() { @@ -176,4 +176,106 @@ describe("tagged template literal functionality", () => { expect(thisValue).toBe(obj); }); + + test("this value of call comes from reference for computed, non-super property", () => { + let thisValue = null; + const obj = { + func() { + thisValue = this; + }, + }; + + obj["func"]``; + + expect(thisValue).toBe(obj); + }); + + test("this value of call comes from reference for private property", () => { + let thisValue = null; + const obj = new (class A { + #func = () => { + thisValue = this; + }; + + constructor() { + this.#func``; + } + })(); + + expect(thisValue).toBe(obj); + }); + + test("this value of call comes from reference for non-computed super call property", () => { + let thisValue = null; + + class A { + func() { + thisValue = this; + } + } + + const obj = new (class B extends A { + constructor() { + super().func``; + } + })(); + + expect(thisValue).toBe(obj); + }); + + test("this value of call comes from reference for computed super call property", () => { + let thisValue = null; + + class A { + func() { + thisValue = this; + } + } + + const obj = new (class B extends A { + constructor() { + super()["func"]``; + } + })(); + + expect(thisValue).toBe(obj); + }); + + test("this value of call comes from reference for non-computed super property", () => { + let thisValue = null; + + class A { + func() { + thisValue = this; + } + } + + const obj = new (class B extends A { + constructor() { + super(); + super.func``; + } + })(); + + expect(thisValue).toBe(obj); + }); + + test("this value of call comes from reference for computed super property", () => { + let thisValue = null; + + class A { + func() { + thisValue = this; + } + } + + const obj = new (class B extends A { + constructor() { + super(); + super["func"]``; + } + })(); + + expect(thisValue).toBe(obj); + }); });