Skip to content

Commit

Permalink
[clang][ASTImporter] Improve structural equivalence of overloadable o…
Browse files Browse the repository at this point in the history
…perators. (#72242)

Operators that are overloadable may be parsed as `CXXOperatorCallExpr`
or as `UnaryOperator` (or `BinaryOperator`). This depends on the context
and can be different if a similar construct is imported into an existing
AST. The two "forms" of the operator call AST nodes should be detected
as equivalent to allow AST import of these cases.

This fix has probably other consequences because if a structure is
imported that has `CXXOperatorCallExpr` into an AST with an existing
similar structure that has `UnaryOperator` (or binary), the additional
data in the `CXXOperatorCallExpr` node is lost at the import (because
the existing node will be used). I am not sure if this can cause
problems.
  • Loading branch information
balazske committed Jan 18, 2024
1 parent f1226ee commit 9ca1a08
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 0 deletions.
57 changes: 57 additions & 0 deletions clang/lib/AST/ASTStructuralEquivalence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
QualType T1, QualType T2);
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Decl *D1, Decl *D2);
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const Stmt *S1, const Stmt *S2);
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const TemplateArgument &Arg1,
const TemplateArgument &Arg2);
Expand Down Expand Up @@ -437,12 +439,67 @@ class StmtComparer {
};
} // namespace

static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const UnaryOperator *E1,
const CXXOperatorCallExpr *E2) {
return UnaryOperator::getOverloadedOperator(E1->getOpcode()) ==
E2->getOperator() &&
IsStructurallyEquivalent(Context, E1->getSubExpr(), E2->getArg(0));
}

static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const CXXOperatorCallExpr *E1,
const UnaryOperator *E2) {
return E1->getOperator() ==
UnaryOperator::getOverloadedOperator(E2->getOpcode()) &&
IsStructurallyEquivalent(Context, E1->getArg(0), E2->getSubExpr());
}

static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const BinaryOperator *E1,
const CXXOperatorCallExpr *E2) {
return BinaryOperator::getOverloadedOperator(E1->getOpcode()) ==
E2->getOperator() &&
IsStructurallyEquivalent(Context, E1->getLHS(), E2->getArg(0)) &&
IsStructurallyEquivalent(Context, E1->getRHS(), E2->getArg(1));
}

static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const CXXOperatorCallExpr *E1,
const BinaryOperator *E2) {
return E1->getOperator() ==
BinaryOperator::getOverloadedOperator(E2->getOpcode()) &&
IsStructurallyEquivalent(Context, E1->getArg(0), E2->getLHS()) &&
IsStructurallyEquivalent(Context, E1->getArg(1), E2->getRHS());
}

/// Determine structural equivalence of two statements.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
const Stmt *S1, const Stmt *S2) {
if (!S1 || !S2)
return S1 == S2;

// Check for statements with similar syntax but different AST.
// A UnaryOperator node is more lightweight than a CXXOperatorCallExpr node.
// The more heavyweight node is only created if the definition-time name
// lookup had any results. The lookup results are stored CXXOperatorCallExpr
// only. The lookup results can be different in a "From" and "To" AST even if
// the compared structure is otherwise equivalent. For this reason we must
// treat a similar unary/binary operator node and CXXOperatorCall node as
// equivalent.
if (const auto *E2CXXOperatorCall = dyn_cast<CXXOperatorCallExpr>(S2)) {
if (const auto *E1Unary = dyn_cast<UnaryOperator>(S1))
return IsStructurallyEquivalent(Context, E1Unary, E2CXXOperatorCall);
if (const auto *E1Binary = dyn_cast<BinaryOperator>(S1))
return IsStructurallyEquivalent(Context, E1Binary, E2CXXOperatorCall);
}
if (const auto *E1CXXOperatorCall = dyn_cast<CXXOperatorCallExpr>(S1)) {
if (const auto *E2Unary = dyn_cast<UnaryOperator>(S2))
return IsStructurallyEquivalent(Context, E1CXXOperatorCall, E2Unary);
if (const auto *E2Binary = dyn_cast<BinaryOperator>(S2))
return IsStructurallyEquivalent(Context, E1CXXOperatorCall, E2Binary);
}

// Compare the statements itself.
StmtComparer Comparer(Context);
if (!Comparer.IsEquivalent(S1, S2))
Expand Down
170 changes: 170 additions & 0 deletions clang/unittests/AST/StructuralEquivalenceTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,176 @@ TEST_F(StructuralEquivalenceStmtTest, UnaryOperatorDifferentOps) {
EXPECT_FALSE(testStructuralMatch(t));
}

TEST_F(StructuralEquivalenceStmtTest,
CXXOperatorCallExprVsUnaryBinaryOperator) {
auto t = makeNamedDecls(
R"(
template <typename T, T x>
class A;
template <typename T, T x, T y>
void foo(
A<T, x + y>,
A<T, x - y>,
A<T, -x>,
A<T, x * y>,
A<T, *x>,
A<T, x / y>,
A<T, x % y>,
A<T, x ^ y>,
A<T, x & y>,
A<T, &x>,
A<T, x | y>,
A<T, ~x>,
A<T, !x>,
A<T, x < y>,
A<T, (x > y)>,
A<T, x << y>,
A<T, (x >> y)>,
A<T, x == y>,
A<T, x != y>,
A<T, x <= y>,
A<T, x >= y>,
A<T, x <=> y>,
A<T, x && y>,
A<T, x || y>,
A<T, ++x>,
A<T, --x>,
A<T, (x , y)>,
A<T, x ->* y>,
A<T, x -> y>
);
)",
R"(
struct Bar {
Bar& operator=(Bar&);
Bar& operator->();
};
Bar& operator+(Bar&, Bar&);
Bar& operator+(Bar&);
Bar& operator-(Bar&, Bar&);
Bar& operator-(Bar&);
Bar& operator*(Bar&, Bar&);
Bar& operator*(Bar&);
Bar& operator/(Bar&, Bar&);
Bar& operator%(Bar&, Bar&);
Bar& operator^(Bar&, Bar&);
Bar& operator&(Bar&, Bar&);
Bar& operator&(Bar&);
Bar& operator|(Bar&, Bar&);
Bar& operator~(Bar&);
Bar& operator!(Bar&);
Bar& operator<(Bar&, Bar&);
Bar& operator>(Bar&, Bar&);
Bar& operator+=(Bar&, Bar&);
Bar& operator-=(Bar&, Bar&);
Bar& operator*=(Bar&, Bar&);
Bar& operator/=(Bar&, Bar&);
Bar& operator%=(Bar&, Bar&);
Bar& operator^=(Bar&, Bar&);
Bar& operator&=(Bar&, Bar&);
Bar& operator|=(Bar&, Bar&);
Bar& operator<<(Bar&, Bar&);
Bar& operator>>(Bar&, Bar&);
Bar& operator<<=(Bar&, Bar&);
Bar& operator>>=(Bar&, Bar&);
Bar& operator==(Bar&, Bar&);
Bar& operator!=(Bar&, Bar&);
Bar& operator<=(Bar&, Bar&);
Bar& operator>=(Bar&, Bar&);
Bar& operator<=>(Bar&, Bar&);
Bar& operator&&(Bar&, Bar&);
Bar& operator||(Bar&, Bar&);
Bar& operator++(Bar&);
Bar& operator--(Bar&);
Bar& operator,(Bar&, Bar&);
Bar& operator->*(Bar&, Bar&);
template <typename T, T x>
class A;
template <typename T, T x, T y>
void foo(
A<T, x + y>,
A<T, x - y>,
A<T, -x>,
A<T, x * y>,
A<T, *x>,
A<T, x / y>,
A<T, x % y>,
A<T, x ^ y>,
A<T, x & y>,
A<T, &x>,
A<T, x | y>,
A<T, ~x>,
A<T, !x>,
A<T, x < y>,
A<T, (x > y)>,
A<T, x << y>,
A<T, (x >> y)>,
A<T, x == y>,
A<T, x != y>,
A<T, x <= y>,
A<T, x >= y>,
A<T, x <=> y>,
A<T, x && y>,
A<T, x || y>,
A<T, ++x>,
A<T, --x>,
A<T, (x , y)>,
A<T, x ->* y>,
A<T, x -> y>
);
)",
Lang_CXX20);
EXPECT_TRUE(testStructuralMatch(t));
}

TEST_F(StructuralEquivalenceStmtTest,
CXXOperatorCallExprVsUnaryBinaryOperatorNe) {
auto t = makeNamedDecls(
R"(
template <typename T, T x>
class A;
template <typename T, T x, T y>
void foo(
A<T, x + y>
);
)",
R"(
struct Bar;
Bar& operator-(Bar&, Bar&);
template <typename T, T x>
class A;
template <typename T, T x, T y>
void foo(
A<T, x - y>
);
)",
Lang_CXX11);
EXPECT_FALSE(testStructuralMatch(t));
}

TEST_F(StructuralEquivalenceStmtTest, NonTypeTemplateParm) {
auto t = makeNamedDecls(
R"(
template <typename T, T x>
class A;
template <typename T, T x, T y>
void foo(A<T, x>);
)",
R"(
template <typename T, T x>
class A;
template <typename T, T x, T y>
void foo(A<T, y>);
)",
Lang_CXX11);
// FIXME: These should not match,
EXPECT_TRUE(testStructuralMatch(t));
}

TEST_F(StructuralEquivalenceStmtTest, UnresolvedLookupDifferentName) {
auto t = makeStmts(
R"(
Expand Down

0 comments on commit 9ca1a08

Please sign in to comment.