LibJS: Support deleting local variables with operator delete

To make this cleaner i also moved the logic into Reference::delete_.
This commit is contained in:
Idan Horowitz 2021-06-08 04:00:53 +03:00 committed by Linus Groh
parent af58779def
commit 064ed8279e
Notes: sideshowbarker 2024-07-18 22:57:59 +09:00
14 changed files with 82 additions and 10 deletions

View file

@ -685,16 +685,7 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
auto reference = m_lhs->to_reference(interpreter, global_object);
if (interpreter.exception())
return {};
if (reference.is_unresolvable())
return Value(true);
// FIXME: Support deleting locals
VERIFY(!reference.is_local_variable());
if (reference.is_global_variable())
return Value(global_object.delete_property(reference.name()));
auto* base_object = reference.base().to_object(global_object);
if (!base_object)
return {};
return Value(base_object->delete_property(reference.name()));
return Value(reference.delete_(global_object));
}
Value lhs_result;

View file

@ -130,6 +130,7 @@
"target is non-extensible") \
M(ProxyTwoArguments, "Proxy constructor requires at least two arguments") \
M(ReduceNoInitial, "Reduce of empty array with no initial value") \
M(ReferenceNullishDeleteProperty, "Cannot delete property '{}' of {}") \
M(ReferenceNullishGetProperty, "Cannot get property '{}' of {}") \
M(ReferenceNullishSetProperty, "Cannot set property '{}' of {}") \
M(ReferencePrimitiveSetProperty, "Cannot set property '{}' of {} '{}'") \

View file

@ -290,6 +290,11 @@ void GlobalObject::put_to_scope(const FlyString& name, Variable variable)
put(name, variable.value);
}
bool GlobalObject::delete_from_scope(FlyString const& name)
{
return delete_property(name);
}
bool GlobalObject::has_this_binding() const
{
return true;

View file

@ -23,6 +23,7 @@ public:
virtual Optional<Variable> get_from_scope(const FlyString&) const override;
virtual void put_to_scope(const FlyString&, Variable) override;
virtual bool delete_from_scope(FlyString const&) override;
virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;

View file

@ -62,6 +62,11 @@ void LexicalEnvironment::put_to_scope(const FlyString& name, Variable variable)
m_variables.set(name, variable);
}
bool LexicalEnvironment::delete_from_scope(FlyString const& name)
{
return m_variables.remove(name);
}
bool LexicalEnvironment::has_super_binding() const
{
return m_environment_record_type == EnvironmentRecordType::Function && this_binding_status() != ThisBindingStatus::Lexical && m_home_object.is_object();

View file

@ -39,6 +39,7 @@ public:
// ^ScopeObject
virtual Optional<Variable> get_from_scope(const FlyString&) const override;
virtual void put_to_scope(const FlyString&, Variable) override;
virtual bool delete_from_scope(FlyString const&) override;
virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;

View file

@ -98,4 +98,32 @@ Value Reference::get(GlobalObject& global_object)
return object->get(m_name).value_or(js_undefined());
}
bool Reference::delete_(GlobalObject& global_object)
{
if (is_unresolvable())
return true;
auto& vm = global_object.vm();
if (is_local_variable() || is_global_variable()) {
if (is_local_variable())
return vm.delete_variable(m_name.to_string());
else
return global_object.delete_property(m_name);
}
auto base = this->base();
if (base.is_nullish()) {
// This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead.
vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects());
return false;
}
auto* object = base.to_object(global_object);
VERIFY(object);
return object->delete_property(m_name);
}
}

View file

@ -67,6 +67,7 @@ public:
void put(GlobalObject&, Value);
Value get(GlobalObject&);
bool delete_(GlobalObject&);
private:
void throw_reference_error(GlobalObject&);

View file

@ -21,6 +21,7 @@ class ScopeObject : public Object {
public:
virtual Optional<Variable> get_from_scope(const FlyString&) const = 0;
virtual void put_to_scope(const FlyString&, Variable) = 0;
virtual bool delete_from_scope(FlyString const&) = 0;
virtual bool has_this_binding() const = 0;
virtual Value get_this_binding(GlobalObject&) const = 0;

View file

@ -161,6 +161,29 @@ void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_o
global_object.put(name, value);
}
bool VM::delete_variable(FlyString const& name)
{
ScopeObject* specific_scope = nullptr;
Optional<Variable> possible_match;
if (!m_call_stack.is_empty()) {
for (auto* scope = current_scope(); scope; scope = scope->parent()) {
possible_match = scope->get_from_scope(name);
if (possible_match.has_value()) {
specific_scope = scope;
break;
}
}
}
if (!possible_match.has_value())
return false;
if (possible_match.value().declaration_kind == DeclarationKind::Const)
return false;
VERIFY(specific_scope);
return specific_scope->delete_from_scope(name);
}
void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, ScopeObject* specific_scope)
{
set_variable(target, move(value), global_object, first_assignment, specific_scope);

View file

@ -187,6 +187,7 @@ public:
Value get_variable(const FlyString& name, GlobalObject&);
void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr);
bool delete_variable(FlyString const& name);
void assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr);
void assign(const FlyString& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr);
void assign(const NonnullRefPtr<BindingPattern>& target, Value, GlobalObject&, bool first_assignment = false, ScopeObject* specific_scope = nullptr);

View file

@ -34,6 +34,11 @@ void WithScope::put_to_scope(const FlyString& name, Variable variable)
m_object.put(name, variable.value);
}
bool WithScope::delete_from_scope(FlyString const& name)
{
return m_object.delete_property(name);
}
bool WithScope::has_this_binding() const
{
return parent()->has_this_binding();

View file

@ -18,6 +18,7 @@ public:
virtual Optional<Variable> get_from_scope(const FlyString&) const override;
virtual void put_to_scope(const FlyString&, Variable) override;
virtual bool delete_from_scope(FlyString const&) override;
virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;

View file

@ -0,0 +1,8 @@
test("basic functionality", () => {
let a = 5;
expect(delete a).toBeTrue();
expect(() => {
a;
}).toThrowWithMessage(ReferenceError, "'a' is not defined");
});