diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 519e2d195f..6bdada2f54 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -865,7 +865,7 @@ struct ForInOfHeadState { VERIFY(declaration.declarations().first().target().has>()); lhs_reference = TRY(declaration.declarations().first().target().get>()->to_reference(interpreter, global_object)); } else { - VERIFY(is(*expression_lhs) || is(*expression_lhs)); + VERIFY(is(*expression_lhs) || is(*expression_lhs) || is(*expression_lhs)); auto& expression = static_cast(*expression_lhs); lhs_reference = TRY(expression.to_reference(interpreter, global_object)); } diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 51712bf3e9..8bb5439ded 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -3492,7 +3492,7 @@ NonnullRefPtr Parser::parse_for_in_of_statement(NonnullRefPtris_identifier() && !is(*lhs)) { + } else if (!lhs->is_identifier() && !is(*lhs) && !is(*lhs)) { bool valid = false; if (is(*lhs) || is(*lhs)) { auto synthesized_binding_pattern = synthesize_binding_pattern(static_cast(*lhs)); diff --git a/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js index d5e70845fc..6795c4baa8 100644 --- a/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js +++ b/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js @@ -67,8 +67,35 @@ test("allow binding patterns", () => { expect(counter).toBe(3); }); -test("allow member expression as variable", () => { - const f = {}; - for (f.a in "abc"); - expect(f.a).toBe("2"); +describe("special left hand sides", () => { + test("allow member expression as variable", () => { + const f = {}; + for (f.a in "abc"); + expect(f.a).toBe("2"); + }); + + test("allow member expression of function call", () => { + const b = {}; + function f() { + return b; + } + + for (f().a in "abc"); + + expect(f().a).toBe("2"); + expect(b.a).toBe("2"); + }); + + test("call function is allowed in parsing but fails in runtime", () => { + function f() { + expect().fail(); + } + + // Does not fail since it does not iterate + expect("for (f() in []);").toEvalTo(undefined); + + expect(() => { + eval("for (f() in [0]) { expect().fail() }"); + }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment"); + }); }); diff --git a/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js index 892894356c..29fcb5d2b9 100644 --- a/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js +++ b/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js @@ -112,8 +112,34 @@ test("allow binding patterns", () => { expect(counter).toBe(3); }); -test("allow member expression as variable", () => { - const f = {}; - for (f.a of "abc"); - expect(f.a).toBe("c"); +describe("special left hand sides", () => { + test("allow member expression as variable", () => { + const f = {}; + for (f.a of "abc"); + expect(f.a).toBe("c"); + }); + + test("allow member expression of function call", () => { + const b = {}; + function f() { + return b; + } + + for (f().a of "abc"); + + expect(f().a).toBe("c"); + }); + + test("call function is allowed in parsing but fails in runtime", () => { + function f() { + expect().fail(); + } + + // Does not fail since it does not iterate but prettier does not like it so we use eval. + expect("for (f() of []);").toEvalTo(undefined); + + expect(() => { + eval("for (f() of [0]) { expect().fail() }"); + }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment"); + }); });