From 2d8b2328fd402456590ae6cf1e6468c6a39f14de Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Sun, 17 Sep 2023 09:04:14 +1200 Subject: [PATCH] LibJS: Syntax error for a unary expression followed by exponentiation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change makes LibJS correctly report a syntax error when a unary expression is followed by exponentiation, as the spec requires. Apparently this is due to that expression being ambiguous ordering. Strangely this check does not seem to apply in the same way for '++' and '--' for reasons that I don't fully understand. For example ``` let x = 5; ++x ** 2 ``` Since `--5` and `++5` on it's own results in a syntax error anyway, it seems we do not need to perform this exponentiation check in those places. Diff Tests: +6 ✅ -6 ❌ --- Userland/Libraries/LibJS/Parser.cpp | 14 +++++++++ .../LibJS/Tests/exponentiation-basic.js | 29 +++++++++++++++++-- ...-unary-expression-before-exponentiation.js | 9 ++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/syntax/syntax-error-unary-expression-before-exponentiation.js diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index ac8403ceac..21b8987992 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -1805,6 +1805,13 @@ NonnullRefPtr Parser::parse_unary_prefixed_expression() auto rule_start = push_start(); auto precedence = g_operator_precedence.get_unary(m_state.current_token.type()); auto associativity = operator_associativity(m_state.current_token.type()); + + auto verify_next_token_is_not_exponentiation = [this]() { + auto lookahead_token = next_token(); + if (lookahead_token.type() == TokenType::DoubleAsterisk) + syntax_error("Unary operator must not be used before exponentiation expression without brackets"); + }; + switch (m_state.current_token.type()) { case TokenType::PlusPlus: { consume(); @@ -1838,27 +1845,34 @@ NonnullRefPtr Parser::parse_unary_prefixed_expression() } case TokenType::ExclamationMark: consume(); + verify_next_token_is_not_exponentiation(); return create_ast_node({ m_source_code, rule_start.position(), position() }, UnaryOp::Not, parse_expression(precedence, associativity)); case TokenType::Tilde: consume(); + verify_next_token_is_not_exponentiation(); return create_ast_node({ m_source_code, rule_start.position(), position() }, UnaryOp::BitwiseNot, parse_expression(precedence, associativity)); case TokenType::Plus: consume(); + verify_next_token_is_not_exponentiation(); return create_ast_node({ m_source_code, rule_start.position(), position() }, UnaryOp::Plus, parse_expression(precedence, associativity)); case TokenType::Minus: consume(); + verify_next_token_is_not_exponentiation(); return create_ast_node({ m_source_code, rule_start.position(), position() }, UnaryOp::Minus, parse_expression(precedence, associativity)); case TokenType::Typeof: consume(); + verify_next_token_is_not_exponentiation(); return create_ast_node({ m_source_code, rule_start.position(), position() }, UnaryOp::Typeof, parse_expression(precedence, associativity)); case TokenType::Void: consume(); + verify_next_token_is_not_exponentiation(); // FIXME: This check is really hiding the fact that we don't deal with different expressions correctly. if (match(TokenType::Yield) && m_state.in_generator_function_context) syntax_error("'yield' is not an identifier in generator function context"); return create_ast_node({ m_source_code, rule_start.position(), position() }, UnaryOp::Void, parse_expression(precedence, associativity)); case TokenType::Delete: { consume(); + verify_next_token_is_not_exponentiation(); auto rhs_start = position(); auto rhs = parse_expression(precedence, associativity); if (is(*rhs) && m_state.strict_mode) { diff --git a/Userland/Libraries/LibJS/Tests/exponentiation-basic.js b/Userland/Libraries/LibJS/Tests/exponentiation-basic.js index ccb0cd0c82..cba52519b2 100644 --- a/Userland/Libraries/LibJS/Tests/exponentiation-basic.js +++ b/Userland/Libraries/LibJS/Tests/exponentiation-basic.js @@ -14,8 +14,26 @@ test("exponentiation with negatives", () => { expect(2 ** -3).toBe(0.125); expect((-2) ** 3).toBe(-8); - // FIXME: This should fail :) - // expect("-2 ** 3").not.toEval(); + expect("-2 ** 3").not.toEval(); +}); + +test("exponentiation with PlusPlus and MinusMinus", () => { + let value = 5; + // prettier-ignore + expect(++value ** 2).toBe(36); + + value = 5; + expect((++value) ** 2).toBe(36); + + value = 5; + // prettier-ignore + expect(--value ** 2).toBe(16); + + value = 5; + expect((--value) ** 2).toBe(16); + + expect("++5 ** 2").not.toEval(); + expect("--5 ** 2").not.toEval(); }); test("exponentiation with non-numeric primitives", () => { @@ -50,3 +68,10 @@ test("exponentiation with infinities", () => { expect((-Infinity) ** 0).toBe(1); expect((-Infinity) ** 1).toBe(-Infinity); }); + +test("unary expression before exponentiation with brackets", () => { + expect((!1) ** 2).toBe(0); + expect((~5) ** 2).toBe(36); + expect((+5) ** 2).toBe(25); + expect((-5) ** 2).toBe(25); +}); diff --git a/Userland/Libraries/LibJS/Tests/syntax/syntax-error-unary-expression-before-exponentiation.js b/Userland/Libraries/LibJS/Tests/syntax/syntax-error-unary-expression-before-exponentiation.js new file mode 100644 index 0000000000..2e59b189cf --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/syntax/syntax-error-unary-expression-before-exponentiation.js @@ -0,0 +1,9 @@ +test("syntax error for an unary expression before exponentiation", () => { + expect(`!5 ** 2`).not.toEval(); + expect(`~5 ** 2`).not.toEval(); + expect(`+5 ** 2`).not.toEval(); + expect(`-5 ** 2`).not.toEval(); + expect(`typeof 5 ** 2`).not.toEval(); + expect(`void 5 ** 2`).not.toEval(); + expect(`delete 5 ** 2`).not.toEval(); +});