Repository navigation
Implement member-wise reading of collections (e.g. uproot-issue-268.root) #70
Description
🤖 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 thepaircase is handled, everything else raises at:234-235) and:327-358(theStdMapmember-wise branch) bothunpack(">hIi")unconditionally and assert the value-version is 0; the vector path also asserts the checksum equals the hardcoded0xD7BED2(thepair<double,double>ofTEfficiency::fBeta_bin_paramsinuproot-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.rootdesynchronises (Expected position 145 but got 131,CHit.fId == 131072) becausefHitsis40 0a | 00 02 | 00000002;pairs.rootfails with a bareAssertionErrorfrom 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-357reads oneStreamHeaderper column whenever the reader is not a_FmtReader; §4.1 has six different rules (no frame for a base class or aTString, one frame per element for object members 61/62, one object slot per element for pointers 64/69, …).pairs.rootexercises all six shapes. TStreamerBase::fBaseVersionis 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-46short-circuitspair<…>infos andsrc/rootfilespec/cpptype.py:52-53always 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.rootrecordspair<int,string>,pair<int,vector<short>>,pair<TString,PHit*>andpair<string,int>;collections.rootrecords 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
Activity