You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
TLeafI not declared in StreamerInfo, e.g. uproot-issue413.root #18
changed the title [-]TLeafI not declared in StreamerInfo perhaps? e.g. uproot-issue413.root[/-][+]TLeafI not declared in StreamerInfo, e.g. uproot-issue413.root[/+]on Apr 29, 2025
Findings below come from the root-io-spec reference now vendored in this repo at reference/root-io-spec (pinned at 9c3f776). This exact file is diagnosed there, and the conclusion agrees with the issue: the file is at fault.
Written by a third party, not ROOT […] ROOT opens it and reads all five entries without complaint, so it is not corrupt — it is just not written the way ROOT writes.
Its basket keys use fVersion 4, the small-key form. ROOT adds 1000 to a basket's key version unconditionally […]
Its TTree's fLeaves entries wrap each object reference in a byte count. ROOT's reader accepts that, so it is now specified rather than rejected: Buffer.md 6.1.
It writes no TStreamerInfo for TLeafI, TLeafD or TLeafC, which ROOT always does. Nothing can decode those leaf records from the file alone; that is StreamerDriven.md 6's case, caused by the writer.
All three are verifiable in the file. Its 16 streamer infos are ROOT::TIOFeatures, TArray, TArrayD, TArrayI, TAttFill, TAttLine, TAttMarker, TBranch(13), TBranchRef, TCollection, TList, TNamed, TObjArray, TRefTable, TSeqCollection, TTree(20) — no TLeaf anything. Every basket key in it reads fVersion 4 (contrast TBasket §1: fVersion += 1000 unconditionally). And in the decompressed TTree record the fLeaves entries really are eight bytes each, e.g. 40 00 00 04 | 00 00 0a 7f — a byte count of 4 in front of a bare object-reference tag.
Streamer-driven reading §6 on "the class is absent from the file's StreamerInfo record": "This is a broken file for every class except the bootstrap set and the classes written without a streamer info at all (TFile, TDirectory, the free list). A reader SHOULD report it rather than guess." TLeaf* is not in either exempt set — the exhaustive lists are Bootstrap classes §3 and §5, where the only TTree-side classes with genuinely no info are TBasket, TTreeIndex and TBranchClones.
If we want to read it anyway
The spec does give the leaf record layout independently of any streamer info — TLeaf §2 and §2.1: TNamed(67), fLen, fLenType, fOffset, fIsRange, fIsUnsigned, fLeafCount(64), plus two fMinimum/fMaximum of the leaf's own element type; TLeaf is class version 2, TLeafI/TLeafD/TLeafC are version 1. So a hardcoded fallback is possible if we ever decide unknown-TLeaf files are worth supporting — but per §6 the right default is to report and skip.
Note the current NotImplementedError is raised from src/rootfilespec/dynamic.py:81-85, and there is a related TLeaf special case in src/rootfilespec/bootstrap/streamedobject.py:242.
Also worth knowing: TLeafI is not the last obstacle in this file. The 40 00 00 04 + tag form above is legal (Buffer framing §6.1: "a reader that requires §6's three shapes rejects a file ROOT reads without complaint"), and StreamHeader.read would currently take the byte count of 4 and then misread the first half of the tag as a version word. That one is a genuine reader gap.
One discrepancy with the issue text
The issue attributes the file to groot v0.21.0; the spec's note calls the branch names "uproot's writing-test fixtures". The substantive claim — third-party writer, not ROOT — is the same either way, and the file's contents (struct.root, tree mytree, branches I32 F64 Str ArrF64 N SliF64, entries evt-0…evt-4) look more like groot's fixtures to me than uproot's. Worth a one-line correction upstream; it changes nothing here.
Verdict: file at fault — skip permanently, or report-and-skip generically. Size: S if we just make "class absent from StreamerInfo" a clean skip; M if we hardcode the TLeaf family and fix the byte-counted object reference.
Answering the maintainer's question — can ROOT open this file but not read the missing data, or is there no data using that schema in the file? — it is neither, quite:
There is data using the schema. The decompressed TTree record contains real TLeafI, TLeafD and TLeafC objects (inside the branches' fLeaves), each written as a new-class object slot: 40 00 00 49 | ff ff ff ff | "TLeafI\0" …, 40 00 00 51 | ff ff ff ff | "TLeafD\0" …, 40 00 00 49 | ff ff ff ff | "TLeafC\0" … (checked in the bytes).
ROOT reads all of it, including those leaves, because TLeaf* are ROOT's own compiled classes: ROOT falls back on its built-in dictionary when a file's StreamerInfo record lacks them. Only a reader that relies solely on the file's StreamerInfo — like rootfilespec — cannot decode the leaf records.
So the file is unusual (written by groot, not ROOT) but fully readable by ROOT. For rootfilespec the agreed default still applies cleanly: each leaf object sits in a slot with a byte count (0x49, 0x51), so it can be returned as Uninterpreted with its class name preserved — tracked in #74. The rest of the tree (branches, basket locations) stays interpretable; a consumer even keeps the leaf's type via the class name in the Uninterpreted header.
Two further things this file needs before it reads end to end, both reader gaps rather than file faults:
A hardcoded TLeaf family (layout in the spec's TLeaf §2) remains possible later if uninterpreted leaves turn out to be too limiting, but is not planned.
Activity
[-]TLeafI not declared in StreamerInfo perhaps? e.g. uproot-issue413.root[/-][+]TLeafI not declared in StreamerInfo, e.g. uproot-issue413.root[/+]nsmith- commentedon Sep 18, 2026
Findings below come from the root-io-spec reference now vendored in this repo at
reference/root-io-spec(pinned at9c3f776). This exact file is diagnosed there, and the conclusion agrees with the issue: the file is at fault.The spec's diagnosis of
uproot-issue413.rootFrom
gen/foreign/IGNORE.toml, which is the list of corpus files "judged to be at fault":All three are verifiable in the file. Its 16 streamer infos are
ROOT::TIOFeatures, TArray, TArrayD, TArrayI, TAttFill, TAttLine, TAttMarker, TBranch(13), TBranchRef, TCollection, TList, TNamed, TObjArray, TRefTable, TSeqCollection, TTree(20)— noTLeafanything. Every basket key in it readsfVersion4 (contrast TBasket §1:fVersion += 1000unconditionally). And in the decompressedTTreerecord thefLeavesentries really are eight bytes each, e.g.40 00 00 04 | 00 00 0a 7f— a byte count of 4 in front of a bare object-reference tag.Streamer-driven reading §6 on "the class is absent from the file's
StreamerInforecord": "This is a broken file for every class except the bootstrap set and the classes written without a streamer info at all (TFile,TDirectory, the free list). A reader SHOULD report it rather than guess."TLeaf*is not in either exempt set — the exhaustive lists are Bootstrap classes §3 and §5, where the onlyTTree-side classes with genuinely no info areTBasket,TTreeIndexandTBranchClones.If we want to read it anyway
The spec does give the leaf record layout independently of any streamer info — TLeaf §2 and §2.1:
TNamed(67),fLen,fLenType,fOffset,fIsRange,fIsUnsigned,fLeafCount(64), plus twofMinimum/fMaximumof the leaf's own element type;TLeafis class version 2,TLeafI/TLeafD/TLeafCare version 1. So a hardcoded fallback is possible if we ever decide unknown-TLeaffiles are worth supporting — but per §6 the right default is to report and skip.Note the current
NotImplementedErroris raised fromsrc/rootfilespec/dynamic.py:81-85, and there is a relatedTLeafspecial case insrc/rootfilespec/bootstrap/streamedobject.py:242.Also worth knowing:
TLeafIis not the last obstacle in this file. The40 00 00 04+ tag form above is legal (Buffer framing §6.1: "a reader that requires §6's three shapes rejects a file ROOT reads without complaint"), andStreamHeader.readwould currently take the byte count of 4 and then misread the first half of the tag as a version word. That one is a genuine reader gap.One discrepancy with the issue text
The issue attributes the file to groot v0.21.0; the spec's note calls the branch names "uproot's writing-test fixtures". The substantive claim — third-party writer, not ROOT — is the same either way, and the file's contents (
struct.root, treemytree, branchesI32 F64 Str ArrF64 N SliF64, entriesevt-0…evt-4) look more like groot's fixtures to me than uproot's. Worth a one-line correction upstream; it changes nothing here.Verdict: file at fault — skip permanently, or report-and-skip generically. Size: S if we just make "class absent from
StreamerInfo" a clean skip; M if we hardcode theTLeaffamily and fix the byte-counted object reference.nsmith- commentedon Sep 18, 2026
Answering the maintainer's question — can ROOT open this file but not read the missing data, or is there no data using that schema in the file? — it is neither, quite:
TTreerecord contains realTLeafI,TLeafDandTLeafCobjects (inside the branches'fLeaves), each written as a new-class object slot:40 00 00 49 | ff ff ff ff | "TLeafI\0" …,40 00 00 51 | ff ff ff ff | "TLeafD\0" …,40 00 00 49 | ff ff ff ff | "TLeafC\0" …(checked in the bytes).TLeaf*are ROOT's own compiled classes: ROOT falls back on its built-in dictionary when a file's StreamerInfo record lacks them. Only a reader that relies solely on the file's StreamerInfo — like rootfilespec — cannot decode the leaf records.So the file is unusual (written by groot, not ROOT) but fully readable by ROOT. For rootfilespec the agreed default still applies cleanly: each leaf object sits in a slot with a byte count (
0x49,0x51), so it can be returned asUninterpretedwith its class name preserved — tracked in #74. The rest of the tree (branches, basket locations) stays interpretable; a consumer even keeps the leaf's type via the class name in theUninterpretedheader.Two further things this file needs before it reads end to end, both reader gaps rather than file faults:
40 00 00 04 | 00 00 0a 7f) used forTTree::fLeavesentries → StreamHeader misparses three legal slot shapes: no-byte-count slots, byte-counted references, negative version words #106;extendingclasses #98.A hardcoded
TLeaffamily (layout in the spec's TLeaf §2) remains possible later if uninterpreted leaves turn out to be too limiting, but is not planned.