diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index fec8aeba07..b40b87e1f0 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -1655,6 +1655,28 @@ Value MemberExpression::execute(Interpreter& interpreter, GlobalObject& global_o return object_result->get(property_name).value_or(js_undefined()); } +void MetaProperty::dump(int indent) const +{ + String name; + if (m_type == MetaProperty::Type::NewTarget) + name = "new.target"; + else if (m_type == MetaProperty::Type::ImportMeta) + name = "import.meta"; + else + ASSERT_NOT_REACHED(); + print_indent(indent); + printf("%s %s\n", class_name(), name.characters()); +} + +Value MetaProperty::execute(Interpreter& interpreter, GlobalObject&) const +{ + if (m_type == MetaProperty::Type::NewTarget) + return interpreter.vm().get_new_target().value_or(js_undefined()); + if (m_type == MetaProperty::Type::ImportMeta) + TODO(); + ASSERT_NOT_REACHED(); +} + Value StringLiteral::execute(Interpreter& interpreter, GlobalObject&) const { return js_string(interpreter.heap(), m_value); diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 6de9d534b0..9f5a8deb61 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -1084,6 +1084,27 @@ private: bool m_computed { false }; }; +class MetaProperty final : public Expression { +public: + enum class Type { + NewTarget, + ImportMeta, + }; + + MetaProperty(Type type) + : m_type(type) + { + } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + virtual void dump(int indent) const override; + +private: + virtual const char* class_name() const override { return "MetaProperty"; } + + Type m_type; +}; + class ConditionalExpression final : public Expression { public: ConditionalExpression(NonnullRefPtr test, NonnullRefPtr consequent, NonnullRefPtr alternate) diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 79d47cf889..b04530ff8f 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -401,7 +401,7 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe bool is_strict = false; auto function_body_result = [&]() -> RefPtr { - TemporaryChange change(m_parser_state.m_in_function_context, true); + TemporaryChange change(m_parser_state.m_in_arrow_function_context, true); if (match(TokenType::CurlyOpen)) { // Parse a function body with statements return parse_block_statement(is_strict); @@ -454,6 +454,26 @@ RefPtr Parser::try_parse_labelled_statement() return statement; } +RefPtr Parser::try_parse_new_target_expression() +{ + save_state(); + ArmedScopeGuard state_rollback_guard = [&] { + load_state(); + }; + + consume(TokenType::New); + if (!match(TokenType::Period)) + return {}; + consume(); + if (!match(TokenType::Identifier)) + return {}; + if (consume().value() != "target") + return {}; + + state_rollback_guard.disarm(); + return create_ast_node(MetaProperty::Type::NewTarget); +} + NonnullRefPtr Parser::parse_class_declaration() { return create_ast_node(parse_class_expression(true)); @@ -593,9 +613,8 @@ NonnullRefPtr Parser::parse_primary_expression() consume(TokenType::ParenOpen); if (match(TokenType::ParenClose) || match(TokenType::Identifier) || match(TokenType::TripleDot)) { auto arrow_function_result = try_parse_arrow_function_expression(true); - if (!arrow_function_result.is_null()) { + if (!arrow_function_result.is_null()) return arrow_function_result.release_nonnull(); - } } auto expression = parse_expression(0); consume(TokenType::ParenClose); @@ -613,9 +632,8 @@ NonnullRefPtr Parser::parse_primary_expression() return create_ast_node(); case TokenType::Identifier: { auto arrow_function_result = try_parse_arrow_function_expression(false); - if (!arrow_function_result.is_null()) { + if (!arrow_function_result.is_null()) return arrow_function_result.release_nonnull(); - } return create_ast_node(consume().value()); } case TokenType::NumericLiteral: @@ -639,8 +657,16 @@ NonnullRefPtr Parser::parse_primary_expression() return parse_regexp_literal(); case TokenType::TemplateLiteralStart: return parse_template_literal(false); - case TokenType::New: + case TokenType::New: { + auto new_start = position(); + auto new_target_result = try_parse_new_target_expression(); + if (!new_target_result.is_null()) { + if (!m_parser_state.m_in_function_context) + syntax_error("'new.target' not allowed outside of a function", new_start); + return new_target_result.release_nonnull(); + } return parse_new_expression(); + } default: expected("primary expression"); consume(); @@ -1191,7 +1217,7 @@ NonnullRefPtr Parser::parse_new_expression() NonnullRefPtr Parser::parse_return_statement() { - if (!m_parser_state.m_in_function_context) + if (!m_parser_state.m_in_function_context && !m_parser_state.m_in_arrow_function_context) syntax_error("'return' not allowed outside of a function"); consume(TokenType::Return); diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 1d9d190ccf..ac719569cf 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -81,7 +81,6 @@ public: NonnullRefPtr parse_while_statement(); NonnullRefPtr parse_debugger_statement(); NonnullRefPtr parse_conditional_expression(NonnullRefPtr test); - NonnullRefPtr parse_expression(int min_precedence, Associativity associate = Associativity::Right, Vector forbidden = {}); NonnullRefPtr parse_primary_expression(); NonnullRefPtr parse_unary_prefixed_expression(); @@ -93,13 +92,15 @@ public: NonnullRefPtr parse_secondary_expression(NonnullRefPtr, int min_precedence, Associativity associate = Associativity::Right); NonnullRefPtr parse_call_expression(NonnullRefPtr); NonnullRefPtr parse_new_expression(); - RefPtr try_parse_arrow_function_expression(bool expect_parens); - RefPtr try_parse_labelled_statement(); NonnullRefPtr parse_class_declaration(); NonnullRefPtr parse_class_expression(bool expect_class_name); NonnullRefPtr parse_property_key(); NonnullRefPtr parse_assignment_expression(AssignmentOp, NonnullRefPtr lhs, int min_precedence, Associativity); + RefPtr try_parse_arrow_function_expression(bool expect_parens); + RefPtr try_parse_labelled_statement(); + RefPtr try_parse_new_target_expression(); + struct Position { size_t line; size_t column; @@ -181,6 +182,7 @@ private: bool m_allow_super_property_lookup { false }; bool m_allow_super_constructor_call { false }; bool m_in_function_context { false }; + bool m_in_arrow_function_context { false }; bool m_in_break_context { false }; bool m_in_continue_context { false }; bool m_string_legacy_octal_escape_sequence_in_scope { false }; diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp index c3575b8c48..92737a1cbb 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -105,6 +105,8 @@ LexicalEnvironment* ScriptFunction::create_environment() auto* environment = heap().allocate(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function); environment->set_home_object(home_object()); environment->set_current_function(*this); + if (m_is_arrow_function) + environment->set_new_target(m_parent_environment->new_target()); return environment; } diff --git a/Libraries/LibJS/Tests/functions/function-new-target.js b/Libraries/LibJS/Tests/functions/function-new-target.js new file mode 100644 index 0000000000..8b5667d3f0 --- /dev/null +++ b/Libraries/LibJS/Tests/functions/function-new-target.js @@ -0,0 +1,26 @@ +test("basic functionality", () => { + function foo() { + return new.target; + } + expect(foo()).toBeUndefined(); + expect(new foo()).toEqual(foo); + + function bar() { + const baz = () => new.target; + return baz(); + } + expect(bar()).toBeUndefined(); + expect(new bar()).toEqual(bar); + + class baz { + constructor() { + this.newTarget = new.target; + } + } + expect(new baz().newTarget).toEqual(baz); +}); + +// FIXME: This does not work as expected as toEval() places the code inside a function :| +test.skip("syntax error outside of function", () => { + expect("new.target").not.toEval(); +});