Skip to content

Commit 0a43d92

Browse files
committed
Rust schema generation.
1 parent f05f234 commit 0a43d92

8 files changed

Lines changed: 194 additions & 28 deletions

File tree

typesystem/evolution/type_evolution.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ template <typename FROM_NAMESPACE, typename FROM_TYPE, typename EVOLVER = Natura
6060
struct Evolve;
6161

6262
// Identity evolvers for primitive types.
63-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
63+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, currnt_type, rs_type, fs_type, md_type, ts_type) \
6464
template <typename FROM_NAMESPACE, typename EVOLVER> \
6565
struct Evolve<FROM_NAMESPACE, cpp_type, EVOLVER> { \
6666
template <typename> \

typesystem/primitive_types.dsl.h

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,36 @@ SOFTWARE.
2626

2727
// This file is used for mass registering of primitives type handlers in reflection and serialization routines.
2828
// Typical usecase:
29-
// #define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) ...
29+
// #define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, crnt_type, rust_type, fs_type, md_type, ts_type) ...
3030
// #include "primitive_types.dsl.h"
3131
// #undef CURRENT_DECLARE_PRIMITIVE_TYPE
3232

3333
#ifdef CURRENT_DECLARE_PRIMITIVE_TYPE // To pass `make check`.
3434

3535
// clang-format off
3636

37-
CURRENT_DECLARE_PRIMITIVE_TYPE(11, bool, Bool, "bool", "`true` or `false`", "boolean")
37+
CURRENT_DECLARE_PRIMITIVE_TYPE(11, bool, Bool, bool, "bool", "`true` or `false`", "boolean")
3838

39-
CURRENT_DECLARE_PRIMITIVE_TYPE(21, uint8_t, UInt8, "byte", "Integer (8-bit unsigned)", "number")
40-
CURRENT_DECLARE_PRIMITIVE_TYPE(22, uint16_t, UInt16, "uint16", "Integer (16-bit unsigned)", "number")
41-
CURRENT_DECLARE_PRIMITIVE_TYPE(23, uint32_t, UInt32, "uint32", "Integer (32-bit unsigned)", "number")
42-
CURRENT_DECLARE_PRIMITIVE_TYPE(24, uint64_t, UInt64, "uint64", "Integer (64-bit unsigned)", "number")
39+
CURRENT_DECLARE_PRIMITIVE_TYPE(21, uint8_t, UInt8, u8, "byte", "Integer (8-bit unsigned)", "number")
40+
CURRENT_DECLARE_PRIMITIVE_TYPE(22, uint16_t, UInt16, u16, "uint16", "Integer (16-bit unsigned)", "number")
41+
CURRENT_DECLARE_PRIMITIVE_TYPE(23, uint32_t, UInt32, u32, "uint32", "Integer (32-bit unsigned)", "number")
42+
CURRENT_DECLARE_PRIMITIVE_TYPE(24, uint64_t, UInt64, u64, "uint64", "Integer (64-bit unsigned)", "number")
4343

44-
CURRENT_DECLARE_PRIMITIVE_TYPE(31, int8_t, Int8, "sbyte", "Integer (8-bit signed)", "number")
45-
CURRENT_DECLARE_PRIMITIVE_TYPE(32, int16_t, Int16, "int16", "Integer (16-bit signed)", "number")
46-
CURRENT_DECLARE_PRIMITIVE_TYPE(33, int32_t, Int32, "int32", "Integer (32-bit signed)", "number")
47-
CURRENT_DECLARE_PRIMITIVE_TYPE(34, int64_t, Int64, "int64", "Integer (64-bit signed)", "number")
44+
CURRENT_DECLARE_PRIMITIVE_TYPE(31, int8_t, Int8, i8, "sbyte", "Integer (8-bit signed)", "number")
45+
CURRENT_DECLARE_PRIMITIVE_TYPE(32, int16_t, Int16, i16, "int16", "Integer (16-bit signed)", "number")
46+
CURRENT_DECLARE_PRIMITIVE_TYPE(33, int32_t, Int32, i32,"int32", "Integer (32-bit signed)", "number")
47+
CURRENT_DECLARE_PRIMITIVE_TYPE(34, int64_t, Int64, i64, "int64", "Integer (64-bit signed)", "number")
4848

49-
CURRENT_DECLARE_PRIMITIVE_TYPE(41, char, Char, "char", "Character", "number") // NOTE(dkorolev): Although F# chars are Unicode.
50-
CURRENT_DECLARE_PRIMITIVE_TYPE(42, std::string, String, "string", "String", "string")
49+
CURRENT_DECLARE_PRIMITIVE_TYPE(41, char, Char, char, "char", "Character", "number") // NOTE(dkorolev): Although F# chars are Unicode.
50+
CURRENT_DECLARE_PRIMITIVE_TYPE(42, std::string, String, String, "string", "String", "string")
5151

52-
CURRENT_DECLARE_PRIMITIVE_TYPE(51, float, Float, "float", "Number (floating point, single precision)", "number")
53-
CURRENT_DECLARE_PRIMITIVE_TYPE(52, double, Double, "double", "Number (floating point, double precision)", "number")
52+
CURRENT_DECLARE_PRIMITIVE_TYPE(51, float, Float, f32, "float", "Number (floating point, single precision)", "number")
53+
CURRENT_DECLARE_PRIMITIVE_TYPE(52, double, Double, f64, "double", "Number (floating point, double precision)", "number")
5454

5555
CURRENT_DECLARE_PRIMITIVE_TYPE(
56-
61, std::chrono::microseconds, Microseconds, "int64 // microseconds.", "Time (microseconds since epoch)", "number")
56+
61, std::chrono::microseconds, Microseconds, i64, "int64 // microseconds.", "Time (microseconds since epoch)", "number")
5757
CURRENT_DECLARE_PRIMITIVE_TYPE(
58-
62, std::chrono::milliseconds, Milliseconds, "int64 // milliseconds.", "Time (milliseconds since epoch)", "number")
58+
62, std::chrono::milliseconds, Milliseconds, i64, "int64 // milliseconds.", "Time (milliseconds since epoch)", "number")
5959

6060
// clang-format on
6161

typesystem/reflection/reflection.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ struct RecursiveTypeTraverser {
132132
fields_list_t& fields_;
133133
};
134134

135-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
135+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rs_type, fs_type, md_type, typescript_type) \
136136
TypeID operator()(TypeSelector<cpp_type>) { return TypeID::current_type; }
137137
#include "../primitive_types.dsl.h"
138138
#undef CURRENT_DECLARE_PRIMITIVE_TYPE
@@ -371,9 +371,9 @@ struct ReflectorImpl {
371371

372372
size_t KnownTypesCountForUnitTest() const { return map_.size(); }
373373

374-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
375-
ReflectedType operator()(TypeSelector<cpp_type>) { \
376-
return ReflectedType(ReflectedType_Primitive(TypeID::current_type)); \
374+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rs_type, fs_type, md_type, ts_type) \
375+
ReflectedType operator()(TypeSelector<cpp_type>) { \
376+
return ReflectedType(ReflectedType_Primitive(TypeID::current_type)); \
377377
}
378378
#include "../primitive_types.dsl.h"
379379
#undef CURRENT_DECLARE_PRIMITIVE_TYPE

typesystem/reflection/types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ constexpr uint64_t TYPEID_CYCLIC_DEPENDENCY_TYPE = TYPEID_TYPE_RANGE * TYPEID_CY
9090

9191
// clang-format off
9292
CURRENT_ENUM(TypeID, uint64_t) {
93-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
93+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rs_type, fs_type, md_type, ts_type) \
9494
current_type = TYPEID_BASIC_TYPE + typeid_index,
9595
#include "../primitive_types.dsl.h"
9696
#undef CURRENT_DECLARE_PRIMITIVE_TYPE

typesystem/schema/schema.h

Lines changed: 168 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@ CURRENT_STRUCT(NamespaceToExpose) {
6666
// TODO(dkorolev): Refactor `PrimitiveTypesList` to avoid copy-pasting of `operator()(const *_Primitive& p)`.
6767
struct PrimitiveTypesListImpl final {
6868
std::map<TypeID, std::string> cpp_name;
69+
std::map<TypeID, std::string> rust_name;
6970
std::map<TypeID, std::string> fsharp_name;
7071
std::map<TypeID, std::string> markdown_name;
7172
std::map<TypeID, std::string> typescript_name;
7273
PrimitiveTypesListImpl() {
73-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
74+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rstype, fs_type, md_type, ts_type) \
7475
cpp_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = #cpp_type; \
76+
rust_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = #rstype; \
7577
fsharp_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = fs_type; \
7678
markdown_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = md_type; \
77-
typescript_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = typescript_type;
79+
typescript_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = ts_type;
7880
#include "../primitive_types.dsl.h"
7981
#undef CURRENT_DECLARE_PRIMITIVE_TYPE
8082
}
@@ -95,6 +97,7 @@ enum class Language : int {
9597
Current, // C++, `CURRENT_STRUCT`-s.
9698
CPP, // C++, native `struct`-s.
9799
FSharp, // F#.
100+
Rust, // Rust.
98101
Markdown, // [GitHub] Markdown.
99102
JSON, // A compact JSON we use to describe schema to third parties.
100103
TypeScript, // TypeScript.
@@ -853,6 +856,167 @@ struct LanguageSyntaxImpl<Language::FSharp> final {
853856
// LCOV_EXCL_STOP
854857
};
855858

859+
template <>
860+
struct LanguageSyntaxImpl<Language::Rust> final {
861+
static std::string Header(const std::string&) {
862+
return "#![allow(unused_imports)]\n"
863+
"use serde::{Deserialize, Serialize};\n"
864+
"use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};\n";
865+
}
866+
867+
static std::string Footer(const std::string&) { return ""; }
868+
869+
static std::string SanitizeRustSymbol(const std::string& unsanitized_name) {
870+
// TODO(dkorolev): Definitely not a complete list.
871+
static std::set<std::string> fsharp_reserved_symbols{"type","pub","in"};
872+
return fsharp_reserved_symbols.count(unsanitized_name) ? "r#" + unsanitized_name : unsanitized_name;
873+
}
874+
875+
struct FullSchemaPrinter final {
876+
const std::map<TypeID, ReflectedType>& types_;
877+
std::ostream& os_;
878+
mutable std::unordered_set<TypeID, GenericHashFunction<TypeID>>
879+
empty_structs_; // To not print the type of a DU case for empty structs.
880+
881+
std::string TypeName(TypeID type_id) const {
882+
const auto cit = types_.find(type_id);
883+
if (cit == types_.end()) {
884+
return "UNKNOWN_TYPE_" + current::ToString(type_id); // LCOV_EXCL_LINE
885+
} else {
886+
struct RustTypeNamePrinter final {
887+
const FullSchemaPrinter& self_;
888+
std::ostringstream& oss_;
889+
890+
RustTypeNamePrinter(const FullSchemaPrinter& self, std::ostringstream& oss) : self_(self), oss_(oss) {}
891+
892+
// `operator()(...)`-s of this block print F# type name only, without the expansion.
893+
// They assume the declaration order is respected, and any dependencies have already been listed.
894+
void operator()(const ReflectedType_Primitive& p) const {
895+
const auto& globals = PrimitiveTypesList();
896+
if (globals.rust_name.count(p.type_id)) {
897+
oss_ << globals.rust_name.at(p.type_id);
898+
} else {
899+
oss_ << "UNKNOWN_BASIC_TYPE_" + current::ToString(p.type_id); // LCOV_EXCL_LINE
900+
}
901+
}
902+
void operator()(const ReflectedType_Enum& e) const { oss_ << SanitizeRustSymbol(e.name); }
903+
void operator()(const ReflectedType_Array& a) const {
904+
oss_ << "Vec<" << SanitizeRustSymbol(self_.TypeName(a.element_type)) << '>';
905+
}
906+
void operator()(const ReflectedType_Vector& v) const {
907+
oss_ << "Vec<" << SanitizeRustSymbol(self_.TypeName(v.element_type)) << '>';
908+
}
909+
void operator()(const ReflectedType_Map& m) const {
910+
// TODO(dkorolev): Use an ordered dictionary in .NET one day.
911+
oss_ << "BTreeMap<" << SanitizeRustSymbol(self_.TypeName(m.key_type)) << ", "
912+
<< self_.TypeName(m.value_type) << '>';
913+
}
914+
void operator()(const ReflectedType_UnorderedMap& m) const {
915+
oss_ << "HashMap<" << SanitizeRustSymbol(self_.TypeName(m.key_type)) << ", "
916+
<< self_.TypeName(m.value_type) << '>';
917+
}
918+
void operator()(const ReflectedType_Set& s) const {
919+
// TODO(dkorolev): Wrong!
920+
oss_ << "BTreeSet<" << self_.TypeName(s.value_type) << '>';
921+
}
922+
void operator()(const ReflectedType_UnorderedSet& s) const {
923+
oss_ << "HashSet<" << self_.TypeName(s.value_type) << '>';
924+
}
925+
void operator()(const ReflectedType_Pair& p) const {
926+
oss_ << '(' << SanitizeRustSymbol(self_.TypeName(p.first_type)) << ", "
927+
<< SanitizeRustSymbol(self_.TypeName(p.second_type)) << ')';
928+
}
929+
void operator()(const ReflectedType_Optional& o) const {
930+
oss_ << "Option<" << SanitizeRustSymbol(self_.TypeName(o.optional_type)) << '>';
931+
}
932+
void operator()(const ReflectedType_Variant& v) const { oss_ << SanitizeRustSymbol(v.name); }
933+
void operator()(const ReflectedType_Struct& s) const {
934+
oss_ << SanitizeRustSymbol(s.TemplateInnerTypeExpandedName());
935+
}
936+
};
937+
938+
std::ostringstream oss;
939+
cit->second.Call(RustTypeNamePrinter(*this, oss));
940+
return oss.str();
941+
}
942+
}
943+
944+
FullSchemaPrinter(const std::map<TypeID, ReflectedType>& types,
945+
std::ostream& os,
946+
const std::string&,
947+
const Optional<NamespaceToExpose>&)
948+
: types_(types), os_(os) {}
949+
950+
// `operator()`-s of this block print complete declarations of F# types.
951+
// The types that require complete declarations in F# are records and discriminated unions.
952+
void operator()(const ReflectedType_Primitive&) const {}
953+
void operator()(const ReflectedType_Enum& e) const {
954+
os_ << "\nTODO(dkorolev): type " << SanitizeRustSymbol(e.name) << " = " << TypeName(e.underlying_type) << '\n';
955+
}
956+
void operator()(const ReflectedType_Array&) const {}
957+
void operator()(const ReflectedType_Vector&) const {}
958+
void operator()(const ReflectedType_Pair&) const {}
959+
void operator()(const ReflectedType_Map&) const {}
960+
void operator()(const ReflectedType_UnorderedMap&) const {}
961+
void operator()(const ReflectedType_Set&) const {}
962+
void operator()(const ReflectedType_UnorderedSet&) const {}
963+
void operator()(const ReflectedType_Optional&) const {}
964+
void operator()(const ReflectedType_Variant& v) const {
965+
os_ << "\n"
966+
<< "#[derive(Debug, Serialize, Deserialize)]\n"
967+
<< "pub enum " << v.name << " {\n";
968+
for (TypeID c : v.cases) {
969+
const auto name = TypeName(c);
970+
const auto& t = types_.at(c);
971+
CURRENT_ASSERT(Exists<ReflectedType_Struct>(t) || Exists<ReflectedType_Variant>(t)); // Must be one of.
972+
if (!empty_structs_.count(Value<ReflectedTypeBase>(t).type_id)) {
973+
os_ << " " << name << '(' << name << "),\n";
974+
}
975+
}
976+
os_ << "}\n";
977+
}
978+
979+
// When dumping a `CURRENT_STRUCT` as an F# record, since inheritance is not supported by Newtonsoft.JSON,
980+
// all base class fields are hoisted to the top of the record.
981+
void RecursivelyListStructFieldsForRust(std::ostringstream& os, const ReflectedType_Struct& s) const {
982+
if (Exists(s.super_id)) {
983+
RecursivelyListStructFieldsForRust(os, Value<ReflectedType_Struct>(types_.at(Value(s.super_id))));
984+
}
985+
for (const auto& f : s.fields) {
986+
if (Exists(f.description)) {
987+
AppendAsMultilineCommentIndentedTwoSpaces(os, Value(f.description));
988+
}
989+
const auto& t = types_.at(f.type_id);
990+
if (Exists<ReflectedType_Struct>(t) || Exists<ReflectedType_Variant>(t)) {
991+
os << " pub " << SanitizeRustSymbol(f.name) << ": Box<" << TypeName(f.type_id) << ">,\n";
992+
} else {
993+
os << " pub " << SanitizeRustSymbol(f.name) << ": " << TypeName(f.type_id) << ",\n";
994+
}
995+
}
996+
}
997+
void operator()(const ReflectedType_Struct& s) const {
998+
std::ostringstream os;
999+
RecursivelyListStructFieldsForRust(os, s);
1000+
const std::string fields = os.str();
1001+
if (!fields.empty()) {
1002+
os_ << "\n"
1003+
<< "#[derive(Debug, Serialize, Deserialize)]\n"
1004+
<< "pub struct " << s.TemplateInnerTypeExpandedName() << " {\n"
1005+
<< fields
1006+
<< "}\n";
1007+
} else {
1008+
empty_structs_.insert(s.type_id);
1009+
}
1010+
}
1011+
}; // struct LanguageSyntax<Language::Rust>::FullSchemaPrinter
1012+
1013+
// LCOV_EXCL_START
1014+
static std::string ErrorMessageWithTypeId(TypeID type_id, FullSchemaPrinter&) {
1015+
return "#error \"Unknown struct with `type_id` = " + current::ToString(type_id) + "\"\n";
1016+
}
1017+
// LCOV_EXCL_STOP
1018+
};
1019+
8561020
template <>
8571021
struct LanguageSyntaxImpl<Language::Markdown> final {
8581022
static std::string Header(const std::string&) { return "# Data Dictionary\n"; }
@@ -1641,6 +1805,8 @@ struct ToStringImpl<reflection::Language, false, true> final {
16411805
return "cpp";
16421806
case reflection::Language::FSharp:
16431807
return "fs";
1808+
case reflection::Language::Rust:
1809+
return "rs";
16441810
case reflection::Language::Markdown:
16451811
return "md";
16461812
case reflection::Language::JSON:

typesystem/schema/test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ TEST(Schema, LanguageEnumIteration) {
425425
for (auto l = Language::begin; l != Language::end; ++l) {
426426
s.push_back(current::ToString(l));
427427
}
428-
EXPECT_EQ("internal_json h cpp fs md json ts", current::strings::Join(s, ' '));
428+
EXPECT_EQ("internal_json h cpp fs rs md json ts", current::strings::Join(s, ' '));
429429
}
430430

431431
namespace schema_test {
@@ -442,7 +442,7 @@ TEST(Schema, LanguageEnumCompileTimeForEach) {
442442
auto it = schema_test::LanguagesIterator();
443443
EXPECT_EQ("", current::strings::Join(it.s, ' '));
444444
current::reflection::ForEachLanguage(it);
445-
EXPECT_EQ("internal_json h cpp fs md json ts", current::strings::Join(it.s, ' '));
445+
EXPECT_EQ("internal_json h cpp fs rs md json ts", current::strings::Join(it.s, ' '));
446446
}
447447

448448
#define SMOKE_TEST_TEMPLATES_NAMESPACE smoke_test_templates_namespace_native

typesystem/serialization/json/primitives.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct JSONValueAssignerImpl<std::chrono::milliseconds> {
5858
};
5959
} // namespace json
6060

61-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
61+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, currnt_type, rs_type, fs_type, md_type, ts_type) \
6262
template <class JSON_FORMAT> \
6363
struct SerializeImpl<json::JSONStringifier<JSON_FORMAT>, cpp_type> { \
6464
static void DoSerialize(json::JSONStringifier<JSON_FORMAT>& json_stringifier, copy_free<cpp_type> value) { \

typesystem/typename.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ struct CurrentTypeNameImpl<NF, T, false, false, true, false> {
155155
static std::string GetCurrentTypeName() { return reflection::EnumName<T>(); }
156156
};
157157

158-
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
158+
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rstype, fs_type, md_type, ts_type) \
159159
template <NameFormat NF> \
160160
struct CurrentTypeNameImpl<NF, cpp_type, false, false, false, false> { \
161161
static const char* GetCurrentTypeName() { return #cpp_type; } \

0 commit comments

Comments
 (0)