From 86401e078d0746d2381735415f8c2dfe849f3f52 Mon Sep 17 00:00:00 2001 From: Casper Date: Fri, 12 Feb 2021 09:41:10 -0500 Subject: [PATCH] Default strings and vectors: Parser + Rust support (#6421) * 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 --- include/flatbuffers/idl.h | 2 + rust/flatbuffers/Cargo.toml | 2 +- rust/flatbuffers/src/vector.rs | 8 + src/idl_gen_rust.cpp | 175 +++++++++--------- src/idl_parser.cpp | 38 +++- tests/generate_code.sh | 3 + tests/monster_test_generated.rs | 2 +- tests/more_defaults.fbs | 7 + tests/more_defaults_generated.rs | 214 ++++++++++++++++++++++ tests/rust_usage_test/tests/integration_test.rs | 1 + tests/rust_usage_test/tests/more_defaults_test.rs | 26 +++ tests/test.cpp | 25 ++- 12 files changed, 403 insertions(+), 100 deletions(-) create mode 100644 tests/more_defaults.fbs create mode 100644 tests/more_defaults_generated.rs create mode 100644 tests/rust_usage_test/tests/more_defaults_test.rs diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 116f500..955ac3f 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -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(); diff --git a/rust/flatbuffers/Cargo.toml b/rust/flatbuffers/Cargo.toml index 13d534f..d2bd785 100644 --- a/rust/flatbuffers/Cargo.toml +++ b/rust/flatbuffers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flatbuffers" -version = "0.8.2" +version = "0.8.3" edition = "2018" authors = ["Robert Winslow ", "FlatBuffers Maintainers"] license = "Apache-2.0" diff --git a/rust/flatbuffers/src/vector.rs b/rust/flatbuffers/src/vector.rs index c53a878..b54d3ec 100644 --- a/rust/flatbuffers/src/vector.rs +++ b/rust/flatbuffers/src/vector.rs @@ -29,6 +29,14 @@ use crate::primitives::*; pub struct Vector<'a, T: 'a>(&'a [u8], usize, PhantomData); +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::()], 0, Default::default()) + } +} + impl<'a, T> Debug for Vector<'a, T> where T: 'a + Follow<'a>, diff --git a/src/idl_gen_rust.cpp b/src/idl_gen_rust.cpp index 5236649..dc26fa3 100644 --- a/src/idl_gen_rust.cpp +++ b/src/idl_gen_rust.cpp @@ -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 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>"); + }; + 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>>"; + return WrapOption("flatbuffers::WIPOffset<" + typname + "<" + lifetime + + ">>"); } case ftString: { - return "Option>"; + 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>"; @@ -986,36 +1014,25 @@ class RustGenerator : public BaseGenerator { case ftVectorOfBool: case ftVectorOfFloat: { const auto typname = GetTypeBasic(type.VectorType()); - return "Option>>"; + return WrapVector(typname); } case ftVectorOfEnumKey: { const auto typname = WrapInNameSpace(*type.enum_def); - return "Option>>"; + return WrapVector(typname); } case ftVectorOfStruct: { const auto typname = WrapInNameSpace(*type.struct_def); - return "Option>>"; + return WrapVector(typname); } case ftVectorOfTable: { const auto typname = WrapInNameSpace(*type.struct_def); - return "Option>>>>"; + return WrapUOffsetsVector(typname + "<" + lifetime + ">"); } case ftVectorOfString: { - return "Option>>>"; + return WrapUOffsetsVector("&" + lifetime + " str"); } case ftVectorOfUnionValue: { - const auto typname = - WrapInNameSpace(*type.enum_def) + "UnionTableOffset"; - return "Option>>>"; + 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_ += " });"; } } diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index e32adc6..c633191 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -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 & diff --git a/tests/generate_code.sh b/tests/generate_code.sh index 1531281..0f374f6 100755 --- a/tests/generate_code.sh +++ b/tests/generate_code.sh @@ -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 diff --git a/tests/monster_test_generated.rs b/tests/monster_test_generated.rs index 2e4b840..0102c74 100644 --- a/tests/monster_test_generated.rs +++ b/tests/monster_test_generated.rs @@ -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 index 0000000..913f2bd --- /dev/null +++ b/tests/more_defaults.fbs @@ -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 index 0000000..800677e --- /dev/null +++ b/tests/more_defaults_generated.rs @@ -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> { + 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::>>(MoreDefaults::VT_INTS, Some(Default::default())).unwrap() + } + #[inline] + pub fn floats(&self) -> flatbuffers::Vector<'a, f32> { + self._tab.get::>>(MoreDefaults::VT_FLOATS, Some(Default::default())).unwrap() + } + #[inline] + pub fn empty_string(&self) -> &'a str { + self._tab.get::>(MoreDefaults::VT_EMPTY_STRING, Some(&"")).unwrap() + } + #[inline] + pub fn some_string(&self) -> &'a str { + self._tab.get::>(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::>>(&"ints", Self::VT_INTS, false)? + .visit_field::>>(&"floats", Self::VT_FLOATS, false)? + .visit_field::>(&"empty_string", Self::VT_EMPTY_STRING, false)? + .visit_field::>(&"some_string", Self::VT_SOME_STRING, false)? + .finish(); + Ok(()) + } +} +pub struct MoreDefaultsArgs<'a> { + pub ints: Option>>, + pub floats: Option>>, + pub empty_string: Option>, + pub some_string: Option>, +} +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, +} +impl<'a: 'b, 'b> MoreDefaultsBuilder<'a, 'b> { + #[inline] + pub fn add_ints(&mut self, ints: flatbuffers::WIPOffset>) { + self.fbb_.push_slot_always::>(MoreDefaults::VT_INTS, ints); + } + #[inline] + pub fn add_floats(&mut self, floats: flatbuffers::WIPOffset>) { + self.fbb_.push_slot_always::>(MoreDefaults::VT_FLOATS, floats); + } + #[inline] + pub fn add_empty_string(&mut self, empty_string: flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::>(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::>(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> { + 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, + pub floats: Vec, + 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> { + 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, + }) + } +} diff --git a/tests/rust_usage_test/tests/integration_test.rs b/tests/rust_usage_test/tests/integration_test.rs index 9bbbe88..ccc69d2 100644 --- a/tests/rust_usage_test/tests/integration_test.rs +++ b/tests/rust_usage_test/tests/integration_test.rs @@ -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 index 0000000..5127bb9 --- /dev/null +++ b/tests/rust_usage_test/tests/more_defaults_test.rs @@ -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::(&[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"); +} diff --git a/tests/test.cpp b/tests/test.cpp index d070b9d..f745bc7 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -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 @@ -3620,6 +3624,22 @@ void TestEmbeddedBinarySchema() { 0); } +void StringVectorDefaultsTest() { + std::vector 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 schemas; @@ -3852,6 +3872,7 @@ int FlatBufferTests() { FlatbuffersSpanTest(); FixedLengthArrayConstructorTest(); FieldIdentifierTest(); + StringVectorDefaultsTest(); return 0; } -- 2.7.4