Skip to content

Commit

Permalink
Implements kj::HashSet support for jsg.
Browse files Browse the repository at this point in the history
  • Loading branch information
dom96 committed Feb 17, 2025
1 parent 6595ff0 commit 5d12f05
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/workerd/jsg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,21 @@ void doSomething(kj::Array<kj::String> strings) {
doSomething(['a', 'b', 'c']);
```

### Set type (`kj::HashSet<T>`)

The `kj::HashSet<T>` 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<CustomStruct>`.

```cpp
void doSomething(kj::HashSet<kj::String> strings) {
KJ_DBG(strings.has("a"));
}
```
```js
doSomething(new Set(['a', 'b', 'c']));
```

### Sequence types (`jsg::Sequence<T>`)

A [Sequence][] is any JavaScript object that implements `Symbol.iterator`. These are
Expand Down
1 change: 1 addition & 0 deletions src/workerd/jsg/rtti.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions src/workerd/jsg/type-wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ class TypeWrapper: public DynamicResourceTypeMap<Self>,
public MaybeWrapper<Self>,
public OneOfWrapper<Self>,
public ArrayWrapper<Self>,
public SetWrapper<Self>,
public SequenceWrapper<Self>,
public GeneratorWrapper<Self>,
public ArrayBufferWrapper<Self>,
Expand Down Expand Up @@ -435,6 +436,7 @@ class TypeWrapper: public DynamicResourceTypeMap<Self>,
USING_WRAPPER(MaybeWrapper<Self>);
USING_WRAPPER(OneOfWrapper<Self>);
USING_WRAPPER(ArrayWrapper<Self>);
USING_WRAPPER(SetWrapper<Self>);
USING_WRAPPER(SequenceWrapper<Self>);
USING_WRAPPER(GeneratorWrapper<Self>);
USING_WRAPPER(ArrayBufferWrapper<Self>);
Expand Down
31 changes: 31 additions & 0 deletions src/workerd/jsg/value-test.c++
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,37 @@ KJ_TEST("Array Values") {

// ========================================================================================

struct SetContext: public ContextGlobalObject {
kj::HashSet<kj::String> takeSet(kj::HashSet<kj::String> set) {
KJ_ASSERT(set.contains("42"_kj));
return kj::mv(set);
}
kj::HashSet<kj::String> returnStrings(int i, int j, int k) {
auto result = kj::HashSet<kj::String>();
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<SetContext, SetIsolate> 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<kj::String> testSequence(Sequence<kj::String> sequence) {
KJ_ASSERT(sequence.size() == 2);
Expand Down
61 changes: 61 additions & 0 deletions src/workerd/jsg/value.h
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,67 @@ class ArrayWrapper {
}
};

// =======================================================================================
// Sets

// TypeWrapper mixin for sets.
template <typename TypeWrapper>
class SetWrapper {
public:
static auto constexpr MAX_STACK = 64;
template <typename U>
static constexpr const char* getName(kj::HashSet<U>*) {
return "Set";
}

template <typename U>
v8::Local<v8::Value> wrap(v8::Local<v8::Context> context,
kj::Maybe<v8::Local<v8::Object>> creator,
kj::HashSet<U> 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<TypeWrapper*>(this)->wrap(context, creator, item).template As<v8::Value>()));
}

return handleScope.Escape(out);
}

template <typename U>
v8::Local<v8::Value> wrap(v8::Local<v8::Context> context,
kj::Maybe<v8::Local<v8::Object>> creator,
kj::HashSet<U>& set) {
return static_cast<TypeWrapper*>(this)->wrap(context, creator, set.asPtr());
}

template <typename U>
kj::Maybe<kj::HashSet<U>> tryUnwrap(v8::Local<v8::Context> context,
v8::Local<v8::Value> handle,
kj::HashSet<U>*,
kj::Maybe<v8::Local<v8::Object>> parentObject) {
if (!handle->IsSet()) {
return kj::none;
}

auto set = handle.As<v8::Set>();
auto array = set->AsArray();
auto length = array->Length();
auto builder = kj::HashSet<U>();
for (auto i: kj::zeroTo(length)) {
v8::Local<v8::Value> element = check(array->Get(context, i));
auto value = static_cast<TypeWrapper*>(this)->template unwrap<U>(
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
//
Expand Down

0 comments on commit 5d12f05

Please sign in to comment.