Skip to content

Implement ElementType.kCharStar #20

Closed

Description

@nsmith-

This type is used in the StreamerInfo for the TString class, which suggests its implementation.
Here I'm printing out the TString streamer from uproot-sample-5.25.02-uncompressed.root:

TStreamerInfo(
    fVersion=1,
    fUniqueID=0,
    fBits=50331648,
    pidf=None,
    fName=TString(fString=b"TString"),
    fTitle=TString(fString=b""),
    fCheckSum=1332649469,
    fClassVersion=1,
    fObjects=TObjArray(
        fVersion=1,
        fUniqueID=0,
        fBits=50331648,
        pidf=None,
        fName=TString(fString=b""),
        fSize=1,
        fLowerBound=0,
        objects=(
            TStreamerBasicType(
                fVersion=1,
                fUniqueID=0,
                fBits=50331648,
                pidf=None,
                fName=TString(fString=b"fData"),
                fTitle=TString(fString=b"ref. counted data (TStringRef is in front)"),
                fType=ElementType.kCharStar,
                fSize=8,
                fArrayLength=0,
                fArrayDim=0,
                fMaxIndex=ArrayDim(dim0=0, dim1=0, dim2=0, dim3=0, dim4=0),
                fTypeName=TString(fString=b"char*"),
            ),
        ),
    ),
)


@serializable
class TString(StreamedObject):
    fData: Annotated[bytes, Fmt("charstar")]

Activity

  1. nsmith- commented on Sep 18, 2026

    @nsmith-
    OwnerAuthor

    🤖 AI generated content

    From the root-io-spec reference now vendored at reference/root-io-spec (pin 9c3f776): ElementTypes.md §2.2 and 00-conventions.md §5 §5.4.

    The encoding (TBufferFile.cxx:285-317):

    n:i32   n raw bytes
    
    • n is a full signed 4-byte length, not a length byte.
    • No NUL terminator and no 255 escape — this is not the counted string of Conventions §5.1.
    • n <= 0 means nothing follows. A null char* and an empty string are both 00 00 00 00 and cannot be told apart.
    • Bytes are uninterpreted; the spec says a reader SHOULD NOT assume UTF-8, so bytes (as in the issue body) is the right Python type, not str.

    But the sketch in the issue body would be wrong for TString. TString has a hand-written Streamer that writes a counted string (one length byte, 255 escape, 4-byte length) — Conventions §5.1, root/core/base/src/TString.cxx:1418. Its recorded streamer info is fiction: StreamerDriven.md §7 and StreamerInfo.md §3.3 call out exactly this element (fData / char*, and on newer files TString's info lists no members at all). So fData: Annotated[bytes, Fmt("charstar")] would read a 4-byte length where the file has one byte. rootfilespec already gets this right — bootstrap/strings.py:23-37 implements the counted string and TString is in BOOTSTRAP_TYPES, so dynamic.py:27-30 skips the file's info for it. No change needed there; kCharStar is only needed for genuine user char* members.

    Where it bites today. ElementType.as_fmt maps 7 → "charstar" (bootstrap/TStreamerInfo.py:272) and structutil.py:17-19 then raises Unimplemented format charstar at read time. Scanning scikit-hep-testdata, the only non-TString kCharStar members are:

    • Event::fEventName in uproot-Event.root (currently masked — that file xfails earlier on double32, Implement double32 type #41)
    • mydata::name in uproot-issue31.root (latent; that file passes because the member is never instantiated)

    Implementation is a few lines in structutil.py (or a dedicated SerDe): unpack >i, consume that many bytes, None/b"" on n <= 0. No corpus file uses the kOffsetL/kOffsetP forms (27/47), so those can stay unimplemented.

  2. nsmith- commented on Sep 18, 2026

    @nsmith-
    OwnerAuthor

    🤖 AI generated content

    Tie-in with docs/design.md: the design promises ("Annotated builtin types vs. objects") that where a builtin Python type fully captures a ROOT type, the deserialized dataclass member should be that builtin — bytes for strings. kCharStar should therefore land directly as a plain bytes member with its own reader (n:i32 + n raw bytes, per the previous comment), not as a wrapper class and not via the TString.fData sketch in the issue body.

    The general version of that promise — TString, std::string, RNTuple strings and char* all as plain bytes members, which is a large but mechanical refactor since TString/STLString/RString are wrapper dataclasses today — is now #68, with this issue as its smallest first instance. #68 also notes the interaction with #67: if streamer-generated classes stop using annotations as their serialization definition, their string members need no Annotated[...] marker at all.

  3. sathabbott commented on Oct 1, 2026

    @sathabbott
    Collaborator

    🤖 AI generated content

    Fixed by #127: a kCharStar member reads as Annotated[bytes, ROOTString("charstar")]. Its "Fixes #68 and #20" only closed #68.

  4. added this to the Version 0.1 milestone on Oct 1, 2026
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

    No labels
    No labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions