mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:48:11 +00:00
LibJS: Implement non-value-producing statements properly
For various statements the spec states: Return NormalCompletion(empty). In those cases we have been returning undefined so far, which is incorrect. In other cases it states: Return Completion(UpdateEmpty(stmtCompletion, undefined)). Which essentially means a statement is evaluated and its completion value returned if non-empty, and undefined otherwise. While not actually noticeable in normal scripts as the VM's "last value" can't be accessed from JS code directly (with the exception of eval(), see below), it provided an inconsistent experience in the REPL: > if (true) 42; 42 > if (true) { 42; } undefined This also fixes the case where eval() would return undefined if the last executed statement is not a value-producing one: eval("1;;;;;") eval("1;{}") eval("1;var a;") As a consequence of the changes outlined above, these now all correctly return 1. See https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation, "NOTE 2". Fixes #3609.
This commit is contained in:
parent
dadf2e8251
commit
c499239137
4 changed files with 40 additions and 26 deletions
|
@ -61,6 +61,8 @@ void Interpreter::run(GlobalObject& global_object, const Program& program)
|
|||
|
||||
VM::InterpreterExecutionScope scope(*this);
|
||||
|
||||
vm.set_last_value({}, {});
|
||||
|
||||
CallFrame global_call_frame;
|
||||
global_call_frame.current_node = &program;
|
||||
global_call_frame.this_value = &global_object;
|
||||
|
@ -73,6 +75,9 @@ void Interpreter::run(GlobalObject& global_object, const Program& program)
|
|||
VERIFY(!vm.exception());
|
||||
program.execute(*this, global_object);
|
||||
vm.pop_call_frame();
|
||||
|
||||
if (vm.last_value().is_empty())
|
||||
vm.set_last_value({}, js_undefined());
|
||||
}
|
||||
|
||||
GlobalObject& Interpreter::global_object()
|
||||
|
@ -162,11 +167,10 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen
|
|||
auto& block = static_cast<const ScopeNode&>(statement);
|
||||
enter_scope(block, scope_type, global_object);
|
||||
|
||||
if (block.children().is_empty())
|
||||
vm().set_last_value({}, js_undefined());
|
||||
|
||||
for (auto& node : block.children()) {
|
||||
vm().set_last_value({}, node.execute(*this, global_object));
|
||||
auto value = node.execute(*this, global_object);
|
||||
if (!value.is_empty())
|
||||
vm().set_last_value({}, value);
|
||||
if (vm().should_unwind()) {
|
||||
if (!block.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, block.label()))
|
||||
vm().stop_unwind();
|
||||
|
@ -174,14 +178,18 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen
|
|||
}
|
||||
}
|
||||
|
||||
bool did_return = vm().unwind_until() == ScopeType::Function;
|
||||
if (scope_type == ScopeType::Function) {
|
||||
bool did_return = vm().unwind_until() == ScopeType::Function;
|
||||
if (!did_return)
|
||||
vm().set_last_value({}, js_undefined());
|
||||
}
|
||||
|
||||
if (vm().unwind_until() == scope_type)
|
||||
vm().unwind(ScopeType::None);
|
||||
|
||||
exit_scope(block);
|
||||
|
||||
return did_return ? vm().last_value() : js_undefined();
|
||||
return vm().last_value();
|
||||
}
|
||||
|
||||
LexicalEnvironment* Interpreter::current_environment()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue