Fix issues with uint64 enums (#5265)
authorVladimir Glavnyy <31897320+vglavnyy@users.noreply.github.com>
Thu, 2 May 2019 20:57:58 +0000 (03:57 +0700)
committerWouter van Oortmerssen <aardappel@gmail.com>
Thu, 2 May 2019 20:57:58 +0000 (13:57 -0700)
* Fix issues with uint64 enums

- hide the implementation of enums from code generators
- fix uint64 the issue in the cpp-generator
- fix #5108
- new tests
- enums with bit_flags attribute should be unsigned

* Refine objectives of EnumDef's FindByValue and ReverseLookup methods

- move EnumDef::ReverseLookup implementation to idl_parser.cpp
- fix typos

* Make the IsUInt64 method private

16 files changed:
include/flatbuffers/code_generators.h
include/flatbuffers/idl.h
src/idl_gen_cpp.cpp
src/idl_gen_dart.cpp
src/idl_gen_fbs.cpp
src/idl_gen_general.cpp
src/idl_gen_go.cpp
src/idl_gen_js_ts.cpp
src/idl_gen_lobster.cpp
src/idl_gen_lua.cpp
src/idl_gen_php.cpp
src/idl_gen_python.cpp
src/idl_gen_rust.cpp
src/idl_gen_text.cpp
src/idl_parser.cpp
tests/test.cpp

index c2ed707..3128f17 100644 (file)
@@ -53,6 +53,11 @@ class CodeWriter {
     value_map_[key] = value;
   }
 
+  std::string GetValue(const std::string &key) const {
+    const auto it = value_map_.find(key);
+    return it == value_map_.end() ? "" : it->second;
+  }
+
   // Appends the given text to the generated code as well as a newline
   // character.  Any text within {{ and }} delimeters is replaced by values
   // previously stored in the CodeWriter by calling SetValue above.  The newline
index 8299fe0..359cc68 100644 (file)
@@ -123,6 +123,13 @@ inline bool IsLong   (BaseType t) { return t == BASE_TYPE_LONG ||
 inline bool IsBool   (BaseType t) { return t == BASE_TYPE_BOOL; }
 inline bool IsOneByte(BaseType t) { return t >= BASE_TYPE_UTYPE &&
                                            t <= BASE_TYPE_UCHAR; }
+
+inline bool IsUnsigned(BaseType t) {
+  return (t == BASE_TYPE_UTYPE)  || (t == BASE_TYPE_UCHAR) ||
+         (t == BASE_TYPE_USHORT) || (t == BASE_TYPE_UINT)  ||
+         (t == BASE_TYPE_ULONG);
+}
+
 // clang-format on
 
 extern const char *const kTypeNames[];
@@ -327,52 +334,96 @@ inline size_t InlineAlignment(const Type &type) {
   return IsStruct(type) ? type.struct_def->minalign : SizeOf(type.base_type);
 }
 
-struct EnumVal {
-  EnumVal(const std::string &_name, int64_t _val) : name(_name), value(_val) {}
-  EnumVal() : value(0) {}
+struct EnumDef;
+struct EnumValBuilder;
 
+struct EnumVal {
   Offset<reflection::EnumVal> Serialize(FlatBufferBuilder *builder, const Parser &parser) const;
 
   bool Deserialize(const Parser &parser, const reflection::EnumVal *val);
+
+  uint64_t GetAsUInt64() const { return static_cast<uint64_t>(value); }
+  int64_t GetAsInt64() const { return value; }
   bool IsZero() const { return 0 == value; }
   bool IsNonZero() const { return !IsZero(); }
 
   std::string name;
   std::vector<std::string> doc_comment;
-  int64_t value;
   Type union_type;
+
+ private:
+  friend EnumDef;
+  friend EnumValBuilder;
+  friend bool operator==(const EnumVal &lhs, const EnumVal &rhs);
+
+  EnumVal(const std::string &_name, int64_t _val) : name(_name), value(_val) {}
+  EnumVal() : value(0) {}
+
+  int64_t value;
 };
 
 struct EnumDef : public Definition {
   EnumDef() : is_union(false), uses_multiple_type_instances(false) {}
 
-  EnumVal *ReverseLookup(int64_t enum_idx, bool skip_union_default = true) {
-    for (auto it = Vals().begin() +
-                   static_cast<int>(is_union && skip_union_default);
-         it != Vals().end(); ++it) {
-      if ((*it)->value == enum_idx) { return *it; }
-    }
-    return nullptr;
-  }
-
-  Offset<reflection::Enum> Serialize(FlatBufferBuilder *builder, const Parser &parser) const;
+  Offset<reflection::Enum> Serialize(FlatBufferBuilder *builder,
+                                     const Parser &parser) const;
 
   bool Deserialize(Parser &parser, const reflection::Enum *values);
 
+  template<typename T> void ChangeEnumValue(EnumVal *ev, T new_val);
+  void SortByValue();
+  void RemoveDuplicates();
+
+  std::string AllFlags() const;
+  const EnumVal *MinValue() const;
+  const EnumVal *MaxValue() const;
+  // Returns the number of integer steps from v1 to v2.
+  uint64_t Distance(const EnumVal *v1, const EnumVal *v2) const;
+  // Returns the number of integer steps from Min to Max.
+  uint64_t Distance() const { return Distance(MinValue(), MaxValue()); }
+
+  EnumVal *ReverseLookup(int64_t enum_idx,
+                         bool skip_union_default = false) const;
+  EnumVal *FindByValue(const std::string &constant) const;
+
+  std::string ToString(const EnumVal &ev) const {
+    return IsUInt64() ? NumToString(ev.GetAsUInt64())
+                      : NumToString(ev.GetAsInt64());
+  }
+
   size_t size() const { return vals.vec.size(); }
 
   const std::vector<EnumVal *> &Vals() const {
+    FLATBUFFERS_ASSERT(false == vals.vec.empty());
     return vals.vec;
   }
 
-  SymbolTable<EnumVal> vals;
+  const EnumVal *Lookup(const std::string &enum_name) const {
+    return vals.Lookup(enum_name);
+  }
+
   bool is_union;
   // Type is a union which uses type aliases where at least one type is
   // available under two different names.
   bool uses_multiple_type_instances;
   Type underlying_type;
+
+ private:
+  bool IsUInt64() const {
+    return (BASE_TYPE_ULONG == underlying_type.base_type);
+  }
+
+  friend EnumValBuilder;
+  SymbolTable<EnumVal> vals;
 };
 
+inline bool operator==(const EnumVal &lhs, const EnumVal &rhs) {
+  return lhs.value == rhs.value;
+}
+inline bool operator!=(const EnumVal &lhs, const EnumVal &rhs) {
+  return !(lhs == rhs);
+}
+
 inline bool EqualByName(const Type &a, const Type &b) {
   return a.base_type == b.base_type && a.element == b.element &&
          (a.struct_def == b.struct_def ||
index 268c436..d42ebe3 100644 (file)
@@ -28,6 +28,23 @@ namespace flatbuffers {
 // Pedantic warning free version of toupper().
 inline char ToUpper(char c) { return static_cast<char>(::toupper(c)); }
 
+// Make numerical literal with type-suffix.
+// This function is only needed for C++! Other languages do not need it.
+static inline std::string NumToStringCpp(std::string val, BaseType type) {
+  // Avoid issues with -2147483648, -9223372036854775808.
+  switch (type) {
+    case BASE_TYPE_INT:
+      return (val != "-2147483648") ? val : ("(-2147483647 - 1)");
+    case BASE_TYPE_ULONG: return (val == "0") ? val : (val + "ULL");
+    case BASE_TYPE_LONG:
+      if (val == "-9223372036854775808")
+        return "(-9223372036854775807LL - 1LL)";
+      else
+        return (val == "0") ? val : (val + "LL");
+    default: return val;
+  }
+}
+
 static std::string GeneratedFileName(const std::string &path,
                                      const std::string &file_name) {
   return path + file_name + "_generated.h";
@@ -789,7 +806,7 @@ class CppGenerator : public BaseGenerator {
     code_.SetValue("NUM_FIELDS", NumToString(num_fields));
     std::vector<std::string> names;
     std::vector<Type> types;
-    bool consecutive_enum_from_zero = true;
+
     if (struct_def) {
       for (auto it = struct_def->fields.vec.begin();
            it != struct_def->fields.vec.end(); ++it) {
@@ -804,9 +821,6 @@ class CppGenerator : public BaseGenerator {
         names.push_back(Name(ev));
         types.push_back(enum_def->is_union ? ev.union_type
                                            : Type(enum_def->underlying_type));
-        if (static_cast<int64_t>(it - enum_def->Vals().begin()) != ev.value) {
-          consecutive_enum_from_zero = false;
-        }
       }
     }
     std::string ts;
@@ -851,12 +865,16 @@ class CppGenerator : public BaseGenerator {
       ns += "\"" + *it + "\"";
     }
     std::string vs;
+    const auto consecutive_enum_from_zero =
+        enum_def && enum_def->MinValue()->IsZero() &&
+        ((enum_def->size() - 1) == enum_def->Distance());
     if (enum_def && !consecutive_enum_from_zero) {
       for (auto it = enum_def->Vals().begin(); it != enum_def->Vals().end();
            ++it) {
         const auto &ev = **it;
         if (!vs.empty()) vs += ", ";
-        vs += NumToString(ev.value);
+        vs += NumToStringCpp(enum_def->ToString(ev),
+                             enum_def->underlying_type.base_type);
       }
     } else if (struct_def && struct_def->fixed) {
       for (auto it = struct_def->fields.vec.begin();
@@ -883,6 +901,7 @@ class CppGenerator : public BaseGenerator {
       code_ += "  };";
     }
     if (!vs.empty()) {
+      // Problem with uint64_t values greater than 9223372036854775807ULL.
       code_ += "  static const int64_t values[] = { {{VALUES}} };";
     }
     auto has_names =
@@ -907,6 +926,7 @@ class CppGenerator : public BaseGenerator {
   // Generate an enum declaration,
   // an enum string lookup table,
   // and an enum array of values
+
   void GenEnum(const EnumDef &enum_def) {
     code_.SetValue("ENUM_NAME", Name(enum_def));
     code_.SetValue("BASE_TYPE", GenTypeBasic(enum_def.underlying_type, false));
@@ -914,24 +934,31 @@ class CppGenerator : public BaseGenerator {
 
     GenComment(enum_def.doc_comment);
     code_ += GenEnumDecl(enum_def) + "\\";
-    if (parser_.opts.scoped_enums) code_ += " : {{BASE_TYPE}}\\";
+    // MSVC doesn't support int64/uint64 enum without explicitly declared enum
+    // type. The value 4611686018427387904ULL is truncated to zero with warning:
+    // "warning C4309: 'initializing': truncation of constant value".
+    auto add_type = parser_.opts.scoped_enums;
+    add_type |= (enum_def.underlying_type.base_type == BASE_TYPE_LONG);
+    add_type |= (enum_def.underlying_type.base_type == BASE_TYPE_ULONG);
+    if (add_type) code_ += " : {{BASE_TYPE}}\\";
     code_ += " {";
 
-    int64_t anyv = 0;
-    const EnumVal *minv = nullptr, *maxv = nullptr;
     for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
       const auto &ev = **it;
-
-      GenComment(ev.doc_comment, "  ");
+      if (!ev.doc_comment.empty()) {
+        auto prefix = code_.GetValue("SEP") + "  ";
+        GenComment(ev.doc_comment, prefix.c_str());
+        code_.SetValue("SEP", "");
+      }
       code_.SetValue("KEY", GenEnumValDecl(enum_def, Name(ev)));
-      code_.SetValue("VALUE", NumToString(ev.value));
+      code_.SetValue("VALUE",
+                     NumToStringCpp(enum_def.ToString(ev),
+                                    enum_def.underlying_type.base_type));
       code_ += "{{SEP}}  {{KEY}} = {{VALUE}}\\";
       code_.SetValue("SEP", ",\n");
-
-      minv = !minv || minv->value > ev.value ? &ev : minv;
-      maxv = !maxv || maxv->value < ev.value ? &ev : maxv;
-      anyv |= ev.value;
     }
+    const EnumVal *minv = enum_def.MinValue();
+    const EnumVal *maxv = enum_def.MaxValue();
 
     if (parser_.opts.scoped_enums || parser_.opts.prefixed_enums) {
       FLATBUFFERS_ASSERT(minv && maxv);
@@ -943,7 +970,9 @@ class CppGenerator : public BaseGenerator {
         code_ += "{{SEP}}  {{KEY}} = {{VALUE}}\\";
 
         code_.SetValue("KEY", GenEnumValDecl(enum_def, "ANY"));
-        code_.SetValue("VALUE", NumToString(anyv));
+        code_.SetValue("VALUE",
+                       NumToStringCpp(enum_def.AllFlags(),
+                                      enum_def.underlying_type.base_type));
         code_ += "{{SEP}}  {{KEY}} = {{VALUE}}\\";
       } else {  // MIN & MAX are useless for bit_flags
         code_.SetValue("KEY", GenEnumValDecl(enum_def, "MIN"));
@@ -984,22 +1013,23 @@ class CppGenerator : public BaseGenerator {
     // Problem is, if values are very sparse that could generate really big
     // tables. Ideally in that case we generate a map lookup instead, but for
     // the moment we simply don't output a table at all.
-    auto range =
-        enum_def.vals.vec.back()->value - enum_def.vals.vec.front()->value + 1;
+    auto range = enum_def.Distance();
     // Average distance between values above which we consider a table
     // "too sparse". Change at will.
-    static const int kMaxSparseness = 5;
-    if (range / static_cast<int64_t>(enum_def.vals.vec.size()) <
-        kMaxSparseness) {
+    static const uint64_t kMaxSparseness = 5;
+    if (range / static_cast<uint64_t>(enum_def.size()) < kMaxSparseness) {
       code_ += "inline const char * const *EnumNames{{ENUM_NAME}}() {";
       code_ += "  static const char * const names[] = {";
 
-      auto val = enum_def.Vals().front()->value;
+      auto val = enum_def.Vals().front();
       for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end();
            ++it) {
-        const auto &ev = **it;
-        while (val++ != ev.value) { code_ += "    \"\","; }
-        code_ += "    \"" + Name(ev) + "\",";
+        auto ev = *it;
+        for (auto k = enum_def.Distance(val, ev); k > 1; --k) {
+          code_ += "    \"\",";
+        }
+        val = ev;
+        code_ += "    \"" + Name(*ev) + "\",";
       }
       code_ += "    nullptr";
       code_ += "  };";
@@ -1010,14 +1040,13 @@ class CppGenerator : public BaseGenerator {
 
       code_ += "inline const char *EnumName{{ENUM_NAME}}({{ENUM_NAME}} e) {";
 
-      code_ += "  if (e < " +
-               GetEnumValUse(enum_def, *enum_def.vals.vec.front()) +
-               " || e > " + GetEnumValUse(enum_def, *enum_def.vals.vec.back()) +
+      code_ += "  if (e < " + GetEnumValUse(enum_def, *enum_def.MinValue()) +
+               " || e > " + GetEnumValUse(enum_def, *enum_def.MaxValue()) +
                ") return \"\";";
 
       code_ += "  const size_t index = static_cast<size_t>(e)\\";
-      if (enum_def.vals.vec.front()->value) {
-        auto vals = GetEnumValUse(enum_def, *enum_def.vals.vec.front());
+      if (enum_def.MinValue()->IsNonZero()) {
+        auto vals = GetEnumValUse(enum_def, *enum_def.MinValue());
         code_ += " - static_cast<size_t>(" + vals + ")\\";
       }
       code_ += ";";
@@ -1067,8 +1096,8 @@ class CppGenerator : public BaseGenerator {
     if (parser_.opts.generate_object_based_api && enum_def.is_union) {
       // Generate a union type
       code_.SetValue("NAME", Name(enum_def));
-      code_.SetValue("NONE",
-                     GetEnumValUse(enum_def, *enum_def.vals.Lookup("NONE")));
+      FLATBUFFERS_ASSERT(enum_def.Lookup("NONE"));
+      code_.SetValue("NONE", GetEnumValUse(enum_def, *enum_def.Lookup("NONE")));
 
       code_ += "struct {{NAME}}Union {";
       code_ += "  {{NAME}} type;";
@@ -1363,8 +1392,8 @@ class CppGenerator : public BaseGenerator {
       code_ += "";
 
       // Union Reset() function.
-      code_.SetValue("NONE",
-                     GetEnumValUse(enum_def, *enum_def.vals.Lookup("NONE")));
+      FLATBUFFERS_ASSERT(enum_def.Lookup("NONE"));
+      code_.SetValue("NONE", GetEnumValUse(enum_def, *enum_def.Lookup("NONE")));
 
       code_ += "inline void {{ENUM_NAME}}Union::Reset() {";
       code_ += "  switch (type) {";
@@ -1430,18 +1459,19 @@ class CppGenerator : public BaseGenerator {
     if (IsFloat(field.value.type.base_type))
       return float_const_gen_.GenFloatConstant(field);
     else
-      return field.value.constant;
+      return NumToStringCpp(field.value.constant, field.value.type.base_type);
   }
 
   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->ReverseLookup(
-          StringToInt(field.value.constant.c_str()), false);
+      auto ev = field.value.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));
       } else {
-        return GenUnderlyingCast(field, true, field.value.constant);
+        return GenUnderlyingCast(
+            field, true,
+            NumToStringCpp(field.value.constant, field.value.type.base_type));
       }
     } else if (field.value.type.base_type == BASE_TYPE_BOOL) {
       return field.value.constant == "0" ? "false" : "true";
index 2141eb3..c97681b 100644 (file)
@@ -242,9 +242,9 @@ class DartGenerator : public BaseGenerator {
     // holes.
     if (!is_bit_flags) {
       code += "  static const int minValue = " +
-              NumToString(enum_def.vals.vec.front()->value) + ";\n";
+              enum_def.ToString(*enum_def.MinValue()) + ";\n";
       code += "  static const int maxValue = " +
-              NumToString(enum_def.vals.vec.back()->value) + ";\n";
+              enum_def.ToString(*enum_def.MaxValue()) + ";\n";
     }
 
     code +=
@@ -259,13 +259,13 @@ class DartGenerator : public BaseGenerator {
         GenDocComment(ev.doc_comment, &code, "", "  ");
       }
       code += "  static const " + name + " " + ev.name + " = ";
-      code += "const " + name + "._(" + NumToString(ev.value) + ");\n";
+      code += "const " + name + "._(" + enum_def.ToString(ev) + ");\n";
     }
 
     code += "  static get values => {";
     for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
       auto &ev = **it;
-      code += NumToString(ev.value) + ": " + ev.name + ",";
+      code += enum_def.ToString(ev) + ": " + ev.name + ",";
     }
     code += "};\n\n";
 
@@ -507,7 +507,7 @@ class DartGenerator : public BaseGenerator {
           auto &ev = **en_it;
 
           auto enum_name = NamespaceAliasFromUnionType(ev.name);
-          code += "      case " + NumToString(ev.value) + ": return " +
+          code += "      case " + enum_def.ToString(ev) + ": return " +
                   enum_name + ".reader.vTableGet(_bc, _bcOffset, " +
                   NumToString(field.value.offset) + ", null);\n";
         }
index 5a85a95..e5f3723 100644 (file)
@@ -102,7 +102,7 @@ std::string GenerateFBS(const Parser &parser, const std::string &file_name) {
       if (enum_def.is_union)
         schema += "  " + GenType(ev.union_type) + ",\n";
       else
-        schema += "  " + ev.name + " = " + NumToString(ev.value) + ",\n";
+        schema += "  " + ev.name + " = " + enum_def.ToString(ev) + ",\n";
     }
     schema += "}\n\n";
   }
index fe793bf..d4338e3 100644 (file)
@@ -439,21 +439,12 @@ class GeneralGenerator : public BaseGenerator {
   }
 
   std::string GenEnumDefaultValue(const FieldDef &field) const {
-    auto& value = field.value;
-    auto enum_def = value.type.enum_def;
-    auto vec = enum_def->vals.vec;
-    auto default_value = StringToInt(value.constant.c_str());
-
-    auto result = value.constant;
-    for (auto it = vec.begin(); it != vec.end(); ++it) {
-      auto enum_val = **it;
-      if (enum_val.value == default_value) {
-        result = WrapInNameSpace(*enum_def) + "." + enum_val.name;
-        break;
-      }
-    }
-
-    return result;
+    auto &value = field.value;
+    FLATBUFFERS_ASSERT(value.type.enum_def);
+    auto &enum_def = *value.type.enum_def;
+    auto enum_val = enum_def.FindByValue(value.constant);
+    return enum_val ? (WrapInNameSpace(enum_def) + "." + enum_val->name)
+                    : value.constant;
   }
 
   std::string GenDefaultValue(const FieldDef &field, bool enableLangOverrides) const {
@@ -552,7 +543,7 @@ class GeneralGenerator : public BaseGenerator {
         code += GenTypeBasic(enum_def.underlying_type, false);
       }
       code += " " + ev.name + " = ";
-      code += NumToString(ev.value);
+      code += enum_def.ToString(ev);
       code += lang_.enum_separator;
     }
 
@@ -562,21 +553,22 @@ class GeneralGenerator : public BaseGenerator {
       // Problem is, if values are very sparse that could generate really big
       // tables. Ideally in that case we generate a map lookup instead, but for
       // the moment we simply don't output a table at all.
-      auto range = enum_def.vals.vec.back()->value -
-                   enum_def.vals.vec.front()->value + 1;
+      auto range = enum_def.Distance();
       // Average distance between values above which we consider a table
       // "too sparse". Change at will.
-      static const int kMaxSparseness = 5;
-      if (range / static_cast<int64_t>(enum_def.vals.vec.size()) <
-          kMaxSparseness) {
+      static const uint64_t kMaxSparseness = 5;
+      if (range / static_cast<uint64_t>(enum_def.size()) < kMaxSparseness) {
         code += "\n  public static";
         code += lang_.const_decl;
         code += lang_.string_type;
         code += "[] names = { ";
-        auto val = enum_def.Vals().front()->value;
+        auto val = enum_def.Vals().front();
         for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end();
              ++it) {
-          while (val++ != (*it)->value) code += "\"\", ";
+          auto ev = *it;
+          for (auto k = enum_def.Distance(val, ev); k > 1; --k)
+            code += "\"\", ";
+          val = ev;
           code += "\"" + (*it)->name + "\", ";
         }
         code += "};\n\n";
@@ -584,8 +576,8 @@ class GeneralGenerator : public BaseGenerator {
         code += lang_.string_type;
         code += " " + MakeCamel("name", lang_.first_camel_upper);
         code += "(int e) { return names[e";
-        if (enum_def.vals.vec.front()->value)
-          code += " - " + enum_def.vals.vec.front()->name;
+        if (enum_def.MinValue()->IsNonZero())
+          code += " - " + enum_def.MinValue()->name;
         code += "]; }\n";
       }
     }
index 412d1e8..f6119cb 100644 (file)
@@ -162,7 +162,7 @@ class GoGenerator : public BaseGenerator {
     code += " ";
     code += GetEnumTypeName(enum_def);
     code += " = ";
-    code += NumToString(ev.value) + "\n";
+    code += enum_def.ToString(ev) + "\n";
   }
 
   // End enum code.
index fea4620..880bd39 100644 (file)
@@ -359,13 +359,13 @@ class JsTsGenerator : public BaseGenerator {
 
       // Generate mapping between EnumName: EnumValue(int)
       if (reverse) {
-        code += "  " + NumToString(ev.value);
+        code += "  " + enum_def.ToString(ev);
         code += lang_.language == IDLOptions::kTs ? "= " : ": ";
         code += "'" + ev.name + "'";
       } else {
         code += "  " + ev.name;
         code += lang_.language == IDLOptions::kTs ? "= " : ": ";
-        code += NumToString(ev.value);
+        code += enum_def.ToString(ev);
       }
 
       code += (it + 1) != enum_def.Vals().end() ? ",\n" : "\n";
@@ -431,8 +431,7 @@ class JsTsGenerator : public BaseGenerator {
 
   std::string GenDefaultValue(const Value &value, const std::string &context) {
     if (value.type.enum_def) {
-      if (auto val = value.type.enum_def->ReverseLookup(
-              StringToInt(value.constant.c_str()), false)) {
+      if (auto val = value.type.enum_def->FindByValue(value.constant)) {
         if (lang_.language == IDLOptions::kTs) {
           return GenPrefixedTypeName(WrapInNameSpace(*value.type.enum_def),
                                      value.type.enum_def->file) +
index fe23d97..b2e8f42 100644 (file)
@@ -263,7 +263,7 @@ class LobsterGenerator : public BaseGenerator {
       auto &ev = **it;
       GenComment(ev.doc_comment, code_ptr, nullptr, "    ");
       code += "    " + enum_def.name + "_" + NormalizedName(ev) + " = " +
-              NumToString(ev.value);
+              enum_def.ToString(ev);
       if (it + 1 != enum_def.Vals().end()) code += ",";
       code += "\n";
     }
index b26f907..deb66c9 100644 (file)
@@ -111,8 +111,8 @@ namespace lua {
     // A single enum member.
     void EnumMember(const EnumDef &enum_def, const EnumVal &ev, std::string *code_ptr) {
       std::string &code = *code_ptr;
-      code += std::string(Indent) + NormalizedName(ev) + " = " + NumToString(ev.value) + ",\n";
-      (void)enum_def;
+      code += std::string(Indent) + NormalizedName(ev) + " = " +
+              enum_def.ToString(ev) + ",\n";
     }
 
     // End enum code.
index a4b9a46..669b155 100644 (file)
@@ -125,8 +125,7 @@ class PhpGenerator : public BaseGenerator {
     code += Indent + "const ";
     code += ev.name;
     code += " = ";
-    code += NumToString(ev.value) + ";\n";
-    (void)enum_def;
+    code += enum_def.ToString(ev) + ";\n";
   }
 
   // End enum code.
@@ -875,8 +874,7 @@ class PhpGenerator : public BaseGenerator {
 
   std::string GenDefaultValue(const Value &value) {
     if (value.type.enum_def) {
-      if (auto val = value.type.enum_def->ReverseLookup(
-              StringToInt(value.constant.c_str()), false)) {
+      if (auto val = value.type.enum_def->FindByValue(value.constant)) {
         return WrapInNameSpace(*value.type.enum_def) + "::" + val->name;
       }
     }
index f2688e4..556869e 100644 (file)
@@ -118,8 +118,7 @@ class PythonGenerator : public BaseGenerator {
     code += Indent;
     code += NormalizedName(ev);
     code += " = ";
-    code += NumToString(ev.value) + "\n";
-    (void)enum_def;
+    code += enum_def.ToString(ev) + "\n";
   }
 
   // End enum code.
index 23fd34a..cdd5d84 100644 (file)
@@ -589,20 +589,17 @@ class RustGenerator : public BaseGenerator {
     code_ += "#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]";
     code_ += "pub enum " + Name(enum_def) + " {";
 
-    int64_t anyv = 0;
-    const EnumVal *minv = nullptr, *maxv = nullptr;
     for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
       const auto &ev = **it;
 
       GenComment(ev.doc_comment, "  ");
       code_.SetValue("KEY", Name(ev));
-      code_.SetValue("VALUE", NumToString(ev.value));
+      code_.SetValue("VALUE", enum_def.ToString(ev));
       code_ += "  {{KEY}} = {{VALUE}},";
-
-      minv = !minv || minv->value > ev.value ? &ev : minv;
-      maxv = !maxv || maxv->value < ev.value ? &ev : maxv;
-      anyv |= ev.value;
     }
+    const EnumVal *minv = enum_def.MinValue();
+    const EnumVal *maxv = enum_def.MaxValue();
+    FLATBUFFERS_ASSERT(minv && maxv);
 
     code_ += "";
     code_ += "}";
@@ -611,8 +608,8 @@ class RustGenerator : public BaseGenerator {
     code_.SetValue("ENUM_NAME", Name(enum_def));
     code_.SetValue("ENUM_NAME_SNAKE", MakeSnakeCase(Name(enum_def)));
     code_.SetValue("ENUM_NAME_CAPS", MakeUpper(MakeSnakeCase(Name(enum_def))));
-    code_.SetValue("ENUM_MIN_BASE_VALUE", NumToString(minv->value));
-    code_.SetValue("ENUM_MAX_BASE_VALUE", NumToString(maxv->value));
+    code_.SetValue("ENUM_MIN_BASE_VALUE", enum_def.ToString(*minv));
+    code_.SetValue("ENUM_MAX_BASE_VALUE", enum_def.ToString(*maxv));
 
     // Generate enum constants, and impls for Follow, EndianScalar, and Push.
     code_ += "const ENUM_MIN_{{ENUM_NAME_CAPS}}: {{BASE_TYPE}} = \\";
@@ -671,34 +668,36 @@ class RustGenerator : public BaseGenerator {
     // Problem is, if values are very sparse that could generate really big
     // tables. Ideally in that case we generate a map lookup instead, but for
     // the moment we simply don't output a table at all.
-    auto range =
-        enum_def.vals.vec.back()->value - enum_def.vals.vec.front()->value + 1;
+    auto range = enum_def.Distance();
     // Average distance between values above which we consider a table
     // "too sparse". Change at will.
-    static const int kMaxSparseness = 5;
-    if (range / static_cast<int64_t>(enum_def.vals.vec.size()) <
-        kMaxSparseness) {
+    static const uint64_t kMaxSparseness = 5;
+    if (range / static_cast<uint64_t>(enum_def.size()) < kMaxSparseness) {
       code_ += "#[allow(non_camel_case_types)]";
       code_ += "const ENUM_NAMES_{{ENUM_NAME_CAPS}}:[&'static str; " +
-                NumToString(range) + "] = [";
+               NumToString(range + 1) + "] = [";
 
-      auto val = enum_def.Vals().front()->value;
+      auto val = enum_def.Vals().front();
       for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end();
            ++it) {
-        const auto &ev = **it;
-        while (val++ != ev.value) { code_ += "    \"\","; }
-        auto suffix = *it != enum_def.vals.vec.back() ? "," : "";
-        code_ += "    \"" + Name(ev) + "\"" + suffix;
+        auto ev = *it;
+        for (auto k = enum_def.Distance(val, ev); k > 1; --k) {
+          code_ += "    \"\",";
+        }
+        val = ev;
+        auto suffix = *it != enum_def.Vals().back() ? "," : "";
+        code_ += "    \"" + Name(*ev) + "\"" + suffix;
       }
       code_ += "];";
       code_ += "";
 
-      code_ += "pub fn enum_name_{{ENUM_NAME_SNAKE}}(e: {{ENUM_NAME}}) -> "
-               "&'static str {";
+      code_ +=
+          "pub fn enum_name_{{ENUM_NAME_SNAKE}}(e: {{ENUM_NAME}}) -> "
+          "&'static str {";
 
       code_ += "  let index = e as {{BASE_TYPE}}\\";
-      if (enum_def.vals.vec.front()->value) {
-        auto vals = GetEnumValUse(enum_def, *enum_def.vals.vec.front());
+      if (enum_def.MinValue()->IsNonZero()) {
+        auto vals = GetEnumValUse(enum_def, *enum_def.MinValue());
         code_ += " - " + vals + " as {{BASE_TYPE}}\\";
       }
       code_ += ";";
@@ -735,8 +734,7 @@ class RustGenerator : public BaseGenerator {
       }
       case ftUnionKey:
       case ftEnumKey: {
-        auto ev = field.value.type.enum_def->ReverseLookup(
-            StringToInt(field.value.constant.c_str()), false);
+        auto ev = field.value.type.enum_def->FindByValue(field.value.constant);
         assert(ev);
         return WrapInNameSpace(field.value.type.enum_def->defined_namespace,
                                GetEnumValUse(*field.value.type.enum_def, *ev));
index 4c61ff9..eeb34ff 100644 (file)
@@ -51,10 +51,10 @@ bool Print(T val, Type type, int /*indent*/, Type * /*union_type*/,
            const IDLOptions &opts, std::string *_text) {
   std::string &text = *_text;
   if (type.enum_def && opts.output_enum_identifiers) {
-    auto enum_val = type.enum_def->ReverseLookup(static_cast<int64_t>(val));
-    if (enum_val) {
+    auto ev = type.enum_def->ReverseLookup(static_cast<int64_t>(val));
+    if (ev) {
       text += "\"";
-      text += enum_val->name;
+      text += ev->name;
       text += "\"";
       return true;
     }
@@ -251,7 +251,7 @@ static bool GenStruct(const StructDef &struct_def, const Table *table,
       }
       if (fd.value.type.base_type == BASE_TYPE_UTYPE) {
         auto enum_val = fd.value.type.enum_def->ReverseLookup(
-            table->GetField<uint8_t>(fd.value.offset, 0));
+            table->GetField<uint8_t>(fd.value.offset, 0), true);
         union_type = enum_val ? &enum_val->union_type : nullptr;
       }
     }
index e26aa55..c732fed 100644 (file)
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <list>
 #include <string>
+#include <utility>
 
 #include <math.h>
 
@@ -676,14 +677,6 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
       return Error(
             "default values currently only supported for scalars in tables");
   }
-  if (type.enum_def &&
-      !type.enum_def->is_union &&
-      !type.enum_def->attributes.Lookup("bit_flags") &&
-      !type.enum_def->ReverseLookup(StringToInt(
-                                      field->value.constant.c_str()))) {
-    return Error("default value of " + field->value.constant + " for field " +
-                 name + " is not part of enum " + type.enum_def->name);
-  }
   // 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)) {
@@ -699,14 +692,26 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
       field->value.constant += ".0";
     }
   }
-
-  if (type.enum_def && IsScalar(type.base_type) && !struct_def.fixed &&
-      !type.enum_def->attributes.Lookup("bit_flags") &&
-      !type.enum_def->ReverseLookup(StringToInt(
-                                      field->value.constant.c_str())))
-    Warning("enum " + type.enum_def->name +
-            " does not have a declaration for this field\'s default of " +
-            field->value.constant);
+  if (type.enum_def) {
+    // The type.base_type can only be scalar, union or vector.
+    // Table, struct or string can't have enum_def.
+    // Default value of union and vector in NONE, NULL translated to "0".
+    FLATBUFFERS_ASSERT(IsInteger(type.base_type) ||
+                       (type.base_type == BASE_TYPE_UNION) ||
+                       (type.base_type == BASE_TYPE_VECTOR));
+    if (type.base_type == BASE_TYPE_VECTOR) {
+      // Vector can't use initialization list.
+      FLATBUFFERS_ASSERT(field->value.constant == "0");
+    } else {
+      // All unions should have the NONE ("0") enum value.
+      auto in_enum = type.enum_def->attributes.Lookup("bit_flags") ||
+                     type.enum_def->FindByValue(field->value.constant);
+      if (false == in_enum)
+        return Error("default value of " + field->value.constant +
+                     " for field " + name + " is not part of enum " +
+                     type.enum_def->name);
+    }
+  }
 
   field->doc_comment = dc;
   ECHECK(ParseMetaData(&field->attributes));
@@ -917,7 +922,7 @@ CheckedError Parser::ParseAnyValue(Value &val, FieldDef *field,
       } else {
         ECHECK(atot(constant.c_str(), *this, &enum_idx));
       }
-      auto enum_val = val.type.enum_def->ReverseLookup(enum_idx);
+      auto enum_val = val.type.enum_def->ReverseLookup(enum_idx, true);
       if (!enum_val) return Error("illegal type id for: " + field->name);
       if (enum_val->union_type.base_type == BASE_TYPE_STRUCT) {
         ECHECK(ParseTable(*enum_val->union_type.struct_def, &val.constant,
@@ -1338,42 +1343,33 @@ CheckedError Parser::TryTypedValue(const std::string *name, int dtoken,
 
 CheckedError Parser::ParseEnumFromString(const Type &type,
                                          std::string *result) {
-  int64_t i64 = 0;
-  // Parse one or more enum identifiers, separated by spaces.
-  const char *next = attribute_.c_str();
-  do {
-    const char *divider = strchr(next, ' ');
-    std::string word;
-    if (divider) {
-      word = std::string(next, divider);
-      next = divider + strspn(divider, " ");
+  const auto base_type =
+      type.enum_def ? type.enum_def->underlying_type.base_type : type.base_type;
+  if (!IsInteger(base_type)) return Error("not a valid value for this field");
+  uint64_t u64 = 0;
+  for (size_t pos = 0; pos != std::string::npos;) {
+    const auto delim = attribute_.find_first_of(' ', pos);
+    const auto last = (std::string::npos == delim);
+    auto word = attribute_.substr(pos, !last ? delim - pos : std::string::npos);
+    pos = !last ? delim + 1 : std::string::npos;
+    const EnumVal *ev = nullptr;
+    if (type.enum_def) {
+      ev = type.enum_def->Lookup(word);
     } else {
-      word = next;
-      next += word.length();
-    }
-    if (type.enum_def) {  // The field has an enum type
-      auto enum_val = type.enum_def->vals.Lookup(word);
-      if (!enum_val)
-        return Error("unknown enum value: " + word +
-                     ", for enum: " + type.enum_def->name);
-      i64 |= enum_val->value;
-    } else {  // No enum type, probably integral field.
-      if (!IsInteger(type.base_type))
-        return Error("not a valid value for this field: " + word);
-      // TODO: could check if its a valid number constant here.
-      const char *dot = strrchr(word.c_str(), '.');
-      if (!dot)
+      auto dot = word.find_first_of('.');
+      if (std::string::npos == dot)
         return Error("enum values need to be qualified by an enum type");
-      std::string enum_def_str(word.c_str(), dot);
-      std::string enum_val_str(dot + 1, word.c_str() + word.length());
-      auto enum_def = LookupEnum(enum_def_str);
+      auto enum_def_str = word.substr(0, dot);
+      const auto enum_def = LookupEnum(enum_def_str);
       if (!enum_def) return Error("unknown enum: " + enum_def_str);
-      auto enum_val = enum_def->vals.Lookup(enum_val_str);
-      if (!enum_val) return Error("unknown enum value: " + enum_val_str);
-      i64 |= enum_val->value;
+      auto enum_val_str = word.substr(dot + 1);
+      ev = enum_def->Lookup(enum_val_str);
     }
-  } while (*next);
-  *result = NumToString(i64);
+    if (!ev) return Error("unknown enum value: " + word);
+    u64 |= ev->GetAsUInt64();
+  }
+  *result = IsUnsigned(base_type) ? NumToString(u64)
+                                  : NumToString(static_cast<int64_t>(u64));
   return NoError();
 }
 
@@ -1618,7 +1614,204 @@ StructDef *Parser::LookupCreateStruct(const std::string &name,
   return struct_def;
 }
 
-CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
+const EnumVal *EnumDef::MinValue() const {
+  return vals.vec.empty() ? nullptr : vals.vec.front();
+}
+const EnumVal *EnumDef::MaxValue() const {
+  return vals.vec.empty() ? nullptr : vals.vec.back();
+}
+
+template<typename T> static uint64_t EnumDistanceImpl(T e1, T e2) {
+  if (e1 < e2) { std::swap(e1, e2); }  // use std for scalars
+  // Signed overflow may occur, use unsigned calculation.
+  // The unsigned overflow is well-defined by C++ standard (modulo 2^n).
+  return static_cast<uint64_t>(e1) - static_cast<uint64_t>(e2);
+}
+
+uint64_t EnumDef::Distance(const EnumVal *v1, const EnumVal *v2) const {
+  return IsUInt64() ? EnumDistanceImpl(v1->GetAsUInt64(), v2->GetAsUInt64())
+                    : EnumDistanceImpl(v1->GetAsInt64(), v2->GetAsInt64());
+}
+
+std::string EnumDef::AllFlags() const {
+  FLATBUFFERS_ASSERT(attributes.Lookup("bit_flags"));
+  uint64_t u64 = 0;
+  for (auto it = Vals().begin(); it != Vals().end(); ++it) {
+    u64 |= (*it)->GetAsUInt64();
+  }
+  return IsUInt64() ? NumToString(u64) : NumToString(static_cast<int64_t>(u64));
+}
+
+EnumVal *EnumDef::ReverseLookup(int64_t enum_idx,
+                                bool skip_union_default) const {
+  auto skip_first = static_cast<int>(is_union && skip_union_default);
+  for (auto it = Vals().begin() + skip_first; it != Vals().end(); ++it) {
+    if ((*it)->GetAsInt64() == enum_idx) { return *it; }
+  }
+  return nullptr;
+}
+
+EnumVal *EnumDef::FindByValue(const std::string &constant) const {
+  int64_t i64;
+  auto done = false;
+  if (IsUInt64()) {
+    uint64_t u64;  // avoid reinterpret_cast of pointers
+    done = StringToNumber(constant.c_str(), &u64);
+    i64 = static_cast<int64_t>(u64);
+  } else {
+    done = StringToNumber(constant.c_str(), &i64);
+  }
+  FLATBUFFERS_ASSERT(done);
+  if (!done) return nullptr;
+  return ReverseLookup(i64, false);
+}
+
+void EnumDef::SortByValue() {
+  auto &v = vals.vec;
+  if (IsUInt64())
+    std::sort(v.begin(), v.end(), [](const EnumVal *e1, const EnumVal *e2) {
+      return e1->GetAsUInt64() < e2->GetAsUInt64();
+    });
+  else
+    std::sort(v.begin(), v.end(), [](const EnumVal *e1, const EnumVal *e2) {
+      return e1->GetAsInt64() < e2->GetAsInt64();
+    });
+}
+
+void EnumDef::RemoveDuplicates() {
+  // This method depends form SymbolTable implementation!
+  // 1) vals.vec - owner (raw pointer)
+  // 2) vals.dict - access map
+  auto first = vals.vec.begin();
+  auto last = vals.vec.end();
+  if (first == last) return;
+  auto result = first;
+  while (++first != last) {
+    if ((*result)->value != (*first)->value) {
+      *(++result) = *first;
+    } else {
+      auto ev = *first;
+      for (auto it = vals.dict.begin(); it != vals.dict.end(); ++it) {
+        if (it->second == ev) it->second = *result;  // reassign
+      }
+      delete ev;  // delete enum value
+      *first = nullptr;
+    }
+  }
+  vals.vec.erase(++result, last);
+}
+
+template<typename T> void EnumDef::ChangeEnumValue(EnumVal *ev, T new_value) {
+  ev->value = static_cast<int64_t>(new_value);
+}
+
+namespace EnumHelper {
+template<BaseType E> struct EnumValType { typedef int64_t type; };
+template<> struct EnumValType<BASE_TYPE_ULONG> { typedef uint64_t type; };
+}  // namespace EnumHelper
+
+struct EnumValBuilder {
+  EnumVal *CreateEnumerator(const std::string &ev_name) {
+    FLATBUFFERS_ASSERT(!temp);
+    auto first = enum_def.vals.vec.empty();
+    user_value = first;
+    temp = new EnumVal(ev_name, first ? 0 : enum_def.vals.vec.back()->value);
+    return temp;
+  }
+
+  EnumVal *CreateEnumerator(const std::string &ev_name, int64_t val) {
+    FLATBUFFERS_ASSERT(!temp);
+    user_value = true;
+    temp = new EnumVal(ev_name, val);
+    return temp;
+  }
+
+  FLATBUFFERS_CHECKED_ERROR AcceptEnumerator(const std::string &name) {
+    FLATBUFFERS_ASSERT(temp);
+    ECHECK(ValidateValue(&temp->value, false == user_value));
+    FLATBUFFERS_ASSERT((temp->union_type.enum_def == nullptr) ||
+                       (temp->union_type.enum_def == &enum_def));
+    auto not_unique = enum_def.vals.Add(name, temp);
+    temp = nullptr;
+    if (not_unique) return parser.Error("enum value already exists: " + name);
+    return NoError();
+  }
+
+  FLATBUFFERS_CHECKED_ERROR AcceptEnumerator() {
+    return AcceptEnumerator(temp->name);
+  }
+
+  FLATBUFFERS_CHECKED_ERROR AssignEnumeratorValue(const std::string &value) {
+    user_value = true;
+    auto fit = false;
+    auto ascending = false;
+    if (enum_def.IsUInt64()) {
+      uint64_t u64;
+      fit = StringToNumber(value.c_str(), &u64); 
+      ascending = u64 > temp->GetAsUInt64();
+      temp->value = static_cast<int64_t>(u64);  // well-defined since C++20.
+    } else {
+      int64_t i64;
+      fit = StringToNumber(value.c_str(), &i64);
+      ascending = i64 > temp->GetAsInt64();
+      temp->value = i64;
+    }
+    if (!fit) return parser.Error("enum value does not fit, \"" + value + "\"");
+    if (!ascending && strict_ascending && !enum_def.vals.vec.empty())
+      return parser.Error("enum values must be specified in ascending order");
+    return NoError();
+  }
+
+  template<BaseType E, typename CTYPE>
+  inline FLATBUFFERS_CHECKED_ERROR ValidateImpl(int64_t *ev, int m) {
+    typedef typename EnumHelper::EnumValType<E>::type T;  // int64_t or uint64_t
+    static_assert(sizeof(T) == sizeof(int64_t), "invalid EnumValType");
+    const auto v = static_cast<T>(*ev);
+    auto up = static_cast<T>((flatbuffers::numeric_limits<CTYPE>::max)());
+    auto dn = static_cast<T>((flatbuffers::numeric_limits<CTYPE>::lowest)());
+    if (v < dn || v > (up - m)) {
+      return parser.Error("enum value does not fit, \"" + NumToString(v) +
+                          (m ? " + 1\"" : "\"") + " out of " +
+                          TypeToIntervalString<CTYPE>());
+    }
+    *ev = static_cast<int64_t>(v + m);  // well-defined since C++20.
+    return NoError();
+  }
+
+  FLATBUFFERS_CHECKED_ERROR ValidateValue(int64_t *ev, bool next) {
+    // clang-format off
+    switch (enum_def.underlying_type.base_type) {
+    #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE,   \
+                           PTYPE, RTYPE)                                \
+      case BASE_TYPE_##ENUM: {                                          \
+        if (!IsInteger(BASE_TYPE_##ENUM)) break;                        \
+        return ValidateImpl<BASE_TYPE_##ENUM, CTYPE>(ev, next ? 1 : 0); \
+      }
+      FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD);
+    #undef FLATBUFFERS_TD
+    default: break;
+    }
+    // clang-format on
+    return parser.Error("fatal: invalid enum underlying type");
+  }
+
+  EnumValBuilder(Parser &_parser, EnumDef &_enum_def, bool strict_order = true)
+      : parser(_parser),
+        enum_def(_enum_def),
+        temp(nullptr),
+        strict_ascending(strict_order),
+        user_value(false) {}
+
+  ~EnumValBuilder() { delete temp; }
+
+  Parser &parser;
+  EnumDef &enum_def;
+  EnumVal *temp;
+  const bool strict_ascending;
+  bool user_value;
+};
+
+CheckedError Parser::ParseEnum(const bool is_union, EnumDef **dest) {
   std::vector<std::string> enum_comment = doc_comment_;
   NEXT();
   std::string enum_name = attribute_;
@@ -1645,33 +1838,38 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
     enum_def->underlying_type.enum_def = enum_def;
   }
   ECHECK(ParseMetaData(&enum_def->attributes));
+  const auto underlying_type = enum_def->underlying_type.base_type;
+  if (enum_def->attributes.Lookup("bit_flags") &&
+      !IsUnsigned(underlying_type)) {
+    // todo: Convert to the Error in the future?
+    Warning("underlying type of bit_flags enum must be unsigned");
+  }
+  // Protobuf allows them to be specified in any order, so sort afterwards.
+  const auto strict_ascending = (false == opts.proto_mode);
+  EnumValBuilder evb(*this, *enum_def, strict_ascending);
   EXPECT('{');
-  if (is_union) enum_def->vals.Add("NONE", new EnumVal("NONE", 0));
-  std::set<std::pair<BaseType, StructDef*>> union_types;
-  for (;;) {
+  // A lot of code generatos expect that an enum is not-empty.
+  if ((is_union || Is('}')) && !opts.proto_mode) {
+    evb.CreateEnumerator("NONE");
+    ECHECK(evb.AcceptEnumerator());
+  }
+  std::set<std::pair<BaseType, StructDef *>> union_types;
+  while (!Is('}')) {
     if (opts.proto_mode && attribute_ == "option") {
       ECHECK(ParseProtoOption());
     } else {
-      auto value_name = attribute_;
-      auto full_name = value_name;
-      std::vector<std::string> value_comment = doc_comment_;
+      auto &ev = *evb.CreateEnumerator(attribute_);
+      auto full_name = ev.name;
+      ev.doc_comment = doc_comment_;
       EXPECT(kTokenIdentifier);
       if (is_union) {
-        ECHECK(ParseNamespacing(&full_name, &value_name));
+        ECHECK(ParseNamespacing(&full_name, &ev.name));
         if (opts.union_value_namespacing) {
           // Since we can't namespace the actual enum identifiers, turn
           // namespace parts into part of the identifier.
-          value_name = full_name;
-          std::replace(value_name.begin(), value_name.end(), '.', '_');
+          ev.name = full_name;
+          std::replace(ev.name.begin(), ev.name.end(), '.', '_');
         }
-      }
-      auto prevsize = enum_def->vals.vec.size();
-      auto prevvalue = prevsize > 0 ? enum_def->vals.vec.back()->value : 0;
-      auto &ev = *new EnumVal(value_name, 0);
-      if (enum_def->vals.Add(value_name, &ev))
-        return Error("enum value already exists: " + value_name);
-      ev.doc_comment = value_comment;
-      if (is_union) {
         if (Is(':')) {
           NEXT();
           ECHECK(ParseType(ev.union_type));
@@ -1682,51 +1880,22 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
           ev.union_type = Type(BASE_TYPE_STRUCT, LookupCreateStruct(full_name));
         }
         if (!enum_def->uses_multiple_type_instances) {
-          auto union_type_key = std::make_pair(ev.union_type.base_type, ev.union_type.struct_def);
-          if (union_types.count(union_type_key) > 0) {
-            enum_def->uses_multiple_type_instances = true;
-          } else {
-            union_types.insert(union_type_key);
-          }
+          auto ins = union_types.insert(std::make_pair(
+              ev.union_type.base_type, ev.union_type.struct_def));
+          enum_def->uses_multiple_type_instances = (false == ins.second);
         }
       }
+
       if (Is('=')) {
         NEXT();
-        ECHECK(atot(attribute_.c_str(), *this, &ev.value));
+        ECHECK(evb.AssignEnumeratorValue(attribute_));
         EXPECT(kTokenIntegerConstant);
-        if (!opts.proto_mode && prevsize &&
-            enum_def->vals.vec[prevsize - 1]->value >= ev.value)
-          return Error("enum values must be specified in ascending order");
-      } else if (prevsize == 0) {
-        // already set to zero
-      } else if (prevvalue != flatbuffers::numeric_limits<int64_t>::max()) {
-        ev.value = prevvalue + 1;
-      } else {
-        return Error("enum value overflows");
+      } else if (false == strict_ascending) {
+        // The opts.proto_mode flag is active.
+        return Error("Protobuf mode doesn't allow implicit enum values.");
       }
 
-      // Check that value fits into the underlying type.
-      switch (enum_def->underlying_type.base_type) {
-        // clang-format off
-        #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \
-                               PTYPE, RTYPE)                              \
-          case BASE_TYPE_##ENUM: {                                        \
-            int64_t min_value = static_cast<int64_t>(                     \
-              flatbuffers::numeric_limits<CTYPE>::lowest());              \
-            int64_t max_value = static_cast<int64_t>(                     \
-              flatbuffers::numeric_limits<CTYPE>::max());                 \
-            if (ev.value < min_value || ev.value > max_value) {           \
-              return Error(                                               \
-                "enum value does not fit [" +  NumToString(min_value) +   \
-                "; " + NumToString(max_value) + "]");                     \
-            }                                                             \
-            break;                                                        \
-          }
-        FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD);
-        #undef FLATBUFFERS_TD
-        default: break;
-        // clang-format on
-      }
+      ECHECK(evb.AcceptEnumerator());
 
       if (opts.proto_mode && Is('[')) {
         NEXT();
@@ -1737,18 +1906,31 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
     }
     if (!Is(opts.proto_mode ? ';' : ',')) break;
     NEXT();
-    if (Is('}')) break;
   }
   EXPECT('}');
+
+  // At this point, the enum can be empty if input is invalid proto-file.
+  if (!enum_def->size())
+    return Error("incomplete enum declaration, values not found");
+
   if (enum_def->attributes.Lookup("bit_flags")) {
-    for (auto it = enum_def->vals.vec.begin(); it != enum_def->vals.vec.end();
+    const auto base_width = static_cast<uint64_t>(8 * SizeOf(underlying_type));
+    for (auto it = enum_def->Vals().begin(); it != enum_def->Vals().end();
          ++it) {
-      if (static_cast<size_t>((*it)->value) >=
-          SizeOf(enum_def->underlying_type.base_type) * 8)
+      auto ev = *it;
+      const auto u = ev->GetAsUInt64();
+      // Stop manipulations with the sign.
+      if (!IsUnsigned(underlying_type) && u == (base_width - 1))
+        return Error("underlying type of bit_flags enum must be unsigned");
+      if (u >= base_width)
         return Error("bit flag out of range of underlying integral type");
-      (*it)->value = 1LL << (*it)->value;
+      enum_def->ChangeEnumValue(ev, 1ULL << u);
     }
   }
+
+  if (false == strict_ascending)
+    enum_def->SortByValue();  // Must be sorted to use MinValue/MaxValue.
+
   if (dest) *dest = enum_def;
   types_.Add(current_namespace_->GetFullyQualifiedName(enum_def->name),
              new Type(BASE_TYPE_UNION, nullptr, enum_def));
@@ -1982,10 +2164,6 @@ CheckedError Parser::ParseNamespace() {
   return NoError();
 }
 
-static bool compareEnumVals(const EnumVal *a, const EnumVal *b) {
-  return a->value < b->value;
-}
-
 // Best effort parsing of .proto declarations, with the aim to turn them
 // in the closest corresponding FlatBuffer equivalent.
 // We parse everything as identifiers instead of keywords, since we don't
@@ -2031,25 +2209,8 @@ CheckedError Parser::ParseProtoDecl() {
     EnumDef *enum_def;
     ECHECK(ParseEnum(false, &enum_def));
     if (Is(';')) NEXT();
-    // Protobuf allows them to be specified in any order, so sort afterwards.
-    auto &v = enum_def->vals.vec;
-    std::sort(v.begin(), v.end(), compareEnumVals);
-
     // Temp: remove any duplicates, as .fbs files can't handle them.
-    for (auto it = v.begin(); it != v.end();) {
-      if (it != v.begin() && it[0]->value == it[-1]->value) {
-        auto ref = it[-1];
-        auto ev = it[0];
-        for (auto dit = enum_def->vals.dict.begin();
-             dit != enum_def->vals.dict.end(); ++dit) {
-          if (dit->second == ev) dit->second = ref;  // reassign
-        }
-        delete ev;  // delete enum value
-        it = v.erase(it);
-      } else {
-        ++it;
-      }
-    }
+    enum_def->RemoveDuplicates();
   } else if (IsIdent("syntax")) {  // Skip these.
     NEXT();
     EXPECT('=');
@@ -2219,11 +2380,11 @@ CheckedError Parser::ParseProtoFields(StructDef *struct_def, bool isextend,
             return Error("oneof '" + name +
                 "' cannot be mapped to a union because member '" +
                 oneof_field.name + "' is not a table type.");
-          auto enum_val = new EnumVal(oneof_type.struct_def->name,
-                                      oneof_union->vals.vec.size());
-          enum_val->union_type = oneof_type;
-          enum_val->doc_comment = oneof_field.doc_comment;
-          oneof_union->vals.Add(oneof_field.name, enum_val);
+          EnumValBuilder evb(*this, *oneof_union);
+          auto ev = evb.CreateEnumerator(oneof_type.struct_def->name);
+          ev->union_type = oneof_type;
+          ev->doc_comment = oneof_field.doc_comment;
+          ECHECK(evb.AcceptEnumerator(oneof_field.name));
         }
       } else {
         EXPECT(';');
@@ -3205,9 +3366,9 @@ std::string Parser::ConformTo(const Parser &base) {
     for (auto evit = enum_def.Vals().begin(); evit != enum_def.Vals().end();
          ++evit) {
       auto &enum_val = **evit;
-      auto enum_val_base = enum_def_base->vals.Lookup(enum_val.name);
+      auto enum_val_base = enum_def_base->Lookup(enum_val.name);
       if (enum_val_base) {
-        if (enum_val.value != enum_val_base->value)
+        if (enum_val != *enum_val_base)
           return "values differ for enum: " + enum_val.name;
       }
     }
index e023230..c8d4577 100644 (file)
@@ -1297,7 +1297,6 @@ void ErrorTest() {
   TestError("enum X:float {}", "underlying");
   TestError("enum X:byte { Y, Y }", "value already");
   TestError("enum X:byte { Y=2, Z=1 }", "ascending");
-  TestError("enum X:byte (bit_flags) { Y=8 }", "bit flag out");
   TestError("table X { Y:int; } table X {", "datatype already");
   TestError("struct X (force_align: 7) { Y:int; }", "force_align");
   TestError("struct X {}", "size 0");
@@ -1416,6 +1415,13 @@ void EnumStringsTest() {
                         "root_type T;"
                         "{ F:[ \"E.C\", \"E.A E.B E.C\" ] }"),
           true);
+  // unsigned bit_flags
+  flatbuffers::Parser parser3;
+  TEST_EQ(
+      parser3.Parse("enum E:uint16 (bit_flags) { F0, F07=7, F08, F14=14, F15 }"
+                    " table T { F: E = \"F15 F08\"; }"
+                    "root_type T;"),
+      true);
 }
 
 void EnumNamesTest() {
@@ -1436,9 +1442,10 @@ void EnumNamesTest() {
 void EnumOutOfRangeTest() {
   TestError("enum X:byte { Y = 128 }", "enum value does not fit");
   TestError("enum X:byte { Y = -129 }", "enum value does not fit");
-  TestError("enum X:byte { Y = 127, Z }", "enum value does not fit");
+  TestError("enum X:byte { Y = 126, Z0, Z1 }", "enum value does not fit");
   TestError("enum X:ubyte { Y = -1 }", "enum value does not fit");
   TestError("enum X:ubyte { Y = 256 }", "enum value does not fit");
+  TestError("enum X:ubyte { Y = 255, Z }", "enum value does not fit");
   // Unions begin with an implicit "NONE = 0".
   TestError("table Y{} union X { Y = -1 }",
             "enum values must be specified in ascending order");
@@ -1448,12 +1455,13 @@ void EnumOutOfRangeTest() {
   TestError("enum X:int { Y = 2147483648 }", "enum value does not fit");
   TestError("enum X:uint { Y = -1 }", "enum value does not fit");
   TestError("enum X:uint { Y = 4294967297 }", "enum value does not fit");
-  TestError("enum X:long { Y = 9223372036854775808 }", "constant does not fit");
-  TestError("enum X:long { Y = 9223372036854775807, Z }", "enum value overflows");
-  TestError("enum X:ulong { Y = -1 }", "enum value does not fit");
-  // TODO: these are perfectly valid constants that shouldn't fail
-  TestError("enum X:ulong { Y = 13835058055282163712 }", "constant does not fit");
-  TestError("enum X:ulong { Y = 18446744073709551615 }", "constant does not fit");
+  TestError("enum X:long { Y = 9223372036854775808 }", "does not fit");
+  TestError("enum X:long { Y = 9223372036854775807, Z }", "enum value does not fit");
+  TestError("enum X:ulong { Y = -1 }", "does not fit");
+  TestError("enum X:ubyte (bit_flags) { Y=8 }", "bit flag out");
+  TestError("enum X:byte (bit_flags) { Y=7 }", "must be unsigned"); // -128
+  // bit_flgs out of range
+  TestError("enum X:ubyte (bit_flags) { Y0,Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8 }", "out of range");
 }
 
 void EnumValueTest() {
@@ -1472,6 +1480,15 @@ void EnumValueTest() {
   // Generate json with defaults and check.
   TEST_EQ(TestValue<int>(nullptr, "E", "enum E:int { Z, V=5 }"), 0);
   TEST_EQ(TestValue<int>(nullptr, "E=V", "enum E:int { Z, V=5 }"), 5);
+  // u84 test
+  TEST_EQ(TestValue<uint64_t>(nullptr, "E=V",
+                              "enum E:ulong { V = 13835058055282163712 }"),
+          13835058055282163712ULL);
+  TEST_EQ(TestValue<uint64_t>(nullptr, "E=V",
+                              "enum E:ulong { V = 18446744073709551615 }"),
+          18446744073709551615ULL);
+  // Assign non-enum value to enum field. Is it right?
+  TEST_EQ(TestValue<int>("{ Y:7 }", "E", "enum E:int { V = 0 }"), 7);
 }
 
 void IntegerOutOfRangeTest() {
@@ -2694,7 +2711,7 @@ int main(int /*argc*/, const char * /*argv*/ []) {
 
   std::string req_locale;
   if (flatbuffers::ReadEnvironmentVariable("FLATBUFFERS_TEST_LOCALE",
-                                          &req_locale)) {
+                                           &req_locale)) {
     TEST_OUTPUT_LINE("The environment variable FLATBUFFERS_TEST_LOCALE=%s",
                      req_locale.c_str());
     req_locale = flatbuffers::RemoveStringQuotes(req_locale);