Skip to content

Commit 9ca1a08

Browse files
authored
[clang][ASTImporter] Improve structural equivalence of overloadable operators. (#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.
1 parent f1226ee commit 9ca1a08

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed

clang/lib/AST/ASTStructuralEquivalence.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
9898
QualType T1, QualType T2);
9999
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
100100
Decl *D1, Decl *D2);
101+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
102+
const Stmt *S1, const Stmt *S2);
101103
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
102104
const TemplateArgument &Arg1,
103105
const TemplateArgument &Arg2);
@@ -437,12 +439,67 @@ class StmtComparer {
437439
};
438440
} // namespace
439441

442+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
443+
const UnaryOperator *E1,
444+
const CXXOperatorCallExpr *E2) {
445+
return UnaryOperator::getOverloadedOperator(E1->getOpcode()) ==
446+
E2->getOperator() &&
447+
IsStructurallyEquivalent(Context, E1->getSubExpr(), E2->getArg(0));
448+
}
449+
450+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
451+
const CXXOperatorCallExpr *E1,
452+
const UnaryOperator *E2) {
453+
return E1->getOperator() ==
454+
UnaryOperator::getOverloadedOperator(E2->getOpcode()) &&
455+
IsStructurallyEquivalent(Context, E1->getArg(0), E2->getSubExpr());
456+
}
457+
458+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
459+
const BinaryOperator *E1,
460+
const CXXOperatorCallExpr *E2) {
461+
return BinaryOperator::getOverloadedOperator(E1->getOpcode()) ==
462+
E2->getOperator() &&
463+
IsStructurallyEquivalent(Context, E1->getLHS(), E2->getArg(0)) &&
464+
IsStructurallyEquivalent(Context, E1->getRHS(), E2->getArg(1));
465+
}
466+
467+
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
468+
const CXXOperatorCallExpr *E1,
469+
const BinaryOperator *E2) {
470+
return E1->getOperator() ==
471+
BinaryOperator::getOverloadedOperator(E2->getOpcode()) &&
472+
IsStructurallyEquivalent(Context, E1->getArg(0), E2->getLHS()) &&
473+
IsStructurallyEquivalent(Context, E1->getArg(1), E2->getRHS());
474+
}
475+
440476
/// Determine structural equivalence of two statements.
441477
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
442478
const Stmt *S1, const Stmt *S2) {
443479
if (!S1 || !S2)
444480
return S1 == S2;
445481

482+
// Check for statements with similar syntax but different AST.
483+
// A UnaryOperator node is more lightweight than a CXXOperatorCallExpr node.
484+
// The more heavyweight node is only created if the definition-time name
485+
// lookup had any results. The lookup results are stored CXXOperatorCallExpr
486+
// only. The lookup results can be different in a "From" and "To" AST even if
487+
// the compared structure is otherwise equivalent. For this reason we must
488+
// treat a similar unary/binary operator node and CXXOperatorCall node as
489+
// equivalent.
490+
if (const auto *E2CXXOperatorCall = dyn_cast<CXXOperatorCallExpr>(S2)) {
491+
if (const auto *E1Unary = dyn_cast<UnaryOperator>(S1))
492+
return IsStructurallyEquivalent(Context, E1Unary, E2CXXOperatorCall);
493+
if (const auto *E1Binary = dyn_cast<BinaryOperator>(S1))
494+
return IsStructurallyEquivalent(Context, E1Binary, E2CXXOperatorCall);
495+
}
496+
if (const auto *E1CXXOperatorCall = dyn_cast<CXXOperatorCallExpr>(S1)) {
497+
if (const auto *E2Unary = dyn_cast<UnaryOperator>(S2))
498+
return IsStructurallyEquivalent(Context, E1CXXOperatorCall, E2Unary);
499+
if (const auto *E2Binary = dyn_cast<BinaryOperator>(S2))
500+
return IsStructurallyEquivalent(Context, E1CXXOperatorCall, E2Binary);
501+
}
502+
446503
// Compare the statements itself.
447504
StmtComparer Comparer(Context);
448505
if (!Comparer.IsEquivalent(S1, S2))

clang/unittests/AST/StructuralEquivalenceTest.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,176 @@ TEST_F(StructuralEquivalenceStmtTest, UnaryOperatorDifferentOps) {
22752275
EXPECT_FALSE(testStructuralMatch(t));
22762276
}
22772277

2278+
TEST_F(StructuralEquivalenceStmtTest,
2279+
CXXOperatorCallExprVsUnaryBinaryOperator) {
2280+
auto t = makeNamedDecls(
2281+
R"(
2282+
template <typename T, T x>
2283+
class A;
2284+
template <typename T, T x, T y>
2285+
void foo(
2286+
A<T, x + y>,
2287+
A<T, x - y>,
2288+
A<T, -x>,
2289+
A<T, x * y>,
2290+
A<T, *x>,
2291+
A<T, x / y>,
2292+
A<T, x % y>,
2293+
A<T, x ^ y>,
2294+
A<T, x & y>,
2295+
A<T, &x>,
2296+
A<T, x | y>,
2297+
A<T, ~x>,
2298+
A<T, !x>,
2299+
A<T, x < y>,
2300+
A<T, (x > y)>,
2301+
A<T, x << y>,
2302+
A<T, (x >> y)>,
2303+
A<T, x == y>,
2304+
A<T, x != y>,
2305+
A<T, x <= y>,
2306+
A<T, x >= y>,
2307+
A<T, x <=> y>,
2308+
A<T, x && y>,
2309+
A<T, x || y>,
2310+
A<T, ++x>,
2311+
A<T, --x>,
2312+
A<T, (x , y)>,
2313+
A<T, x ->* y>,
2314+
A<T, x -> y>
2315+
);
2316+
)",
2317+
R"(
2318+
struct Bar {
2319+
Bar& operator=(Bar&);
2320+
Bar& operator->();
2321+
};
2322+
2323+
Bar& operator+(Bar&, Bar&);
2324+
Bar& operator+(Bar&);
2325+
Bar& operator-(Bar&, Bar&);
2326+
Bar& operator-(Bar&);
2327+
Bar& operator*(Bar&, Bar&);
2328+
Bar& operator*(Bar&);
2329+
Bar& operator/(Bar&, Bar&);
2330+
Bar& operator%(Bar&, Bar&);
2331+
Bar& operator^(Bar&, Bar&);
2332+
Bar& operator&(Bar&, Bar&);
2333+
Bar& operator&(Bar&);
2334+
Bar& operator|(Bar&, Bar&);
2335+
Bar& operator~(Bar&);
2336+
Bar& operator!(Bar&);
2337+
Bar& operator<(Bar&, Bar&);
2338+
Bar& operator>(Bar&, Bar&);
2339+
Bar& operator+=(Bar&, Bar&);
2340+
Bar& operator-=(Bar&, Bar&);
2341+
Bar& operator*=(Bar&, Bar&);
2342+
Bar& operator/=(Bar&, Bar&);
2343+
Bar& operator%=(Bar&, Bar&);
2344+
Bar& operator^=(Bar&, Bar&);
2345+
Bar& operator&=(Bar&, Bar&);
2346+
Bar& operator|=(Bar&, Bar&);
2347+
Bar& operator<<(Bar&, Bar&);
2348+
Bar& operator>>(Bar&, Bar&);
2349+
Bar& operator<<=(Bar&, Bar&);
2350+
Bar& operator>>=(Bar&, Bar&);
2351+
Bar& operator==(Bar&, Bar&);
2352+
Bar& operator!=(Bar&, Bar&);
2353+
Bar& operator<=(Bar&, Bar&);
2354+
Bar& operator>=(Bar&, Bar&);
2355+
Bar& operator<=>(Bar&, Bar&);
2356+
Bar& operator&&(Bar&, Bar&);
2357+
Bar& operator||(Bar&, Bar&);
2358+
Bar& operator++(Bar&);
2359+
Bar& operator--(Bar&);
2360+
Bar& operator,(Bar&, Bar&);
2361+
Bar& operator->*(Bar&, Bar&);
2362+
2363+
template <typename T, T x>
2364+
class A;
2365+
template <typename T, T x, T y>
2366+
void foo(
2367+
A<T, x + y>,
2368+
A<T, x - y>,
2369+
A<T, -x>,
2370+
A<T, x * y>,
2371+
A<T, *x>,
2372+
A<T, x / y>,
2373+
A<T, x % y>,
2374+
A<T, x ^ y>,
2375+
A<T, x & y>,
2376+
A<T, &x>,
2377+
A<T, x | y>,
2378+
A<T, ~x>,
2379+
A<T, !x>,
2380+
A<T, x < y>,
2381+
A<T, (x > y)>,
2382+
A<T, x << y>,
2383+
A<T, (x >> y)>,
2384+
A<T, x == y>,
2385+
A<T, x != y>,
2386+
A<T, x <= y>,
2387+
A<T, x >= y>,
2388+
A<T, x <=> y>,
2389+
A<T, x && y>,
2390+
A<T, x || y>,
2391+
A<T, ++x>,
2392+
A<T, --x>,
2393+
A<T, (x , y)>,
2394+
A<T, x ->* y>,
2395+
A<T, x -> y>
2396+
);
2397+
)",
2398+
Lang_CXX20);
2399+
EXPECT_TRUE(testStructuralMatch(t));
2400+
}
2401+
2402+
TEST_F(StructuralEquivalenceStmtTest,
2403+
CXXOperatorCallExprVsUnaryBinaryOperatorNe) {
2404+
auto t = makeNamedDecls(
2405+
R"(
2406+
template <typename T, T x>
2407+
class A;
2408+
template <typename T, T x, T y>
2409+
void foo(
2410+
A<T, x + y>
2411+
);
2412+
)",
2413+
R"(
2414+
struct Bar;
2415+
2416+
Bar& operator-(Bar&, Bar&);
2417+
2418+
template <typename T, T x>
2419+
class A;
2420+
template <typename T, T x, T y>
2421+
void foo(
2422+
A<T, x - y>
2423+
);
2424+
)",
2425+
Lang_CXX11);
2426+
EXPECT_FALSE(testStructuralMatch(t));
2427+
}
2428+
2429+
TEST_F(StructuralEquivalenceStmtTest, NonTypeTemplateParm) {
2430+
auto t = makeNamedDecls(
2431+
R"(
2432+
template <typename T, T x>
2433+
class A;
2434+
template <typename T, T x, T y>
2435+
void foo(A<T, x>);
2436+
)",
2437+
R"(
2438+
template <typename T, T x>
2439+
class A;
2440+
template <typename T, T x, T y>
2441+
void foo(A<T, y>);
2442+
)",
2443+
Lang_CXX11);
2444+
// FIXME: These should not match,
2445+
EXPECT_TRUE(testStructuralMatch(t));
2446+
}
2447+
22782448
TEST_F(StructuralEquivalenceStmtTest, UnresolvedLookupDifferentName) {
22792449
auto t = makeStmts(
22802450
R"(

0 commit comments

Comments
 (0)