Default strings and vectors: Parser + Rust support (#6421)
authorCasper <casperneo@uchicago.edu>
Fri, 12 Feb 2021 14:41:10 +0000 (09:41 -0500)
committerGitHub <noreply@github.com>
Fri, 12 Feb 2021 14:41:10 +0000 (09:41 -0500)
* Fix tests.cpp

* Parser support for vector/string defaults

* tests and default empty vectors

* addressed comments

* Default strings and vectors for Rust

* Tested Rust more_defaults

* git-clang-format

* add more_defaults_test

* fixed vector default

* removed commented out code

* more unreachable

Co-authored-by: Casper Neo <cneo@google.com>
12 files changed:
include/flatbuffers/idl.h
rust/flatbuffers/Cargo.toml
rust/flatbuffers/src/vector.rs
src/idl_gen_rust.cpp
src/idl_parser.cpp
tests/generate_code.sh
tests/monster_test_generated.rs
tests/more_defaults.fbs [new file with mode: 0644]
tests/more_defaults_generated.rs [new file with mode: 0644]
tests/rust_usage_test/tests/integration_test.rs
tests/rust_usage_test/tests/more_defaults_test.rs [new file with mode: 0644]
tests/test.cpp

index 116f500..955ac3f 100644 (file)
@@ -337,6 +337,7 @@ struct FieldDef : public Definition {
     kDefault,
   };
   Presence static MakeFieldPresence(bool optional, bool required) {
+    FLATBUFFERS_ASSERT(!(required && optional));
     // clang-format off
     return required ? FieldDef::kRequired
          : optional ? FieldDef::kOptional
@@ -973,6 +974,7 @@ class Parser : public ParserState {
   bool SupportsAdvancedUnionFeatures() const;
   bool SupportsAdvancedArrayFeatures() const;
   bool SupportsOptionalScalars() const;
+  bool SupportsDefaultVectorsAndStrings() const;
   Namespace *UniqueNamespace(Namespace *ns);
 
   FLATBUFFERS_CHECKED_ERROR RecurseError();
index 13d534f..d2bd785 100644 (file)
@@ -1,6 +1,6 @@
 [package]
 name = "flatbuffers"
-version = "0.8.2"
+version = "0.8.3"
 edition = "2018"
 authors = ["Robert Winslow <hello@rwinslow.com>", "FlatBuffers Maintainers"]
 license = "Apache-2.0"
index c53a878..b54d3ec 100644 (file)
@@ -29,6 +29,14 @@ use crate::primitives::*;
 
 pub struct Vector<'a, T: 'a>(&'a [u8], usize, PhantomData<T>);
 
+impl<'a, T:'a> Default for Vector<'a, T> {
+    fn default() -> Self {
+        // Static, length 0 vector.
+        // Note that derived default causes UB due to issues in read_scalar_at /facepalm.
+        Self(&[0; core::mem::size_of::<UOffsetT>()], 0, Default::default())
+    }
+}
+
 impl<'a, T> Debug for Vector<'a, T>
 where
     T: 'a + Follow<'a>,
index 5236649..dc26fa3 100644 (file)
@@ -183,6 +183,12 @@ bool IsBitFlagsEnum(const FieldDef &field) {
   return ed && IsBitFlagsEnum(*ed);
 }
 
+// TableArgs make required non-scalars "Option<_>".
+// TODO(cneo): Rework how we do defaults and stuff.
+bool IsOptionalToBuilder(const FieldDef &field) {
+  return field.IsOptional() || !IsScalar(field.value.type.base_type);
+}
+
 namespace rust {
 
 class RustGenerator : public BaseGenerator {
@@ -883,8 +889,10 @@ class RustGenerator : public BaseGenerator {
     return "VT_" + MakeUpper(Name(field));
   }
 
-  std::string GetDefaultValue(const FieldDef &field, bool for_builder) {
-    if (for_builder) {
+  enum DefaultContext { kBuilder, kAccessor, kObject };
+  std::string GetDefaultValue(const FieldDef &field,
+                              const DefaultContext context) {
+    if (context == kBuilder) {
       // Builders and Args structs model nonscalars "optional" even if they're
       // required or have defaults according to the schema. I guess its because
       // WIPOffset is not nullable.
@@ -915,9 +923,19 @@ class RustGenerator : public BaseGenerator {
         return ObjectFieldType(field, true) + "::NONE";
       }
       case ftString: {
-        // Required strings.
-        return "String::new()";  // No default strings yet.
+        // Required fields do not have defaults defined by the schema, but we
+        // need one for Rust's Default trait so we use empty string. The usual
+        // value of field.value.constant is `0`, which is non-sensical except
+        // maybe to c++ (nullptr == 0).
+        // TODO: Escape strings?
+        const std::string defval =
+            field.IsRequired() ? "\"\"" : "\"" + field.value.constant + "\"";
+        if (context == kObject) return defval + ".to_string()";
+        if (context == kAccessor) return "&" + defval;
+        FLATBUFFERS_ASSERT("Unreachable.");
+        return "INVALID_CODE_GENERATION";
       }
+
       case ftVectorOfBool:
       case ftVectorOfFloat:
       case ftVectorOfInteger:
@@ -925,14 +943,16 @@ class RustGenerator : public BaseGenerator {
       case ftVectorOfStruct:
       case ftVectorOfTable:
       case ftVectorOfEnumKey:
-      case ftVectorOfUnionValue: {
-        // Required vectors.
-        return "Vec::new()";  // No default strings yet.
-      }
+      case ftVectorOfUnionValue:
       case ftStruct:
       case ftTable: {
-        // Required struct/tables.
-        return "Default::default()";  // punt.
+        // We only support empty vectors which matches the defaults for
+        // &[T] and Vec<T> anyway.
+        //
+        // For required structs and tables fields, we defer to their object API
+        // defaults. This works so long as there's nothing recursive happening,
+        // but `table Infinity { i: Infinity (required); }` does compile.
+        return "Default::default()";
       }
     }
     FLATBUFFERS_ASSERT("Unreachable.");
@@ -953,30 +973,38 @@ class RustGenerator : public BaseGenerator {
   std::string TableBuilderArgsDefnType(const FieldDef &field,
                                        const std::string &lifetime) {
     const Type &type = field.value.type;
+    auto WrapOption = [&](std::string s) {
+      return IsOptionalToBuilder(field) ? "Option<" + s + ">" : s;
+    };
+    auto WrapVector = [&](std::string ty) {
+      return WrapOption("flatbuffers::WIPOffset<flatbuffers::Vector<" +
+                        lifetime + ", " + ty + ">>");
+    };
+    auto WrapUOffsetsVector = [&](std::string ty) {
+      return WrapVector("flatbuffers::ForwardsUOffset<" + ty + ">");
+    };
 
     switch (GetFullType(type)) {
       case ftInteger:
       case ftFloat:
       case ftBool: {
-        const auto typname = GetTypeBasic(type);
-        return field.IsOptional() ? "Option<" + typname + ">" : typname;
+        return WrapOption(GetTypeBasic(type));
       }
       case ftStruct: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return "Option<&" + lifetime + " " + typname + ">";
+        return WrapOption("&" + lifetime + " " + typname);
       }
       case ftTable: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return "Option<flatbuffers::WIPOffset<" + typname + "<" + lifetime +
-               ">>>";
+        return WrapOption("flatbuffers::WIPOffset<" + typname + "<" + lifetime +
+                          ">>");
       }
       case ftString: {
-        return "Option<flatbuffers::WIPOffset<&" + lifetime + " str>>";
+        return WrapOption("flatbuffers::WIPOffset<&" + lifetime + " str>");
       }
       case ftEnumKey:
       case ftUnionKey: {
-        const auto typname = WrapInNameSpace(*type.enum_def);
-        return field.IsOptional() ? "Option<" + typname + ">" : typname;
+        return WrapOption(WrapInNameSpace(*type.enum_def));
       }
       case ftUnionValue: {
         return "Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>";
@@ -986,36 +1014,25 @@ class RustGenerator : public BaseGenerator {
       case ftVectorOfBool:
       case ftVectorOfFloat: {
         const auto typname = GetTypeBasic(type.VectorType());
-        return "Option<flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
-               ", " + typname + ">>>";
+        return WrapVector(typname);
       }
       case ftVectorOfEnumKey: {
         const auto typname = WrapInNameSpace(*type.enum_def);
-        return "Option<flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
-               ", " + typname + ">>>";
+        return WrapVector(typname);
       }
       case ftVectorOfStruct: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return "Option<flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
-               ", " + typname + ">>>";
+        return WrapVector(typname);
       }
       case ftVectorOfTable: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return "Option<flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
-               ", flatbuffers::ForwardsUOffset<" + typname + "<" + lifetime +
-               ">>>>>";
+        return WrapUOffsetsVector(typname + "<" + lifetime + ">");
       }
       case ftVectorOfString: {
-        return "Option<flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
-               ", flatbuffers::ForwardsUOffset<&" + lifetime + " str>>>>";
+        return WrapUOffsetsVector("&" + lifetime + " str");
       }
       case ftVectorOfUnionValue: {
-        const auto typname =
-            WrapInNameSpace(*type.enum_def) + "UnionTableOffset";
-        return "Option<flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
-               ", flatbuffers::ForwardsUOffset<"
-               "flatbuffers::Table<" +
-               lifetime + ">>>>";
+        return WrapUOffsetsVector("flatbuffers::Table<" + lifetime + ">");
       }
     }
     return "INVALID_CODE_GENERATION";  // for return analysis
@@ -1084,8 +1101,7 @@ class RustGenerator : public BaseGenerator {
         return "INVALID_CODE_GENERATION";  // OH NO!
       }
     }
-    if (in_a_table && !IsUnion(type) &&
-        (IsScalar(type.base_type) ? field.IsOptional() : !field.IsRequired())) {
+    if (in_a_table && !IsUnion(type) && field.IsOptional()) {
       return "Option<" + ty + ">";
     } else {
       return ty;
@@ -1207,73 +1223,64 @@ class RustGenerator : public BaseGenerator {
   std::string GenTableAccessorFuncReturnType(const FieldDef &field,
                                              const std::string &lifetime) {
     const Type &type = field.value.type;
+    const auto WrapOption = [&](std::string s) {
+      return field.IsOptional() ? "Option<" + s + ">" : s;
+    };
 
     switch (GetFullType(field.value.type)) {
       case ftInteger:
       case ftFloat:
       case ftBool: {
-        const auto typname = GetTypeBasic(type);
-        return field.IsOptional() ? "Option<" + typname + ">" : typname;
+        return WrapOption(GetTypeBasic(type));
       }
       case ftStruct: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return WrapInOptionIfNotRequired("&" + lifetime + " " + typname,
-                                         field.IsRequired());
+        return WrapOption("&" + lifetime + " " + typname);
       }
       case ftTable: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return WrapInOptionIfNotRequired(typname + "<" + lifetime + ">",
-                                         field.IsRequired());
+        return WrapOption(typname + "<" + lifetime + ">");
       }
       case ftEnumKey:
       case ftUnionKey: {
-        const auto typname = WrapInNameSpace(*type.enum_def);
-        return field.IsOptional() ? "Option<" + typname + ">" : typname;
+        return WrapOption(WrapInNameSpace(*type.enum_def));
       }
 
       case ftUnionValue: {
-        return WrapInOptionIfNotRequired("flatbuffers::Table<" + lifetime + ">",
-                                         field.IsRequired());
+        return WrapOption("flatbuffers::Table<" + lifetime + ">");
       }
       case ftString: {
-        return WrapInOptionIfNotRequired("&" + lifetime + " str",
-                                         field.IsRequired());
+        return WrapOption("&" + lifetime + " str");
       }
       case ftVectorOfInteger:
       case ftVectorOfBool:
       case ftVectorOfFloat: {
         const auto typname = GetTypeBasic(type.VectorType());
-        if (IsOneByte(type.VectorType().base_type)) {
-          return WrapInOptionIfNotRequired(
-              "&" + lifetime + " [" + typname + "]", field.IsRequired());
-        }
-        return WrapInOptionIfNotRequired(
-            "flatbuffers::Vector<" + lifetime + ", " + typname + ">",
-            field.IsRequired());
+        const auto vector_type =
+            IsOneByte(type.VectorType().base_type)
+                ? "&" + lifetime + " [" + typname + "]"
+                : "flatbuffers::Vector<" + lifetime + ", " + typname + ">";
+        return WrapOption(vector_type);
       }
       case ftVectorOfEnumKey: {
         const auto typname = WrapInNameSpace(*type.enum_def);
-        return WrapInOptionIfNotRequired(
-            "flatbuffers::Vector<" + lifetime + ", " + typname + ">",
-            field.IsRequired());
+        return WrapOption("flatbuffers::Vector<" + lifetime + ", " + typname +
+                          ">");
       }
       case ftVectorOfStruct: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return WrapInOptionIfNotRequired("&" + lifetime + " [" + typname + "]",
-                                         field.IsRequired());
+        return WrapOption("&" + lifetime + " [" + typname + "]");
       }
       case ftVectorOfTable: {
         const auto typname = WrapInNameSpace(*type.struct_def);
-        return WrapInOptionIfNotRequired("flatbuffers::Vector<" + lifetime +
-                                             ", flatbuffers::ForwardsUOffset<" +
-                                             typname + "<" + lifetime + ">>>",
-                                         field.IsRequired());
+        return WrapOption("flatbuffers::Vector<" + lifetime +
+                          ", flatbuffers::ForwardsUOffset<" + typname + "<" +
+                          lifetime + ">>>");
       }
       case ftVectorOfString: {
-        return WrapInOptionIfNotRequired(
-            "flatbuffers::Vector<" + lifetime +
-                ", flatbuffers::ForwardsUOffset<&" + lifetime + " str>>",
-            field.IsRequired());
+        return WrapOption("flatbuffers::Vector<" + lifetime +
+                          ", flatbuffers::ForwardsUOffset<&" + lifetime +
+                          " str>>");
       }
       case ftVectorOfUnionValue: {
         FLATBUFFERS_ASSERT(false && "vectors of unions are not yet supported");
@@ -1354,7 +1361,7 @@ class RustGenerator : public BaseGenerator {
     // Default-y fields (scalars so far) are neither optional nor required.
     const std::string default_value =
         !(field.IsOptional() || field.IsRequired())
-            ? "Some(" + GetDefaultValue(field, /*builder=*/true) + ")"
+            ? "Some(" + GetDefaultValue(field, kAccessor) + ")"
             : "None";
     const std::string unwrap = field.IsOptional() ? "" : ".unwrap()";
 
@@ -1373,18 +1380,6 @@ class RustGenerator : public BaseGenerator {
            ", " + default_value + ")" + safe_slice + unwrap;
   }
 
-  bool TableFieldReturnsOption(const FieldDef &field) {
-    if (field.IsOptional()) return true;
-    switch (GetFullType(field.value.type)) {
-      case ftInteger:
-      case ftFloat:
-      case ftBool:
-      case ftEnumKey:
-      case ftUnionKey: return false;
-      default: return true;
-    }
-  }
-
   // Generates a fully-qualified name getter for use with --gen-name-strings
   void GenFullyQualifiedNameGetter(const StructDef &struct_def,
                                    const std::string &name) {
@@ -1425,7 +1420,7 @@ class RustGenerator : public BaseGenerator {
       code_.SetValue("OFFSET_NAME", GetFieldOffsetName(field));
       code_.SetValue("OFFSET_VALUE", NumToString(field.value.offset));
       code_.SetValue("FIELD_NAME", Name(field));
-      code_.SetValue("BLDR_DEF_VAL", GetDefaultValue(field, /*builder=*/true));
+      code_.SetValue("BLDR_DEF_VAL", GetDefaultValue(field, kBuilder));
       cb(field);
     };
     const auto &fields = struct_def.fields.vec;
@@ -1498,7 +1493,7 @@ class RustGenerator : public BaseGenerator {
             if (struct_def.sortbysize &&
                 size != SizeOf(field.value.type.base_type))
               return;
-            if (TableFieldReturnsOption(field)) {
+            if (IsOptionalToBuilder(field)) {
               code_ +=
                   "      if let Some(x) = args.{{FIELD_NAME}} "
                   "{ builder.add_{{FIELD_NAME}}(x); }";
@@ -1943,7 +1938,7 @@ class RustGenerator : public BaseGenerator {
     code_ += "    Self {";
     ForAllObjectTableFields(table, [&](const FieldDef &field) {
       if (field.value.type.base_type == BASE_TYPE_UTYPE) return;
-      std::string default_value = GetDefaultValue(field, /*builder=*/false);
+      std::string default_value = GetDefaultValue(field, kObject);
       code_ += "      {{FIELD_NAME}}: " + default_value + ",";
     });
     code_ += "    }";
@@ -2064,17 +2059,17 @@ class RustGenerator : public BaseGenerator {
     }
   }
   void MapNativeTableField(const FieldDef &field, const std::string &expr) {
-    if (field.IsRequired()) {
+    if (field.IsOptional()) {
+      code_ += "    let {{FIELD_NAME}} = self.{{FIELD_NAME}}.as_ref().map(|x|{";
+      code_ += "      " + expr;
+      code_ += "    });";
+    } else {
       // For some reason Args has optional types for required fields.
       // TODO(cneo): Fix this... but its a breaking change?
       code_ += "    let {{FIELD_NAME}} = Some({";
       code_ += "      let x = &self.{{FIELD_NAME}};";
       code_ += "      " + expr;
       code_ += "    });";
-    } else {
-      code_ += "    let {{FIELD_NAME}} = self.{{FIELD_NAME}}.as_ref().map(|x|{";
-      code_ += "      " + expr;
-      code_ += "    });";
     }
   }
 
index e32adc6..c633191 100644 (file)
@@ -794,10 +794,19 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
   if (token_ == '=') {
     NEXT();
     ECHECK(ParseSingleValue(&field->name, field->value, true));
-    if (!IsScalar(type.base_type) ||
-        (struct_def.fixed && field->value.constant != "0"))
+    if (IsStruct(type) || (struct_def.fixed && field->value.constant != "0"))
       return Error(
-          "default values currently only supported for scalars in tables");
+          "default values are not supported for struct fields, table fields, "
+          "or in structs.");
+    if ((IsString(type) || IsVector(type)) && field->value.constant != "0" &&
+        field->value.constant != "null" && !SupportsDefaultVectorsAndStrings())
+      return Error(
+          "Default values for strings and vectors are not supported in one of "
+          "the specified programming languages");
+    if (IsVector(type) && field->value.constant != "0" &&
+        field->value.constant != "[]") {
+      return Error("The only supported default for vectors is `[]`.");
+    }
   }
 
   // Append .0 if the value has not it (skip hex and scientific floats).
@@ -856,8 +865,11 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
   field->key = field->attributes.Lookup("key") != nullptr;
   const bool required = field->attributes.Lookup("required") != nullptr ||
                         (IsString(type) && field->key);
-  const bool optional =
-      IsScalar(type.base_type) ? (field->value.constant == "null") : !required;
+  const bool default_str_or_vec =
+      ((IsString(type) || IsVector(type)) && field->value.constant != "0");
+  const bool optional = IsScalar(type.base_type)
+                            ? (field->value.constant == "null")
+                            : !(required || default_str_or_vec);
   if (required && optional) {
     return Error("Fields cannot be both optional and required.");
   }
@@ -1962,6 +1974,15 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
     // Integer token can init any scalar (integer of float).
     FORCE_ECHECK(kTokenIntegerConstant, IsScalar(in_type), BASE_TYPE_INT);
   }
+  // Match empty vectors for default-empty-vectors.
+  if (!match && IsVector(e.type) && token_ == '[') {
+    NEXT();
+    if (token_ != ']') { return Error("Expected `]` in vector default"); }
+    NEXT();
+    match = true;
+    e.constant = "[]";
+  }
+
 #undef FORCE_ECHECK
 #undef TRY_ECHECK
 #undef IF_ECHECK_
@@ -2406,12 +2427,17 @@ bool Parser::SupportsOptionalScalars(const flatbuffers::IDLOptions &opts) {
   unsigned long langs = opts.lang_to_generate;
   return (langs > 0 && langs < IDLOptions::kMAX) && !(langs & ~supported_langs);
 }
-
 bool Parser::SupportsOptionalScalars() const {
   // Check in general if a language isn't specified.
   return opts.lang_to_generate == 0 || SupportsOptionalScalars(opts);
 }
 
+bool Parser::SupportsDefaultVectorsAndStrings() const {
+  static FLATBUFFERS_CONSTEXPR unsigned long supported_langs =
+      IDLOptions::kRust;
+  return !(opts.lang_to_generate & ~supported_langs);
+}
+
 bool Parser::SupportsAdvancedUnionFeatures() const {
   return opts.lang_to_generate != 0 &&
          (opts.lang_to_generate &
index 1531281..0f374f6 100755 (executable)
@@ -61,6 +61,9 @@ $TEST_NOINCL_FLAGS $TEST_CPP_FLAGS $TEST_CS_FLAGS $TEST_JS_TS_FLAGS -o namespace
 ../flatc --csharp --rust --gen-object-api optional_scalars.fbs
 ../flatc $TEST_NOINCL_FLAGS $TEST_CPP_FLAGS --cpp optional_scalars.fbs
 
+# Generate string/vector default code for tests
+../flatc --rust --gen-object-api more_defaults.fbs
+
 # Generate the schema evolution tests
 ../flatc --cpp --scoped-enums $TEST_CPP_FLAGS -o evolution_test ./evolution_test/evolution_v*.fbs
 
index 2e4b840..0102c74 100644 (file)
@@ -3197,7 +3197,7 @@ impl Default for MonsterT {
       pos: None,
       mana: 150,
       hp: 100,
-      name: String::new(),
+      name: "".to_string(),
       inventory: None,
       color: Color::Blue,
       test: AnyT::NONE,
diff --git a/tests/more_defaults.fbs b/tests/more_defaults.fbs
new file mode 100644 (file)
index 0000000..913f2bd
--- /dev/null
@@ -0,0 +1,7 @@
+
+table MoreDefaults {
+  ints: [int] = [];
+  floats: [float] = [     ];
+  empty_string: string = "";
+  some_string: string = "some";
+}
diff --git a/tests/more_defaults_generated.rs b/tests/more_defaults_generated.rs
new file mode 100644 (file)
index 0000000..800677e
--- /dev/null
@@ -0,0 +1,214 @@
+// automatically generated by the FlatBuffers compiler, do not modify
+
+
+
+use std::mem;
+use std::cmp::Ordering;
+
+extern crate flatbuffers;
+use self::flatbuffers::EndianScalar;
+
+pub enum MoreDefaultsOffset {}
+#[derive(Copy, Clone, PartialEq)]
+
+pub struct MoreDefaults<'a> {
+  pub _tab: flatbuffers::Table<'a>,
+}
+
+impl<'a> flatbuffers::Follow<'a> for MoreDefaults<'a> {
+    type Inner = MoreDefaults<'a>;
+    #[inline]
+    fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {
+        Self { _tab: flatbuffers::Table { buf, loc } }
+    }
+}
+
+impl<'a> MoreDefaults<'a> {
+    #[inline]
+    pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self {
+        MoreDefaults { _tab: table }
+    }
+    #[allow(unused_mut)]
+    pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(
+        _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,
+        args: &'args MoreDefaultsArgs<'args>) -> flatbuffers::WIPOffset<MoreDefaults<'bldr>> {
+      let mut builder = MoreDefaultsBuilder::new(_fbb);
+      if let Some(x) = args.some_string { builder.add_some_string(x); }
+      if let Some(x) = args.empty_string { builder.add_empty_string(x); }
+      if let Some(x) = args.floats { builder.add_floats(x); }
+      if let Some(x) = args.ints { builder.add_ints(x); }
+      builder.finish()
+    }
+
+    pub fn unpack(&self) -> MoreDefaultsT {
+      let ints = {
+        let x = self.ints();
+        x.into_iter().collect()
+      };
+      let floats = {
+        let x = self.floats();
+        x.into_iter().collect()
+      };
+      let empty_string = {
+        let x = self.empty_string();
+        x.to_string()
+      };
+      let some_string = {
+        let x = self.some_string();
+        x.to_string()
+      };
+      MoreDefaultsT {
+        ints,
+        floats,
+        empty_string,
+        some_string,
+      }
+    }
+    pub const VT_INTS: flatbuffers::VOffsetT = 4;
+    pub const VT_FLOATS: flatbuffers::VOffsetT = 6;
+    pub const VT_EMPTY_STRING: flatbuffers::VOffsetT = 8;
+    pub const VT_SOME_STRING: flatbuffers::VOffsetT = 10;
+
+  #[inline]
+  pub fn ints(&self) -> flatbuffers::Vector<'a, i32> {
+    self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, i32>>>(MoreDefaults::VT_INTS, Some(Default::default())).unwrap()
+  }
+  #[inline]
+  pub fn floats(&self) -> flatbuffers::Vector<'a, f32> {
+    self._tab.get::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'a, f32>>>(MoreDefaults::VT_FLOATS, Some(Default::default())).unwrap()
+  }
+  #[inline]
+  pub fn empty_string(&self) -> &'a str {
+    self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(MoreDefaults::VT_EMPTY_STRING, Some(&"")).unwrap()
+  }
+  #[inline]
+  pub fn some_string(&self) -> &'a str {
+    self._tab.get::<flatbuffers::ForwardsUOffset<&str>>(MoreDefaults::VT_SOME_STRING, Some(&"some")).unwrap()
+  }
+}
+
+impl flatbuffers::Verifiable for MoreDefaults<'_> {
+  #[inline]
+  fn run_verifier(
+    v: &mut flatbuffers::Verifier, pos: usize
+  ) -> Result<(), flatbuffers::InvalidFlatbuffer> {
+    use self::flatbuffers::Verifiable;
+    v.visit_table(pos)?
+     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, i32>>>(&"ints", Self::VT_INTS, false)?
+     .visit_field::<flatbuffers::ForwardsUOffset<flatbuffers::Vector<'_, f32>>>(&"floats", Self::VT_FLOATS, false)?
+     .visit_field::<flatbuffers::ForwardsUOffset<&str>>(&"empty_string", Self::VT_EMPTY_STRING, false)?
+     .visit_field::<flatbuffers::ForwardsUOffset<&str>>(&"some_string", Self::VT_SOME_STRING, false)?
+     .finish();
+    Ok(())
+  }
+}
+pub struct MoreDefaultsArgs<'a> {
+    pub ints: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, i32>>>,
+    pub floats: Option<flatbuffers::WIPOffset<flatbuffers::Vector<'a, f32>>>,
+    pub empty_string: Option<flatbuffers::WIPOffset<&'a str>>,
+    pub some_string: Option<flatbuffers::WIPOffset<&'a str>>,
+}
+impl<'a> Default for MoreDefaultsArgs<'a> {
+    #[inline]
+    fn default() -> Self {
+        MoreDefaultsArgs {
+            ints: None,
+            floats: None,
+            empty_string: None,
+            some_string: None,
+        }
+    }
+}
+pub struct MoreDefaultsBuilder<'a: 'b, 'b> {
+  fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,
+  start_: flatbuffers::WIPOffset<flatbuffers::TableUnfinishedWIPOffset>,
+}
+impl<'a: 'b, 'b> MoreDefaultsBuilder<'a, 'b> {
+  #[inline]
+  pub fn add_ints(&mut self, ints: flatbuffers::WIPOffset<flatbuffers::Vector<'b , i32>>) {
+    self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(MoreDefaults::VT_INTS, ints);
+  }
+  #[inline]
+  pub fn add_floats(&mut self, floats: flatbuffers::WIPOffset<flatbuffers::Vector<'b , f32>>) {
+    self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(MoreDefaults::VT_FLOATS, floats);
+  }
+  #[inline]
+  pub fn add_empty_string(&mut self, empty_string: flatbuffers::WIPOffset<&'b  str>) {
+    self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(MoreDefaults::VT_EMPTY_STRING, empty_string);
+  }
+  #[inline]
+  pub fn add_some_string(&mut self, some_string: flatbuffers::WIPOffset<&'b  str>) {
+    self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>(MoreDefaults::VT_SOME_STRING, some_string);
+  }
+  #[inline]
+  pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> MoreDefaultsBuilder<'a, 'b> {
+    let start = _fbb.start_table();
+    MoreDefaultsBuilder {
+      fbb_: _fbb,
+      start_: start,
+    }
+  }
+  #[inline]
+  pub fn finish(self) -> flatbuffers::WIPOffset<MoreDefaults<'a>> {
+    let o = self.fbb_.end_table(self.start_);
+    flatbuffers::WIPOffset::new(o.value())
+  }
+}
+
+impl std::fmt::Debug for MoreDefaults<'_> {
+  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    let mut ds = f.debug_struct("MoreDefaults");
+      ds.field("ints", &self.ints());
+      ds.field("floats", &self.floats());
+      ds.field("empty_string", &self.empty_string());
+      ds.field("some_string", &self.some_string());
+      ds.finish()
+  }
+}
+#[non_exhaustive]
+#[derive(Debug, Clone, PartialEq)]
+pub struct MoreDefaultsT {
+  pub ints: Vec<i32>,
+  pub floats: Vec<f32>,
+  pub empty_string: String,
+  pub some_string: String,
+}
+impl Default for MoreDefaultsT {
+  fn default() -> Self {
+    Self {
+      ints: Default::default(),
+      floats: Default::default(),
+      empty_string: "".to_string(),
+      some_string: "some".to_string(),
+    }
+  }
+}
+impl MoreDefaultsT {
+  pub fn pack<'b>(
+    &self,
+    _fbb: &mut flatbuffers::FlatBufferBuilder<'b>
+  ) -> flatbuffers::WIPOffset<MoreDefaults<'b>> {
+    let ints = Some({
+      let x = &self.ints;
+      _fbb.create_vector(x)
+    });
+    let floats = Some({
+      let x = &self.floats;
+      _fbb.create_vector(x)
+    });
+    let empty_string = Some({
+      let x = &self.empty_string;
+      _fbb.create_string(x)
+    });
+    let some_string = Some({
+      let x = &self.some_string;
+      _fbb.create_string(x)
+    });
+    MoreDefaults::create(_fbb, &MoreDefaultsArgs{
+      ints,
+      floats,
+      empty_string,
+      some_string,
+    })
+  }
+}
index 9bbbe88..ccc69d2 100644 (file)
@@ -30,6 +30,7 @@ extern crate quickcheck_derive;
 
 mod flexbuffers_tests;
 mod optional_scalars_test;
+mod more_defaults_test;
 
 #[allow(dead_code, unused_imports)]
 #[path = "../../include_test/include_test1_generated.rs"]
diff --git a/tests/rust_usage_test/tests/more_defaults_test.rs b/tests/rust_usage_test/tests/more_defaults_test.rs
new file mode 100644 (file)
index 0000000..5127bb9
--- /dev/null
@@ -0,0 +1,26 @@
+#[allow(dead_code, unused_imports)]
+#[path = "../../more_defaults_generated.rs"]
+mod more_defaults_generated;
+use self::more_defaults_generated::*;
+
+#[test]
+fn object_defaults() {
+    assert_eq!(
+        MoreDefaultsT::default(),
+        MoreDefaultsT {
+            ints: Vec::new(),
+            floats: Vec::new(),
+            empty_string: "".to_string(),
+            some_string: "some".to_string(),
+        },
+    )
+}
+
+#[test]
+fn nonpresent_values() {
+    let m = flatbuffers::root::<MoreDefaults>(&[0; 4]).unwrap();
+    assert_eq!(m.ints().len(), 0);
+    assert_eq!(m.floats().len(), 0);
+    assert_eq!(m.empty_string(), "");
+    assert_eq!(m.some_string(), "some");
+}
index d070b9d..f745bc7 100644 (file)
@@ -204,7 +204,6 @@ flatbuffers::DetachedBuffer CreateFlatBufferTest(std::string &buffer) {
   flexbuild.Int(1234);
   flexbuild.Finish();
   auto flex = builder.CreateVector(flexbuild.GetBuffer());
-
   // Test vector of enums.
   Color colors[] = { Color_Blue, Color_Green };
   // We use this special creation function because we have an array of
@@ -1645,7 +1644,6 @@ void ErrorTest() {
   TestError("table X { Y:int; Y:int; }", "field already");
   TestError("table Y {} table X { Y:int; }", "same as table");
   TestError("struct X { Y:string; }", "only scalar");
-  TestError("table X { Y:string = \"\"; }", "default values");
   TestError("struct X { a:uint = 42; }", "default values");
   TestError("enum Y:byte { Z = 1 } table X { y:Y; }", "not part of enum");
   TestError("struct X { Y:int (deprecated); }", "deprecate");
@@ -1690,6 +1688,12 @@ void ErrorTest() {
             "may contain only scalar or struct fields");
   // Non-snake case field names
   TestError("table X { Y: int; } root_type Y: {Y:1.0}", "snake_case");
+  // Complex defaults
+  TestError("table X { y: string = 1; }", "expecting: string");
+  TestError("table X { y: string = []; }", " Cannot assign token");
+  TestError("table X { y: [int] = [1]; }", "Expected `]`");
+  TestError("table X { y: [int] = [; }", "Expected `]`");
+  TestError("table X { y: [int] = \"\"; }", "type mismatch");
 }
 
 template<typename T>
@@ -3620,6 +3624,22 @@ void TestEmbeddedBinarySchema() {
           0);
 }
 
+void StringVectorDefaultsTest() {
+  std::vector<std::string> schemas;
+  schemas.push_back("table Monster { mana: string = \"\"; }");
+  schemas.push_back("table Monster { mana: string = \"mystr\"; }");
+  schemas.push_back("table Monster { mana: string = \"  \"; }");
+  schemas.push_back("table Monster { mana: [int] = []; }");
+  schemas.push_back("table Monster { mana: [uint] = [  ]; }");
+  schemas.push_back("table Monster { mana: [byte] = [\t\t\n]; }");
+  for (auto s = schemas.begin(); s < schemas.end(); s++) {
+    flatbuffers::Parser parser;
+    TEST_ASSERT(parser.Parse(s->c_str()));
+    const auto *mana = parser.structs_.Lookup("Monster")->fields.Lookup("mana");
+    TEST_EQ(mana->IsDefault(), true);
+  }
+}
+
 void OptionalScalarsTest() {
   // Simple schemas and a "has optional scalar" sentinal.
   std::vector<std::string> schemas;
@@ -3852,6 +3872,7 @@ int FlatBufferTests() {
   FlatbuffersSpanTest();
   FixedLengthArrayConstructorTest();
   FieldIdentifierTest();
+  StringVectorDefaultsTest();
   return 0;
 }