Skip to content

Implement member-wise reading of collections (e.g. uproot-issue-268.root) #70

Open

Description

@nsmith-

🤖 AI generated content

Line item of #9: Memberwise reading of StdVector not implemented (uproot-issue-268.root). A genuine reader gap, and a prerequisite for most real TTree payloads. The failing member is a std::vector<AnalysisTree::BranchConfig> with fByteCount 1073 and version word 0x4009 (member-wise flag 0x4000 set).

Spec: Collections §4 "Member-wise". Layout:

byteCount  version|0x4000  valueVersion  count:i32  <member 0 × count> <member 1 × count> …

§4.1 gives the per-column framing table (fundamentals bare, a base class bare, TString bare, an object member framed per element, a collection member with one frame for the whole column); §4.2 covers base classes (TStreamerBase::fBaseVersion is needed here because base version words are not on disk); §4.3 the empty case (no columns at all); §5 which containers use which mode.

Current code (from the bootstrap review, #66):

  • No bare-version-word path. src/rootfilespec/container.py:218-233 (StdVector.read_as; only the pair case is handled, everything else raises at :234-235) and :327-358 (the StdMap member-wise branch) both unpack(">hIi") unconditionally and assert the value-version is 0; the vector path also asserts the checksum equals the hardcoded 0xD7BED2 (the pair<double,double> of TEfficiency::fBeta_bin_params in uproot-issue38c.root). Per §4 the value-version word is read with no byte count in front, and a checksum follows only when it is ≤ 0. Evidence, with the version-9 check relaxed: collection-forms.root desynchronises (Expected position 145 but got 131, CHit.fId == 131072) because fHits is 40 0a | 00 02 | 00000002; pairs.root fails with a bare AssertionError from the same asserts.
  • The checksum does not identify a pair (§8.2): resolve the value class from the member's declared type name and use the checksum only to pick a version within it.
  • Column framing is uniform, the spec's is not. container.py:338-357 reads one StreamHeader per column whenever the reader is not a _FmtReader; §4.1 has six different rules (no frame for a base class or a TString, one frame per element for object members 61/62, one object slot per element for pointers 64/69, …). pairs.root exercises all six shapes.
  • TStreamerBase::fBaseVersion is parsed (src/rootfilespec/bootstrap/TStreamerInfo.py:339) and never used; §4.2 says it is the only source of a base class's version inside a member-wise column.
  • A recorded pair<K,V> info is ignored: TStreamerInfo.py:45-46 short-circuits pair<…> infos and src/rootfilespec/cpptype.py:52-53 always synthesises the pair. §8 says a reader must look for a recorded info first (its element classes decide the §4.1 framing) and synthesise from the template arguments only when absent. pairs.root records pair<int,string>, pair<int,vector<short>>, pair<TString,PHit*> and pair<string,int>; collections.root records none.
  • The collection version word is compared to 9 — tracked separately in Collection frame version word is hardcoded to 9; files from ROOT >= 6.36 (and < 5.26) fail #99.

Test vectors: reference/root-io-spec/data/serialization/{collections,collection-forms,pairs}.root with byte-level case.toml assertions.

Severity / size: high value, L. Files: container.py, bootstrap/streamedobject.py. Strongly coupled to #67: member-wise reading is a column-wise traversal of the same element list, which the annotation-driven update_members design cannot express.

Related: #9, #67, #99, #90 (fixed arrays of collections).

Assisted-by: claude-code:claude-fable-5-1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions