Skip to content

Pack() method incorrectly accesses fixed array of structs as single object in Python object API #9061

@scweiser

Description

@scweiser

When a struct contains a fixed-size array of structs, the generated Pack() method in the Python object API treats self.items as a single struct instance instead of a list, causing runtime errors.

This is inconsistent with _UnPack(), the .pyi stub, and CreateOuter(), all of which correctly treat the field as a list.

flatc version: 25.12.19

Schema to reproduce:


struct Inner {
  x: float;
  y: float;
}

struct Outer {
  items: [Inner: 3];
}

table Root {
  data: Outer (native_inline);
}

root_type Root;

The following command
flatc --python --gen-object-api --python-typing --gen-onefile --filename-suffix "" bug.fbs

results in an incorrectly generated Pack:

pythondef Pack(self, builder):
    return CreateOuter(builder, self.items.x, self.items.y)  # self.items is a list

and a correct _UnPack:

pythondef _UnPack(self, outer):
    self.items = []
    for i in range(outer.ItemsLength()):
        inner_ = InnerT.InitFromObj(outer.Items(i))
        self.items.append(inner_)
Generated CreateOuter() — expects lists
pythondef CreateOuter(builder, items_x, items_y):
    builder.Prep(4, 24)
    for _idx0 in range(3, 0, -1):
        builder.Prep(4, 8)
        builder.PrependFloat32(items_y[_idx0-1])  # indexes into list
        builder.PrependFloat32(items_x[_idx0-1])  # indexes into list
    return builder.Offset()

The generated stubs expect list (as expected):

pythonclass OuterT(object):
    items: typing.List[InnerT]  # list, not a single InnerT

I would expect Pack() to iterate over self.items and extract scalar fields into lists, consistent with how CreateOuter() consumes them:

def Pack(self, builder):
    return CreateOuter(
        builder,
        [_item.x for _item in self.items],
        [_item.y for _item in self.items],
    )

Notes

The bug also affects nested cases where the element struct itself contains fixed arrays, requiring 2D list comprehensions
As a workaround we are patching the generated Pack() method as a post-processing step in our build pipeline, which fixes the issue locally, similar to below:

def Pack(self, builder):
    return CreateOuter(
        builder,
        [_item.x for _item in self.items],
        [_item.y for _item in self.items],
    )

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions