From 5d12f0553fdfb1e2156378df57049c381b2b7d86 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Fri, 14 Feb 2025 14:16:58 +0000 Subject: [PATCH] Implements kj::HashSet support for jsg. --- src/workerd/jsg/README.md | 15 +++++++++ src/workerd/jsg/rtti.h | 1 + src/workerd/jsg/type-wrapper.h | 2 ++ src/workerd/jsg/value-test.c++ | 31 +++++++++++++++++ src/workerd/jsg/value.h | 61 ++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+) diff --git a/src/workerd/jsg/README.md b/src/workerd/jsg/README.md index e3a94aadbb7..ee1b20a04d1 100644 --- a/src/workerd/jsg/README.md +++ b/src/workerd/jsg/README.md @@ -170,6 +170,21 @@ void doSomething(kj::Array strings) { doSomething(['a', 'b', 'c']); ``` +### Set type (`kj::HashSet`) + +The `kj::HashSet` type maps to JavaScript sets. The `T` can be any value, though there are +currently some restrictions. For example, you cannot have a `kj::HashSet`. + +```cpp +void doSomething(kj::HashSet strings) { + KJ_DBG(strings.has("a")); +} +``` + +```js +doSomething(new Set(['a', 'b', 'c'])); +``` + ### Sequence types (`jsg::Sequence`) A [Sequence][] is any JavaScript object that implements `Symbol.iterator`. These are diff --git a/src/workerd/jsg/rtti.h b/src/workerd/jsg/rtti.h index ecc24de91d0..b5b03c68dfc 100644 --- a/src/workerd/jsg/rtti.h +++ b/src/workerd/jsg/rtti.h @@ -379,6 +379,7 @@ FOR_EACH_MAYBE_TYPE(DECLARE_MAYBE_TYPE) #define FOR_EACH_ARRAY_TYPE(F) \ F(kj::Array) \ F(kj::ArrayPtr) \ + F(kj::HashSet) \ F(jsg::Sequence) \ F(jsg::AsyncGenerator) diff --git a/src/workerd/jsg/type-wrapper.h b/src/workerd/jsg/type-wrapper.h index b5a716d9424..f4b8f200c0a 100644 --- a/src/workerd/jsg/type-wrapper.h +++ b/src/workerd/jsg/type-wrapper.h @@ -378,6 +378,7 @@ class TypeWrapper: public DynamicResourceTypeMap, public MaybeWrapper, public OneOfWrapper, public ArrayWrapper, + public SetWrapper, public SequenceWrapper, public GeneratorWrapper, public ArrayBufferWrapper, @@ -435,6 +436,7 @@ class TypeWrapper: public DynamicResourceTypeMap, USING_WRAPPER(MaybeWrapper); USING_WRAPPER(OneOfWrapper); USING_WRAPPER(ArrayWrapper); + USING_WRAPPER(SetWrapper); USING_WRAPPER(SequenceWrapper); USING_WRAPPER(GeneratorWrapper); USING_WRAPPER(ArrayBufferWrapper); diff --git a/src/workerd/jsg/value-test.c++ b/src/workerd/jsg/value-test.c++ index 22522740bd2..af28d7ebf49 100644 --- a/src/workerd/jsg/value-test.c++ +++ b/src/workerd/jsg/value-test.c++ @@ -908,6 +908,37 @@ KJ_TEST("Array Values") { // ======================================================================================== +struct SetContext: public ContextGlobalObject { + kj::HashSet takeSet(kj::HashSet set) { + KJ_ASSERT(set.contains("42"_kj)); + return kj::mv(set); + } + kj::HashSet returnStrings(int i, int j, int k) { + auto result = kj::HashSet(); + result.insert(kj::str(i)); + result.insert(kj::str(j)); + result.insert(kj::str(k)); + return kj::mv(result); + } + JSG_RESOURCE_TYPE(SetContext) { + JSG_METHOD(takeSet); + JSG_METHOD(returnStrings); + } +}; +JSG_DECLARE_ISOLATE_TYPE(SetIsolate, SetContext); + +KJ_TEST("Set Values") { + Evaluator e(v8System); + e.expectEval("m = new Set(); m.add('42'); takeSet(m).has('42')", "boolean", "true"); + e.expectEval( + "m = new Set(); m.add({ toString: () => '42' }); m.add({ toString: () => '42' }); takeSet(m).has('42')", + "throws", "TypeError: Duplicate values in the set after unwrapping."); + + e.expectEval("returnStrings(123, 1024, 456).has('1024')", "boolean", "true"); +} + +// ======================================================================================== + struct SequenceContext: public ContextGlobalObject { Sequence testSequence(Sequence sequence) { KJ_ASSERT(sequence.size() == 2); diff --git a/src/workerd/jsg/value.h b/src/workerd/jsg/value.h index 85a769a14fb..cf2045ce4f3 100644 --- a/src/workerd/jsg/value.h +++ b/src/workerd/jsg/value.h @@ -851,6 +851,67 @@ class ArrayWrapper { } }; +// ======================================================================================= +// Sets + +// TypeWrapper mixin for sets. +template +class SetWrapper { + public: + static auto constexpr MAX_STACK = 64; + template + static constexpr const char* getName(kj::HashSet*) { + return "Set"; + } + + template + v8::Local wrap(v8::Local context, + kj::Maybe> creator, + kj::HashSet set) { + v8::Isolate* isolate = context->GetIsolate(); + v8::EscapableHandleScope handleScope(isolate); + + auto out = v8::Set::New(isolate); + for (const auto& item: set) { + out = check(out->Add(context, + static_cast(this)->wrap(context, creator, item).template As())); + } + + return handleScope.Escape(out); + } + + template + v8::Local wrap(v8::Local context, + kj::Maybe> creator, + kj::HashSet& set) { + return static_cast(this)->wrap(context, creator, set.asPtr()); + } + + template + kj::Maybe> tryUnwrap(v8::Local context, + v8::Local handle, + kj::HashSet*, + kj::Maybe> parentObject) { + if (!handle->IsSet()) { + return kj::none; + } + + auto set = handle.As(); + auto array = set->AsArray(); + auto length = array->Length(); + auto builder = kj::HashSet(); + for (auto i: kj::zeroTo(length)) { + v8::Local element = check(array->Get(context, i)); + auto value = static_cast(this)->template unwrap( + context, element, TypeErrorContext::arrayElement(i)); + builder.upsert(kj::mv(value), [&](U& existing, U&& replacement) { + JSG_FAIL_REQUIRE(TypeError, "Duplicate values in the set after unwrapping."); + }); + } + return kj::mv(builder); + } +}; + // ======================================================================================= // ArrayBuffers / ArrayBufferViews //