diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 09bdbe2ff7..6cf93920a8 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1331,6 +1331,8 @@ void BindingPattern::dump(int indent) const entry.alias.get>()->dump(indent + 3); } else if (entry.alias.has>()) { entry.alias.get>()->dump(indent + 3); + } else if (entry.alias.has>()) { + entry.alias.get>()->dump(indent + 3); } else { print_indent(indent + 3); outln(""); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index c9aeb8bfaf..e6954a3666 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -27,6 +27,7 @@ namespace JS { class VariableDeclaration; class FunctionDeclaration; class Identifier; +class MemberExpression; enum class FunctionKind { Generator, @@ -319,7 +320,7 @@ struct BindingPattern : RefCounted { struct BindingEntry { // If this entry represents a BindingElement, then name will be Empty Variant, NonnullRefPtr, Empty> name {}; - Variant, NonnullRefPtr, Empty> alias {}; + Variant, NonnullRefPtr, NonnullRefPtr, Empty> alias {}; RefPtr initializer {}; bool is_rest { false }; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 2fd58b5346..4c4d145a72 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -778,6 +778,9 @@ static void generate_array_binding_pattern_bytecode(Bytecode::Generator& generat auto target_reg = generator.allocate_register(); generator.emit(target_reg); generate_binding_pattern_bytecode(generator, pattern, target_reg); + }, + [&](NonnullRefPtr const&) { + TODO(); }); }; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index b39f118c2c..c18248c09f 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, Stephan Unverwerth * Copyright (c) 2020-2021, Linus Groh + * Copyright (c) 2021, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -1545,6 +1546,36 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr Parser::synthesize_binding_pattern(Expression const& expression) +{ + VERIFY(is(expression) || is(expression)); + // Clear any syntax error that has occurred in the range that 'expression' spans. + m_state.errors.remove_all_matching([range = expression.source_range()](auto const& error) { + return error.position.has_value() && range.contains(*error.position); + }); + // Make a parser and parse the source for this expression as a binding pattern. + auto source = m_state.lexer.source().substring_view(expression.source_range().start.offset - 2, expression.source_range().end.offset - expression.source_range().start.offset); + Lexer lexer { source, m_state.lexer.filename(), expression.source_range().start.line, expression.source_range().start.column }; + Parser parser { lexer }; + + parser.m_state.strict_mode = m_state.strict_mode; + parser.m_state.allow_super_property_lookup = m_state.allow_super_property_lookup; + parser.m_state.allow_super_constructor_call = m_state.allow_super_constructor_call; + parser.m_state.in_function_context = m_state.in_function_context; + parser.m_state.in_formal_parameter_context = m_state.in_formal_parameter_context; + parser.m_state.in_generator_function_context = m_state.in_generator_function_context; + parser.m_state.in_arrow_function_context = m_state.in_arrow_function_context; + parser.m_state.in_break_context = m_state.in_break_context; + parser.m_state.in_continue_context = m_state.in_continue_context; + parser.m_state.string_legacy_octal_escape_sequence_in_scope = m_state.string_legacy_octal_escape_sequence_in_scope; + parser.m_state.in_class_field_initializer = m_state.in_class_field_initializer; + + auto result = parser.parse_binding_pattern(AllowDuplicates::Yes, AllowMemberExpressions::Yes); + if (parser.has_errors()) + m_state.errors.extend(parser.errors()); + return result; +} + NonnullRefPtr Parser::parse_assignment_expression(AssignmentOp assignment_op, NonnullRefPtr lhs, int min_precedence, Associativity associativity) { auto rule_start = push_start(); @@ -1567,33 +1598,6 @@ NonnullRefPtr Parser::parse_assignment_expression(Assignme consume(); if (assignment_op == AssignmentOp::Assignment) { - auto synthesize_binding_pattern = [this](Expression const& expression) -> RefPtr { - // Clear any syntax error that has occurred in the range that 'expression' spans. - m_state.errors.remove_all_matching([range = expression.source_range()](auto const& error) { - return error.position.has_value() && range.contains(*error.position); - }); - // Make a parser and parse the source for this expression as a binding pattern. - auto source = m_state.lexer.source().substring_view(expression.source_range().start.offset - 2, expression.source_range().end.offset - expression.source_range().start.offset); - Lexer lexer { source, m_state.lexer.filename(), expression.source_range().start.line, expression.source_range().start.column }; - Parser parser { lexer }; - - parser.m_state.strict_mode = m_state.strict_mode; - parser.m_state.allow_super_property_lookup = m_state.allow_super_property_lookup; - parser.m_state.allow_super_constructor_call = m_state.allow_super_constructor_call; - parser.m_state.in_function_context = m_state.in_function_context; - parser.m_state.in_formal_parameter_context = m_state.in_formal_parameter_context; - parser.m_state.in_generator_function_context = m_state.in_generator_function_context; - parser.m_state.in_arrow_function_context = m_state.in_arrow_function_context; - parser.m_state.in_break_context = m_state.in_break_context; - parser.m_state.in_continue_context = m_state.in_continue_context; - parser.m_state.string_legacy_octal_escape_sequence_in_scope = m_state.string_legacy_octal_escape_sequence_in_scope; - parser.m_state.in_class_field_initializer = m_state.in_class_field_initializer; - - auto result = parser.parse_binding_pattern(); - if (parser.has_errors()) - m_state.errors.extend(parser.errors()); - return result; - }; if (is(*lhs) || is(*lhs)) { auto binding_pattern = synthesize_binding_pattern(*lhs); if (binding_pattern) { @@ -1919,7 +1923,7 @@ Vector Parser::parse_formal_parameters(int& function_le Vector parameters; auto consume_identifier_or_binding_pattern = [&]() -> Variant> { - if (auto pattern = parse_binding_pattern(true)) + if (auto pattern = parse_binding_pattern(AllowDuplicates::No, AllowMemberExpressions::Yes)) return pattern.release_nonnull(); auto token = consume_identifier(); @@ -2001,7 +2005,7 @@ Vector Parser::parse_formal_parameters(int& function_le static constexpr AK::Array s_reserved_words = { "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with" }; -RefPtr Parser::parse_binding_pattern(bool strict_checks) +RefPtr Parser::parse_binding_pattern(Parser::AllowDuplicates allow_duplicates, Parser::AllowMemberExpressions allow_member_expressions) { auto rule_start = push_start(); @@ -2040,17 +2044,36 @@ RefPtr Parser::parse_binding_pattern(bool strict_checks) RefPtr initializer = {}; if (is_object) { - if (match_identifier_name()) { - name = create_ast_node( - { m_state.current_token.filename(), rule_start.position(), position() }, - consume().value()); + bool needs_alias = false; + if (allow_member_expressions == AllowMemberExpressions::Yes && is_rest) { + auto expression_position = position(); + auto expression = parse_expression(2, Associativity::Right, { TokenType::Equals }); + if (is(*expression)) + alias = static_ptr_cast(expression); + else if (is(*expression)) + name = static_ptr_cast(expression); + else + syntax_error("Invalid destructuring assignment target", expression_position); + } else if (match_identifier_name() || match(TokenType::StringLiteral) || match(TokenType::NumericLiteral)) { + if (match(TokenType::StringLiteral) || match(TokenType::NumericLiteral)) + needs_alias = true; + + if (match(TokenType::StringLiteral)) { + auto token = consume(TokenType::StringLiteral); + auto string_literal = parse_string_literal(token); + + name = create_ast_node( + { m_state.current_token.filename(), rule_start.position(), position() }, + string_literal->value()); + } else { + name = create_ast_node( + { m_state.current_token.filename(), rule_start.position(), position() }, + consume().value()); + } } else if (match(TokenType::BracketOpen)) { consume(); auto expression = parse_expression(0); - if (strict_checks && m_state.in_generator_function_context && is(*expression)) - syntax_error("Yield expression not allowed in formal parameter within generator context"); - name = move(expression); consume(TokenType::BracketClose); } else { @@ -2060,45 +2083,69 @@ RefPtr Parser::parse_binding_pattern(bool strict_checks) if (!is_rest && match(TokenType::Colon)) { consume(); - if (match(TokenType::CurlyOpen) || match(TokenType::BracketOpen)) { - auto binding_pattern = parse_binding_pattern(strict_checks); + if (allow_member_expressions == AllowMemberExpressions::Yes) { + auto expression_position = position(); + auto expression = parse_expression(2, Associativity::Right, { TokenType::Equals }); + if (is(*expression) || is(*expression)) { + if (auto synthesized_binding_pattern = synthesize_binding_pattern(*expression)) + alias = synthesized_binding_pattern.release_nonnull(); + else + syntax_error("Invalid destructuring assignment target", expression_position); + } else if (is(*expression)) { + alias = static_ptr_cast(expression); + } else if (is(*expression)) { + alias = static_ptr_cast(expression); + } else { + syntax_error("Invalid destructuring assignment target", expression_position); + } + } else if (match(TokenType::CurlyOpen) || match(TokenType::BracketOpen)) { + auto binding_pattern = parse_binding_pattern(allow_duplicates, allow_member_expressions); if (!binding_pattern) return {}; alias = binding_pattern.release_nonnull(); } else if (match_identifier_name()) { alias = create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, - consume_identifier().value()); - if (strict_checks && match(TokenType::BracketOpen)) - syntax_error("Illegal property in declaration context"); + consume().value()); + } else { expected("identifier or binding pattern"); return {}; } + } else if (needs_alias) { + expected("alias for string or numeric literal name"); + return {}; } } else { - if (match_identifier_name()) { - // BindingElement must always have an Empty name field - auto identifier_name = consume_identifier().value(); - alias = create_ast_node( - { m_state.current_token.filename(), rule_start.position(), position() }, - identifier_name); + if (allow_member_expressions == AllowMemberExpressions::Yes) { + auto expression_position = position(); + auto expression = parse_expression(2, Associativity::Right, { TokenType::Equals }); - if (strict_checks) { - for (auto& entry : entries) { - if (entry.alias.has>()) { - if (entry.alias.get>()->string() == identifier_name) - syntax_error("Duplicate parameter names in bindings"); - } - } + if (is(*expression) || is(*expression)) { + if (auto synthesized_binding_pattern = synthesize_binding_pattern(*expression)) + alias = synthesized_binding_pattern.release_nonnull(); + else + syntax_error("Invalid destructuring assignment target", expression_position); + } else if (is(*expression)) { + alias = static_ptr_cast(expression); + } else if (is(*expression)) { + alias = static_ptr_cast(expression); + } else { + syntax_error("Invalid destructuring assignment target", expression_position); } } else if (match(TokenType::BracketOpen) || match(TokenType::CurlyOpen)) { - auto pattern = parse_binding_pattern(strict_checks); + auto pattern = parse_binding_pattern(allow_duplicates, allow_member_expressions); if (!pattern) { expected("binding pattern"); return {}; } alias = pattern.release_nonnull(); + } else if (match_identifier_name()) { + // BindingElement must always have an Empty name field + auto identifier_name = consume_identifier().value(); + alias = create_ast_node( + { m_state.current_token.filename(), rule_start.position(), position() }, + identifier_name); } else { expected("identifier or binding pattern"); return {}; @@ -2118,8 +2165,6 @@ RefPtr Parser::parse_binding_pattern(bool strict_checks) expected("initialization expression"); return {}; } - if (strict_checks && is(*initializer)) - syntax_error("Yield expression is not allow in formal parameter"); } entries.append(BindingPattern::BindingEntry { move(name), move(alias), move(initializer), is_rest }); @@ -2145,7 +2190,7 @@ RefPtr Parser::parse_binding_pattern(bool strict_checks) Vector bound_names; pattern->for_each_bound_name([&](auto& name) { - if (strict_checks) { + if (allow_duplicates == AllowDuplicates::No) { if (bound_names.contains_slow(name)) syntax_error("Duplicate parameter names in bindings"); bound_names.append(name); @@ -2231,25 +2276,13 @@ NonnullRefPtr Parser::parse_variable_declaration(bool for_l check_declarations(declaration); } } - } else if (auto pattern = parse_binding_pattern(declaration_kind != DeclarationKind::Var)) { + } else if (auto pattern = parse_binding_pattern(declaration_kind != DeclarationKind::Var ? AllowDuplicates::No : AllowDuplicates::Yes, AllowMemberExpressions::No)) { target = pattern.release_nonnull(); if ((declaration_kind == DeclarationKind::Let || declaration_kind == DeclarationKind::Const)) { - target.get>()->for_each_bound_name([this, &declarations](auto& name) { + target.get>()->for_each_bound_name([this](auto& name) { if (name == "let"sv) syntax_error("Lexical binding may not be called 'let'"); - - // FIXME: Again we do not check everything here see for example the parameter check above. - // However a more sustainable solution should be used since these checks are now spread over multiple sites. - - for (auto& declaration : declarations) { - declaration.target().visit( - [&](NonnullRefPtr const& identifier) { - if (identifier->string() == name) - syntax_error(String::formatted("Identifier '{}' has already been declared", name)); - }, - [&](auto const&) {}); - } }); } } else if (!m_state.in_generator_function_context && match(TokenType::Yield)) { @@ -2612,7 +2645,7 @@ NonnullRefPtr Parser::parse_catch_clause() if (match_identifier_name() && (!match(TokenType::Yield) || !m_state.in_generator_function_context)) parameter = consume().value(); else - pattern_parameter = parse_binding_pattern(true); + pattern_parameter = parse_binding_pattern(AllowDuplicates::No, AllowMemberExpressions::No); consume(TokenType::ParenClose); } diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index d961353664..22be8a2e42 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -42,7 +42,18 @@ public: template NonnullRefPtr parse_function_node(u8 parse_options = FunctionNodeParseOptions::CheckForFunctionAndName); Vector parse_formal_parameters(int& function_length, u8 parse_options = 0); - RefPtr parse_binding_pattern(bool strict_checks = false); + + enum class AllowDuplicates { + Yes, + No + }; + + enum class AllowMemberExpressions { + Yes, + No + }; + + RefPtr parse_binding_pattern(AllowDuplicates is_var_declaration = AllowDuplicates::No, AllowMemberExpressions allow_member_expressions = AllowMemberExpressions::No); struct PrimaryExpressionParseResult { NonnullRefPtr result; @@ -179,6 +190,8 @@ private: void discard_saved_state(); Position position() const; + RefPtr synthesize_binding_pattern(Expression const& expression); + Token next_token(); void check_identifier_name_for_assignment_validity(StringView, bool force_strict = false); diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index b98969b86c..ec114f2a47 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -197,6 +197,20 @@ void VM::assign(const NonnullRefPtr& target, Value value, Global auto& entry = binding.entries[i]; + Optional assignment_target; + entry.alias.visit( + [&](Empty) {}, + [&](NonnullRefPtr const&) { + // FIXME: We need to get the reference but bindings are broken so this doesn't work yet. + }, + [&](NonnullRefPtr const&) {}, + [&](NonnullRefPtr const& member_expression) { + assignment_target = member_expression->to_reference(interpreter(), global_object); + }); + + if (exception()) + return; + if (entry.is_rest) { VERIFY(i == binding.entries.size() - 1); @@ -254,8 +268,15 @@ void VM::assign(const NonnullRefPtr& target, Value value, Global }, [&](NonnullRefPtr const& pattern) { assign(pattern, value, global_object, first_assignment, specific_scope); + }, + [&](NonnullRefPtr const&) { + VERIFY(assignment_target.has_value()); + assignment_target->put_value(global_object, value); }); + if (exception()) + return; + if (entry.is_rest) break; } @@ -269,10 +290,21 @@ void VM::assign(const NonnullRefPtr& target, Value value, Global VERIFY(!property.is_elision()); PropertyName assignment_name; + Optional assignment_target; + JS::Value value_to_assign; if (property.is_rest) { - VERIFY(property.name.has>()); - assignment_name = property.name.get>()->string(); + if (auto identifier_ptr = property.name.get_pointer>()) + assignment_name = (*identifier_ptr)->string(); + property.alias.visit( + [&](Empty) {}, + [&](NonnullRefPtr const&) { + // FIXME: We need to get the reference but bindings are broken so this doesn't work yet. + }, + [&](NonnullRefPtr const&) {}, + [&](NonnullRefPtr const& member_expression) { + assignment_target = member_expression->to_reference(interpreter(), global_object); + }); auto* rest_object = Object::create(global_object, global_object.object_prototype()); for (auto& object_property : object->shape().property_table()) { @@ -298,6 +330,15 @@ void VM::assign(const NonnullRefPtr& target, Value value, Global return; assignment_name = result.to_property_key(global_object); }); + property.alias.visit( + [&](Empty) {}, + [&](NonnullRefPtr const&) { + // FIXME: We need to get the reference but bindings are broken so this doesn't work yet. + }, + [&](NonnullRefPtr const&) {}, + [&](NonnullRefPtr const& member_expression) { + assignment_target = member_expression->to_reference(interpreter(), global_object); + }); if (exception()) break; @@ -327,8 +368,15 @@ void VM::assign(const NonnullRefPtr& target, Value value, Global [&](NonnullRefPtr const& pattern) { VERIFY(!property.is_rest); assign(pattern, value_to_assign, global_object, first_assignment, specific_scope); + }, + [&](NonnullRefPtr const&) { + VERIFY(assignment_target.has_value()); + assignment_target->put_value(global_object, value_to_assign); }); + if (exception()) + return; + if (property.is_rest) break; }