ladybird/Libraries/LibJS/Runtime/GeneratorObject.h
Andreas Kling 410f0fdbb0 LibJS: Yield delegated iterator results directly
Sync yield* must suspend with the delegated IteratorResult object itself
on non-terminal steps. We were reading value and yielding that value,
causing Generator.prototype.next() to create a fresh result object.

Add a bytecode op for this path so ordinary yield keeps wrapping its
value, and teach generator resume to return the prebuilt result object.
This also avoids touching value before the delegated iterator is done.

Add regression coverage and update the yield* bytecode baseline.
2026-05-22 01:56:57 +02:00

80 lines
3 KiB
C++

/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
namespace JS {
class GeneratorObject : public Object {
JS_OBJECT(GeneratorObject, Object);
GC_DECLARE_ALLOCATOR(GeneratorObject);
public:
static GC::Ref<GeneratorObject> create(Realm&, Variant<GC::Ref<ECMAScriptFunctionObject>, GC::Ref<NativeJavaScriptBackedFunction>>, NonnullOwnPtr<ExecutionContext>);
virtual ~GeneratorObject() override = default;
void visit_edges(Cell::Visitor&) override;
struct IterationResult {
IterationResult() = delete;
explicit IterationResult(Value value, bool done, bool value_is_iterator_result = false)
: done(done)
, value(value)
, value_is_iterator_result(value_is_iterator_result)
{
}
bool done { false };
Value value;
bool value_is_iterator_result { false };
};
ThrowCompletionOr<IterationResult> resume(VM&, Value value, Optional<StringView> const& generator_brand);
ThrowCompletionOr<IterationResult> resume_abrupt(VM&, JS::Completion abrupt_completion, Optional<StringView> const& generator_brand);
enum class GeneratorState {
SuspendedStart,
SuspendedYield,
Executing,
Completed,
};
GeneratorState generator_state() const { return m_generator_state; }
void set_generator_state(GeneratorState generator_state) { m_generator_state = generator_state; }
void set_pending_completion(Completion const& completion)
{
m_pending_completion_value = completion.value();
m_pending_completion_type = completion.type();
}
Value pending_completion_value() const { return m_pending_completion_value; }
Completion::Type pending_completion_type() const { return m_pending_completion_type; }
void set_pending_completion_type(Completion::Type completion_type) { m_pending_completion_type = completion_type; }
void clear_pending_completion()
{
m_pending_completion_value = js_undefined();
m_pending_completion_type = Completion::Type::Normal;
}
protected:
GeneratorObject(Realm&, Object* prototype, NonnullOwnPtr<ExecutionContext>, Optional<StringView> generator_brand = {});
ThrowCompletionOr<GeneratorState> validate(VM&, Optional<StringView> const& generator_brand);
virtual ThrowCompletionOr<IterationResult> execute(VM&, JS::Completion const& completion);
private:
NonnullOwnPtr<ExecutionContext> m_execution_context;
GC::Ptr<Bytecode::Executable> m_generating_executable;
u32 m_yield_continuation { ExecutionContext::no_yield_continuation };
GeneratorState m_generator_state { GeneratorState::SuspendedStart };
Optional<StringView> m_generator_brand;
Value m_pending_completion_value { js_undefined() };
Completion::Type m_pending_completion_type { Completion::Type::Normal };
};
}