Ver Fonte

LibJS: Correctly handle parentheses and new Object

Parses `new Object()?.foo`, `(new Object)?.foo`
and shows syntax error on `new Object?.foo`
Malik Ammar Faisal há 2 anos atrás
pai
commit
5c913d9cc4

+ 5 - 5
Userland/Libraries/LibJS/AST.cpp

@@ -3348,7 +3348,7 @@ ThrowCompletionOr<OptionalChain::ReferenceAndValue> OptionalChain::to_reference_
             [&](Call const& call) -> NonnullRefPtr<Expression const> {
                 return CallExpression::create(source_range(),
                     create_ast_node<SyntheticReferenceExpression>(source_range(), base_reference, base),
-                    call.arguments);
+                    call.arguments, InvocationStyleEnum::Parenthesized, InsideParenthesesEnum::NotInsideParentheses);
             },
             [&](ComputedReference const& ref) -> NonnullRefPtr<Expression const> {
                 return create_ast_node<MemberExpression>(source_range(),
@@ -4965,14 +4965,14 @@ DeprecatedString SourceRange::filename() const
     return code->filename().to_deprecated_string();
 }
 
-NonnullRefPtr<CallExpression> CallExpression::create(SourceRange source_range, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments)
+NonnullRefPtr<CallExpression> CallExpression::create(SourceRange source_range, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens)
 {
-    return ASTNodeWithTailArray::create<CallExpression>(arguments.size(), move(source_range), move(callee), arguments);
+    return ASTNodeWithTailArray::create<CallExpression>(arguments.size(), move(source_range), move(callee), arguments, invocation_style, inside_parens);
 }
 
-NonnullRefPtr<NewExpression> NewExpression::create(SourceRange source_range, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments)
+NonnullRefPtr<NewExpression> NewExpression::create(SourceRange source_range, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens)
 {
-    return ASTNodeWithTailArray::create<NewExpression>(arguments.size(), move(source_range), move(callee), arguments);
+    return ASTNodeWithTailArray::create<NewExpression>(arguments.size(), move(source_range), move(callee), arguments, invocation_style, inside_parens);
 }
 
 }

+ 24 - 5
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<CallExpression, Expression, CallExpressionArgument> {
     friend class ASTNodeWithTailArray;
 
+    InvocationStyleEnum m_invocation_style;
+    InsideParenthesesEnum m_inside_parentheses;
+
 public:
     using Argument = CallExpressionArgument;
 
-    static NonnullRefPtr<CallExpression> create(SourceRange, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments);
+    static NonnullRefPtr<CallExpression> create(SourceRange, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> 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<Argument> 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<Expression const> callee, ReadonlySpan<Argument> arguments)
+    CallExpression(SourceRange source_range, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> 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<NewExpression> create(SourceRange, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments);
+    static NonnullRefPtr<NewExpression> create(SourceRange, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> 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<Expression const> callee, ReadonlySpan<Argument> arguments)
-        : CallExpression(move(source_range), move(callee), arguments)
+    NewExpression(SourceRange source_range, NonnullRefPtr<Expression const> callee, ReadonlySpan<Argument> arguments, InvocationStyleEnum invocation_style, InsideParenthesesEnum inside_parens)
+        : CallExpression(move(source_range), move(callee), arguments, invocation_style, inside_parens)
     {
     }
 };

+ 22 - 13
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<FunctionExpression>(*expression)) {
+        if (is<NewExpression>(*expression)) {
+            auto& new_expression = static_cast<NewExpression&>(*static_cast<NonnullRefPtr<Expression>>(expression));
+            new_expression.set_inside_parens();
+        } else if (is<FunctionExpression>(*expression)) {
             auto& function = static_cast<FunctionExpression const&>(*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<Expres
         return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity, forbidden);
     case TokenType::QuestionMark:
         return parse_conditional_expression(move(lhs), forbidden);
-    case TokenType::QuestionMarkPeriod:
-        // FIXME: This should allow `(new Foo)?.bar', but as our parser strips parenthesis,
-        //        we can't really tell if `lhs' was parenthesized at this point.
-        if (is<NewExpression>(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<NewExpression>(lhs_expression)) {
+            auto const& new_expression = static_cast<NewExpression const&>(*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<Expression const> Parser::parse_call_expression(NonnullRefPtr<Expr
     if (is<SuperExpression>(*lhs))
         return create_ast_node<SuperCall>({ 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<NewExpression const> Parser::parse_new_expression()
@@ -2394,8 +2400,10 @@ NonnullRefPtr<NewExpression const> Parser::parse_new_expression()
 
     Vector<CallExpression::Argument> 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<NewExpression const> 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<YieldExpression const> Parser::parse_yield_expression()
@@ -4846,5 +4856,4 @@ Parser::ForbiddenTokens Parser::ForbiddenTokens::forbid(std::initializer_list<To
 
 template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u16, Optional<Position> const&);
 template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u16, Optional<Position> const&);
-
 }