Add support of Optional<T> scalars to C++ code generator (#6092)

This commit is contained in:
Vladimir Glavnyy
2020-09-19 02:51:17 +07:00
committed by GitHub
parent 6228b66d3d
commit 8c67b5b129
19 changed files with 2471 additions and 133 deletions

View File

@@ -751,6 +751,12 @@ class CppGenerator : public BaseGenerator {
return ptr_type == "naked" ? "" : ".get()";
}
std::string GenOptionalNull() { return "flatbuffers::nullopt"; }
std::string GenOptionalDecl(const Type &type) {
return "flatbuffers::Optional<" + GenTypeBasic(type, true) + ">";
}
std::string GenTypeNative(const Type &type, bool invector,
const FieldDef &field) {
switch (type.base_type) {
@@ -788,7 +794,8 @@ class CppGenerator : public BaseGenerator {
return type_name + "Union";
}
default: {
return GenTypeBasic(type, true);
return field.IsScalarOptional() ? GenOptionalDecl(type)
: GenTypeBasic(type, true);
}
}
}
@@ -1581,17 +1588,17 @@ class CppGenerator : public BaseGenerator {
}
std::string GetDefaultScalarValue(const FieldDef &field, bool is_ctor) {
if (field.value.type.enum_def && IsScalar(field.value.type.base_type)) {
auto ev = field.value.type.enum_def->FindByValue(field.value.constant);
const auto &type = field.value.type;
if (type.enum_def && IsScalar(type.base_type)) {
auto ev = type.enum_def->FindByValue(field.value.constant);
if (ev) {
return WrapInNameSpace(field.value.type.enum_def->defined_namespace,
GetEnumValUse(*field.value.type.enum_def, *ev));
return WrapInNameSpace(type.enum_def->defined_namespace,
GetEnumValUse(*type.enum_def, *ev));
} else {
return GenUnderlyingCast(
field, true,
NumToStringCpp(field.value.constant, field.value.type.base_type));
field, true, NumToStringCpp(field.value.constant, type.base_type));
}
} else if (field.value.type.base_type == BASE_TYPE_BOOL) {
} else if (type.base_type == BASE_TYPE_BOOL) {
return field.value.constant == "0" ? "false" : "true";
} else if (field.attributes.Lookup("cpp_type")) {
if (is_ctor) {
@@ -1604,7 +1611,8 @@ class CppGenerator : public BaseGenerator {
return "0";
}
} else {
return GenDefaultConstant(field);
return field.IsScalarOptional() ? GenOptionalNull()
: GenDefaultConstant(field);
}
}
@@ -1629,8 +1637,14 @@ class CppGenerator : public BaseGenerator {
}
code_.SetValue("PARAM_VALUE", "nullptr");
} else {
code_.SetValue("PARAM_TYPE", GenTypeWire(field.value.type, " ", true));
code_.SetValue("PARAM_VALUE", GetDefaultScalarValue(field, false));
const auto &type = field.value.type;
if (field.IsScalarOptional()) {
code_.SetValue("PARAM_TYPE", GenOptionalDecl(type));
code_.SetValue("PARAM_VALUE", GenOptionalNull());
} else {
code_.SetValue("PARAM_TYPE", GenTypeWire(type, " ", true));
code_.SetValue("PARAM_VALUE", GetDefaultScalarValue(field, false));
}
}
code_ += "{{PRE}}{{PARAM_TYPE}}{{PARAM_NAME}} = {{PARAM_VALUE}}\\";
}
@@ -1692,7 +1706,9 @@ class CppGenerator : public BaseGenerator {
auto native_default = field.attributes.Lookup("native_default");
// Scalar types get parsed defaults, raw pointers get nullptrs.
if (IsScalar(field.value.type.base_type)) {
if (!initializer_list.empty()) { initializer_list += ",\n "; }
if (!initializer_list.empty()) {
initializer_list += ",\n ";
}
initializer_list += Name(field);
initializer_list +=
"(" +
@@ -1906,16 +1922,6 @@ class CppGenerator : public BaseGenerator {
}
}
std::string GetFieldAccessor(const FieldDef &field) const {
const auto &type = field.value.type;
if (IsScalar(type.base_type))
return "GetField<";
else if (IsStruct(type))
return "GetStruct<";
else
return "GetPointer<";
}
void GenTableUnionAsGetters(const FieldDef &field) {
const auto &type = field.value.type;
auto u = type.enum_def;
@@ -1950,33 +1956,47 @@ class CppGenerator : public BaseGenerator {
}
void GenTableFieldGetter(const FieldDef &field) {
const auto& type = field.value.type;
const bool is_scalar = IsScalar(type.base_type);
const auto &type = field.value.type;
const auto offset_str = GenFieldOffsetName(field);
// Call a different accessor for pointers, that indirects.
auto accessor = GetFieldAccessor(field);
auto offset_str = GenFieldOffsetName(field);
auto offset_type = GenTypeGet(type, "", "const ", " *", false);
auto call = accessor + offset_type + ">(" + offset_str;
// Default value as second arg for non-pointer types.
if (is_scalar) { call += ", " + GenDefaultConstant(field); }
call += ")";
std::string afterptr = " *" + NullableExtension();
GenComment(field.doc_comment, " ");
code_.SetValue("FIELD_TYPE", GenTypeGet(type, " ", "const ",
afterptr.c_str(), true));
code_.SetValue("FIELD_VALUE", GenUnderlyingCast(field, true, call));
code_.SetValue("NULLABLE_EXT", NullableExtension());
// Call a different accessor for pointers, that indirects.
if (false == field.IsScalarOptional()) {
const bool is_scalar = IsScalar(type.base_type);
std::string accessor;
if (is_scalar)
accessor = "GetField<";
else if (IsStruct(type))
accessor = "GetStruct<";
else
accessor = "GetPointer<";
auto offset_type = GenTypeGet(type, "", "const ", " *", false);
auto call = accessor + offset_type + ">(" + offset_str;
// Default value as second arg for non-pointer types.
if (is_scalar) { call += ", " + GenDefaultConstant(field); }
call += ")";
code_ += " {{FIELD_TYPE}}{{FIELD_NAME}}() const {";
code_ += " return {{FIELD_VALUE}};";
code_ += " }";
if (type.base_type == BASE_TYPE_UNION) {
GenTableUnionAsGetters(field);
std::string afterptr = " *" + NullableExtension();
code_.SetValue("FIELD_TYPE",
GenTypeGet(type, " ", "const ", afterptr.c_str(), true));
code_.SetValue("FIELD_VALUE", GenUnderlyingCast(field, true, call));
code_.SetValue("NULLABLE_EXT", NullableExtension());
code_ += " {{FIELD_TYPE}}{{FIELD_NAME}}() const {";
code_ += " return {{FIELD_VALUE}};";
code_ += " }";
}
else {
auto wire_type = GenTypeBasic(type, false);
auto face_type = GenTypeBasic(type, true);
auto opt_value = "GetOptional<" + wire_type + ", " + face_type + ">(" +
offset_str + ")";
code_.SetValue("FIELD_TYPE", GenOptionalDecl(type));
code_ += " {{FIELD_TYPE}} {{FIELD_NAME}}() const {";
code_ += " return " + opt_value + ";";
code_ += " }";
}
if (type.base_type == BASE_TYPE_UNION) { GenTableUnionAsGetters(field); }
}
void GenTableFieldSetter(const FieldDef &field) {
@@ -1984,7 +2004,7 @@ class CppGenerator : public BaseGenerator {
const bool is_scalar = IsScalar(type.base_type);
if (is_scalar && IsUnion(type))
return; // changing of a union's type is forbidden
auto accessor = GetFieldAccessor(field);
auto offset_str = GenFieldOffsetName(field);
if (is_scalar) {
const auto wire_type = GenTypeWire(type, "", false);
@@ -1993,18 +2013,23 @@ class CppGenerator : public BaseGenerator {
code_.SetValue("FIELD_TYPE", GenTypeBasic(type, true));
code_.SetValue("FIELD_VALUE",
GenUnderlyingCast(field, false, "_" + Name(field)));
code_.SetValue("DEFAULT_VALUE", GenDefaultConstant(field));
code_ +=
" bool mutate_{{FIELD_NAME}}({{FIELD_TYPE}} "
"_{{FIELD_NAME}}) {";
code_ +=
" return {{SET_FN}}({{OFFSET_NAME}}, {{FIELD_VALUE}}, "
"{{DEFAULT_VALUE}});";
if (false == field.IsScalarOptional()) {
code_.SetValue("DEFAULT_VALUE", GenDefaultConstant(field));
code_ +=
" return {{SET_FN}}({{OFFSET_NAME}}, {{FIELD_VALUE}}, "
"{{DEFAULT_VALUE}});";
} else {
code_ += " return {{SET_FN}}({{OFFSET_NAME}}, {{FIELD_VALUE}});";
}
code_ += " }";
} else {
auto postptr = " *" + NullableExtension();
auto wire_type = GenTypeGet(type, " ", "", postptr.c_str(), true);
std::string accessor = IsStruct(type) ? "GetStruct<" : "GetPointer<";
auto underlying = accessor + wire_type + ">(" + offset_str + ")";
code_.SetValue("FIELD_TYPE", wire_type);
code_.SetValue("FIELD_VALUE", GenUnderlyingCast(field, true, underlying));
@@ -2073,7 +2098,7 @@ class CppGenerator : public BaseGenerator {
// Deprecated fields won't be accessible.
continue;
}
code_.SetValue("FIELD_NAME", Name(field));
GenTableFieldGetter(field);
if (opts_.mutable_buffer) {
@@ -2219,43 +2244,44 @@ class CppGenerator : public BaseGenerator {
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
const auto &field = **it;
if (!field.deprecated) {
const bool is_scalar = IsScalar(field.value.type.base_type);
const bool is_string = field.value.type.base_type == BASE_TYPE_STRING;
const bool is_vector = field.value.type.base_type == BASE_TYPE_VECTOR;
if (is_string || is_vector) { has_string_or_vector_fields = true; }
if (field.deprecated)
continue;
const bool is_scalar = IsScalar(field.value.type.base_type);
const bool is_default_scalar = is_scalar && !field.IsScalarOptional();
const bool is_string = field.value.type.base_type == BASE_TYPE_STRING;
const bool is_vector = field.value.type.base_type == BASE_TYPE_VECTOR;
if (is_string || is_vector) { has_string_or_vector_fields = true; }
std::string offset = GenFieldOffsetName(field);
std::string name = GenUnderlyingCast(field, false, Name(field));
std::string value = is_scalar ? GenDefaultConstant(field) : "";
std::string offset = GenFieldOffsetName(field);
std::string name = GenUnderlyingCast(field, false, Name(field));
std::string value = is_default_scalar ? GenDefaultConstant(field) : "";
// Generate accessor functions of the form:
// void add_name(type name) {
// fbb_.AddElement<type>(offset, name, default);
// }
code_.SetValue("FIELD_NAME", Name(field));
code_.SetValue("FIELD_TYPE", GenTypeWire(field.value.type, " ", true));
code_.SetValue("ADD_OFFSET", Name(struct_def) + "::" + offset);
code_.SetValue("ADD_NAME", name);
code_.SetValue("ADD_VALUE", value);
if (is_scalar) {
const auto type = GenTypeWire(field.value.type, "", false);
code_.SetValue("ADD_FN", "AddElement<" + type + ">");
} else if (IsStruct(field.value.type)) {
code_.SetValue("ADD_FN", "AddStruct");
} else {
code_.SetValue("ADD_FN", "AddOffset");
}
code_ += " void add_{{FIELD_NAME}}({{FIELD_TYPE}}{{FIELD_NAME}}) {";
code_ += " fbb_.{{ADD_FN}}(\\";
if (is_scalar) {
code_ += "{{ADD_OFFSET}}, {{ADD_NAME}}, {{ADD_VALUE}});";
} else {
code_ += "{{ADD_OFFSET}}, {{ADD_NAME}});";
}
code_ += " }";
// Generate accessor functions of the form:
// void add_name(type name) {
// fbb_.AddElement<type>(offset, name, default);
// }
code_.SetValue("FIELD_NAME", Name(field));
code_.SetValue("FIELD_TYPE", GenTypeWire(field.value.type, " ", true));
code_.SetValue("ADD_OFFSET", Name(struct_def) + "::" + offset);
code_.SetValue("ADD_NAME", name);
code_.SetValue("ADD_VALUE", value);
if (is_scalar) {
const auto type = GenTypeWire(field.value.type, "", false);
code_.SetValue("ADD_FN", "AddElement<" + type + ">");
} else if (IsStruct(field.value.type)) {
code_.SetValue("ADD_FN", "AddStruct");
} else {
code_.SetValue("ADD_FN", "AddOffset");
}
code_ += " void add_{{FIELD_NAME}}({{FIELD_TYPE}}{{FIELD_NAME}}) {";
code_ += " fbb_.{{ADD_FN}}(\\";
if (is_default_scalar) {
code_ += "{{ADD_OFFSET}}, {{ADD_NAME}}, {{ADD_VALUE}});";
} else {
code_ += "{{ADD_OFFSET}}, {{ADD_NAME}});";
}
code_ += " }";
}
// Builder constructor
@@ -2307,7 +2333,13 @@ class CppGenerator : public BaseGenerator {
if (!field.deprecated && (!struct_def.sortbysize ||
size == SizeOf(field.value.type.base_type))) {
code_.SetValue("FIELD_NAME", Name(field));
code_ += " builder_.add_{{FIELD_NAME}}({{FIELD_NAME}});";
if (field.IsScalarOptional()) {
code_ +=
" if({{FIELD_NAME}}) { "
"builder_.add_{{FIELD_NAME}}(*{{FIELD_NAME}}); }";
} else {
code_ += " builder_.add_{{FIELD_NAME}}({{FIELD_NAME}});";
}
}
}
}

View File

@@ -743,6 +743,34 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
return Error(
"default values currently only supported for scalars in tables");
}
// Mark the optional scalars. Note that a side effect of ParseSingleValue is
// fixing field->value.constant to null.
if (IsScalar(type.base_type)) {
field->optional = (field->value.constant == "null");
if (field->optional) {
if (type.enum_def && type.enum_def->Lookup("null")) {
FLATBUFFERS_ASSERT(IsInteger(type.base_type));
return Error(
"the default 'null' is reserved for declaring optional scalar "
"fields, it conflicts with declaration of enum '" +
type.enum_def->name + "'.");
}
if (field->attributes.Lookup("key")) {
return Error("only a non-optional scalar field can be used as a 'key' field");
}
if (!SupportsOptionalScalars()) {
return Error(
"Optional scalars are not yet supported in at least one the of "
"the specified programming languages.");
}
}
} else {
// For nonscalars, only required fields are non-optional.
// At least until https://github.com/google/flatbuffers/issues/6053
field->optional = !field->required;
}
// Append .0 if the value has not it (skip hex and scientific floats).
// This suffix needed for generated C++ code.
if (IsFloat(type.base_type)) {
@@ -772,6 +800,7 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
} else {
// All unions should have the NONE ("0") enum value.
auto in_enum = type.enum_def->attributes.Lookup("bit_flags") ||
field->IsScalarOptional() ||
type.enum_def->FindByValue(field->value.constant);
if (false == in_enum)
return Error("default value of " + field->value.constant +
@@ -833,17 +862,7 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
if (field->required && (struct_def.fixed || IsScalar(type.base_type)))
return Error("only non-scalar fields in tables may be 'required'");
// Mark the optional scalars. Note that a side effect of ParseSingleValue is
// fixing field->value.constant to null.
if (IsScalar(type.base_type)) {
field->optional = (field->value.constant == "null");
if (field->optional && !SupportsOptionalScalars()) {
return Error(
"Optional scalars are not yet supported in at least one the of "
"the specified programming languages."
);
}
} else {
if(!IsScalar(type.base_type)) {
// For nonscalars, only required fields are non-optional.
// At least until https://github.com/google/flatbuffers/issues/6053
field->optional = !field->required;
@@ -2260,11 +2279,17 @@ CheckedError Parser::CheckClash(std::vector<FieldDef *> &fields,
return NoError();
}
bool Parser::SupportsOptionalScalars(const flatbuffers::IDLOptions &opts){
static FLATBUFFERS_CONSTEXPR unsigned long supported_langs =
IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kLobster |
IDLOptions::kKotlin | IDLOptions::kCpp;
unsigned long langs = opts.lang_to_generate;
return (langs > 0 && langs < IDLOptions::kMAX) && !(langs & ~supported_langs);
}
bool Parser::SupportsOptionalScalars() const {
return !(opts.lang_to_generate &
~(IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kLobster |
IDLOptions::kKotlin));
// Check in general if a language isn't specified.
return opts.lang_to_generate == 0 || SupportsOptionalScalars(opts);
}
bool Parser::SupportsAdvancedUnionFeatures() const {