[Go] Make enums into real types, add String() (#5235)

* [Go] Make enums into real types, add String()

This changes the generated code for enums: instead of type aliases,
they're now distinct types, allowing for better type-checking. Some
client code may have to be changed to add casts.

Enum types now have a String() method, so they implement fmt.Stringer.

An EnumValues map is now generated, in addition to the existing
EnumNames map, to easily map strings to values.

Generated enum files are now gofmt-clean.

Fixes #5207

* use example.ColorGreen explicitly

* use valid enum value in mutation test, add new test for "invalid" enum

* add length check and comment
This commit is contained in:
David Reiss
2019-05-17 12:41:39 -07:00
committed by Robert
parent 8d86b5347f
commit 718ddea558
11 changed files with 328 additions and 94 deletions

View File

@@ -83,7 +83,7 @@ class GoGenerator : public BaseGenerator {
if (parser_.opts.one_file) {
one_file_code += enumcode;
} else {
if (!SaveType(**it, enumcode, false)) return false;
if (!SaveType(**it, enumcode, false, true)) return false;
}
}
@@ -95,13 +95,14 @@ class GoGenerator : public BaseGenerator {
if (parser_.opts.one_file) {
one_file_code += declcode;
} else {
if (!SaveType(**it, declcode, true)) return false;
if (!SaveType(**it, declcode, true, false)) return false;
}
}
if (parser_.opts.one_file) {
std::string code = "";
BeginFile(LastNamespacePart(go_namespace_), true, &code);
const bool is_enum = !parser_.enums_.vec.empty();
BeginFile(LastNamespacePart(go_namespace_), true, is_enum, &code);
code += one_file_code;
const std::string filename = GeneratedFileName(path_, file_name_);
return SaveFile(filename.c_str(), code, false);
@@ -140,7 +141,7 @@ class GoGenerator : public BaseGenerator {
code += "\n}\n\n";
}
// Construct the name of the type alias for this enum.
// Construct the name of the type for this enum.
std::string GetEnumTypeName(const EnumDef &enum_def) {
return WrapInNameSpaceAndTrack(cur_name_space_, GoIdentity(enum_def.name));
}
@@ -148,8 +149,8 @@ class GoGenerator : public BaseGenerator {
// Create a type for the enum values.
void GenEnumType(const EnumDef &enum_def, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "type " + GetEnumTypeName(enum_def) + " = ";
code += GenTypeBasic(enum_def.underlying_type) + "\n";
code += "type " + GetEnumTypeName(enum_def) + " ";
code += GenTypeBasic(enum_def.underlying_type) + "\n\n";
}
// Begin enum code with a class declaration.
@@ -160,12 +161,13 @@ class GoGenerator : public BaseGenerator {
// A single enum member.
void EnumMember(const EnumDef &enum_def, const EnumVal &ev,
std::string *code_ptr) {
const int max_name_length, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "\t";
code += enum_def.name;
code += ev.name;
code += " ";
code += std::string(max_name_length - ev.name.length(), ' ');
code += GetEnumTypeName(enum_def);
code += " = ";
code += enum_def.ToString(ev) + "\n";
@@ -177,7 +179,7 @@ class GoGenerator : public BaseGenerator {
code += ")\n\n";
}
// Begin enum name code.
// Begin enum name map.
void BeginEnumNames(const EnumDef &enum_def, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "var EnumNames";
@@ -187,22 +189,63 @@ class GoGenerator : public BaseGenerator {
// A single enum name member.
void EnumNameMember(const EnumDef &enum_def, const EnumVal ev,
std::string *code_ptr) {
const int max_name_length, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "\t";
code += enum_def.name;
code += ev.name;
code += ":\"";
code += ": ";
code += std::string(max_name_length - ev.name.length(), ' ');
code += "\"";
code += ev.name;
code += "\",\n";
}
// End enum name code.
// End enum name map.
void EndEnumNames(std::string *code_ptr) {
std::string &code = *code_ptr;
code += "}\n\n";
}
// Generate String() method on enum type.
void EnumStringer(const EnumDef &enum_def, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "func (v " + enum_def.name + ") String() string {\n";
code += "\tif s, ok := EnumNames" + enum_def.name + "[v]; ok {\n";
code += "\t\treturn s\n";
code += "\t}\n";
code += "\treturn \""+ enum_def.name;
code += "(\" + strconv.FormatInt(int64(v), 10) + \")\"\n";
code += "}\n\n";
}
// Begin enum value map.
void BeginEnumValues(const EnumDef &enum_def, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "var EnumValues";
code += enum_def.name;
code += " = map[string]" + GetEnumTypeName(enum_def) + "{\n";
}
// A single enum value member.
void EnumValueMember(const EnumDef &enum_def, const EnumVal ev,
const int max_name_length, std::string *code_ptr) {
std::string &code = *code_ptr;
code += "\t\"";
code += ev.name;
code += "\": ";
code += std::string(max_name_length - ev.name.length(), ' ');
code += enum_def.name;
code += ev.name;
code += ",\n";
}
// End enum value map.
void EndEnumValues(std::string *code_ptr) {
std::string &code = *code_ptr;
code += "}\n\n";
}
// Initialize a new struct or table from existing data.
void NewRootTypeFromBuffer(const StructDef &struct_def,
std::string *code_ptr) {
@@ -281,9 +324,11 @@ class GoGenerator : public BaseGenerator {
GenReceiver(struct_def, code_ptr);
code += " " + MakeCamel(field.name);
code += "() " + TypeName(field) + " {\n";
code += "\treturn " + getter;
code += "(rcv._tab.Pos + flatbuffers.UOffsetT(";
code += NumToString(field.value.offset) + "))\n}\n";
code += "\treturn " + CastToEnum(
field.value.type,
getter + "(rcv._tab.Pos + flatbuffers.UOffsetT(" +
NumToString(field.value.offset) + "))");
code += "\n}\n";
}
// Get the value of a table's scalar.
@@ -295,8 +340,9 @@ class GoGenerator : public BaseGenerator {
GenReceiver(struct_def, code_ptr);
code += " " + MakeCamel(field.name);
code += "() " + TypeName(field) + " ";
code += OffsetPrefix(field) + "\t\treturn " + getter;
code += "(o + rcv._tab.Pos)\n\t}\n";
code += OffsetPrefix(field) + "\t\treturn ";
code += CastToEnum(field.value.type, getter + "(o + rcv._tab.Pos)");
code += "\n\t}\n";
code += "\treturn " + GenConstant(field) + "\n";
code += "}\n\n";
}
@@ -364,7 +410,7 @@ class GoGenerator : public BaseGenerator {
std::string &code = *code_ptr;
GenReceiver(struct_def, code_ptr);
code += " " + MakeCamel(field.name) + "(";
code += "obj " + TypeName(field) + ") bool ";
code += "obj " + GenTypePointer(field.value.type) + ") bool ";
code += OffsetPrefix(field);
code += "\t\t" + GenGetter(field.value.type);
code += "(obj, o)\n\t\treturn true\n\t}\n";
@@ -395,8 +441,7 @@ class GoGenerator : public BaseGenerator {
code += "}\n\n";
}
// Get the value of a vector's non-struct member. Uses a named return
// argument to conveniently set the zero value for the result.
// Get the value of a vector's non-struct member.
void GetMemberOfVectorOfNonStruct(const StructDef &struct_def,
const FieldDef &field,
std::string *code_ptr) {
@@ -408,10 +453,11 @@ class GoGenerator : public BaseGenerator {
code += "(j int) " + TypeName(field) + " ";
code += OffsetPrefix(field);
code += "\t\ta := rcv._tab.Vector(o)\n";
code += "\t\treturn " + GenGetter(field.value.type) + "(";
code += "a + flatbuffers.UOffsetT(j*";
code += NumToString(InlineSize(vectortype)) + "))\n";
code += "\t}\n";
code += "\t\treturn " + CastToEnum(
field.value.type,
GenGetter(field.value.type) + "(a + flatbuffers.UOffsetT(j*" +
NumToString(InlineSize(vectortype)) + "))");
code += "\n\t}\n";
if (vectortype.base_type == BASE_TYPE_STRING) {
code += "\treturn nil\n";
} else if (vectortype.base_type == BASE_TYPE_BOOL) {
@@ -609,7 +655,8 @@ class GoGenerator : public BaseGenerator {
code += " Mutate" + MakeCamel(field.name);
code += "(n " + TypeName(field) + ") bool {\n\treturn " + setter;
code += "(rcv._tab.Pos+flatbuffers.UOffsetT(";
code += NumToString(field.value.offset) + "), n)\n}\n\n";
code += NumToString(field.value.offset) + "), ";
code += CastToBaseType(field.value.type, "n") + ")\n}\n\n";
}
// Mutate the value of a table's scalar.
@@ -622,7 +669,8 @@ class GoGenerator : public BaseGenerator {
GenReceiver(struct_def, code_ptr);
code += " Mutate" + MakeCamel(field.name);
code += "(n " + TypeName(field) + ") bool {\n\treturn ";
code += setter + "(" + NumToString(field.value.offset) + ", n)\n";
code += setter + "(" + NumToString(field.value.offset) + ", ";
code += CastToBaseType(field.value.type, "n") + ")\n";
code += "}\n\n";
}
@@ -641,7 +689,8 @@ class GoGenerator : public BaseGenerator {
code += "\t\ta := rcv._tab.Vector(o)\n";
code += "\t\treturn " + setter + "(";
code += "a+flatbuffers.UOffsetT(j*";
code += NumToString(InlineSize(vectortype)) + "), n)\n";
code += NumToString(InlineSize(vectortype)) + "), ";
code += CastToBaseType(vectortype, "n") + ")\n";
code += "\t}\n";
code += "\treturn false\n";
code += "}\n\n";
@@ -726,6 +775,7 @@ class GoGenerator : public BaseGenerator {
void GenEnum(const EnumDef &enum_def, std::string *code_ptr) {
if (enum_def.generated) return;
const int max_name_length = MaxNameLength(enum_def);
cur_name_space_ = enum_def.defined_namespace;
GenComment(enum_def.doc_comment, code_ptr, nullptr);
@@ -734,16 +784,26 @@ class GoGenerator : public BaseGenerator {
for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
auto &ev = **it;
GenComment(ev.doc_comment, code_ptr, nullptr, "\t");
EnumMember(enum_def, ev, code_ptr);
EnumMember(enum_def, ev, max_name_length, code_ptr);
}
EndEnum(code_ptr);
BeginEnumNames(enum_def, code_ptr);
for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
auto &ev = **it;
EnumNameMember(enum_def, ev, code_ptr);
EnumNameMember(enum_def, ev, max_name_length, code_ptr);
}
EndEnumNames(code_ptr);
BeginEnumValues(enum_def, code_ptr);
for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end();
++it) {
auto &ev = **it;
EnumValueMember(enum_def, ev, max_name_length, code_ptr);
}
EndEnumValues(code_ptr);
EnumStringer(enum_def, code_ptr);
}
// Returns the function name that is able to read a value of the given type.
@@ -788,7 +848,7 @@ class GoGenerator : public BaseGenerator {
}
std::string GenTypeGet(const Type &type) {
if (type.enum_def != nullptr && !type.enum_def->is_union) {
if (type.enum_def != nullptr) {
return GetEnumTypeName(*type.enum_def);
}
return IsScalar(type.base_type) ? GenTypeBasic(type) : GenTypePointer(type);
@@ -798,6 +858,26 @@ class GoGenerator : public BaseGenerator {
return GenTypeGet(field.value.type);
}
// If type is an enum, returns value with a cast to the enum type, otherwise
// returns value as-is.
std::string CastToEnum(const Type &type, std::string value) {
if (type.enum_def == nullptr) {
return value;
} else {
return GenTypeGet(type) + "(" + value + ")";
}
}
// If type is an enum, returns value with a cast to the enum base type,
// otherwise returns value as-is.
std::string CastToBaseType(const Type &type, std::string value) {
if (type.enum_def == nullptr) {
return value;
} else {
return GenTypeBasic(type) + "(" + value + ")";
}
}
std::string GenConstant(const FieldDef &field) {
switch (field.value.type.base_type) {
case BASE_TYPE_BOOL: return field.value.constant == "0" ? "false" : "true";;
@@ -816,12 +896,15 @@ class GoGenerator : public BaseGenerator {
}
// Begin by declaring namespace and imports.
void BeginFile(const std::string name_space_name, const bool needs_imports,
std::string *code_ptr) {
const bool is_enum, std::string *code_ptr) {
std::string &code = *code_ptr;
code = code + "// Code generated by the FlatBuffers compiler. DO NOT EDIT.\n\n";
code += "package " + name_space_name + "\n\n";
if (needs_imports) {
code += "import (\n";
if (is_enum) {
code += "\t\"strconv\"\n\n";
}
if (!parser_.opts.go_import.empty()) {
code += "\tflatbuffers \"" + parser_.opts.go_import + "\"\n";
} else {
@@ -837,19 +920,27 @@ class GoGenerator : public BaseGenerator {
}
}
code += ")\n\n";
} else {
if (is_enum) {
code += "import \"strconv\"\n\n";
}
}
}
// Save out the generated code for a Go Table type.
bool SaveType(const Definition &def, const std::string &classcode,
bool needs_imports) {
const bool needs_imports, const bool is_enum) {
if (!classcode.length()) return true;
Namespace &ns = go_namespace_.components.empty() ? *def.defined_namespace
: go_namespace_;
std::string code = "";
BeginFile(LastNamespacePart(ns), needs_imports, &code);
BeginFile(LastNamespacePart(ns), needs_imports, is_enum, &code);
code += classcode;
// Strip extra newlines at end of file to make it gofmt-clean.
while (code.length() > 2 && code.substr(code.length() - 2) == "\n\n") {
code.pop_back();
}
std::string filename = NamespaceDir(ns) + def.name + ".go";
return SaveFile(filename.c_str(), code, false);
}
@@ -897,6 +988,16 @@ class GoGenerator : public BaseGenerator {
}
const Namespace *CurrentNameSpace() const { return cur_name_space_; }
static int MaxNameLength(const EnumDef &enum_def) {
int max = 0;
for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end();
++it) {
const int length = (*it)->name.length();
max = length > max ? length : max;
}
return max;
}
};
} // namespace go