From 5196685acd7b44d7dd346ed102c4ce1458babec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hana=20Dus=C3=ADkov=C3=A1?= Date: Thu, 13 Feb 2025 08:54:55 +0100 Subject: [PATCH 01/30] declcall-squashed declcall (P2825R5) prototype ported from hanickadot/llvm-project onto intel/llvm sycl. (cherry picked from commit 4c54efc218e2b81e2c014b5f328e30f6151ab191) --- clang-tools-extra/clang-tidy/utils/Matchers.h | 2 +- clang/include/clang/AST/APValue.h | 9 ++- clang/include/clang/AST/ComputeDependence.h | 2 + .../include/clang/AST/EvaluatedExprVisitor.h | 1 + clang/include/clang/AST/ExprCXX.h | 42 ++++++++++ clang/include/clang/AST/PropertiesBase.td | 5 +- clang/include/clang/AST/RecursiveASTVisitor.h | 1 + clang/include/clang/AST/TextNodeDumper.h | 1 + clang/include/clang/ASTMatchers/ASTMatchers.h | 4 + .../include/clang/Basic/DiagnosticLexKinds.td | 2 + .../clang/Basic/DiagnosticSemaKinds.td | 7 ++ clang/include/clang/Basic/StmtNodes.td | 1 + clang/include/clang/Basic/TokenKinds.def | 6 ++ clang/include/clang/Sema/Sema.h | 5 ++ .../include/clang/Serialization/ASTBitCodes.h | 1 + clang/lib/AST/APValue.cpp | 20 +++-- clang/lib/AST/ASTImporter.cpp | 18 ++++- clang/lib/AST/ByteCode/Compiler.cpp | 6 ++ clang/lib/AST/ByteCode/Compiler.h | 1 + clang/lib/AST/ComputeDependence.cpp | 4 + clang/lib/AST/Expr.cpp | 1 + clang/lib/AST/ExprClassification.cpp | 1 + clang/lib/AST/ExprConstant.cpp | 62 ++++++++++----- clang/lib/AST/ItaniumMangle.cpp | 7 ++ clang/lib/AST/StmtPrinter.cpp | 7 ++ clang/lib/AST/StmtProfile.cpp | 4 + clang/lib/AST/TextNodeDumper.cpp | 11 +++ clang/lib/ASTMatchers/ASTMatchersInternal.cpp | 2 + clang/lib/ASTMatchers/Dynamic/Registry.cpp | 1 + clang/lib/Analysis/UnsafeBufferUsage.cpp | 7 ++ clang/lib/CodeGen/CGCXXABI.cpp | 4 +- clang/lib/CodeGen/CGCXXABI.h | 4 +- clang/lib/CodeGen/CGExprConstant.cpp | 2 +- clang/lib/CodeGen/CGExprScalar.cpp | 26 ++++++ clang/lib/CodeGen/ItaniumCXXABI.cpp | 19 ++--- clang/lib/CodeGen/MicrosoftCXXABI.cpp | 10 +-- clang/lib/Edit/RewriteObjCFoundationAPI.cpp | 1 + clang/lib/Parse/ParseExpr.cpp | 19 +++++ clang/lib/Sema/SemaExceptionSpec.cpp | 1 + clang/lib/Sema/SemaExpr.cpp | 1 + clang/lib/Sema/SemaExprCXX.cpp | 79 +++++++++++++++++++ clang/lib/Sema/SemaFixItUtils.cpp | 1 + clang/lib/Sema/TreeTransform.h | 19 +++++ clang/lib/Serialization/ASTReaderStmt.cpp | 10 +++ clang/lib/Serialization/ASTWriterStmt.cpp | 7 ++ clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 + clang/tools/libclang/CXCursor.cpp | 1 + 47 files changed, 395 insertions(+), 51 deletions(-) diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h b/clang-tools-extra/clang-tidy/utils/Matchers.h index fea3ef041df1c..aa979b2c6cde0 100644 --- a/clang-tools-extra/clang-tidy/utils/Matchers.h +++ b/clang-tools-extra/clang-tidy/utils/Matchers.h @@ -58,7 +58,7 @@ AST_MATCHER(QualType, isSimpleChar) { } AST_MATCHER(Expr, hasUnevaluatedContext) { - if (isa(Node) || isa(Node)) + if (isa(Node) || isa(Node) || isa(Node)) return true; if (const auto *UnaryExpr = dyn_cast(&Node)) { switch (UnaryExpr->getKind()) { diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index acbd922ba5319..57c5486d4b6e7 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -436,9 +436,9 @@ class APValue { /// \param IsDerivedMember Whether member is a derived one. /// \param Path The path of the member. APValue(const ValueDecl *Member, bool IsDerivedMember, - ArrayRef Path) + ArrayRef Path, bool DeVirtualized = false) : Kind(None), AllowConstexprUnknown(false) { - MakeMemberPointer(Member, IsDerivedMember, Path); + MakeMemberPointer(Member, IsDerivedMember, Path, DeVirtualized); } /// Creates a new address label diff APValue. /// \param LHSExpr The left-hand side of the difference. @@ -691,6 +691,7 @@ class APValue { const ValueDecl *getMemberPointerDecl() const; bool isMemberPointerToDerivedMember() const; ArrayRef getMemberPointerPath() const; + bool isDeVirtualized() const; const AddrLabelExpr* getAddrLabelDiffLHS() const { assert(isAddrLabelDiff() && "Invalid accessor"); @@ -799,7 +800,7 @@ class APValue { Kind = Union; } void MakeMemberPointer(const ValueDecl *Member, bool IsDerivedMember, - ArrayRef Path); + ArrayRef Path, bool DeVirtualized); void MakeAddrLabelDiff() { assert(isAbsent() && "Bad state change"); new ((void *)(char *)&Data) AddrLabelDiffData(); @@ -831,7 +832,7 @@ class APValue { bool OnePastTheEnd, bool IsNullPtr); MutableArrayRef setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember, - unsigned Size); + unsigned Size, bool DeVirtualized); }; } // end namespace clang. diff --git a/clang/include/clang/AST/ComputeDependence.h b/clang/include/clang/AST/ComputeDependence.h index 5044f63fe0765..d88698d2331d5 100644 --- a/clang/include/clang/AST/ComputeDependence.h +++ b/clang/include/clang/AST/ComputeDependence.h @@ -64,6 +64,7 @@ class CXXDeleteExpr; class ArrayTypeTraitExpr; class ExpressionTraitExpr; class CXXNoexceptExpr; +class CXXDeclcallExpr; class PackExpansionExpr; class PackIndexingExpr; class SubstNonTypeTemplateParmExpr; @@ -161,6 +162,7 @@ ExprDependence computeDependence(CXXDeleteExpr *E); ExprDependence computeDependence(ArrayTypeTraitExpr *E); ExprDependence computeDependence(ExpressionTraitExpr *E); ExprDependence computeDependence(CXXNoexceptExpr *E, CanThrowResult CT); +ExprDependence computeDependence(CXXDeclcallExpr *E); ExprDependence computeDependence(PackExpansionExpr *E); ExprDependence computeDependence(PackIndexingExpr *E); ExprDependence computeDependence(SubstNonTypeTemplateParmExpr *E); diff --git a/clang/include/clang/AST/EvaluatedExprVisitor.h b/clang/include/clang/AST/EvaluatedExprVisitor.h index 2991f2859ac4e..d4d40d8838901 100644 --- a/clang/include/clang/AST/EvaluatedExprVisitor.h +++ b/clang/include/clang/AST/EvaluatedExprVisitor.h @@ -48,6 +48,7 @@ class EvaluatedExprVisitorBase : public StmtVisitorBase { void VisitBlockExpr(PTR(BlockExpr) E) { } void VisitCXXUuidofExpr(PTR(CXXUuidofExpr) E) { } void VisitCXXNoexceptExpr(PTR(CXXNoexceptExpr) E) { } + void VisitCXXDeclcallExpr(PTR(CXXDeclcallExpr) E) { } void VisitMemberExpr(PTR(MemberExpr) E) { // Only the base matters. diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index f1c8559cbff9b..72fe70418b733 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4343,6 +4343,48 @@ class CXXNoexceptExpr : public Expr { } }; +/// Represents a C++26 declcall expression (C++ [??]). +/// +/// An object which gives you member function pointer or function pointer +class CXXDeclcallExpr : public Expr { + friend class ASTStmtReader; + + Stmt *Operand; + SourceRange Range; + + bool Devirtualize = false; +public: + CXXDeclcallExpr(QualType Ty, Expr *Operand, bool Devirtualize, + SourceLocation Keyword, SourceLocation RParen) + : Expr(CXXDeclcallExprClass, Ty, VK_PRValue, OK_Ordinary), + Operand(Operand), Range(Keyword, RParen), Devirtualize(Devirtualize) { + setDependence(computeDependence(this)); + } + + CXXDeclcallExpr(EmptyShell Empty) : Expr(CXXDeclcallExprClass, Empty) {} + + Expr *getOperand() const { return static_cast(Operand); } + + SourceLocation getBeginLoc() const { return Range.getBegin(); } + SourceLocation getEndLoc() const { return Range.getEnd(); } + SourceRange getSourceRange() const { return Range; } + + static bool classof(const Stmt *T) { + return T->getStmtClass() == CXXDeclcallExprClass; + } + + bool isDevirtualized() const { + return Devirtualize; + } + + // Iterators + child_range children() { return child_range(&Operand, &Operand + 1); } + + const_child_range children() const { + return const_child_range(&Operand, &Operand + 1); + } +}; + /// Represents a C++11 pack expansion that produces a sequence of /// expressions. /// diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td index fd3cce10be303..145fcd4ee3947 100644 --- a/clang/include/clang/AST/PropertiesBase.td +++ b/clang/include/clang/AST/PropertiesBase.td @@ -466,11 +466,14 @@ let Class = PropertyTypeCase in { def : Property<"memberPath", Array> { let Read = [{ node.getMemberPointerPath() }]; } + def : Property<"isDeVirtualized", Bool> { + let Read = [{ node.isDeVirtualized() }]; + } def : Creator<[{ APValue result; unsigned pathSize = memberPath.size(); const CXXRecordDecl **pathArray = - result.setMemberPointerUninit(member, isDerived, pathSize).data(); + result.setMemberPointerUninit(member, isDerived, pathSize, isDeVirtualized).data(); for (unsigned i = 0; i < pathSize; ++i) pathArray[i] = memberPath[i]->getCanonicalDecl(); return result; diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 82c8185d3748a..52b705064fce1 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -3121,6 +3121,7 @@ DEF_TRAVERSE_STMT(UnaryOperator, {}) DEF_TRAVERSE_STMT(BinaryOperator, {}) DEF_TRAVERSE_STMT(CompoundAssignOperator, {}) DEF_TRAVERSE_STMT(CXXNoexceptExpr, {}) +DEF_TRAVERSE_STMT(CXXDeclcallExpr, {}) DEF_TRAVERSE_STMT(PackExpansionExpr, {}) DEF_TRAVERSE_STMT(SizeOfPackExpr, {}) DEF_TRAVERSE_STMT(PackIndexingExpr, {}) diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h index 640a4ea38d394..11f20b2ed6e6d 100644 --- a/clang/include/clang/AST/TextNodeDumper.h +++ b/clang/include/clang/AST/TextNodeDumper.h @@ -301,6 +301,7 @@ class TextNodeDumper void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *Node); void VisitCXXStaticCastExpr(const CXXStaticCastExpr *Node); void VisitCXXUnresolvedConstructExpr(const CXXUnresolvedConstructExpr *Node); + void VisitCXXNoexceptExpr(const CXXNoexceptExpr *Node); void VisitCXXConstructExpr(const CXXConstructExpr *Node); void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *Node); void VisitCXXNewExpr(const CXXNewExpr *Node); diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h index 8108ee559acea..fc79e64248e74 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -2057,6 +2057,10 @@ extern const internal::VariadicDynCastAllOfMatcher extern const internal::VariadicDynCastAllOfMatcher cxxNoexceptExpr; +/// Matches declcall expressions. +extern const internal::VariadicDynCastAllOfMatcher + cxxDeclcallExpr; + /// Matches a loop initializing the elements of an array in a number of contexts: /// * in the implicit copy/move constructor for a class with an array member /// * when a lambda-expression captures an array by value diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index 79e74a846e3ea..06905c67de8cc 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -74,6 +74,8 @@ def warn_cxx11_keyword : Warning<"'%0' is a keyword in C++11">, InGroup, DefaultIgnore; def warn_cxx20_keyword : Warning<"'%0' is a keyword in C++20">, InGroup, DefaultIgnore; +def warn_cxx26_keyword : Warning<"'%0' is a keyword in C++26">, + InGroup, DefaultIgnore; def warn_c99_keyword : Warning<"'%0' is a keyword in C99">, InGroup, DefaultIgnore; def warn_c23_keyword : Warning<"'%0' is a keyword in C23">, diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 568a18e2c375c..15156d24076b6 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10161,6 +10161,13 @@ def note_inequality_comparison_to_or_assign : Note< def err_incomplete_type_used_in_type_trait_expr : Error< "incomplete type %0 used in type trait expression">, NoSFINAE; +def err_declcall_must_contain_a_call : Error< + "declcall doesn't contain a call">; +def err_declcall_not_implemented_for : Error< + "declcall doesn't support %0 yet">; +def err_declcall_must_be_constant_evaluable : Error< + "declcall must not depend on runtime known value">; + // C++20 constinit and require_constant_initialization attribute def warn_cxx20_compat_constinit : Warning<"'constinit' specifier is incompatible with C++ standards before " diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td index f4245f8afea1f..9e2f2d77b6294 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -166,6 +166,7 @@ def OverloadExpr : StmtNode; def UnresolvedLookupExpr : StmtNode; def UnresolvedMemberExpr : StmtNode; def CXXNoexceptExpr : StmtNode; +def CXXDeclcallExpr : StmtNode; def PackExpansionExpr : StmtNode; def SizeOfPackExpr : StmtNode; def PackIndexingExpr : StmtNode; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index d24f4c9b3107d..02b0666d41638 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -28,6 +28,9 @@ #ifndef CXX20_KEYWORD #define CXX20_KEYWORD(X,Y) KEYWORD(X,KEYCXX20|(Y)) #endif +#ifndef CXX26_KEYWORD +#define CXX26_KEYWORD(X,Y) KEYWORD(X,KEYCXX26|(Y)) +#endif #ifndef C99_KEYWORD #define C99_KEYWORD(X,Y) KEYWORD(X,KEYC99|(Y)) #endif @@ -444,6 +447,9 @@ CXX20_KEYWORD(constinit , 0) CXX20_KEYWORD(concept , 0) CXX20_KEYWORD(requires , 0) +// C++26 keywords. +CXX26_KEYWORD(declcall , 0) + // Not a CXX20_KEYWORD because it is disabled by -fno-char8_t. KEYWORD(char8_t , CHAR8SUPPORT) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1fe05f2299596..e345879296cd1 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -8730,6 +8730,11 @@ class Sema final : public SemaBase { ExprResult BuildCXXNoexceptExpr(SourceLocation KeyLoc, Expr *Operand, SourceLocation RParen); + ExprResult ActOnDeclcallExpr(SourceLocation KeyLoc, SourceLocation LParen, + Expr *Operand, SourceLocation RParen); + ExprResult BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, + SourceLocation RParen); + ExprResult ActOnStartCXXMemberReference(Scope *S, Expr *Base, SourceLocation OpLoc, tok::TokenKind OpKind, diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 4815a7cb57018..baa133a523291 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -1937,6 +1937,7 @@ enum StmtCode { EXPR_CXX_EXPRESSION_TRAIT, // ExpressionTraitExpr EXPR_CXX_NOEXCEPT, // CXXNoexceptExpr + EXPR_CXX_DECLCALL, // CXXDeclCallExpr EXPR_OPAQUE_VALUE, // OpaqueValueExpr EXPR_BINARY_CONDITIONAL_OPERATOR, // BinaryConditionalOperator diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp index fd51584f564bb..22c1724eca47c 100644 --- a/clang/lib/AST/APValue.cpp +++ b/clang/lib/AST/APValue.cpp @@ -242,6 +242,7 @@ namespace { struct MemberPointerBase { llvm::PointerIntPair MemberAndIsDerivedMember; unsigned PathLength; + bool DeVirtualized; }; } @@ -254,7 +255,7 @@ struct APValue::MemberPointerData : MemberPointerBase { PathElem *PathPtr; }; - MemberPointerData() { PathLength = 0; } + MemberPointerData() { PathLength = 0; DeVirtualized = false; } ~MemberPointerData() { resizePath(0); } void resizePath(unsigned Length) { @@ -362,7 +363,7 @@ APValue::APValue(const APValue &RHS) case MemberPointer: MakeMemberPointer(RHS.getMemberPointerDecl(), RHS.isMemberPointerToDerivedMember(), - RHS.getMemberPointerPath()); + RHS.getMemberPointerPath(), RHS.isDeVirtualized()); break; case AddrLabelDiff: MakeAddrLabelDiff(); @@ -621,6 +622,7 @@ void APValue::Profile(llvm::FoldingSetNodeID &ID) const { case MemberPointer: ID.AddPointer(getMemberPointerDecl()); ID.AddInteger(isMemberPointerToDerivedMember()); + ID.AddInteger(isDeVirtualized()); for (const CXXRecordDecl *D : getMemberPointerPath()) ID.AddPointer(D); return; @@ -1102,6 +1104,13 @@ ArrayRef APValue::getMemberPointerPath() const { return {MPD.getPath(), MPD.PathLength}; } +bool APValue::isDeVirtualized() const { + assert(isMemberPointer() && "Invalid accessor"); + const MemberPointerData &MPD = + *((const MemberPointerData *)(const char *)&Data); + return MPD.DeVirtualized; +} + void APValue::MakeLValue() { assert(isAbsent() && "Bad state change"); static_assert(sizeof(LV) <= DataSize, "LV too big"); @@ -1117,10 +1126,11 @@ void APValue::MakeArray(unsigned InitElts, unsigned Size) { MutableArrayRef APValue::setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember, - unsigned Size) { + unsigned Size, bool DeVirtualized) { assert(isAbsent() && "Bad state change"); MemberPointerData *MPD = new ((void *)(char *)&Data) MemberPointerData; Kind = MemberPointer; + MPD->DeVirtualized = DeVirtualized; MPD->MemberAndIsDerivedMember.setPointer( Member ? cast(Member->getCanonicalDecl()) : nullptr); MPD->MemberAndIsDerivedMember.setInt(IsDerivedMember); @@ -1129,9 +1139,9 @@ APValue::setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember, } void APValue::MakeMemberPointer(const ValueDecl *Member, bool IsDerivedMember, - ArrayRef Path) { + ArrayRef Path, bool DeVirtualized) { MutableArrayRef InternalPath = - setMemberPointerUninit(Member, IsDerivedMember, Path.size()); + setMemberPointerUninit(Member, IsDerivedMember, Path.size(), DeVirtualized); for (unsigned I = 0; I != Path.size(); ++I) InternalPath[I] = Path[I]->getCanonicalDecl(); } diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index b64db01d572b1..2ddbe1715f964 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -666,6 +666,7 @@ namespace clang { ExpectedStmt VisitOffsetOfExpr(OffsetOfExpr *OE); ExpectedStmt VisitCXXThrowExpr(CXXThrowExpr *E); ExpectedStmt VisitCXXNoexceptExpr(CXXNoexceptExpr *E); + ExpectedStmt VisitCXXDeclcallExpr(CXXDeclcallExpr *E); ExpectedStmt VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E); ExpectedStmt VisitCXXScalarValueInitExpr(CXXScalarValueInitExpr *E); ExpectedStmt VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E); @@ -8561,6 +8562,20 @@ ExpectedStmt ASTNodeImporter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { ToType, ToOperand, ToCanThrow, ToBeginLoc, ToEndLoc); } +ExpectedStmt ASTNodeImporter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { + Error Err = Error::success(); + auto ToType = importChecked(Err, E->getType()); + auto ToOperand = importChecked(Err, E->getOperand()); // FIXME: dependent values?! + auto ToBeginLoc = importChecked(Err, E->getBeginLoc()); + auto ToEndLoc = importChecked(Err, E->getEndLoc()); + if (Err) + return std::move(Err); + + return new (Importer.getToContext()) CXXDeclcallExpr( + ToType, ToOperand, E->isDevirtualized(), ToBeginLoc, ToEndLoc); +} + + ExpectedStmt ASTNodeImporter::VisitCXXThrowExpr(CXXThrowExpr *E) { Error Err = Error::success(); auto ToSubExpr = importChecked(Err, E->getSubExpr()); @@ -10866,7 +10881,8 @@ ASTNodeImporter::ImportAPValue(const APValue &FromValue) { Result.setMemberPointerUninit( cast(ImpMemPtrDecl), FromValue.isMemberPointerToDerivedMember(), - FromValue.getMemberPointerPath().size()); + FromValue.getMemberPointerPath().size(), + FromValue.isDeVirtualized()); ArrayRef FromPath = Result.getMemberPointerPath(); for (unsigned Idx = 0; Idx < FromValue.getMemberPointerPath().size(); Idx++) { diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 113bf3c964421..e26740ee69b8e 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -3820,6 +3820,12 @@ bool Compiler::VisitCXXNoexceptExpr(const CXXNoexceptExpr *E) { return this->emitConstBool(E->getValue(), E); } +template +bool Compiler::VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { + assert(false && "unimplemented"); + return false; +} + template bool Compiler::VisitCXXConstructExpr(const CXXConstructExpr *E) { QualType T = E->getType(); diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 8af918f58c06d..b21a1683b2e76 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -201,6 +201,7 @@ class Compiler : public ConstStmtVisitor, bool>, bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E); bool VisitCXXDynamicCastExpr(const CXXDynamicCastExpr *E); bool VisitCXXNoexceptExpr(const CXXNoexceptExpr *E); + bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E); bool VisitCXXConstructExpr(const CXXConstructExpr *E); bool VisitSourceLocExpr(const SourceLocExpr *E); bool VisitOffsetOfExpr(const OffsetOfExpr *E); diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp index 3394c3ea264d9..5bc81bdf5a8b8 100644 --- a/clang/lib/AST/ComputeDependence.cpp +++ b/clang/lib/AST/ComputeDependence.cpp @@ -378,6 +378,10 @@ ExprDependence clang::computeDependence(CXXNoexceptExpr *E, CanThrowResult CT) { return D; } +ExprDependence clang::computeDependence(CXXDeclcallExpr *E) { + return E->getOperand()->getDependence(); +} + ExprDependence clang::computeDependence(PackExpansionExpr *E) { return (E->getPattern()->getDependence() & ~ExprDependence::UnexpandedPack) | ExprDependence::TypeValueInstantiation; diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index 0e876a258061c..51e6156d622c1 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -3803,6 +3803,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx, case ArrayTypeTraitExprClass: case ExpressionTraitExprClass: case CXXNoexceptExprClass: + case CXXDeclcallExprClass: case SizeOfPackExprClass: case ObjCStringLiteralClass: case ObjCEncodeExprClass: diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp index 7e5299425f9e2..356bd75402f2c 100644 --- a/clang/lib/AST/ExprClassification.cpp +++ b/clang/lib/AST/ExprClassification.cpp @@ -191,6 +191,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) { case Expr::BlockExprClass: case Expr::FloatingLiteralClass: case Expr::CXXNoexceptExprClass: + case Expr::CXXDeclcallExprClass: case Expr::CXXScalarValueInitExprClass: case Expr::TypeTraitExprClass: case Expr::ArrayTypeTraitExprClass: diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 6dae4c0c89fc2..c862adf535437 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1627,7 +1627,7 @@ namespace { } void moveInto(APValue &V) const { - V = APValue(getDecl(), isDerivedMember(), Path); + V = APValue(getDecl(), isDerivedMember(), Path, DeVirtualized); } void setFrom(const APValue &V) { assert(V.isMemberPointer()); @@ -1635,6 +1635,7 @@ namespace { DeclAndIsDerivedMember.setInt(V.isMemberPointerToDerivedMember()); Path.clear(); llvm::append_range(Path, V.getMemberPointerPath()); + DeVirtualized = V.isDeVirtualized(); } /// DeclAndIsDerivedMember - The member declaration, and a flag indicating @@ -1644,6 +1645,7 @@ namespace { /// Path - The path of base/derived classes from the member declaration's /// class (exclusive) to the class type of the member pointer (inclusive). SmallVector Path; + bool DeVirtualized = false; /// Perform a cast towards the class of the Decl (either up or down the /// hierarchy). @@ -5288,21 +5290,27 @@ static bool EvaluateObjectArgument(EvalInfo &Info, const Expr *Object, /// creating a bound member function. /// \return The field or method declaration to which the member pointer refers, /// or 0 if evaluation fails. -static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, + +struct MemberPointerAccessResult { + const ValueDecl * Decl; + bool DeVirtualized = false; +}; + +static const MemberPointerAccessResult HandleMemberPointerAccess(EvalInfo &Info, QualType LVType, LValue &LV, const Expr *RHS, bool IncludeMember = true) { MemberPtr MemPtr; if (!EvaluateMemberPointer(RHS, MemPtr, Info)) - return nullptr; + return {nullptr}; // C++11 [expr.mptr.oper]p6: If the second operand is the null pointer to // member value, the behavior is undefined. if (!MemPtr.getDecl()) { // FIXME: Specific diagnostic. Info.FFDiag(RHS); - return nullptr; + return {nullptr}; } if (MemPtr.isDerivedMember()) { @@ -5315,7 +5323,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, if (LV.Designator.MostDerivedPathLength + MemPtr.Path.size() > LV.Designator.Entries.size()) { Info.FFDiag(RHS); - return nullptr; + return {nullptr}; } unsigned PathLengthToMember = LV.Designator.Entries.size() - MemPtr.Path.size(); @@ -5325,7 +5333,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, const CXXRecordDecl *MPDecl = MemPtr.Path[I]; if (LVDecl->getCanonicalDecl() != MPDecl->getCanonicalDecl()) { Info.FFDiag(RHS); - return nullptr; + return {nullptr}; } } // MemPtr.Path only contains the base classes of the class directly @@ -5350,7 +5358,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, // Truncate the lvalue to the appropriate derived class. if (!CastToDerivedClass(Info, RHS, LV, MemPtr.getContainingRecord(), PathLengthToMember)) - return nullptr; + return {nullptr}; } else if (!MemPtr.Path.empty()) { // Extend the LValue path with the member pointer's path. LV.Designator.Entries.reserve(LV.Designator.Entries.size() + @@ -5365,33 +5373,33 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, for (unsigned I = 1, N = MemPtr.Path.size(); I != N; ++I) { const CXXRecordDecl *Base = MemPtr.Path[N - I - 1]; if (!HandleLValueDirectBase(Info, RHS, LV, RD, Base)) - return nullptr; + return {nullptr}; RD = Base; } // Finally cast to the class containing the member. if (!HandleLValueDirectBase(Info, RHS, LV, RD, MemPtr.getContainingRecord())) - return nullptr; + return {nullptr}; } // Add the member. Note that we cannot build bound member functions here. if (IncludeMember) { if (const FieldDecl *FD = dyn_cast(MemPtr.getDecl())) { if (!HandleLValueMember(Info, RHS, LV, FD)) - return nullptr; + return {nullptr}; } else if (const IndirectFieldDecl *IFD = dyn_cast(MemPtr.getDecl())) { if (!HandleLValueIndirectMember(Info, RHS, LV, IFD)) - return nullptr; + return {nullptr}; } else { llvm_unreachable("can't construct reference to bound member function"); } } - return MemPtr.getDecl(); + return {MemPtr.getDecl(), MemPtr.DeVirtualized}; } -static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, +static const MemberPointerAccessResult HandleMemberPointerAccess(EvalInfo &Info, const BinaryOperator *BO, LValue &LV, bool IncludeMember = true) { @@ -5402,7 +5410,7 @@ static const ValueDecl *HandleMemberPointerAccess(EvalInfo &Info, MemberPtr MemPtr; EvaluateMemberPointer(BO->getRHS(), MemPtr, Info); } - return nullptr; + return {nullptr}; } return HandleMemberPointerAccess(Info, BO->getLHS()->getType(), LV, @@ -8612,7 +8620,7 @@ class ExprEvaluatorBase case BO_PtrMemD: case BO_PtrMemI: { LValue Obj; - if (!HandleMemberPointerAccess(Info, E, Obj)) + if (!HandleMemberPointerAccess(Info, E, Obj).Decl) return false; APValue Result; if (!handleLValueToRValueConversion(Info, E, E->getType(), Obj, Result)) @@ -8748,14 +8756,15 @@ class ExprEvaluatorBase HasQualifier = ME->hasQualifier(); } else if (const BinaryOperator *BE = dyn_cast(Callee)) { // Indirect bound member calls ('.*' or '->*'). - const ValueDecl *D = + const auto &&[Decl, DeVirtualized] = HandleMemberPointerAccess(Info, BE, ObjectArg, false); - if (!D) + if (!Decl) return false; - Member = dyn_cast(D); + Member = dyn_cast(Decl); if (!Member) return Error(Callee); This = &ObjectArg; + HasQualifier = DeVirtualized; } else if (const auto *PDE = dyn_cast(Callee)) { if (!Info.getLangOpts().CPlusPlus20) Info.CCEDiag(PDE, diag::note_constexpr_pseudo_destructor); @@ -9245,7 +9254,7 @@ class LValueExprEvaluatorBase case BO_PtrMemD: case BO_PtrMemI: - return HandleMemberPointerAccess(this->Info, E, Result); + return HandleMemberPointerAccess(this->Info, E, Result).Decl; } } @@ -9595,7 +9604,7 @@ bool LValueExprEvaluator::VisitMaterializeTemporaryExpr( case SubobjectAdjustment::MemberPointerAdjustment: if (!HandleMemberPointerAccess(this->Info, Type, Result, - Adjustments[I].Ptr.RHS)) + Adjustments[I].Ptr.RHS).Decl) return false; Type = Adjustments[I].Ptr.MPT->getPointeeType(); break; @@ -10053,6 +10062,10 @@ class PointerExprEvaluator Result.addArray(Info, E, cast(ArrayTy)); return true; } + + bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { + return Visit(E->getOperand()); + } bool VisitSYCLUniqueStableIdExpr(const SYCLUniqueStableIdExpr *E) { std::string ResultStr = E->ComputeName(Info.Ctx); @@ -11121,6 +11134,13 @@ class MemberPointerExprEvaluator bool VisitCastExpr(const CastExpr *E); bool VisitUnaryAddrOf(const UnaryOperator *E); + bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { + bool out = Visit(E->getOperand()); + if (out && E->isDevirtualized()) { + Result.DeVirtualized = true; + } + return out; + } }; } // end anonymous namespace @@ -11183,6 +11203,7 @@ bool MemberPointerExprEvaluator::VisitUnaryAddrOf(const UnaryOperator *E) { return Success(cast(E->getSubExpr())->getDecl()); } + //===----------------------------------------------------------------------===// // Record Evaluation //===----------------------------------------------------------------------===// @@ -22349,6 +22370,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { case Expr::SYCLBuiltinNumFieldsExprClass: case Expr::SYCLBuiltinNumBasesExprClass: case Expr::CXXReflectExprClass: + case Expr::CXXDeclcallExprClass: return NoDiag(); case Expr::CallExprClass: case Expr::CXXOperatorCallExprClass: { diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 197a874eb180b..85595b27e83ed 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -5409,6 +5409,13 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity, mangleExpression(cast(E)->getOperand()); break; + case Expr::CXXDeclcallExprClass: + // FIXME: ??? + //NotPrimaryExpr(); + //Out << "nx"; + //mangleExpression(cast(E)->getOperand()); + break; + case Expr::UnaryExprOrTypeTraitExprClass: { // Non-instantiation-dependent traits are an integer literal. const UnaryExprOrTypeTraitExpr *SAE = cast(E); diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index ed830962a8949..f34e1913b1161 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -2729,6 +2729,13 @@ void StmtPrinter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { OS << ")"; } +void StmtPrinter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { + OS << "declcall("; + PrintExpr(E->getOperand()); + OS << ")"; +} + + void StmtPrinter::VisitPackExpansionExpr(PackExpansionExpr *E) { PrintExpr(E->getPattern()); OS << "..."; diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index c7c2dfce5b1e7..b225f32f37888 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -2399,6 +2399,10 @@ void StmtProfiler::VisitCXXNoexceptExpr(const CXXNoexceptExpr *S) { VisitExpr(S); } +void StmtProfiler::VisitCXXDeclcallExpr(const CXXDeclcallExpr *S) { + VisitExpr(S); +} + void StmtProfiler::VisitPackExpansionExpr(const PackExpansionExpr *S) { VisitExpr(S); } diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index 050277fbf5a9b..105ec9dfc3433 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -861,6 +861,8 @@ void TextNodeDumper::Visit(const APValue &Value, QualType Ty) { OS << MemDecl->getDeclName(); else OS << "null"; + if (Value.isDeVirtualized()) + OS << " (devirtualized)"; return; } case APValue::AddrLabelDiff: @@ -1811,6 +1813,15 @@ void TextNodeDumper::VisitCXXUnresolvedConstructExpr( OS << " list"; } +void TextNodeDumper::VisitCXXNoexceptExpr(const CXXNoexceptExpr *Node) { + if (Node->isValueDependent()) + OS << " noexcept()"; + else if (Node->getValue()) + OS << " noexcept(true)"; + else + OS << " noexcept(false)"; +} + void TextNodeDumper::VisitCXXConstructExpr(const CXXConstructExpr *Node) { CXXConstructorDecl *Ctor = Node->getConstructor(); dumpType(Ctor->getType()); diff --git a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp index 5cbf134620e34..ff7e8a626c2ce 100644 --- a/clang/lib/ASTMatchers/ASTMatchersInternal.cpp +++ b/clang/lib/ASTMatchers/ASTMatchersInternal.cpp @@ -921,6 +921,8 @@ const internal::VariadicDynCastAllOfMatcher cxxNewExpr; const internal::VariadicDynCastAllOfMatcher cxxDeleteExpr; const internal::VariadicDynCastAllOfMatcher cxxNoexceptExpr; +const internal::VariadicDynCastAllOfMatcher + cxxDeclcallExpr; const internal::VariadicDynCastAllOfMatcher arraySubscriptExpr; const internal::VariadicDynCastAllOfMatcher diff --git a/clang/lib/ASTMatchers/Dynamic/Registry.cpp b/clang/lib/ASTMatchers/Dynamic/Registry.cpp index a04070971f0eb..0c04b6075dc46 100644 --- a/clang/lib/ASTMatchers/Dynamic/Registry.cpp +++ b/clang/lib/ASTMatchers/Dynamic/Registry.cpp @@ -204,6 +204,7 @@ RegistryMaps::RegistryMaps() { REGISTER_MATCHER(cxxMethodDecl); REGISTER_MATCHER(cxxNewExpr); REGISTER_MATCHER(cxxNoexceptExpr); + REGISTER_MATCHER(cxxDeclcallExpr); REGISTER_MATCHER(cxxNullPtrLiteralExpr); REGISTER_MATCHER(cxxOperatorCallExpr); REGISTER_MATCHER(cxxRecordDecl); diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp index e80dfc82e60ba..d418899a942b2 100644 --- a/clang/lib/Analysis/UnsafeBufferUsage.cpp +++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -208,6 +208,13 @@ class MatchDescendantVisitor : public DynamicRecursiveASTVisitor { return true; return DynamicRecursiveASTVisitor::TraverseCXXNoexceptExpr(Node); } + + bool TraverseCXXDeclcallExpr(CXXDeclcallExpr *Node) { + // Unevaluated context. + if (ignoreUnevaluatedContext) + return true; + return VisitorBase::TraverseCXXDeclcallExpr(Node); + } bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) override { // Unevaluated context. diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp index 4a417dce88a1f..d7e2e887d7b3b 100644 --- a/clang/lib/CodeGen/CGCXXABI.cpp +++ b/clang/lib/CodeGen/CGCXXABI.cpp @@ -103,7 +103,7 @@ CGCXXABI::EmitNullMemberPointer(const MemberPointerType *MPT) { return GetBogusMemberPointer(QualType(MPT, 0)); } -llvm::Constant *CGCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) { +llvm::Constant *CGCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) { return GetBogusMemberPointer(CGM.getContext().getMemberPointerType( MD->getType(), /*Qualifier=*/std::nullopt, MD->getParent())); } @@ -113,7 +113,7 @@ llvm::Constant *CGCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT, return GetBogusMemberPointer(QualType(MPT, 0)); } -llvm::Constant *CGCXXABI::EmitMemberPointer(const APValue &MP, QualType MPT) { +llvm::Constant *CGCXXABI::EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual) { return GetBogusMemberPointer(MPT); } diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h index 47090276c56b0..25d6121b1de40 100644 --- a/clang/lib/CodeGen/CGCXXABI.h +++ b/clang/lib/CodeGen/CGCXXABI.h @@ -218,14 +218,14 @@ class CGCXXABI { virtual llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT); /// Create a member pointer for the given method. - virtual llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD); + virtual llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual = true); /// Create a member pointer for the given field. virtual llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset); /// Create a member pointer for the given member pointer constant. - virtual llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT); + virtual llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual = false); /// Emit a comparison between two member pointers. Returns an i1. virtual llvm::Value * diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 27b7423a42f98..7b7b7e8332e57 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -2725,7 +2725,7 @@ ConstantEmitter::tryEmitPrivate(const APValue &Value, QualType DestType, Filler); } case APValue::MemberPointer: - return CGM.getCXXABI().EmitMemberPointer(Value, DestType); + return CGM.getCXXABI().EmitMemberPointer(Value, DestType, !Value.isDeVirtualized()); } llvm_unreachable("Unknown APValue kind"); } diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index bbe1b48e06a94..4e1bd3daa2320 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -828,6 +828,32 @@ class ScalarExprEmitter Value *VisitCXXNoexceptExpr(const CXXNoexceptExpr *E) { return Builder.getInt1(E->getValue()); } + + Value *VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { + Expr::EvalResult Eval; + if (E->getType()->isMemberFunctionPointerType()) { + //E->dump(); + const UnaryOperator * uo = cast(E->getOperand()); + const ValueDecl *decl = cast(uo->getSubExpr())->getDecl(); + + // A member function pointer. + if (const CXXMethodDecl *method = dyn_cast(decl)) + return CGF.CGM.getCXXABI().EmitMemberFunctionPointer(method, !E->isDevirtualized()); + + assert(false); + return nullptr; + } else if (E->EvaluateAsConstantExpr(Eval, CGF.getContext())) { + APValue &Value = Eval.Val; + + //return ConstantEmitter(CGF).tryEmitAbstract(E->getOperand(), E->getType()); + return ConstantEmitter(CGF).tryEmitAbstract(Value, E->getType()); + } else { + return Visit(E->getOperand()); + } + // + //assert(false); + //return nullptr; + } // Binary Operators. Value *EmitMul(const BinOpInfo &Ops) { diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index fd3ee14175612..92cf6b470e9b6 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -149,12 +149,12 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI { llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT) override; - llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD) override; + llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) override; llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset) override; - llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT) override; + llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual) override; llvm::Constant *BuildMemberPointer(const CXXMethodDecl *MD, - CharUnits ThisAdjustment); + CharUnits ThisAdjustment, bool AllowVirtual = true); llvm::Value *EmitMemberPointerComparison(CodeGenFunction &CGF, llvm::Value *L, llvm::Value *R, @@ -1156,19 +1156,19 @@ ItaniumCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT, } llvm::Constant * -ItaniumCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) { - return BuildMemberPointer(MD, CharUnits::Zero()); +ItaniumCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) { + return BuildMemberPointer(MD, CharUnits::Zero(), AllowVirtual); } llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD, - CharUnits ThisAdjustment) { + CharUnits ThisAdjustment, bool AllowVirtual) { assert(MD->isInstance() && "Member function must not be static!"); CodeGenTypes &Types = CGM.getTypes(); // Get the function pointer (or index if this is a virtual function). llvm::Constant *MemPtr[2]; - if (MD->isVirtual()) { + if (MD->isVirtual() && AllowVirtual) { uint64_t Index = CGM.getItaniumVTableContext().getMethodVTableIndex(MD); uint64_t VTableOffset; if (CGM.getLangOpts().RelativeCXXABIVTables) { @@ -1247,7 +1247,8 @@ llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD, } llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP, - QualType MPType) { + QualType MPType, bool AllowVirtual) { + const MemberPointerType *MPT = MPType->castAs(); const ValueDecl *MPD = MP.getMemberPointerDecl(); if (!MPD) @@ -1256,7 +1257,7 @@ llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP, CharUnits ThisAdjustment = getContext().getMemberPointerPathAdjustment(MP); if (const CXXMethodDecl *MD = dyn_cast(MPD)) { - llvm::Constant *Src = BuildMemberPointer(MD, ThisAdjustment); + llvm::Constant *Src = BuildMemberPointer(MD, ThisAdjustment, AllowVirtual); QualType SrcType = getContext().getMemberPointerType( MD->getType(), /*Qualifier=*/std::nullopt, MD->getParent()); return pointerAuthResignMemberFunctionPointer(Src, MPType, SrcType, CGM); diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 78781da8fdfd4..dd8a223e4cf42 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -690,8 +690,8 @@ class MicrosoftCXXABI : public CGCXXABI { llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset) override; - llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD) override; - llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT) override; + llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) override; + llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual) override; llvm::Value *EmitMemberPointerComparison(CodeGenFunction &CGF, llvm::Value *L, @@ -2957,7 +2957,7 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberDataPointer(const CXXRecordDecl *RD, } llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP, - QualType MPType) { + QualType MPType, bool AllowVirtual) { const MemberPointerType *DstTy = MPType->castAs(); const ValueDecl *MPD = MP.getMemberPointerDecl(); if (!MPD) @@ -2968,7 +2968,7 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP, llvm::Constant *C; if (const CXXMethodDecl *MD = dyn_cast(MPD)) { - C = EmitMemberFunctionPointer(MD); + C = EmitMemberFunctionPointer(MD, AllowVirtual); } else { // For a pointer to data member, start off with the offset of the field in // the class in which it was declared, and convert from there if necessary. @@ -3021,7 +3021,7 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP, } llvm::Constant * -MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD) { +MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) { assert(MD->isInstance() && "Member function must not be static!"); CharUnits NonVirtualBaseAdjustment = CharUnits::Zero(); diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp index e8d4660fd36b2..3e58f9d078b21 100644 --- a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp +++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp @@ -917,6 +917,7 @@ static bool castOperatorNeedsParens(const Expr *FullExpr) { isa(Expr) || isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 56943087004d4..e46d9f245dd2b 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1501,6 +1501,25 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand, AllowSuffix = false; break; + case tok::kw_declcall: { + SourceLocation KeyLoc = ConsumeToken(); + BalancedDelimiterTracker T(*this, tok::l_paren); + + if (T.expectAndConsume(diag::err_expected_lparen_after, "declcall")) + return ExprError(); + + // it can't be marked as unevaluated as we need to keep all the instantiations + Res = ParseExpression(); + + T.consumeClose(); + + if (!Res.isInvalid()) + Res = Actions.ActOnDeclcallExpr(KeyLoc, T.getOpenLocation(), Res.get(), + T.getCloseLocation()); + AllowSuffix = false; + break; + } + case tok::kw_noexcept: { // [C++0x] 'noexcept' '(' expression ')' if (NotPrimaryExpression) *NotPrimaryExpression = true; diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 779d75a5bd4eb..024df669a9b64 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1393,6 +1393,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) { case Expr::TypeTraitExprClass: case Expr::CXXBoolLiteralExprClass: case Expr::CXXNoexceptExprClass: + case Expr::CXXDeclcallExprClass: case Expr::CXXNullPtrLiteralExprClass: case Expr::CXXPseudoDestructorExprClass: case Expr::CXXReflectExprClass: diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 8100afe45db37..402b6cf414494 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -15051,6 +15051,7 @@ static void diagnoseAddressOfInvalidType(Sema &S, SourceLocation Loc, bool Sema::CheckUseOfCXXMethodAsAddressOfOperand(SourceLocation OpLoc, const Expr *Op, const CXXMethodDecl *MD) { + return false; // FIXME allow this only in declcall const auto *DRE = cast(Op->IgnoreParens()); if (Op != DRE) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 7c23e814d42ff..73a8beb0870c2 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7492,6 +7492,85 @@ ExprResult Sema::ActOnNoexceptExpr(SourceLocation KeyLoc, SourceLocation, return BuildCXXNoexceptExpr(KeyLoc, Operand, RParen); } +ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, + SourceLocation RParen) { + // in template ... do it later + if (Operand->isInstantiationDependent()) { + return new (Context) + CXXDeclcallExpr(Context.DependentTy, Operand, false, KeyLoc, RParen); + } + + // unwrap parens + Operand = Operand->IgnoreParens(); + CallExpr * CE = dyn_cast(Operand); + + if (CE == nullptr) { + Diag(Operand->getExprLoc(), diag::err_declcall_must_contain_a_call); + Operand->dump(); + return ExprError(); + } + + const auto mustBeEvaluableInCompileTime = [this](Expr * e) { + Expr::EvalResult Eval; + if (!e->isValueDependent() && !e->EvaluateAsConstantExpr(Eval, Context)) { + return false; + } + return true; + }; + + + + if (CE->getCallee() == nullptr) { + return ExprError(); + } + + // Check if we are calling a method (it can be static.) + CXXMethodDecl * method = dyn_cast_or_null(CE->getCalleeDecl()) ; + + bool MemberCallHasQualifierAndIsVirtual = false; + + // transform method call + if (method && method->isInstance()) { + const auto memberCallHasQualifier = [](const CallExpr * e) { + const MemberExpr *ME = dyn_cast(e->getCallee()->IgnoreParens()); + if (!ME) + return false; + + return ME->hasQualifier(); + }; + + MemberCallHasQualifierAndIsVirtual = method->isVirtual() && memberCallHasQualifier(CE); + + // member function pointer + const auto T = QualType(method->getFunctionType(), 0); + + // create reference to the function + auto * DRE = DeclRefExpr::Create( + Context, NestedNameSpecifierLoc(), SourceLocation(), method, + /*RefersToEnclosingVariableOrCapture=*/false, CE->getExprLoc(), + T, CE->getValueKind(), nullptr, nullptr, NOUR_None); + + // get its address + Operand = CreateBuiltinUnaryOp(CE->getSourceRange().getBegin(), UO_AddrOf, DRE, false).get(); + } else { + + // convert function to pointer + Operand = CallExprUnaryConversions(CE->getCallee()->IgnoreParens()).get(); + } + + if (!mustBeEvaluableInCompileTime(Operand)) { + Diag(CE->getExprLoc(), diag::err_declcall_must_be_constant_evaluable); + } + + return new (Context) + CXXDeclcallExpr(Operand->getType(), Operand, MemberCallHasQualifierAndIsVirtual, KeyLoc, RParen); +} + +ExprResult Sema::ActOnDeclcallExpr(SourceLocation KeyLoc, SourceLocation, + Expr *Operand, SourceLocation RParen) { + return BuildCXXDeclcallExpr(KeyLoc, Operand, RParen); +} + static void MaybeDecrementCount( Expr *E, llvm::DenseMap &RefsMinusAssignments) { DeclRefExpr *LHS = nullptr; diff --git a/clang/lib/Sema/SemaFixItUtils.cpp b/clang/lib/Sema/SemaFixItUtils.cpp index 1dad46fd6b940..33ffe82f461b0 100644 --- a/clang/lib/Sema/SemaFixItUtils.cpp +++ b/clang/lib/Sema/SemaFixItUtils.cpp @@ -73,6 +73,7 @@ bool ConversionFixItGenerator::tryToFixConversion(const Expr *FullExpr, isa(Expr) || isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || isa(Expr) || isa(Expr) || diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index f2f06ddfdedb4..abaf62f9c744d 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -3779,6 +3779,10 @@ class TreeTransform { ExprResult RebuildCXXNoexceptExpr(SourceRange Range, Expr *Arg) { return SemaRef.BuildCXXNoexceptExpr(Range.getBegin(), Arg, Range.getEnd()); } + + ExprResult RebuildCXXDeclcallExpr(SourceRange Range, Expr *Arg) { + return SemaRef.BuildCXXDeclcallExpr(Range.getBegin(), Arg, Range.getEnd()); + } UnsignedOrNone ComputeSizeOfPackExprWithoutSubstitution(ArrayRef PackArgs); @@ -16770,6 +16774,21 @@ TreeTransform::TransformCXXNoexceptExpr(CXXNoexceptExpr *E) { return getDerived().RebuildCXXNoexceptExpr(E->getSourceRange(),SubExpr.get()); } +template +ExprResult +TreeTransform::TransformCXXDeclcallExpr(CXXDeclcallExpr *E) { + // we need to instantiate referenced functions, so it's evaluated scope + ExprResult SubExpr = getDerived().TransformExpr(E->getOperand()); + if (SubExpr.isInvalid()) + return ExprError(); + + if (!getDerived().AlwaysRebuild() && SubExpr.get() == E->getOperand()) + return E; + + return getDerived().RebuildCXXDeclcallExpr(E->getSourceRange(),SubExpr.get()); +} + + template ExprResult TreeTransform::TransformPackExpansionExpr(PackExpansionExpr *E) { diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index db5d1f03e15f1..25f3552a701e3 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -2309,6 +2309,12 @@ void ASTStmtReader::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { E->Operand = Record.readSubExpr(); } +void ASTStmtReader::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { + VisitExpr(E); + E->Range = readSourceRange(); + E->Operand = Record.readSubExpr(); +} + void ASTStmtReader::VisitPackExpansionExpr(PackExpansionExpr *E) { VisitExpr(E); E->EllipsisLoc = readSourceLocation(); @@ -4513,6 +4519,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { S = new (Context) CXXNoexceptExpr(Empty); break; + case EXPR_CXX_DECLCALL: + S = new (Context) CXXDeclcallExpr(Empty); + break; + case EXPR_PACK_EXPANSION: S = new (Context) PackExpansionExpr(Empty); break; diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 752d91e2a1678..462d9d9a29753 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -2328,6 +2328,13 @@ void ASTStmtWriter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { Code = serialization::EXPR_CXX_NOEXCEPT; } +void ASTStmtWriter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { + VisitExpr(E); + Record.AddSourceRange(E->getSourceRange()); + Record.AddStmt(E->getOperand()); + Code = serialization::EXPR_CXX_DECLCALL; +} + void ASTStmtWriter::VisitPackExpansionExpr(PackExpansionExpr *E) { VisitExpr(E); Record.AddSourceLocation(E->getEllipsisLoc()); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 00118db13f19b..ec7e5cb60744f 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1703,6 +1703,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::UnresolvedMemberExprClass: case Stmt::RecoveryExprClass: case Stmt::CXXNoexceptExprClass: + case Stmt::CXXDeclcallExprClass: case Stmt::PackExpansionExprClass: case Stmt::PackIndexingExprClass: case Stmt::SubstNonTypeTemplateParmPackExprClass: diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index 267f8a10b14f6..9669d1ae00855 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -433,6 +433,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, case Stmt::UnaryExprOrTypeTraitExprClass: case Stmt::CXXNoexceptExprClass: + case Stmt::CXXDeclcallExprClass: K = CXCursor_UnaryExpr; break; From c8090b355a87c60e601c0577a51cd4a84456b22f Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 11:28:46 -0700 Subject: [PATCH 02/30] Adapt declcall port to intel/llvm drift (KEYCXX26, visitor, return type) --- clang/include/clang/Basic/TokenKinds.h | 5 +++-- clang/lib/AST/ExprConstant.cpp | 2 +- clang/lib/Analysis/UnsafeBufferUsage.cpp | 6 +++--- clang/lib/Basic/IdentifierTable.cpp | 7 +++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Basic/TokenKinds.h b/clang/include/clang/Basic/TokenKinds.h index 89b2d43076511..7cb2511755a6f 100644 --- a/clang/include/clang/Basic/TokenKinds.h +++ b/clang/include/clang/Basic/TokenKinds.h @@ -53,8 +53,9 @@ enum TokenKey : unsigned { KEYFIXEDPOINT = 0x8000000, KEYDEFERTS = 0x10000000, KEYNOHLSL = 0x20000000, - KEYMAX = KEYNOHLSL, // The maximum key - KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20, + KEYCXX26 = 0x40000000, + KEYMAX = KEYCXX26, // The maximum key + KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20 | KEYCXX26, KEYALL = (KEYMAX | (KEYMAX - 1)) & ~KEYNOMS18 & ~KEYNOOPENCL & ~KEYNOHLSL // KEYNOMS18, KEYNOOPENCL, KEYNOHLSL excluded. }; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index c862adf535437..b0711c86688cf 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5352,7 +5352,7 @@ static const MemberPointerAccessResult HandleMemberPointerAccess(EvalInfo &Info, const CXXRecordDecl *LastMPDecl = MemPtr.getContainingRecord(); if (LastLVDecl->getCanonicalDecl() != LastMPDecl->getCanonicalDecl()) { Info.FFDiag(RHS); - return nullptr; + return {nullptr}; } // Truncate the lvalue to the appropriate derived class. diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp index d418899a942b2..0ef610ec1cb40 100644 --- a/clang/lib/Analysis/UnsafeBufferUsage.cpp +++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -209,11 +209,11 @@ class MatchDescendantVisitor : public DynamicRecursiveASTVisitor { return DynamicRecursiveASTVisitor::TraverseCXXNoexceptExpr(Node); } - bool TraverseCXXDeclcallExpr(CXXDeclcallExpr *Node) { + bool TraverseCXXDeclcallExpr(CXXDeclcallExpr *Node) override { // Unevaluated context. - if (ignoreUnevaluatedContext) + if (IgnoreUnevaluatedContext) return true; - return VisitorBase::TraverseCXXDeclcallExpr(Node); + return DynamicRecursiveASTVisitor::TraverseCXXDeclcallExpr(Node); } bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) override { diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index f11a72c7fa95c..bd82871fa209a 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -108,6 +108,10 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, if (LangOpts.CPlusPlus20) return KS_Enabled; return LangOpts.CPlusPlus ? KS_Future : KS_Unknown; + case KEYCXX26: + if (LangOpts.CPlusPlus26) + return KS_Enabled; + return LangOpts.CPlusPlus ? KS_Future : KS_Unknown; case KEYGNU: return LangOpts.GNUKeywords ? KS_Extension : KS_Unknown; case KEYMS: @@ -848,6 +852,9 @@ IdentifierTable::getFutureCompatDiagKind(const IdentifierInfo &II, if (((Flags & KEYCXX20) == KEYCXX20) || ((Flags & CHAR8SUPPORT) == CHAR8SUPPORT)) return diag::warn_cxx20_keyword; + + if ((Flags & KEYCXX26) == KEYCXX26) + return diag::warn_cxx26_keyword; } else { if ((Flags & KEYC99) == KEYC99) return diag::warn_c99_keyword; From 69e2852b0fd5209c523d0f59ae21012f7864581b Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 11:39:20 -0700 Subject: [PATCH 03/30] [clang] Add baseline tests for declcall (P2825) --- clang/test/AST/ast-dump-declcall.cpp | 9 +++++++++ clang/test/CodeGenCXX/declcall.cpp | 19 ++++++++++++++++++ clang/test/SemaCXX/declcall.cpp | 30 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 clang/test/AST/ast-dump-declcall.cpp create mode 100644 clang/test/CodeGenCXX/declcall.cpp create mode 100644 clang/test/SemaCXX/declcall.cpp diff --git a/clang/test/AST/ast-dump-declcall.cpp b/clang/test/AST/ast-dump-declcall.cpp new file mode 100644 index 0000000000000..4c1d63627bcc3 --- /dev/null +++ b/clang/test/AST/ast-dump-declcall.cpp @@ -0,0 +1,9 @@ +// RUN: %clang_cc1 -std=c++2c -ast-dump %s | FileCheck %s + +int f(int); +int f(char); +auto p = declcall(f(0)); + +// CHECK: CXXDeclcallExpr {{.*}} 'int (*)(int)' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int (*)(int)' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int (int)' {{.*}} Function {{.*}} 'f' 'int (int)' diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp new file mode 100644 index 0000000000000..f318b1654860b --- /dev/null +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s + +int f(int); +int f(char); +using FP = int (*)(int); + +// A qualified call to a virtual member is devirtualized: the constant is a +// direct pointer to the member, not a vtable index. +// CHECK: @pv = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 } +// A non-virtual member is a direct pointer as well. +// CHECK: @ph = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1hEi to i64), i64 0 } +struct B { virtual int g(int); int h(int); }; +auto pv = declcall(((B *)0)->B::g(0)); +auto ph = declcall(((B *)0)->h(0)); + +// A free-function declcall lowers to the address of the selected overload. +// CHECK-LABEL: define {{.*}} ptr @_Z3getv() +// CHECK: ret ptr @_Z1fi +FP get() { return declcall(f(0)); } diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp new file mode 100644 index 0000000000000..ea7d8ddf363d5 --- /dev/null +++ b/clang/test/SemaCXX/declcall.cpp @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify %s + +// Baseline semantics for declcall (P2825): overload resolution, operand +// requirements, and dependent-operand instantiation. + +int f(int); +int f(char); +using FPInt = int (*)(int); +using FPChar = int (*)(char); + +// declcall(f(0)) resolves the overload as if calling f(0) and yields a +// pointer to the selected function. +constexpr auto p = declcall(f(0)); +static_assert(__is_same(decltype(p), int (*const)(int))); +static_assert(p == static_cast(f)); + +// A char argument selects the other overload. +constexpr auto pc = declcall(f('a')); +static_assert(__is_same(decltype(pc), int (*const)(char))); + +// The operand must be a call expression. +int x; +auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}} + +// Dependent operands are resolved at instantiation time. +template auto call(T v) { return declcall(f(v)); } +void use() { + static_assert(__is_same(decltype(call(0)), FPInt)); + static_assert(__is_same(decltype(call('a')), FPChar)); +} From e258c75d2193272d0b5b95f93b0183773a28e5dd Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 11:41:50 -0700 Subject: [PATCH 04/30] [clang] Serialize the devirtualized flag of CXXDeclcallExpr --- clang/lib/Serialization/ASTReaderStmt.cpp | 1 + clang/lib/Serialization/ASTWriterStmt.cpp | 1 + clang/test/PCH/declcall.cpp | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 clang/test/PCH/declcall.cpp diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 25f3552a701e3..4fe2cabed67be 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -2313,6 +2313,7 @@ void ASTStmtReader::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { VisitExpr(E); E->Range = readSourceRange(); E->Operand = Record.readSubExpr(); + E->Devirtualize = Record.readInt(); } void ASTStmtReader::VisitPackExpansionExpr(PackExpansionExpr *E) { diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 462d9d9a29753..9897228f19ab4 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -2332,6 +2332,7 @@ void ASTStmtWriter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { VisitExpr(E); Record.AddSourceRange(E->getSourceRange()); Record.AddStmt(E->getOperand()); + Record.push_back(E->isDevirtualized()); Code = serialization::EXPR_CXX_DECLCALL; } diff --git a/clang/test/PCH/declcall.cpp b/clang/test/PCH/declcall.cpp new file mode 100644 index 0000000000000..6924d5c39a33f --- /dev/null +++ b/clang/test/PCH/declcall.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-pch -o %t %s +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -include-pch %t -emit-llvm %s -o - | FileCheck %s + +// Check that the devirtualized flag of a declcall expression survives +// serialization. The declcall is written into the PCH and re-evaluated in the +// second run. + +#ifndef HEADER +#define HEADER +struct B { virtual int g(int); }; +constexpr auto getptr() { return declcall(((B *)0)->B::g(0)); } +#else +auto p = getptr(); +// A direct pointer to the member (devirtualized), not a vtable index +// ({ i64 1, i64 0 }). +// CHECK: @p = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 } +#endif From a43876dae9f54061fc7ac78d8b38d59734b95b52 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 11:44:26 -0700 Subject: [PATCH 05/30] [clang] Return ExprError from failed declcall instead of a live node --- clang/lib/Sema/SemaExprCXX.cpp | 2 +- clang/test/SemaCXX/declcall.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 73a8beb0870c2..57fc68494a1c9 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7506,7 +7506,6 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, if (CE == nullptr) { Diag(Operand->getExprLoc(), diag::err_declcall_must_contain_a_call); - Operand->dump(); return ExprError(); } @@ -7560,6 +7559,7 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, if (!mustBeEvaluableInCompileTime(Operand)) { Diag(CE->getExprLoc(), diag::err_declcall_must_be_constant_evaluable); + return ExprError(); } return new (Context) diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index ea7d8ddf363d5..2d8c2fa8cd38d 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -22,6 +22,12 @@ static_assert(__is_same(decltype(pc), int (*const)(char))); int x; auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}} +// A call through a runtime function pointer is not constant-evaluable. This +// must produce exactly one diagnostic: the failed declcall must not leave a +// live expression behind that cascades into further errors. +int (*fp)(int) = &f; +constexpr auto rt = declcall(fp(0)); // expected-error {{declcall must not depend on runtime known value}} + // Dependent operands are resolved at instantiation time. template auto call(T v) { return declcall(f(v)); } void use() { From 4d66abc6fc4309b3202b12a62890aab55aeadcf6 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 11:49:22 -0700 Subject: [PATCH 06/30] [clang] Mangle declcall as an Itanium vendor extended expression --- clang/lib/AST/ItaniumMangle.cpp | 21 ++++++++++++++++----- clang/test/CodeGenCXX/mangle-declcall.cpp | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 clang/test/CodeGenCXX/mangle-declcall.cpp diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 85595b27e83ed..7ab1e551c15c1 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -5409,12 +5409,23 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity, mangleExpression(cast(E)->getOperand()); break; - case Expr::CXXDeclcallExprClass: - // FIXME: ??? - //NotPrimaryExpr(); - //Out << "nx"; - //mangleExpression(cast(E)->getOperand()); + case Expr::CXXDeclcallExprClass: { + // There is no standard Itanium mangling for declcall (P2825) yet. Mangle + // it as a vendor extended expression + // ::= u * E + // so that distinct operands mangle distinctly and the symbol stays + // demanglable. The operand is encoded as an template-arg + // (X E). + // FIXME: Replace with the ABI-assigned mangling once declcall is voted + // into the standard and the Itanium C++ ABI defines one. + NotPrimaryExpr(); + Out << "u8declcall"; + Out << 'X'; + mangleExpression(cast(E)->getOperand()); + Out << 'E'; + Out << 'E'; break; + } case Expr::UnaryExprOrTypeTraitExprClass: { // Non-instantiation-dependent traits are an integer literal. diff --git a/clang/test/CodeGenCXX/mangle-declcall.cpp b/clang/test/CodeGenCXX/mangle-declcall.cpp new file mode 100644 index 0000000000000..38bef13fcb313 --- /dev/null +++ b/clang/test/CodeGenCXX/mangle-declcall.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++2c -triple x86_64-pc-windows-msvc -emit-llvm %s -o /dev/null + +int f(int); +int g(int); + +template void sink(T); +template void h(decltype(declcall(f(T{}))) p) { sink(p); } +template void k(decltype(declcall(g(T{}))) p) { sink(p); } + +// A dependent declcall reaches the Itanium mangler. It is mangled as a vendor +// extended expression (u * E), so h and k -- which +// differ only in the declcall operand -- mangle distinctly, instead of both +// collapsing to a malformed, non-demanglable empty decltype ("DTE"). +// CHECK-DAG: define {{.*}} @_Z1hIiEvDTu8declcallXcl1ftlT_EEEEE +// CHECK-DAG: define {{.*}} @_Z1kIiEvDTu8declcallXcl1gtlT_EEEEE +template void h(int (*)(int)); +template void k(int (*)(int)); From 3dcc7ce234270d98f7e31b8931017bf31bc678ca Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 11:52:55 -0700 Subject: [PATCH 07/30] [clang] Emit declcall via constant evaluation instead of fragile AST casts --- clang/lib/CodeGen/CGExprScalar.cpp | 33 ++++++++++-------------------- clang/test/CodeGenCXX/declcall.cpp | 10 +++++++++ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 4e1bd3daa2320..2911c95d95fd2 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -830,29 +830,18 @@ class ScalarExprEmitter } Value *VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { + // A declcall is required to be constant-evaluable (enforced in Sema), so + // emit it as a constant. This covers both function pointers and member + // function pointers; the devirtualized flag is carried by the evaluated + // APValue and honored by the member-pointer constant emission. Expr::EvalResult Eval; - if (E->getType()->isMemberFunctionPointerType()) { - //E->dump(); - const UnaryOperator * uo = cast(E->getOperand()); - const ValueDecl *decl = cast(uo->getSubExpr())->getDecl(); - - // A member function pointer. - if (const CXXMethodDecl *method = dyn_cast(decl)) - return CGF.CGM.getCXXABI().EmitMemberFunctionPointer(method, !E->isDevirtualized()); - - assert(false); - return nullptr; - } else if (E->EvaluateAsConstantExpr(Eval, CGF.getContext())) { - APValue &Value = Eval.Val; - - //return ConstantEmitter(CGF).tryEmitAbstract(E->getOperand(), E->getType()); - return ConstantEmitter(CGF).tryEmitAbstract(Value, E->getType()); - } else { - return Visit(E->getOperand()); - } - // - //assert(false); - //return nullptr; + if (E->EvaluateAsConstantExpr(Eval, CGF.getContext())) + if (llvm::Constant *C = ConstantEmitter(CGF).tryEmitAbstract( + Eval.Val, E->getType())) + return C; + + // Fall back to emitting the resolved operand directly. + return Visit(E->getOperand()); } // Binary Operators. diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp index f318b1654860b..2c1bba29b41bf 100644 --- a/clang/test/CodeGenCXX/declcall.cpp +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -17,3 +17,13 @@ auto ph = declcall(((B *)0)->h(0)); // CHECK-LABEL: define {{.*}} ptr @_Z3getv() // CHECK: ret ptr @_Z1fi FP get() { return declcall(f(0)); } + +// A declcall in a local (non-constant-initializer) context is emitted through +// the scalar emitter; it must still produce a devirtualized direct pointer, +// not a vtable index. +// CHECK-LABEL: define {{.*}} @_Z3usePM1BFiiE +// CHECK: store { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 } +void use(int (B::**out)(int)) { + auto pv = declcall(((B *)0)->B::g(0)); + *out = pv; +} From 9475d8fed06e4ebd642a7675704e3029c227f692 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:01:40 -0700 Subject: [PATCH 08/30] [clang] Implement declcall in the bytecode interpreter, incl. devirtualization --- clang/lib/AST/ByteCode/Compiler.cpp | 14 ++++++++++++-- clang/lib/AST/ByteCode/Interp.cpp | 7 +++++++ clang/lib/AST/ByteCode/Interp.h | 1 + clang/lib/AST/ByteCode/MemberPointer.cpp | 3 ++- clang/lib/AST/ByteCode/MemberPointer.h | 6 ++++++ clang/lib/AST/ByteCode/Opcodes.td | 3 +++ clang/test/CodeGenCXX/declcall.cpp | 1 + 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index e26740ee69b8e..20c5f59cc375d 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -3822,8 +3822,18 @@ bool Compiler::VisitCXXNoexceptExpr(const CXXNoexceptExpr *E) { template bool Compiler::VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { - assert(false && "unimplemented"); - return false; + // declcall evaluates to its resolved operand: a pointer or member pointer + // to the function that overload resolution selected. + if (!this->delegate(E->getOperand())) + return false; + + // A declcall of a qualified virtual member is devirtualized: mark the + // resulting member pointer so it lowers to a direct pointer rather than a + // vtable index. isDevirtualized() implies a member function pointer. + if (E->isDevirtualized() && !DiscardResult) + return this->emitDevirtualizeMemberPtr(E); + + return true; } template diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 58bbf4aaf5f89..84fa4eca40b17 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -3142,6 +3142,13 @@ bool GetMemberPtrBase(InterpState &S) { return true; } +bool DevirtualizeMemberPtr(InterpState &S) { + MemberPointer MP = S.Stk.pop(); + MP.setDevirtualized(); + S.Stk.push(MP); + return true; +} + bool GetMemberPtrDecl(InterpState &S) { const auto &MP = S.Stk.pop(); diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 201679017c98b..7f40dc3b89723 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -3607,6 +3607,7 @@ inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Type *Ty) { bool GetMemberPtr(InterpState &S, const ValueDecl *D); bool GetMemberPtrBase(InterpState &S); bool GetMemberPtrDecl(InterpState &S); +bool DevirtualizeMemberPtr(InterpState &S); bool CopyMemberPtrPath(InterpState &S, const RecordDecl *Entry, bool IsDerived); /// Just emit a diagnostic. The expression that caused emission of this diff --git a/clang/lib/AST/ByteCode/MemberPointer.cpp b/clang/lib/AST/ByteCode/MemberPointer.cpp index bccadc4d4fc8f..6796a4f960970 100644 --- a/clang/lib/AST/ByteCode/MemberPointer.cpp +++ b/clang/lib/AST/ByteCode/MemberPointer.cpp @@ -84,7 +84,8 @@ APValue MemberPointer::toAPValue(const ASTContext &ASTCtx) const { return Base.toAPValue(ASTCtx); return APValue(getDecl(), /*IsDerivedMember=*/isDerivedMember(), - /*Path=*/ArrayRef(Path, PathLength)); + /*Path=*/ArrayRef(Path, PathLength), + /*DeVirtualized=*/DeVirtualized); } ComparisonCategoryResult diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h index b23acf7befc67..01a2cdd98ede3 100644 --- a/clang/lib/AST/ByteCode/MemberPointer.h +++ b/clang/lib/AST/ByteCode/MemberPointer.h @@ -33,6 +33,9 @@ class MemberPointer final { const CXXRecordDecl **Path = nullptr; int32_t PtrOffset = 0; uint8_t PathLength = 0; + /// Whether a declcall requested that virtual dispatch be skipped for this + /// member function pointer (see CXXDeclcallExpr). + bool DeVirtualized = false; MemberPointer(Pointer Base, const ValueDecl *Dcl, int32_t PtrOffset, uint8_t PathLength = 0, const CXXRecordDecl **Path = nullptr, @@ -63,6 +66,9 @@ class MemberPointer final { /// Does this member pointer have a base declaration? bool hasDecl() const { return DeclAndIsDerivedMember.getPointer(); } bool isDerivedMember() const { return DeclAndIsDerivedMember.getInt(); } + /// Whether virtual dispatch should be skipped (declcall devirtualization). + bool isDevirtualized() const { return DeVirtualized; } + void setDevirtualized(bool V = true) { DeVirtualized = V; } /// Return the base declaration. Might be null. const ValueDecl *getDecl() const { return DeclAndIsDerivedMember.getPointer(); diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 1705ad9d9d24b..2d6aa1f5d57ec 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -954,6 +954,9 @@ def GetMemberPtr : SuccessOpcode { def GetMemberPtrBase : Opcode { let NeedsOpPC = 0; } +def DevirtualizeMemberPtr : Opcode { + let NeedsOpPC = 0; +} def GetMemberPtrDecl : Opcode{ let NeedsOpPC = 0; } diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp index 2c1bba29b41bf..56b7854483b51 100644 --- a/clang/test/CodeGenCXX/declcall.cpp +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -fexperimental-new-constant-interpreter -emit-llvm %s -o - | FileCheck %s int f(int); int f(char); From d792da10e4d77a4f9a0fa600c7e308587f6bd8b3 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:06:18 -0700 Subject: [PATCH 09/30] [clang] Diagnose declcall of a pointer-to-member call instead of crashing --- clang/lib/Sema/SemaExprCXX.cpp | 25 +++++++++++++++++++++---- clang/test/SemaCXX/declcall.cpp | 7 +++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 57fc68494a1c9..e3e955201f64e 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7550,11 +7550,28 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, T, CE->getValueKind(), nullptr, nullptr, NOUR_None); // get its address - Operand = CreateBuiltinUnaryOp(CE->getSourceRange().getBegin(), UO_AddrOf, DRE, false).get(); + ExprResult AddrOf = CreateBuiltinUnaryOp(CE->getSourceRange().getBegin(), + UO_AddrOf, DRE, false); + if (AddrOf.isInvalid()) + return ExprError(); + Operand = AddrOf.get(); } else { - - // convert function to pointer - Operand = CallExprUnaryConversions(CE->getCallee()->IgnoreParens()).get(); + // A call through a pointer to member has no compile-time-known callee for + // declcall to resolve; P2825 handles such runtime cases via an explicit + // static_cast. Diagnose it rather than crashing on the failed conversion. + const Expr *Callee = CE->getCallee()->IgnoreParenImpCasts(); + if (const auto *BO = dyn_cast(Callee); + BO && BO->isPtrMemOp()) { + Diag(CE->getExprLoc(), diag::err_declcall_not_implemented_for) + << "a call through a pointer to member"; + return ExprError(); + } + + // Convert the callee (a function or function pointer) to a pointer. + ExprResult Conv = CallExprUnaryConversions(CE->getCallee()->IgnoreParens()); + if (Conv.isInvalid()) + return ExprError(); + Operand = Conv.get(); } if (!mustBeEvaluableInCompileTime(Operand)) { diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 2d8c2fa8cd38d..925f197eff98a 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -28,6 +28,13 @@ auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}} int (*fp)(int) = &f; constexpr auto rt = declcall(fp(0)); // expected-error {{declcall must not depend on runtime known value}} +// A call through a pointer to member has no compile-time-known callee. This +// must be diagnosed, not crash the compiler. +struct S { int m(int); }; +S s; +int (S::*pmf)(int); +auto pm = declcall((s.*pmf)(0)); // expected-error {{declcall doesn't support a call through a pointer to member yet}} + // Dependent operands are resolved at instantiation time. template auto call(T v) { return declcall(f(v)); } void use() { From 292ab531500122f16d4d2af90e0d157113bb375c Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:09:39 -0700 Subject: [PATCH 10/30] [clang][test] Lock that unqualified virtual declcall is not devirtualized --- clang/test/CodeGenCXX/declcall.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp index 56b7854483b51..018f49cf104b8 100644 --- a/clang/test/CodeGenCXX/declcall.cpp +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -10,9 +10,13 @@ using FP = int (*)(int); // CHECK: @pv = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1gEi to i64), i64 0 } // A non-virtual member is a direct pointer as well. // CHECK: @ph = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN1B1hEi to i64), i64 0 } +// An *unqualified* virtual call is NOT devirtualized: the constant keeps +// virtual dispatch, i.e. a vtable index ({ i64 1, i64 0 }). +// CHECK: @pu = {{.*}}global { i64, i64 } { i64 1, i64 0 } struct B { virtual int g(int); int h(int); }; auto pv = declcall(((B *)0)->B::g(0)); auto ph = declcall(((B *)0)->h(0)); +auto pu = declcall(((B *)0)->g(0)); // A free-function declcall lowers to the address of the selected overload. // CHECK-LABEL: define {{.*}} ptr @_Z3getv() From 66d06354903579ffd17fb5635f0bce9079c04ed1 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:10:15 -0700 Subject: [PATCH 11/30] [clang][test] Cover declcall noexcept type fidelity --- clang/test/SemaCXX/declcall.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 925f197eff98a..a4e82e1c51237 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -18,6 +18,14 @@ static_assert(p == static_cast(f)); constexpr auto pc = declcall(f('a')); static_assert(__is_same(decltype(pc), int (*const)(char))); +// declcall preserves the exact function type, including noexcept. +int nef(int) noexcept; +static_assert(__is_same(decltype(declcall(nef(0))), int (*)(int) noexcept)); +static_assert(!__is_same(decltype(declcall(nef(0))), int (*)(int))); +struct NE { int m(int) noexcept; }; +static_assert( + __is_same(decltype(declcall(((NE *)0)->m(0))), int (NE::*)(int) noexcept)); + // The operand must be a call expression. int x; auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}} From 156cf6cec71fc05a542fc5a1a308df2dd7ce38ce Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:11:02 -0700 Subject: [PATCH 12/30] [clang][test] Cover declcall function-template selection and instantiation --- clang/test/CodeGenCXX/declcall.cpp | 8 ++++++++ clang/test/SemaCXX/declcall.cpp | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp index 018f49cf104b8..c0b36c33b91ab 100644 --- a/clang/test/CodeGenCXX/declcall.cpp +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -32,3 +32,11 @@ void use(int (B::**out)(int)) { auto pv = declcall(((B *)0)->B::g(0)); *out = pv; } + +// declcall selects and instantiates a function template specialization, and +// that specialization is emitted. +template T tfn(T v) { return v; } +// CHECK-LABEL: define {{.*}} ptr @_Z8get_tmplv() +// CHECK: ret ptr @_Z3tfnIiET_S0_ +// CHECK: define {{.*}} @_Z3tfnIiET_S0_ +FP get_tmpl() { return declcall(tfn(0)); } diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index a4e82e1c51237..97f5d69dfc4b9 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -26,6 +26,11 @@ struct NE { int m(int) noexcept; }; static_assert( __is_same(decltype(declcall(((NE *)0)->m(0))), int (NE::*)(int) noexcept)); +// declcall deduces and selects the right function template specialization. +template T ttmpl(T); +static_assert(__is_same(decltype(declcall(ttmpl(0))), int (*)(int))); +static_assert(__is_same(decltype(declcall(ttmpl('a'))), char (*)(char))); + // The operand must be a call expression. int x; auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}} From 3f15eb8f17cb57ec80fec00500c5a955be4ef40a Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:12:11 -0700 Subject: [PATCH 13/30] [clang][test] Cover declcall on free and member operator calls --- clang/test/CodeGenCXX/declcall.cpp | 11 +++++++++++ clang/test/SemaCXX/declcall.cpp | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp index c0b36c33b91ab..07da7b3fc6323 100644 --- a/clang/test/CodeGenCXX/declcall.cpp +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -13,6 +13,10 @@ using FP = int (*)(int); // An *unqualified* virtual call is NOT devirtualized: the constant keeps // virtual dispatch, i.e. a vtable index ({ i64 1, i64 0 }). // CHECK: @pu = {{.*}}global { i64, i64 } { i64 1, i64 0 } +// declcall on an operator call selects the operator function: a free operator +// yields a function pointer, a member operator yields a member pointer. +// CHECK: @pfree = {{.*}}global ptr @_Zpl2OpS_ +// CHECK: @pmem = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN2OmmiES_ to i64), i64 0 } struct B { virtual int g(int); int h(int); }; auto pv = declcall(((B *)0)->B::g(0)); auto ph = declcall(((B *)0)->h(0)); @@ -40,3 +44,10 @@ template T tfn(T v) { return v; } // CHECK: ret ptr @_Z3tfnIiET_S0_ // CHECK: define {{.*}} @_Z3tfnIiET_S0_ FP get_tmpl() { return declcall(tfn(0)); } + +// Operator-call declcall values (checked with the other globals above). +struct Op {}; +int operator+(Op, Op); +struct Om { int operator-(Om); }; +auto pfree = declcall(Op{} + Op{}); +auto pmem = declcall(((Om *)0)->operator-(Om{})); diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 97f5d69dfc4b9..6b60b39e71089 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -31,6 +31,14 @@ template T ttmpl(T); static_assert(__is_same(decltype(declcall(ttmpl(0))), int (*)(int))); static_assert(__is_same(decltype(declcall(ttmpl('a'))), char (*)(char))); +// declcall on an operator call selects the operator function. +struct OpA {}; +int operator+(OpA, OpA); +struct OpM { int operator-(OpM); }; +static_assert(__is_same(decltype(declcall(OpA{} + OpA{})), int (*)(OpA, OpA))); +static_assert( + __is_same(decltype(declcall(((OpM *)0)->operator-(OpM{}))), int (OpM::*)(OpM))); + // The operand must be a call expression. int x; auto bad = declcall(x); // expected-error {{declcall doesn't contain a call}} From 5360b0410441cbb92b7ca1d3cbd7045b476e5968 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:15:05 -0700 Subject: [PATCH 14/30] [clang][test] Cover declcall on static member functions --- clang/test/CodeGenCXX/declcall.cpp | 4 ++++ clang/test/SemaCXX/declcall.cpp | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/clang/test/CodeGenCXX/declcall.cpp b/clang/test/CodeGenCXX/declcall.cpp index 07da7b3fc6323..ee10036be8855 100644 --- a/clang/test/CodeGenCXX/declcall.cpp +++ b/clang/test/CodeGenCXX/declcall.cpp @@ -17,6 +17,8 @@ using FP = int (*)(int); // yields a function pointer, a member operator yields a member pointer. // CHECK: @pfree = {{.*}}global ptr @_Zpl2OpS_ // CHECK: @pmem = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZN2OmmiES_ to i64), i64 0 } +// A static member function yields a plain function pointer. +// CHECK: @ps = {{.*}}global ptr @_ZN2SM2sfEi struct B { virtual int g(int); int h(int); }; auto pv = declcall(((B *)0)->B::g(0)); auto ph = declcall(((B *)0)->h(0)); @@ -51,3 +53,5 @@ int operator+(Op, Op); struct Om { int operator-(Om); }; auto pfree = declcall(Op{} + Op{}); auto pmem = declcall(((Om *)0)->operator-(Om{})); +struct SM { static int sf(int); }; +auto ps = declcall(SM::sf(0)); diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 6b60b39e71089..249c5fa3144bb 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -31,6 +31,15 @@ template T ttmpl(T); static_assert(__is_same(decltype(declcall(ttmpl(0))), int (*)(int))); static_assert(__is_same(decltype(declcall(ttmpl('a'))), char (*)(char))); +// declcall of a static member function yields a plain function pointer, not a +// member pointer. +struct SM { + static int sf(int); + static int sf(char); +}; +static_assert(__is_same(decltype(declcall(SM::sf(0))), int (*)(int))); +static_assert(__is_same(decltype(declcall(SM::sf('a'))), int (*)(char))); + // declcall on an operator call selects the operator function. struct OpA {}; int operator+(OpA, OpA); From 775dad806da91ce05270e00b441df679a84f8e1f Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:17:15 -0700 Subject: [PATCH 15/30] [clang] Reject declcall of a destructor or builtin function --- clang/lib/Sema/SemaExprCXX.cpp | 16 ++++++++++++++++ clang/test/SemaCXX/declcall.cpp | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index e3e955201f64e..6391f060314b7 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7523,6 +7523,22 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, return ExprError(); } + // P2825: declcall is ill-formed when the selected callee is a destructor or + // a builtin function (constructors cannot form a call expression and are + // rejected above). + const Decl *CalleeDecl = CE->getCalleeDecl(); + if (isa_and_nonnull(CalleeDecl)) { + Diag(CE->getExprLoc(), diag::err_declcall_not_implemented_for) + << "a destructor"; + return ExprError(); + } + if (const auto *FD = dyn_cast_or_null(CalleeDecl); + FD && FD->getBuiltinID() != 0) { + Diag(CE->getExprLoc(), diag::err_declcall_not_implemented_for) + << "a builtin function"; + return ExprError(); + } + // Check if we are calling a method (it can be static.) CXXMethodDecl * method = dyn_cast_or_null(CE->getCalleeDecl()) ; diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 249c5fa3144bb..12474d9b78f64 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -65,6 +65,14 @@ S s; int (S::*pmf)(int); auto pm = declcall((s.*pmf)(0)); // expected-error {{declcall doesn't support a call through a pointer to member yet}} +// declcall is ill-formed for destructors and builtin functions. +struct D { ~D(); }; +void ill_formed(D *d) { + auto pd = declcall(d->~D()); // expected-error {{declcall doesn't support a destructor yet}} + (void)pd; +} +auto pbuiltin = declcall(__builtin_abs(0)); // expected-error {{declcall doesn't support a builtin function yet}} + // Dependent operands are resolved at instantiation time. template auto call(T v) { return declcall(f(v)); } void use() { From 3505ddbb8b30f6b7c4dafb109ee133adc2cefd6d Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:18:50 -0700 Subject: [PATCH 16/30] [clang][test] Cover declcall SFINAE-friendliness --- clang/test/SemaCXX/declcall-sfinae.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 clang/test/SemaCXX/declcall-sfinae.cpp diff --git a/clang/test/SemaCXX/declcall-sfinae.cpp b/clang/test/SemaCXX/declcall-sfinae.cpp new file mode 100644 index 0000000000000..fd28d0525ee9d --- /dev/null +++ b/clang/test/SemaCXX/declcall-sfinae.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify %s +// expected-no-diagnostics + +// A declcall that is ill-formed in a SFINAE context is a substitution failure, +// not a hard error. + +int f(int); + +template auto sel(int) -> decltype(declcall(f(T{})), char{}); +template auto sel(...) -> int; + +// declcall(f(int{})) is well-formed: the first overload is selected. +static_assert(sizeof(decltype(sel(0))) == sizeof(char)); + +// __builtin_abs(T{}) is a valid call, but declcall rejects builtins. The +// failure must be a substitution failure, selecting the fallback overload, +// rather than a hard error. +template auto selb(int) -> decltype(declcall(__builtin_abs(T{})), char{}); +template auto selb(...) -> int; +static_assert(sizeof(decltype(selb(0))) == sizeof(int)); From e2390612fd75f72cc7e061730fdddf467b035a5b Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:19:46 -0700 Subject: [PATCH 17/30] [clang][test] Cover declcall devirtualization through C++20 modules --- clang/test/CodeGenCXX/declcall-modules.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 clang/test/CodeGenCXX/declcall-modules.cpp diff --git a/clang/test/CodeGenCXX/declcall-modules.cpp b/clang/test/CodeGenCXX/declcall-modules.cpp new file mode 100644 index 0000000000000..e477d7420e8b2 --- /dev/null +++ b/clang/test/CodeGenCXX/declcall-modules.cpp @@ -0,0 +1,18 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -emit-module-interface %t/M.cppm -o %t/M.pcm +// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-linux-gnu -fmodule-file=M=%t/M.pcm %t/user.cpp -emit-llvm -o - | FileCheck %s + +// The devirtualized flag of a declcall must survive C++20 module +// serialization, just as it does for a PCH. + +//--- M.cppm +export module M; +struct B { virtual int g(int); }; +export constexpr auto getptr() { return declcall(((B *)0)->B::g(0)); } + +//--- user.cpp +import M; +auto p = getptr(); +// A direct pointer to the member (devirtualized), not a vtable index. +// CHECK: @p = {{.*}}global { i64, i64 } { i64 ptrtoint (ptr @_ZNW1M1B1gEi to i64), i64 0 } From a8cefd2d36d1c3992402c792d1898cf79cbd1b08 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:21:14 -0700 Subject: [PATCH 18/30] [clang][test] Cover declcall on cv/ref-qualified member overloads --- clang/test/SemaCXX/declcall.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 12474d9b78f64..1bc231b2c4f7f 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -31,6 +31,21 @@ template T ttmpl(T); static_assert(__is_same(decltype(declcall(ttmpl(0))), int (*)(int))); static_assert(__is_same(decltype(declcall(ttmpl('a'))), char (*)(char))); +// declcall selects the right cv/ref-qualified member overload and preserves +// the qualifier in the resulting member-pointer type. +struct CV { + int m(int); + int m(int) const; + int r(int) &; + int r(int) &&; +}; +static_assert(__is_same(decltype(declcall(((CV *)0)->m(0))), int (CV::*)(int))); +static_assert( + __is_same(decltype(declcall(((const CV *)0)->m(0))), int (CV::*)(int) const)); +CV cv; +static_assert(__is_same(decltype(declcall(cv.r(0))), int (CV::*)(int) &)); +static_assert(__is_same(decltype(declcall(CV{}.r(0))), int (CV::*)(int) &&)); + // declcall of a static member function yields a plain function pointer, not a // member pointer. struct SM { From 6442e7f69d5c0106183890767d4317f2feb66fe8 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:21:56 -0700 Subject: [PATCH 19/30] [clang][test] Check the devirtualized marker in declcall AST dump --- clang/test/AST/ast-dump-declcall.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clang/test/AST/ast-dump-declcall.cpp b/clang/test/AST/ast-dump-declcall.cpp index 4c1d63627bcc3..422009a0ffe7a 100644 --- a/clang/test/AST/ast-dump-declcall.cpp +++ b/clang/test/AST/ast-dump-declcall.cpp @@ -7,3 +7,9 @@ auto p = declcall(f(0)); // CHECK: CXXDeclcallExpr {{.*}} 'int (*)(int)' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int (*)(int)' // CHECK-NEXT: DeclRefExpr {{.*}} 'int (int)' {{.*}} Function {{.*}} 'f' 'int (int)' + +// A devirtualized member-pointer value is dumped with a "(devirtualized)" +// marker. +struct B { virtual int g(int); }; +constexpr auto pm = declcall(((B *)0)->B::g(0)); +// CHECK: value: MemberPointer {{.*}}g (devirtualized) From d7663d515d16d41c10075569ff2f1a18043f4d79 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:29:28 -0700 Subject: [PATCH 20/30] [clang][NFC] clang-format declcall changes --- clang-tools-extra/clang-tidy/utils/Matchers.h | 3 +- clang/include/clang/AST/APValue.h | 3 +- .../include/clang/AST/EvaluatedExprVisitor.h | 2 +- clang/include/clang/AST/ExprCXX.h | 9 ++-- clang/include/clang/Basic/TokenKinds.h | 2 +- clang/lib/AST/APValue.cpp | 12 +++-- clang/lib/AST/ASTImporter.cpp | 4 +- clang/lib/AST/ExprConstant.cpp | 25 +++++----- clang/lib/AST/StmtPrinter.cpp | 1 - clang/lib/AST/TextNodeDumper.cpp | 4 +- clang/lib/Analysis/UnsafeBufferUsage.cpp | 2 +- clang/lib/CodeGen/CGCXXABI.cpp | 6 ++- clang/lib/CodeGen/CGCXXABI.h | 6 ++- clang/lib/CodeGen/CGExprConstant.cpp | 3 +- clang/lib/CodeGen/CGExprScalar.cpp | 6 +-- clang/lib/CodeGen/ItaniumCXXABI.cpp | 20 +++++--- clang/lib/CodeGen/MicrosoftCXXABI.cpp | 12 +++-- clang/lib/Edit/RewriteObjCFoundationAPI.cpp | 34 +++++-------- clang/lib/Parse/ParseExpr.cpp | 9 ++-- clang/lib/Sema/SemaExprCXX.cpp | 49 ++++++++++--------- clang/lib/Sema/SemaFixItUtils.cpp | 31 ++++-------- clang/lib/Sema/TreeTransform.h | 8 +-- 22 files changed, 123 insertions(+), 128 deletions(-) diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h b/clang-tools-extra/clang-tidy/utils/Matchers.h index aa979b2c6cde0..d3727f3de45ea 100644 --- a/clang-tools-extra/clang-tidy/utils/Matchers.h +++ b/clang-tools-extra/clang-tidy/utils/Matchers.h @@ -58,7 +58,8 @@ AST_MATCHER(QualType, isSimpleChar) { } AST_MATCHER(Expr, hasUnevaluatedContext) { - if (isa(Node) || isa(Node) || isa(Node)) + if (isa(Node) || isa(Node) || + isa(Node)) return true; if (const auto *UnaryExpr = dyn_cast(&Node)) { switch (UnaryExpr->getKind()) { diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 57c5486d4b6e7..d4f3761cdf726 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -800,7 +800,8 @@ class APValue { Kind = Union; } void MakeMemberPointer(const ValueDecl *Member, bool IsDerivedMember, - ArrayRef Path, bool DeVirtualized); + ArrayRef Path, + bool DeVirtualized); void MakeAddrLabelDiff() { assert(isAbsent() && "Bad state change"); new ((void *)(char *)&Data) AddrLabelDiffData(); diff --git a/clang/include/clang/AST/EvaluatedExprVisitor.h b/clang/include/clang/AST/EvaluatedExprVisitor.h index d4d40d8838901..5f8417f6976a6 100644 --- a/clang/include/clang/AST/EvaluatedExprVisitor.h +++ b/clang/include/clang/AST/EvaluatedExprVisitor.h @@ -48,7 +48,7 @@ class EvaluatedExprVisitorBase : public StmtVisitorBase { void VisitBlockExpr(PTR(BlockExpr) E) { } void VisitCXXUuidofExpr(PTR(CXXUuidofExpr) E) { } void VisitCXXNoexceptExpr(PTR(CXXNoexceptExpr) E) { } - void VisitCXXDeclcallExpr(PTR(CXXDeclcallExpr) E) { } + void VisitCXXDeclcallExpr(PTR(CXXDeclcallExpr) E) {} void VisitMemberExpr(PTR(MemberExpr) E) { // Only the base matters. diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 72fe70418b733..8ed3dcc017940 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4351,8 +4351,9 @@ class CXXDeclcallExpr : public Expr { Stmt *Operand; SourceRange Range; - + bool Devirtualize = false; + public: CXXDeclcallExpr(QualType Ty, Expr *Operand, bool Devirtualize, SourceLocation Keyword, SourceLocation RParen) @@ -4372,10 +4373,8 @@ class CXXDeclcallExpr : public Expr { static bool classof(const Stmt *T) { return T->getStmtClass() == CXXDeclcallExprClass; } - - bool isDevirtualized() const { - return Devirtualize; - } + + bool isDevirtualized() const { return Devirtualize; } // Iterators child_range children() { return child_range(&Operand, &Operand + 1); } diff --git a/clang/include/clang/Basic/TokenKinds.h b/clang/include/clang/Basic/TokenKinds.h index 7cb2511755a6f..393644ed47912 100644 --- a/clang/include/clang/Basic/TokenKinds.h +++ b/clang/include/clang/Basic/TokenKinds.h @@ -57,7 +57,7 @@ enum TokenKey : unsigned { KEYMAX = KEYCXX26, // The maximum key KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20 | KEYCXX26, KEYALL = (KEYMAX | (KEYMAX - 1)) & ~KEYNOMS18 & ~KEYNOOPENCL & - ~KEYNOHLSL // KEYNOMS18, KEYNOOPENCL, KEYNOHLSL excluded. + ~KEYNOHLSL // KEYNOMS18, KEYNOOPENCL, KEYNOHLSL excluded. }; namespace tok { diff --git a/clang/lib/AST/APValue.cpp b/clang/lib/AST/APValue.cpp index 22c1724eca47c..5252dadc5e4e5 100644 --- a/clang/lib/AST/APValue.cpp +++ b/clang/lib/AST/APValue.cpp @@ -255,7 +255,10 @@ struct APValue::MemberPointerData : MemberPointerBase { PathElem *PathPtr; }; - MemberPointerData() { PathLength = 0; DeVirtualized = false; } + MemberPointerData() { + PathLength = 0; + DeVirtualized = false; + } ~MemberPointerData() { resizePath(0); } void resizePath(unsigned Length) { @@ -1139,9 +1142,10 @@ APValue::setMemberPointerUninit(const ValueDecl *Member, bool IsDerivedMember, } void APValue::MakeMemberPointer(const ValueDecl *Member, bool IsDerivedMember, - ArrayRef Path, bool DeVirtualized) { - MutableArrayRef InternalPath = - setMemberPointerUninit(Member, IsDerivedMember, Path.size(), DeVirtualized); + ArrayRef Path, + bool DeVirtualized) { + MutableArrayRef InternalPath = setMemberPointerUninit( + Member, IsDerivedMember, Path.size(), DeVirtualized); for (unsigned I = 0; I != Path.size(); ++I) InternalPath[I] = Path[I]->getCanonicalDecl(); } diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 2ddbe1715f964..364e7603af1cf 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8565,7 +8565,8 @@ ExpectedStmt ASTNodeImporter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { ExpectedStmt ASTNodeImporter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { Error Err = Error::success(); auto ToType = importChecked(Err, E->getType()); - auto ToOperand = importChecked(Err, E->getOperand()); // FIXME: dependent values?! + auto ToOperand = + importChecked(Err, E->getOperand()); // FIXME: dependent values?! auto ToBeginLoc = importChecked(Err, E->getBeginLoc()); auto ToEndLoc = importChecked(Err, E->getEndLoc()); if (Err) @@ -8575,7 +8576,6 @@ ExpectedStmt ASTNodeImporter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { ToType, ToOperand, E->isDevirtualized(), ToBeginLoc, ToEndLoc); } - ExpectedStmt ASTNodeImporter::VisitCXXThrowExpr(CXXThrowExpr *E) { Error Err = Error::success(); auto ToSubExpr = importChecked(Err, E->getSubExpr()); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index b0711c86688cf..c180a113f7fa9 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5292,15 +5292,13 @@ static bool EvaluateObjectArgument(EvalInfo &Info, const Expr *Object, /// or 0 if evaluation fails. struct MemberPointerAccessResult { - const ValueDecl * Decl; + const ValueDecl *Decl; bool DeVirtualized = false; }; -static const MemberPointerAccessResult HandleMemberPointerAccess(EvalInfo &Info, - QualType LVType, - LValue &LV, - const Expr *RHS, - bool IncludeMember = true) { +static const MemberPointerAccessResult +HandleMemberPointerAccess(EvalInfo &Info, QualType LVType, LValue &LV, + const Expr *RHS, bool IncludeMember = true) { MemberPtr MemPtr; if (!EvaluateMemberPointer(RHS, MemPtr, Info)) return {nullptr}; @@ -5399,10 +5397,9 @@ static const MemberPointerAccessResult HandleMemberPointerAccess(EvalInfo &Info, return {MemPtr.getDecl(), MemPtr.DeVirtualized}; } -static const MemberPointerAccessResult HandleMemberPointerAccess(EvalInfo &Info, - const BinaryOperator *BO, - LValue &LV, - bool IncludeMember = true) { +static const MemberPointerAccessResult +HandleMemberPointerAccess(EvalInfo &Info, const BinaryOperator *BO, LValue &LV, + bool IncludeMember = true) { assert(BO->getOpcode() == BO_PtrMemD || BO->getOpcode() == BO_PtrMemI); if (!EvaluateObjectArgument(Info, BO->getLHS(), LV)) { @@ -9604,7 +9601,8 @@ bool LValueExprEvaluator::VisitMaterializeTemporaryExpr( case SubobjectAdjustment::MemberPointerAdjustment: if (!HandleMemberPointerAccess(this->Info, Type, Result, - Adjustments[I].Ptr.RHS).Decl) + Adjustments[I].Ptr.RHS) + .Decl) return false; Type = Adjustments[I].Ptr.MPT->getPointeeType(); break; @@ -10062,9 +10060,9 @@ class PointerExprEvaluator Result.addArray(Info, E, cast(ArrayTy)); return true; } - + bool VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { - return Visit(E->getOperand()); + return Visit(E->getOperand()); } bool VisitSYCLUniqueStableIdExpr(const SYCLUniqueStableIdExpr *E) { @@ -11203,7 +11201,6 @@ bool MemberPointerExprEvaluator::VisitUnaryAddrOf(const UnaryOperator *E) { return Success(cast(E->getSubExpr())->getDecl()); } - //===----------------------------------------------------------------------===// // Record Evaluation //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index f34e1913b1161..ca0c32f5ea690 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -2735,7 +2735,6 @@ void StmtPrinter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { OS << ")"; } - void StmtPrinter::VisitPackExpansionExpr(PackExpansionExpr *E) { PrintExpr(E->getPattern()); OS << "..."; diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index 105ec9dfc3433..f411eaeceb0d1 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -1816,9 +1816,9 @@ void TextNodeDumper::VisitCXXUnresolvedConstructExpr( void TextNodeDumper::VisitCXXNoexceptExpr(const CXXNoexceptExpr *Node) { if (Node->isValueDependent()) OS << " noexcept()"; - else if (Node->getValue()) + else if (Node->getValue()) OS << " noexcept(true)"; - else + else OS << " noexcept(false)"; } diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp index 0ef610ec1cb40..ce738fb93ded7 100644 --- a/clang/lib/Analysis/UnsafeBufferUsage.cpp +++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -208,7 +208,7 @@ class MatchDescendantVisitor : public DynamicRecursiveASTVisitor { return true; return DynamicRecursiveASTVisitor::TraverseCXXNoexceptExpr(Node); } - + bool TraverseCXXDeclcallExpr(CXXDeclcallExpr *Node) override { // Unevaluated context. if (IgnoreUnevaluatedContext) diff --git a/clang/lib/CodeGen/CGCXXABI.cpp b/clang/lib/CodeGen/CGCXXABI.cpp index d7e2e887d7b3b..aa40541f0fa0d 100644 --- a/clang/lib/CodeGen/CGCXXABI.cpp +++ b/clang/lib/CodeGen/CGCXXABI.cpp @@ -103,7 +103,8 @@ CGCXXABI::EmitNullMemberPointer(const MemberPointerType *MPT) { return GetBogusMemberPointer(QualType(MPT, 0)); } -llvm::Constant *CGCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) { +llvm::Constant *CGCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, + bool AllowVirtual) { return GetBogusMemberPointer(CGM.getContext().getMemberPointerType( MD->getType(), /*Qualifier=*/std::nullopt, MD->getParent())); } @@ -113,7 +114,8 @@ llvm::Constant *CGCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT, return GetBogusMemberPointer(QualType(MPT, 0)); } -llvm::Constant *CGCXXABI::EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual) { +llvm::Constant *CGCXXABI::EmitMemberPointer(const APValue &MP, QualType MPT, + bool AllowVirtual) { return GetBogusMemberPointer(MPT); } diff --git a/clang/lib/CodeGen/CGCXXABI.h b/clang/lib/CodeGen/CGCXXABI.h index 25d6121b1de40..1ea4bd8e25922 100644 --- a/clang/lib/CodeGen/CGCXXABI.h +++ b/clang/lib/CodeGen/CGCXXABI.h @@ -218,14 +218,16 @@ class CGCXXABI { virtual llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT); /// Create a member pointer for the given method. - virtual llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual = true); + virtual llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, + bool AllowVirtual = true); /// Create a member pointer for the given field. virtual llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset); /// Create a member pointer for the given member pointer constant. - virtual llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual = false); + virtual llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, + bool AllowVirtual = false); /// Emit a comparison between two member pointers. Returns an i1. virtual llvm::Value * diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 7b7b7e8332e57..6848cc444863d 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -2725,7 +2725,8 @@ ConstantEmitter::tryEmitPrivate(const APValue &Value, QualType DestType, Filler); } case APValue::MemberPointer: - return CGM.getCXXABI().EmitMemberPointer(Value, DestType, !Value.isDeVirtualized()); + return CGM.getCXXABI().EmitMemberPointer(Value, DestType, + !Value.isDeVirtualized()); } llvm_unreachable("Unknown APValue kind"); } diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index 2911c95d95fd2..acbef0688b0da 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -828,7 +828,7 @@ class ScalarExprEmitter Value *VisitCXXNoexceptExpr(const CXXNoexceptExpr *E) { return Builder.getInt1(E->getValue()); } - + Value *VisitCXXDeclcallExpr(const CXXDeclcallExpr *E) { // A declcall is required to be constant-evaluable (enforced in Sema), so // emit it as a constant. This covers both function pointers and member @@ -836,8 +836,8 @@ class ScalarExprEmitter // APValue and honored by the member-pointer constant emission. Expr::EvalResult Eval; if (E->EvaluateAsConstantExpr(Eval, CGF.getContext())) - if (llvm::Constant *C = ConstantEmitter(CGF).tryEmitAbstract( - Eval.Val, E->getType())) + if (llvm::Constant *C = + ConstantEmitter(CGF).tryEmitAbstract(Eval.Val, E->getType())) return C; // Fall back to emitting the resolved operand directly. diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index 92cf6b470e9b6..adb332f09fa5a 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -149,12 +149,15 @@ class ItaniumCXXABI : public CodeGen::CGCXXABI { llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT) override; - llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) override; + llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, + bool AllowVirtual) override; llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset) override; - llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual) override; + llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, + bool AllowVirtual) override; llvm::Constant *BuildMemberPointer(const CXXMethodDecl *MD, - CharUnits ThisAdjustment, bool AllowVirtual = true); + CharUnits ThisAdjustment, + bool AllowVirtual = true); llvm::Value *EmitMemberPointerComparison(CodeGenFunction &CGF, llvm::Value *L, llvm::Value *R, @@ -1156,12 +1159,14 @@ ItaniumCXXABI::EmitMemberDataPointer(const MemberPointerType *MPT, } llvm::Constant * -ItaniumCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) { +ItaniumCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, + bool AllowVirtual) { return BuildMemberPointer(MD, CharUnits::Zero(), AllowVirtual); } llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD, - CharUnits ThisAdjustment, bool AllowVirtual) { + CharUnits ThisAdjustment, + bool AllowVirtual) { assert(MD->isInstance() && "Member function must not be static!"); CodeGenTypes &Types = CGM.getTypes(); @@ -1247,8 +1252,9 @@ llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD, } llvm::Constant *ItaniumCXXABI::EmitMemberPointer(const APValue &MP, - QualType MPType, bool AllowVirtual) { - + QualType MPType, + bool AllowVirtual) { + const MemberPointerType *MPT = MPType->castAs(); const ValueDecl *MPD = MP.getMemberPointerDecl(); if (!MPD) diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index dd8a223e4cf42..4404ed8c6d4d5 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -690,8 +690,10 @@ class MicrosoftCXXABI : public CGCXXABI { llvm::Constant *EmitMemberDataPointer(const MemberPointerType *MPT, CharUnits offset) override; - llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) override; - llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, bool AllowVirtual) override; + llvm::Constant *EmitMemberFunctionPointer(const CXXMethodDecl *MD, + bool AllowVirtual) override; + llvm::Constant *EmitMemberPointer(const APValue &MP, QualType MPT, + bool AllowVirtual) override; llvm::Value *EmitMemberPointerComparison(CodeGenFunction &CGF, llvm::Value *L, @@ -2957,7 +2959,8 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberDataPointer(const CXXRecordDecl *RD, } llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP, - QualType MPType, bool AllowVirtual) { + QualType MPType, + bool AllowVirtual) { const MemberPointerType *DstTy = MPType->castAs(); const ValueDecl *MPD = MP.getMemberPointerDecl(); if (!MPD) @@ -3021,7 +3024,8 @@ llvm::Constant *MicrosoftCXXABI::EmitMemberPointer(const APValue &MP, } llvm::Constant * -MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, bool AllowVirtual) { +MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, + bool AllowVirtual) { assert(MD->isInstance() && "Member function must not be static!"); CharUnits NonVirtualBaseAdjustment = CharUnits::Zero(); diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp index 3e58f9d078b21..c2ce880ab30c7 100644 --- a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp +++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp @@ -909,29 +909,17 @@ static bool subscriptOperatorNeedsParens(const Expr *FullExpr) { } static bool castOperatorNeedsParens(const Expr *FullExpr) { const Expr* Expr = FullExpr->IgnoreImpCasts(); - if (isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(FullExpr) || - isa(Expr) || - isa(Expr) || - isa(Expr)) + if (isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(FullExpr) || isa(Expr) || + isa(Expr) || isa(Expr)) return false; return true; diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index e46d9f245dd2b..4a928ce487920 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1507,12 +1507,13 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand, if (T.expectAndConsume(diag::err_expected_lparen_after, "declcall")) return ExprError(); - - // it can't be marked as unevaluated as we need to keep all the instantiations + + // it can't be marked as unevaluated as we need to keep all the + // instantiations Res = ParseExpression(); - + T.consumeClose(); - + if (!Res.isInvalid()) Res = Actions.ActOnDeclcallExpr(KeyLoc, T.getOpenLocation(), Res.get(), T.getCloseLocation()); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 6391f060314b7..d90bb2f63cea7 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7499,26 +7499,24 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, return new (Context) CXXDeclcallExpr(Context.DependentTy, Operand, false, KeyLoc, RParen); } - + // unwrap parens Operand = Operand->IgnoreParens(); - CallExpr * CE = dyn_cast(Operand); - + CallExpr *CE = dyn_cast(Operand); + if (CE == nullptr) { Diag(Operand->getExprLoc(), diag::err_declcall_must_contain_a_call); return ExprError(); } - - const auto mustBeEvaluableInCompileTime = [this](Expr * e) { + + const auto mustBeEvaluableInCompileTime = [this](Expr *e) { Expr::EvalResult Eval; if (!e->isValueDependent() && !e->EvaluateAsConstantExpr(Eval, Context)) { return false; } return true; }; - - - + if (CE->getCallee() == nullptr) { return ExprError(); } @@ -7540,31 +7538,33 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, } // Check if we are calling a method (it can be static.) - CXXMethodDecl * method = dyn_cast_or_null(CE->getCalleeDecl()) ; - + CXXMethodDecl *method = dyn_cast_or_null(CE->getCalleeDecl()); + bool MemberCallHasQualifierAndIsVirtual = false; - + // transform method call if (method && method->isInstance()) { - const auto memberCallHasQualifier = [](const CallExpr * e) { - const MemberExpr *ME = dyn_cast(e->getCallee()->IgnoreParens()); - if (!ME) + const auto memberCallHasQualifier = [](const CallExpr *e) { + const MemberExpr *ME = + dyn_cast(e->getCallee()->IgnoreParens()); + if (!ME) return false; - + return ME->hasQualifier(); }; - - MemberCallHasQualifierAndIsVirtual = method->isVirtual() && memberCallHasQualifier(CE); - + + MemberCallHasQualifierAndIsVirtual = + method->isVirtual() && memberCallHasQualifier(CE); + // member function pointer const auto T = QualType(method->getFunctionType(), 0); - + // create reference to the function - auto * DRE = DeclRefExpr::Create( + auto *DRE = DeclRefExpr::Create( Context, NestedNameSpecifierLoc(), SourceLocation(), method, - /*RefersToEnclosingVariableOrCapture=*/false, CE->getExprLoc(), - T, CE->getValueKind(), nullptr, nullptr, NOUR_None); - + /*RefersToEnclosingVariableOrCapture=*/false, CE->getExprLoc(), T, + CE->getValueKind(), nullptr, nullptr, NOUR_None); + // get its address ExprResult AddrOf = CreateBuiltinUnaryOp(CE->getSourceRange().getBegin(), UO_AddrOf, DRE, false); @@ -7596,7 +7596,8 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, } return new (Context) - CXXDeclcallExpr(Operand->getType(), Operand, MemberCallHasQualifierAndIsVirtual, KeyLoc, RParen); + CXXDeclcallExpr(Operand->getType(), Operand, + MemberCallHasQualifierAndIsVirtual, KeyLoc, RParen); } ExprResult Sema::ActOnDeclcallExpr(SourceLocation KeyLoc, SourceLocation, diff --git a/clang/lib/Sema/SemaFixItUtils.cpp b/clang/lib/Sema/SemaFixItUtils.cpp index 33ffe82f461b0..c4e11da7b50c2 100644 --- a/clang/lib/Sema/SemaFixItUtils.cpp +++ b/clang/lib/Sema/SemaFixItUtils.cpp @@ -65,27 +65,16 @@ bool ConversionFixItGenerator::tryToFixConversion(const Expr *FullExpr, const Expr* Expr = FullExpr->IgnoreImpCasts(); bool NeedParen = true; - if (isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(Expr) || - isa(FullExpr) || - isa(Expr) || - isa(Expr) || + if (isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(Expr) || + isa(Expr) || isa(FullExpr) || + isa(Expr) || isa(Expr) || isa(Expr)) NeedParen = false; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index abaf62f9c744d..72c3be1772bf0 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -3779,7 +3779,7 @@ class TreeTransform { ExprResult RebuildCXXNoexceptExpr(SourceRange Range, Expr *Arg) { return SemaRef.BuildCXXNoexceptExpr(Range.getBegin(), Arg, Range.getEnd()); } - + ExprResult RebuildCXXDeclcallExpr(SourceRange Range, Expr *Arg) { return SemaRef.BuildCXXDeclcallExpr(Range.getBegin(), Arg, Range.getEnd()); } @@ -16774,7 +16774,7 @@ TreeTransform::TransformCXXNoexceptExpr(CXXNoexceptExpr *E) { return getDerived().RebuildCXXNoexceptExpr(E->getSourceRange(),SubExpr.get()); } -template +template ExprResult TreeTransform::TransformCXXDeclcallExpr(CXXDeclcallExpr *E) { // we need to instantiate referenced functions, so it's evaluated scope @@ -16785,10 +16785,10 @@ TreeTransform::TransformCXXDeclcallExpr(CXXDeclcallExpr *E) { if (!getDerived().AlwaysRebuild() && SubExpr.get() == E->getOperand()) return E; - return getDerived().RebuildCXXDeclcallExpr(E->getSourceRange(),SubExpr.get()); + return getDerived().RebuildCXXDeclcallExpr(E->getSourceRange(), + SubExpr.get()); } - template ExprResult TreeTransform::TransformPackExpansionExpr(PackExpansionExpr *E) { From 22d36fc87854b72fa7f599eb1f077829325e5bed Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:30:04 -0700 Subject: [PATCH 21/30] [clang][NFC] Fix placeholder standard reference in CXXDeclcallExpr doc --- clang/include/clang/AST/ExprCXX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 8ed3dcc017940..27cab25f6f019 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4343,7 +4343,7 @@ class CXXNoexceptExpr : public Expr { } }; -/// Represents a C++26 declcall expression (C++ [??]). +/// Represents a C++26 declcall expression (P2825). /// /// An object which gives you member function pointer or function pointer class CXXDeclcallExpr : public Expr { From a23dc43467f3fc8de67da8bc7a3c0571d4126ed4 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:43:25 -0700 Subject: [PATCH 22/30] [clang][docs] Document the declcall extension --- clang/docs/LanguageExtensions.md | 18 ++++++++++++++++++ clang/docs/ReleaseNotes.md | 1 + 2 files changed, 19 insertions(+) diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md index bd9ae30c2607e..7a0e3c2636af2 100644 --- a/clang/docs/LanguageExtensions.md +++ b/clang/docs/LanguageExtensions.md @@ -514,6 +514,24 @@ These spellings result in the type of the operand, stripping all qualifiers. `char32_t` respectively, but are also available in C++ modes before C++11. They are only supported in C++. `__char8_t` is not available. +### declcall + +`declcall(call-expression)` yields a pointer, or pointer to member, to the +function that overload resolution would select for the *call-expression*, which +is an unevaluated operand: + +```c++ +int f(int); +int f(char); +constexpr auto p = declcall(f(0)); // &f(int) +``` + +For a member function it produces a pointer to member; a qualified call to a +virtual member is devirtualized. `declcall` is ill-formed if the operand is not +a call expression, names a constructor, destructor, or builtin function, or does +not select a function at compile time. It implements P2825 and is available as a +Clang extension. + % FIXME: This should list all the keyword extensions (langext-vectors)= diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 9063e54b3e692..420080124b1cf 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -248,6 +248,7 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the - `__is_trivially_equality_comparable` no longer returns false for all enum types. (#GH132672) - `auto` parameters are now available in all C++ language modes as an extension. +- Clang now supports `declcall` ([P2825](https://wg21.link/p2825)) as an extension, yielding a pointer to the function that overload resolution would select for an unevaluated call expression. #### C++2d Feature Support From e9c5aa2067d04016df41d20a89befce847207fb7 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 12:49:35 -0700 Subject: [PATCH 23/30] [clang] Gate declcall: native under -fsycl, Clang extension otherwise --- clang/docs/LanguageExtensions.md | 7 +++++-- clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../include/clang/Basic/DiagnosticSemaKinds.td | 2 ++ clang/include/clang/Basic/TokenKinds.def | 2 +- clang/lib/Sema/SemaExprCXX.cpp | 4 ++++ clang/test/SemaCXX/declcall-extension.cpp | 17 +++++++++++++++++ clang/test/SemaCXX/declcall-sfinae.cpp | 2 +- clang/test/SemaCXX/declcall.cpp | 2 +- 8 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 clang/test/SemaCXX/declcall-extension.cpp diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md index 7a0e3c2636af2..f9052cdd86574 100644 --- a/clang/docs/LanguageExtensions.md +++ b/clang/docs/LanguageExtensions.md @@ -529,8 +529,11 @@ constexpr auto p = declcall(f(0)); // &f(int) For a member function it produces a pointer to member; a qualified call to a virtual member is devirtualized. `declcall` is ill-formed if the operand is not a call expression, names a constructor, destructor, or builtin function, or does -not select a function at compile time. It implements P2825 and is available as a -Clang extension. +not select a function at compile time. + +`declcall` implements P2825, which is not yet standardized. It is enabled as a +native feature under `-fsycl`; otherwise it is a Clang extension diagnosed by +`-Wdeclcall-extension`. % FIXME: This should list all the keyword extensions diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 937220f188585..edd03a4cc33e1 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -233,6 +233,7 @@ def UndefinedFuncTemplate : DiagGroup<"undefined-func-template">; def MissingNoEscape : DiagGroup<"missing-noescape">; def DefaultedFunctionDeleted : DiagGroup<"defaulted-function-deleted">; +def DeclcallExtension : DiagGroup<"declcall-extension">; def DeleteIncomplete : DiagGroup<"delete-incomplete">; def DeleteNonAbstractNonVirtualDtor : DiagGroup<"delete-non-abstract-non-virtual-dtor">; def DeleteAbstractNonVirtualDtor : DiagGroup<"delete-abstract-non-virtual-dtor">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 15156d24076b6..7b190d463ce5c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10161,6 +10161,8 @@ def note_inequality_comparison_to_or_assign : Note< def err_incomplete_type_used_in_type_trait_expr : Error< "incomplete type %0 used in type trait expression">, NoSFINAE; +def ext_declcall : ExtWarn< + "'declcall' is a Clang extension">, InGroup; def err_declcall_must_contain_a_call : Error< "declcall doesn't contain a call">; def err_declcall_not_implemented_for : Error< diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 02b0666d41638..a373fed05ee2a 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -448,7 +448,7 @@ CXX20_KEYWORD(concept , 0) CXX20_KEYWORD(requires , 0) // C++26 keywords. -CXX26_KEYWORD(declcall , 0) +CXX26_KEYWORD(declcall , KEYSYCL) // Not a CXX20_KEYWORD because it is disabled by -fno-char8_t. KEYWORD(char8_t , CHAR8SUPPORT) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index d90bb2f63cea7..b11616b6fc9e5 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7602,6 +7602,10 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, ExprResult Sema::ActOnDeclcallExpr(SourceLocation KeyLoc, SourceLocation, Expr *Operand, SourceLocation RParen) { + // declcall (P2825) is not standardized yet. It is a native feature in SYCL + // mode; elsewhere it is a Clang extension. + if (!getLangOpts().isSYCL()) + Diag(KeyLoc, diag::ext_declcall); return BuildCXXDeclcallExpr(KeyLoc, Operand, RParen); } diff --git a/clang/test/SemaCXX/declcall-extension.cpp b/clang/test/SemaCXX/declcall-extension.cpp new file mode 100644 index 0000000000000..2272229d3535d --- /dev/null +++ b/clang/test/SemaCXX/declcall-extension.cpp @@ -0,0 +1,17 @@ +// declcall (P2825) is not standardized. It is a native feature under -fsycl and +// a Clang extension otherwise. + +// Extension warning by default in non-SYCL mode. +// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify=ext %s +// Promoted to an error under -pedantic-errors. +// RUN: %clang_cc1 -std=c++2c -pedantic-errors -fsyntax-only -verify=pedantic %s +// No diagnostic in SYCL mode, and available even before C++26. +// RUN: %clang_cc1 -fsycl-is-host -std=c++17 -fsyntax-only -verify=sycl %s +// RUN: %clang_cc1 -fsycl-is-device -std=c++17 -fsyntax-only -verify=sycl %s + +// sycl-no-diagnostics + +int f(int); + +auto p = declcall(f(0)); // ext-warning {{'declcall' is a Clang extension}} \ + // pedantic-error {{'declcall' is a Clang extension}} diff --git a/clang/test/SemaCXX/declcall-sfinae.cpp b/clang/test/SemaCXX/declcall-sfinae.cpp index fd28d0525ee9d..cba025be00637 100644 --- a/clang/test/SemaCXX/declcall-sfinae.cpp +++ b/clang/test/SemaCXX/declcall-sfinae.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s // expected-no-diagnostics // A declcall that is ill-formed in a SFINAE context is a substitution failure, diff --git a/clang/test/SemaCXX/declcall.cpp b/clang/test/SemaCXX/declcall.cpp index 1bc231b2c4f7f..bc456bfd6edc6 100644 --- a/clang/test/SemaCXX/declcall.cpp +++ b/clang/test/SemaCXX/declcall.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++2c -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s // Baseline semantics for declcall (P2825): overload resolution, operand // requirements, and dependent-operand instantiation. From 85773577626a6fe986807845624fe18de15b1bf5 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 13:56:54 -0700 Subject: [PATCH 24/30] [clang] Don't disable member-function address-of diagnostics for declcall declcall now forms its pointer to member from a qualified reference to the selected member function, so the global CheckUseOfCXXMethodAsAddressOfOperand short-circuit is no longer needed. Restoring it re-enables the diagnostics for improperly taking a member function's address (unqualified name, destructor, parenthesized), which the prototype had disabled for all code. --- clang/lib/Sema/SemaExpr.cpp | 1 - clang/lib/Sema/SemaExprCXX.cpp | 16 +++++++++++++--- .../SemaCXX/declcall-addressof-member.cpp | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 clang/test/SemaCXX/declcall-addressof-member.cpp diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 402b6cf414494..8100afe45db37 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -15051,7 +15051,6 @@ static void diagnoseAddressOfInvalidType(Sema &S, SourceLocation Loc, bool Sema::CheckUseOfCXXMethodAsAddressOfOperand(SourceLocation OpLoc, const Expr *Op, const CXXMethodDecl *MD) { - return false; // FIXME allow this only in declcall const auto *DRE = cast(Op->IgnoreParens()); if (Op != DRE) diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index b11616b6fc9e5..19958581e9954 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7559,11 +7559,21 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, // member function pointer const auto T = QualType(method->getFunctionType(), 0); + // Build a qualified reference to the member (i.e. &Class::method). The + // nested-name-specifier makes this a well-formed pointer-to-member and + // lets CheckUseOfCXXMethodAsAddressOfOperand accept the address-of below. + NestedNameSpecifierLocBuilder NNSBuilder; + NNSBuilder.MakeTrivial( + Context, + NestedNameSpecifier( + Context.getCanonicalTagType(method->getParent())->getTypePtr()), + CE->getSourceRange()); + // create reference to the function auto *DRE = DeclRefExpr::Create( - Context, NestedNameSpecifierLoc(), SourceLocation(), method, - /*RefersToEnclosingVariableOrCapture=*/false, CE->getExprLoc(), T, - CE->getValueKind(), nullptr, nullptr, NOUR_None); + Context, NNSBuilder.getWithLocInContext(Context), SourceLocation(), + method, /*RefersToEnclosingVariableOrCapture=*/false, CE->getExprLoc(), + T, CE->getValueKind(), nullptr, nullptr, NOUR_None); // get its address ExprResult AddrOf = CreateBuiltinUnaryOp(CE->getSourceRange().getBegin(), diff --git a/clang/test/SemaCXX/declcall-addressof-member.cpp b/clang/test/SemaCXX/declcall-addressof-member.cpp new file mode 100644 index 0000000000000..0c98c54deba7e --- /dev/null +++ b/clang/test/SemaCXX/declcall-addressof-member.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s + +// declcall builds a qualified reference to the selected member function to form +// its pointer to member. That must not disable the normal diagnostics for +// improperly taking the address of a member function. + +struct S { + void m(); + ~S(); + void f() { + auto p = &m; // expected-error {{must explicitly qualify name of member function when taking its address}} + (void)p; + } +}; + +auto pd = &S::~S; // expected-error {{taking the address of a destructor}} + +// declcall of a member function is still accepted. +auto ok = declcall(((S *)0)->m()); From a690fc3056bdc65f1b2d2d579f912415a4ede098 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 14:15:31 -0700 Subject: [PATCH 25/30] [clang] Parse declcall's operand as an unevaluated operand The argument subexpressions of the call are no longer odr-used or evaluated, matching the paper's unevaluated-operand rule (previously they were treated as potentially-evaluated, causing spurious odr-use and diagnostics). The selected function is odr-used separately in the declcall's own evaluation context, so it is still instantiated when the declcall is potentially-evaluated. --- clang/lib/Parse/ParseExpr.cpp | 13 ++++++++++--- clang/lib/Sema/SemaExprCXX.cpp | 8 ++++++++ clang/test/SemaCXX/declcall-unevaluated.cpp | 12 ++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 clang/test/SemaCXX/declcall-unevaluated.cpp diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 4a928ce487920..7430e07b5b55e 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1508,9 +1508,16 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand, if (T.expectAndConsume(diag::err_expected_lparen_after, "declcall")) return ExprError(); - // it can't be marked as unevaluated as we need to keep all the - // instantiations - Res = ParseExpression(); + // The operand of declcall is an unevaluated operand: its argument + // subexpressions must not be odr-used or evaluated. This scope covers only + // parsing; ActOnDeclcallExpr then runs in the declcall's own evaluation + // context, so the selected function is odr-used (and instantiated) exactly + // when the declcall expression itself is. + { + EnterExpressionEvaluationContext Unevaluated( + Actions, Sema::ExpressionEvaluationContext::Unevaluated); + Res = ParseExpression(); + } T.consumeClose(); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 19958581e9954..c145c7899fa97 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7537,6 +7537,14 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, return ExprError(); } + // The operand's argument subexpressions were parsed unevaluated and are not + // odr-used. The selected function, however, is what declcall yields a pointer + // to, so odr-use it here: this runs in the declcall's own evaluation context, + // so it is instantiated/emitted exactly when the declcall expression is + // potentially-evaluated (and left alone inside decltype, sizeof, etc.). + if (auto *FD = dyn_cast_or_null(CE->getCalleeDecl())) + MarkFunctionReferenced(KeyLoc, FD); + // Check if we are calling a method (it can be static.) CXXMethodDecl *method = dyn_cast_or_null(CE->getCalleeDecl()); diff --git a/clang/test/SemaCXX/declcall-unevaluated.cpp b/clang/test/SemaCXX/declcall-unevaluated.cpp new file mode 100644 index 0000000000000..fc483da8030a1 --- /dev/null +++ b/clang/test/SemaCXX/declcall-unevaluated.cpp @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -Wundefined-internal -verify %s +// expected-no-diagnostics + +// The operand of declcall is an unevaluated operand: its argument +// subexpressions are not odr-used. An internal-linkage function used only as an +// argument of the call must therefore not be diagnosed as used-but-not-defined +// (as it would be in a potentially-evaluated context). +namespace { +int undef_arg(); +} +int f(int); +auto p = declcall(f(undef_arg())); From 2e59913e842c502bb91935d8e73ca124477d0558 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 14:21:59 -0700 Subject: [PATCH 26/30] [clang] Honor declcall devirtualization in the Microsoft C++ ABI EmitMemberFunctionPointer took an AllowVirtual parameter but ignored it, so a devirtualized declcall of a qualified virtual member still emitted a vftable thunk. Emit a direct function pointer when devirtualization is requested. --- clang/lib/CodeGen/MicrosoftCXXABI.cpp | 4 +++- clang/test/CodeGenCXX/declcall-microsoft.cpp | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 clang/test/CodeGenCXX/declcall-microsoft.cpp diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 4404ed8c6d4d5..681bf52f5796c 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -3035,7 +3035,9 @@ MicrosoftCXXABI::EmitMemberFunctionPointer(const CXXMethodDecl *MD, unsigned VBTableIndex = 0; llvm::Constant *FirstField; const FunctionProtoType *FPT = MD->getType()->castAs(); - if (!MD->isVirtual()) { + // A devirtualized pointer (declcall of a qualified virtual call, AllowVirtual + // == false) points directly at the function rather than at a vftable thunk. + if (!MD->isVirtual() || !AllowVirtual) { llvm::Type *Ty; // Check whether the function has a computable LLVM signature. if (Types.isFuncTypeConvertible(FPT)) { diff --git a/clang/test/CodeGenCXX/declcall-microsoft.cpp b/clang/test/CodeGenCXX/declcall-microsoft.cpp new file mode 100644 index 0000000000000..867e8fbbb5184 --- /dev/null +++ b/clang/test/CodeGenCXX/declcall-microsoft.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-pc-windows-msvc -emit-llvm %s -o - | FileCheck %s + +struct B { virtual int g(int); int h(int); }; + +// A qualified call to a virtual member devirtualizes to a direct function +// pointer, not a vftable (vcall) thunk. +// CHECK: @"?pv@@{{[^"]*}}" = {{.*}}global ptr @"?g@B@@UEAAHH@Z" +auto pv = declcall(((B *)0)->B::g(0)); + +// An unqualified virtual call keeps virtual dispatch: a vcall thunk. +// CHECK: @"?pu@@{{[^"]*}}" = {{.*}}global ptr @"??_9B@@$BA@AA" +auto pu = declcall(((B *)0)->g(0)); + +// A non-virtual member is a direct pointer. +// CHECK: @"?ph@@{{[^"]*}}" = {{.*}}global ptr @"?h@B@@QEAAHH@Z" +auto ph = declcall(((B *)0)->h(0)); From 656c55de7513ea0254731860349e009070cf4779 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 15:18:37 -0700 Subject: [PATCH 27/30] [clang] Include declcall's devirtualized flag in the statement profile The devirtualized flag is the only state distinguishing declcall of a qualified virtual call from an unqualified one (both rebuild the same &Class::method operand). Without profiling it, two inline definitions differing only in devirtualization hash identically, so a cross-module ODR mismatch is silently merged (import-order-dependent: direct pointer vs vtable index) instead of diagnosed. --- clang/lib/AST/StmtProfile.cpp | 4 ++ clang/test/Modules/declcall-odr.cpp | 69 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 clang/test/Modules/declcall-odr.cpp diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index b225f32f37888..064ca6ebc351a 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -2401,6 +2401,10 @@ void StmtProfiler::VisitCXXNoexceptExpr(const CXXNoexceptExpr *S) { void StmtProfiler::VisitCXXDeclcallExpr(const CXXDeclcallExpr *S) { VisitExpr(S); + // The devirtualized flag is the only state distinguishing a declcall of a + // qualified virtual call from an unqualified one (both rebuild the same + // &Class::method operand), so it must participate in the profile / ODR hash. + ID.AddBoolean(S->isDevirtualized()); } void StmtProfiler::VisitPackExpansionExpr(const PackExpansionExpr *S) { diff --git a/clang/test/Modules/declcall-odr.cpp b/clang/test/Modules/declcall-odr.cpp new file mode 100644 index 0000000000000..2d6c9a4bc163b --- /dev/null +++ b/clang/test/Modules/declcall-odr.cpp @@ -0,0 +1,69 @@ +// Clear and create directories +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: mkdir %t/cache +// RUN: mkdir %t/Inputs +// Build a shared base header that defines the polymorphic class B in its own +// module, so both FirstModule and SecondModule import (and merge) the *same* B. +// RUN: echo "#ifndef BASE_H" >> %t/Inputs/base.h +// RUN: echo "#define BASE_H" >> %t/Inputs/base.h +// RUN: echo "struct B { virtual int g(int); };" >> %t/Inputs/base.h +// RUN: echo "#endif" >> %t/Inputs/base.h +// Build first header file +// RUN: echo "#define FIRST" >> %t/Inputs/first.h +// RUN: cat %s >> %t/Inputs/first.h +// Build second header file +// RUN: echo "#define SECOND" >> %t/Inputs/second.h +// RUN: cat %s >> %t/Inputs/second.h +// Build module map file +// RUN: echo "module BaseModule {" >> %t/Inputs/module.modulemap +// RUN: echo " header \"base.h\"" >> %t/Inputs/module.modulemap +// RUN: echo "}" >> %t/Inputs/module.modulemap +// RUN: echo "module FirstModule {" >> %t/Inputs/module.modulemap +// RUN: echo " header \"first.h\"" >> %t/Inputs/module.modulemap +// RUN: echo "}" >> %t/Inputs/module.modulemap +// RUN: echo "module SecondModule {" >> %t/Inputs/module.modulemap +// RUN: echo " header \"second.h\"" >> %t/Inputs/module.modulemap +// RUN: echo "}" >> %t/Inputs/module.modulemap +// Run test +// RUN: %clang_cc1 -triple x86_64-linux-gnu -x c++ -std=c++2c -Wno-declcall-extension \ +// RUN: -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache \ +// RUN: -I%t/Inputs -verify %s + +// The two definitions of S differ only in the declcall devirtualization flag: +// FirstModule uses a qualified virtual call (B::g, which devirtualizes), +// SecondModule uses an unqualified virtual call (g, which does not). Both +// declcall operands rebuild the same &B::g operand AST, so only the +// Devirtualize bit distinguishes them. That bit must participate in the ODR +// hash so the mismatch is diagnosed rather than silently merged. + +#include "base.h" +#if !defined(FIRST) && !defined(SECOND) +#include "first.h" +#include "second.h" +#endif + +#if defined(FIRST) +struct S { + int (B::*p)(int) = declcall(((B *)0)->B::g(0)); // devirtualized +}; +#elif defined(SECOND) +struct S { + int (B::*p)(int) = declcall(((B *)0)->g(0)); // virtual +}; +#else +S s; +// expected-error@first.h:* {{'S' has different definitions in different modules; first difference is definition in module 'FirstModule' found field 'p' with an initializer}} +// expected-note@second.h:* {{but in 'SecondModule' found field 'p' with a different initializer}} +#endif + +// Keep the FIRST/SECOND macros contained to their own module so they do not +// leak into the main translation unit (which would re-enter a branch above and +// spuriously redefine 'S'). +#ifdef FIRST +#undef FIRST +#endif + +#ifdef SECOND +#undef SECOND +#endif From be5cd3c1162877d1922125138fa00d919e2826ae Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 16:03:43 -0700 Subject: [PATCH 28/30] [clang] Honor declcall devirtualization when calling a member pointer in the bytecode interpreter Calling through a devirtualized declcall member pointer under -fexperimental-new-constant-interpreter dispatched virtually, disagreeing with the classic evaluator and runtime codegen. The devirtualized flag is now: preserved across MemberPointer instance-binding/path copies, restored when a member pointer is materialized from an APValue, and honored by a new CallMemberPtr opcode that calls directly instead of dispatching. --- clang/lib/AST/ByteCode/Compiler.cpp | 13 +++- clang/lib/AST/ByteCode/Interp.cpp | 74 ++++++++++++++----- clang/lib/AST/ByteCode/Interp.h | 2 + clang/lib/AST/ByteCode/MemberPointer.h | 14 ++-- clang/lib/AST/ByteCode/Opcodes.td | 3 + .../test/SemaCXX/declcall-constexpr-call.cpp | 24 ++++++ 6 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 clang/test/SemaCXX/declcall-constexpr-call.cpp diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 20c5f59cc375d..f2fa4c1aaac6f 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -5846,6 +5846,11 @@ bool Compiler::visitAPValue(const APValue &Val, PrimType ValType, return false; } + // Restore the devirtualized flag (declcall of a qualified virtual call) + // so a call through this pointer bypasses virtual dispatch. + if (Val.isDeVirtualized() && !this->emitDevirtualizeMemberPtr(Info)) + return false; + return true; } return this->emitNullMemberPtr(0, nullptr, Info); @@ -6342,16 +6347,18 @@ bool Compiler::VisitCallExpr(const CallExpr *E) { // Get the callee, either from a member pointer or function pointer saved in // CalleeOffset. if (isa(E) && CalleeOffset) { + // Keep the member pointer on the stack so the call honors a + // devirtualized declcall pointer rather than always dispatching. if (!this->emitGetLocal(PT_MemberPtr, *CalleeOffset, E)) return false; - if (!this->emitGetMemberPtrDecl(E)) + if (!this->emitCallMemberPtr(ArgSize, E, E)) return false; } else { if (!this->emitGetLocal(PT_Ptr, *CalleeOffset, E)) return false; + if (!this->emitCallPtr(ArgSize, E, E)) + return false; } - if (!this->emitCallPtr(ArgSize, E, E)) - return false; } // Cleanup for discarded return values. diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 84fa4eca40b17..b58d31ae21269 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -2370,25 +2370,12 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE, return InterpretBuiltin(S, OpPC, CE, BuiltinID); } -bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, - const CallExpr *CE) { - const Pointer &Ptr = S.Stk.pop(); - - if (Ptr.isZero()) { - S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_null_callee) - << const_cast(CE->getCallee()) << CE->getSourceRange(); - return false; - } - - if (!Ptr.isFunctionPointer()) - return Invalid(S, OpPC); - - const Function *F = Ptr.asFunctionPointer().Func; - assert(F); - // Don't allow calling block pointers. - if (!F->getDecl()) - return Invalid(S, OpPC); - +// Shared tail of CallPtr / CallMemberPtr: given an already-resolved callee +// Function, perform the call. \p NoVirtual forces a direct (non-virtual) call, +// used for a devirtualized declcall member pointer. +static bool CallResolvedFunctionPointer(InterpState &S, CodePtr OpPC, + const Function *F, uint32_t ArgSize, + const CallExpr *CE, bool NoVirtual) { // This happens when the call expression has been cast to // something else, but we don't support that. if (S.Ctx.classify(F->getDecl()->getReturnType()) != @@ -2426,12 +2413,59 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, if (F->hasExplicitThisPointer()) VarArgSize -= align(primSize(PT_Ptr)); - if (F->isVirtual()) + if (!NoVirtual && F->isVirtual()) return CallVirt(S, OpPC, F, VarArgSize); return Call(S, OpPC, F, VarArgSize); } +bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, + const CallExpr *CE) { + const Pointer &Ptr = S.Stk.pop(); + + if (Ptr.isZero()) { + S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_null_callee) + << const_cast(CE->getCallee()) << CE->getSourceRange(); + return false; + } + + if (!Ptr.isFunctionPointer()) + return Invalid(S, OpPC); + + const Function *F = Ptr.asFunctionPointer().Func; + assert(F); + // Don't allow calling block pointers. + if (!F->getDecl()) + return Invalid(S, OpPC); + + return CallResolvedFunctionPointer(S, OpPC, F, ArgSize, CE, + /*NoVirtual=*/false); +} + +bool CallMemberPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, + const CallExpr *CE) { + const MemberPointer MP = S.Stk.pop(); + + const auto *FD = dyn_cast_if_present(MP.getDecl()); + if (!FD || !isa(FD)) + return false; + + // The method must be accessible via the base of the member pointer. + const CXXRecordDecl *MethodParent = cast(FD)->getParent(); + const Pointer &Base = MP.getBase(); + if (!Base.getRecord() || Base.getRecord()->getDecl() != MethodParent) + return false; + + const Function *F = S.getContext().getOrCreateFunction(FD); + if (!F) + return false; + + // A declcall of a qualified virtual call yields a devirtualized member + // pointer, which bypasses virtual dispatch when called. + return CallResolvedFunctionPointer(S, OpPC, F, ArgSize, CE, + /*NoVirtual=*/MP.isDevirtualized()); +} + static void startLifetimeRecurse(PtrView Ptr) { if (const Record *R = Ptr.getRecord()) { Ptr.startLifetime(); diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 7f40dc3b89723..d2d58e57c7a64 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -127,6 +127,8 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE, uint32_t BuiltinID); bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, const CallExpr *CE); +bool CallMemberPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, + const CallExpr *CE); bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T); bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index); bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits, diff --git a/clang/lib/AST/ByteCode/MemberPointer.h b/clang/lib/AST/ByteCode/MemberPointer.h index 01a2cdd98ede3..9213d1891b437 100644 --- a/clang/lib/AST/ByteCode/MemberPointer.h +++ b/clang/lib/AST/ByteCode/MemberPointer.h @@ -39,9 +39,10 @@ class MemberPointer final { MemberPointer(Pointer Base, const ValueDecl *Dcl, int32_t PtrOffset, uint8_t PathLength = 0, const CXXRecordDecl **Path = nullptr, - bool IsDerived = false) + bool IsDerived = false, bool DeVirtualized = false) : Base(Base), DeclAndIsDerivedMember(Dcl, IsDerived), Path(Path), - PtrOffset(PtrOffset), PathLength(PathLength) {} + PtrOffset(PtrOffset), PathLength(PathLength), + DeVirtualized(DeVirtualized) {} public: MemberPointer() = default; @@ -145,22 +146,23 @@ class MemberPointer final { bool NewIsDerived = false) const { if (Base.isZero()) return MemberPointer(Base, DeclAndIsDerivedMember.getPointer(), Offset, - PathLength, Path, NewIsDerived); + PathLength, Path, NewIsDerived, DeVirtualized); return MemberPointer(this->Base, DeclAndIsDerivedMember.getPointer(), - Offset + PtrOffset, PathLength, Path, NewIsDerived); + Offset + PtrOffset, PathLength, Path, NewIsDerived, + DeVirtualized); } MemberPointer takeInstance(Pointer Instance) const { assert(this->Base.isZero()); return MemberPointer(Instance, DeclAndIsDerivedMember.getPointer(), this->PtrOffset, PathLength, Path, - DeclAndIsDerivedMember.getInt()); + DeclAndIsDerivedMember.getInt(), DeVirtualized); } MemberPointer withPath(uint8_t PathLength, const CXXRecordDecl **Path, bool IsDerived) const { return MemberPointer(this->Base, DeclAndIsDerivedMember.getPointer(), - PtrOffset, PathLength, Path, IsDerived); + PtrOffset, PathLength, Path, IsDerived, DeVirtualized); } APValue toAPValue(const ASTContext &) const; diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 2d6aa1f5d57ec..0331d0e90f035 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -238,6 +238,9 @@ def CallBI : Opcode { let Args = [ArgCallExpr, ArgUint32]; } def CallPtr : Opcode { let Args = [ArgUint32, ArgCallExpr]; } +def CallMemberPtr : Opcode { + let Args = [ArgUint32, ArgCallExpr]; +} def CallVar : Opcode { let Args = [ArgFunction, ArgUint32]; diff --git a/clang/test/SemaCXX/declcall-constexpr-call.cpp b/clang/test/SemaCXX/declcall-constexpr-call.cpp new file mode 100644 index 0000000000000..a3d46f466215b --- /dev/null +++ b/clang/test/SemaCXX/declcall-constexpr-call.cpp @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -fexperimental-new-constant-interpreter -fsyntax-only -verify %s +// expected-no-diagnostics + +// Calling through a declcall member pointer at compile time must honor +// devirtualization consistently in both constant evaluators. + +struct Base { + constexpr virtual int foo() const { return 1; } +}; +struct Derived : Base { + constexpr int foo() const override { return 2; } +}; + +constexpr Derived d; + +// A qualified virtual call devirtualizes: calling through the pointer bypasses +// virtual dispatch and invokes Base::foo (1). +constexpr auto devirt = declcall(((Base *)0)->Base::foo()); +static_assert((d.*devirt)() == 1); + +// An unqualified virtual call keeps virtual dispatch: Derived::foo (2). +constexpr auto virt = declcall(((Base *)0)->foo()); +static_assert((d.*virt)() == 2); From b10c186d48b0d644e40063f86759c6e315c3589f Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 16:37:42 -0700 Subject: [PATCH 29/30] [clang] Keep declcall's original call expression for pretty-printing CXXDeclcallExpr rewrote its operand to the resolved callee, so -ast-print emitted declcall(f) / declcall(&B::g), which does not re-parse. Store the original call expression separately and print it, restoring round-tripping; evaluation and codegen continue to use the resolved operand. --- clang/include/clang/AST/ExprCXX.h | 14 +++++++++++--- clang/lib/AST/ASTImporter.cpp | 6 ++++-- clang/lib/AST/StmtPrinter.cpp | 3 ++- clang/lib/Sema/SemaExprCXX.cpp | 9 ++++++--- clang/lib/Serialization/ASTReaderStmt.cpp | 1 + clang/lib/Serialization/ASTWriterStmt.cpp | 1 + clang/test/AST/ast-print-declcall.cpp | 15 +++++++++++++++ 7 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 clang/test/AST/ast-print-declcall.cpp diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 27cab25f6f019..ce45d5f0d0529 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4350,21 +4350,29 @@ class CXXDeclcallExpr : public Expr { friend class ASTStmtReader; Stmt *Operand; + /// The original call expression the declcall was written with, kept for + /// source fidelity (e.g. -ast-print). Evaluation and codegen use Operand, + /// which is the resolved callee (a pointer or pointer to member). + Stmt *SourceExpr; SourceRange Range; bool Devirtualize = false; public: - CXXDeclcallExpr(QualType Ty, Expr *Operand, bool Devirtualize, - SourceLocation Keyword, SourceLocation RParen) + CXXDeclcallExpr(QualType Ty, Expr *Operand, Expr *SourceExpr, + bool Devirtualize, SourceLocation Keyword, + SourceLocation RParen) : Expr(CXXDeclcallExprClass, Ty, VK_PRValue, OK_Ordinary), - Operand(Operand), Range(Keyword, RParen), Devirtualize(Devirtualize) { + Operand(Operand), SourceExpr(SourceExpr), Range(Keyword, RParen), + Devirtualize(Devirtualize) { setDependence(computeDependence(this)); } CXXDeclcallExpr(EmptyShell Empty) : Expr(CXXDeclcallExprClass, Empty) {} Expr *getOperand() const { return static_cast(Operand); } + /// The original call expression, for printing / source fidelity. + Expr *getSourceExpr() const { return static_cast(SourceExpr); } SourceLocation getBeginLoc() const { return Range.getBegin(); } SourceLocation getEndLoc() const { return Range.getEnd(); } diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 364e7603af1cf..2351eca6fac87 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8567,13 +8567,15 @@ ExpectedStmt ASTNodeImporter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { auto ToType = importChecked(Err, E->getType()); auto ToOperand = importChecked(Err, E->getOperand()); // FIXME: dependent values?! + auto ToSourceExpr = importChecked(Err, E->getSourceExpr()); auto ToBeginLoc = importChecked(Err, E->getBeginLoc()); auto ToEndLoc = importChecked(Err, E->getEndLoc()); if (Err) return std::move(Err); - return new (Importer.getToContext()) CXXDeclcallExpr( - ToType, ToOperand, E->isDevirtualized(), ToBeginLoc, ToEndLoc); + return new (Importer.getToContext()) + CXXDeclcallExpr(ToType, ToOperand, ToSourceExpr, E->isDevirtualized(), + ToBeginLoc, ToEndLoc); } ExpectedStmt ASTNodeImporter::VisitCXXThrowExpr(CXXThrowExpr *E) { diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index ca0c32f5ea690..bf3b0208a8801 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -2731,7 +2731,8 @@ void StmtPrinter::VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { void StmtPrinter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { OS << "declcall("; - PrintExpr(E->getOperand()); + // Print the original call expression, not the resolved callee operand. + PrintExpr(E->getSourceExpr() ? E->getSourceExpr() : E->getOperand()); OS << ")"; } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index c145c7899fa97..57c1ef7f96044 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7494,10 +7494,13 @@ ExprResult Sema::ActOnNoexceptExpr(SourceLocation KeyLoc, SourceLocation, ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, SourceLocation RParen) { + // Remember the operand as written; the node keeps it for source fidelity + // (e.g. -ast-print) since Operand below is rewritten to the resolved callee. + Expr *SourceExpr = Operand; // in template ... do it later if (Operand->isInstantiationDependent()) { - return new (Context) - CXXDeclcallExpr(Context.DependentTy, Operand, false, KeyLoc, RParen); + return new (Context) CXXDeclcallExpr(Context.DependentTy, Operand, + SourceExpr, false, KeyLoc, RParen); } // unwrap parens @@ -7614,7 +7617,7 @@ ExprResult Sema::BuildCXXDeclcallExpr(SourceLocation KeyLoc, Expr *Operand, } return new (Context) - CXXDeclcallExpr(Operand->getType(), Operand, + CXXDeclcallExpr(Operand->getType(), Operand, SourceExpr, MemberCallHasQualifierAndIsVirtual, KeyLoc, RParen); } diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 4fe2cabed67be..e02dac621b2ff 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -2313,6 +2313,7 @@ void ASTStmtReader::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { VisitExpr(E); E->Range = readSourceRange(); E->Operand = Record.readSubExpr(); + E->SourceExpr = Record.readSubExpr(); E->Devirtualize = Record.readInt(); } diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 9897228f19ab4..bf57a2521b092 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -2332,6 +2332,7 @@ void ASTStmtWriter::VisitCXXDeclcallExpr(CXXDeclcallExpr *E) { VisitExpr(E); Record.AddSourceRange(E->getSourceRange()); Record.AddStmt(E->getOperand()); + Record.AddStmt(E->getSourceExpr()); Record.push_back(E->isDevirtualized()); Code = serialization::EXPR_CXX_DECLCALL; } diff --git a/clang/test/AST/ast-print-declcall.cpp b/clang/test/AST/ast-print-declcall.cpp new file mode 100644 index 0000000000000..77c97e7ed8bd5 --- /dev/null +++ b/clang/test/AST/ast-print-declcall.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-unknown-linux-gnu -ast-print %s -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-unknown-linux-gnu -ast-print %s -o - | %clang_cc1 -std=c++2c -fsycl-is-host -triple x86_64-unknown-linux-gnu -fsyntax-only -x c++ - + +// declcall prints the original call expression it was written with (not the +// resolved callee), so the pretty-printed output re-parses. + +int f(int); +int f(char); +struct B { virtual int g(int); }; + +// CHECK: auto p = declcall(f(0)); +auto p = declcall(f(0)); + +// CHECK: auto q = declcall(((B *)0)->B::g(0)); +auto q = declcall(((B *)0)->B::g(0)); From 42bbf64e814ee73880bf4a1d03d815e590d701df Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris Date: Tue, 11 Aug 2026 16:47:32 -0700 Subject: [PATCH 30/30] [clang][docs] Regenerate LibASTMatchersReference.html for cxxDeclcallExpr --- clang/docs/LibASTMatchersReference.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/docs/LibASTMatchersReference.html b/clang/docs/LibASTMatchersReference.html index 7e0bbbcb18938..3071624d8a2f8 100644 --- a/clang/docs/LibASTMatchersReference.html +++ b/clang/docs/LibASTMatchersReference.html @@ -1667,6 +1667,11 @@

Node Matchers

+Matcher<Stmt>cxxDeclcallExprMatcher<CXXDeclcallExpr>... +
Matches declcall expressions.
+
+ + Matcher<Stmt>cxxDefaultArgExprMatcher<CXXDefaultArgExpr>...
Matches the value of a default argument at the call site.