// Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 #include #include #include #include #include #include #include struct MockConfig {}; namespace workerd::jsg::rtti { namespace { template kj::String tType() { // returns textual encoding of rtti. Builder builder((MockConfig())); auto type = builder.type(); capnp::TextCodec codec; return codec.encode(type); } template kj::String tStructure() { // returns textual encoding of structure. Builder builder((MockConfig())); auto type = builder.structure(); capnp::TextCodec codec; return codec.encode(type); } KJ_TEST("jsg::Js* types") { KJ_EXPECT(tType() == "(unknown = void)"); KJ_EXPECT(tType() == "(object = void)"); KJ_EXPECT(tType() == "(boolt = void)"); KJ_EXPECT(tType() == "(array = (element = (unknown = void), name = \"jsg::JsArray\"))"); KJ_EXPECT(tType() == "(string = (name = \"jsg::JsString\"))"); KJ_EXPECT(tType() == "(number = (name = \"jsg::JsBigInt\"))"); KJ_EXPECT(tType() == "(number = (name = \"jsg::JsNumber\"))"); KJ_EXPECT(tType() == "(number = (name = \"jsg::JsInt32\"))"); KJ_EXPECT(tType() == "(number = (name = \"jsg::JsUint32\"))"); KJ_EXPECT(tType() == "(builtin = (type = kjDate))"); KJ_EXPECT(tType() == "(unknown = void)"); KJ_EXPECT(tType() == "(unknown = void)"); KJ_EXPECT(tType() == "(unknown = void)"); KJ_EXPECT(tType() == "(unknown = void)"); KJ_EXPECT(tType>() == "(unknown = void)"); KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(boolt = void)"); KJ_EXPECT( tType>() == "(array = (element = (unknown = void), name = \"jsg::JsArray\"))"); KJ_EXPECT(tType>() == "(string = (name = \"jsg::JsString\"))"); KJ_EXPECT(tType>() == "(number = (name = \"jsg::JsBigInt\"))"); KJ_EXPECT(tType>() == "(number = (name = \"jsg::JsNumber\"))"); KJ_EXPECT(tType>() == "(number = (name = \"jsg::JsInt32\"))"); KJ_EXPECT(tType>() == "(number = (name = \"jsg::JsUint32\"))"); KJ_EXPECT(tType>() == "(builtin = (type = kjDate))"); KJ_EXPECT(tType>() == "(unknown = void)"); KJ_EXPECT(tType>() == "(unknown = void)"); KJ_EXPECT(tType>() == "(unknown = void)"); KJ_EXPECT(tType>() == "(unknown = void)"); } KJ_TEST("primitive types") { KJ_EXPECT(tType() == "(voidt = void)"); KJ_EXPECT(tType() == "(boolt = void)"); KJ_EXPECT(tType() == "(unknown = void)"); } KJ_TEST("number types") { KJ_EXPECT(tType() == "(number = (name = \"char\"))"); KJ_EXPECT(tType() == "(number = (name = \"signed char\"))"); KJ_EXPECT(tType() == "(number = (name = \"unsigned char\"))"); KJ_EXPECT(tType() == "(number = (name = \"short\"))"); KJ_EXPECT(tType() == "(number = (name = \"unsigned short\"))"); KJ_EXPECT(tType() == "(number = (name = \"int\"))"); KJ_EXPECT(tType() == "(number = (name = \"unsigned int\"))"); KJ_EXPECT(tType() == "(number = (name = \"long\"))"); KJ_EXPECT(tType() == "(number = (name = \"unsigned long\"))"); KJ_EXPECT(tType() == "(number = (name = \"double\"))"); } KJ_TEST("string types") { KJ_EXPECT(tType() == "(string = (name = \"kj::String\"))"); KJ_EXPECT(tType() == "(string = (name = \"kj::StringPtr\"))"); KJ_EXPECT(tType() == "(string = (name = \"v8::String\"))"); KJ_EXPECT(tType() == "(string = (name = \"USVString\"))"); } KJ_TEST("object types") { KJ_EXPECT(tType() == "(object = void)"); KJ_EXPECT(tType() == "(object = void)"); } KJ_TEST("promises") { KJ_EXPECT(tType>() == "(promise = (value = (voidt = void)))"); KJ_EXPECT(tType>() == "(promise = (value = (number = (name = \"int\"))))"); KJ_EXPECT(tType>() == "(promise = (value = (number = (name = \"int\"))))"); KJ_EXPECT(tType() == "(promise = (value = (unknown = void)))"); } KJ_TEST("generic types") { KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(object = void)"); KJ_EXPECT(tType>() == "(string = (name = \"kj::String\"))"); KJ_EXPECT(tType>() == "(array = (element = (number = (name = \"int\")), name = \"kj::Array\"))"); KJ_EXPECT(tType>() == "(array = (element = (number = (name = \"int\")), name = \"kj::ArrayPtr\"))"); KJ_EXPECT(tType>() == "(array = (element = (number = (name = \"int\")), name = \"jsg::Sequence\"))"); KJ_EXPECT(tType>() == "(maybe = (value = (number = (name = \"int\")), name = \"kj::Maybe\"))"); KJ_EXPECT(tType>() == "(maybe = (value = (number = (name = \"int\")), name = \"jsg::Optional\"))"); KJ_EXPECT(tType>() == "(maybe = (value = (number = (name = \"int\")), name = \"jsg::LenientOptional\"))"); KJ_EXPECT(tType>() == "(dict = (key = (string = (name = \"kj::String\")), value = (number = (name = \"int\"))))"); KJ_EXPECT((tType>()) == "(dict = (key = (number = (name = \"double\")), value = (number = (name = \"int\"))))"); KJ_EXPECT((tType>()) == "(oneOf = (variants = [" "(number = (name = \"int\")), " "(number = (name = \"double\"))]))"); KJ_EXPECT((tType>()) == "(oneOf = (variants = [" "(number = (name = \"int\")), " "(number = (name = \"double\")), " "(string = (name = \"kj::String\"))]))"); } KJ_TEST("builtins") { KJ_EXPECT(tType() == "(builtin = (type = jsgBufferSource))"); KJ_EXPECT(tType() == "(builtin = (type = v8Uint8Array))"); KJ_EXPECT(tType() == "(builtin = (type = v8ArrayBufferView))"); KJ_EXPECT(tType() == "(builtin = (type = v8Function))"); KJ_EXPECT(tType() == "(builtin = (type = kjDate))"); } KJ_TEST("jsgImpl") { KJ_EXPECT(tType() == "(jsgImpl = (type = jsgLock))"); KJ_EXPECT(tType() == "(jsgImpl = (type = jsgSelfRef))"); KJ_EXPECT(tType() == "(jsgImpl = (type = jsgUnimplemented))"); KJ_EXPECT(tType() == "(jsgImpl = (type = v8Isolate))"); KJ_EXPECT(tType() == "(jsgImpl = (type = configuration))"); KJ_EXPECT(tType>() == "(jsgImpl = (type = jsgTypeHandler))"); KJ_EXPECT(tType>() == "(jsgImpl = (type = v8FunctionCallbackInfo))"); KJ_EXPECT(tType>() == "(jsgImpl = (type = v8PropertyCallbackInfo))"); } KJ_TEST("functions") { KJ_EXPECT(tType>() == "(function = (returnType = (number = (name = \"int\")), args = []))"); KJ_EXPECT(tType>() == "(function = (returnType = (voidt = void), args = [(number = (name = \"int\")), (number = (name = \"double\"))]))"); } KJ_TEST("c++ modifiers") { KJ_EXPECT(tType() == "(number = (name = \"int\"))"); KJ_EXPECT(tType() == "(number = (name = \"int\"))"); KJ_EXPECT(tType() == "(number = (name = \"int\"))"); KJ_EXPECT(tType() == "(number = (name = \"int\"))"); } struct Base: public Object { JSG_RESOURCE_TYPE(Base) { JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype); } }; struct TestResource: public Base { void instanceMethod(int i, double f) {} static int staticMethod() { return 42; } int getSize() { return 1; } void setSize(int size) {} static jsg::Ref constructor(jsg::Optional label); JSG_RESOURCE_TYPE(TestResource) { JSG_INHERIT(Base); JSG_METHOD(instanceMethod); JSG_STATIC_METHOD(staticMethod); JSG_INSTANCE_PROPERTY(size, getSize, setSize); JSG_READONLY_INSTANCE_PROPERTY(readonlySize, getSize); JSG_LAZY_INSTANCE_PROPERTY(lazySize, getSize); JSG_LAZY_READONLY_INSTANCE_PROPERTY(lazyReadonlySize, getSize); JSG_PROTOTYPE_PROPERTY(protoSize, getSize, setSize); JSG_READONLY_PROTOTYPE_PROPERTY(protoReadonlySize, getSize); } }; KJ_TEST("resource reference") { KJ_EXPECT(tType() == "(structure = (name = \"TestResource\", fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestResource\"))"); } KJ_TEST("resource structure") { KJ_EXPECT(tStructure() == "(name = \"Base\", members = [], " "extends = (intrinsic = (name = \"v8::kIteratorPrototype\")), " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::Base\", tsRoot = false, " "disposable = false, asyncDisposable = false)"); KJ_EXPECT(tStructure() == "(name = \"TestResource\", members = [" "(method = (name = \"instanceMethod\", returnType = (voidt = void), args = [(number = (name = \"int\")), (number = (name = \"double\"))], static = false, fastApiCompatible = true)), " "(method = (name = \"staticMethod\", returnType = (number = (name = \"int\")), args = [], static = true, fastApiCompatible = true)), " "(property = (name = \"size\", type = (number = (name = \"int\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = true)), " "(property = (name = \"readonlySize\", type = (number = (name = \"int\")), readonly = true, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = false)), " "(property = (name = \"lazySize\", type = (number = (name = \"int\")), readonly = false, lazy = true, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false)), " "(property = (name = \"lazyReadonlySize\", type = (number = (name = \"int\")), readonly = true, lazy = true, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false)), " "(property = (name = \"protoSize\", type = (number = (name = \"int\")), readonly = false, lazy = false, prototype = true, getterFastApiCompatible = true, setterFastApiCompatible = true)), " "(property = (name = \"protoReadonlySize\", type = (number = (name = \"int\")), readonly = true, lazy = false, prototype = true, getterFastApiCompatible = false, setterFastApiCompatible = false)), " "(constructor = (args = [(maybe = (value = (string = (name = \"kj::String\")), name = \"jsg::Optional\"))]))], " "extends = (structure = (name = \"Base\", fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::Base\")), " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestResource\", tsRoot = false, " "disposable = false, asyncDisposable = false)"); } struct TestNested: jsg::Object { JSG_RESOURCE_TYPE(TestNested) { JSG_NESTED_TYPE(Base); }; }; KJ_TEST("nested structure") { KJ_EXPECT(tStructure() == "(name = \"TestNested\", members = [(" "nested = (" "structure = (" "name = \"Base\", members = [], " "extends = (intrinsic = (name = \"v8::kIteratorPrototype\")), " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::Base\", " "tsRoot = false, disposable = false, asyncDisposable = false" "), " "name = \"Base\"))" "], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestNested\", tsRoot = false, " "disposable = false, asyncDisposable = false)"); } struct TestConstant: jsg::Object { static constexpr int ENABLED [[maybe_unused]] = 1; enum Type { CIRCLE = 2, }; JSG_RESOURCE_TYPE(TestConstant) { JSG_STATIC_CONSTANT(ENABLED); JSG_STATIC_CONSTANT(CIRCLE); }; }; KJ_TEST("constant members") { KJ_EXPECT(tStructure() == "(name = \"TestConstant\", members = [" "(constant = (name = \"ENABLED\", value = 1)), " "(constant = (name = \"CIRCLE\", value = 2))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestConstant\", " "tsRoot = false, disposable = false, asyncDisposable = false)"); } struct TestStruct { int a; bool b; JSG_STRUCT(a, b); }; KJ_TEST("struct reference") { KJ_EXPECT(tType() == "(structure = (name = \"TestStruct\", fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestStruct\"))"); } KJ_TEST("struct structure") { KJ_EXPECT(tStructure() == "(name = \"TestStruct\", members = [" "(property = (name = \"a\", type = (number = (name = \"int\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false)), " "(property = (name = \"b\", type = (boolt = void), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestStruct\", " "tsRoot = false, disposable = false, asyncDisposable = false)"); } struct TestSymbolTable: public jsg::Object { void acceptResource(const TestResource& resource) {}; void recursiveTypeFunction(const TestSymbolTable& table) {} JSG_RESOURCE_TYPE(TestSymbolTable) { JSG_METHOD(acceptResource); JSG_METHOD(recursiveTypeFunction); }; }; KJ_TEST("symbol table") { Builder builder((MockConfig())); auto type = builder.structure(); capnp::TextCodec codec; KJ_EXPECT(codec.encode(type) == "(name = \"TestSymbolTable\", members = [" "(method = (name = \"acceptResource\", returnType = (voidt = void), args = [(structure = (name = \"TestResource\", fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestResource\"))], static = false, fastApiCompatible = true)), " "(method = (name = \"recursiveTypeFunction\", returnType = (voidt = void), args = [(structure = (name = \"TestSymbolTable\", fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestSymbolTable\"))], static = false, fastApiCompatible = true))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestSymbolTable\", " "tsRoot = false, disposable = false, asyncDisposable = false)"); KJ_EXPECT(builder.structure("workerd::jsg::rtti::(anonymous namespace)::TestSymbolTable"_kj) != kj::none); KJ_EXPECT( builder.structure("workerd::jsg::rtti::(anonymous namespace)::TestResource"_kj) != kj::none); KJ_EXPECT(KJ_REQUIRE_NONNULL( builder.structure("workerd::jsg::rtti::(anonymous namespace)::TestResource"_kj)) .getMembers() .size() > 0); } struct TestTypeScriptResourceType: public jsg::Object { int getThing() { return 42; } JSG_RESOURCE_TYPE(TestTypeScriptResourceType) { JSG_READONLY_INSTANCE_PROPERTY(thing, getThing); JSG_TS_ROOT(); JSG_TS_DEFINE(interface Define {}); JSG_TS_OVERRIDE({ readonly thing: 42 }); }; }; struct TestTypeScriptStruct { int structThing; JSG_STRUCT(structThing); JSG_STRUCT_TS_ROOT(); JSG_STRUCT_TS_DEFINE(interface StructDefine {}); JSG_STRUCT_TS_OVERRIDE(RenamedStructThing { structThing: 42 }); }; struct TestTypeScriptStdioResourceType: public jsg::Object { int getThing() { return 42; } JSG_RESOURCE_TYPE(TestTypeScriptStdioResourceType) { JSG_READONLY_INSTANCE_PROPERTY(thing, getThing); JSG_TS_ROOT(); JSG_TS_DEFINE(interface Define { stdin: 0; stdout: 1; stderr: 2 }); JSG_TS_OVERRIDE({ stdin: 0; stdout: 1; stderr: 2 }); }; }; struct TestTypeScriptStdioStruct { int structThing; JSG_STRUCT(structThing); JSG_STRUCT_TS_ROOT(); JSG_STRUCT_TS_DEFINE(interface StructDefine { stdin: 0; stdout: 1; stderr: 2 }); JSG_STRUCT_TS_OVERRIDE(Renamed { stdin: 0; stdout: 1; stderr: 2 }); }; KJ_TEST("typescript macros") { KJ_EXPECT(tStructure() == "(name = \"TestTypeScriptResourceType\", members = [" "(property = (name = \"thing\", type = (number = (name = \"int\")), readonly = true, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = false))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestTypeScriptResourceType\", " "tsRoot = true, " "tsOverride = \"{ readonly thing: 42 }\", " "tsDefine = \"interface Define {}\", " "disposable = false, asyncDisposable = false)"); KJ_EXPECT(tStructure() == "(name = \"TestTypeScriptStruct\", members = [" "(property = (name = \"structThing\", type = (number = (name = \"int\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestTypeScriptStruct\", " "tsRoot = true, " "tsOverride = \"RenamedStructThing { structThing: 42 }\", " "tsDefine = \"interface StructDefine {}\", " "disposable = false, asyncDisposable = false)"); } KJ_TEST("typescript macros with stdio identifiers") { // `` on Darwin defines `stdin`/`stdout`/`stderr` as macros. The TS override and define // macros must capture the user's source verbatim so these tokens are not expanded. KJ_EXPECT(tStructure() == "(name = \"TestTypeScriptStdioResourceType\", members = [" "(property = (name = \"thing\", type = (number = (name = \"int\")), readonly = true, lazy = false, prototype = false, getterFastApiCompatible = true, setterFastApiCompatible = false))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestTypeScriptStdioResourceType\", " "tsRoot = true, " "tsOverride = \"{ stdin: 0; stdout: 1; stderr: 2 }\", " "tsDefine = \"interface Define { stdin: 0; stdout: 1; stderr: 2 }\", " "disposable = false, asyncDisposable = false)"); KJ_EXPECT(tStructure() == "(name = \"TestTypeScriptStdioStruct\", members = [" "(property = (name = \"structThing\", type = (number = (name = \"int\")), readonly = false, lazy = false, prototype = false, getterFastApiCompatible = false, setterFastApiCompatible = false))], " "iterable = false, asyncIterable = false, " "fullyQualifiedName = \"workerd::jsg::rtti::(anonymous namespace)::TestTypeScriptStdioStruct\", " "tsRoot = true, " "tsOverride = \"Renamed { stdin: 0; stdout: 1; stderr: 2 }\", " "tsDefine = \"interface StructDefine { stdin: 0; stdout: 1; stderr: 2 }\", " "disposable = false, asyncDisposable = false)"); } } // namespace } // namespace workerd::jsg::rtti