blob: a60046ef459fff2b766ed9d0bb897860833a514b [file] [log] [blame]
/*
* Copyright 2018 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// independent from idl_parser, since this code is not needed for most clients
#include "flatbuffers/code_generators.h"
#include "flatbuffers/flatbuffers.h"
#include "flatbuffers/idl.h"
#include "flatbuffers/util.h"
#include "idl_namer.h"
namespace flatbuffers {
namespace {
static Namer::Config RustDefaultConfig() {
// Historical note: We've been using "keep" casing since the original
// implementation, presumably because Flatbuffers schema style and Rust style
// roughly align. We are not going to enforce proper casing since its an
// unnecessary breaking change.
return { /*types=*/Case::kKeep,
/*constants=*/Case::kScreamingSnake,
/*methods=*/Case::kSnake,
/*functions=*/Case::kSnake,
/*fields=*/Case::kKeep,
/*variables=*/Case::kUnknown, // Unused.
/*variants=*/Case::kKeep,
/*enum_variant_seperator=*/"::",
/*escape_keywords=*/Namer::Config::Escape::BeforeConvertingCase,
/*namespaces=*/Case::kSnake,
/*namespace_seperator=*/"::",
/*object_prefix=*/"",
/*object_suffix=*/"T",
/*keyword_prefix=*/"",
/*keyword_suffix=*/"_",
/*filenames=*/Case::kSnake,
/*directories=*/Case::kSnake,
/*output_path=*/"",
/*filename_suffix=*/"_generated",
/*filename_extension=*/".rs" };
}
static std::set<std::string> RustKeywords() {
return {
// https://doc.rust-lang.org/book/second-edition/appendix-01-keywords.html
"as",
"break",
"const",
"continue",
"crate",
"else",
"enum",
"extern",
"false",
"fn",
"for",
"if",
"impl",
"in",
"let",
"loop",
"match",
"mod",
"move",
"mut",
"pub",
"ref",
"return",
"Self",
"self",
"static",
"struct",
"super",
"trait",
"true",
"type",
"unsafe",
"use",
"where",
"while",
// future possible keywords
"abstract",
"alignof",
"become",
"box",
"do",
"final",
"macro",
"offsetof",
"override",
"priv",
"proc",
"pure",
"sizeof",
"typeof",
"unsized",
"virtual",
"yield",
// other rust terms we should not use
"std",
"usize",
"isize",
"u8",
"i8",
"u16",
"i16",
"u32",
"i32",
"u64",
"i64",
"u128",
"i128",
"f32",
"f64",
// Terms that we use ourselves
"follow",
"push",
"size",
"alignment",
"to_little_endian",
"from_little_endian",
"ENUM_MAX",
"ENUM_MIN",
"ENUM_VALUES",
};
}
// Encapsulate all logical field types in this enum. This allows us to write
// field logic based on type switches, instead of branches on the properties
// set on the Type.
// TODO(rw): for backwards compatibility, we can't use a strict `enum class`
// declaration here. could we use the `-Wswitch-enum` warning to
// achieve the same effect?
enum FullType {
ftInteger = 0,
ftFloat = 1,
ftBool = 2,
ftStruct = 3,
ftTable = 4,
ftEnumKey = 5,
ftUnionKey = 6,
ftUnionValue = 7,
// TODO(rw): bytestring?
ftString = 8,
ftVectorOfInteger = 9,
ftVectorOfFloat = 10,
ftVectorOfBool = 11,
ftVectorOfEnumKey = 12,
ftVectorOfStruct = 13,
ftVectorOfTable = 14,
ftVectorOfString = 15,
ftVectorOfUnionValue = 16,
ftArrayOfBuiltin = 17,
ftArrayOfEnum = 18,
ftArrayOfStruct = 19,
};
// Convert a Type to a FullType (exhaustive).
static FullType GetFullType(const Type &type) {
// N.B. The order of these conditionals matters for some types.
if (IsString(type)) {
return ftString;
} else if (type.base_type == BASE_TYPE_STRUCT) {
if (type.struct_def->fixed) {
return ftStruct;
} else {
return ftTable;
}
} else if (IsVector(type)) {
switch (GetFullType(type.VectorType())) {
case ftInteger: {
return ftVectorOfInteger;
}
case ftFloat: {
return ftVectorOfFloat;
}
case ftBool: {
return ftVectorOfBool;
}
case ftStruct: {
return ftVectorOfStruct;
}
case ftTable: {
return ftVectorOfTable;
}
case ftString: {
return ftVectorOfString;
}
case ftEnumKey: {
return ftVectorOfEnumKey;
}
case ftUnionKey:
case ftUnionValue: {
FLATBUFFERS_ASSERT(false && "vectors of unions are unsupported");
break;
}
default: {
FLATBUFFERS_ASSERT(false && "vector of vectors are unsupported");
}
}
} else if (IsArray(type)) {
switch (GetFullType(type.VectorType())) {
case ftInteger:
case ftFloat:
case ftBool: {
return ftArrayOfBuiltin;
}
case ftStruct: {
return ftArrayOfStruct;
}
case ftEnumKey: {
return ftArrayOfEnum;
}
default: {
FLATBUFFERS_ASSERT(false && "Unsupported type for fixed array");
}
}
} else if (type.enum_def != nullptr) {
if (type.enum_def->is_union) {
if (type.base_type == BASE_TYPE_UNION) {
return ftUnionValue;
} else if (IsInteger(type.base_type)) {
return ftUnionKey;
} else {
FLATBUFFERS_ASSERT(false && "unknown union field type");
}
} else {
return ftEnumKey;
}
} else if (IsScalar(type.base_type)) {
if (IsBool(type.base_type)) {
return ftBool;
} else if (IsInteger(type.base_type)) {
return ftInteger;
} else if (IsFloat(type.base_type)) {
return ftFloat;
} else {
FLATBUFFERS_ASSERT(false && "unknown number type");
}
}
FLATBUFFERS_ASSERT(false && "completely unknown type");
// this is only to satisfy the compiler's return analysis.
return ftBool;
}
static bool IsBitFlagsEnum(const EnumDef &enum_def) {
return enum_def.attributes.Lookup("bit_flags") != nullptr;
}
// TableArgs make required non-scalars "Option<_>".
// TODO(cneo): Rework how we do defaults and stuff.
static bool IsOptionalToBuilder(const FieldDef &field) {
return field.IsOptional() || !IsScalar(field.value.type.base_type);
}
} // namespace
bool GenerateRustModuleRootFile(const Parser &parser,
const std::string &output_dir) {
if (!parser.opts.rust_module_root_file) {
// Don't generate a root file when generating one file. This isn't an error
// so return true.
return true;
}
Namer namer(WithFlagOptions(RustDefaultConfig(), parser.opts, output_dir),
RustKeywords());
// We gather the symbols into a tree of namespaces (which are rust mods) and
// generate a file that gathers them all.
struct Module {
std::map<std::string, Module> sub_modules;
std::vector<std::string> generated_files;
// Add a symbol into the tree.
void Insert(const Namer &namer, const Definition *s) {
const Definition &symbol = *s;
Module *current_module = this;
for (auto it = symbol.defined_namespace->components.begin();
it != symbol.defined_namespace->components.end(); it++) {
std::string ns_component = namer.Namespace(*it);
current_module = &current_module->sub_modules[ns_component];
}
current_module->generated_files.push_back(
namer.File(symbol.name, SkipFile::Extension));
}
// Recursively create the importer file.
void GenerateImports(CodeWriter &code) {
for (auto it = sub_modules.begin(); it != sub_modules.end(); it++) {
code += "pub mod " + it->first + " {";
code.IncrementIdentLevel();
code += "use super::*;";
it->second.GenerateImports(code);
code.DecrementIdentLevel();
code += "} // " + it->first;
}
for (auto it = generated_files.begin(); it != generated_files.end();
it++) {
code += "mod " + *it + ";";
code += "pub use self::" + *it + "::*;";
}
}
};
Module root_module;
for (auto it = parser.enums_.vec.begin(); it != parser.enums_.vec.end();
it++) {
root_module.Insert(namer, *it);
}
for (auto it = parser.structs_.vec.begin(); it != parser.structs_.vec.end();
it++) {
root_module.Insert(namer, *it);
}
CodeWriter code(" ");
// TODO(caspern): Move generated warning out of BaseGenerator.
code +=
"// Automatically generated by the Flatbuffers compiler. "
"Do not modify.";
code += "// @generated";
root_module.GenerateImports(code);
const bool success =
SaveFile((output_dir + "mod.rs").c_str(), code.ToString(), false);
code.Clear();
return success;
}
namespace rust {
class RustGenerator : public BaseGenerator {
public:
RustGenerator(const Parser &parser, const std::string &path,
const std::string &file_name)
: BaseGenerator(parser, path, file_name, "", "::", "rs"),
cur_name_space_(nullptr),
namer_(WithFlagOptions(RustDefaultConfig(), parser.opts, path),
RustKeywords()) {
// TODO: Namer flag overrides should be in flatc or flatc_main.
code_.SetPadding(" ");
}
bool generate() {
if (!parser_.opts.rust_module_root_file) {
return GenerateOneFile();
} else {
return GenerateIndividualFiles();
}
}
template<typename T>
bool GenerateSymbols(const SymbolTable<T> &symbols,
std::function<void(const T &)> gen_symbol) {
for (auto it = symbols.vec.begin(); it != symbols.vec.end(); it++) {
const T &symbol = **it;
if (symbol.generated) continue;
code_.Clear();
code_ += "// " + std::string(FlatBuffersGeneratedWarning());
code_ += "// @generated";
code_ += "extern crate alloc;";
code_ += "extern crate flatbuffers;";
code_ += "use alloc::boxed::Box;";
code_ += "use alloc::string::{String, ToString};";
code_ += "use alloc::vec::Vec;";
code_ += "use core::mem;";
code_ += "use core::cmp::Ordering;";
if (parser_.opts.rust_serialize) {
code_ += "extern crate serde;";
code_ +=
"use self::serde::ser::{Serialize, Serializer, SerializeStruct};";
}
code_ += "use self::flatbuffers::{EndianScalar, Follow};";
code_ += "use super::*;";
cur_name_space_ = symbol.defined_namespace;
gen_symbol(symbol);
const std::string directories =
namer_.Directories(*symbol.defined_namespace);
EnsureDirExists(directories);
const std::string file_path = directories + namer_.File(symbol);
const bool save_success =
SaveFile(file_path.c_str(), code_.ToString(), /*binary=*/false);
if (!save_success) return false;
}
return true;
}
bool GenerateIndividualFiles() {
code_.Clear();
// Don't bother with imports. Use absolute paths everywhere.
return GenerateSymbols<EnumDef>(
parser_.enums_, [&](const EnumDef &e) { this->GenEnum(e); }) &&
GenerateSymbols<StructDef>(
parser_.structs_, [&](const StructDef &s) {
if (s.fixed) {
this->GenStruct(s);
} else {
this->GenTable(s);
if (this->parser_.opts.generate_object_based_api) {
this->GenTableObject(s);
}
}
if (this->parser_.root_struct_def_ == &s) {
this->GenRootTableFuncs(s);
}
});
}
// Generates code organized by .fbs files. This is broken legacy behavior
// that does not work with multiple fbs files with shared namespaces.
// Iterate through all definitions we haven't generated code for (enums,
// structs, and tables) and output them to a single file.
bool GenerateOneFile() {
code_.Clear();
code_ += "// " + std::string(FlatBuffersGeneratedWarning()) + "\n\n";
code_ += "// @generated";
assert(!cur_name_space_);
// Generate imports for the global scope in case no namespace is used
// in the schema file.
GenNamespaceImports(0);
code_ += "";
// Generate all code in their namespaces, once, because Rust does not
// permit re-opening modules.
//
// TODO(rw): Use a set data structure to reduce namespace evaluations from
// O(n**2) to O(n).
for (auto ns_it = parser_.namespaces_.begin();
ns_it != parser_.namespaces_.end(); ++ns_it) {
const auto &ns = *ns_it;
// Generate code for all the enum declarations.
for (auto it = parser_.enums_.vec.begin(); it != parser_.enums_.vec.end();
++it) {
const auto &enum_def = **it;
if (enum_def.defined_namespace == ns && !enum_def.generated) {
SetNameSpace(enum_def.defined_namespace);
GenEnum(enum_def);
}
}
// Generate code for all structs.
for (auto it = parser_.structs_.vec.begin();
it != parser_.structs_.vec.end(); ++it) {
const auto &struct_def = **it;
if (struct_def.defined_namespace == ns && struct_def.fixed &&
!struct_def.generated) {
SetNameSpace(struct_def.defined_namespace);
GenStruct(struct_def);
}
}
// Generate code for all tables.
for (auto it = parser_.structs_.vec.begin();
it != parser_.structs_.vec.end(); ++it) {
const auto &struct_def = **it;
if (struct_def.defined_namespace == ns && !struct_def.fixed &&
!struct_def.generated) {
SetNameSpace(struct_def.defined_namespace);
GenTable(struct_def);
if (parser_.opts.generate_object_based_api) {
GenTableObject(struct_def);
}
}
}
// Generate global helper functions.
if (parser_.root_struct_def_) {
auto &struct_def = *parser_.root_struct_def_;
if (struct_def.defined_namespace != ns) { continue; }
SetNameSpace(struct_def.defined_namespace);
GenRootTableFuncs(struct_def);
}
}
if (cur_name_space_) SetNameSpace(nullptr);
const auto file_path = GeneratedFileName(path_, file_name_, parser_.opts);
const auto final_code = code_.ToString();
return SaveFile(file_path.c_str(), final_code, false);
}
private:
CodeWriter code_;
// This tracks the current namespace so we can insert namespace declarations.
const Namespace *cur_name_space_;
const Namespace *CurrentNameSpace() const { return cur_name_space_; }
// Determine if a Type needs a lifetime template parameter when used in the
// Rust builder args.
bool TableBuilderTypeNeedsLifetime(const Type &type) const {
switch (GetFullType(type)) {
case ftInteger:
case ftFloat:
case ftBool:
case ftEnumKey:
case ftUnionKey:
case ftUnionValue: {
return false;
}
default: {
return true;
}
}
}
// Determine if a table args rust type needs a lifetime template parameter.
bool TableBuilderArgsNeedsLifetime(const StructDef &struct_def) const {
FLATBUFFERS_ASSERT(!struct_def.fixed);
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
const auto &field = **it;
if (field.deprecated) { continue; }
if (TableBuilderTypeNeedsLifetime(field.value.type)) { return true; }
}
return false;
}
std::string NamespacedNativeName(const EnumDef &def) {
return WrapInNameSpace(def.defined_namespace, namer_.ObjectType(def));
}
std::string NamespacedNativeName(const StructDef &def) {
return WrapInNameSpace(def.defined_namespace, namer_.ObjectType(def));
}
std::string WrapInNameSpace(const Definition &def) const {
return WrapInNameSpace(def.defined_namespace,
namer_.EscapeKeyword(def.name));
}
std::string WrapInNameSpace(const Namespace *ns,
const std::string &name) const {
if (CurrentNameSpace() == ns) return name;
std::string prefix = GetRelativeNamespaceTraversal(CurrentNameSpace(), ns);
return prefix + name;
}
// Determine the relative namespace traversal needed to reference one
// namespace from another namespace. This is useful because it does not force
// the user to have a particular file layout. (If we output absolute
// namespace paths, that may require users to organize their Rust crates in a
// particular way.)
std::string GetRelativeNamespaceTraversal(const Namespace *src,
const Namespace *dst) const {
// calculate the path needed to reference dst from src.
// example: f(A::B::C, A::B::C) -> (none)
// example: f(A::B::C, A::B) -> super::
// example: f(A::B::C, A::B::D) -> super::D
// example: f(A::B::C, A) -> super::super::
// example: f(A::B::C, D) -> super::super::super::D
// example: f(A::B::C, D::E) -> super::super::super::D::E
// example: f(A, D::E) -> super::D::E
// does not include leaf object (typically a struct type).
std::stringstream stream;
size_t common = 0;
std::vector<std::string> s, d;
if (src) s = src->components;
if (dst) d = dst->components;
while (common < s.size() && common < d.size() && s[common] == d[common])
common++;
// If src namespace is empty, this must be an absolute path.
for (size_t i = common; i < s.size(); i++) stream << "super::";
for (size_t i = common; i < d.size(); i++)
stream << namer_.Namespace(d[i]) + "::";
return stream.str();
}
// Generate a comment from the schema.
void GenComment(const std::vector<std::string> &dc, const char *prefix = "") {
for (auto it = dc.begin(); it != dc.end(); it++) {
code_ += std::string(prefix) + "///" + *it;
}
}
// Return a Rust type from the table in idl.h.
std::string GetTypeBasic(const Type &type) const {
switch (GetFullType(type)) {
case ftInteger:
case ftFloat:
case ftBool:
case ftEnumKey:
case ftUnionKey: {
break;
}
default: {
FLATBUFFERS_ASSERT(false && "incorrect type given");
}
}
// clang-format off
static const char * const ctypename[] = {
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, \
RTYPE, ...) \
#RTYPE,
FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD)
#undef FLATBUFFERS_TD
};
// clang-format on
if (type.enum_def) { return WrapInNameSpace(*type.enum_def); }
return ctypename[type.base_type];
}
// Look up the native type for an enum. This will always be an integer like
// u8, i32, etc.
std::string GetEnumTypeForDecl(const Type &type) {
const auto ft = GetFullType(type);
if (!(ft == ftEnumKey || ft == ftUnionKey)) {
FLATBUFFERS_ASSERT(false && "precondition failed in GetEnumTypeForDecl");
}
// clang-format off
static const char *ctypename[] = {
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, PTYPE, \
RTYPE, ...) \
#RTYPE,
FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD)
#undef FLATBUFFERS_TD
};
// clang-format on
// Enums can be bools, but their Rust representation must be a u8, as used
// in the repr attribute (#[repr(bool)] is an invalid attribute).
if (type.base_type == BASE_TYPE_BOOL) return "u8";
return ctypename[type.base_type];
}
// Return a Rust type for any type (scalar, table, struct) specifically for
// using a FlatBuffer.
std::string GetTypeGet(const Type &type) const {
switch (GetFullType(type)) {
case ftInteger:
case ftFloat:
case ftBool:
case ftEnumKey:
case ftUnionKey: {
return GetTypeBasic(type);
}
case ftArrayOfBuiltin:
case ftArrayOfEnum:
case ftArrayOfStruct: {
return "[" + GetTypeGet(type.VectorType()) + "; " +
NumToString(type.fixed_length) + "]";
}
case ftTable: {
return WrapInNameSpace(type.struct_def->defined_namespace,
type.struct_def->name) +
"<'a>";
}
default: {
return WrapInNameSpace(type.struct_def->defined_namespace,
type.struct_def->name);
}
}
}
std::string GetEnumValue(const EnumDef &enum_def,
const EnumVal &enum_val) const {
return namer_.EnumVariant(enum_def, enum_val);
}
// 1 suffix since old C++ can't figure out the overload.
void ForAllEnumValues1(const EnumDef &enum_def,
std::function<void(const EnumVal &)> cb) {
for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
const auto &ev = **it;
code_.SetValue("VARIANT", namer_.Variant(ev));
code_.SetValue("VALUE", enum_def.ToString(ev));
code_.IncrementIdentLevel();
cb(ev);
code_.DecrementIdentLevel();
}
}
void ForAllEnumValues(const EnumDef &enum_def, std::function<void()> cb) {
std::function<void(const EnumVal &)> wrapped = [&](const EnumVal &unused) {
(void)unused;
cb();
};
ForAllEnumValues1(enum_def, wrapped);
}
// Generate an enum declaration,
// an enum string lookup table,
// an enum match function,
// and an enum array of values
void GenEnum(const EnumDef &enum_def) {
const bool is_private = parser_.opts.no_leak_private_annotations &&
(enum_def.attributes.Lookup("private") != nullptr);
code_.SetValue("ACCESS_TYPE", is_private ? "pub(crate)" : "pub");
code_.SetValue("ENUM_TY", namer_.Type(enum_def));
code_.SetValue("BASE_TYPE", GetEnumTypeForDecl(enum_def.underlying_type));
code_.SetValue("ENUM_NAMESPACE", namer_.Namespace(enum_def.name));
code_.SetValue("ENUM_CONSTANT", namer_.Constant(enum_def.name));
const EnumVal *minv = enum_def.MinValue();
const EnumVal *maxv = enum_def.MaxValue();
FLATBUFFERS_ASSERT(minv && maxv);
code_.SetValue("ENUM_MIN_BASE_VALUE", enum_def.ToString(*minv));
code_.SetValue("ENUM_MAX_BASE_VALUE", enum_def.ToString(*maxv));
if (IsBitFlagsEnum(enum_def)) {
// Defer to the convenient and canonical bitflags crate. We declare it in
// a module to #allow camel case constants in a smaller scope. This
// matches Flatbuffers c-modeled enums where variants are associated
// constants but in camel case.
code_ += "#[allow(non_upper_case_globals)]";
code_ += "mod bitflags_{{ENUM_NAMESPACE}} {";
code_ += " flatbuffers::bitflags::bitflags! {";
GenComment(enum_def.doc_comment, " ");
code_ += " #[derive(Default)]";
code_ += " {{ACCESS_TYPE}} struct {{ENUM_TY}}: {{BASE_TYPE}} {";
ForAllEnumValues1(enum_def, [&](const EnumVal &ev) {
this->GenComment(ev.doc_comment, " ");
code_ += " const {{VARIANT}} = {{VALUE}};";
});
code_ += " }";
code_ += " }";
code_ += "}";
code_ += "pub use self::bitflags_{{ENUM_NAMESPACE}}::{{ENUM_TY}};";
code_ += "";
code_.SetValue("INTO_BASE", "self.bits()");
} else {
// Normal, c-modelled enums.
// Deprecated associated constants;
const std::string deprecation_warning =
"#[deprecated(since = \"2.0.0\", note = \"Use associated constants"
" instead. This will no longer be generated in 2021.\")]";
code_ += deprecation_warning;
code_ +=
"pub const ENUM_MIN_{{ENUM_CONSTANT}}: {{BASE_TYPE}}"
" = {{ENUM_MIN_BASE_VALUE}};";
code_ += deprecation_warning;
code_ +=
"pub const ENUM_MAX_{{ENUM_CONSTANT}}: {{BASE_TYPE}}"
" = {{ENUM_MAX_BASE_VALUE}};";
auto num_fields = NumToString(enum_def.size());
code_ += deprecation_warning;
code_ += "#[allow(non_camel_case_types)]";
code_ += "pub const ENUM_VALUES_{{ENUM_CONSTANT}}: [{{ENUM_TY}}; " +
num_fields + "] = [";
ForAllEnumValues1(enum_def, [&](const EnumVal &ev) {
code_ += namer_.EnumVariant(enum_def, ev) + ",";
});
code_ += "];";
code_ += "";
GenComment(enum_def.doc_comment);
// Derive Default to be 0. flatc enforces this when the enum
// is put into a struct, though this isn't documented behavior, it is
// needed to derive defaults in struct objects.
code_ +=
"#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, "
"Default)]";
code_ += "#[repr(transparent)]";
code_ += "{{ACCESS_TYPE}} struct {{ENUM_TY}}(pub {{BASE_TYPE}});";
code_ += "#[allow(non_upper_case_globals)]";
code_ += "impl {{ENUM_TY}} {";
ForAllEnumValues1(enum_def, [&](const EnumVal &ev) {
this->GenComment(ev.doc_comment);
code_ += "pub const {{VARIANT}}: Self = Self({{VALUE}});";
});
code_ += "";
// Generate Associated constants
code_ += " pub const ENUM_MIN: {{BASE_TYPE}} = {{ENUM_MIN_BASE_VALUE}};";
code_ += " pub const ENUM_MAX: {{BASE_TYPE}} = {{ENUM_MAX_BASE_VALUE}};";
code_ += " pub const ENUM_VALUES: &'static [Self] = &[";
ForAllEnumValues(enum_def, [&]() { code_ += " Self::{{VARIANT}},"; });
code_ += " ];";
code_ += " /// Returns the variant's name or \"\" if unknown.";
code_ += " pub fn variant_name(self) -> Option<&'static str> {";
code_ += " match self {";
ForAllEnumValues(enum_def, [&]() {
code_ += " Self::{{VARIANT}} => Some(\"{{VARIANT}}\"),";
});
code_ += " _ => None,";
code_ += " }";
code_ += " }";
code_ += "}";
// Generate Debug. Unknown variants are printed like "<UNKNOWN 42>".
code_ += "impl core::fmt::Debug for {{ENUM_TY}} {";
code_ +=
" fn fmt(&self, f: &mut core::fmt::Formatter) ->"
" core::fmt::Result {";
code_ += " if let Some(name) = self.variant_name() {";
code_ += " f.write_str(name)";
code_ += " } else {";
code_ += " f.write_fmt(format_args!(\"<UNKNOWN {:?}>\", self.0))";
code_ += " }";
code_ += " }";
code_ += "}";
code_.SetValue("INTO_BASE", "self.0");
}
// Implement serde::Serialize
if (parser_.opts.rust_serialize) {
code_ += "impl Serialize for {{ENUM_TY}} {";
code_ +=
" fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>";
code_ += " where";
code_ += " S: Serializer,";
code_ += " {";
if (IsBitFlagsEnum(enum_def)) {
code_ += " serializer.serialize_u32(self.bits() as u32)";
} else {
code_ +=
" serializer.serialize_unit_variant(\"{{ENUM_TY}}\", self.0 "
"as "
"u32, self.variant_name().unwrap())";
}
code_ += " }";
code_ += "}";
code_ += "";
}
// Generate Follow and Push so we can serialize and stuff.
code_ += "impl<'a> flatbuffers::Follow<'a> for {{ENUM_TY}} {";
code_ += " type Inner = Self;";
code_ += " #[inline]";
code_ += " unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {";
code_ += " let b = flatbuffers::read_scalar_at::<{{BASE_TYPE}}>(buf, loc);";
if (IsBitFlagsEnum(enum_def)) {
// Safety:
// This is safe because we know bitflags is implemented with a repr transparent uint of the correct size.
// from_bits_unchecked will be replaced by an equivalent but safe from_bits_retain in bitflags 2.0
// https://github.com/bitflags/bitflags/issues/262
code_ += " // Safety:";
code_ += " // This is safe because we know bitflags is implemented with a repr transparent uint of the correct size.";
code_ += " // from_bits_unchecked will be replaced by an equivalent but safe from_bits_retain in bitflags 2.0";
code_ += " // https://github.com/bitflags/bitflags/issues/262";
code_ += " Self::from_bits_unchecked(b)";
} else {
code_ += " Self(b)";
}
code_ += " }";
code_ += "}";
code_ += "";
code_ += "impl flatbuffers::Push for {{ENUM_TY}} {";
code_ += " type Output = {{ENUM_TY}};";
code_ += " #[inline]";
code_ += " unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {";
code_ += " flatbuffers::emplace_scalar::<{{BASE_TYPE}}>(dst, {{INTO_BASE}});";
code_ += " }";
code_ += "}";
code_ += "";
code_ += "impl flatbuffers::EndianScalar for {{ENUM_TY}} {";
code_ += " type Scalar = {{BASE_TYPE}};";
code_ += " #[inline]";
code_ += " fn to_little_endian(self) -> {{BASE_TYPE}} {";
code_ += " {{INTO_BASE}}.to_le()";
code_ += " }";
code_ += " #[inline]";
code_ += " #[allow(clippy::wrong_self_convention)]";
code_ += " fn from_little_endian(v: {{BASE_TYPE}}) -> Self {";
code_ += " let b = {{BASE_TYPE}}::from_le(v);";
if (IsBitFlagsEnum(enum_def)) {
// Safety:
// This is safe because we know bitflags is implemented with a repr transparent uint of the correct size.
// from_bits_unchecked will be replaced by an equivalent but safe from_bits_retain in bitflags 2.0
// https://github.com/bitflags/bitflags/issues/262
code_ += " // Safety:";
code_ += " // This is safe because we know bitflags is implemented with a repr transparent uint of the correct size.";
code_ += " // from_bits_unchecked will be replaced by an equivalent but safe from_bits_retain in bitflags 2.0";
code_ += " // https://github.com/bitflags/bitflags/issues/262";
code_ += " unsafe { Self::from_bits_unchecked(b) }";
} else {
code_ += " Self(b)";
}
code_ += " }";
code_ += "}";
code_ += "";
// Generate verifier - deferring to the base type.
code_ += "impl<'a> flatbuffers::Verifiable for {{ENUM_TY}} {";
code_ += " #[inline]";
code_ += " fn run_verifier(";
code_ += " v: &mut flatbuffers::Verifier, pos: usize";
code_ += " ) -> Result<(), flatbuffers::InvalidFlatbuffer> {";
code_ += " use self::flatbuffers::Verifiable;";
code_ += " {{BASE_TYPE}}::run_verifier(v, pos)";
code_ += " }";
code_ += "}";
code_ += "";
// Enums are basically integers.
code_ += "impl flatbuffers::SimpleToVerifyInSlice for {{ENUM_TY}} {}";
if (enum_def.is_union) {
// Generate typesafe offset(s) for unions
code_.SetValue("UNION_TYPE", namer_.Type(enum_def));
code_ += "{{ACCESS_TYPE}} struct {{UNION_TYPE}}UnionTableOffset {}";
code_ += "";
if (parser_.opts.generate_object_based_api) { GenUnionObject(enum_def); }
}
}
// TODO(cneo): dedup Object versions from non object versions.
void ForAllUnionObjectVariantsBesidesNone(const EnumDef &enum_def,
std::function<void()> cb) {
for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) {
auto &enum_val = **it;
if (enum_val.union_type.base_type == BASE_TYPE_NONE) continue;
code_.SetValue("VARIANT_NAME", namer_.Variant(enum_val));
// For legacy reasons, enum variants are Keep case while enum native
// variants are UpperCamel case.
code_.SetValue("NATIVE_VARIANT",
namer_.LegacyRustNativeVariant(enum_val));
code_.SetValue("U_ELEMENT_NAME", namer_.Method(enum_val));
code_.SetValue("U_ELEMENT_TABLE_TYPE",
NamespacedNativeName(*enum_val.union_type.struct_def));
code_.IncrementIdentLevel();
cb();
code_.DecrementIdentLevel();
}
}
void GenUnionObject(const EnumDef &enum_def) {
code_.SetValue("ENUM_TY", namer_.Type(enum_def));
code_.SetValue("ENUM_FN", namer_.Function(enum_def));
code_.SetValue("ENUM_OTY", namer_.ObjectType(enum_def));
// Generate native union.
code_ += "#[allow(clippy::upper_case_acronyms)]"; // NONE's spelling is
// intended.
code_ += "#[non_exhaustive]";
code_ += "#[derive(Debug, Clone, PartialEq)]";
code_ += "{{ACCESS_TYPE}} enum {{ENUM_OTY}} {";
code_ += " NONE,";
ForAllUnionObjectVariantsBesidesNone(enum_def, [&] {
code_ += "{{NATIVE_VARIANT}}(Box<{{U_ELEMENT_TABLE_TYPE}}>),";
});
code_ += "}";
// Generate Default (NONE).
code_ += "impl Default for {{ENUM_OTY}} {";
code_ += " fn default() -> Self {";
code_ += " Self::NONE";
code_ += " }";
code_ += "}";
// Generate native union methods.
code_ += "impl {{ENUM_OTY}} {";
// Get flatbuffers union key.
// TODO(cneo): add docstrings?
code_ += " pub fn {{ENUM_FN}}_type(&self) -> {{ENUM_TY}} {";
code_ += " match self {";
code_ += " Self::NONE => {{ENUM_TY}}::NONE,";
ForAllUnionObjectVariantsBesidesNone(enum_def, [&] {
code_ +=
" Self::{{NATIVE_VARIANT}}(_) => {{ENUM_TY}}::"
"{{VARIANT_NAME}},";
});
code_ += " }";
code_ += " }";
// Pack flatbuffers union value
code_ +=
" pub fn pack(&self, fbb: &mut flatbuffers::FlatBufferBuilder)"
" -> Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>"
" {";
code_ += " match self {";
code_ += " Self::NONE => None,";
ForAllUnionObjectVariantsBesidesNone(enum_def, [&] {
code_ += " Self::{{NATIVE_VARIANT}}(v) => \\";
code_ += "Some(v.pack(fbb).as_union_value()),";
});
code_ += " }";
code_ += " }";
// Generate some accessors;
ForAllUnionObjectVariantsBesidesNone(enum_def, [&] {
// Move accessor.
code_ +=
"/// If the union variant matches, return the owned "
"{{U_ELEMENT_TABLE_TYPE}}, setting the union to NONE.";
code_ +=
"pub fn take_{{U_ELEMENT_NAME}}(&mut self) -> "
"Option<Box<{{U_ELEMENT_TABLE_TYPE}}>> {";
code_ += " if let Self::{{NATIVE_VARIANT}}(_) = self {";
code_ += " let v = core::mem::replace(self, Self::NONE);";
code_ += " if let Self::{{NATIVE_VARIANT}}(w) = v {";
code_ += " Some(w)";
code_ += " } else {";
code_ += " unreachable!()";
code_ += " }";
code_ += " } else {";
code_ += " None";
code_ += " }";
code_ += "}";
// Immutable reference accessor.
code_ +=
"/// If the union variant matches, return a reference to the "
"{{U_ELEMENT_TABLE_TYPE}}.";
code_ +=
"pub fn as_{{U_ELEMENT_NAME}}(&self) -> "
"Option<&{{U_ELEMENT_TABLE_TYPE}}> {";
code_ +=
" if let Self::{{NATIVE_VARIANT}}(v) = self "
"{ Some(v.as_ref()) } else { None }";
code_ += "}";
// Mutable reference accessor.
code_ +=
"/// If the union variant matches, return a mutable reference"
" to the {{U_ELEMENT_TABLE_TYPE}}.";
code_ +=
"pub fn as_{{U_ELEMENT_NAME}}_mut(&mut self) -> "
"Option<&mut {{U_ELEMENT_TABLE_TYPE}}> {";
code_ +=
" if let Self::{{NATIVE_VARIANT}}(v) = self "
"{ Some(v.as_mut()) } else { None }";
code_ += "}";
});
code_ += "}"; // End union methods impl.
}
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.
if (!IsScalar(field.value.type.base_type) || field.IsOptional()) {
return "None";
}
} else {
// This for defaults in objects.
// Unions have a NONE variant instead of using Rust's None.
if (field.IsOptional() && !IsUnion(field.value.type)) { return "None"; }
}
switch (GetFullType(field.value.type)) {
case ftInteger:
case ftFloat: {
return field.value.constant;
}
case ftBool: {
return field.value.constant == "0" ? "false" : "true";
}
case ftUnionKey:
case ftEnumKey: {
auto ev = field.value.type.enum_def->FindByValue(field.value.constant);
if (!ev) return "Default::default()"; // Bitflags enum.
return WrapInNameSpace(
field.value.type.enum_def->defined_namespace,
namer_.EnumVariant(*field.value.type.enum_def, *ev));
}
case ftUnionValue: {
return ObjectFieldType(field, true) + "::NONE";
}
case ftString: {
// 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(false);
return "INVALID_CODE_GENERATION";
}
case ftArrayOfStruct:
case ftArrayOfEnum:
case ftArrayOfBuiltin:
case ftVectorOfBool:
case ftVectorOfFloat:
case ftVectorOfInteger:
case ftVectorOfString:
case ftVectorOfStruct:
case ftVectorOfTable:
case ftVectorOfEnumKey:
case ftVectorOfUnionValue:
case ftStruct:
case ftTable: {
// 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(false);
return "INVALID_CODE_GENERATION";
}
// Create the return type for fields in the *BuilderArgs structs that are
// used to create Tables.
//
// Note: we could make all inputs to the BuilderArgs be an Option, as well
// as all outputs. But, the UX of Flatbuffers is that the user doesn't get to
// know if the value is default or not, because there are three ways to
// return a default value:
// 1) return a stored value that happens to be the default,
// 2) return a hardcoded value because the relevant vtable field is not in
// the vtable, or
// 3) return a hardcoded value because the vtable field value is set to zero.
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: {
return WrapOption(GetTypeBasic(type));
}
case ftStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapOption("&" + lifetime + " " + typname);
}
case ftTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapOption("flatbuffers::WIPOffset<" + typname + "<" + lifetime +
">>");
}
case ftString: {
return WrapOption("flatbuffers::WIPOffset<&" + lifetime + " str>");
}
case ftEnumKey:
case ftUnionKey: {
return WrapOption(WrapInNameSpace(*type.enum_def));
}
case ftUnionValue: {
return "Option<flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>>";
}
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat: {
const auto typname = GetTypeBasic(type.VectorType());
return WrapVector(typname);
}
case ftVectorOfEnumKey: {
const auto typname = WrapInNameSpace(*type.enum_def);
return WrapVector(typname);
}
case ftVectorOfStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapVector(typname);
}
case ftVectorOfTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapUOffsetsVector(typname + "<" + lifetime + ">");
}
case ftVectorOfString: {
return WrapUOffsetsVector("&" + lifetime + " str");
}
case ftVectorOfUnionValue: {
return WrapUOffsetsVector("flatbuffers::Table<" + lifetime + ">");
}
case ftArrayOfEnum:
case ftArrayOfStruct:
case ftArrayOfBuiltin: {
FLATBUFFERS_ASSERT(false && "arrays are not supported within tables");
return "ARRAYS_NOT_SUPPORTED_IN_TABLES";
}
}
return "INVALID_CODE_GENERATION"; // for return analysis
}
std::string ObjectFieldType(const FieldDef &field, bool in_a_table) {
const Type &type = field.value.type;
std::string ty;
switch (GetFullType(type)) {
case ftInteger:
case ftBool:
case ftFloat: {
ty = GetTypeBasic(type);
break;
}
case ftString: {
ty = "String";
break;
}
case ftStruct: {
ty = NamespacedNativeName(*type.struct_def);
break;
}
case ftTable: {
// Since Tables can contain themselves, Box is required to avoid
// infinite types.
ty = "Box<" + NamespacedNativeName(*type.struct_def) + ">";
break;
}
case ftUnionKey: {
// There is no native "UnionKey", natively, unions are rust enums with
// newtype-struct-variants.
return "INVALID_CODE_GENERATION";
}
case ftUnionValue: {
ty = NamespacedNativeName(*type.enum_def);
break;
}
case ftEnumKey: {
ty = WrapInNameSpace(*type.enum_def);
break;
}
// Vectors are in tables and are optional
case ftVectorOfEnumKey: {
ty = "Vec<" + WrapInNameSpace(*type.VectorType().enum_def) + ">";
break;
}
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat: {
ty = "Vec<" + GetTypeBasic(type.VectorType()) + ">";
break;
}
case ftVectorOfString: {
ty = "Vec<String>";
break;
}
case ftVectorOfTable:
case ftVectorOfStruct: {
ty = NamespacedNativeName(*type.VectorType().struct_def);
ty = "Vec<" + ty + ">";
break;
}
case ftVectorOfUnionValue: {
FLATBUFFERS_ASSERT(false && "vectors of unions are not yet supported");
return "INVALID_CODE_GENERATION"; // OH NO!
}
case ftArrayOfEnum: {
ty = "[" + WrapInNameSpace(*type.VectorType().enum_def) + "; " +
NumToString(type.fixed_length) + "]";
break;
}
case ftArrayOfStruct: {
ty = "[" + NamespacedNativeName(*type.VectorType().struct_def) + "; " +
NumToString(type.fixed_length) + "]";
break;
}
case ftArrayOfBuiltin: {
ty = "[" + GetTypeBasic(type.VectorType()) + "; " +
NumToString(type.fixed_length) + "]";
break;
}
}
if (in_a_table && !IsUnion(type) && field.IsOptional()) {
return "Option<" + ty + ">";
} else {
return ty;
}
}
std::string TableBuilderArgsAddFuncType(const FieldDef &field,
const std::string &lifetime) {
const Type &type = field.value.type;
switch (GetFullType(field.value.type)) {
case ftVectorOfStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return "flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime + ", " +
typname + ">>";
}
case ftVectorOfTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return "flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
", flatbuffers::ForwardsUOffset<" + typname + "<" + lifetime +
">>>>";
}
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat: {
const auto typname = GetTypeBasic(type.VectorType());
return "flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime + ", " +
typname + ">>";
}
case ftVectorOfString: {
return "flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
", flatbuffers::ForwardsUOffset<&" + lifetime + " str>>>";
}
case ftVectorOfEnumKey: {
const auto typname = WrapInNameSpace(*type.enum_def);
return "flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime + ", " +
typname + ">>";
}
case ftVectorOfUnionValue: {
return "flatbuffers::WIPOffset<flatbuffers::Vector<" + lifetime +
", flatbuffers::ForwardsUOffset<flatbuffers::Table<" + lifetime +
">>>";
}
case ftEnumKey:
case ftUnionKey: {
const auto typname = WrapInNameSpace(*type.enum_def);
return typname;
}
case ftStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return "&" + typname + "";
}
case ftTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return "flatbuffers::WIPOffset<" + typname + "<" + lifetime + ">>";
}
case ftInteger:
case ftBool:
case ftFloat: {
return GetTypeBasic(type);
}
case ftString: {
return "flatbuffers::WIPOffset<&" + lifetime + " str>";
}
case ftUnionValue: {
return "flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>";
}
case ftArrayOfBuiltin: {
const auto typname = GetTypeBasic(type.VectorType());
return "flatbuffers::Array<" + lifetime + ", " + typname + ", " +
NumToString(type.fixed_length) + ">";
}
case ftArrayOfEnum: {
const auto typname = WrapInNameSpace(*type.enum_def);
return "flatbuffers::Array<" + lifetime + ", " + typname + ", " +
NumToString(type.fixed_length) + ">";
}
case ftArrayOfStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return "flatbuffers::Array<" + lifetime + ", " + typname + ", " +
NumToString(type.fixed_length) + ">";
}
}
return "INVALID_CODE_GENERATION"; // for return analysis
}
std::string TableBuilderArgsAddFuncBody(const FieldDef &field) {
const Type &type = field.value.type;
switch (GetFullType(field.value.type)) {
case ftInteger:
case ftBool:
case ftFloat: {
const auto typname = GetTypeBasic(field.value.type);
return (field.IsOptional() ? "self.fbb_.push_slot_always::<"
: "self.fbb_.push_slot::<") +
typname + ">";
}
case ftEnumKey:
case ftUnionKey: {
const auto underlying_typname = GetTypeBasic(type);
return (field.IsOptional() ? "self.fbb_.push_slot_always::<"
: "self.fbb_.push_slot::<") +
underlying_typname + ">";
}
case ftStruct: {
const std::string typname = WrapInNameSpace(*type.struct_def);
return "self.fbb_.push_slot_always::<&" + typname + ">";
}
case ftTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return "self.fbb_.push_slot_always::<flatbuffers::WIPOffset<" +
typname + ">>";
}
case ftUnionValue:
case ftString:
case ftVectorOfInteger:
case ftVectorOfFloat:
case ftVectorOfBool:
case ftVectorOfEnumKey:
case ftVectorOfStruct:
case ftVectorOfTable:
case ftVectorOfString:
case ftVectorOfUnionValue: {
return "self.fbb_.push_slot_always::<flatbuffers::WIPOffset<_>>";
}
case ftArrayOfEnum:
case ftArrayOfStruct:
case ftArrayOfBuiltin: {
FLATBUFFERS_ASSERT(false && "arrays are not supported within tables");
return "ARRAYS_NOT_SUPPORTED_IN_TABLES";
}
}
return "INVALID_CODE_GENERATION"; // for return analysis
}
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: {
return WrapOption(GetTypeBasic(type));
}
case ftStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapOption("&" + lifetime + " " + typname);
}
case ftTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapOption(typname + "<" + lifetime + ">");
}
case ftEnumKey:
case ftUnionKey: {
return WrapOption(WrapInNameSpace(*type.enum_def));
}
case ftUnionValue: {
return WrapOption("flatbuffers::Table<" + lifetime + ">");
}
case ftString: {
return WrapOption("&" + lifetime + " str");
}
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat: {
const auto typname = GetTypeBasic(type.VectorType());
return WrapOption("flatbuffers::Vector<" + lifetime + ", " + typname + ">");
}
case ftVectorOfEnumKey: {
const auto typname = WrapInNameSpace(*type.enum_def);
return WrapOption("flatbuffers::Vector<" + lifetime + ", " + typname +
">");
}
case ftVectorOfStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapOption("flatbuffers::Vector<" + lifetime + ", " + typname + ">");
}
case ftVectorOfTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapOption("flatbuffers::Vector<" + lifetime +
", flatbuffers::ForwardsUOffset<" + typname + "<" +
lifetime + ">>>");
}
case ftVectorOfString: {
return WrapOption("flatbuffers::Vector<" + lifetime +
", flatbuffers::ForwardsUOffset<&" + lifetime +
" str>>");
}
case ftVectorOfUnionValue: {
FLATBUFFERS_ASSERT(false && "vectors of unions are not yet supported");
// TODO(rw): when we do support these, we should consider using the
// Into trait to convert tables to typesafe union values.
return "INVALID_CODE_GENERATION"; // for return analysis
}
case ftArrayOfEnum:
case ftArrayOfStruct:
case ftArrayOfBuiltin: {
FLATBUFFERS_ASSERT(false && "arrays are not supported within tables");
return "ARRAYS_NOT_SUPPORTED_IN_TABLES";
}
}
return "INVALID_CODE_GENERATION"; // for return analysis
}
std::string FollowType(const Type &type, const std::string &lifetime) {
// IsVector... This can be made iterative?
const auto WrapForwardsUOffset = [](std::string ty) -> std::string {
return "flatbuffers::ForwardsUOffset<" + ty + ">";
};
const auto WrapVector = [&](std::string ty) -> std::string {
return "flatbuffers::Vector<" + lifetime + ", " + ty + ">";
};
const auto WrapArray = [&](std::string ty, uint16_t length) -> std::string {
return "flatbuffers::Array<" + lifetime + ", " + ty + ", " +
NumToString(length) + ">";
};
switch (GetFullType(type)) {
case ftInteger:
case ftFloat:
case ftBool: {
return GetTypeBasic(type);
}
case ftStruct: {
return WrapInNameSpace(*type.struct_def);
}
case ftUnionKey:
case ftEnumKey: {
return WrapInNameSpace(*type.enum_def);
}
case ftTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapForwardsUOffset(typname);
}
case ftUnionValue: {
return WrapForwardsUOffset("flatbuffers::Table<" + lifetime + ">");
}
case ftString: {
return WrapForwardsUOffset("&str");
}
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat: {
const auto typname = GetTypeBasic(type.VectorType());
return WrapForwardsUOffset(WrapVector(typname));
}
case ftVectorOfEnumKey: {
const auto typname = WrapInNameSpace(*type.VectorType().enum_def);
return WrapForwardsUOffset(WrapVector(typname));
}
case ftVectorOfStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapForwardsUOffset(WrapVector(typname));
}
case ftVectorOfTable: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapForwardsUOffset(WrapVector(WrapForwardsUOffset(typname)));
}
case ftVectorOfString: {
return WrapForwardsUOffset(
WrapVector(WrapForwardsUOffset("&" + lifetime + " str")));
}
case ftVectorOfUnionValue: {
FLATBUFFERS_ASSERT(false && "vectors of unions are not yet supported");
return "INVALID_CODE_GENERATION"; // for return analysis
}
case ftArrayOfEnum: {
const auto typname = WrapInNameSpace(*type.VectorType().enum_def);
return WrapArray(typname, type.fixed_length);
}
case ftArrayOfStruct: {
const auto typname = WrapInNameSpace(*type.struct_def);
return WrapArray(typname, type.fixed_length);
}
case ftArrayOfBuiltin: {
const auto typname = GetTypeBasic(type.VectorType());
return WrapArray(typname, type.fixed_length);
}
}
return "INVALID_CODE_GENERATION"; // for return analysis
}
std::string GenTableAccessorFuncBody(const FieldDef &field,
const std::string &lifetime) {
const std::string vt_offset = namer_.LegacyRustFieldOffsetName(field);
const std::string typname = FollowType(field.value.type, lifetime);
// Default-y fields (scalars so far) are neither optional nor required.
const std::string default_value =
!(field.IsOptional() || field.IsRequired())
? "Some(" + GetDefaultValue(field, kAccessor) + ")"
: "None";
const std::string unwrap = field.IsOptional() ? "" : ".unwrap()";
return "unsafe { self._tab.get::<" + typname + ">({{STRUCT_TY}}::" + vt_offset +
", " + default_value + ")" + unwrap + "}";
}
// Generates a fully-qualified name getter for use with --gen-name-strings
void GenFullyQualifiedNameGetter(const StructDef &struct_def,
const std::string &name) {
const std::string fully_qualified_name =
struct_def.defined_namespace->GetFullyQualifiedName(name);
code_ += " pub const fn get_fully_qualified_name() -> &'static str {";
code_ += " \"" + fully_qualified_name + "\"";
code_ += " }";
code_ += "";
}
void ForAllUnionVariantsBesidesNone(
const EnumDef &def, std::function<void(const EnumVal &ev)> cb) {
FLATBUFFERS_ASSERT(def.is_union);
for (auto it = def.Vals().begin(); it != def.Vals().end(); ++it) {
const EnumVal &ev = **it;
// TODO(cneo): Can variants be deprecated, should we skip them?
if (ev.union_type.base_type == BASE_TYPE_NONE) { continue; }
code_.SetValue(
"U_ELEMENT_ENUM_TYPE",
WrapInNameSpace(def.defined_namespace, namer_.EnumVariant(def, ev)));
code_.SetValue(
"U_ELEMENT_TABLE_TYPE",
WrapInNameSpace(ev.union_type.struct_def->defined_namespace,
ev.union_type.struct_def->name));
code_.SetValue("U_ELEMENT_NAME", namer_.Function(ev.name));
cb(ev);
}
}
void ForAllTableFields(const StructDef &struct_def,
std::function<void(const FieldDef &)> cb,
bool reversed = false) {
// TODO(cneo): Remove `reversed` overload. It's only here to minimize the
// diff when refactoring to the `ForAllX` helper functions.
auto go = [&](const FieldDef &field) {
if (field.deprecated) return;
code_.SetValue("OFFSET_NAME", namer_.LegacyRustFieldOffsetName(field));
code_.SetValue("OFFSET_VALUE", NumToString(field.value.offset));
code_.SetValue("FIELD", namer_.Field(field));
code_.SetValue("BLDR_DEF_VAL", GetDefaultValue(field, kBuilder));
code_.SetValue("DISCRIMINANT", namer_.Field(field) + "_type");
code_.IncrementIdentLevel();
cb(field);
code_.DecrementIdentLevel();
};
const auto &fields = struct_def.fields.vec;
if (reversed) {
for (auto it = fields.rbegin(); it != fields.rend(); ++it) go(**it);
} else {
for (auto it = fields.begin(); it != fields.end(); ++it) go(**it);
}
}
// Generate an accessor struct, builder struct, and create function for a
// table.
void GenTable(const StructDef &struct_def) {
const bool is_private = parser_.opts.no_leak_private_annotations &&
(struct_def.attributes.Lookup("private") != nullptr);
code_.SetValue("ACCESS_TYPE", is_private ? "pub(crate)" : "pub");
code_.SetValue("STRUCT_TY", namer_.Type(struct_def));
code_.SetValue("STRUCT_FN", namer_.Function(struct_def));
// Generate an offset type, the base type, the Follow impl, and the
// init_from_table impl.
code_ += "{{ACCESS_TYPE}} enum {{STRUCT_TY}}Offset {}";
code_ += "#[derive(Copy, Clone, PartialEq)]";
code_ += "";
GenComment(struct_def.doc_comment);
code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}<'a> {";
code_ += " pub _tab: flatbuffers::Table<'a>,";
code_ += "}";
code_ += "";
code_ += "impl<'a> flatbuffers::Follow<'a> for {{STRUCT_TY}}<'a> {";
code_ += " type Inner = {{STRUCT_TY}}<'a>;";
code_ += " #[inline]";
code_ += " unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {";
code_ += " Self { _tab: flatbuffers::Table::new(buf, loc) }";
code_ += " }";
code_ += "}";
code_ += "";
code_ += "impl<'a> {{STRUCT_TY}}<'a> {";
// Generate field id constants.
ForAllTableFields(struct_def, [&](const FieldDef &unused) {
(void)unused;
code_ +=
"pub const {{OFFSET_NAME}}: flatbuffers::VOffsetT = "
"{{OFFSET_VALUE}};";
});
code_ += "";
if (parser_.opts.generate_name_strings) {
GenFullyQualifiedNameGetter(struct_def, struct_def.name);
}
code_ += " #[inline]";
code_ +=
" pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> "
"Self {";
code_ += " {{STRUCT_TY}} { _tab: table }";
code_ += " }";
// Generate a convenient create* function that uses the above builder
// to create a table in one function call.
code_.SetValue("MAYBE_US", struct_def.fields.vec.size() == 0 ? "_" : "");
code_.SetValue("MAYBE_LT",
TableBuilderArgsNeedsLifetime(struct_def) ? "<'args>" : "");
code_ += " #[allow(unused_mut)]";
code_ += " pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>(";
code_ += " _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>,";
code_ += " {{MAYBE_US}}args: &'args {{STRUCT_TY}}Args{{MAYBE_LT}}";
code_ += " ) -> flatbuffers::WIPOffset<{{STRUCT_TY}}<'bldr>> {";
code_ += " let mut builder = {{STRUCT_TY}}Builder::new(_fbb);";
for (size_t size = struct_def.sortbysize ? sizeof(largest_scalar_t) : 1;
size; size /= 2) {
ForAllTableFields(
struct_def,
[&](const FieldDef &field) {
if (struct_def.sortbysize &&
size != SizeOf(field.value.type.base_type))
return;
if (IsOptionalToBuilder(field)) {
code_ +=
" if let Some(x) = args.{{FIELD}} "
"{ builder.add_{{FIELD}}(x); }";
} else {
code_ += " builder.add_{{FIELD}}(args.{{FIELD}});";
}
},
/*reverse=*/true);
}
code_ += " builder.finish()";
code_ += " }";
code_ += "";
// Generate Object API Packer function.
if (parser_.opts.generate_object_based_api) {
// TODO(cneo): Replace more for loops with ForAllX stuff.
// TODO(cneo): Manage indentation with IncrementIdentLevel?
code_.SetValue("STRUCT_OTY", namer_.ObjectType(struct_def));
code_ += " pub fn unpack(&self) -> {{STRUCT_OTY}} {";
ForAllObjectTableFields(struct_def, [&](const FieldDef &field) {
const Type &type = field.value.type;
switch (GetFullType(type)) {
case ftInteger:
case ftBool:
case ftFloat:
case ftEnumKey: {
code_ += " let {{FIELD}} = self.{{FIELD}}();";
return;
}
case ftUnionKey: return;
case ftUnionValue: {
const auto &enum_def = *type.enum_def;
code_.SetValue("ENUM_TY", WrapInNameSpace(enum_def));
code_.SetValue("NATIVE_ENUM_NAME", NamespacedNativeName(enum_def));
code_ += " let {{FIELD}} = match self.{{FIELD}}_type() {";
code_ += " {{ENUM_TY}}::NONE => {{NATIVE_ENUM_NAME}}::NONE,";
ForAllUnionObjectVariantsBesidesNone(enum_def, [&] {
code_ +=
" {{ENUM_TY}}::{{VARIANT_NAME}} => "
"{{NATIVE_ENUM_NAME}}::{{NATIVE_VARIANT}}(Box::new(";
code_ += " self.{{FIELD}}_as_{{U_ELEMENT_NAME}}()";
code_ +=
" .expect(\"Invalid union table, "
"expected `{{ENUM_TY}}::{{VARIANT_NAME}}`.\")";
code_ += " .unpack()";
code_ += " )),";
});
// Maybe we shouldn't throw away unknown discriminants?
code_ += " _ => {{NATIVE_ENUM_NAME}}::NONE,";
code_ += " };";
return;
}
// The rest of the types need special handling based on if the field
// is optional or not.
case ftString: {
code_.SetValue("EXPR", "x.to_string()");
break;
}
case ftStruct: {
code_.SetValue("EXPR", "x.unpack()");
break;
}
case ftTable: {
code_.SetValue("EXPR", "Box::new(x.unpack())");
break;
}
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat:
case ftVectorOfEnumKey: {
code_.SetValue("EXPR", "x.into_iter().collect()");
break;
}
case ftVectorOfString: {
code_.SetValue("EXPR", "x.iter().map(|s| s.to_string()).collect()");
break;
}
case ftVectorOfStruct:
case ftVectorOfTable: {
code_.SetValue("EXPR", "x.iter().map(|t| t.unpack()).collect()");
break;
}
case ftVectorOfUnionValue: {
FLATBUFFERS_ASSERT(false && "vectors of unions not yet supported");
return;
}
case ftArrayOfEnum:
case ftArrayOfStruct:
case ftArrayOfBuiltin: {
FLATBUFFERS_ASSERT(false &&
"arrays are not supported within tables");
return;
}
}
if (field.IsOptional()) {
code_ += " let {{FIELD}} = self.{{FIELD}}().map(|x| {";
code_ += " {{EXPR}}";
code_ += " });";
} else {
code_ += " let {{FIELD}} = {";
code_ += " let x = self.{{FIELD}}();";
code_ += " {{EXPR}}";
code_ += " };";
}
});
code_ += " {{STRUCT_OTY}} {";
ForAllObjectTableFields(struct_def, [&](const FieldDef &field) {
if (field.value.type.base_type == BASE_TYPE_UTYPE) return;
code_ += " {{FIELD}},";
});
code_ += " }";
code_ += " }";
}
if (struct_def.fields.vec.size() > 0) code_ += "";
// Generate the accessors. Each has one of two forms:
//
// If a value can be None:
// pub fn name(&'a self) -> Option<user_facing_type> {
// self._tab.get::<internal_type>(offset, defaultval)
// }
//
// If a value is always Some:
// pub fn name(&'a self) -> user_facing_type {
// self._tab.get::<internal_type>(offset, defaultval).unwrap()
// }
ForAllTableFields(struct_def, [&](const FieldDef &field) {
code_.SetValue("RETURN_TYPE",
GenTableAccessorFuncReturnType(field, "'a"));
this->GenComment(field.doc_comment);
code_ += "#[inline]";
code_ += "pub fn {{FIELD}}(&self) -> {{RETURN_TYPE}} {";
code_ += " // Safety:";
code_ += " // Created from valid Table for this object";
code_ += " // which contains a valid value in this slot";
code_ += " " + GenTableAccessorFuncBody(field, "'a");
code_ += "}";
// Generate a comparison function for this field if it is a key.
if (field.key) { GenKeyFieldMethods(field); }
// Generate a nested flatbuffer field, if applicable.
auto nested = field.attributes.Lookup("nested_flatbuffer");
if (nested) {
std::string qualified_name = nested->constant;
auto nested_root = parser_.LookupStruct(nested->constant);
if (nested_root == nullptr) {
qualified_name = parser_.current_namespace_->GetFullyQualifiedName(
nested->constant);
nested_root = parser_.LookupStruct(qualified_name);
}
FLATBUFFERS_ASSERT(nested_root); // Guaranteed to exist by parser.
code_.SetValue("NESTED", WrapInNameSpace(*nested_root));
code_ += "pub fn {{FIELD}}_nested_flatbuffer(&'a self) -> \\";
if (field.IsRequired()) {
code_ += "{{NESTED}}<'a> {";
code_ += " let data = self.{{FIELD}}();";
code_ += " use flatbuffers::Follow;";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid flatbuffer in this slot";
code_ +=
" unsafe { <flatbuffers::ForwardsUOffset<{{NESTED}}<'a>>>"
"::follow(data.bytes(), 0) }";
} else {
code_ += "Option<{{NESTED}}<'a>> {";
code_ += " self.{{FIELD}}().map(|data| {";
code_ += " use flatbuffers::Follow;";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid flatbuffer in this slot";
code_ +=
" unsafe { <flatbuffers::ForwardsUOffset<{{NESTED}}<'a>>>"
"::follow(data.bytes(), 0) }";
code_ += " })";
}
code_ += "}";
}
});
// Explicit specializations for union accessors
ForAllTableFields(struct_def, [&](const FieldDef &field) {
if (field.value.type.base_type != BASE_TYPE_UNION) return;
ForAllUnionVariantsBesidesNone(
*field.value.type.enum_def, [&](const EnumVal &unused) {
(void)unused;
code_ += "#[inline]";
code_ += "#[allow(non_snake_case)]";
code_ +=
"pub fn {{FIELD}}_as_{{U_ELEMENT_NAME}}(&self) -> "
"Option<{{U_ELEMENT_TABLE_TYPE}}<'a>> {";
// If the user defined schemas name a field that clashes with a
// language reserved word, flatc will try to escape the field name
// by appending an underscore. This works well for most cases,
// except one. When generating union accessors (and referring to
// them internally within the code generated here), an extra
// underscore will be appended to the name, causing build failures.
//
// This only happens when unions have members that overlap with
// language reserved words.
//
// To avoid this problem the type field name is used unescaped here:
code_ +=
" if self.{{DISCRIMINANT}}() == {{U_ELEMENT_ENUM_TYPE}} {";
// The following logic is not tested in the integration test,
// as of April 10, 2020
if (field.IsRequired()) {
code_ += " let u = self.{{FIELD}}();";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid union in this slot";
code_ += " Some(unsafe { {{U_ELEMENT_TABLE_TYPE}}::init_from_table(u) })";
} else {
code_ +=" self.{{FIELD}}().map(|t| {";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid union in this slot";
code_ += " unsafe { {{U_ELEMENT_TABLE_TYPE}}::init_from_table(t) }";
code_ += " })";
}
code_ += " } else {";
code_ += " None";
code_ += " }";
code_ += "}";
code_ += "";
});
});
code_ += "}"; // End of table impl.
code_ += "";
// Generate Verifier;
code_ += "impl flatbuffers::Verifiable for {{STRUCT_TY}}<'_> {";
code_ += " #[inline]";
code_ += " fn run_verifier(";
code_ += " v: &mut flatbuffers::Verifier, pos: usize";
code_ += " ) -> Result<(), flatbuffers::InvalidFlatbuffer> {";
code_ += " use self::flatbuffers::Verifiable;";
code_ += " v.visit_table(pos)?\\";
// Escape newline and insert it onthe next line so we can end the builder
// with a nice semicolon.
ForAllTableFields(struct_def, [&](const FieldDef &field) {
if (GetFullType(field.value.type) == ftUnionKey) return;
code_.SetValue("IS_REQ", field.IsRequired() ? "true" : "false");
if (GetFullType(field.value.type) != ftUnionValue) {
// All types besides unions.
code_.SetValue("TY", FollowType(field.value.type, "'_"));
code_ +=
"\n .visit_field::<{{TY}}>(\"{{FIELD}}\", "
"Self::{{OFFSET_NAME}}, {{IS_REQ}})?\\";
return;
}
// Unions.
const EnumDef &union_def = *field.value.type.enum_def;
code_.SetValue("UNION_TYPE", WrapInNameSpace(union_def));
code_.SetValue("UNION_TYPE_OFFSET_NAME",
namer_.LegacyRustFieldOffsetName(field) + "_TYPE");
code_ +=
"\n .visit_union::<{{UNION_TYPE}}, _>("
"\"{{FIELD}}_type\", Self::{{UNION_TYPE_OFFSET_NAME}}, "
"\"{{FIELD}}\", Self::{{OFFSET_NAME}}, {{IS_REQ}}, "
"|key, v, pos| {";
code_ += " match key {";
ForAllUnionVariantsBesidesNone(union_def, [&](const EnumVal &unused) {
(void)unused;
code_ +=
" {{U_ELEMENT_ENUM_TYPE}} => v.verify_union_variant::"
"<flatbuffers::ForwardsUOffset<{{U_ELEMENT_TABLE_TYPE}}>>("
"\"{{U_ELEMENT_ENUM_TYPE}}\", pos),";
});
code_ += " _ => Ok(()),";
code_ += " }";
code_ += " })?\\";
});
code_ += "\n .finish();";
code_ += " Ok(())";
code_ += " }";
code_ += "}";
// Generate an args struct:
code_.SetValue("MAYBE_LT",
TableBuilderArgsNeedsLifetime(struct_def) ? "<'a>" : "");
code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}Args{{MAYBE_LT}} {";
ForAllTableFields(struct_def, [&](const FieldDef &field) {
code_.SetValue("PARAM_TYPE", TableBuilderArgsDefnType(field, "'a"));
code_ += " pub {{FIELD}}: {{PARAM_TYPE}},";
});
code_ += "}";
// Generate an impl of Default for the *Args type:
code_ += "impl<'a> Default for {{STRUCT_TY}}Args{{MAYBE_LT}} {";
code_ += " #[inline]";
code_ += " fn default() -> Self {";
code_ += " {{STRUCT_TY}}Args {";
ForAllTableFields(struct_def, [&](const FieldDef &field) {
code_ += " {{FIELD}}: {{BLDR_DEF_VAL}},\\";
code_ += field.IsRequired() ? " // required field" : "";
});
code_ += " }";
code_ += " }";
code_ += "}";
code_ += "";
// Implement serde::Serialize
if (parser_.opts.rust_serialize) {
const auto numFields = struct_def.fields.vec.size();
code_.SetValue("NUM_FIELDS", NumToString(numFields));
code_ += "impl Serialize for {{STRUCT_TY}}<'_> {";
code_ +=
" fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>";
code_ += " where";
code_ += " S: Serializer,";
code_ += " {";
if (numFields == 0) {
code_ +=
" let s = serializer.serialize_struct(\"{{STRUCT_TY}}\", 0)?;";
} else {
code_ +=
" let mut s = serializer.serialize_struct(\"{{STRUCT_TY}}\", "
"{{NUM_FIELDS}})?;";
}
ForAllTableFields(struct_def, [&](const FieldDef &field) {
const Type &type = field.value.type;
if (IsUnion(type)) {
if (type.base_type == BASE_TYPE_UNION) {
const auto &enum_def = *type.enum_def;
code_.SetValue("ENUM_TY", WrapInNameSpace(enum_def));
code_.SetValue("FIELD", namer_.Field(field));
code_ += " match self.{{FIELD}}_type() {";
code_ += " {{ENUM_TY}}::NONE => (),";
ForAllUnionObjectVariantsBesidesNone(enum_def, [&] {
code_.SetValue("FIELD", namer_.Field(field));
code_ += " {{ENUM_TY}}::{{VARIANT_NAME}} => {";
code_ +=
" let f = "
"self.{{FIELD}}_as_{{U_ELEMENT_NAME}}()";
code_ +=
" .expect(\"Invalid union table, expected "
"`{{ENUM_TY}}::{{VARIANT_NAME}}`.\");";
code_ += " s.serialize_field(\"{{FIELD}}\", &f)?;";
code_ += " }";
});
code_ += " _ => unimplemented!(),";
code_ += " }";
} else {
code_ +=
" s.serialize_field(\"{{FIELD}}\", "
"&self.{{FIELD}}())?;";
}
} else {
if (field.IsOptional()) {
code_ += " if let Some(f) = self.{{FIELD}}() {";
code_ += " s.serialize_field(\"{{FIELD}}\", &f)?;";
code_ += " } else {";
code_ += " s.skip_field(\"{{FIELD}}\")?;";
code_ += " }";
} else {
code_ +=
" s.serialize_field(\"{{FIELD}}\", "
"&self.{{FIELD}}())?;";
}
}
});
code_ += " s.end()";
code_ += " }";
code_ += "}";
code_ += "";
}
// Generate a builder struct:
code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}Builder<'a: 'b, 'b> {";
code_ += " fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,";
code_ +=
" start_: flatbuffers::WIPOffset<"
"flatbuffers::TableUnfinishedWIPOffset>,";
code_ += "}";
// Generate builder functions:
code_ += "impl<'a: 'b, 'b> {{STRUCT_TY}}Builder<'a, 'b> {";
ForAllTableFields(struct_def, [&](const FieldDef &field) {
const bool is_scalar = IsScalar(field.value.type.base_type);
std::string offset = namer_.LegacyRustFieldOffsetName(field);
// Generate functions to add data, which take one of two forms.
//
// If a value has a default:
// fn add_x(x_: type) {
// fbb_.push_slot::<type>(offset, x_, Some(default));
// }
//
// If a value does not have a default:
// fn add_x(x_: type) {
// fbb_.push_slot_always::<type>(offset, x_);
// }
code_.SetValue("FIELD_OFFSET", namer_.Type(struct_def) + "::" + offset);
code_.SetValue("FIELD_TYPE", TableBuilderArgsAddFuncType(field, "'b "));
code_.SetValue("FUNC_BODY", TableBuilderArgsAddFuncBody(field));
code_ += "#[inline]";
code_ +=
"pub fn add_{{FIELD}}(&mut self, {{FIELD}}: "
"{{FIELD_TYPE}}) {";
if (is_scalar && !field.IsOptional()) {
code_ +=
" {{FUNC_BODY}}({{FIELD_OFFSET}}, {{FIELD}}, "
"{{BLDR_DEF_VAL}});";
} else {
code_ += " {{FUNC_BODY}}({{FIELD_OFFSET}}, {{FIELD}});";
}
code_ += "}";
});
// Struct initializer (all fields required);
code_ += " #[inline]";
code_ +=
" pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> "
"{{STRUCT_TY}}Builder<'a, 'b> {";
code_.SetValue("NUM_FIELDS", NumToString(struct_def.fields.vec.size()));
code_ += " let start = _fbb.start_table();";
code_ += " {{STRUCT_TY}}Builder {";
code_ += " fbb_: _fbb,";
code_ += " start_: start,";
code_ += " }";
code_ += " }";
// finish() function.
code_ += " #[inline]";
code_ +=
" pub fn finish(self) -> "
"flatbuffers::WIPOffset<{{STRUCT_TY}}<'a>> {";
code_ += " let o = self.fbb_.end_table(self.start_);";
ForAllTableFields(struct_def, [&](const FieldDef &field) {
if (!field.IsRequired()) return;
code_ +=
" self.fbb_.required(o, {{STRUCT_TY}}::{{OFFSET_NAME}},"
"\"{{FIELD}}\");";
});
code_ += " flatbuffers::WIPOffset::new(o.value())";
code_ += " }";
code_ += "}";
code_ += "";
code_ += "impl core::fmt::Debug for {{STRUCT_TY}}<'_> {";
code_ +=
" fn fmt(&self, f: &mut core::fmt::Formatter<'_>"
") -> core::fmt::Result {";
code_ += " let mut ds = f.debug_struct(\"{{STRUCT_TY}}\");";
ForAllTableFields(struct_def, [&](const FieldDef &field) {
if (GetFullType(field.value.type) == ftUnionValue) {
// Generate a match statement to handle unions properly.
code_.SetValue("KEY_TYPE", GenTableAccessorFuncReturnType(field, ""));
code_.SetValue("UNION_ERR",
"&\"InvalidFlatbuffer: Union discriminant"
" does not match value.\"");
code_ += " match self.{{DISCRIMINANT}}() {";
ForAllUnionVariantsBesidesNone(
*field.value.type.enum_def, [&](const EnumVal &unused) {
(void)unused;
code_ += " {{U_ELEMENT_ENUM_TYPE}} => {";
code_ +=
" if let Some(x) = "
"self.{{FIELD}}_as_"
"{{U_ELEMENT_NAME}}() {";
code_ += " ds.field(\"{{FIELD}}\", &x)";
code_ += " } else {";
code_ += " ds.field(\"{{FIELD}}\", {{UNION_ERR}})";
code_ += " }";
code_ += " },";
});
code_ += " _ => {";
code_ += " let x: Option<()> = None;";
code_ += " ds.field(\"{{FIELD}}\", &x)";
code_ += " },";
code_ += " };";
} else {
// Most fields.
code_ += " ds.field(\"{{FIELD}}\", &self.{{FIELD}}());";
}
});
code_ += " ds.finish()";
code_ += " }";
code_ += "}";
}
void GenTableObject(const StructDef &table) {
code_.SetValue("STRUCT_OTY", namer_.ObjectType(table));
code_.SetValue("STRUCT_TY", namer_.Type(table));
// Generate the native object.
code_ += "#[non_exhaustive]";
code_ += "#[derive(Debug, Clone, PartialEq)]";
code_ += "{{ACCESS_TYPE}} struct {{STRUCT_OTY}} {";
ForAllObjectTableFields(table, [&](const FieldDef &field) {
// Union objects combine both the union discriminant and value, so we
// skip making a field for the discriminant.
if (field.value.type.base_type == BASE_TYPE_UTYPE) return;
code_ += "pub {{FIELD}}: {{FIELD_OTY}},";
});
code_ += "}";
code_ += "impl Default for {{STRUCT_OTY}} {";
code_ += " fn default() -> Self {";
code_ += " Self {";
ForAllObjectTableFields(table, [&](const FieldDef &field) {
if (field.value.type.base_type == BASE_TYPE_UTYPE) return;
std::string default_value = GetDefaultValue(field, kObject);
code_ += " {{FIELD}}: " + default_value + ",";
});
code_ += " }";
code_ += " }";
code_ += "}";
// TODO(cneo): Generate defaults for Native tables. However, since structs
// may be required, they, and therefore enums need defaults.
// Generate pack function.
code_ += "impl {{STRUCT_OTY}} {";
code_ += " pub fn pack<'b>(";
code_ += " &self,";
code_ += " _fbb: &mut flatbuffers::FlatBufferBuilder<'b>";
code_ += " ) -> flatbuffers::WIPOffset<{{STRUCT_TY}}<'b>> {";
// First we generate variables for each field and then later assemble them
// using "StructArgs" to more easily manage ownership of the builder.
ForAllObjectTableFields(table, [&](const FieldDef &field) {
const Type &type = field.value.type;
switch (GetFullType(type)) {
case ftInteger:
case ftBool:
case ftFloat:
case ftEnumKey: {
code_ += " let {{FIELD}} = self.{{FIELD}};";
return;
}
case ftUnionKey: return; // Generate union type with union value.
case ftUnionValue: {
code_.SetValue("ENUM_METHOD",
namer_.Method(*field.value.type.enum_def));
code_ +=
" let {{FIELD}}_type = "
"self.{{FIELD}}.{{ENUM_METHOD}}_type();";
code_ += " let {{FIELD}} = self.{{FIELD}}.pack(_fbb);";
return;
}
// The rest of the types require special casing around optionalness
// due to "required" annotation.
case ftString: {
MapNativeTableField(field, "_fbb.create_string(x)");
return;
}
case ftStruct: {
// Hold the struct in a variable so we can reference it.
if (field.IsRequired()) {
code_ += " let {{FIELD}}_tmp = Some(self.{{FIELD}}.pack());";
} else {
code_ +=
" let {{FIELD}}_tmp = self.{{FIELD}}"
".as_ref().map(|x| x.pack());";
}
code_ += " let {{FIELD}} = {{FIELD}}_tmp.as_ref();";
return;
}
case ftTable: {
MapNativeTableField(field, "x.pack(_fbb)");
return;
}
case ftVectorOfEnumKey:
case ftVectorOfInteger:
case ftVectorOfBool:
case ftVectorOfFloat: {
MapNativeTableField(field, "_fbb.create_vector(x)");
return;
}
case ftVectorOfStruct: {
MapNativeTableField(
field,
"let w: Vec<_> = x.iter().map(|t| t.pack()).collect();"
"_fbb.create_vector(&w)");
return;
}
case ftVectorOfString: {
// TODO(cneo): create_vector* should be more generic to avoid
// allocations.
MapNativeTableField(
field,
"let w: Vec<_> = x.iter().map(|s| _fbb.create_string(s)).collect();"
"_fbb.create_vector(&w)");
return;
}
case ftVectorOfTable: {
MapNativeTableField(
field,
"let w: Vec<_> = x.iter().map(|t| t.pack(_fbb)).collect();"
"_fbb.create_vector(&w)");
return;
}
case ftVectorOfUnionValue: {
FLATBUFFERS_ASSERT(false && "vectors of unions not yet supported");
return;
}
case ftArrayOfEnum:
case ftArrayOfStruct:
case ftArrayOfBuiltin: {
FLATBUFFERS_ASSERT(false && "arrays are not supported within tables");
return;
}
}
});
code_ += " {{STRUCT_TY}}::create(_fbb, &{{STRUCT_TY}}Args{";
ForAllObjectTableFields(table, [&](const FieldDef &field) {
(void)field; // Unused.
code_ += " {{FIELD}},";
});
code_ += " })";
code_ += " }";
code_ += "}";
}
void ForAllObjectTableFields(const StructDef &table,
std::function<void(const FieldDef &)> cb) {
const std::vector<FieldDef *> &v = table.fields.vec;
for (auto it = v.begin(); it != v.end(); it++) {
const FieldDef &field = **it;
if (field.deprecated) continue;
code_.SetValue("FIELD", namer_.Field(field));
code_.SetValue("FIELD_OTY", ObjectFieldType(field, true));
code_.IncrementIdentLevel();
cb(field);
code_.DecrementIdentLevel();
}
}
void MapNativeTableField(const FieldDef &field, const std::string &expr) {
if (field.IsOptional()) {
code_ += " let {{FIELD}} = self.{{FIELD}}.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}} = Some({";
code_ += " let x = &self.{{FIELD}};";
code_ += " " + expr;
code_ += " });";
}
}
// Generate functions to compare tables and structs by key. This function
// must only be called if the field key is defined.
void GenKeyFieldMethods(const FieldDef &field) {
FLATBUFFERS_ASSERT(field.key);
code_.SetValue("KEY_TYPE", GenTableAccessorFuncReturnType(field, ""));
code_.SetValue("REF", IsString(field.value.type) ? "" : "&");
code_ += "#[inline]";
code_ +=
"pub fn key_compare_less_than(&self, o: &{{STRUCT_TY}}) -> "
"bool {";
code_ += " self.{{FIELD}}() < o.{{FIELD}}()";
code_ += "}";
code_ += "";
code_ += "#[inline]";
code_ +=
"pub fn key_compare_with_value(&self, val: {{KEY_TYPE}}) -> "
"::core::cmp::Ordering {";
code_ += " let key = self.{{FIELD}}();";
code_ += " key.cmp({{REF}}val)";
code_ += "}";
}
// Generate functions for accessing the root table object. This function
// must only be called if the root table is defined.
void GenRootTableFuncs(const StructDef &struct_def) {
FLATBUFFERS_ASSERT(parser_.root_struct_def_ && "root table not defined");
code_.SetValue("STRUCT_TY", namer_.Type(struct_def));
code_.SetValue("STRUCT_FN", namer_.Function(struct_def));
code_.SetValue("STRUCT_CONST", namer_.Constant(struct_def.name));
// Default verifier root fns.
code_ += "#[inline]";
code_ += "/// Verifies that a buffer of bytes contains a `{{STRUCT_TY}}`";
code_ += "/// and returns it.";
code_ += "/// Note that verification is still experimental and may not";
code_ += "/// catch every error, or be maximally performant. For the";
code_ += "/// previous, unchecked, behavior use";
code_ += "/// `root_as_{{STRUCT_FN}}_unchecked`.";
code_ +=
"pub fn root_as_{{STRUCT_FN}}(buf: &[u8]) "
"-> Result<{{STRUCT_TY}}, flatbuffers::InvalidFlatbuffer> {";
code_ += " flatbuffers::root::<{{STRUCT_TY}}>(buf)";
code_ += "}";
code_ += "#[inline]";
code_ += "/// Verifies that a buffer of bytes contains a size prefixed";
code_ += "/// `{{STRUCT_TY}}` and returns it.";
code_ += "/// Note that verification is still experimental and may not";
code_ += "/// catch every error, or be maximally performant. For the";
code_ += "/// previous, unchecked, behavior use";
code_ += "/// `size_prefixed_root_as_{{STRUCT_FN}}_unchecked`.";
code_ +=
"pub fn size_prefixed_root_as_{{STRUCT_FN}}"
"(buf: &[u8]) -> Result<{{STRUCT_TY}}, "
"flatbuffers::InvalidFlatbuffer> {";
code_ += " flatbuffers::size_prefixed_root::<{{STRUCT_TY}}>(buf)";
code_ += "}";
// Verifier with options root fns.
code_ += "#[inline]";
code_ += "/// Verifies, with the given options, that a buffer of bytes";
code_ += "/// contains a `{{STRUCT_TY}}` and returns it.";
code_ += "/// Note that verification is still experimental and may not";
code_ += "/// catch every error, or be maximally performant. For the";
code_ += "/// previous, unchecked, behavior use";
code_ += "/// `root_as_{{STRUCT_FN}}_unchecked`.";
code_ += "pub fn root_as_{{STRUCT_FN}}_with_opts<'b, 'o>(";
code_ += " opts: &'o flatbuffers::VerifierOptions,";
code_ += " buf: &'b [u8],";
code_ +=
") -> Result<{{STRUCT_TY}}<'b>, flatbuffers::InvalidFlatbuffer>"
" {";
code_ += " flatbuffers::root_with_opts::<{{STRUCT_TY}}<'b>>(opts, buf)";
code_ += "}";
code_ += "#[inline]";
code_ += "/// Verifies, with the given verifier options, that a buffer of";
code_ += "/// bytes contains a size prefixed `{{STRUCT_TY}}` and returns";
code_ += "/// it. Note that verification is still experimental and may not";
code_ += "/// catch every error, or be maximally performant. For the";
code_ += "/// previous, unchecked, behavior use";
code_ += "/// `root_as_{{STRUCT_FN}}_unchecked`.";
code_ +=
"pub fn size_prefixed_root_as_{{STRUCT_FN}}_with_opts"
"<'b, 'o>(";
code_ += " opts: &'o flatbuffers::VerifierOptions,";
code_ += " buf: &'b [u8],";
code_ +=
") -> Result<{{STRUCT_TY}}<'b>, flatbuffers::InvalidFlatbuffer>"
" {";
code_ +=
" flatbuffers::size_prefixed_root_with_opts::<{{STRUCT_TY}}"
"<'b>>(opts, buf)";
code_ += "}";
// Unchecked root fns.
code_ += "#[inline]";
code_ +=
"/// Assumes, without verification, that a buffer of bytes "
"contains a {{STRUCT_TY}} and returns it.";
code_ += "/// # Safety";
code_ +=
"/// Callers must trust the given bytes do indeed contain a valid"
" `{{STRUCT_TY}}`.";
code_ +=
"pub unsafe fn root_as_{{STRUCT_FN}}_unchecked"
"(buf: &[u8]) -> {{STRUCT_TY}} {";
code_ += " flatbuffers::root_unchecked::<{{STRUCT_TY}}>(buf)";
code_ += "}";
code_ += "#[inline]";
code_ +=
"/// Assumes, without verification, that a buffer of bytes "
"contains a size prefixed {{STRUCT_TY}} and returns it.";
code_ += "/// # Safety";
code_ +=
"/// Callers must trust the given bytes do indeed contain a valid"
" size prefixed `{{STRUCT_TY}}`.";
code_ +=
"pub unsafe fn size_prefixed_root_as_{{STRUCT_FN}}"
"_unchecked(buf: &[u8]) -> {{STRUCT_TY}} {";
code_ +=
" flatbuffers::size_prefixed_root_unchecked::<{{STRUCT_TY}}>"
"(buf)";
code_ += "}";
if (parser_.file_identifier_.length()) {
// Declare the identifier
// (no lifetime needed as constants have static lifetimes by default)
code_ += "pub const {{STRUCT_CONST}}_IDENTIFIER: &str\\";
code_ += " = \"" + parser_.file_identifier_ + "\";";
code_ += "";
// Check if a buffer has the identifier.
code_ += "#[inline]";
code_ += "pub fn {{STRUCT_FN}}_buffer_has_identifier\\";
code_ += "(buf: &[u8]) -> bool {";
code_ += " flatbuffers::buffer_has_identifier(buf, \\";
code_ += "{{STRUCT_CONST}}_IDENTIFIER, false)";
code_ += "}";
code_ += "";
code_ += "#[inline]";
code_ += "pub fn {{STRUCT_FN}}_size_prefixed\\";
code_ += "_buffer_has_identifier(buf: &[u8]) -> bool {";
code_ += " flatbuffers::buffer_has_identifier(buf, \\";
code_ += "{{STRUCT_CONST}}_IDENTIFIER, true)";
code_ += "}";
code_ += "";
}
if (parser_.file_extension_.length()) {
// Return the extension
code_ += "pub const {{STRUCT_CONST}}_EXTENSION: &str = \\";
code_ += "\"" + parser_.file_extension_ + "\";";
code_ += "";
}
// Finish a buffer with a given root object:
code_ += "#[inline]";
code_ += "pub fn finish_{{STRUCT_FN}}_buffer<'a, 'b>(";
code_ += " fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>,";
code_ += " root: flatbuffers::WIPOffset<{{STRUCT_TY}}<'a>>) {";
if (parser_.file_identifier_.length()) {
code_ += " fbb.finish(root, Some({{STRUCT_CONST}}_IDENTIFIER));";
} else {
code_ += " fbb.finish(root, None);";
}
code_ += "}";
code_ += "";
code_ += "#[inline]";
code_ +=
"pub fn finish_size_prefixed_{{STRUCT_FN}}_buffer"
"<'a, 'b>("
"fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>, "
"root: flatbuffers::WIPOffset<{{STRUCT_TY}}<'a>>) {";
if (parser_.file_identifier_.length()) {
code_ +=
" fbb.finish_size_prefixed(root, "
"Some({{STRUCT_CONST}}_IDENTIFIER));";
} else {
code_ += " fbb.finish_size_prefixed(root, None);";
}
code_ += "}";
}
static void GenPadding(
const FieldDef &field, std::string *code_ptr, int *id,
const std::function<void(int bits, std::string *code_ptr, int *id)> &f) {
if (field.padding) {
for (int i = 0; i < 4; i++) {
if (static_cast<int>(field.padding) & (1 << i)) {
f((1 << i) * 8, code_ptr, id);
}
}
assert(!(field.padding & ~0xF));
}
}
static void PaddingDefinition(int bits, std::string *code_ptr, int *id) {
*code_ptr +=
" padding" + NumToString((*id)++) + "__: u" + NumToString(bits) + ",";
}
static void PaddingInitializer(int bits, std::string *code_ptr, int *id) {
(void)bits;
*code_ptr += "padding" + NumToString((*id)++) + "__: 0,";
}
void ForAllStructFields(const StructDef &struct_def,
std::function<void(const FieldDef &field)> cb) {
size_t offset_to_field = 0;
for (auto it = struct_def.fields.vec.begin();
it != struct_def.fields.vec.end(); ++it) {
const auto &field = **it;
code_.SetValue("FIELD_TYPE", GetTypeGet(field.value.type));
code_.SetValue("FIELD_OTY", ObjectFieldType(field, false));
code_.SetValue("FIELD", namer_.Field(field));
code_.SetValue("FIELD_OFFSET", NumToString(offset_to_field));
code_.SetValue(
"REF",
IsStruct(field.value.type) || IsArray(field.value.type) ? "&" : "");
code_.IncrementIdentLevel();
cb(field);
code_.DecrementIdentLevel();
const size_t size = InlineSize(field.value.type);
offset_to_field += size + field.padding;
}
}
// Generate an accessor struct with constructor for a flatbuffers struct.
void GenStruct(const StructDef &struct_def) {
const bool is_private = parser_.opts.no_leak_private_annotations &&
(struct_def.attributes.Lookup("private") != nullptr);
code_.SetValue("ACCESS_TYPE", is_private ? "pub(crate)" : "pub");
// Generates manual padding and alignment.
// Variables are private because they contain little endian data on all
// platforms.
GenComment(struct_def.doc_comment);
code_.SetValue("ALIGN", NumToString(struct_def.minalign));
code_.SetValue("STRUCT_TY", namer_.Type(struct_def));
code_.SetValue("STRUCT_SIZE", NumToString(struct_def.bytesize));
// We represent Flatbuffers-structs in Rust-u8-arrays since the data may be
// of the wrong endianness and alignment 1.
//
// PartialEq is useful to derive because we can correctly compare structs
// for equality by just comparing their underlying byte data. This doesn't
// hold for PartialOrd/Ord.
code_ += "// struct {{STRUCT_TY}}, aligned to {{ALIGN}}";
code_ += "#[repr(transparent)]";
code_ += "#[derive(Clone, Copy, PartialEq)]";
code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}(pub [u8; {{STRUCT_SIZE}}]);";
code_ += "impl Default for {{STRUCT_TY}} { ";
code_ += " fn default() -> Self { ";
code_ += " Self([0; {{STRUCT_SIZE}}])";
code_ += " }";
code_ += "}";
// Debug for structs.
code_ += "impl core::fmt::Debug for {{STRUCT_TY}} {";
code_ +=
" fn fmt(&self, f: &mut core::fmt::Formatter"
") -> core::fmt::Result {";
code_ += " f.debug_struct(\"{{STRUCT_TY}}\")";
ForAllStructFields(struct_def, [&](const FieldDef &unused) {
(void)unused;
code_ += " .field(\"{{FIELD}}\", &self.{{FIELD}}())";
});
code_ += " .finish()";
code_ += " }";
code_ += "}";
code_ += "";
// Generate impls for SafeSliceAccess (because all structs are endian-safe),
// Follow for the value type, Follow for the reference type, Push for the
// value type, and Push for the reference type.
code_ += "impl flatbuffers::SimpleToVerifyInSlice for {{STRUCT_TY}} {}";
code_ += "impl<'a> flatbuffers::Follow<'a> for {{STRUCT_TY}} {";
code_ += " type Inner = &'a {{STRUCT_TY}};";
code_ += " #[inline]";
code_ += " unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {";
code_ += " <&'a {{STRUCT_TY}}>::follow(buf, loc)";
code_ += " }";
code_ += "}";
code_ += "impl<'a> flatbuffers::Follow<'a> for &'a {{STRUCT_TY}} {";
code_ += " type Inner = &'a {{STRUCT_TY}};";
code_ += " #[inline]";
code_ += " unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {";
code_ += " flatbuffers::follow_cast_ref::<{{STRUCT_TY}}>(buf, loc)";
code_ += " }";
code_ += "}";
code_ += "impl<'b> flatbuffers::Push for {{STRUCT_TY}} {";
code_ += " type Output = {{STRUCT_TY}};";
code_ += " #[inline]";
code_ += " unsafe fn push(&self, dst: &mut [u8], _written_len: usize) {";
code_ += " let src = ::core::slice::from_raw_parts(self as *const {{STRUCT_TY}} as *const u8, Self::size());";
code_ += " dst.copy_from_slice(src);";
code_ += " }";
code_ += "}";
code_ += "";
// Generate verifier: Structs are simple so presence and alignment are
// all that need to be checked.
code_ += "impl<'a> flatbuffers::Verifiable for {{STRUCT_TY}} {";
code_ += " #[inline]";
code_ += " fn run_verifier(";
code_ += " v: &mut flatbuffers::Verifier, pos: usize";
code_ += " ) -> Result<(), flatbuffers::InvalidFlatbuffer> {";
code_ += " use self::flatbuffers::Verifiable;";
code_ += " v.in_buffer::<Self>(pos)";
code_ += " }";
code_ += "}";
code_ += "";
// Implement serde::Serialize
if (parser_.opts.rust_serialize) {
const auto numFields = struct_def.fields.vec.size();
code_.SetValue("NUM_FIELDS", NumToString(numFields));
code_ += "impl Serialize for {{STRUCT_TY}} {";
code_ +=
" fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>";
code_ += " where";
code_ += " S: Serializer,";
code_ += " {";
if (numFields == 0) {
code_ +=
" let s = serializer.serialize_struct(\"{{STRUCT_TY}}\", 0)?;";
} else {
code_ +=
" let mut s = serializer.serialize_struct(\"{{STRUCT_TY}}\", "
"{{NUM_FIELDS}})?;";
}
ForAllStructFields(struct_def, [&](const FieldDef &unused) {
(void)unused;
code_ +=
" s.serialize_field(\"{{FIELD}}\", "
"&self.{{FIELD}}())?;";
});
code_ += " s.end()";
code_ += " }";
code_ += "}";
code_ += "";
}
// Generate a constructor that takes all fields as arguments.
code_ += "impl<'a> {{STRUCT_TY}} {";
code_ += " #[allow(clippy::too_many_arguments)]";
code_ += " pub fn new(";
ForAllStructFields(struct_def, [&](const FieldDef &unused) {
(void)unused;
code_ += " {{FIELD}}: {{REF}}{{FIELD_TYPE}},";
});
code_ += " ) -> Self {";
code_ += " let mut s = Self([0; {{STRUCT_SIZE}}]);";
ForAllStructFields(struct_def, [&](const FieldDef &unused) {
(void)unused;
code_ += " s.set_{{FIELD}}({{FIELD}});";
});
code_ += " s";
code_ += " }";
code_ += "";
if (parser_.opts.generate_name_strings) {
GenFullyQualifiedNameGetter(struct_def, struct_def.name);
}
// Generate accessor methods for the struct.
ForAllStructFields(struct_def, [&](const FieldDef &field) {
this->GenComment(field.doc_comment);
// Getter.
if (IsStruct(field.value.type)) {
code_ += "pub fn {{FIELD}}(&self) -> &{{FIELD_TYPE}} {";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid struct in this slot";
code_ +=
" unsafe {"
" &*(self.0[{{FIELD_OFFSET}}..].as_ptr() as *const"
" {{FIELD_TYPE}}) }";
} else if (IsArray(field.value.type)) {
code_.SetValue("ARRAY_SIZE",
NumToString(field.value.type.fixed_length));
code_.SetValue("ARRAY_ITEM", GetTypeGet(field.value.type.VectorType()));
code_ +=
"pub fn {{FIELD}}(&'a self) -> "
"flatbuffers::Array<'a, {{ARRAY_ITEM}}, {{ARRAY_SIZE}}> {";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid array in this slot";
code_ += " unsafe { flatbuffers::Array::follow(&self.0, {{FIELD_OFFSET}}) }";
} else {
code_ += "pub fn {{FIELD}}(&self) -> {{FIELD_TYPE}} {";
code_ +=
" let mut mem = core::mem::MaybeUninit::"
"<<{{FIELD_TYPE}} as EndianScalar>::Scalar>::uninit();";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid value in this slot";
code_ += " EndianScalar::from_little_endian(unsafe {";
code_ += " core::ptr::copy_nonoverlapping(";
code_ += " self.0[{{FIELD_OFFSET}}..].as_ptr(),";
code_ += " mem.as_mut_ptr() as *mut u8,";
code_ += " core::mem::size_of::<<{{FIELD_TYPE}} as EndianScalar>::Scalar>(),";
code_ += " );";
code_ += " mem.assume_init()";
code_ += " })";
}
code_ += "}\n";
// Setter.
if (IsStruct(field.value.type)) {
code_.SetValue("FIELD_SIZE", NumToString(InlineSize(field.value.type)));
code_ += "#[allow(clippy::identity_op)]"; // If FIELD_OFFSET=0.
code_ += "pub fn set_{{FIELD}}(&mut self, x: &{{FIELD_TYPE}}) {";
code_ +=
" self.0[{{FIELD_OFFSET}}..{{FIELD_OFFSET}} + {{FIELD_SIZE}}]"
".copy_from_slice(&x.0)";
} else if (IsArray(field.value.type)) {
if (GetFullType(field.value.type) == ftArrayOfBuiltin) {
code_.SetValue("ARRAY_ITEM",
GetTypeGet(field.value.type.VectorType()));
code_.SetValue(
"ARRAY_ITEM_SIZE",
NumToString(InlineSize(field.value.type.VectorType())));
code_ +=
"pub fn set_{{FIELD}}(&mut self, items: &{{FIELD_TYPE}}) "
"{";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid array in this slot";
code_ +=
" unsafe { flatbuffers::emplace_scalar_array(&mut self.0, "
"{{FIELD_OFFSET}}, items) };";
} else {
code_.SetValue("FIELD_SIZE",
NumToString(InlineSize(field.value.type)));
code_ += "pub fn set_{{FIELD}}(&mut self, x: &{{FIELD_TYPE}}) {";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid array in this slot";
code_ += " unsafe {";
code_ += " core::ptr::copy(";
code_ += " x.as_ptr() as *const u8,";
code_ += " self.0.as_mut_ptr().add({{FIELD_OFFSET}}),";
code_ += " {{FIELD_SIZE}},";
code_ += " );";
code_ += " }";
}
} else {
code_ += "pub fn set_{{FIELD}}(&mut self, x: {{FIELD_TYPE}}) {";
code_ += " let x_le = x.to_little_endian();";
code_ += " // Safety:";
code_ += " // Created from a valid Table for this object";
code_ += " // Which contains a valid value in this slot";
code_ += " unsafe {";
code_ += " core::ptr::copy_nonoverlapping(";
code_ += " &x_le as *const _ as *const u8,";
code_ += " self.0[{{FIELD_OFFSET}}..].as_mut_ptr(),";
code_ += " core::mem::size_of::<<{{FIELD_TYPE}} as EndianScalar>::Scalar>(),";
code_ += " );";
code_ += " }";
}
code_ += "}\n";
// Generate a comparison function for this field if it is a key.
if (field.key) { GenKeyFieldMethods(field); }
});
// Generate Object API unpack method.
if (parser_.opts.generate_object_based_api) {
code_.SetValue("STRUCT_OTY", namer_.ObjectType(struct_def));
code_ += " pub fn unpack(&self) -> {{STRUCT_OTY}} {";
code_ += " {{STRUCT_OTY}} {";
ForAllStructFields(struct_def, [&](const FieldDef &field) {
if (IsArray(field.value.type)) {
if (GetFullType(field.value.type) == ftArrayOfStruct) {
code_ +=
" {{FIELD}}: { let {{FIELD}} = "
"self.{{FIELD}}(); flatbuffers::array_init(|i| "
"{{FIELD}}.get(i).unpack()) },";
} else {
code_ += " {{FIELD}}: self.{{FIELD}}().into(),";
}
} else {
std::string unpack = IsStruct(field.value.type) ? ".unpack()" : "";
code_ += " {{FIELD}}: self.{{FIELD}}()" + unpack + ",";
}
});
code_ += " }";
code_ += " }";
}
code_ += "}"; // End impl Struct methods.
code_ += "";
// Generate Struct Object.
if (parser_.opts.generate_object_based_api) {
// Struct declaration
code_ += "#[derive(Debug, Clone, PartialEq, Default)]";
code_ += "{{ACCESS_TYPE}} struct {{STRUCT_OTY}} {";
ForAllStructFields(struct_def, [&](const FieldDef &field) {
(void)field; // unused.
code_ += "pub {{FIELD}}: {{FIELD_OTY}},";
});
code_ += "}";
// The `pack` method that turns the native struct into its Flatbuffers
// counterpart.
code_ += "impl {{STRUCT_OTY}} {";
code_ += " pub fn pack(&self) -> {{STRUCT_TY}} {";
code_ += " {{STRUCT_TY}}::new(";
ForAllStructFields(struct_def, [&](const FieldDef &field) {
if (IsStruct(field.value.type)) {
code_ += " &self.{{FIELD}}.pack(),";
} else if (IsArray(field.value.type)) {
if (GetFullType(field.value.type) == ftArrayOfStruct) {
code_ +=
" &flatbuffers::array_init(|i| "
"self.{{FIELD}}[i].pack()),";
} else {
code_ += " &self.{{FIELD}},";
}
} else {
code_ += " self.{{FIELD}},";
}
});
code_ += " )";
code_ += " }";
code_ += "}";
code_ += "";
}
}
void GenNamespaceImports(const int white_spaces) {
// DO not use global attributes (i.e. #![...]) since it interferes
// with users who include! generated files.
// See: https://github.com/google/flatbuffers/issues/6261
std::string indent = std::string(white_spaces, ' ');
code_ += "";
if (!parser_.opts.generate_all) {
for (auto it = parser_.included_files_.begin();
it != parser_.included_files_.end(); ++it) {
if (it->second.empty()) continue;
auto noext = flatbuffers::StripExtension(it->second);
auto basename = flatbuffers::StripPath(noext);
if (parser_.opts.include_prefix.empty()) {
code_ += indent + "use crate::" + basename +
parser_.opts.filename_suffix + "::*;";
} else {
auto prefix = parser_.opts.include_prefix;
prefix.pop_back();
code_ += indent + "use crate::" + prefix + "::" + basename +
parser_.opts.filename_suffix + "::*;";
}
}
}
code_ += indent + "use core::mem;";
code_ += indent + "use core::cmp::Ordering;";
code_ += "";
if (parser_.opts.rust_serialize) {
code_ += indent + "extern crate serde;";
code_ +=
indent +
"use self::serde::ser::{Serialize, Serializer, SerializeStruct};";
code_ += "";
}
code_ += indent + "extern crate flatbuffers;";
code_ += indent + "use self::flatbuffers::{EndianScalar, Follow};";
}
// Set up the correct namespace. This opens a namespace if the current
// namespace is different from the target namespace. This function
// closes and opens the namespaces only as necessary.
//
// The file must start and end with an empty (or null) namespace so that
// namespaces are properly opened and closed.
void SetNameSpace(const Namespace *ns) {
if (cur_name_space_ == ns) { return; }
// Compute the size of the longest common namespace prefix.
// If cur_name_space is A::B::C::D and ns is A::B::E::F::G,
// the common prefix is A::B:: and we have old_size = 4, new_size = 5
// and common_prefix_size = 2
size_t old_size = cur_name_space_ ? cur_name_space_->components.size() : 0;
size_t new_size = ns ? ns->components.size() : 0;
size_t common_prefix_size = 0;
while (common_prefix_size < old_size && common_prefix_size < new_size &&
ns->components[common_prefix_size] ==
cur_name_space_->components[common_prefix_size]) {
common_prefix_size++;
}
// Close cur_name_space in reverse order to reach the common prefix.
// In the previous example, D then C are closed.
for (size_t j = old_size; j > common_prefix_size; --j) {
code_ += "} // pub mod " + cur_name_space_->components[j - 1];
}
if (old_size != common_prefix_size) { code_ += ""; }
// open namespace parts to reach the ns namespace
// in the previous example, E, then F, then G are opened
for (auto j = common_prefix_size; j != new_size; ++j) {
code_ += "#[allow(unused_imports, dead_code)]";
code_ += "pub mod " + namer_.Namespace(ns->components[j]) + " {";
// Generate local namespace imports.
GenNamespaceImports(2);
}
if (new_size != common_prefix_size) { code_ += ""; }
cur_name_space_ = ns;
}
private:
IdlNamer namer_;
};
} // namespace rust
bool GenerateRust(const Parser &parser, const std::string &path,
const std::string &file_name) {
rust::RustGenerator generator(parser, path, file_name);
return generator.generate();
}
std::string RustMakeRule(const Parser &parser, const std::string &path,
const std::string &file_name) {
std::string filebase =
flatbuffers::StripPath(flatbuffers::StripExtension(file_name));
rust::RustGenerator generator(parser, path, file_name);
std::string make_rule =
generator.GeneratedFileName(path, filebase, parser.opts) + ": ";
auto included_files = parser.GetIncludedFilesRecursive(file_name);
for (auto it = included_files.begin(); it != included_files.end(); ++it) {
make_rule += " " + *it;
}
return make_rule;
}
} // namespace flatbuffers
// TODO(rw): Generated code should import other generated files.
// TODO(rw): Generated code should refer to namespaces in included files in a
// way that makes them referrable.
// TODO(rw): Generated code should indent according to nesting level.
// TODO(rw): Generated code should generate endian-safe Debug impls.
// TODO(rw): Generated code could use a Rust-only enum type to access unions,
// instead of making the user use _type() to manually switch.
// TODO(maxburke): There should be test schemas added that use language
// keywords as fields of structs, tables, unions, enums, to make sure
// that internal code generated references escaped names correctly.
// TODO(maxburke): We should see if there is a more flexible way of resolving
// module paths for use declarations. Right now if schemas refer to
// other flatbuffer files, the include paths in emitted Rust bindings
// are crate-relative which may undesirable.