diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 8e4e322b60..e183a9115e 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -436,7 +436,9 @@ Bytecode::CodeGenerationErrorOr UnaryExpression::generate_bytecode(Bytecod if (m_op == UnaryOp::Delete) return generator.emit_delete_reference(m_lhs); - TRY(m_lhs->generate_bytecode(generator)); + // Typeof needs some special handling for when the LHS is an Identifier. Namely, it shouldn't throw on unresolvable references, but instead return "undefined". + if (m_op != UnaryOp::Typeof) + TRY(m_lhs->generate_bytecode(generator)); switch (m_op) { case UnaryOp::BitwiseNot: @@ -452,6 +454,13 @@ Bytecode::CodeGenerationErrorOr UnaryExpression::generate_bytecode(Bytecod generator.emit(); break; case UnaryOp::Typeof: + if (is(*m_lhs)) { + auto& identifier = static_cast(*m_lhs); + generator.emit(generator.intern_identifier(identifier.string())); + break; + } + + TRY(m_lhs->generate_bytecode(generator)); generator.emit(); break; case UnaryOp::Void: diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index c56e25c796..d5accd5586 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -82,6 +82,7 @@ O(Sub) \ O(Throw) \ O(Typeof) \ + O(TypeofVariable) \ O(UnaryMinus) \ O(UnaryPlus) \ O(UnsignedRightShift) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 1623a31063..7393c62995 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -764,6 +764,29 @@ ThrowCompletionOr NewClass::execute_impl(Bytecode::Interpreter& interprete return {}; } +// 13.5.3.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-typeof-operator-runtime-semantics-evaluation +ThrowCompletionOr TypeofVariable::execute_impl(Bytecode::Interpreter& interpreter) const +{ + // 1. Let val be the result of evaluating UnaryExpression. + auto const& string = interpreter.current_executable().get_identifier(m_identifier); + auto reference = TRY(interpreter.vm().resolve_binding(string)); + + // 2. If val is a Reference Record, then + // a. If IsUnresolvableReference(val) is true, return "undefined". + if (reference.is_unresolvable()) { + interpreter.accumulator() = js_string(interpreter.vm(), "undefined"sv); + return {}; + } + + // 3. Set val to ? GetValue(val). + auto value = TRY(reference.get_value(interpreter.global_object())); + + // 4. NOTE: This step is replaced in section B.3.6.3. + // 5. Return a String according to Table 41. + interpreter.accumulator() = js_string(interpreter.vm(), value.typeof()); + return {}; +} + String Load::to_string_impl(Bytecode::Executable const&) const { return String::formatted("Load {}", m_src); @@ -1076,4 +1099,9 @@ String GetNewTarget::to_string_impl(Bytecode::Executable const&) const return "GetNewTarget"sv; } +String TypeofVariable::to_string_impl(Bytecode::Executable const& executable) const +{ + return String::formatted("TypeofVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 6f2f80bf80..293ee76889 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -910,6 +910,22 @@ public: void replace_references_impl(BasicBlock const&, BasicBlock const&) { } }; +class TypeofVariable final : public Instruction { +public: + explicit TypeofVariable(IdentifierTableIndex identifier) + : Instruction(Type::TypeofVariable) + , m_identifier(identifier) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + +private: + IdentifierTableIndex m_identifier; +}; + } namespace JS::Bytecode {