mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 02:27:43 +00:00
LibJS: Bring ForIn body evaluation closer to the specification
This fixes 2 bugs in our current implementation: * Properties deleted during iteration were still being iterated * Properties with the same name in both the object and it's prototype were iterated twice
This commit is contained in:
parent
9cfbbfd8d8
commit
02e97b3313
2 changed files with 40 additions and 20 deletions
|
@ -1021,30 +1021,29 @@ Completion ForInStatement::loop_evaluation(Interpreter& interpreter, GlobalObjec
|
||||||
// 3. Let V be undefined.
|
// 3. Let V be undefined.
|
||||||
auto last_value = js_undefined();
|
auto last_value = js_undefined();
|
||||||
|
|
||||||
while (object) {
|
auto result = object->enumerate_object_properties([&](auto value) -> Optional<Completion> {
|
||||||
auto property_names = TRY(object->enumerable_own_property_names(Object::PropertyKind::Key));
|
TRY(for_in_head_state.execute_head(interpreter, global_object, value));
|
||||||
for (auto& value : property_names) {
|
|
||||||
TRY(for_in_head_state.execute_head(interpreter, global_object, value));
|
|
||||||
|
|
||||||
// l. Let result be the result of evaluating stmt.
|
// l. Let result be the result of evaluating stmt.
|
||||||
auto result = m_body->execute(interpreter, global_object);
|
auto result = m_body->execute(interpreter, global_object);
|
||||||
|
|
||||||
// m. Set the running execution context's LexicalEnvironment to oldEnv.
|
// m. Set the running execution context's LexicalEnvironment to oldEnv.
|
||||||
interpreter.vm().running_execution_context().lexical_environment = old_environment;
|
interpreter.vm().running_execution_context().lexical_environment = old_environment;
|
||||||
|
|
||||||
// n. If LoopContinues(result, labelSet) is false, then
|
// n. If LoopContinues(result, labelSet) is false, then
|
||||||
if (!loop_continues(result, label_set)) {
|
if (!loop_continues(result, label_set)) {
|
||||||
// 1. Return Completion(UpdateEmpty(result, V)).
|
// 1. Return Completion(UpdateEmpty(result, V)).
|
||||||
return result.update_empty(last_value);
|
return result.update_empty(last_value);
|
||||||
}
|
|
||||||
|
|
||||||
// o. If result.[[Value]] is not empty, set V to result.[[Value]].
|
|
||||||
if (result.value().has_value())
|
|
||||||
last_value = *result.value();
|
|
||||||
}
|
}
|
||||||
object = TRY(object->internal_get_prototype_of());
|
|
||||||
}
|
// o. If result.[[Value]] is not empty, set V to result.[[Value]].
|
||||||
return last_value;
|
if (result.value().has_value())
|
||||||
|
last_value = *result.value();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.value_or(last_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation
|
// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation
|
||||||
|
|
|
@ -99,3 +99,24 @@ describe("special left hand sides", () => {
|
||||||
}).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment");
|
}).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("remove properties while iterating", () => {
|
||||||
|
const from = [1, 2, 3];
|
||||||
|
const to = [];
|
||||||
|
for (const prop in from) {
|
||||||
|
to.push(prop);
|
||||||
|
from.pop();
|
||||||
|
}
|
||||||
|
expect(to).toEqual(["0", "1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("duplicated properties in prototype", () => {
|
||||||
|
const object = { a: 1 };
|
||||||
|
const proto = { a: 2 };
|
||||||
|
Object.setPrototypeOf(object, proto);
|
||||||
|
const a = [];
|
||||||
|
for (const prop in object) {
|
||||||
|
a.push(prop);
|
||||||
|
}
|
||||||
|
expect(a).toEqual(["a"]);
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue