Skip to content

Commit

Permalink
JSG_STRUCT_TS_OVERRIDE_DYNAMIC by refactoring JSG_STRUCT
Browse files Browse the repository at this point in the history
  • Loading branch information
tewaro committed Aug 19, 2024
1 parent a47804f commit fc2590a
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 19 deletions.
87 changes: 70 additions & 17 deletions src/workerd/jsg/jsg.h
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,13 @@ using HasGetTemplateOverload = decltype(
#define JSG_STRUCT_TS_OVERRIDE(...) \
static constexpr char _JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY[] = JSG_STRING_LITERAL(__VA_ARGS__)

// Like JSG_STRUCT_TS_OVERRIDE, however it enables dynamic selection of TS_OVERRIDE.
// Should be placed adjacent to the JSG_STRUCT delcaration, inside the same struct definition.
#define JSG_STRUCT_TS_OVERRIDE_DYNAMIC(...) \
static void jsgConfiguration(__VA_ARGS__); \
template <typename Registry> \
static void registerTypeScriptDynamicOverride(Registry& registry, ##__VA_ARGS__)

// Like JSG_TS_DEFINE but for use with JSG_STRUCT. Should be placed adjacent to the JSG_STRUCT
// declaration, inside the same `struct` definition. See the `## TypeScript`section of the JSG README.md
// for more details.
Expand Down Expand Up @@ -623,6 +630,41 @@ namespace {
struct HasStructTypeScriptDefine<T, decltype(T::_JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY, 0)> : std::true_type { };
}

// This function is the first part of a JSG_STRUCT and defines the Kind, fields, and type wrapper
#define _JSG_STRUCT_INTERNAL_KIND_AND_FIELDS_AND_TYPE_WRAPPER(...) \
static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = \
::workerd::jsg::JsgKind::STRUCT; \
static constexpr char JSG_FOR_EACH(JSG_STRUCT_FIELD_NAME, , __VA_ARGS__); \
template <typename TypeWrapper, typename Self> \
using JsgFieldWrappers = ::workerd::jsg::TypeTuple< \
JSG_FOR_EACH(JSG_STRUCT_FIELD, , __VA_ARGS__) \
>

// This registers TS ROOT objects within a JSG_STRUCT
#define _JSG_STRUCT_REGISTER_MEMBERS_TS_ROOT() \
if constexpr (::workerd::jsg::HasStructTypeScriptRoot<Self>::value) { \
registry.registerTypeScriptRoot(); \
}

// This registers TS OVERRIDE objects within a JSG_STRUCT
#define _JSG_STRUCT_REGISTER_MEMBERS_TS_OVERRIDE() \
if constexpr (::workerd::jsg::HasStructTypeScriptOverride<Self>::value) { \
registry.template registerTypeScriptOverride<Self::_JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY>(); \
}

// This registers TS OVERRIDE for dynamic objects
#define _JSG_STRUCT_REGISTER_MEMBERS_TS_DYNAMIC_OVERRIDE() \
if constexpr (requires (jsg::GetConfiguration<Self> arg) \
{ registerTypeScriptDynamicOverride<Registry>(registry, arg); }) { \
registerTypeScriptDynamicOverride<Registry>(registry, arg); \
}

// This registers TS DEFINE objects within a register members function
#define _JSG_STRUCT_REGISTER_MEMBERS_TS_DEFINE() \
if constexpr (::workerd::jsg::HasStructTypeScriptDefine<Self>::value) { \
registry.template registerTypeScriptDefine<Self::_JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY>(); \
}

// Nest this inside a simple struct declaration in order to support translating it to/from a
// JavaScript object / Web IDL dictionary.
//
Expand All @@ -641,6 +683,20 @@ namespace {
// all in JavaScript when the optional is null in C++ (as opposed to the field being present but
// assigned the value `undefined`).
//
// Note that if a `validate` function is provided, then it will be called after the struct is
// unwrapped from v8. This would be an appropriate time to throw an error.
// Signature: void validate(jsg::Lock& js);
// Example:
// struct ValidatingFoo {
// kj::String abc;
// void validate(jsg::Lock& js) {
// JSG_REQUIRE(abc.size() != 0, TypeError, "Field 'abc' had no length in 'ValidatingFoo'.");
// }
// JSG_STRUCT(abc);
// };
//
// In this example the validate method would throw a `TypeError` if the size of the `abc` field was zero.
//
// Fields with a starting '$' will have that dollar sign prefix stripped in the JS binding. A
// motivating example to enact that change was WebCrypto which has a field in a dictionary called
// "public". '$' was chosen as a token we can use because it's a character for a C++ identifier. If
Expand All @@ -649,26 +705,23 @@ namespace {
// exporting), then you should be able to use '$$' as the identifier prefix in C++ since only the
// first '$' gets stripped.
#define JSG_STRUCT(...) \
static constexpr ::workerd::jsg::JsgKind JSG_KIND KJ_UNUSED = \
::workerd::jsg::JsgKind::STRUCT; \
static constexpr char JSG_FOR_EACH(JSG_STRUCT_FIELD_NAME, , __VA_ARGS__); \
_JSG_STRUCT_INTERNAL_KIND_AND_FIELDS_AND_TYPE_WRAPPER(__VA_ARGS__); \
template <typename Registry, typename Self> \
static void registerMembers(Registry& registry) { \
static void registerMembers(Registry& registry) \
requires (!jsg::HasConfiguration<Self>) { \
JSG_FOR_EACH(JSG_STRUCT_REGISTER_MEMBER, , __VA_ARGS__); \
if constexpr (::workerd::jsg::HasStructTypeScriptRoot<Self>::value) { \
registry.registerTypeScriptRoot(); \
} \
if constexpr (::workerd::jsg::HasStructTypeScriptOverride<Self>::value) { \
registry.template registerTypeScriptOverride<Self::_JSG_STRUCT_TS_OVERRIDE_DO_NOT_USE_DIRECTLY>(); \
} \
if constexpr (::workerd::jsg::HasStructTypeScriptDefine<Self>::value) { \
registry.template registerTypeScriptDefine<Self::_JSG_STRUCT_TS_DEFINE_DO_NOT_USE_DIRECTLY>(); \
} \
_JSG_STRUCT_REGISTER_MEMBERS_TS_ROOT(); \
_JSG_STRUCT_REGISTER_MEMBERS_TS_OVERRIDE(); \
_JSG_STRUCT_REGISTER_MEMBERS_TS_DEFINE(); \
} \
template <typename TypeWrapper, typename Self> \
using JsgFieldWrappers = ::workerd::jsg::TypeTuple< \
JSG_FOR_EACH(JSG_STRUCT_FIELD, , __VA_ARGS__) \
>
template <typename Registry, typename Self> \
static void registerMembers(Registry& registry, jsg::GetConfiguration<Self> arg) \
requires jsg::HasConfiguration<Self> { \
JSG_FOR_EACH(JSG_STRUCT_REGISTER_MEMBER, , __VA_ARGS__); \
_JSG_STRUCT_REGISTER_MEMBERS_TS_ROOT(); \
_JSG_STRUCT_REGISTER_MEMBERS_TS_DYNAMIC_OVERRIDE(); \
_JSG_STRUCT_REGISTER_MEMBERS_TS_DEFINE(); \
}

namespace {
template <size_t N>
Expand Down
13 changes: 11 additions & 2 deletions src/workerd/jsg/struct.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,19 @@ class StructWrapper<Self, T, TypeTuple<FieldWrappers...>, kj::_::Indexes<indices
// it prescribes lexicographically-ordered member initialization, with base members ordered
// before derived members. Objects with mutating getters might be broken by this, but it
// doesn't seem worth fixing absent a compelling use case.

return T {
auto t = T {
kj::get<indices>(fields).unwrap(static_cast<Self&>(*this), isolate, context, in)...
};

// Note that if a `validate` function is provided, then it will be called after the struct is
// unwrapped from v8. This would be an appropriate time to throw an error.
// Signature: void validate(jsg::Lock& js);
if constexpr (requires (jsg::Lock& js) { t.validate(js); }) {
jsg::Lock& js = jsg::Lock::from(isolate);
t.validate(js);
}

return t;
}

void newContext() = delete;
Expand Down
3 changes: 3 additions & 0 deletions src/workerd/jsg/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ template <typename Arg> auto getParameterType(void (*)(Arg)) -> Arg;
// SFINAE-friendly accessor for a resource type's configuration parameter.
template <typename T> using GetConfiguration = decltype(getParameterType(&T::jsgConfiguration));

template <typename T> concept HasConfiguration =
requires (GetConfiguration<T> arg) { T::jsgConfiguration(arg); };

inline bool isFinite(double value) {
return !(kj::isNaN(value) || value == kj::inf() || value == -kj::inf());
}
Expand Down

0 comments on commit fc2590a

Please # to comment.