Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions tree/dataframe/src/RDFInterfaceUtils.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ std::pair<ColumnNames_t, ColumnNames_t> FindUsedColsAndAliases(const std::string
// token is not a potential variable name, skip it
continue;
}
// Skip symbols that are member accesses (obj.method) — they are not column references.
Comment thread
vepadulano marked this conversation as resolved.
// lexertk does not produce dot-prefixed tokens, so the token immediately before a method
// name is always the literal "." token when it is a member access.
if (i > 0 && tokens[i - 1].value == ".") {
continue;
}

ColumnNames_t potentialColNames({tok.value});

Expand Down Expand Up @@ -159,7 +165,7 @@ TString ResolveAliases(const TString &expr, const ColumnNames_t &usedAliases,

for (const auto &alias : usedAliases) {
const auto &col = colRegister.ResolveAlias(alias);
TPRegexp replacer("\\b" + EscapeDots(alias) + "\\b");
TPRegexp replacer("(?<!\\.)\\b" + EscapeDots(alias) + "\\b");
replacer.Substitute(out, col.data(), "g");
}

Expand Down Expand Up @@ -200,7 +206,7 @@ ParsedExpression ParseRDFExpression(std::string_view expr, const ROOT::Internal:
[](const std::string &a, const std::string &b) { return a.size() > b.size(); });
for (const auto &col : usedCols) {
const auto varIdx = std::distance(usedCols.begin(), std::find(usedCols.begin(), usedCols.end(), col));
TPRegexp replacer("\\b" + EscapeDots(col) + "\\b");
TPRegexp replacer("(?<!\\.)\\b" + EscapeDots(col) + "\\b");
replacer.Substitute(exprWithVars, varNames[varIdx], "g");
}

Expand Down
54 changes: 54 additions & 0 deletions tree/dataframe/test/dataframe_colnames.cxx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "ROOT/RDataFrame.hxx"
#include <TInterpreter.h>
#include <TTree.h>

#include "gtest/gtest.h"
Expand Down Expand Up @@ -89,3 +90,56 @@ TEST(Aliases, FilterOnAliasJit)
auto c = tdf.Define("c0", [&i]() { return i++; }).Alias("c1", "c0").Filter("c1>1").Count();
EXPECT_EQ(1U, *c);
}

// Regression test for https://github.com/root-project/root/issues/22295
// A column named "phi" must not be confused with a .phi() method call in a JIT expression.
TEST(ColNames, MethodCallNotConfusedWithColumn)
{
// Declare minimal helper types for use in JIT strings.
// Using gInterpreter::Declare avoids any dependency on GenVector or other libraries.
gInterpreter->Declare("struct RDF22295Vec { double phi() const { return 42.0; } };");
gInterpreter->Declare("struct RDF22295Mem { double phi = 1.5; };");

// Case 1: column "phi" exists, expression calls .phi() method — must not throw,
// and the result must be the value returned by the method (42.0), not a substitution
// artifact. Before the fix this crashed with "no member named 'var0'" because the
// parser replaced .phi() with .var0().
{
ROOT::RDataFrame df(1);
auto df2 = df.Define("phi", "1.0");
std::vector<double> v;
EXPECT_NO_THROW({
auto df3 = df2.Define("result", "RDF22295Vec{}.phi()");
v = *df3.Take<double>("result");
});
Comment thread
vepadulano marked this conversation as resolved.
EXPECT_NEAR(42.0, v[0], 1e-9);
}

// Case 2: column "phi" exists, expression uses BOTH .phi() method call AND standalone
// phi column reference — only the standalone reference must be substituted.
{
ROOT::RDataFrame df(1);
auto df2 = df.Define("phi", "0.5");
std::vector<double> v;
EXPECT_NO_THROW({
auto df3 = df2.Define("result", "RDF22295Vec{}.phi() + phi");
v = *df3.Take<double>("result");
});
// .phi() returns 42.0; standalone phi column = 0.5
EXPECT_NEAR(42.5, v[0], 1e-9);
}

// Case 3: data-member access obj.phi (not a function call) must still work after the
// fix — the preceding-dot guard must not break legitimate dot-chain expressions.
{
ROOT::RDataFrame df(1);
auto df2 = df.Define("phi", "0.5");
std::vector<double> v;
EXPECT_NO_THROW({
auto df3 = df2.Define("result", "RDF22295Mem{}.phi + phi");
v = *df3.Take<double>("result");
});
// data member .phi = 1.5; standalone phi column = 0.5
EXPECT_NEAR(2.0, v[0], 1e-9);
}
}
Loading