From 3211f857d13ffe75d44f3d9b3e804e8a50dfdb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Baz=20Castillo?= Date: Wed, 4 Feb 2026 13:37:41 +0100 Subject: [PATCH 1/6] Add --ts-undefined-for-optionals command line option (#8861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add --ts-undefined-for-optionals command line option # Details - Fixes #7656 - Added a new `--ts-undefined-for-optionals` command line option for `flatc`. - If enabled, generated TypeScript code uses `undefined` for optional fields rather than `null`. * Also add TS generated test files * Run `sh scripts/clang-format-git.sh` * also add tests/ts/lalala-options.ts to the repo * move new tests to tests/ts/optional_values dir * add tests/ts/optional_values/optional_values_generated.cjs to the repo * reuse existing optional_scalars.fbs and add new test * add comma * sh scripts/clang-format-git.sh * remove comma * sh scripts/clang-format-git.sh * trying things * sh scripts/clang-format-git.sh * done * address feedback * sh scripts/clang-format-git.sh * run `sh scripts/clang-format-git.sh` * remove uneeded `eslint-disable @typescript-eslint/no-namespace` line --------- Co-authored-by: José Luis Millán --- include/flatbuffers/idl.h | 2 + src/flatc.cpp | 5 + src/idl_gen_swift.cpp | 2 +- src/idl_gen_ts.cpp | 96 +-- tests/ts/JavaScriptUndefinedForOptionals.js | 60 ++ tests/ts/TypeScriptTest.py | 15 + .../optional-scalars.ts | 6 + .../optional-scalars/optional-byte.ts | 9 + .../optional-scalars/scalar-stuff.ts | 589 ++++++++++++++++++ .../optional_scalars.ts | 5 + .../optional_scalars_generated.cjs | 551 ++++++++++++++++ ts/builder.ts | 22 +- ts/flexbuffers.ts | 1 - 13 files changed, 1307 insertions(+), 56 deletions(-) create mode 100644 tests/ts/JavaScriptUndefinedForOptionals.js create mode 100644 tests/ts/ts-undefined-for-optionals/optional-scalars.ts create mode 100644 tests/ts/ts-undefined-for-optionals/optional-scalars/optional-byte.ts create mode 100644 tests/ts/ts-undefined-for-optionals/optional-scalars/scalar-stuff.ts create mode 100644 tests/ts/ts-undefined-for-optionals/optional_scalars.ts create mode 100644 tests/ts/ts-undefined-for-optionals/optional_scalars_generated.cjs diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 3e35b48be..5a63fa75a 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -734,6 +734,7 @@ struct IDLOptions { bool python_gen_numpy; bool ts_omit_entrypoint; + bool ts_undefined_for_optionals; ProtoIdGapAction proto_id_gap_action; // Possible options for the more general generator below. @@ -860,6 +861,7 @@ struct IDLOptions { python_typing(false), python_gen_numpy(true), ts_omit_entrypoint(false), + ts_undefined_for_optionals(false), proto_id_gap_action(ProtoIdGapAction::WARNING), mini_reflect(IDLOptions::kNone), require_explicit_ids(false), diff --git a/src/flatc.cpp b/src/flatc.cpp index 4bb590b5f..6a5708574 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -265,6 +265,9 @@ const static FlatCOption flatc_options[] = { {"", "python-gen-numpy", "", "Whether to generate numpy helpers."}, {"", "ts-omit-entrypoint", "", "Omit emission of namespace entrypoint file"}, + {"", "ts-undefined-for-optionals", "", + "Whether to generate undefined values instead of null values for missing " + "optional keys"}, {"", "file-names-only", "", "Print out generated file names without writing to the files"}, {"", "grpc-filename-suffix", "SUFFIX", @@ -710,6 +713,8 @@ FlatCOptions FlatCompiler::ParseFromCommandLineArguments(int argc, opts.python_gen_numpy = false; } else if (arg == "--ts-omit-entrypoint") { opts.ts_omit_entrypoint = true; + } else if (arg == "--ts-undefined-for-optionals") { + opts.ts_undefined_for_optionals = true; } else if (arg == "--annotate-sparse-vectors") { options.annotate_include_vector_contents = false; } else if (arg == "--annotate") { diff --git a/src/idl_gen_swift.cpp b/src/idl_gen_swift.cpp index 7d3a00d6c..0f519b249 100644 --- a/src/idl_gen_swift.cpp +++ b/src/idl_gen_swift.cpp @@ -1963,7 +1963,7 @@ class SwiftGenerator : public BaseGenerator { std::string GenType(const Type& type, const bool should_consider_suffix = false) const { return IsScalar(type.base_type) ? GenTypeBasic(type) - : IsArray(type) ? GenType(type.VectorType()) + : IsArray(type) ? GenType(type.VectorType()) : GenTypePointer(type, should_consider_suffix); } diff --git a/src/idl_gen_ts.cpp b/src/idl_gen_ts.cpp index 14f366a83..923bca9d9 100644 --- a/src/idl_gen_ts.cpp +++ b/src/idl_gen_ts.cpp @@ -87,6 +87,7 @@ std::set TypescriptKeywords() { "throw", "true", "try", "typeof", "var", "void", "while", "with", "as", "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield", + "undefined" // Used with --ts-undefined-for-optionals }; } @@ -111,7 +112,9 @@ class TsGenerator : public BaseGenerator { const std::string& file_name) : BaseGenerator(parser, path, file_name, "", "_", "ts"), namer_(WithFlagOptions(TypeScriptDefaultConfig(), parser.opts, path), - TypescriptKeywords()) {} + TypescriptKeywords()), + null_keyword_(parser_.opts.ts_undefined_for_optionals ? "undefined" + : "null") {} bool generate() { generateEnums(); @@ -215,6 +218,8 @@ class TsGenerator : public BaseGenerator { std::map ns_defs_; + std::string null_keyword_; + // Generate code for all enums. void generateEnums() { for (auto it = parser_.enums_.vec.begin(); it != parser_.enums_.vec.end(); @@ -468,7 +473,7 @@ class TsGenerator : public BaseGenerator { std::string GenDefaultValue(const FieldDef& field, import_set& imports) { if (field.IsScalarOptional()) { - return "null"; + return null_keyword_; } const auto& value = field.value; @@ -519,7 +524,7 @@ class TsGenerator : public BaseGenerator { case BASE_TYPE_STRING: case BASE_TYPE_UNION: case BASE_TYPE_STRUCT: { - return "null"; + return null_keyword_; } case BASE_TYPE_ARRAY: @@ -555,16 +560,16 @@ class TsGenerator : public BaseGenerator { } else { name = AddImport(imports, owner, *type.struct_def).name; } - return allowNull ? (name + "|null") : name; + return allowNull ? (name + "|" + null_keyword_) : name; } } switch (type.base_type) { case BASE_TYPE_BOOL: - return allowNull ? "boolean|null" : "boolean"; + return allowNull ? ("boolean|" + null_keyword_) : "boolean"; case BASE_TYPE_LONG: case BASE_TYPE_ULONG: - return allowNull ? "bigint|null" : "bigint"; + return allowNull ? ("bigint|" + null_keyword_) : "bigint"; case BASE_TYPE_ARRAY: { std::string name; if (type.element == BASE_TYPE_LONG || type.element == BASE_TYPE_ULONG) { @@ -579,16 +584,16 @@ class TsGenerator : public BaseGenerator { } } - return name + (allowNull ? "|null" : ""); + return name + (allowNull ? ("|" + null_keyword_) : ""); } default: if (IsScalar(type.base_type)) { if (type.enum_def) { const auto enum_name = AddImport(imports, owner, *type.enum_def).name; - return allowNull ? (enum_name + "|null") : enum_name; + return allowNull ? (enum_name + "|" + null_keyword_) : enum_name; } - return allowNull ? "number|null" : "number"; + return allowNull ? ("number|" + null_keyword_) : "number"; } return "flatbuffers.Offset"; } @@ -1040,7 +1045,8 @@ class TsGenerator : public BaseGenerator { const auto& enum_def = *union_type.enum_def; const auto valid_union_type = GenUnionTypeTS(enum_def, imports); - const auto valid_union_type_with_null = valid_union_type + "|null"; + const auto valid_union_type_with_null = + valid_union_type + "|" + null_keyword_; auto ret = "\n\nexport function " + GenUnionConvFuncName(enum_def) + "(\n type: " + GetTypeName(enum_def) + @@ -1052,7 +1058,7 @@ class TsGenerator : public BaseGenerator { const auto union_enum_loop = [&](const std::string& accessor_str) { ret += " switch(" + enum_type + "[type]) {\n"; - ret += " case 'NONE': return null; \n"; + ret += " case 'NONE': return " + null_keyword_ + "; \n"; for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { @@ -1076,7 +1082,7 @@ class TsGenerator : public BaseGenerator { ret += "\n"; } - ret += " default: return null;\n"; + ret += " default: return " + null_keyword_ + ";\n"; ret += " }\n"; }; @@ -1121,7 +1127,8 @@ class TsGenerator : public BaseGenerator { ret += " const temp = " + conversion_function + "(this." + namer_.Method(field_name, "Type") + "(), " + field_binded_method + ");\n"; - ret += " if(temp === null) { return null; }\n"; + ret += " if(temp === " + null_keyword_ + ") { return " + + null_keyword_ + "; }\n"; ret += union_has_string ? " if(typeof temp === 'string') { return temp; }\n" : ""; @@ -1141,12 +1148,12 @@ class TsGenerator : public BaseGenerator { "++targetEnumIndex) {\n"; ret += " const targetEnum = this." + namer_.Method(field_name, "Type") + "(targetEnumIndex);\n"; - ret += " if(targetEnum === null || " + enum_type + + ret += " if(targetEnum === " + null_keyword_ + " || " + enum_type + "[targetEnum!] === 'NONE') { " "continue; }\n\n"; ret += " const temp = " + conversion_function + "(targetEnum, " + field_binded_method + ", targetEnumIndex);\n"; - ret += " if(temp === null) { continue; }\n"; + ret += " if(temp === " + null_keyword_ + ") { continue; }\n"; ret += union_has_string ? " if(typeof temp === 'string') { " "ret.push(temp); continue; }\n" : ""; @@ -1163,11 +1170,11 @@ class TsGenerator : public BaseGenerator { return ""; } - static std::string GenNullCheckConditional( - const std::string& nullCheckVar, const std::string& trueVal, - const std::string& falseVal = "null") { - return "(" + nullCheckVar + " !== null ? " + trueVal + " : " + falseVal + - ")"; + std::string GenNullCheckConditional(const std::string& nullCheckVar, + const std::string& trueVal, + const std::string& falseVal) { + return "(" + nullCheckVar + " !== " + null_keyword_ + " ? " + trueVal + + " : " + falseVal + ")"; } std::string GenStructMemberValueTS(const StructDef& struct_def, @@ -1301,8 +1308,8 @@ class TsGenerator : public BaseGenerator { const std::string field_accessor = "this." + namer_.Method(field) + "()"; - field_val = GenNullCheckConditional(field_accessor, - field_accessor + "!.unpack()"); + field_val = GenNullCheckConditional( + field_accessor, field_accessor + "!.unpack()", null_keyword_); auto packing = GenNullCheckConditional( "this." + field_field, "this." + field_field + "!.pack(builder)", "0"); @@ -1510,8 +1517,8 @@ class TsGenerator : public BaseGenerator { break; } - // length 0 vector is simply empty instead of null - field_type += is_vector ? "" : "|null"; + // length 0 vector is simply empty instead of null/undefined. + field_type += is_vector ? "" : ("|" + null_keyword_); } if (!field_offset_decl.empty()) { @@ -1540,7 +1547,7 @@ class TsGenerator : public BaseGenerator { } else { if (field.IsScalarOptional()) { pack_func_create_call += - " if (" + field_offset_val + " !== null)\n "; + " if (" + field_offset_val + " !== " + null_keyword_ + ")\n "; } pack_func_create_call += " " + struct_name + "." + namer_.Method("add", field) + "(builder, " + @@ -1630,7 +1637,8 @@ class TsGenerator : public BaseGenerator { "> {\n"; else code += " {\n"; - code += " bb: flatbuffers.ByteBuffer|null = null;\n"; + code += " bb: flatbuffers.ByteBuffer|" + null_keyword_ + " = " + + null_keyword_ + ";\n"; code += " bb_pos = 0;\n"; // Generate the __init method that sets the field in a pre-existing @@ -1682,7 +1690,7 @@ class TsGenerator : public BaseGenerator { GenDocComment(field.doc_comment, code_ptr); std::string prefix = namer_.Method(field) + "("; if (is_string) { - code += prefix + "):string|null\n"; + code += prefix + "):string|" + null_keyword_ + "\n"; code += prefix + "optionalEncoding:flatbuffers.Encoding" + "):" + GenTypeName(imports, struct_def, field.value.type, false, true) + @@ -1732,7 +1740,8 @@ class TsGenerator : public BaseGenerator { .name; GenDocComment(field.doc_comment, code_ptr); code += namer_.Method(field); - code += "(obj?:" + type + "):" + type + "|null {\n"; + code += + "(obj?:" + type + "):" + type + "|" + null_keyword_ + " {\n"; if (struct_def.fixed) { code += " return (obj || " + GenerateNewExpression(type); @@ -1745,7 +1754,7 @@ class TsGenerator : public BaseGenerator { code += field.value.type.struct_def->fixed ? "this.bb_pos + offset" : GenBBAccess() + ".__indirect(this.bb_pos + offset)"; - code += ", " + GenBBAccess() + ") : null;\n"; + code += ", " + GenBBAccess() + ") : " + null_keyword_ + ";\n"; } break; @@ -1798,7 +1807,7 @@ class TsGenerator : public BaseGenerator { } else { code += prefix; } - code += "):" + vectortypename + "|null {\n"; + code += "):" + vectortypename + "|" + null_keyword_ + " {\n"; if (vectortype.base_type == BASE_TYPE_STRUCT) { code += offset_prefix + "(obj || " + @@ -1838,7 +1847,7 @@ class TsGenerator : public BaseGenerator { code += " : 0"; } } else { - code += ": null"; + code += ": " + null_keyword_; } break; } @@ -1896,7 +1905,7 @@ class TsGenerator : public BaseGenerator { } else { code += prefix; } - code += "):" + vectortypename + "|null {\n"; + code += "):" + vectortypename + "|" + null_keyword_ + " {\n"; if (vectortype.base_type == BASE_TYPE_STRUCT) { code += offset_prefix + "(obj || " + @@ -1922,12 +1931,12 @@ class TsGenerator : public BaseGenerator { code += "BigInt(0)"; } else if (IsScalar(field.value.type.element)) { if (field.value.type.enum_def) { - code += "null"; + code += null_keyword_; } else { code += "0"; } } else { - code += "null"; + code += null_keyword_; } code += ";\n"; break; @@ -1940,13 +1949,13 @@ class TsGenerator : public BaseGenerator { const auto& union_enum = *(field.value.type.enum_def); const auto union_type = GenUnionGenericTypeTS(union_enum); code += "(obj:" + union_type + - "):" + union_type + - "|null " + "):" + union_type + "|" + null_keyword_ + + " " "{\n"; code += offset_prefix + GenGetter(field.value.type, "(obj, this.bb_pos + offset)") + - " : null;\n"; + " : " + null_keyword_ + ";\n"; break; } default: @@ -2008,14 +2017,15 @@ class TsGenerator : public BaseGenerator { GenDocComment(code_ptr); code += namer_.Method(field, "Array"); - code += - "():" + GenType(vectorType) + "Array|null {\n" + offset_prefix; + code += "():" + GenType(vectorType) + "Array|" + null_keyword_ + + " {\n" + offset_prefix; code += "new " + GenType(vectorType) + "Array(" + GenBBAccess() + ".bytes().buffer, " + GenBBAccess() + ".bytes().byteOffset + " + GenBBAccess() + ".__vector(this.bb_pos + offset), " + GenBBAccess() + - ".__vector_len(this.bb_pos + offset)) : null;\n}\n\n"; + ".__vector_len(this.bb_pos + offset)) : " + null_keyword_ + + ";\n}\n\n"; } } } @@ -2085,7 +2095,7 @@ class TsGenerator : public BaseGenerator { if (!IsScalar(field.value.type.base_type)) { code += "0"; } else if (HasNullDefault(field)) { - code += "null"; + code += null_keyword_; } else { if (field.value.type.base_type == BASE_TYPE_BOOL) { code += "+"; @@ -2202,7 +2212,7 @@ class TsGenerator : public BaseGenerator { const auto arg_name = GetArgName(field); if (field.IsScalarOptional()) { - code += " if (" + arg_name + " !== null)\n "; + code += " if (" + arg_name + " !== " + null_keyword_ + ")\n "; } code += " " + methodPrefix + "." + namer_.Method("add", field) + "("; @@ -2242,7 +2252,7 @@ class TsGenerator : public BaseGenerator { } } - static bool HasNullDefault(const FieldDef& field) { + bool HasNullDefault(const FieldDef& field) { return field.IsOptional() && field.value.constant == "null"; } diff --git a/tests/ts/JavaScriptUndefinedForOptionals.js b/tests/ts/JavaScriptUndefinedForOptionals.js new file mode 100644 index 000000000..6cbd28371 --- /dev/null +++ b/tests/ts/JavaScriptUndefinedForOptionals.js @@ -0,0 +1,60 @@ +import assert from 'assert' +import * as flatbuffers from 'flatbuffers' + +import optional_scalars from './ts-undefined-for-optionals/optional_scalars_generated.cjs' + +const { ScalarStuff, ScalarStuffT } = optional_scalars.optional_scalars; + +function testScalarStuffBuf(scalarStuff) { + assert.strictEqual(scalarStuff.justI8(), -1); + assert.strictEqual(scalarStuff.maybeI8(), undefined); + assert.strictEqual(scalarStuff.defaultI8(), 42); + assert.strictEqual(scalarStuff.justU8(), 1); + assert.strictEqual(scalarStuff.maybeU8(), undefined); + assert.strictEqual(scalarStuff.defaultU8(), 42); +} + +function testScalarStuffUnpack(scalarStuff) { + assert.strictEqual(scalarStuff.justI8, -1); + assert.strictEqual(scalarStuff.maybeI8, undefined); + assert.strictEqual(scalarStuff.defaultI8, 42); + assert.strictEqual(scalarStuff.justU8, 1); + assert.strictEqual(scalarStuff.maybeU8, undefined); + assert.strictEqual(scalarStuff.defaultU8, 42); +} + +function createScalarStuff(fbb) { + ScalarStuff.startScalarStuff(fbb); + ScalarStuff.addJustI8(fbb, -1); + ScalarStuff.addJustU8(fbb, 1); + var offset = ScalarStuff.endScalarStuff(fbb); + ScalarStuff.finishScalarStuffBuffer(fbb, offset); +} + +function main() { + var fbb = new flatbuffers.Builder(); + + createScalarStuff(fbb); + + var buf = new flatbuffers.ByteBuffer(fbb.asUint8Array()); + var scalarStuff = ScalarStuff.getRootAsScalarStuff(buf); + + testScalarStuffBuf(scalarStuff); + + testScalarStuffUnpack(scalarStuff.unpack()); + + var scalarStuff_to = new ScalarStuffT(); + scalarStuff.unpackTo(scalarStuff_to); + + testScalarStuffUnpack(scalarStuff_to); + + fbb.clear(); + ScalarStuff.finishScalarStuffBuffer(fbb, scalarStuff_to.pack(fbb)); + var unpackBuf = new flatbuffers.ByteBuffer(fbb.asUint8Array()); + + testScalarStuffBuf(ScalarStuff.getRootAsScalarStuff(unpackBuf)); + + console.log('FlatBuffers --ts-undefined-for-optionals test: completed successfully'); +} + +main(); diff --git a/tests/ts/TypeScriptTest.py b/tests/ts/TypeScriptTest.py index a12bc25ab..6870ff995 100755 --- a/tests/ts/TypeScriptTest.py +++ b/tests/ts/TypeScriptTest.py @@ -99,6 +99,20 @@ flatc( schema="../non_zero_enum.fbs", ) +flatc( + options=[ + "--ts", + "--gen-object-api", + "--ts-undefined-for-optionals", + ], + schema="../optional_scalars.fbs", + prefix="ts-undefined-for-optionals", +) +esbuild( + "ts-undefined-for-optionals/optional_scalars.ts", + "ts-undefined-for-optionals/optional_scalars_generated.cjs", +) + flatc( options=[ "--ts", @@ -201,6 +215,7 @@ check_call(NODE_CMD + ["JavaScriptUnionVectorTest"]) check_call(NODE_CMD + ["JavaScriptFlexBuffersTest"]) check_call(NODE_CMD + ["JavaScriptComplexArraysTest"]) check_call(NODE_CMD + ["JavaScriptUnionUnderlyingTypeTest"]) +check_call(NODE_CMD + ["JavaScriptUndefinedForOptionals"]) print("Running old v1 TypeScript Tests...") check_call(NODE_CMD + ["JavaScriptTestv1.cjs", "./monster_test_generated.cjs"]) diff --git a/tests/ts/ts-undefined-for-optionals/optional-scalars.ts b/tests/ts/ts-undefined-for-optionals/optional-scalars.ts new file mode 100644 index 000000000..aba7511a4 --- /dev/null +++ b/tests/ts/ts-undefined-for-optionals/optional-scalars.ts @@ -0,0 +1,6 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +export { OptionalByte } from './optional-scalars/optional-byte.js'; +export { ScalarStuff, ScalarStuffT } from './optional-scalars/scalar-stuff.js'; diff --git a/tests/ts/ts-undefined-for-optionals/optional-scalars/optional-byte.ts b/tests/ts/ts-undefined-for-optionals/optional-scalars/optional-byte.ts new file mode 100644 index 000000000..9bb66a9bc --- /dev/null +++ b/tests/ts/ts-undefined-for-optionals/optional-scalars/optional-byte.ts @@ -0,0 +1,9 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +export enum OptionalByte { + None = 0, + One = 1, + Two = 2 +} diff --git a/tests/ts/ts-undefined-for-optionals/optional-scalars/scalar-stuff.ts b/tests/ts/ts-undefined-for-optionals/optional-scalars/scalar-stuff.ts new file mode 100644 index 000000000..448b9d6b6 --- /dev/null +++ b/tests/ts/ts-undefined-for-optionals/optional-scalars/scalar-stuff.ts @@ -0,0 +1,589 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +import * as flatbuffers from 'flatbuffers'; + +import { OptionalByte } from '../optional-scalars/optional-byte.js'; + + +export class ScalarStuff implements flatbuffers.IUnpackableObject { + bb: flatbuffers.ByteBuffer|undefined = undefined; + bb_pos = 0; + __init(i:number, bb:flatbuffers.ByteBuffer):ScalarStuff { + this.bb_pos = i; + this.bb = bb; + return this; +} + +static getRootAsScalarStuff(bb:flatbuffers.ByteBuffer, obj?:ScalarStuff):ScalarStuff { + return (obj || new ScalarStuff()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsScalarStuff(bb:flatbuffers.ByteBuffer, obj?:ScalarStuff):ScalarStuff { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new ScalarStuff()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static bufferHasIdentifier(bb:flatbuffers.ByteBuffer):boolean { + return bb.__has_identifier('NULL'); +} + +justI8():number { + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.readInt8(this.bb_pos + offset) : 0; +} + +maybeI8():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? this.bb!.readInt8(this.bb_pos + offset) : undefined; +} + +defaultI8():number { + const offset = this.bb!.__offset(this.bb_pos, 8); + return offset ? this.bb!.readInt8(this.bb_pos + offset) : 42; +} + +justU8():number { + const offset = this.bb!.__offset(this.bb_pos, 10); + return offset ? this.bb!.readUint8(this.bb_pos + offset) : 0; +} + +maybeU8():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 12); + return offset ? this.bb!.readUint8(this.bb_pos + offset) : undefined; +} + +defaultU8():number { + const offset = this.bb!.__offset(this.bb_pos, 14); + return offset ? this.bb!.readUint8(this.bb_pos + offset) : 42; +} + +justI16():number { + const offset = this.bb!.__offset(this.bb_pos, 16); + return offset ? this.bb!.readInt16(this.bb_pos + offset) : 0; +} + +maybeI16():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 18); + return offset ? this.bb!.readInt16(this.bb_pos + offset) : undefined; +} + +defaultI16():number { + const offset = this.bb!.__offset(this.bb_pos, 20); + return offset ? this.bb!.readInt16(this.bb_pos + offset) : 42; +} + +justU16():number { + const offset = this.bb!.__offset(this.bb_pos, 22); + return offset ? this.bb!.readUint16(this.bb_pos + offset) : 0; +} + +maybeU16():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 24); + return offset ? this.bb!.readUint16(this.bb_pos + offset) : undefined; +} + +defaultU16():number { + const offset = this.bb!.__offset(this.bb_pos, 26); + return offset ? this.bb!.readUint16(this.bb_pos + offset) : 42; +} + +justI32():number { + const offset = this.bb!.__offset(this.bb_pos, 28); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 0; +} + +maybeI32():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 30); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : undefined; +} + +defaultI32():number { + const offset = this.bb!.__offset(this.bb_pos, 32); + return offset ? this.bb!.readInt32(this.bb_pos + offset) : 42; +} + +justU32():number { + const offset = this.bb!.__offset(this.bb_pos, 34); + return offset ? this.bb!.readUint32(this.bb_pos + offset) : 0; +} + +maybeU32():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 36); + return offset ? this.bb!.readUint32(this.bb_pos + offset) : undefined; +} + +defaultU32():number { + const offset = this.bb!.__offset(this.bb_pos, 38); + return offset ? this.bb!.readUint32(this.bb_pos + offset) : 42; +} + +justI64():bigint { + const offset = this.bb!.__offset(this.bb_pos, 40); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('0'); +} + +maybeI64():bigint|undefined { + const offset = this.bb!.__offset(this.bb_pos, 42); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : undefined; +} + +defaultI64():bigint { + const offset = this.bb!.__offset(this.bb_pos, 44); + return offset ? this.bb!.readInt64(this.bb_pos + offset) : BigInt('42'); +} + +justU64():bigint { + const offset = this.bb!.__offset(this.bb_pos, 46); + return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('0'); +} + +maybeU64():bigint|undefined { + const offset = this.bb!.__offset(this.bb_pos, 48); + return offset ? this.bb!.readUint64(this.bb_pos + offset) : undefined; +} + +defaultU64():bigint { + const offset = this.bb!.__offset(this.bb_pos, 50); + return offset ? this.bb!.readUint64(this.bb_pos + offset) : BigInt('42'); +} + +justF32():number { + const offset = this.bb!.__offset(this.bb_pos, 52); + return offset ? this.bb!.readFloat32(this.bb_pos + offset) : 0.0; +} + +maybeF32():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 54); + return offset ? this.bb!.readFloat32(this.bb_pos + offset) : undefined; +} + +defaultF32():number { + const offset = this.bb!.__offset(this.bb_pos, 56); + return offset ? this.bb!.readFloat32(this.bb_pos + offset) : 42.0; +} + +justF64():number { + const offset = this.bb!.__offset(this.bb_pos, 58); + return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 0.0; +} + +maybeF64():number|undefined { + const offset = this.bb!.__offset(this.bb_pos, 60); + return offset ? this.bb!.readFloat64(this.bb_pos + offset) : undefined; +} + +defaultF64():number { + const offset = this.bb!.__offset(this.bb_pos, 62); + return offset ? this.bb!.readFloat64(this.bb_pos + offset) : 42.0; +} + +justBool():boolean { + const offset = this.bb!.__offset(this.bb_pos, 64); + return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false; +} + +maybeBool():boolean|undefined { + const offset = this.bb!.__offset(this.bb_pos, 66); + return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : undefined; +} + +defaultBool():boolean { + const offset = this.bb!.__offset(this.bb_pos, 68); + return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : true; +} + +justEnum():OptionalByte { + const offset = this.bb!.__offset(this.bb_pos, 70); + return offset ? this.bb!.readInt8(this.bb_pos + offset) : OptionalByte.None; +} + +maybeEnum():OptionalByte|undefined { + const offset = this.bb!.__offset(this.bb_pos, 72); + return offset ? this.bb!.readInt8(this.bb_pos + offset) : undefined; +} + +defaultEnum():OptionalByte { + const offset = this.bb!.__offset(this.bb_pos, 74); + return offset ? this.bb!.readInt8(this.bb_pos + offset) : OptionalByte.One; +} + +static startScalarStuff(builder:flatbuffers.Builder) { + builder.startObject(36); +} + +static addJustI8(builder:flatbuffers.Builder, justI8:number) { + builder.addFieldInt8(0, justI8, 0); +} + +static addMaybeI8(builder:flatbuffers.Builder, maybeI8:number) { + builder.addFieldInt8(1, maybeI8, undefined); +} + +static addDefaultI8(builder:flatbuffers.Builder, defaultI8:number) { + builder.addFieldInt8(2, defaultI8, 42); +} + +static addJustU8(builder:flatbuffers.Builder, justU8:number) { + builder.addFieldInt8(3, justU8, 0); +} + +static addMaybeU8(builder:flatbuffers.Builder, maybeU8:number) { + builder.addFieldInt8(4, maybeU8, undefined); +} + +static addDefaultU8(builder:flatbuffers.Builder, defaultU8:number) { + builder.addFieldInt8(5, defaultU8, 42); +} + +static addJustI16(builder:flatbuffers.Builder, justI16:number) { + builder.addFieldInt16(6, justI16, 0); +} + +static addMaybeI16(builder:flatbuffers.Builder, maybeI16:number) { + builder.addFieldInt16(7, maybeI16, undefined); +} + +static addDefaultI16(builder:flatbuffers.Builder, defaultI16:number) { + builder.addFieldInt16(8, defaultI16, 42); +} + +static addJustU16(builder:flatbuffers.Builder, justU16:number) { + builder.addFieldInt16(9, justU16, 0); +} + +static addMaybeU16(builder:flatbuffers.Builder, maybeU16:number) { + builder.addFieldInt16(10, maybeU16, undefined); +} + +static addDefaultU16(builder:flatbuffers.Builder, defaultU16:number) { + builder.addFieldInt16(11, defaultU16, 42); +} + +static addJustI32(builder:flatbuffers.Builder, justI32:number) { + builder.addFieldInt32(12, justI32, 0); +} + +static addMaybeI32(builder:flatbuffers.Builder, maybeI32:number) { + builder.addFieldInt32(13, maybeI32, undefined); +} + +static addDefaultI32(builder:flatbuffers.Builder, defaultI32:number) { + builder.addFieldInt32(14, defaultI32, 42); +} + +static addJustU32(builder:flatbuffers.Builder, justU32:number) { + builder.addFieldInt32(15, justU32, 0); +} + +static addMaybeU32(builder:flatbuffers.Builder, maybeU32:number) { + builder.addFieldInt32(16, maybeU32, undefined); +} + +static addDefaultU32(builder:flatbuffers.Builder, defaultU32:number) { + builder.addFieldInt32(17, defaultU32, 42); +} + +static addJustI64(builder:flatbuffers.Builder, justI64:bigint) { + builder.addFieldInt64(18, justI64, BigInt('0')); +} + +static addMaybeI64(builder:flatbuffers.Builder, maybeI64:bigint) { + builder.addFieldInt64(19, maybeI64, undefined); +} + +static addDefaultI64(builder:flatbuffers.Builder, defaultI64:bigint) { + builder.addFieldInt64(20, defaultI64, BigInt('42')); +} + +static addJustU64(builder:flatbuffers.Builder, justU64:bigint) { + builder.addFieldInt64(21, justU64, BigInt('0')); +} + +static addMaybeU64(builder:flatbuffers.Builder, maybeU64:bigint) { + builder.addFieldInt64(22, maybeU64, undefined); +} + +static addDefaultU64(builder:flatbuffers.Builder, defaultU64:bigint) { + builder.addFieldInt64(23, defaultU64, BigInt('42')); +} + +static addJustF32(builder:flatbuffers.Builder, justF32:number) { + builder.addFieldFloat32(24, justF32, 0.0); +} + +static addMaybeF32(builder:flatbuffers.Builder, maybeF32:number) { + builder.addFieldFloat32(25, maybeF32, undefined); +} + +static addDefaultF32(builder:flatbuffers.Builder, defaultF32:number) { + builder.addFieldFloat32(26, defaultF32, 42.0); +} + +static addJustF64(builder:flatbuffers.Builder, justF64:number) { + builder.addFieldFloat64(27, justF64, 0.0); +} + +static addMaybeF64(builder:flatbuffers.Builder, maybeF64:number) { + builder.addFieldFloat64(28, maybeF64, undefined); +} + +static addDefaultF64(builder:flatbuffers.Builder, defaultF64:number) { + builder.addFieldFloat64(29, defaultF64, 42.0); +} + +static addJustBool(builder:flatbuffers.Builder, justBool:boolean) { + builder.addFieldInt8(30, +justBool, +false); +} + +static addMaybeBool(builder:flatbuffers.Builder, maybeBool:boolean) { + builder.addFieldInt8(31, +maybeBool, undefined); +} + +static addDefaultBool(builder:flatbuffers.Builder, defaultBool:boolean) { + builder.addFieldInt8(32, +defaultBool, +true); +} + +static addJustEnum(builder:flatbuffers.Builder, justEnum:OptionalByte) { + builder.addFieldInt8(33, justEnum, OptionalByte.None); +} + +static addMaybeEnum(builder:flatbuffers.Builder, maybeEnum:OptionalByte) { + builder.addFieldInt8(34, maybeEnum, undefined); +} + +static addDefaultEnum(builder:flatbuffers.Builder, defaultEnum:OptionalByte) { + builder.addFieldInt8(35, defaultEnum, OptionalByte.One); +} + +static endScalarStuff(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static finishScalarStuffBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) { + builder.finish(offset, 'NULL'); +} + +static finishSizePrefixedScalarStuffBuffer(builder:flatbuffers.Builder, offset:flatbuffers.Offset) { + builder.finish(offset, 'NULL', true); +} + +static createScalarStuff(builder:flatbuffers.Builder, justI8:number, maybeI8:number|undefined, defaultI8:number, justU8:number, maybeU8:number|undefined, defaultU8:number, justI16:number, maybeI16:number|undefined, defaultI16:number, justU16:number, maybeU16:number|undefined, defaultU16:number, justI32:number, maybeI32:number|undefined, defaultI32:number, justU32:number, maybeU32:number|undefined, defaultU32:number, justI64:bigint, maybeI64:bigint|undefined, defaultI64:bigint, justU64:bigint, maybeU64:bigint|undefined, defaultU64:bigint, justF32:number, maybeF32:number|undefined, defaultF32:number, justF64:number, maybeF64:number|undefined, defaultF64:number, justBool:boolean, maybeBool:boolean|undefined, defaultBool:boolean, justEnum:OptionalByte, maybeEnum:OptionalByte|undefined, defaultEnum:OptionalByte):flatbuffers.Offset { + ScalarStuff.startScalarStuff(builder); + ScalarStuff.addJustI8(builder, justI8); + if (maybeI8 !== undefined) + ScalarStuff.addMaybeI8(builder, maybeI8); + ScalarStuff.addDefaultI8(builder, defaultI8); + ScalarStuff.addJustU8(builder, justU8); + if (maybeU8 !== undefined) + ScalarStuff.addMaybeU8(builder, maybeU8); + ScalarStuff.addDefaultU8(builder, defaultU8); + ScalarStuff.addJustI16(builder, justI16); + if (maybeI16 !== undefined) + ScalarStuff.addMaybeI16(builder, maybeI16); + ScalarStuff.addDefaultI16(builder, defaultI16); + ScalarStuff.addJustU16(builder, justU16); + if (maybeU16 !== undefined) + ScalarStuff.addMaybeU16(builder, maybeU16); + ScalarStuff.addDefaultU16(builder, defaultU16); + ScalarStuff.addJustI32(builder, justI32); + if (maybeI32 !== undefined) + ScalarStuff.addMaybeI32(builder, maybeI32); + ScalarStuff.addDefaultI32(builder, defaultI32); + ScalarStuff.addJustU32(builder, justU32); + if (maybeU32 !== undefined) + ScalarStuff.addMaybeU32(builder, maybeU32); + ScalarStuff.addDefaultU32(builder, defaultU32); + ScalarStuff.addJustI64(builder, justI64); + if (maybeI64 !== undefined) + ScalarStuff.addMaybeI64(builder, maybeI64); + ScalarStuff.addDefaultI64(builder, defaultI64); + ScalarStuff.addJustU64(builder, justU64); + if (maybeU64 !== undefined) + ScalarStuff.addMaybeU64(builder, maybeU64); + ScalarStuff.addDefaultU64(builder, defaultU64); + ScalarStuff.addJustF32(builder, justF32); + if (maybeF32 !== undefined) + ScalarStuff.addMaybeF32(builder, maybeF32); + ScalarStuff.addDefaultF32(builder, defaultF32); + ScalarStuff.addJustF64(builder, justF64); + if (maybeF64 !== undefined) + ScalarStuff.addMaybeF64(builder, maybeF64); + ScalarStuff.addDefaultF64(builder, defaultF64); + ScalarStuff.addJustBool(builder, justBool); + if (maybeBool !== undefined) + ScalarStuff.addMaybeBool(builder, maybeBool); + ScalarStuff.addDefaultBool(builder, defaultBool); + ScalarStuff.addJustEnum(builder, justEnum); + if (maybeEnum !== undefined) + ScalarStuff.addMaybeEnum(builder, maybeEnum); + ScalarStuff.addDefaultEnum(builder, defaultEnum); + return ScalarStuff.endScalarStuff(builder); +} + +unpack(): ScalarStuffT { + return new ScalarStuffT( + this.justI8(), + this.maybeI8(), + this.defaultI8(), + this.justU8(), + this.maybeU8(), + this.defaultU8(), + this.justI16(), + this.maybeI16(), + this.defaultI16(), + this.justU16(), + this.maybeU16(), + this.defaultU16(), + this.justI32(), + this.maybeI32(), + this.defaultI32(), + this.justU32(), + this.maybeU32(), + this.defaultU32(), + this.justI64(), + this.maybeI64(), + this.defaultI64(), + this.justU64(), + this.maybeU64(), + this.defaultU64(), + this.justF32(), + this.maybeF32(), + this.defaultF32(), + this.justF64(), + this.maybeF64(), + this.defaultF64(), + this.justBool(), + this.maybeBool(), + this.defaultBool(), + this.justEnum(), + this.maybeEnum(), + this.defaultEnum() + ); +} + + +unpackTo(_o: ScalarStuffT): void { + _o.justI8 = this.justI8(); + _o.maybeI8 = this.maybeI8(); + _o.defaultI8 = this.defaultI8(); + _o.justU8 = this.justU8(); + _o.maybeU8 = this.maybeU8(); + _o.defaultU8 = this.defaultU8(); + _o.justI16 = this.justI16(); + _o.maybeI16 = this.maybeI16(); + _o.defaultI16 = this.defaultI16(); + _o.justU16 = this.justU16(); + _o.maybeU16 = this.maybeU16(); + _o.defaultU16 = this.defaultU16(); + _o.justI32 = this.justI32(); + _o.maybeI32 = this.maybeI32(); + _o.defaultI32 = this.defaultI32(); + _o.justU32 = this.justU32(); + _o.maybeU32 = this.maybeU32(); + _o.defaultU32 = this.defaultU32(); + _o.justI64 = this.justI64(); + _o.maybeI64 = this.maybeI64(); + _o.defaultI64 = this.defaultI64(); + _o.justU64 = this.justU64(); + _o.maybeU64 = this.maybeU64(); + _o.defaultU64 = this.defaultU64(); + _o.justF32 = this.justF32(); + _o.maybeF32 = this.maybeF32(); + _o.defaultF32 = this.defaultF32(); + _o.justF64 = this.justF64(); + _o.maybeF64 = this.maybeF64(); + _o.defaultF64 = this.defaultF64(); + _o.justBool = this.justBool(); + _o.maybeBool = this.maybeBool(); + _o.defaultBool = this.defaultBool(); + _o.justEnum = this.justEnum(); + _o.maybeEnum = this.maybeEnum(); + _o.defaultEnum = this.defaultEnum(); +} +} + +export class ScalarStuffT implements flatbuffers.IGeneratedObject { +constructor( + public justI8: number = 0, + public maybeI8: number|undefined = undefined, + public defaultI8: number = 42, + public justU8: number = 0, + public maybeU8: number|undefined = undefined, + public defaultU8: number = 42, + public justI16: number = 0, + public maybeI16: number|undefined = undefined, + public defaultI16: number = 42, + public justU16: number = 0, + public maybeU16: number|undefined = undefined, + public defaultU16: number = 42, + public justI32: number = 0, + public maybeI32: number|undefined = undefined, + public defaultI32: number = 42, + public justU32: number = 0, + public maybeU32: number|undefined = undefined, + public defaultU32: number = 42, + public justI64: bigint = BigInt('0'), + public maybeI64: bigint|undefined = undefined, + public defaultI64: bigint = BigInt('42'), + public justU64: bigint = BigInt('0'), + public maybeU64: bigint|undefined = undefined, + public defaultU64: bigint = BigInt('42'), + public justF32: number = 0.0, + public maybeF32: number|undefined = undefined, + public defaultF32: number = 42.0, + public justF64: number = 0.0, + public maybeF64: number|undefined = undefined, + public defaultF64: number = 42.0, + public justBool: boolean = false, + public maybeBool: boolean|undefined = undefined, + public defaultBool: boolean = true, + public justEnum: OptionalByte = OptionalByte.None, + public maybeEnum: OptionalByte|undefined = undefined, + public defaultEnum: OptionalByte = OptionalByte.One +){} + + +pack(builder:flatbuffers.Builder): flatbuffers.Offset { + return ScalarStuff.createScalarStuff(builder, + this.justI8, + this.maybeI8, + this.defaultI8, + this.justU8, + this.maybeU8, + this.defaultU8, + this.justI16, + this.maybeI16, + this.defaultI16, + this.justU16, + this.maybeU16, + this.defaultU16, + this.justI32, + this.maybeI32, + this.defaultI32, + this.justU32, + this.maybeU32, + this.defaultU32, + this.justI64, + this.maybeI64, + this.defaultI64, + this.justU64, + this.maybeU64, + this.defaultU64, + this.justF32, + this.maybeF32, + this.defaultF32, + this.justF64, + this.maybeF64, + this.defaultF64, + this.justBool, + this.maybeBool, + this.defaultBool, + this.justEnum, + this.maybeEnum, + this.defaultEnum + ); +} +} diff --git a/tests/ts/ts-undefined-for-optionals/optional_scalars.ts b/tests/ts/ts-undefined-for-optionals/optional_scalars.ts new file mode 100644 index 000000000..3805ab68c --- /dev/null +++ b/tests/ts/ts-undefined-for-optionals/optional_scalars.ts @@ -0,0 +1,5 @@ +// automatically generated by the FlatBuffers compiler, do not modify + +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */ + +export * as optional_scalars from './optional-scalars.js'; diff --git a/tests/ts/ts-undefined-for-optionals/optional_scalars_generated.cjs b/tests/ts/ts-undefined-for-optionals/optional_scalars_generated.cjs new file mode 100644 index 000000000..3997f0081 --- /dev/null +++ b/tests/ts/ts-undefined-for-optionals/optional_scalars_generated.cjs @@ -0,0 +1,551 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// ts-undefined-for-optionals/optional_scalars.ts +var optional_scalars_exports2 = {}; +__export(optional_scalars_exports2, { + optional_scalars: () => optional_scalars_exports +}); +module.exports = __toCommonJS(optional_scalars_exports2); + +// ts-undefined-for-optionals/optional-scalars.ts +var optional_scalars_exports = {}; +__export(optional_scalars_exports, { + OptionalByte: () => OptionalByte, + ScalarStuff: () => ScalarStuff, + ScalarStuffT: () => ScalarStuffT +}); + +// ts-undefined-for-optionals/optional-scalars/optional-byte.ts +var OptionalByte = /* @__PURE__ */ ((OptionalByte2) => { + OptionalByte2[OptionalByte2["None"] = 0] = "None"; + OptionalByte2[OptionalByte2["One"] = 1] = "One"; + OptionalByte2[OptionalByte2["Two"] = 2] = "Two"; + return OptionalByte2; +})(OptionalByte || {}); + +// ts-undefined-for-optionals/optional-scalars/scalar-stuff.ts +var flatbuffers = __toESM(require("flatbuffers"), 1); +var ScalarStuff = class _ScalarStuff { + constructor() { + this.bb = void 0; + this.bb_pos = 0; + } + __init(i, bb) { + this.bb_pos = i; + this.bb = bb; + return this; + } + static getRootAsScalarStuff(bb, obj) { + return (obj || new _ScalarStuff()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + static getSizePrefixedRootAsScalarStuff(bb, obj) { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new _ScalarStuff()).__init(bb.readInt32(bb.position()) + bb.position(), bb); + } + static bufferHasIdentifier(bb) { + return bb.__has_identifier("NULL"); + } + justI8() { + const offset = this.bb.__offset(this.bb_pos, 4); + return offset ? this.bb.readInt8(this.bb_pos + offset) : 0; + } + maybeI8() { + const offset = this.bb.__offset(this.bb_pos, 6); + return offset ? this.bb.readInt8(this.bb_pos + offset) : void 0; + } + defaultI8() { + const offset = this.bb.__offset(this.bb_pos, 8); + return offset ? this.bb.readInt8(this.bb_pos + offset) : 42; + } + justU8() { + const offset = this.bb.__offset(this.bb_pos, 10); + return offset ? this.bb.readUint8(this.bb_pos + offset) : 0; + } + maybeU8() { + const offset = this.bb.__offset(this.bb_pos, 12); + return offset ? this.bb.readUint8(this.bb_pos + offset) : void 0; + } + defaultU8() { + const offset = this.bb.__offset(this.bb_pos, 14); + return offset ? this.bb.readUint8(this.bb_pos + offset) : 42; + } + justI16() { + const offset = this.bb.__offset(this.bb_pos, 16); + return offset ? this.bb.readInt16(this.bb_pos + offset) : 0; + } + maybeI16() { + const offset = this.bb.__offset(this.bb_pos, 18); + return offset ? this.bb.readInt16(this.bb_pos + offset) : void 0; + } + defaultI16() { + const offset = this.bb.__offset(this.bb_pos, 20); + return offset ? this.bb.readInt16(this.bb_pos + offset) : 42; + } + justU16() { + const offset = this.bb.__offset(this.bb_pos, 22); + return offset ? this.bb.readUint16(this.bb_pos + offset) : 0; + } + maybeU16() { + const offset = this.bb.__offset(this.bb_pos, 24); + return offset ? this.bb.readUint16(this.bb_pos + offset) : void 0; + } + defaultU16() { + const offset = this.bb.__offset(this.bb_pos, 26); + return offset ? this.bb.readUint16(this.bb_pos + offset) : 42; + } + justI32() { + const offset = this.bb.__offset(this.bb_pos, 28); + return offset ? this.bb.readInt32(this.bb_pos + offset) : 0; + } + maybeI32() { + const offset = this.bb.__offset(this.bb_pos, 30); + return offset ? this.bb.readInt32(this.bb_pos + offset) : void 0; + } + defaultI32() { + const offset = this.bb.__offset(this.bb_pos, 32); + return offset ? this.bb.readInt32(this.bb_pos + offset) : 42; + } + justU32() { + const offset = this.bb.__offset(this.bb_pos, 34); + return offset ? this.bb.readUint32(this.bb_pos + offset) : 0; + } + maybeU32() { + const offset = this.bb.__offset(this.bb_pos, 36); + return offset ? this.bb.readUint32(this.bb_pos + offset) : void 0; + } + defaultU32() { + const offset = this.bb.__offset(this.bb_pos, 38); + return offset ? this.bb.readUint32(this.bb_pos + offset) : 42; + } + justI64() { + const offset = this.bb.__offset(this.bb_pos, 40); + return offset ? this.bb.readInt64(this.bb_pos + offset) : BigInt("0"); + } + maybeI64() { + const offset = this.bb.__offset(this.bb_pos, 42); + return offset ? this.bb.readInt64(this.bb_pos + offset) : void 0; + } + defaultI64() { + const offset = this.bb.__offset(this.bb_pos, 44); + return offset ? this.bb.readInt64(this.bb_pos + offset) : BigInt("42"); + } + justU64() { + const offset = this.bb.__offset(this.bb_pos, 46); + return offset ? this.bb.readUint64(this.bb_pos + offset) : BigInt("0"); + } + maybeU64() { + const offset = this.bb.__offset(this.bb_pos, 48); + return offset ? this.bb.readUint64(this.bb_pos + offset) : void 0; + } + defaultU64() { + const offset = this.bb.__offset(this.bb_pos, 50); + return offset ? this.bb.readUint64(this.bb_pos + offset) : BigInt("42"); + } + justF32() { + const offset = this.bb.__offset(this.bb_pos, 52); + return offset ? this.bb.readFloat32(this.bb_pos + offset) : 0; + } + maybeF32() { + const offset = this.bb.__offset(this.bb_pos, 54); + return offset ? this.bb.readFloat32(this.bb_pos + offset) : void 0; + } + defaultF32() { + const offset = this.bb.__offset(this.bb_pos, 56); + return offset ? this.bb.readFloat32(this.bb_pos + offset) : 42; + } + justF64() { + const offset = this.bb.__offset(this.bb_pos, 58); + return offset ? this.bb.readFloat64(this.bb_pos + offset) : 0; + } + maybeF64() { + const offset = this.bb.__offset(this.bb_pos, 60); + return offset ? this.bb.readFloat64(this.bb_pos + offset) : void 0; + } + defaultF64() { + const offset = this.bb.__offset(this.bb_pos, 62); + return offset ? this.bb.readFloat64(this.bb_pos + offset) : 42; + } + justBool() { + const offset = this.bb.__offset(this.bb_pos, 64); + return offset ? !!this.bb.readInt8(this.bb_pos + offset) : false; + } + maybeBool() { + const offset = this.bb.__offset(this.bb_pos, 66); + return offset ? !!this.bb.readInt8(this.bb_pos + offset) : void 0; + } + defaultBool() { + const offset = this.bb.__offset(this.bb_pos, 68); + return offset ? !!this.bb.readInt8(this.bb_pos + offset) : true; + } + justEnum() { + const offset = this.bb.__offset(this.bb_pos, 70); + return offset ? this.bb.readInt8(this.bb_pos + offset) : 0 /* None */; + } + maybeEnum() { + const offset = this.bb.__offset(this.bb_pos, 72); + return offset ? this.bb.readInt8(this.bb_pos + offset) : void 0; + } + defaultEnum() { + const offset = this.bb.__offset(this.bb_pos, 74); + return offset ? this.bb.readInt8(this.bb_pos + offset) : 1 /* One */; + } + static startScalarStuff(builder) { + builder.startObject(36); + } + static addJustI8(builder, justI8) { + builder.addFieldInt8(0, justI8, 0); + } + static addMaybeI8(builder, maybeI8) { + builder.addFieldInt8(1, maybeI8, void 0); + } + static addDefaultI8(builder, defaultI8) { + builder.addFieldInt8(2, defaultI8, 42); + } + static addJustU8(builder, justU8) { + builder.addFieldInt8(3, justU8, 0); + } + static addMaybeU8(builder, maybeU8) { + builder.addFieldInt8(4, maybeU8, void 0); + } + static addDefaultU8(builder, defaultU8) { + builder.addFieldInt8(5, defaultU8, 42); + } + static addJustI16(builder, justI16) { + builder.addFieldInt16(6, justI16, 0); + } + static addMaybeI16(builder, maybeI16) { + builder.addFieldInt16(7, maybeI16, void 0); + } + static addDefaultI16(builder, defaultI16) { + builder.addFieldInt16(8, defaultI16, 42); + } + static addJustU16(builder, justU16) { + builder.addFieldInt16(9, justU16, 0); + } + static addMaybeU16(builder, maybeU16) { + builder.addFieldInt16(10, maybeU16, void 0); + } + static addDefaultU16(builder, defaultU16) { + builder.addFieldInt16(11, defaultU16, 42); + } + static addJustI32(builder, justI32) { + builder.addFieldInt32(12, justI32, 0); + } + static addMaybeI32(builder, maybeI32) { + builder.addFieldInt32(13, maybeI32, void 0); + } + static addDefaultI32(builder, defaultI32) { + builder.addFieldInt32(14, defaultI32, 42); + } + static addJustU32(builder, justU32) { + builder.addFieldInt32(15, justU32, 0); + } + static addMaybeU32(builder, maybeU32) { + builder.addFieldInt32(16, maybeU32, void 0); + } + static addDefaultU32(builder, defaultU32) { + builder.addFieldInt32(17, defaultU32, 42); + } + static addJustI64(builder, justI64) { + builder.addFieldInt64(18, justI64, BigInt("0")); + } + static addMaybeI64(builder, maybeI64) { + builder.addFieldInt64(19, maybeI64, void 0); + } + static addDefaultI64(builder, defaultI64) { + builder.addFieldInt64(20, defaultI64, BigInt("42")); + } + static addJustU64(builder, justU64) { + builder.addFieldInt64(21, justU64, BigInt("0")); + } + static addMaybeU64(builder, maybeU64) { + builder.addFieldInt64(22, maybeU64, void 0); + } + static addDefaultU64(builder, defaultU64) { + builder.addFieldInt64(23, defaultU64, BigInt("42")); + } + static addJustF32(builder, justF32) { + builder.addFieldFloat32(24, justF32, 0); + } + static addMaybeF32(builder, maybeF32) { + builder.addFieldFloat32(25, maybeF32, void 0); + } + static addDefaultF32(builder, defaultF32) { + builder.addFieldFloat32(26, defaultF32, 42); + } + static addJustF64(builder, justF64) { + builder.addFieldFloat64(27, justF64, 0); + } + static addMaybeF64(builder, maybeF64) { + builder.addFieldFloat64(28, maybeF64, void 0); + } + static addDefaultF64(builder, defaultF64) { + builder.addFieldFloat64(29, defaultF64, 42); + } + static addJustBool(builder, justBool) { + builder.addFieldInt8(30, +justBool, 0); + } + static addMaybeBool(builder, maybeBool) { + builder.addFieldInt8(31, +maybeBool, void 0); + } + static addDefaultBool(builder, defaultBool) { + builder.addFieldInt8(32, +defaultBool, 1); + } + static addJustEnum(builder, justEnum) { + builder.addFieldInt8(33, justEnum, 0 /* None */); + } + static addMaybeEnum(builder, maybeEnum) { + builder.addFieldInt8(34, maybeEnum, void 0); + } + static addDefaultEnum(builder, defaultEnum) { + builder.addFieldInt8(35, defaultEnum, 1 /* One */); + } + static endScalarStuff(builder) { + const offset = builder.endObject(); + return offset; + } + static finishScalarStuffBuffer(builder, offset) { + builder.finish(offset, "NULL"); + } + static finishSizePrefixedScalarStuffBuffer(builder, offset) { + builder.finish(offset, "NULL", true); + } + static createScalarStuff(builder, justI8, maybeI8, defaultI8, justU8, maybeU8, defaultU8, justI16, maybeI16, defaultI16, justU16, maybeU16, defaultU16, justI32, maybeI32, defaultI32, justU32, maybeU32, defaultU32, justI64, maybeI64, defaultI64, justU64, maybeU64, defaultU64, justF32, maybeF32, defaultF32, justF64, maybeF64, defaultF64, justBool, maybeBool, defaultBool, justEnum, maybeEnum, defaultEnum) { + _ScalarStuff.startScalarStuff(builder); + _ScalarStuff.addJustI8(builder, justI8); + if (maybeI8 !== void 0) + _ScalarStuff.addMaybeI8(builder, maybeI8); + _ScalarStuff.addDefaultI8(builder, defaultI8); + _ScalarStuff.addJustU8(builder, justU8); + if (maybeU8 !== void 0) + _ScalarStuff.addMaybeU8(builder, maybeU8); + _ScalarStuff.addDefaultU8(builder, defaultU8); + _ScalarStuff.addJustI16(builder, justI16); + if (maybeI16 !== void 0) + _ScalarStuff.addMaybeI16(builder, maybeI16); + _ScalarStuff.addDefaultI16(builder, defaultI16); + _ScalarStuff.addJustU16(builder, justU16); + if (maybeU16 !== void 0) + _ScalarStuff.addMaybeU16(builder, maybeU16); + _ScalarStuff.addDefaultU16(builder, defaultU16); + _ScalarStuff.addJustI32(builder, justI32); + if (maybeI32 !== void 0) + _ScalarStuff.addMaybeI32(builder, maybeI32); + _ScalarStuff.addDefaultI32(builder, defaultI32); + _ScalarStuff.addJustU32(builder, justU32); + if (maybeU32 !== void 0) + _ScalarStuff.addMaybeU32(builder, maybeU32); + _ScalarStuff.addDefaultU32(builder, defaultU32); + _ScalarStuff.addJustI64(builder, justI64); + if (maybeI64 !== void 0) + _ScalarStuff.addMaybeI64(builder, maybeI64); + _ScalarStuff.addDefaultI64(builder, defaultI64); + _ScalarStuff.addJustU64(builder, justU64); + if (maybeU64 !== void 0) + _ScalarStuff.addMaybeU64(builder, maybeU64); + _ScalarStuff.addDefaultU64(builder, defaultU64); + _ScalarStuff.addJustF32(builder, justF32); + if (maybeF32 !== void 0) + _ScalarStuff.addMaybeF32(builder, maybeF32); + _ScalarStuff.addDefaultF32(builder, defaultF32); + _ScalarStuff.addJustF64(builder, justF64); + if (maybeF64 !== void 0) + _ScalarStuff.addMaybeF64(builder, maybeF64); + _ScalarStuff.addDefaultF64(builder, defaultF64); + _ScalarStuff.addJustBool(builder, justBool); + if (maybeBool !== void 0) + _ScalarStuff.addMaybeBool(builder, maybeBool); + _ScalarStuff.addDefaultBool(builder, defaultBool); + _ScalarStuff.addJustEnum(builder, justEnum); + if (maybeEnum !== void 0) + _ScalarStuff.addMaybeEnum(builder, maybeEnum); + _ScalarStuff.addDefaultEnum(builder, defaultEnum); + return _ScalarStuff.endScalarStuff(builder); + } + unpack() { + return new ScalarStuffT( + this.justI8(), + this.maybeI8(), + this.defaultI8(), + this.justU8(), + this.maybeU8(), + this.defaultU8(), + this.justI16(), + this.maybeI16(), + this.defaultI16(), + this.justU16(), + this.maybeU16(), + this.defaultU16(), + this.justI32(), + this.maybeI32(), + this.defaultI32(), + this.justU32(), + this.maybeU32(), + this.defaultU32(), + this.justI64(), + this.maybeI64(), + this.defaultI64(), + this.justU64(), + this.maybeU64(), + this.defaultU64(), + this.justF32(), + this.maybeF32(), + this.defaultF32(), + this.justF64(), + this.maybeF64(), + this.defaultF64(), + this.justBool(), + this.maybeBool(), + this.defaultBool(), + this.justEnum(), + this.maybeEnum(), + this.defaultEnum() + ); + } + unpackTo(_o) { + _o.justI8 = this.justI8(); + _o.maybeI8 = this.maybeI8(); + _o.defaultI8 = this.defaultI8(); + _o.justU8 = this.justU8(); + _o.maybeU8 = this.maybeU8(); + _o.defaultU8 = this.defaultU8(); + _o.justI16 = this.justI16(); + _o.maybeI16 = this.maybeI16(); + _o.defaultI16 = this.defaultI16(); + _o.justU16 = this.justU16(); + _o.maybeU16 = this.maybeU16(); + _o.defaultU16 = this.defaultU16(); + _o.justI32 = this.justI32(); + _o.maybeI32 = this.maybeI32(); + _o.defaultI32 = this.defaultI32(); + _o.justU32 = this.justU32(); + _o.maybeU32 = this.maybeU32(); + _o.defaultU32 = this.defaultU32(); + _o.justI64 = this.justI64(); + _o.maybeI64 = this.maybeI64(); + _o.defaultI64 = this.defaultI64(); + _o.justU64 = this.justU64(); + _o.maybeU64 = this.maybeU64(); + _o.defaultU64 = this.defaultU64(); + _o.justF32 = this.justF32(); + _o.maybeF32 = this.maybeF32(); + _o.defaultF32 = this.defaultF32(); + _o.justF64 = this.justF64(); + _o.maybeF64 = this.maybeF64(); + _o.defaultF64 = this.defaultF64(); + _o.justBool = this.justBool(); + _o.maybeBool = this.maybeBool(); + _o.defaultBool = this.defaultBool(); + _o.justEnum = this.justEnum(); + _o.maybeEnum = this.maybeEnum(); + _o.defaultEnum = this.defaultEnum(); + } +}; +var ScalarStuffT = class { + constructor(justI8 = 0, maybeI8 = void 0, defaultI8 = 42, justU8 = 0, maybeU8 = void 0, defaultU8 = 42, justI16 = 0, maybeI16 = void 0, defaultI16 = 42, justU16 = 0, maybeU16 = void 0, defaultU16 = 42, justI32 = 0, maybeI32 = void 0, defaultI32 = 42, justU32 = 0, maybeU32 = void 0, defaultU32 = 42, justI64 = BigInt("0"), maybeI64 = void 0, defaultI64 = BigInt("42"), justU64 = BigInt("0"), maybeU64 = void 0, defaultU64 = BigInt("42"), justF32 = 0, maybeF32 = void 0, defaultF32 = 42, justF64 = 0, maybeF64 = void 0, defaultF64 = 42, justBool = false, maybeBool = void 0, defaultBool = true, justEnum = 0 /* None */, maybeEnum = void 0, defaultEnum = 1 /* One */) { + this.justI8 = justI8; + this.maybeI8 = maybeI8; + this.defaultI8 = defaultI8; + this.justU8 = justU8; + this.maybeU8 = maybeU8; + this.defaultU8 = defaultU8; + this.justI16 = justI16; + this.maybeI16 = maybeI16; + this.defaultI16 = defaultI16; + this.justU16 = justU16; + this.maybeU16 = maybeU16; + this.defaultU16 = defaultU16; + this.justI32 = justI32; + this.maybeI32 = maybeI32; + this.defaultI32 = defaultI32; + this.justU32 = justU32; + this.maybeU32 = maybeU32; + this.defaultU32 = defaultU32; + this.justI64 = justI64; + this.maybeI64 = maybeI64; + this.defaultI64 = defaultI64; + this.justU64 = justU64; + this.maybeU64 = maybeU64; + this.defaultU64 = defaultU64; + this.justF32 = justF32; + this.maybeF32 = maybeF32; + this.defaultF32 = defaultF32; + this.justF64 = justF64; + this.maybeF64 = maybeF64; + this.defaultF64 = defaultF64; + this.justBool = justBool; + this.maybeBool = maybeBool; + this.defaultBool = defaultBool; + this.justEnum = justEnum; + this.maybeEnum = maybeEnum; + this.defaultEnum = defaultEnum; + } + pack(builder) { + return ScalarStuff.createScalarStuff( + builder, + this.justI8, + this.maybeI8, + this.defaultI8, + this.justU8, + this.maybeU8, + this.defaultU8, + this.justI16, + this.maybeI16, + this.defaultI16, + this.justU16, + this.maybeU16, + this.defaultU16, + this.justI32, + this.maybeI32, + this.defaultI32, + this.justU32, + this.maybeU32, + this.defaultU32, + this.justI64, + this.maybeI64, + this.defaultI64, + this.justU64, + this.maybeU64, + this.defaultU64, + this.justF32, + this.maybeF32, + this.defaultF32, + this.justF64, + this.maybeF64, + this.defaultF64, + this.justBool, + this.maybeBool, + this.defaultBool, + this.justEnum, + this.maybeEnum, + this.defaultEnum + ); + } +}; diff --git a/ts/builder.ts b/ts/builder.ts index 54a95ab04..25557727d 100644 --- a/ts/builder.ts +++ b/ts/builder.ts @@ -212,7 +212,7 @@ export class Builder { addFieldInt8( voffset: number, value: number, - defaultValue: number | null, + defaultValue: number | null | undefined, ): void { if (this.force_defaults || value != defaultValue) { this.addInt8(value); @@ -223,7 +223,7 @@ export class Builder { addFieldInt16( voffset: number, value: number, - defaultValue: number | null, + defaultValue: number | null | undefined, ): void { if (this.force_defaults || value != defaultValue) { this.addInt16(value); @@ -234,7 +234,7 @@ export class Builder { addFieldInt32( voffset: number, value: number, - defaultValue: number | null, + defaultValue: number | null | undefined, ): void { if (this.force_defaults || value != defaultValue) { this.addInt32(value); @@ -245,7 +245,7 @@ export class Builder { addFieldInt64( voffset: number, value: bigint, - defaultValue: bigint | null, + defaultValue: bigint | null | undefined, ): void { if (this.force_defaults || value !== defaultValue) { this.addInt64(value); @@ -256,7 +256,7 @@ export class Builder { addFieldFloat32( voffset: number, value: number, - defaultValue: number | null, + defaultValue: number | null | undefined, ): void { if (this.force_defaults || value != defaultValue) { this.addFloat32(value); @@ -267,7 +267,7 @@ export class Builder { addFieldFloat64( voffset: number, value: number, - defaultValue: number | null, + defaultValue: number | null | undefined, ): void { if (this.force_defaults || value != defaultValue) { this.addFloat64(value); @@ -614,8 +614,8 @@ export class Builder { * * @returns offset of obj */ - createObjectOffset(obj: string | IGeneratedObject | null): Offset { - if (obj === null) { + createObjectOffset(obj: string | IGeneratedObject | null | undefined): Offset { + if (obj === null || obj === undefined) { return 0; } @@ -629,7 +629,7 @@ export class Builder { /** * A helper function to pack a list of object * - * @returns list of offsets of each non null object + * @returns list of offsets of each non null/undefined object */ createObjectOffsetList(list: (string | IGeneratedObject)[]): Offset[] { const ret: number[] = []; @@ -637,11 +637,11 @@ export class Builder { for (let i = 0; i < list.length; ++i) { const val = list[i]; - if (val !== null) { + if (val !== null && val !== undefined) { ret.push(this.createObjectOffset(val)); } else { throw new TypeError( - 'FlatBuffers: Argument for createObjectOffsetList cannot contain null.', + 'FlatBuffers: Argument for createObjectOffsetList cannot contain null or undefined.', ); } } diff --git a/ts/flexbuffers.ts b/ts/flexbuffers.ts index e6d71c094..0fdc0955d 100644 --- a/ts/flexbuffers.ts +++ b/ts/flexbuffers.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-namespace */ import {Builder} from './flexbuffers/builder.js'; import {toReference} from './flexbuffers/reference.js'; export {toReference} from './flexbuffers/reference.js'; From b84b676c89c1df2526770d5fc158d0727e37c00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Wed, 4 Feb 2026 23:51:49 +1100 Subject: [PATCH 2/6] Fix example of JSON export with flatc (#8892) Binary files should be placed after "--". Also add a note about missing file_identifier and --raw-binary. --- docs/source/flatc.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/flatc.md b/docs/source/flatc.md index 330ebc3d9..598992e22 100644 --- a/docs/source/flatc.md +++ b/docs/source/flatc.md @@ -79,11 +79,12 @@ list of `FILES...`. `myschema.fbs` to JSON: ```sh - flatc --json myschema.fbs mydata.bin + flatc --json myschema.fbs -- mydata.bin ``` - This will generate a `mydata.json` file. - + This will generate a `mydata.json` file. If there is no + [`file_identifier`](schema.md/#file-identification-and-extension) defined + for this schema, you will need to use the `--raw-binary` option. ### Additional options From e53732b9b9842a8091de33a0b78fd7d8e464b31e Mon Sep 17 00:00:00 2001 From: Justin Davis Date: Wed, 4 Feb 2026 08:05:08 -0500 Subject: [PATCH 3/6] Feature: lua now file_ident aware (#8850) * lua code not file ident aware * update genned code * make mac happy * pr comments --- lua/flatbuffers/builder.lua | 47 ++++++++++++++++++++++++++++---- src/bfbs_gen_lua.cpp | 35 ++++++++++++++++++++++++ tests/MyGame/Example/Monster.lua | 10 +++++++ 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/lua/flatbuffers/builder.lua b/lua/flatbuffers/builder.lua index 25e0032a1..0f1ac79c0 100644 --- a/lua/flatbuffers/builder.lua +++ b/lua/flatbuffers/builder.lua @@ -296,25 +296,62 @@ function mt:Slot(slotnum) self.currentVTable[slotnum + 1] = self:Offset() end -local function finish(self, rootTable, sizePrefix) +local function finish(self, rootTable, sizePrefix, fileIdentifier) UOffsetT:EnforceNumber(rootTable) - self:Prep(self.minalign, sizePrefix and 8 or 4) - self:PrependUOffsetTRelative(rootTable) + local hasFid = fileIdentifier ~= nil + if hasFid then + assert(#fileIdentifier == 4, "File identifier must be exactly 4 bytes") + end + + local fid_byte_count = (hasFid and 4 or 0) + + -- alignment: + -- root offset (4) + -- optional file id (4) + -- optional size prefix (4) + self:Prep( + self.minalign, + (sizePrefix and 4 or 0) + + 4 + + fid_byte_count + ) + + -- root offset (points past file identifier if present) + self:PrependUOffsetTRelative(rootTable + fid_byte_count) + + -- file identifier + if hasFid then + for i = 4, 1, -1 do + self:PrependByte(string.byte(fileIdentifier, i)) + end + end + + -- size prefix if sizePrefix then local size = self.bytes.size - self.head Int32:EnforceNumber(size) self:PrependInt32(size) end + self.finished = true return self.head end + function mt:Finish(rootTable) - return finish(self, rootTable, false) + return finish(self, rootTable, false, nil) end function mt:FinishSizePrefixed(rootTable) - return finish(self, rootTable, true) + return finish(self, rootTable, true, nil) +end + +function mt:FinishWithIdentifier(rootTable, fileIdentifier) + return finish(self, rootTable, false, fileIdentifier) +end + +function mt:FinishSizePrefixedWithIdentifier(rootTable, fileIdentifier) + return finish(self, rootTable, true, fileIdentifier) end function mt:Prepend(flags, off) diff --git a/src/bfbs_gen_lua.cpp b/src/bfbs_gen_lua.cpp index 4507fe6d6..5e8def216 100644 --- a/src/bfbs_gen_lua.cpp +++ b/src/bfbs_gen_lua.cpp @@ -195,6 +195,13 @@ class LuaBfbsGenerator : public BaseBfbsGenerator { code += "\n"; if (object == root_object) { + // emit file identifier if present + const auto ident = schema_->file_ident(); + if (ident && ident->size() == 4) { + code += "local FileIdentifier = \"" + ident->str() + "\"\n"; + code += "\n"; + } + code += "function " + object_name + ".GetRootAs" + object_name + "(buf, offset)\n"; code += " if type(buf) == \"string\" then\n"; @@ -455,6 +462,34 @@ class LuaBfbsGenerator : public BaseBfbsGenerator { code += " return builder:EndObject()\n"; code += "end\n"; code += "\n"; + + if (object == root_object) { + code += "function " + object_name + ".Finish" + object_name + + "Buffer(builder, offset)\n"; + // emit file identifier if present + const auto ident = schema_->file_ident(); + if (ident && ident->size() == 4) { + code += " builder:FinishWithIdentifier(offset, FileIdentifier)\n"; + } else { + code += " builder:Finish(offset)\n"; + } + code += "end\n"; + code += "\n"; + + // size prefixed option + code += "function " + object_name + ".FinishSizePrefixed" + + object_name + "Buffer(builder, offset)\n"; + // emit file identifier if present + if (ident && ident->size() == 4) { + code += + " builder:FinishSizePrefixedWithIdentifier(offset, " + "FileIdentifier)\n"; + } else { + code += " builder:FinishSizePrefixed(offset)\n"; + } + code += "end\n"; + code += "\n"; + } } EmitCodeBlock(code, object_name, ns, object->declaration_file()->str()); diff --git a/tests/MyGame/Example/Monster.lua b/tests/MyGame/Example/Monster.lua index 851ae0300..e39d17280 100644 --- a/tests/MyGame/Example/Monster.lua +++ b/tests/MyGame/Example/Monster.lua @@ -28,6 +28,8 @@ function Monster.New() return o end +local FileIdentifier = "MONS" + function Monster.GetRootAsMonster(buf, offset) if type(buf) == "string" then buf = flatbuffers.binaryArray.New(buf) @@ -1099,4 +1101,12 @@ function Monster.End(builder) return builder:EndObject() end +function Monster.FinishMonsterBuffer(builder, offset) + builder:FinishWithIdentifier(offset, FileIdentifier) +end + +function Monster.FinishSizePrefixedMonsterBuffer(builder, offset) + builder:FinishSizePrefixedWithIdentifier(offset, FileIdentifier) +end + return Monster \ No newline at end of file From e5a9ff757f94ec6f3fb62c8f2c8a9618fc6d63e2 Mon Sep 17 00:00:00 2001 From: Markus Junginger Date: Wed, 4 Feb 2026 14:34:55 +0100 Subject: [PATCH 4/6] flatbuffers.h: fix C++11 compilation (#8857) (#8858) This constexpr officially works only with C++14 (assignment to lhs, 2 statements) and actually broke some C++11 compilers --- include/flatbuffers/flatbuffers.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/flatbuffers/flatbuffers.h b/include/flatbuffers/flatbuffers.h index 87b271e7c..0d9b5cced 100644 --- a/include/flatbuffers/flatbuffers.h +++ b/include/flatbuffers/flatbuffers.h @@ -260,15 +260,15 @@ inline const char* flatbuffers_version_string() { inline FLATBUFFERS_CONSTEXPR_CPP11 E operator ~ (E lhs){\ return E(~T(lhs));\ }\ - inline FLATBUFFERS_CONSTEXPR_CPP11 E operator |= (E &lhs, E rhs){\ + inline FLATBUFFERS_CONSTEXPR_CPP14 E operator |= (E &lhs, E rhs){\ lhs = lhs | rhs;\ return lhs;\ }\ - inline FLATBUFFERS_CONSTEXPR_CPP11 E operator &= (E &lhs, E rhs){\ + inline FLATBUFFERS_CONSTEXPR_CPP14 E operator &= (E &lhs, E rhs){\ lhs = lhs & rhs;\ return lhs;\ }\ - inline FLATBUFFERS_CONSTEXPR_CPP11 E operator ^= (E &lhs, E rhs){\ + inline FLATBUFFERS_CONSTEXPR_CPP14 E operator ^= (E &lhs, E rhs){\ lhs = lhs ^ rhs;\ return lhs;\ }\ From 429c28c783fe953e113c83c61118585bf539cf53 Mon Sep 17 00:00:00 2001 From: brianmacy Date: Wed, 4 Feb 2026 09:00:44 -0500 Subject: [PATCH 5/6] fix(rust): Zero vtable memory in write_vtable to prevent uninitialized data (#8898) The write_vtable() function's comment claimed to "fill the WIP vtable with zeros" but make_space() only reserves memory without initializing it. When using custom allocators with non-zeroed buffers, unset vtable field entries would contain garbage instead of zero (which indicates "use default value"). This fix explicitly zeros the vtable memory after reserving space, matching the C++ implementation's buf_.fill_big() behavior. Added regression test using a garbage-filled allocator (0xAA) that verifies vtable entries for unset fields are properly zeroed. Fixes #8894 --- rust/flatbuffers/src/builder.rs | 2 + .../tests/vtable_zeroed_test.rs | 164 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 tests/rust_usage_test/tests/vtable_zeroed_test.rs diff --git a/rust/flatbuffers/src/builder.rs b/rust/flatbuffers/src/builder.rs index d8a6e81dc..84bb62258 100644 --- a/rust/flatbuffers/src/builder.rs +++ b/rust/flatbuffers/src/builder.rs @@ -611,6 +611,8 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> { // Write the VTable (we may delete it afterwards, if it is a duplicate): let vt_start_pos = self.head; let vt_end_pos = self.head + vtable_byte_len; + // Zero out the vtable space - make_space only reserves but doesn't initialize + self.allocator[vt_start_pos.range_to(vt_end_pos)].fill(0); { // write the vtable header: let vtfw = diff --git a/tests/rust_usage_test/tests/vtable_zeroed_test.rs b/tests/rust_usage_test/tests/vtable_zeroed_test.rs new file mode 100644 index 000000000..401bc2fcf --- /dev/null +++ b/tests/rust_usage_test/tests/vtable_zeroed_test.rs @@ -0,0 +1,164 @@ +/* + * Copyright 2024 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. + */ + +//! Regression test for https://github.com/google/flatbuffers/issues/8894 +//! +//! Tests that vtable memory is properly zeroed when building FlatBuffers. + +extern crate alloc; +extern crate flatbuffers; + +use alloc::vec; +use alloc::vec::Vec; +use core::convert::Infallible; +use core::cmp::max; +use core::ops::{Deref, DerefMut}; +use core::ptr::write_bytes; + +use flatbuffers::{Allocator, FlatBufferBuilder}; + +/// Custom allocator that pre-fills buffer with garbage (0xAA) to detect +/// uninitialized memory bugs. +struct GarbageFilledAllocator(Vec); + +impl GarbageFilledAllocator { + fn new(size: usize) -> Self { + Self(vec![0xAA; size]) + } +} + +impl Deref for GarbageFilledAllocator { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for GarbageFilledAllocator { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +// SAFETY: grow_downwards properly moves data and the new space is filled with garbage +// (intentionally, to detect bugs where code assumes zeroed memory) +unsafe impl Allocator for GarbageFilledAllocator { + type Error = Infallible; + + fn grow_downwards(&mut self) -> Result<(), Self::Error> { + let old_len = self.0.len(); + let new_len = max(1, old_len * 2); + + // Resize and fill new space with garbage + self.0.resize(new_len, 0xAA); + + if new_len == 1 { + return Ok(()); + } + + // Move old data to the end + let middle = new_len / 2; + { + let (left, right) = &mut self.0[..].split_at_mut(middle); + right.copy_from_slice(left); + } + // Fill old space with garbage (NOT zeros) + { + let ptr = self.0[..middle].as_mut_ptr(); + unsafe { + write_bytes(ptr, 0xAA, middle); + } + } + Ok(()) + } + + fn len(&self) -> usize { + self.0.len() + } +} + +/// Regression test for https://github.com/google/flatbuffers/issues/8894 +/// +/// The bug: write_vtable() called make_space() which only reserves memory +/// but doesn't zero it. If the allocator's buffer contains garbage data, +/// vtable entries for fields with default values would contain garbage +/// instead of zero (which indicates "use default"). +/// +/// This test uses a garbage-filled allocator to detect if vtable memory +/// is properly zeroed before being written. +#[test] +fn test_vtable_zeroed_with_garbage_allocator() { + // Create a builder with garbage-filled allocator + let allocator = GarbageFilledAllocator::new(256); + let mut builder: FlatBufferBuilder = + FlatBufferBuilder::new_in(allocator); + + // Start a table + let table_start = builder.start_table(); + + // Set a field at a HIGH slot ID (14) to force a larger vtable. + // This leaves slots 4, 6, 8, 10, 12 unset (should be zero). + // VTable layout: [vtable_size:2][table_size:2][field0:2][field1:2][field2:2][field3:2][field4:2][field5:2] + // Offsets: 0 2 4 6 8 10 12 14 + builder.push_slot::(14, 42, 0); // Set field 5 (at vtable offset 14) to 42 + + // End the table - this calls write_vtable() + let table_end = builder.end_table(table_start); + + // Finish the buffer + builder.finish(table_end, None); + + let data = builder.finished_data(); + + // Read the root table offset (first 4 bytes, little-endian) + let root_offset = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize; + let table_pos = root_offset; + + // Read the vtable offset (signed, at table position) + let vtable_offset = i32::from_le_bytes([ + data[table_pos], + data[table_pos + 1], + data[table_pos + 2], + data[table_pos + 3], + ]); + let vtable_pos = (table_pos as i32 - vtable_offset) as usize; + + // Read vtable size (first 2 bytes of vtable) + let vtable_size = u16::from_le_bytes([data[vtable_pos], data[vtable_pos + 1]]) as usize; + + // Verify vtable structure is as expected (16 bytes total for 6 fields + header) + assert_eq!( + vtable_size, 16, + "VTable should be 16 bytes (4 header + 6*2 fields)" + ); + + // Check that unset fields (at offsets 4, 6, 8, 10, 12) are zero. + // Only field at offset 14 was set. + // If the bug exists, unset fields would be 0xAAAA instead of 0. + for i in [4_usize, 6, 8, 10, 12] { + let field_offset = u16::from_le_bytes([data[vtable_pos + i], data[vtable_pos + i + 1]]); + assert_eq!( + field_offset, 0, + "Vtable entry at offset {} should be 0 (default), but was 0x{:04X}. \ + This indicates uninitialized vtable memory (issue #8894).", + i, field_offset + ); + } + + // Verify the field we DID set has a non-zero offset + let field5_offset = u16::from_le_bytes([data[vtable_pos + 14], data[vtable_pos + 14 + 1]]); + assert_ne!(field5_offset, 0, "Field 5 should have a non-zero offset"); +} From 9c2c56dc6ae24ece6c684409d9a486703408938a Mon Sep 17 00:00:00 2001 From: Peter Shih Date: Wed, 4 Feb 2026 22:12:50 +0800 Subject: [PATCH 6/6] Fix typo in comment in idl.h (#8907) Co-authored-by: Justin Davis --- include/flatbuffers/idl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 5a63fa75a..06f93f338 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -223,7 +223,7 @@ struct Type { uint16_t fixed_length; // only set if t == BASE_TYPE_ARRAY }; -// Represents a parsed scalar value, it's type, and field offset. +// Represents a parsed scalar value, its type, and field offset. struct Value { Value() : constant("0"),