[clang] 1ba89dd - [AST]Fix Location and Range for reversed rewritten CXXOperatorCallExpr (#192467)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 17 06:15:56 PDT 2026
Author: Fred Tingaud
Date: 2026-04-17T13:15:51Z
New Revision: 1ba89ddd9b2baa5e5a1fdd98dfccc07cbfc1c0c3
URL: https://github.com/llvm/llvm-project/commit/1ba89ddd9b2baa5e5a1fdd98dfccc07cbfc1c0c3
DIFF: https://github.com/llvm/llvm-project/commit/1ba89ddd9b2baa5e5a1fdd98dfccc07cbfc1c0c3.diff
LOG: [AST]Fix Location and Range for reversed rewritten CXXOperatorCallExpr (#192467)
In C++20+, when `a != b` is automatically rewritten to `!(b == a)`, the
range and sourceLocation of the generated nodes are incorrect and the
range has begin > end.
Assisted-by: Claude code
Added:
clang/test/AST/ast-dump-cxx20-reversed-operator.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/ExprCXX.h
clang/include/clang/AST/Stmt.h
clang/lib/AST/ExprCXX.cpp
clang/lib/Sema/SemaOverload.cpp
clang/lib/Serialization/ASTReaderStmt.cpp
clang/lib/Serialization/ASTWriterDecl.cpp
clang/lib/Serialization/ASTWriterStmt.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d1eefd57a2e1e..46dc5bb97463e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -498,6 +498,7 @@ Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed a bug where explicit nullability property attributes were not stored in AST nodes in Objective-C. (#GH179703)
- Fixed a crash when parsing Doxygen ``@param`` commands attached to invalid declarations or non-function entities. (#GH182737)
+- Fixed the SourceLocation and SourceRange of reversed rewritten CXXOperatorCallExpr. (#GH192467)
Miscellaneous Bug Fixes
^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 948f18b31a5fc..bfdff25a2509b 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -96,7 +96,7 @@ class CXXOperatorCallExpr final : public CallExpr {
CXXOperatorCallExpr(OverloadedOperatorKind OpKind, Expr *Fn,
ArrayRef<Expr *> Args, QualType Ty, ExprValueKind VK,
SourceLocation OperatorLoc, FPOptionsOverride FPFeatures,
- ADLCallKind UsesADL);
+ ADLCallKind UsesADL, bool IsReversed);
CXXOperatorCallExpr(unsigned NumArgs, bool HasFPFeatures, EmptyShell Empty);
@@ -105,7 +105,7 @@ class CXXOperatorCallExpr final : public CallExpr {
Create(const ASTContext &Ctx, OverloadedOperatorKind OpKind, Expr *Fn,
ArrayRef<Expr *> Args, QualType Ty, ExprValueKind VK,
SourceLocation OperatorLoc, FPOptionsOverride FPFeatures,
- ADLCallKind UsesADL = NotADL);
+ ADLCallKind UsesADL = NotADL, bool IsReversed = false);
static CXXOperatorCallExpr *CreateEmpty(const ASTContext &Ctx,
unsigned NumArgs, bool HasFPFeatures,
@@ -142,6 +142,9 @@ class CXXOperatorCallExpr final : public CallExpr {
}
bool isComparisonOp() const { return isComparisonOp(getOperator()); }
+ /// Whether this is a C++20 rewritten reversed operator.
+ bool isReversed() const { return CXXOperatorCallExprBits.IsReversed; }
+
/// Is this written as an infix binary operator?
bool isInfixBinaryOp() const;
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 1711d05a16d93..d940aa6562c4c 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -785,6 +785,11 @@ class alignas(void *) Stmt {
/// value of OverloadedOperatorKind.
LLVM_PREFERRED_TYPE(OverloadedOperatorKind)
unsigned OperatorKind : 6;
+
+ /// Whether this is a C++20 rewritten reversed operator, where the
+ /// arguments are in reversed source order.
+ LLVM_PREFERRED_TYPE(bool)
+ unsigned IsReversed : 1;
};
class CXXRewrittenBinaryOperatorBitfields {
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index dd603bf548926..be1bd3ba913ea 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -605,10 +605,11 @@ CXXOperatorCallExpr::CXXOperatorCallExpr(OverloadedOperatorKind OpKind,
QualType Ty, ExprValueKind VK,
SourceLocation OperatorLoc,
FPOptionsOverride FPFeatures,
- ADLCallKind UsesADL)
+ ADLCallKind UsesADL, bool IsReversed)
: CallExpr(CXXOperatorCallExprClass, Fn, /*PreArgs=*/{}, Args, Ty, VK,
OperatorLoc, FPFeatures, /*MinNumArgs=*/0, UsesADL) {
CXXOperatorCallExprBits.OperatorKind = OpKind;
+ CXXOperatorCallExprBits.IsReversed = IsReversed;
assert(
(CXXOperatorCallExprBits.OperatorKind == static_cast<unsigned>(OpKind)) &&
"OperatorKind overflow!");
@@ -620,12 +621,11 @@ CXXOperatorCallExpr::CXXOperatorCallExpr(unsigned NumArgs, bool HasFPFeatures,
: CallExpr(CXXOperatorCallExprClass, /*NumPreArgs=*/0, NumArgs,
HasFPFeatures, Empty) {}
-CXXOperatorCallExpr *
-CXXOperatorCallExpr::Create(const ASTContext &Ctx,
- OverloadedOperatorKind OpKind, Expr *Fn,
- ArrayRef<Expr *> Args, QualType Ty,
- ExprValueKind VK, SourceLocation OperatorLoc,
- FPOptionsOverride FPFeatures, ADLCallKind UsesADL) {
+CXXOperatorCallExpr *CXXOperatorCallExpr::Create(
+ const ASTContext &Ctx, OverloadedOperatorKind OpKind, Expr *Fn,
+ ArrayRef<Expr *> Args, QualType Ty, ExprValueKind VK,
+ SourceLocation OperatorLoc, FPOptionsOverride FPFeatures,
+ ADLCallKind UsesADL, bool IsReversed) {
// Allocate storage for the trailing objects of CallExpr.
unsigned NumArgs = Args.size();
unsigned SizeOfTrailingObjects = CallExpr::sizeOfTrailingObjects(
@@ -635,7 +635,7 @@ CXXOperatorCallExpr::Create(const ASTContext &Ctx,
SizeOfTrailingObjects),
alignof(CXXOperatorCallExpr));
return new (Mem) CXXOperatorCallExpr(OpKind, Fn, Args, Ty, VK, OperatorLoc,
- FPFeatures, UsesADL);
+ FPFeatures, UsesADL, IsReversed);
}
CXXOperatorCallExpr *CXXOperatorCallExpr::CreateEmpty(const ASTContext &Ctx,
@@ -670,6 +670,8 @@ SourceRange CXXOperatorCallExpr::getSourceRangeImpl() const {
} else if (getNumArgs() == 1) {
return SourceRange(getOperatorLoc(), getArg(0)->getEndLoc());
} else if (getNumArgs() == 2) {
+ if (CXXOperatorCallExprBits.IsReversed)
+ return SourceRange(getArg(1)->getBeginLoc(), getArg(0)->getEndLoc());
return SourceRange(getArg(0)->getBeginLoc(), getArg(1)->getEndLoc());
} else {
return getOperatorLoc();
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 170699085ea11..96c4ce489fe04 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -15702,7 +15702,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
TheCall = CXXOperatorCallExpr::Create(
Context, ChosenOp, FnExpr.get(), Args, ResultTy, VK, OpLoc,
CurFPFeatureOverrides(),
- static_cast<CallExpr::ADLCallKind>(Best->IsADLCandidate));
+ static_cast<CallExpr::ADLCallKind>(Best->IsADLCandidate),
+ IsReversed);
if (const auto *Method = dyn_cast<CXXMethodDecl>(FnDecl);
Method && Method->isImplicitObjectMemberFunction()) {
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index fb81e4fefdebb..4ada1dc58042d 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1787,6 +1787,7 @@ void ASTStmtReader::VisitMSDependentExistsStmt(MSDependentExistsStmt *S) {
void ASTStmtReader::VisitCXXOperatorCallExpr(CXXOperatorCallExpr *E) {
VisitCallExpr(E);
E->CXXOperatorCallExprBits.OperatorKind = Record.readInt();
+ E->CXXOperatorCallExprBits.IsReversed = Record.readInt();
E->BeginLoc = Record.readSourceLocation();
}
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index e415ac1e47862..08107aca85bfc 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -2923,6 +2923,7 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Source Location
// CXXOperatorCallExpr
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Operator Kind
+ Abv->Add(BitCodeAbbrevOp(0)); // IsReversed
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Source Location
CXXOperatorCallExprAbbrev = Stream.EmitAbbrev(std::move(Abv));
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 4612cd2a7944d..47cbef06c3cc4 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1770,10 +1770,11 @@ void ASTStmtWriter::VisitMSDependentExistsStmt(MSDependentExistsStmt *S) {
void ASTStmtWriter::VisitCXXOperatorCallExpr(CXXOperatorCallExpr *E) {
VisitCallExpr(E);
Record.push_back(E->getOperator());
+ Record.push_back(E->isReversed());
Record.AddSourceLocation(E->BeginLoc);
if (!E->hasStoredFPFeatures() && !static_cast<bool>(E->getADLCallKind()) &&
- !E->isCoroElideSafe() && !E->usesMemberSyntax())
+ !E->isCoroElideSafe() && !E->usesMemberSyntax() && !E->isReversed())
AbbrevToUse = Writer.getCXXOperatorCallExprAbbrev();
Code = serialization::EXPR_CXX_OPERATOR_CALL;
diff --git a/clang/test/AST/ast-dump-cxx20-reversed-operator.cpp b/clang/test/AST/ast-dump-cxx20-reversed-operator.cpp
new file mode 100644
index 0000000000000..a3053b1d47135
--- /dev/null
+++ b/clang/test/AST/ast-dump-cxx20-reversed-operator.cpp
@@ -0,0 +1,29 @@
+// Test without serialization:
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown -ast-dump %s \
+// RUN: | FileCheck -strict-whitespace %s
+//
+// Test with serialization:
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown -emit-pch -o %t %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown -x c++ \
+// RUN: -include-pch %t -ast-dump-all /dev/null \
+// RUN: | sed -e "s/ <undeserialized declarations>//" -e "s/ imported//" \
+// RUN: | FileCheck -strict-whitespace %s
+
+// Verify that a C++20 reversed rewritten operator does not produce inverted
+// source ranges on the synthesized CXXOperatorCallExpr and UnaryOperator.
+// When `A{} != B{}` is rewritten as `!(B{}.operator==(A{}))`, the arguments
+// are reversed in the AST but the source ranges must stay in source order.
+
+struct B {
+ bool operator==(B);
+};
+struct A {
+ operator B();
+};
+bool x = A{} != B{};
+
+// CHECK: VarDecl {{.*}} <line:23:1, col:19> col:6 x 'bool'
+// CHECK-NEXT: `-ExprWithCleanups {{.*}} <col:10, col:19>
+// CHECK-NEXT: `-CXXRewrittenBinaryOperator {{.*}} <col:10, col:19>
+// CHECK-NEXT: `-UnaryOperator {{.*}} <col:14, col:19> 'bool' prefix '!'
+// CHECK-NEXT: `-CXXOperatorCallExpr {{.*}} <col:10, col:19> 'bool' '=='
More information about the cfe-commits
mailing list