Skip to content

Commit 508a1af

Browse files
committed
[ntuple] fix type name normalization for C style arrays
1 parent 705376f commit 508a1af

File tree

4 files changed

+78
-52
lines changed

4 files changed

+78
-52
lines changed

tree/ntuple/inc/ROOT/RFieldUtils.hxx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ namespace Internal {
2020
/// Applies RNTuple specific type name normalization rules (see specs) that help the string parsing in
2121
/// RFieldBase::Create(). The normalization of templated types does not include full normalization of the
2222
/// template arguments (hence "Prefix").
23+
/// Furthermore, if the type is a C-style array, rules are applied to the base type and the C style array
24+
/// is then mapped to an std::array.
2325
std::string GetCanonicalTypePrefix(const std::string &typeName);
2426

2527
/// Given a type name normalized by ROOT meta, renormalize it for RNTuple. E.g., insert std::prefix.
@@ -48,13 +50,6 @@ enum class ERNTupleSerializationMode {
4850

4951
ERNTupleSerializationMode GetRNTupleSerializationMode(TClass *cl);
5052

51-
/// Parse a type name of the form `T[n][m]...` and return the base type `T` and a vector that contains,
52-
/// in order, the declared size for each dimension, e.g. for `unsigned char[1][2][3]` it returns the tuple
53-
/// `{"unsigned char", {1, 2, 3}}`. Extra whitespace in `typeName` should be removed before calling this function.
54-
///
55-
/// If `typeName` is not an array type, it returns a tuple `{T, {}}`. On error, it returns a default-constructed tuple.
56-
std::tuple<std::string, std::vector<std::size_t>> ParseArrayType(const std::string &typeName);
57-
5853
/// Used in RFieldBase::Create() in order to get the comma-separated list of template types
5954
/// E.g., gets {"int", "std::variant<double,int>"} from "int,std::variant<double,int>".
6055
/// If maxArgs > 0, stop tokenizing after the given number of tokens are found. Used to strip

tree/ntuple/src/RFieldBase.cxx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ ROOT::RFieldBase::Create(const std::string &fieldName, const std::string &typeNa
292292
const ROOT::RCreateFieldOptions &options, const ROOT::RNTupleDescriptor *desc,
293293
ROOT::DescriptorId_t fieldId)
294294
{
295-
using ROOT::Internal::ParseArrayType;
296295
using ROOT::Internal::ParseUIntTypeToken;
297296
using ROOT::Internal::TokenizeTypeList;
298297

@@ -331,15 +330,6 @@ ROOT::RFieldBase::Create(const std::string &fieldName, const std::string &typeNa
331330
// try-catch block to intercept any exception that may be thrown by Unwrap() so that this
332331
// function never throws but returns RResult::Error instead.
333332
try {
334-
if (auto [arrayBaseType, arraySizes] = ParseArrayType(resolvedType); !arraySizes.empty()) {
335-
std::unique_ptr<RFieldBase> arrayField = Create("_0", arrayBaseType, options, desc, fieldId).Unwrap();
336-
for (int i = arraySizes.size() - 1; i >= 0; --i) {
337-
arrayField =
338-
std::make_unique<RArrayField>((i == 0) ? fieldName : "_0", std::move(arrayField), arraySizes[i]);
339-
}
340-
return arrayField;
341-
}
342-
343333
if (resolvedType == "bool") {
344334
result = std::make_unique<RField<bool>>(fieldName);
345335
} else if (resolvedType == "char") {

tree/ntuple/src/RFieldUtils.cxx

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,50 @@ bool IsUserClass(const std::string &typeName)
6666
return typeName.rfind("std::", 0) != 0 && typeName.rfind("ROOT::VecOps::RVec<", 0) != 0;
6767
}
6868

69+
/// Parse a type name of the form `T[n][m]...` and return the base type `T` and a vector that contains,
70+
/// in order, the declared size for each dimension, e.g. for `unsigned char[1][2][3]` it returns the tuple
71+
/// `{"unsigned char", {1, 2, 3}}`. Extra whitespace in `typeName` should be removed before calling this function.
72+
///
73+
/// If `typeName` is not an array type, it returns a tuple `{T, {}}`. On error, it returns a default-constructed tuple.
74+
std::tuple<std::string, std::vector<std::size_t>> ParseArrayType(const std::string &typeName)
75+
{
76+
std::vector<std::size_t> sizeVec;
77+
78+
// Only parse outer array definition, i.e. the right `]` should be at the end of the type name
79+
std::string prefix{typeName};
80+
while (prefix.back() == ']') {
81+
auto posRBrace = prefix.size() - 1;
82+
auto posLBrace = prefix.rfind('[', posRBrace);
83+
if (posLBrace == std::string_view::npos) {
84+
throw ROOT::RException(R__FAIL(std::string("invalid array type: ") + typeName));
85+
}
86+
if (posRBrace - posLBrace <= 1) {
87+
throw ROOT::RException(R__FAIL(std::string("invalid array type: ") + typeName));
88+
}
89+
90+
const std::size_t size =
91+
ROOT::Internal::ParseUIntTypeToken(prefix.substr(posLBrace + 1, posRBrace - posLBrace - 1));
92+
if (size == 0) {
93+
throw ROOT::RException(R__FAIL(std::string("invalid array size: ") + typeName));
94+
}
95+
96+
sizeVec.insert(sizeVec.begin(), size);
97+
prefix.resize(posLBrace);
98+
}
99+
return std::make_tuple(prefix, sizeVec);
100+
}
101+
102+
/// Assembles a (nested) std::array<> based type based on the dimensions retrieved from ParseArrayType(). Returns
103+
/// baseType if there are no dimensions.
104+
std::string GetStandardArrayType(const std::string &baseType, const std::vector<std::size_t> &dimensions)
105+
{
106+
std::string typeName = baseType;
107+
for (auto i = dimensions.rbegin(), iEnd = dimensions.rend(); i != iEnd; ++i) {
108+
typeName = "std::array<" + typeName + "," + std::to_string(*i) + ">";
109+
}
110+
return typeName;
111+
}
112+
69113
// Recursively normalizes a template argument using the regular type name normalizer F as a helper.
70114
template <typename F>
71115
std::string GetNormalizedTemplateArg(const std::string &arg, bool keepQualifier, F fnTypeNormalizer)
@@ -85,7 +129,7 @@ std::string GetNormalizedTemplateArg(const std::string &arg, bool keepQualifier,
85129
// strips the qualifier.
86130
// Demangled names may have the CV qualifiers suffixed and not prefixed (but const always before volatile).
87131
// Note that in the latter case, we may have the CV qualifiers before array brackets, e.g. `int const[2]`.
88-
const auto [base, _] = ROOT::Internal::ParseArrayType(arg);
132+
const auto [base, _] = ParseArrayType(arg);
89133
if (base.rfind("const ", 0) == 0 || base.rfind("volatile const ", 0) == 0 ||
90134
base.find(" const", base.length() - 6) != std::string::npos ||
91135
base.find(" const volatile", base.length() - 15) != std::string::npos) {
@@ -336,7 +380,7 @@ void NormalizeTemplateArguments(std::string &templatedTypeName, int maxTemplateA
336380
// Given a type name normalized by ROOT Meta, return the type name normalized according to the RNTuple rules.
337381
std::string GetRenormalizedMetaTypeName(const std::string &metaNormalizedName)
338382
{
339-
const std::string canonicalTypePrefix{ROOT::Internal::GetCanonicalTypePrefix(metaNormalizedName)};
383+
const auto canonicalTypePrefix = ROOT::Internal::GetCanonicalTypePrefix(metaNormalizedName);
340384
// RNTuple resolves Double32_t for the normalized type name but keeps Double32_t for the type alias
341385
// (also in template parameters)
342386
if (canonicalTypePrefix == "Double32_t")
@@ -357,14 +401,16 @@ std::string GetRenormalizedMetaTypeName(const std::string &metaNormalizedName)
357401
// RNTuple rules.
358402
std::string GetRenormalizedDemangledTypeName(const std::string &demangledName, bool renormalizeStdString)
359403
{
360-
std::string canonicalTypePrefix{demangledName};
404+
std::string tn{demangledName};
405+
RemoveSpaceBefore(tn, '[');
406+
auto [canonicalTypePrefix, dimensions] = ParseArrayType(tn);
361407
RemoveCVQualifiers(canonicalTypePrefix);
362408
RemoveLeadingKeyword(canonicalTypePrefix);
363409
MapIntegerType(canonicalTypePrefix);
364410

365411
if (canonicalTypePrefix.find('<') == std::string::npos) {
366412
// If there are no templates, the function is done.
367-
return canonicalTypePrefix;
413+
return GetStandardArrayType(canonicalTypePrefix, dimensions);
368414
}
369415
RemoveSpaceBefore(canonicalTypePrefix, '>');
370416
RemoveSpaceAfter(canonicalTypePrefix, ',');
@@ -396,15 +442,21 @@ std::string GetRenormalizedDemangledTypeName(const std::string &demangledName, b
396442
RenormalizeStdString(normName);
397443
}
398444

399-
return normName;
445+
return GetStandardArrayType(normName, dimensions);
400446
}
401447

402448
} // namespace
403449

404450
std::string ROOT::Internal::GetCanonicalTypePrefix(const std::string &typeName)
405451
{
406-
// Remove outer cv qualifiers
407-
std::string canonicalType{TClassEdit::CleanType(typeName.c_str(), /*mode=*/1)};
452+
// Remove outer cv qualifiers and extra white spaces
453+
const std::string cleanedType = TClassEdit::CleanType(typeName.c_str(), /*mode=*/1);
454+
455+
// Can happen when called from RFieldBase::Create() and is caught there
456+
if (cleanedType.empty())
457+
return "";
458+
459+
auto [canonicalType, dimensions] = ParseArrayType(cleanedType);
408460

409461
RemoveLeadingKeyword(canonicalType);
410462
if (canonicalType.substr(0, 2) == "::") {
@@ -456,7 +508,7 @@ std::string ROOT::Internal::GetCanonicalTypePrefix(const std::string &typeName)
456508

457509
MapIntegerType(canonicalType);
458510

459-
return canonicalType;
511+
return GetStandardArrayType(canonicalType, dimensions);
460512
}
461513

462514
std::string ROOT::Internal::GetRenormalizedTypeName(const std::type_info &ti)
@@ -474,9 +526,9 @@ std::string ROOT::Internal::GetNormalizedUnresolvedTypeName(const std::string &o
474526
const TClassEdit::EModType modType = static_cast<TClassEdit::EModType>(
475527
TClassEdit::kDropStlDefault | TClassEdit::kDropComparator | TClassEdit::kDropHash);
476528
TClassEdit::TSplitType splitname(origName.c_str(), modType);
477-
std::string canonicalTypePrefix;
478-
splitname.ShortType(canonicalTypePrefix, modType);
479-
canonicalTypePrefix = GetCanonicalTypePrefix(canonicalTypePrefix);
529+
std::string shortType;
530+
splitname.ShortType(shortType, modType);
531+
const auto canonicalTypePrefix = GetCanonicalTypePrefix(shortType);
480532

481533
if (canonicalTypePrefix.find('<') == std::string::npos) {
482534
// If there are no templates, the function is done.
@@ -620,30 +672,6 @@ ROOT::Internal::ERNTupleSerializationMode ROOT::Internal::GetRNTupleSerializatio
620672
}
621673
}
622674

623-
std::tuple<std::string, std::vector<std::size_t>> ROOT::Internal::ParseArrayType(const std::string &typeName)
624-
{
625-
std::vector<std::size_t> sizeVec;
626-
627-
// Only parse outer array definition, i.e. the right `]` should be at the end of the type name
628-
std::string prefix{typeName};
629-
while (prefix.back() == ']') {
630-
auto posRBrace = prefix.size() - 1;
631-
auto posLBrace = prefix.rfind('[', posRBrace);
632-
if (posLBrace == std::string_view::npos) {
633-
throw RException(R__FAIL(std::string("invalid array type: ") + typeName));
634-
}
635-
636-
const std::size_t size = ParseUIntTypeToken(prefix.substr(posLBrace + 1, posRBrace - posLBrace - 1));
637-
if (size == 0) {
638-
throw RException(R__FAIL(std::string("invalid array size: ") + typeName));
639-
}
640-
641-
sizeVec.insert(sizeVec.begin(), size);
642-
prefix.resize(posLBrace);
643-
}
644-
return std::make_tuple(prefix, sizeVec);
645-
}
646-
647675
std::vector<std::string> ROOT::Internal::TokenizeTypeList(std::string_view templateType, std::size_t maxArgs)
648676
{
649677
std::vector<std::string> result;

tree/ntuple/test/ntuple_type_name.cxx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ TEST(RNTuple, TypeNameNormalizationByName)
3737
EXPECT_EQ("std::uint32_t", RFieldBase::Create("f", "SG::sgkey_t").Unwrap()->GetTypeName());
3838
EXPECT_EQ("SG::sgkey_t", RFieldBase::Create("f", "SG::sgkey_t").Unwrap()->GetTypeAlias());
3939

40+
EXPECT_EQ("std::array<std::array<std::uint32_t,3>,2>",
41+
RFieldBase::Create("f", "SG::sgkey_t[2][3]").Unwrap()->GetTypeName());
42+
EXPECT_EQ("std::array<SG::sgkey_t,2>", RFieldBase::Create("f", "SG::sgkey_t[2]").Unwrap()->GetTypeAlias());
43+
EXPECT_EQ("std::vector<std::array<double,2>>",
44+
RFieldBase::Create("f", "vector<Double32_t[2]>").Unwrap()->GetTypeName());
45+
EXPECT_EQ("std::vector<std::array<Double32_t,2>>",
46+
RFieldBase::Create("f", "vector<Double32_t[2]>").Unwrap()->GetTypeAlias());
47+
4048
const std::string innerCV = "class InnerCV<const int, const volatile int, volatile const int, volatile int>";
4149
const std::string normInnerCV =
4250
"InnerCV<const std::int32_t,const volatile std::int32_t,const volatile std::int32_t,volatile std::int32_t>";
@@ -84,6 +92,11 @@ TEST(RNTuple, TypeNameNormalizationById)
8492
EXPECT_EQ("std::unique_ptr<std::int32_t>", ROOT::Internal::GetRenormalizedTypeName(typeid(std::unique_ptr<int>)));
8593
EXPECT_EQ("std::optional<std::int32_t>", ROOT::Internal::GetRenormalizedTypeName(typeid(std::optional<int>)));
8694

95+
EXPECT_EQ("std::array<std::array<std::uint32_t,3>,2>",
96+
ROOT::Internal::GetRenormalizedTypeName(typeid(SG::sgkey_t[2][3])));
97+
EXPECT_EQ("std::vector<std::array<double,2>>",
98+
ROOT::Internal::GetRenormalizedTypeName(typeid(std::vector<Double32_t[2]>)));
99+
87100
EXPECT_EQ("std::set<std::int32_t>", ROOT::Internal::GetRenormalizedTypeName(typeid(std::set<int>)));
88101
EXPECT_EQ("std::unordered_set<std::int32_t>",
89102
ROOT::Internal::GetRenormalizedTypeName(typeid(std::unordered_set<int>)));

0 commit comments

Comments
 (0)