Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion typesystem/evolution/type_evolution.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ template <typename FROM_NAMESPACE, typename FROM_TYPE, typename EVOLVER = Natura
struct Evolve;

// Identity evolvers for primitive types.
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, currnt_type, rs_type, fs_type, md_type, ts_type) \
template <typename FROM_NAMESPACE, typename EVOLVER> \
struct Evolve<FROM_NAMESPACE, cpp_type, EVOLVER> { \
template <typename> \
Expand Down
32 changes: 16 additions & 16 deletions typesystem/primitive_types.dsl.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,36 @@ SOFTWARE.

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

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

// clang-format off

CURRENT_DECLARE_PRIMITIVE_TYPE(11, bool, Bool, "bool", "`true` or `false`", "boolean")
CURRENT_DECLARE_PRIMITIVE_TYPE(11, bool, Bool, bool, "bool", "`true` or `false`", "boolean")

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

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

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

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

CURRENT_DECLARE_PRIMITIVE_TYPE(
61, std::chrono::microseconds, Microseconds, "int64 // microseconds.", "Time (microseconds since epoch)", "number")
61, std::chrono::microseconds, Microseconds, i64, "int64 // microseconds.", "Time (microseconds since epoch)", "number")
CURRENT_DECLARE_PRIMITIVE_TYPE(
62, std::chrono::milliseconds, Milliseconds, "int64 // milliseconds.", "Time (milliseconds since epoch)", "number")
62, std::chrono::milliseconds, Milliseconds, i64, "int64 // milliseconds.", "Time (milliseconds since epoch)", "number")

// clang-format on

Expand Down
8 changes: 4 additions & 4 deletions typesystem/reflection/reflection.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct RecursiveTypeTraverser {
fields_list_t& fields_;
};

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

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

#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
ReflectedType operator()(TypeSelector<cpp_type>) { \
return ReflectedType(ReflectedType_Primitive(TypeID::current_type)); \
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rs_type, fs_type, md_type, ts_type) \
ReflectedType operator()(TypeSelector<cpp_type>) { \
return ReflectedType(ReflectedType_Primitive(TypeID::current_type)); \
}
#include "../primitive_types.dsl.h"
#undef CURRENT_DECLARE_PRIMITIVE_TYPE
Expand Down
2 changes: 1 addition & 1 deletion typesystem/reflection/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ constexpr uint64_t TYPEID_CYCLIC_DEPENDENCY_TYPE = TYPEID_TYPE_RANGE * TYPEID_CY

// clang-format off
CURRENT_ENUM(TypeID, uint64_t) {
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rs_type, fs_type, md_type, ts_type) \
current_type = TYPEID_BASIC_TYPE + typeid_index,
#include "../primitive_types.dsl.h"
#undef CURRENT_DECLARE_PRIMITIVE_TYPE
Expand Down
170 changes: 168 additions & 2 deletions typesystem/schema/schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,17 @@ CURRENT_STRUCT(NamespaceToExpose) {
// TODO(dkorolev): Refactor `PrimitiveTypesList` to avoid copy-pasting of `operator()(const *_Primitive& p)`.
struct PrimitiveTypesListImpl final {
std::map<TypeID, std::string> cpp_name;
std::map<TypeID, std::string> rust_name;
std::map<TypeID, std::string> fsharp_name;
std::map<TypeID, std::string> markdown_name;
std::map<TypeID, std::string> typescript_name;
PrimitiveTypesListImpl() {
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, rstype, fs_type, md_type, ts_type) \
cpp_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = #cpp_type; \
rust_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = #rstype; \
fsharp_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = fs_type; \
markdown_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = md_type; \
typescript_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = typescript_type;
typescript_name[static_cast<TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = ts_type;
#include "../primitive_types.dsl.h"
#undef CURRENT_DECLARE_PRIMITIVE_TYPE
}
Expand All @@ -95,6 +97,7 @@ enum class Language : int {
Current, // C++, `CURRENT_STRUCT`-s.
CPP, // C++, native `struct`-s.
FSharp, // F#.
Rust, // Rust.
Markdown, // [GitHub] Markdown.
JSON, // A compact JSON we use to describe schema to third parties.
TypeScript, // TypeScript.
Expand Down Expand Up @@ -853,6 +856,167 @@ struct LanguageSyntaxImpl<Language::FSharp> final {
// LCOV_EXCL_STOP
};

template <>
struct LanguageSyntaxImpl<Language::Rust> final {
static std::string Header(const std::string&) {
return "#![allow(unused_imports)]\n"
"use serde::{Deserialize, Serialize};\n"
"use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};\n";
}

static std::string Footer(const std::string&) { return ""; }

static std::string SanitizeRustSymbol(const std::string& unsanitized_name) {
// TODO(dkorolev): Definitely not a complete list.
static std::set<std::string> fsharp_reserved_symbols{"type","pub","in"};
return fsharp_reserved_symbols.count(unsanitized_name) ? "r#" + unsanitized_name : unsanitized_name;
}

struct FullSchemaPrinter final {
const std::map<TypeID, ReflectedType>& types_;
std::ostream& os_;
mutable std::unordered_set<TypeID, GenericHashFunction<TypeID>>
empty_structs_; // To not print the type of a DU case for empty structs.

std::string TypeName(TypeID type_id) const {
const auto cit = types_.find(type_id);
if (cit == types_.end()) {
return "UNKNOWN_TYPE_" + current::ToString(type_id); // LCOV_EXCL_LINE
} else {
struct RustTypeNamePrinter final {
const FullSchemaPrinter& self_;
std::ostringstream& oss_;

RustTypeNamePrinter(const FullSchemaPrinter& self, std::ostringstream& oss) : self_(self), oss_(oss) {}

// `operator()(...)`-s of this block print F# type name only, without the expansion.
// They assume the declaration order is respected, and any dependencies have already been listed.
void operator()(const ReflectedType_Primitive& p) const {
const auto& globals = PrimitiveTypesList();
if (globals.rust_name.count(p.type_id)) {
oss_ << globals.rust_name.at(p.type_id);
} else {
oss_ << "UNKNOWN_BASIC_TYPE_" + current::ToString(p.type_id); // LCOV_EXCL_LINE
}
}
void operator()(const ReflectedType_Enum& e) const { oss_ << SanitizeRustSymbol(e.name); }
void operator()(const ReflectedType_Array& a) const {
oss_ << "Vec<" << SanitizeRustSymbol(self_.TypeName(a.element_type)) << '>';
}
void operator()(const ReflectedType_Vector& v) const {
oss_ << "Vec<" << SanitizeRustSymbol(self_.TypeName(v.element_type)) << '>';
}
void operator()(const ReflectedType_Map& m) const {
// TODO(dkorolev): Use an ordered dictionary in .NET one day.
oss_ << "BTreeMap<" << SanitizeRustSymbol(self_.TypeName(m.key_type)) << ", "
<< self_.TypeName(m.value_type) << '>';
}
void operator()(const ReflectedType_UnorderedMap& m) const {
oss_ << "HashMap<" << SanitizeRustSymbol(self_.TypeName(m.key_type)) << ", "
<< self_.TypeName(m.value_type) << '>';
}
void operator()(const ReflectedType_Set& s) const {
// TODO(dkorolev): Wrong!
oss_ << "BTreeSet<" << self_.TypeName(s.value_type) << '>';
}
void operator()(const ReflectedType_UnorderedSet& s) const {
oss_ << "HashSet<" << self_.TypeName(s.value_type) << '>';
}
void operator()(const ReflectedType_Pair& p) const {
oss_ << '(' << SanitizeRustSymbol(self_.TypeName(p.first_type)) << ", "
<< SanitizeRustSymbol(self_.TypeName(p.second_type)) << ')';
}
void operator()(const ReflectedType_Optional& o) const {
oss_ << "Option<" << SanitizeRustSymbol(self_.TypeName(o.optional_type)) << '>';
}
void operator()(const ReflectedType_Variant& v) const { oss_ << SanitizeRustSymbol(v.name); }
void operator()(const ReflectedType_Struct& s) const {
oss_ << SanitizeRustSymbol(s.TemplateInnerTypeExpandedName());
}
};

std::ostringstream oss;
cit->second.Call(RustTypeNamePrinter(*this, oss));
return oss.str();
}
}

FullSchemaPrinter(const std::map<TypeID, ReflectedType>& types,
std::ostream& os,
const std::string&,
const Optional<NamespaceToExpose>&)
: types_(types), os_(os) {}

// `operator()`-s of this block print complete declarations of F# types.
// The types that require complete declarations in F# are records and discriminated unions.
void operator()(const ReflectedType_Primitive&) const {}
void operator()(const ReflectedType_Enum& e) const {
os_ << "\nTODO(dkorolev): type " << SanitizeRustSymbol(e.name) << " = " << TypeName(e.underlying_type) << '\n';
}
void operator()(const ReflectedType_Array&) const {}
void operator()(const ReflectedType_Vector&) const {}
void operator()(const ReflectedType_Pair&) const {}
void operator()(const ReflectedType_Map&) const {}
void operator()(const ReflectedType_UnorderedMap&) const {}
void operator()(const ReflectedType_Set&) const {}
void operator()(const ReflectedType_UnorderedSet&) const {}
void operator()(const ReflectedType_Optional&) const {}
void operator()(const ReflectedType_Variant& v) const {
os_ << "\n"
<< "#[derive(Debug, Serialize, Deserialize)]\n"
<< "pub enum " << v.name << " {\n";
for (TypeID c : v.cases) {
const auto name = TypeName(c);
const auto& t = types_.at(c);
CURRENT_ASSERT(Exists<ReflectedType_Struct>(t) || Exists<ReflectedType_Variant>(t)); // Must be one of.
if (!empty_structs_.count(Value<ReflectedTypeBase>(t).type_id)) {
os_ << " " << name << '(' << name << "),\n";
}
}
os_ << "}\n";
}

// When dumping a `CURRENT_STRUCT` as an F# record, since inheritance is not supported by Newtonsoft.JSON,
// all base class fields are hoisted to the top of the record.
void RecursivelyListStructFieldsForRust(std::ostringstream& os, const ReflectedType_Struct& s) const {
if (Exists(s.super_id)) {
RecursivelyListStructFieldsForRust(os, Value<ReflectedType_Struct>(types_.at(Value(s.super_id))));
}
for (const auto& f : s.fields) {
if (Exists(f.description)) {
AppendAsMultilineCommentIndentedTwoSpaces(os, Value(f.description));
}
const auto& t = types_.at(f.type_id);
if (Exists<ReflectedType_Struct>(t) || Exists<ReflectedType_Variant>(t)) {
os << " pub " << SanitizeRustSymbol(f.name) << ": Box<" << TypeName(f.type_id) << ">,\n";
} else {
os << " pub " << SanitizeRustSymbol(f.name) << ": " << TypeName(f.type_id) << ",\n";
}
}
}
void operator()(const ReflectedType_Struct& s) const {
std::ostringstream os;
RecursivelyListStructFieldsForRust(os, s);
const std::string fields = os.str();
if (!fields.empty()) {
os_ << "\n"
<< "#[derive(Debug, Serialize, Deserialize)]\n"
<< "pub struct " << s.TemplateInnerTypeExpandedName() << " {\n"
<< fields
<< "}\n";
} else {
empty_structs_.insert(s.type_id);
}
}
}; // struct LanguageSyntax<Language::Rust>::FullSchemaPrinter

// LCOV_EXCL_START
static std::string ErrorMessageWithTypeId(TypeID type_id, FullSchemaPrinter&) {
return "#error \"Unknown struct with `type_id` = " + current::ToString(type_id) + "\"\n";
}
// LCOV_EXCL_STOP
};

template <>
struct LanguageSyntaxImpl<Language::Markdown> final {
static std::string Header(const std::string&) { return "# Data Dictionary\n"; }
Expand Down Expand Up @@ -1641,6 +1805,8 @@ struct ToStringImpl<reflection::Language, false, true> final {
return "cpp";
case reflection::Language::FSharp:
return "fs";
case reflection::Language::Rust:
return "rs";
case reflection::Language::Markdown:
return "md";
case reflection::Language::JSON:
Expand Down
4 changes: 2 additions & 2 deletions typesystem/schema/test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ TEST(Schema, LanguageEnumIteration) {
for (auto l = Language::begin; l != Language::end; ++l) {
s.push_back(current::ToString(l));
}
EXPECT_EQ("internal_json h cpp fs md json ts", current::strings::Join(s, ' '));
EXPECT_EQ("internal_json h cpp fs rs md json ts", current::strings::Join(s, ' '));
}

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

#define SMOKE_TEST_TEMPLATES_NAMESPACE smoke_test_templates_namespace_native
Expand Down
2 changes: 1 addition & 1 deletion typesystem/serialization/json/primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct JSONValueAssignerImpl<std::chrono::milliseconds> {
};
} // namespace json

#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type) \
#define CURRENT_DECLARE_PRIMITIVE_TYPE(typeid_index, cpp_type, currnt_type, rs_type, fs_type, md_type, ts_type) \
template <class JSON_FORMAT> \
struct SerializeImpl<json::JSONStringifier<JSON_FORMAT>, cpp_type> { \
static void DoSerialize(json::JSONStringifier<JSON_FORMAT>& json_stringifier, copy_free<cpp_type> value) { \
Expand Down
2 changes: 1 addition & 1 deletion typesystem/typename.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ struct CurrentTypeNameImpl<NF, T, false, false, true, false> {
static std::string GetCurrentTypeName() { return reflection::EnumName<T>(); }
};

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