From 2001ee0cdf126b15f53e589f1d2da21bae250470 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Mon, 7 Sep 2026 00:05:15 +0200 Subject: [PATCH] [ntuple] add support for emulated enums --- tree/ntuple/inc/ROOT/RField.hxx | 12 +++++ tree/ntuple/src/RFieldBase.cxx | 8 ++++ tree/ntuple/src/RFieldMeta.cxx | 16 +++++++ tree/ntuple/test/ntuple_emulated.cxx | 67 ++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/tree/ntuple/inc/ROOT/RField.hxx b/tree/ntuple/inc/ROOT/RField.hxx index 16db2c9f1c680..1429a8e3f0b5c 100644 --- a/tree/ntuple/inc/ROOT/RField.hxx +++ b/tree/ntuple/inc/ROOT/RField.hxx @@ -285,11 +285,23 @@ public: void AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const final; }; +namespace Internal { +std::unique_ptr CreateEmulatedEnumField(std::string_view fieldName, std::string_view emulatedFromType, + std::string_view underlyingIntType); +} + /// The field for an unscoped or scoped enum with dictionary class REnumField : public RFieldBase { + friend std::unique_ptr Internal::CreateEmulatedEnumField(std::string_view fieldName, + std::string_view emulatedFromType, + std::string_view underlyingIntType); + private: REnumField(std::string_view fieldName, TEnum *enump); + // Used by CloneImpl() REnumField(std::string_view fieldName, std::string_view enumName, std::unique_ptr intField); + // Used by field emulation + REnumField(std::string_view fieldName, std::string_view emulatedFromType, std::string_view underlyingIntType); protected: std::unique_ptr CloneImpl(std::string_view newName) const final; diff --git a/tree/ntuple/src/RFieldBase.cxx b/tree/ntuple/src/RFieldBase.cxx index 96360e767537a..42b33d86d3a7d 100644 --- a/tree/ntuple/src/RFieldBase.cxx +++ b/tree/ntuple/src/RFieldBase.cxx @@ -546,6 +546,14 @@ ROOT::RFieldBase::Create(const std::string &fieldName, const std::string &typeNa ROOT::Internal::CreateEmulatedVectorField(fieldName, std::move(itemField), fieldDesc.GetTypeName()); vecField->fTypeAlias = fieldDesc.GetTypeAlias(); return vecField; + } else if (ROOT::Internal::IsCustomEnumFieldDesc(*desc, fieldDesc)) { + R__ASSERT(!fieldDesc.GetLinkIds().empty()); + auto underlyingIntFieldId = fieldDesc.GetLinkIds()[0]; + const auto &underlyingIntFieldDesc = desc->GetFieldDescriptor(underlyingIntFieldId); + auto enumField = ROOT::Internal::CreateEmulatedEnumField(fieldName, fieldDesc.GetTypeName(), + underlyingIntFieldDesc.GetTypeName()); + enumField->fTypeAlias = fieldDesc.GetTypeAlias(); + return enumField; } } } diff --git a/tree/ntuple/src/RFieldMeta.cxx b/tree/ntuple/src/RFieldMeta.cxx index f6a2a2471b9ed..ebfc7dba72570 100644 --- a/tree/ntuple/src/RFieldMeta.cxx +++ b/tree/ntuple/src/RFieldMeta.cxx @@ -1120,6 +1120,13 @@ void ROOT::Experimental::RSoAField::AcceptVisitor(ROOT::Detail::RFieldVisitor &v //------------------------------------------------------------------------------ +std::unique_ptr ROOT::Internal::CreateEmulatedEnumField(std::string_view fieldName, + std::string_view emulatedFromType, + std::string_view underlyingIntType) +{ + return std::unique_ptr(new REnumField(fieldName, emulatedFromType, underlyingIntType)); +} + ROOT::REnumField::REnumField(std::string_view fieldName, std::string_view enumName) : REnumField(fieldName, EnsureValidEnum(enumName)) { @@ -1160,6 +1167,15 @@ ROOT::REnumField::REnumField(std::string_view fieldName, std::string_view enumNa fTraits |= kTraitTriviallyConstructible | kTraitTriviallyDestructible; } +ROOT::REnumField::REnumField(std::string_view fieldName, std::string_view emulatedFromType, + std::string_view underlyingIntType) + : ROOT::RFieldBase(fieldName, emulatedFromType, ROOT::ENTupleStructure::kPlain, false /* isSimple */) +{ + auto intField = Create("_0", std::string(underlyingIntType)).Unwrap(); + Attach(std::move(intField)); + fTraits |= kTraitTriviallyConstructible | kTraitTriviallyDestructible | kTraitEmulatedField; +} + std::unique_ptr ROOT::REnumField::CloneImpl(std::string_view newName) const { auto newIntField = fSubfields[0]->Clone(fSubfields[0]->GetFieldName()); diff --git a/tree/ntuple/test/ntuple_emulated.cxx b/tree/ntuple/test/ntuple_emulated.cxx index 0daf224b10ebb..3717646085c23 100644 --- a/tree/ntuple/test/ntuple_emulated.cxx +++ b/tree/ntuple/test/ntuple_emulated.cxx @@ -688,6 +688,73 @@ TEST(RNTupleEmulated, CollectionProxy) EXPECT_EQ(0u, vec->size()); } +TEST(RNTupleEmulated, EmulatedFields_Enum) +{ + FileRaii fileGuard("test_ntuple_emulated_fields_enum.root"); + + ExecInFork([&] { + // The child process writes the file and exits, but the file must be preserved to be read by the parent. + fileGuard.PreserveFile(); + + ASSERT_TRUE(gInterpreter->Declare(R"( + enum class EmulatedEnum { kZero, kOne }; + )")); + + auto model = RNTupleModel::Create(); + model->AddField(RFieldBase::Create("f", "EmulatedEnum").Unwrap()); + + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + void *ptr = writer->GetModel().GetDefaultEntry().GetPtr("f").get(); + DeclarePointer("EmulatedEnum", "ptr", ptr); + + ProcessLine("*ptr = EmulatedEnum::kOne"); + writer->Fill(); + }); + + auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); + const auto &desc = reader->GetDescriptor(); + + { + RNTupleDescriptor::RCreateModelOptions opts; + opts.SetEmulateUnknownTypes(false); + try { + auto model = desc.CreateModel(opts); + FAIL() << "Creating a model without fEmulateUnknownTypes should fail"; + } catch (const ROOT::RException &ex) { + ASSERT_THAT(ex.GetError().GetReport(), testing::HasSubstr("unknown type")); + } + } + + { + RNTupleDescriptor::RCreateModelOptions opts; + opts.SetEmulateUnknownTypes(true); + auto model = desc.CreateModel(opts); + ASSERT_NE(model, nullptr); + + const auto &e = model->GetConstField("f"); + EXPECT_EQ(e.GetTypeName(), "EmulatedEnum"); + EXPECT_TRUE(e.GetTraits() & ROOT::RFieldBase::kTraitEmulatedField); + EXPECT_EQ(e.GetStructure(), ROOT::ENTupleStructure::kPlain); + + const auto underlyingIntField = e.GetConstSubfields()[0]; + EXPECT_EQ(underlyingIntField->GetTypeName(), "std::int32_t"); + } + + RNTupleDescriptor::RCreateModelOptions cmOpts; + cmOpts.SetEmulateUnknownTypes(true); + + std::unique_ptr file(TFile::Open(fileGuard.GetPath().c_str())); + std::unique_ptr ntpl(file->Get("ntpl")); + reader = RNTupleReader::Open(cmOpts, *ntpl); + EXPECT_EQ(reader->GetNEntries(), 1); + + std::int32_t value = 0; + auto e = reader->CreateEntry(); + e->BindRawPtr("f", &value); + reader->LoadEntry(0, *e); + EXPECT_EQ(1, value); +} + TEST(RNTupleEmulated, EmulatedFields_SoA) { FileRaii fileGuard("test_ntuple_emulated_fields_soa.root");