mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 15:17:46 +00:00
LibJS: Add using declaration support, RAII like operation in js
In this patch only top level and not the more complicated for loop using statements are supported. Also, as noted in the latest meeting of tc39 async parts of the spec are not stage 3 thus not included.
This commit is contained in:
parent
2c87ff2218
commit
541637e15a
15 changed files with 861 additions and 68 deletions
|
@ -31,6 +31,7 @@
|
|||
#include <LibJS/Runtime/PropertyKey.h>
|
||||
#include <LibJS/Runtime/ProxyObject.h>
|
||||
#include <LibJS/Runtime/Reference.h>
|
||||
#include <LibJS/Runtime/SuppressedError.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -1316,4 +1317,160 @@ ThrowCompletionOr<String> get_substitution(VM& vm, Utf16View const& matched, Utf
|
|||
return TRY_OR_THROW_OOM(vm, Utf16View { result }.to_utf8());
|
||||
}
|
||||
|
||||
// 2.1.2 AddDisposableResource ( disposable, V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-adddisposableresource-disposable-v-hint-disposemethod
|
||||
ThrowCompletionOr<void> add_disposable_resource(VM& vm, Vector<DisposableResource>& disposable, Value value, Environment::InitializeBindingHint hint, FunctionObject* method)
|
||||
{
|
||||
// NOTE: For now only sync is a valid hint
|
||||
VERIFY(hint == Environment::InitializeBindingHint::SyncDispose);
|
||||
|
||||
Optional<DisposableResource> resource;
|
||||
|
||||
// 1. If method is not present then,
|
||||
if (!method) {
|
||||
// a. If V is null or undefined, return NormalCompletion(empty).
|
||||
if (value.is_nullish())
|
||||
return {};
|
||||
|
||||
// b. If Type(V) is not Object, throw a TypeError exception.
|
||||
if (!value.is_object())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
|
||||
|
||||
// c. Let resource be ? CreateDisposableResource(V, hint).
|
||||
resource = TRY(create_disposable_resource(vm, value, hint));
|
||||
}
|
||||
// 2. Else,
|
||||
else {
|
||||
// a. If V is null or undefined, then
|
||||
if (value.is_nullish()) {
|
||||
// i. Let resource be ? CreateDisposableResource(undefined, hint, method).
|
||||
resource = TRY(create_disposable_resource(vm, js_undefined(), hint, method));
|
||||
}
|
||||
// b. Else,
|
||||
else {
|
||||
// i. If Type(V) is not Object, throw a TypeError exception.
|
||||
if (!value.is_object())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
|
||||
|
||||
// ii. Let resource be ? CreateDisposableResource(V, hint, method).
|
||||
resource = TRY(create_disposable_resource(vm, value, hint, method));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Append resource to disposable.[[DisposableResourceStack]].
|
||||
VERIFY(resource.has_value());
|
||||
disposable.append(resource.release_value());
|
||||
|
||||
// 4. Return NormalCompletion(empty).
|
||||
return {};
|
||||
}
|
||||
|
||||
// 2.1.3 CreateDisposableResource ( V, hint [ , method ] ), https://tc39.es/proposal-explicit-resource-management/#sec-createdisposableresource
|
||||
ThrowCompletionOr<DisposableResource> create_disposable_resource(VM& vm, Value value, Environment::InitializeBindingHint hint, FunctionObject* method)
|
||||
{
|
||||
// 1. If method is not present, then
|
||||
if (!method) {
|
||||
// a. If V is undefined, throw a TypeError exception.
|
||||
if (value.is_undefined())
|
||||
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "value");
|
||||
|
||||
// b. Set method to ? GetDisposeMethod(V, hint).
|
||||
method = TRY(get_dispose_method(vm, value, hint));
|
||||
|
||||
// c. If method is undefined, throw a TypeError exception.
|
||||
if (!method)
|
||||
return vm.throw_completion<TypeError>(ErrorType::NoDisposeMethod, value.to_string_without_side_effects());
|
||||
}
|
||||
// 2. Else,
|
||||
// a. If IsCallable(method) is false, throw a TypeError exception.
|
||||
// NOTE: This is guaranteed to never occur from the type.
|
||||
VERIFY(method);
|
||||
|
||||
// 3. Return the DisposableResource Record { [[ResourceValue]]: V, [[Hint]]: hint, [[DisposeMethod]]: method }.
|
||||
// NOTE: Since we only support sync dispose we don't store the hint for now.
|
||||
VERIFY(hint == Environment::InitializeBindingHint::SyncDispose);
|
||||
return DisposableResource {
|
||||
value,
|
||||
*method
|
||||
};
|
||||
}
|
||||
|
||||
// 2.1.4 GetDisposeMethod ( V, hint ), https://tc39.es/proposal-explicit-resource-management/#sec-getdisposemethod
|
||||
ThrowCompletionOr<GCPtr<FunctionObject>> get_dispose_method(VM& vm, Value value, Environment::InitializeBindingHint hint)
|
||||
{
|
||||
// NOTE: We only have sync dispose for now which means we ignore step 1.
|
||||
VERIFY(hint == Environment::InitializeBindingHint::SyncDispose);
|
||||
|
||||
// 2. Else,
|
||||
// a. Let method be ? GetMethod(V, @@dispose).
|
||||
return GCPtr<FunctionObject> { TRY(value.get_method(vm, *vm.well_known_symbol_dispose())) };
|
||||
}
|
||||
|
||||
// 2.1.5 Dispose ( V, hint, method ), https://tc39.es/proposal-explicit-resource-management/#sec-dispose
|
||||
Completion dispose(VM& vm, Value value, NonnullGCPtr<FunctionObject> method)
|
||||
{
|
||||
// 1. Let result be ? Call(method, V).
|
||||
[[maybe_unused]] auto result = TRY(call(vm, *method, value));
|
||||
|
||||
// NOTE: Hint can only be sync-dispose so we ignore step 2.
|
||||
// 2. If hint is async-dispose and result is not undefined, then
|
||||
// a. Perform ? Await(result).
|
||||
|
||||
// 3. Return undefined.
|
||||
return js_undefined();
|
||||
}
|
||||
|
||||
// 2.1.6 DisposeResources ( disposable, completion ), https://tc39.es/proposal-explicit-resource-management/#sec-disposeresources-disposable-completion-errors
|
||||
Completion dispose_resources(VM& vm, Vector<DisposableResource> const& disposable, Completion completion)
|
||||
{
|
||||
// 1. If disposable is not undefined, then
|
||||
// NOTE: At this point disposable is always defined.
|
||||
|
||||
// a. For each resource of disposable.[[DisposableResourceStack]], in reverse list order, do
|
||||
for (auto const& resource : disposable.in_reverse()) {
|
||||
// i. Let result be Dispose(resource.[[ResourceValue]], resource.[[Hint]], resource.[[DisposeMethod]]).
|
||||
auto result = dispose(vm, resource.resource_value, resource.dispose_method);
|
||||
|
||||
// ii. If result.[[Type]] is throw, then
|
||||
if (result.is_error()) {
|
||||
// 1. If completion.[[Type]] is throw, then
|
||||
if (completion.is_error()) {
|
||||
// a. Set result to result.[[Value]].
|
||||
|
||||
// b. Let suppressed be completion.[[Value]].
|
||||
auto suppressed = completion.value().value();
|
||||
|
||||
// c. Let error be a newly created SuppressedError object.
|
||||
auto error = SuppressedError::create(*vm.current_realm());
|
||||
|
||||
// d. Perform ! DefinePropertyOrThrow(error, "error", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: result }).
|
||||
MUST(error->define_property_or_throw(vm.names.error, { .value = result.value(), .writable = true, .enumerable = true, .configurable = true }));
|
||||
|
||||
// e. Perform ! DefinePropertyOrThrow(error, "suppressed", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: suppressed }).
|
||||
MUST(error->define_property_or_throw(vm.names.suppressed, { .value = suppressed, .writable = true, .enumerable = false, .configurable = true }));
|
||||
|
||||
// f. Set completion to ThrowCompletion(error).
|
||||
completion = throw_completion(error);
|
||||
}
|
||||
// 2. Else,
|
||||
else {
|
||||
// a. Set completion to result.
|
||||
completion = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Return completion.
|
||||
return completion;
|
||||
}
|
||||
|
||||
Completion dispose_resources(VM& vm, GCPtr<DeclarativeEnvironment> disposable, Completion completion)
|
||||
{
|
||||
// 1. If disposable is not undefined, then
|
||||
if (disposable)
|
||||
return dispose_resources(vm, disposable->disposable_resource_stack(), completion);
|
||||
|
||||
// 2. Return completion.
|
||||
return completion;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -42,6 +42,17 @@ ThrowCompletionOr<Object*> get_prototype_from_constructor(VM&, FunctionObject co
|
|||
Object* create_unmapped_arguments_object(VM&, Span<Value> arguments);
|
||||
Object* create_mapped_arguments_object(VM&, FunctionObject&, Vector<FunctionParameter> const&, Span<Value> arguments, Environment&);
|
||||
|
||||
struct DisposableResource {
|
||||
Value resource_value;
|
||||
NonnullGCPtr<FunctionObject> dispose_method;
|
||||
};
|
||||
ThrowCompletionOr<void> add_disposable_resource(VM&, Vector<DisposableResource>& disposable, Value, Environment::InitializeBindingHint, FunctionObject* = nullptr);
|
||||
ThrowCompletionOr<DisposableResource> create_disposable_resource(VM&, Value, Environment::InitializeBindingHint, FunctionObject* method = nullptr);
|
||||
ThrowCompletionOr<GCPtr<FunctionObject>> get_dispose_method(VM&, Value, Environment::InitializeBindingHint);
|
||||
Completion dispose(VM& vm, Value, NonnullGCPtr<FunctionObject> method);
|
||||
Completion dispose_resources(VM& vm, Vector<DisposableResource> const& disposable, Completion completion);
|
||||
Completion dispose_resources(VM& vm, GCPtr<DeclarativeEnvironment> disposable, Completion completion);
|
||||
|
||||
enum class CanonicalIndexMode {
|
||||
DetectNumericRoundtrip,
|
||||
IgnoreNumericRoundtrip,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <LibJS/Interpreter.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/DeclarativeEnvironment.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/FunctionObject.h>
|
||||
|
@ -42,6 +43,11 @@ void DeclarativeEnvironment::visit_edges(Visitor& visitor)
|
|||
Base::visit_edges(visitor);
|
||||
for (auto& binding : m_bindings)
|
||||
visitor.visit(binding.value);
|
||||
|
||||
for (auto& disposable : m_disposable_resource_stack) {
|
||||
visitor.visit(disposable.resource_value);
|
||||
visitor.visit(disposable.dispose_method);
|
||||
}
|
||||
}
|
||||
|
||||
// 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n
|
||||
|
@ -97,7 +103,7 @@ ThrowCompletionOr<void> DeclarativeEnvironment::create_immutable_binding(VM&, De
|
|||
|
||||
// 9.1.1.1.4 InitializeBinding ( N, V ), https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v
|
||||
// 4.1.1.1.1 InitializeBinding ( N, V, hint ), https://tc39.es/proposal-explicit-resource-management/#sec-declarative-environment-records
|
||||
ThrowCompletionOr<void> DeclarativeEnvironment::initialize_binding(VM&, DeprecatedFlyString const& name, Value value, Environment::InitializeBindingHint)
|
||||
ThrowCompletionOr<void> DeclarativeEnvironment::initialize_binding(VM& vm, DeprecatedFlyString const& name, Value value, Environment::InitializeBindingHint hint)
|
||||
{
|
||||
auto binding_and_index = find_binding_and_index(name);
|
||||
VERIFY(binding_and_index.has_value());
|
||||
|
@ -106,7 +112,9 @@ ThrowCompletionOr<void> DeclarativeEnvironment::initialize_binding(VM&, Deprecat
|
|||
// 1. Assert: envRec must have an uninitialized binding for N.
|
||||
VERIFY(binding.initialized == false);
|
||||
|
||||
// FIXME: 2. If hint is not normal, perform ? AddDisposableResource(envRec, V, hint).
|
||||
// 2. If hint is not normal, perform ? AddDisposableResource(envRec, V, hint).
|
||||
if (hint != Environment::InitializeBindingHint::Normal)
|
||||
TRY(add_disposable_resource(vm, m_disposable_resource_stack, value, hint));
|
||||
|
||||
// 3. Set the bound value for N in envRec to V.
|
||||
binding.value = value;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <AK/DeprecatedFlyString.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
@ -63,6 +64,9 @@ private:
|
|||
ThrowCompletionOr<Value> get_binding_value_direct(VM&, Binding&, bool strict);
|
||||
ThrowCompletionOr<void> set_mutable_binding_direct(VM&, Binding&, Value, bool strict);
|
||||
|
||||
friend Completion dispose_resources(VM&, GCPtr<DeclarativeEnvironment>, Completion);
|
||||
Vector<DisposableResource> const& disposable_resource_stack() const { return m_disposable_resource_stack; }
|
||||
|
||||
protected:
|
||||
DeclarativeEnvironment();
|
||||
explicit DeclarativeEnvironment(Environment* parent_environment);
|
||||
|
@ -116,6 +120,7 @@ private:
|
|||
virtual bool is_declarative_environment() const override { return true; }
|
||||
|
||||
Vector<Binding> m_bindings;
|
||||
Vector<DisposableResource> m_disposable_resource_stack;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -736,7 +736,7 @@ void async_block_start(VM& vm, NonnullRefPtr<Statement> const& async_body, Promi
|
|||
auto& running_context = vm.running_execution_context();
|
||||
|
||||
// 3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
|
||||
auto execution_steps = NativeFunction::create(realm, "", [&async_body, &promise_capability](auto& vm) -> ThrowCompletionOr<Value> {
|
||||
auto execution_steps = NativeFunction::create(realm, "", [&async_body, &promise_capability, &async_context](auto& vm) -> ThrowCompletionOr<Value> {
|
||||
// a. Let result be the result of evaluating asyncBody.
|
||||
auto result = async_body->execute(vm.interpreter());
|
||||
|
||||
|
@ -745,17 +745,24 @@ void async_block_start(VM& vm, NonnullRefPtr<Statement> const& async_body, Promi
|
|||
// c. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
|
||||
vm.pop_execution_context();
|
||||
|
||||
// d. If result.[[Type]] is normal, then
|
||||
// d. Let env be asyncContext's LexicalEnvironment.
|
||||
auto* env = async_context.lexical_environment;
|
||||
VERIFY(is<DeclarativeEnvironment>(env));
|
||||
|
||||
// e. Set result to DisposeResources(env, result).
|
||||
result = dispose_resources(vm, static_cast<DeclarativeEnvironment*>(env), result);
|
||||
|
||||
// f. If result.[[Type]] is normal, then
|
||||
if (result.type() == Completion::Type::Normal) {
|
||||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
|
||||
MUST(call(vm, *promise_capability.resolve(), js_undefined(), js_undefined()));
|
||||
}
|
||||
// e. Else if result.[[Type]] is return, then
|
||||
// g. Else if result.[[Type]] is return, then
|
||||
else if (result.type() == Completion::Type::Return) {
|
||||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
|
||||
MUST(call(vm, *promise_capability.resolve(), js_undefined(), *result.value()));
|
||||
}
|
||||
// f. Else,
|
||||
// h. Else,
|
||||
else {
|
||||
// i. Assert: result.[[Type]] is throw.
|
||||
VERIFY(result.type() == Completion::Type::Throw);
|
||||
|
@ -763,7 +770,7 @@ void async_block_start(VM& vm, NonnullRefPtr<Statement> const& async_body, Promi
|
|||
// ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
|
||||
MUST(call(vm, *promise_capability.reject(), js_undefined(), *result.value()));
|
||||
}
|
||||
// g. Return unused.
|
||||
// i. Return unused.
|
||||
// NOTE: We don't support returning an empty/optional/unused value here.
|
||||
return js_undefined();
|
||||
});
|
||||
|
@ -882,8 +889,15 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
// 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList).
|
||||
TRY(function_declaration_instantiation(ast_interpreter));
|
||||
|
||||
// 2. Return the result of evaluating FunctionStatementList.
|
||||
return m_ecmascript_code->execute(*ast_interpreter);
|
||||
// 2. Let result be result of evaluating FunctionStatementList.
|
||||
auto result = m_ecmascript_code->execute(*ast_interpreter);
|
||||
|
||||
// 3. Let env be the running execution context's LexicalEnvironment.
|
||||
auto* env = vm.running_execution_context().lexical_environment;
|
||||
VERIFY(is<DeclarativeEnvironment>(env));
|
||||
|
||||
// 4. Return ? DisposeResources(env, result).
|
||||
return dispose_resources(vm, static_cast<DeclarativeEnvironment*>(env), result);
|
||||
}
|
||||
// AsyncFunctionBody : FunctionBody
|
||||
else if (m_kind == FunctionKind::Async) {
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
M(ModuleNotFound, "Cannot find/open module: '{}'") \
|
||||
M(ModuleNotFoundNoReferencingScript, "Cannot resolve module {} without any active script or module") \
|
||||
M(NegativeExponent, "Exponent must be positive") \
|
||||
M(NoDisposeMethod, "{} does not have dispose method") \
|
||||
M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \
|
||||
M(NotAConstructor, "{} is not a constructor") \
|
||||
M(NotAFunction, "{} is not a function") \
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue