diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index e84ffb8449..28177e3797 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -3348,7 +3348,7 @@ ThrowCompletionOr OptionalChain::to_reference_ [&](Call const& call) -> NonnullRefPtr { return CallExpression::create(source_range(), create_ast_node(source_range(), base_reference, base), - call.arguments); + call.arguments, InvocationStyleEnum::Parenthesized, InsideParenthesesEnum::NotInsideParentheses); }, [&](ComputedReference const& ref) -> NonnullRefPtr { return create_ast_node(source_range(), @@ -4965,14 +4965,14 @@ DeprecatedString SourceRange::filename() const return code->filename().to_deprecated_string(); } -NonnullRefPtr CallExpression::create(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments) +NonnullRefPtr CallExpression::create(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens) { - return ASTNodeWithTailArray::create(arguments.size(), move(source_range), move(callee), arguments); + return ASTNodeWithTailArray::create(arguments.size(), move(source_range), move(callee), arguments, invocation_style, inside_parens); } -NonnullRefPtr NewExpression::create(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments) +NonnullRefPtr NewExpression::create(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens) { - return ASTNodeWithTailArray::create(arguments.size(), move(source_range), move(callee), arguments); + return ASTNodeWithTailArray::create(arguments.size(), move(source_range), move(callee), arguments, invocation_style, inside_parens); } } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index c4269f8772..adfff4ced9 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1507,13 +1507,26 @@ struct CallExpressionArgument { bool is_spread; }; +enum InvocationStyleEnum { + Parenthesized, + NotParenthesized, +}; + +enum InsideParenthesesEnum { + InsideParentheses, + NotInsideParentheses, +}; + class CallExpression : public ASTNodeWithTailArray { friend class ASTNodeWithTailArray; + InvocationStyleEnum m_invocation_style; + InsideParenthesesEnum m_inside_parentheses; + public: using Argument = CallExpressionArgument; - static NonnullRefPtr create(SourceRange, NonnullRefPtr callee, ReadonlySpan arguments); + static NonnullRefPtr create(SourceRange, NonnullRefPtr callee, ReadonlySpan arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens); virtual Completion execute(Interpreter&) const override; virtual void dump(int indent) const override; @@ -1523,9 +1536,15 @@ public: ReadonlySpan arguments() const { return tail_span(); } + bool is_parenthesized() const { return m_invocation_style == InvocationStyleEnum::Parenthesized; } + bool is_inside_parens() const { return m_inside_parentheses == InsideParenthesesEnum::InsideParentheses; } + void set_inside_parens() { m_inside_parentheses = InsideParenthesesEnum::InsideParentheses; } + protected: - CallExpression(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments) + CallExpression(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens = InsideParenthesesEnum::NotInsideParentheses) : ASTNodeWithTailArray(move(source_range), arguments) + , m_invocation_style(invocation_style) + , m_inside_parentheses(inside_parens) , m_callee(move(callee)) { } @@ -1550,15 +1569,15 @@ class NewExpression final : public CallExpression { friend class ASTNodeWithTailArray; public: - static NonnullRefPtr create(SourceRange, NonnullRefPtr callee, ReadonlySpan arguments); + static NonnullRefPtr create(SourceRange, NonnullRefPtr callee, ReadonlySpan arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens); virtual Completion execute(Interpreter&) const override; virtual bool is_new_expression() const override { return true; } private: - NewExpression(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments) - : CallExpression(move(source_range), move(callee), arguments) + NewExpression(SourceRange source_range, NonnullRefPtr callee, ReadonlySpan arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens) + : CallExpression(move(source_range), move(callee), arguments, invocation_style, inside_parens) { } }; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 4254ccfaf1..a03a560a59 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -1419,7 +1419,10 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() } auto expression = parse_expression(0); consume(TokenType::ParenClose); - if (is(*expression)) { + if (is(*expression)) { + auto& new_expression = static_cast(*static_cast>(expression)); + new_expression.set_inside_parens(); + } else if (is(*expression)) { auto& function = static_cast(*expression); if (function.kind() == FunctionKind::Generator && function.name() == "yield"sv) syntax_error("function is not allowed to be called 'yield' in this context", function.source_range().start); @@ -2221,15 +2224,18 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr(lhs.ptr())) { - syntax_error("'new' cannot be used with optional chaining", position()); - consume(); - return lhs; + case TokenType::QuestionMarkPeriod: { + auto const* lhs_expression = lhs.ptr(); + if (is(lhs_expression)) { + auto const& new_expression = static_cast(*lhs_expression); + if (!new_expression.is_parenthesized() && !new_expression.is_inside_parens()) { + syntax_error("'new' cannot be used with optional chaining", position()); + consume(); + return lhs; + } } return parse_optional_chain(move(lhs)); + } default: expected("secondary expression"); consume(); @@ -2380,7 +2386,7 @@ NonnullRefPtr Parser::parse_call_expression(NonnullRefPtr(*lhs)) return create_ast_node({ m_source_code, rule_start.position(), position() }, move(arguments)); - return CallExpression::create({ m_source_code, rule_start.position(), position() }, move(lhs), arguments.span()); + return CallExpression::create({ m_source_code, rule_start.position(), position() }, move(lhs), arguments.span(), InvocationStyleEnum::Parenthesized, InsideParenthesesEnum::NotInsideParentheses); } NonnullRefPtr Parser::parse_new_expression() @@ -2394,8 +2400,10 @@ NonnullRefPtr Parser::parse_new_expression() Vector arguments; - if (match(TokenType::ParenOpen)) { - consume(TokenType::ParenOpen); + auto is_parenthesized = match(TokenType::ParenOpen); + + if (is_parenthesized) { + consume(); while (match_expression() || match(TokenType::TripleDot)) { if (match(TokenType::TripleDot)) { consume(); @@ -2410,7 +2418,9 @@ NonnullRefPtr Parser::parse_new_expression() consume(TokenType::ParenClose); } - return NewExpression::create({ m_source_code, rule_start.position(), position() }, move(callee), move(arguments)); + InvocationStyleEnum invocation_style = is_parenthesized ? InvocationStyleEnum::Parenthesized : InvocationStyleEnum::NotParenthesized; + + return NewExpression::create({ m_source_code, rule_start.position(), position() }, move(callee), move(arguments), invocation_style, InsideParenthesesEnum::NotInsideParentheses); } NonnullRefPtr Parser::parse_yield_expression() @@ -4846,5 +4856,4 @@ Parser::ForbiddenTokens Parser::ForbiddenTokens::forbid(std::initializer_list Parser::parse_function_node(u16, Optional const&); template NonnullRefPtr Parser::parse_function_node(u16, Optional const&); - }