diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp index a4a734588ca..cdd4d8e4a37 100644 --- a/src/idl_gen_go.cpp +++ b/src/idl_gen_go.cpp @@ -417,6 +417,57 @@ class GoGenerator : public BaseGenerator { code += "\treturn nil\n}\n\n"; } + // Get an element of a fixed-size scalar array within a struct. + void GetScalarArrayFieldOfStruct(const StructDef& struct_def, + const FieldDef& field, + std::string* code_ptr) { + std::string& code = *code_ptr; + auto vectortype = field.value.type.VectorType(); + std::string getter = + "rcv._tab.Get" + namer_.Function(GenTypeBasic(vectortype)); + GenReceiver(struct_def, code_ptr); + code += " " + namer_.Function(field); + code += "(j int) " + GenTypeGet(vectortype) + " {\n"; + code += "\treturn " + + CastToEnum(vectortype, + getter + "(rcv._tab.Pos + flatbuffers.UOffsetT(" + + NumToString(field.value.offset) + "+j*" + + NumToString(InlineSize(vectortype)) + "))"); + code += "\n}\n"; + } + + // Get the length of a fixed-size array field. + void GetArrayFieldLength(const StructDef& struct_def, const FieldDef& field, + std::string* code_ptr) { + std::string& code = *code_ptr; + GenReceiver(struct_def, code_ptr); + code += " " + namer_.Function(field) + "Length("; + code += ") int {\n"; + code += "\treturn " + NumToString(field.value.type.fixed_length) + "\n"; + code += "}\n\n"; + } + + // Get an element of a fixed-size struct array within a struct. + void GetStructArrayFieldOfStruct(const StructDef& struct_def, + const FieldDef& field, + std::string* code_ptr) { + std::string& code = *code_ptr; + auto vectortype = field.value.type.VectorType(); + GenReceiver(struct_def, code_ptr); + code += " " + namer_.Function(field); + code += "(obj *" + TypeName(field); + code += ", j int) *" + TypeName(field); + code += " {\n"; + code += "\tif obj == nil {\n"; + code += "\t\tobj = new(" + TypeName(field) + ")\n"; + code += "\t}\n"; + code += "\tobj.Init(rcv._tab.Bytes, rcv._tab.Pos+flatbuffers.UOffsetT("; + code += NumToString(field.value.offset) + "+j*"; + code += NumToString(InlineSize(vectortype)) + "))"; + code += "\n\treturn obj\n"; + code += "}\n"; + } + // Get the value of a struct's scalar. void GetScalarFieldOfStruct(const StructDef& struct_def, const FieldDef& field, std::string* code_ptr) { @@ -616,9 +667,9 @@ class GoGenerator : public BaseGenerator { } // Recursively generate arguments for a constructor, to deal with nested - // structs. + // structs. `array_depth` tracks the nesting of struct arrays. void StructBuilderArgs(const StructDef& struct_def, const char* nameprefix, - std::string* code_ptr) { + std::string* code_ptr, int array_depth = 0) { for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { auto& field = **it; @@ -627,12 +678,32 @@ class GoGenerator : public BaseGenerator { // don't clash, and to make it obvious these arguments are constructing // a nested struct, prefix the name with the field name. StructBuilderArgs(*field.value.type.struct_def, - (nameprefix + (field.name + "_")).c_str(), code_ptr); + (nameprefix + (field.name + "_")).c_str(), code_ptr, + array_depth); + } else if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + if (IsStruct(vectortype)) { + // Flatten struct array fields with increased depth. + StructBuilderArgs(*vectortype.struct_def, + (nameprefix + (field.name + "_")).c_str(), code_ptr, + array_depth + 1); + } else { + std::string& code = *code_ptr; + code += std::string(", ") + nameprefix; + code += namer_.Variable(field); + // Build slice type with correct dimensionality. + std::string slice_prefix; + for (int i = 0; i < array_depth + 1; i++) slice_prefix += "[]"; + code += " " + slice_prefix + GenTypeGet(vectortype); + } } else { std::string& code = *code_ptr; code += std::string(", ") + nameprefix; code += namer_.Variable(field); - code += " " + TypeName(field); + // Scalar fields inside struct arrays become slices. + std::string slice_prefix; + for (int i = 0; i < array_depth; i++) slice_prefix += "[]"; + code += " " + slice_prefix + GenTypeGet(field.value.type); } } } @@ -643,7 +714,7 @@ class GoGenerator : public BaseGenerator { code += ") flatbuffers.UOffsetT {\n"; } - // Recursively generate struct construction statements and instert manual + // Recursively generate struct construction statements and insert manual // padding. void StructBuilderBody(const StructDef& struct_def, const char* nameprefix, std::string* code_ptr) { @@ -658,6 +729,25 @@ class GoGenerator : public BaseGenerator { if (IsStruct(field.value.type)) { StructBuilderBody(*field.value.type.struct_def, (nameprefix + (field.name + "_")).c_str(), code_ptr); + } else if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + auto len = NumToString(field.value.type.fixed_length); + if (IsStruct(vectortype)) { + code += "\tfor _idx0 := " + len + " - 1; _idx0 >= 0; _idx0-- {\n"; + StructBuilderBodyArray(*vectortype.struct_def, + (nameprefix + (field.name + "_")).c_str(), + code_ptr); + code += "\t}\n"; + } else { + code += "\tfor _idx0 := " + len + " - 1; _idx0 >= 0; _idx0-- {\n"; + code += "\t\tbuilder.Prepend" + + namer_.Method(GenTypeBasic(vectortype)) + "("; + code += CastToBaseType( + vectortype, + nameprefix + namer_.Variable(field) + "[_idx0]") + + ")\n"; + code += "\t}\n"; + } } else { code += "\tbuilder.Prepend" + GenMethod(field) + "("; code += CastToBaseType(field.value.type, @@ -667,6 +757,85 @@ class GoGenerator : public BaseGenerator { } } + // Generate the body of a struct array element write (used inside a loop). + void StructBuilderBodyArray(const StructDef& struct_def, + const char* nameprefix, + std::string* code_ptr) { + std::string& code = *code_ptr; + code += "\t\tbuilder.Prep(" + NumToString(struct_def.minalign) + ", "; + code += NumToString(struct_def.bytesize) + ")\n"; + for (auto it = struct_def.fields.vec.rbegin(); + it != struct_def.fields.vec.rend(); ++it) { + auto& field = **it; + if (field.padding) + code += "\t\tbuilder.Pad(" + NumToString(field.padding) + ")\n"; + if (IsStruct(field.value.type)) { + // Nested struct within array element: use indexed arg. + StructBuilderBodyArrayNested(*field.value.type.struct_def, + (nameprefix + (field.name + "_")).c_str(), + code_ptr); + } else if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + auto len = NumToString(field.value.type.fixed_length); + code += "\t\tfor _idx1 := " + len + " - 1; _idx1 >= 0; _idx1-- {\n"; + code += "\t\t\tbuilder.Prepend" + + namer_.Method(GenTypeBasic(vectortype)) + "("; + code += CastToBaseType( + vectortype, + nameprefix + namer_.Variable(field) + "[_idx0][_idx1]") + + ")\n"; + code += "\t\t}\n"; + } else { + code += "\t\tbuilder.Prepend" + + namer_.Method(GenTypeBasic(field.value.type)) + "("; + code += CastToBaseType(field.value.type, + nameprefix + namer_.Variable(field) + + "[_idx0]") + + ")\n"; + } + } + } + + // Like StructBuilderBodyArray but for nested structs within struct array + // elements — uses [_idx0] indexing for all leaf values. + void StructBuilderBodyArrayNested(const StructDef& struct_def, + const char* nameprefix, + std::string* code_ptr) { + std::string& code = *code_ptr; + code += "\t\tbuilder.Prep(" + NumToString(struct_def.minalign) + ", "; + code += NumToString(struct_def.bytesize) + ")\n"; + for (auto it = struct_def.fields.vec.rbegin(); + it != struct_def.fields.vec.rend(); ++it) { + auto& field = **it; + if (field.padding) + code += "\t\tbuilder.Pad(" + NumToString(field.padding) + ")\n"; + if (IsStruct(field.value.type)) { + // Recursively handle nested struct within the array element. + StructBuilderBodyArrayNested(*field.value.type.struct_def, + (nameprefix + (field.name + "_")).c_str(), + code_ptr); + } else if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + auto len = NumToString(field.value.type.fixed_length); + code += "\t\tfor _idx1 := " + len + " - 1; _idx1 >= 0; _idx1-- {\n"; + code += "\t\t\tbuilder.Prepend" + + namer_.Method(GenTypeBasic(vectortype)) + "("; + code += CastToBaseType( + vectortype, + nameprefix + namer_.Variable(field) + "[_idx0][_idx1]") + + ")\n"; + code += "\t\t}\n"; + } else { + code += "\t\tbuilder.Prepend" + + namer_.Method(GenTypeBasic(field.value.type)) + "("; + code += CastToBaseType(field.value.type, + nameprefix + namer_.Variable(field) + + "[_idx0]") + + ")\n"; + } + } + } + void EndBuilderBody(std::string* code_ptr) { std::string& code = *code_ptr; code += "\treturn builder.Offset()\n"; @@ -787,6 +956,16 @@ class GoGenerator : public BaseGenerator { } break; } + case BASE_TYPE_ARRAY: { + auto vectortype = field.value.type.VectorType(); + if (IsStruct(vectortype)) { + GetStructArrayFieldOfStruct(struct_def, field, code_ptr); + } else { + GetScalarArrayFieldOfStruct(struct_def, field, code_ptr); + } + GetArrayFieldLength(struct_def, field, code_ptr); + break; + } case BASE_TYPE_UNION: GetUnionField(struct_def, field, code_ptr); break; @@ -802,6 +981,23 @@ class GoGenerator : public BaseGenerator { } } + // Mutate an element of a fixed-size scalar array within a struct. + void MutateScalarArrayFieldOfStruct(const StructDef& struct_def, + const FieldDef& field, + std::string* code_ptr) { + std::string& code = *code_ptr; + auto vectortype = field.value.type.VectorType(); + std::string setter = + "rcv._tab.Mutate" + namer_.Method(GenTypeBasic(vectortype)); + GenReceiver(struct_def, code_ptr); + code += " Mutate" + namer_.Function(field); + code += "(j int, n " + GenTypeGet(vectortype) + ") bool {\n"; + code += "\treturn " + setter + "(rcv._tab.Pos+flatbuffers.UOffsetT("; + code += NumToString(field.value.offset) + "+j*" + + NumToString(InlineSize(vectortype)) + "), "; + code += CastToBaseType(vectortype, "n") + ")\n}\n\n"; + } + // Mutate the value of a struct's scalar. void MutateScalarFieldOfStruct(const StructDef& struct_def, const FieldDef& field, std::string* code_ptr) { @@ -863,6 +1059,10 @@ class GoGenerator : public BaseGenerator { } else { MutateScalarFieldOfTable(struct_def, field, code_ptr); } + } else if (IsArray(field.value.type)) { + if (IsScalar(field.value.type.element)) { + MutateScalarArrayFieldOfStruct(struct_def, field, code_ptr); + } } else if (IsVector(field.value.type)) { if (IsScalar(field.value.type.element)) { MutateElementOfVectorOfNonStruct(struct_def, field, code_ptr); @@ -1317,12 +1517,76 @@ class GoGenerator : public BaseGenerator { code += "func (t *" + NativeName(struct_def) + ") Pack(builder *flatbuffers.Builder) flatbuffers.UOffsetT {\n"; code += "\tif t == nil {\n\t\treturn 0\n\t}\n"; + // Pre-extract struct array fields into temporary slices. + GenStructArrayPackVars(struct_def, "", code_ptr); code += "\treturn Create" + namer_.Type(struct_def) + "(builder"; StructPackArgs(struct_def, "", code_ptr); code += ")\n"; code += "}\n"; } + // Generate temporary variables for struct array fields in Pack. + // For a field like d:[NestedStruct:2] where NestedStruct has a:[int:2], + // b:TestEnum, etc., we generate: + // _d_a := make([][]int32, 2) + // for i := range _d_a { _d_a[i] = t.D[i].A[:] } + void GenStructArrayPackVars(const StructDef& struct_def, + const std::string& prefix, + std::string* code_ptr) { + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + const FieldDef& field = **it; + if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + if (IsStruct(vectortype)) { + auto len = NumToString(field.value.type.fixed_length); + auto field_name = namer_.Field(field); + auto access_prefix = prefix.empty() + ? std::string("t.") + field_name + : prefix + "." + field_name; + auto var_prefix = "_" + namer_.Variable(field) + "_"; + GenStructArrayPackVarsInner(*vectortype.struct_def, var_prefix, + access_prefix, len, code_ptr); + } + } + } + } + + // Generate temp slice vars for each field of the struct array element. + void GenStructArrayPackVarsInner(const StructDef& struct_def, + const std::string& var_prefix, + const std::string& access_prefix, + const std::string& outer_len, + std::string* code_ptr) { + std::string& code = *code_ptr; + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + const FieldDef& field = **it; + auto field_name = namer_.Field(field); + auto var_name = var_prefix + namer_.Variable(field); + if (IsStruct(field.value.type)) { + // Nested plain struct inside array element: recurse with chained access. + GenStructArrayPackVarsInner( + *field.value.type.struct_def, var_prefix + namer_.Variable(field) + "_", + access_prefix + "[_i]." + field_name, outer_len, code_ptr); + } else if (IsArray(field.value.type)) { + // Array inside struct array: produces a 2D slice. + auto inner_type = field.value.type.VectorType(); + code += "\t" + var_name + " := make([][]" + GenTypeGet(inner_type) + + ", " + outer_len + ")\n"; + code += "\tfor _i := range " + var_name + " { " + var_name + + "[_i] = " + access_prefix + "[_i]." + field_name + "[:] }\n"; + } else { + // Scalar inside struct array: produces a 1D slice. + code += "\t" + var_name + " := make([]" + + GenTypeGet(field.value.type) + ", " + outer_len + ")\n"; + code += "\tfor _i := range " + var_name + " { " + var_name + + "[_i] = " + access_prefix + "[_i]." + field_name + " }\n"; + } + } + } + + // Generate arguments passed to the Create function from the Pack method. void StructPackArgs(const StructDef& struct_def, const char* nameprefix, std::string* code_ptr) { std::string& code = *code_ptr; @@ -1333,12 +1597,41 @@ class GoGenerator : public BaseGenerator { StructPackArgs(*field.value.type.struct_def, (nameprefix + namer_.Field(field) + ".").c_str(), code_ptr); + } else if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + if (IsStruct(vectortype)) { + // For struct arrays, pass the pre-extracted temp variables. + StructPackArgsStructArray(*vectortype.struct_def, + ("_" + namer_.Variable(field) + "_").c_str(), + code_ptr); + } else { + code += std::string(", t.") + nameprefix + namer_.Field(field) + + "[:]"; + } } else { code += std::string(", t.") + nameprefix + namer_.Field(field); } } } + // Pass pre-extracted temp slice vars as Create args for a struct array. + void StructPackArgsStructArray(const StructDef& struct_def, + const char* var_prefix, + std::string* code_ptr) { + std::string& code = *code_ptr; + for (auto it = struct_def.fields.vec.begin(); + it != struct_def.fields.vec.end(); ++it) { + const FieldDef& field = **it; + if (IsStruct(field.value.type)) { + StructPackArgsStructArray( + *field.value.type.struct_def, + (var_prefix + namer_.Variable(field) + "_").c_str(), code_ptr); + } else { + code += std::string(", ") + var_prefix + namer_.Variable(field); + } + } + } + void GenNativeStructUnPack(const StructDef& struct_def, std::string* code_ptr) { std::string& code = *code_ptr; @@ -1351,6 +1644,18 @@ class GoGenerator : public BaseGenerator { if (field.value.type.base_type == BASE_TYPE_STRUCT) { code += "\tt." + namer_.Field(field) + " = rcv." + namer_.Method(field) + "(nil).UnPack()\n"; + } else if (IsArray(field.value.type)) { + auto vectortype = field.value.type.VectorType(); + auto len = NumToString(field.value.type.fixed_length); + code += "\tfor _j := 0; _j < " + len + "; _j++ {\n"; + if (IsStruct(vectortype)) { + code += "\t\tt." + namer_.Field(field) + "[_j] = rcv." + + namer_.Method(field) + "(nil, _j).UnPack()\n"; + } else { + code += "\t\tt." + namer_.Field(field) + "[_j] = rcv." + + namer_.Method(field) + "(_j)\n"; + } + code += "\t}\n"; } else { code += "\tt." + namer_.Field(field) + " = rcv." + namer_.Method(field) + "()\n"; @@ -1453,6 +1758,7 @@ class GoGenerator : public BaseGenerator { if (type.enum_def != nullptr) { return GetEnumTypeName(*type.enum_def); } + if (IsArray(type)) { return GenTypeGet(type.VectorType()); } return IsScalar(type.base_type) ? GenTypeBasic(type) : GenTypePointer(type); } @@ -1530,6 +1836,15 @@ class GoGenerator : public BaseGenerator { } } else if (IsString(type)) { return "string"; + } else if (IsArray(type)) { + auto vectortype = type.VectorType(); + if (IsStruct(vectortype)) { + return "[" + NumToString(type.fixed_length) + "]*" + + WrapInNameSpaceAndTrack(vectortype.struct_def, + NativeName(*vectortype.struct_def)); + } + return "[" + NumToString(type.fixed_length) + "]" + + NativeType(vectortype); } else if (IsVector(type)) { return "[]" + NativeType(type.VectorType()); } else if (type.base_type == BASE_TYPE_STRUCT) { diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index fe2878a9612..5f5614e4556 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -2827,7 +2827,7 @@ bool Parser::SupportsAdvancedArrayFeatures() const { ~(IDLOptions::kCpp | IDLOptions::kPython | IDLOptions::kJava | IDLOptions::kCSharp | IDLOptions::kJsonSchema | IDLOptions::kJson | IDLOptions::kBinary | IDLOptions::kRust | IDLOptions::kTs | - IDLOptions::kSwift)) == 0; + IDLOptions::kSwift | IDLOptions::kGo)) == 0; } bool Parser::Supports64BitOffsets() const { diff --git a/tests/GoArraysTest.sh b/tests/GoArraysTest.sh new file mode 100755 index 00000000000..390a6f21659 --- /dev/null +++ b/tests/GoArraysTest.sh @@ -0,0 +1,46 @@ +#!/bin/bash -eu +# +# Copyright 2025 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. + +# Test script for Go fixed-size array support. +# Generates Go code from arrays_test.fbs, compiles, and runs tests. + +pushd "$(dirname $0)" >/dev/null +test_dir="$(pwd)" +go_path=${test_dir}/go_arrays_gen +go_src=${go_path}/src + +# Generate Go code from the arrays test schema. +../build/flatc -g --gen-object-api -o ${go_src} arrays_test.fbs + +# Set up the GOPATH layout that Go expects. +mkdir -p ${go_src}/github.com/google/flatbuffers/go +mkdir -p ${go_src}/flatbuffers_arrays_test + +cp -a ../go/* ${go_src}/github.com/google/flatbuffers/go/ +cp -a go_arrays_test_test.go ${go_src}/flatbuffers_arrays_test/ + +GO111MODULE=off GOPATH=${go_path} go test flatbuffers_arrays_test -v + +GO_TEST_RESULT=$? +rm -rf ${go_path} +if [[ $GO_TEST_RESULT == 0 ]]; then + echo "OK: Go fixed-size array tests passed." +else + echo "KO: Go fixed-size array tests failed." + exit 1 +fi + +popd >/dev/null diff --git a/tests/go_arrays_test_test.go b/tests/go_arrays_test_test.go new file mode 100644 index 00000000000..e2319a9dbd7 --- /dev/null +++ b/tests/go_arrays_test_test.go @@ -0,0 +1,283 @@ +// Test for Go fixed-size array support in FlatBuffers. +// +// Validates that structs with fixed-size array fields can be correctly +// built, read, mutated, and round-tripped through the object API. + +package main + +import ( + "testing" + + example "MyGame/Example" + + flatbuffers "github.com/google/flatbuffers/go" +) + +// buildArrayTable creates a FlatBuffer containing an ArrayTable with known +// values, returning the finished byte slice. +func buildArrayTable(t *testing.T) []byte { + t.Helper() + builder := flatbuffers.NewBuilder(512) + + // NestedStruct field values for d[0] and d[1]. + d_a := [][]int32{{1, 2}, {3, 4}} + d_b := []example.TestEnum{example.TestEnumA, example.TestEnumB} + d_c := [][]example.TestEnum{ + {example.TestEnumB, example.TestEnumC}, + {example.TestEnumA, example.TestEnumC}, + } + d_d := [][]int64{{100, 200}, {300, 400}} + + b := make([]int32, 15) + for i := range b { + b[i] = int32(i + 1) + } + f := []int64{-1, -2} + + off := example.CreateArrayStruct(builder, + 1.5, // a + b, // b: [int:15] + 42, // c + d_a, d_b, d_c, d_d, // d: [NestedStruct:2] + 99, // e + f, // f: [int64:2] + ) + + example.ArrayTableStart(builder) + example.ArrayTableAddA(builder, off) + root := example.ArrayTableEnd(builder) + builder.Finish(root) + + return builder.FinishedBytes() +} + +func TestArrayStructRead(t *testing.T) { + buf := buildArrayTable(t) + table := example.GetRootAsArrayTable(buf, 0) + + s := new(example.ArrayStruct) + table.A(s) + + // Scalar field. + if got := s.A(); got != 1.5 { + t.Fatalf("A: got %v, want 1.5", got) + } + + // Scalar array field. + if s.BLength() != 15 { + t.Fatalf("BLength: got %d, want 15", s.BLength()) + } + for i := 0; i < 15; i++ { + want := int32(i + 1) + if got := s.B(i); got != want { + t.Fatalf("B(%d): got %d, want %d", i, got, want) + } + } + + if got := s.C(); got != 42 { + t.Fatalf("C: got %d, want 42", got) + } + + // Struct array field. + if s.DLength() != 2 { + t.Fatalf("DLength: got %d, want 2", s.DLength()) + } + + ns0 := s.D(nil, 0) + if ns0.A(0) != 1 || ns0.A(1) != 2 { + t.Fatalf("D(0).A: got [%d,%d], want [1,2]", ns0.A(0), ns0.A(1)) + } + if ns0.B() != example.TestEnumA { + t.Fatalf("D(0).B: got %d, want %d", ns0.B(), example.TestEnumA) + } + if ns0.C(0) != example.TestEnumB || ns0.C(1) != example.TestEnumC { + t.Fatalf("D(0).C: got [%d,%d], want [%d,%d]", + ns0.C(0), ns0.C(1), example.TestEnumB, example.TestEnumC) + } + if ns0.D(0) != 100 || ns0.D(1) != 200 { + t.Fatalf("D(0).D: got [%d,%d], want [100,200]", ns0.D(0), ns0.D(1)) + } + + ns1 := s.D(nil, 1) + if ns1.A(0) != 3 || ns1.A(1) != 4 { + t.Fatalf("D(1).A: got [%d,%d], want [3,4]", ns1.A(0), ns1.A(1)) + } + if ns1.B() != example.TestEnumB { + t.Fatalf("D(1).B: got %d, want %d", ns1.B(), example.TestEnumB) + } + if ns1.D(0) != 300 || ns1.D(1) != 400 { + t.Fatalf("D(1).D: got [%d,%d], want [300,400]", ns1.D(0), ns1.D(1)) + } + + if got := s.E(); got != 99 { + t.Fatalf("E: got %d, want 99", got) + } + + // Int64 array. + if s.FLength() != 2 { + t.Fatalf("FLength: got %d, want 2", s.FLength()) + } + if s.F(0) != -1 || s.F(1) != -2 { + t.Fatalf("F: got [%d,%d], want [-1,-2]", s.F(0), s.F(1)) + } +} + +func TestArrayStructMutate(t *testing.T) { + buf := buildArrayTable(t) + table := example.GetRootAsArrayTable(buf, 0) + + s := new(example.ArrayStruct) + table.A(s) + + // Mutate scalar array elements. + if !s.MutateB(0, 777) { + t.Fatal("MutateB returned false") + } + if got := s.B(0); got != 777 { + t.Fatalf("after MutateB: got %d, want 777", got) + } + + // Mutate nested struct scalar array. + ns := s.D(nil, 0) + if !ns.MutateA(1, 999) { + t.Fatal("MutateA returned false") + } + if got := ns.A(1); got != 999 { + t.Fatalf("after MutateA: got %d, want 999", got) + } + + // Mutate int64 array. + if !s.MutateF(1, 12345) { + t.Fatal("MutateF returned false") + } + if got := s.F(1); got != 12345 { + t.Fatalf("after MutateF: got %d, want 12345", got) + } +} + +func TestArrayStructObjectAPI(t *testing.T) { + buf := buildArrayTable(t) + table := example.GetRootAsArrayTable(buf, 0) + + s := new(example.ArrayStruct) + table.A(s) + + // Unpack to native struct. + native := s.UnPack() + if native == nil { + t.Fatal("UnPack returned nil") + } + + if native.A != 1.5 { + t.Fatalf("native.A: got %v, want 1.5", native.A) + } + for i := 0; i < 15; i++ { + want := int32(i + 1) + if native.B[i] != want { + t.Fatalf("native.B[%d]: got %d, want %d", i, native.B[i], want) + } + } + if native.C != 42 { + t.Fatalf("native.C: got %d, want 42", native.C) + } + if native.D[0] == nil || native.D[1] == nil { + t.Fatal("native.D elements should not be nil") + } + if native.D[0].A != [2]int32{1, 2} { + t.Fatalf("native.D[0].A: got %v, want [1,2]", native.D[0].A) + } + if native.D[1].D != [2]int64{300, 400} { + t.Fatalf("native.D[1].D: got %v, want [300,400]", native.D[1].D) + } + if native.E != 99 { + t.Fatalf("native.E: got %d, want 99", native.E) + } + if native.F != [2]int64{-1, -2} { + t.Fatalf("native.F: got %v, want [-1,-2]", native.F) + } + + // Round-trip: Pack the native struct back, then re-read. + builder2 := flatbuffers.NewBuilder(512) + off2 := native.Pack(builder2) + + example.ArrayTableStart(builder2) + example.ArrayTableAddA(builder2, off2) + root2 := example.ArrayTableEnd(builder2) + builder2.Finish(root2) + + buf2 := builder2.FinishedBytes() + table2 := example.GetRootAsArrayTable(buf2, 0) + s2 := new(example.ArrayStruct) + table2.A(s2) + + // Verify all values survived the round trip. + if s2.A() != 1.5 { + t.Fatal("round trip: A mismatch") + } + for i := 0; i < 15; i++ { + if s2.B(i) != int32(i+1) { + t.Fatalf("round trip: B(%d) mismatch: got %d", i, s2.B(i)) + } + } + if s2.C() != 42 { + t.Fatal("round trip: C mismatch") + } + ns0 := s2.D(nil, 0) + if ns0.A(0) != 1 || ns0.A(1) != 2 { + t.Fatal("round trip: D(0).A mismatch") + } + ns1 := s2.D(nil, 1) + if ns1.D(0) != 300 || ns1.D(1) != 400 { + t.Fatal("round trip: D(1).D mismatch") + } + if s2.E() != 99 { + t.Fatal("round trip: E mismatch") + } + if s2.F(0) != -1 || s2.F(1) != -2 { + t.Fatal("round trip: F mismatch") + } +} + +func TestNestedStructStandalone(t *testing.T) { + builder := flatbuffers.NewBuilder(128) + + a := []int32{10, 20} + c := []example.TestEnum{example.TestEnumC, example.TestEnumA} + d := []int64{0x7FFFFFFFFFFFFFFF, -0x7FFFFFFFFFFFFFFF} + example.CreateNestedStruct(builder, a, example.TestEnumB, c, d) + + buf := builder.Bytes[builder.Head():] + ns := &example.NestedStruct{} + ns.Init(buf, 0) + + if ns.A(0) != 10 || ns.A(1) != 20 { + t.Fatalf("A: got [%d,%d], want [10,20]", ns.A(0), ns.A(1)) + } + if ns.ALength() != 2 { + t.Fatalf("ALength: got %d, want 2", ns.ALength()) + } + if ns.B() != example.TestEnumB { + t.Fatalf("B: got %d, want %d", ns.B(), example.TestEnumB) + } + if ns.C(0) != example.TestEnumC || ns.C(1) != example.TestEnumA { + t.Fatalf("C: got [%d,%d]", ns.C(0), ns.C(1)) + } + if ns.D(0) != 0x7FFFFFFFFFFFFFFF || ns.D(1) != -0x7FFFFFFFFFFFFFFF { + t.Fatalf("D: got [%d,%d]", ns.D(0), ns.D(1)) + } + + // Object API round-trip. + native := ns.UnPack() + builder2 := flatbuffers.NewBuilder(128) + native.Pack(builder2) + buf2 := builder2.Bytes[builder2.Head():] + ns2 := &example.NestedStruct{} + ns2.Init(buf2, 0) + + if ns2.A(0) != 10 || ns2.A(1) != 20 { + t.Fatal("round trip: A mismatch") + } + if ns2.D(0) != 0x7FFFFFFFFFFFFFFF { + t.Fatal("round trip: D mismatch") + } +}