[clang] [Clang] Instantiate constexpr function when they are needed. (PR #173537)
Corentin Jabot via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 9 01:52:51 PST 2026
https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/173537
>From 3f06fd997749fe3a3a89cdbf85eb4329c1982422 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 24 Dec 2025 14:30:29 +0100
Subject: [PATCH 1/4] [Clang] Instantiate constexpr function when they are
needed.
This is a rework of #115168, with the sole aim of fixing #73232.
The scaffolding is introduced here is necessary - but not sufficient
for reflection.
Reading the comments on #115168, I think we need to separate multiple
concerns:
- Do we have access to Sema (which this PR provides)
- Can we mutate the AST - which is something individual callers
will have to figure out.
This does **not** fix #59966, as the change is complicated enough
that i want to explore it in a separate PR.
Fixes #73232
---
clang/docs/ReleaseNotes.rst | 11 +--
clang/include/clang/AST/ASTContext.h | 17 ++++
clang/include/clang/Sema/Sema.h | 3 +
clang/lib/AST/ASTContext.cpp | 7 ++
clang/lib/AST/ByteCode/Interp.cpp | 30 +++++--
clang/lib/AST/ByteCode/InterpState.cpp | 2 +
clang/lib/AST/ByteCode/State.h | 2 +
clang/lib/AST/ExprConstant.cpp | 42 +++++++++-
clang/lib/Interpreter/IncrementalParser.cpp | 6 +-
clang/lib/Parse/ParseAST.cpp | 6 ++
clang/lib/Sema/Sema.cpp | 30 +++++++
.../SemaCXX/constexpr-late-instantiation.cpp | 84 +++++++++++++++++--
.../constexpr-subobj-initialization.cpp | 5 +-
.../SemaCXX/cxx2b-consteval-propagate.cpp | 5 +-
14 files changed, 225 insertions(+), 25 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 22aa8083d32ee..f481e330f2972 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -337,8 +337,8 @@ Non-comprehensive list of changes in this release
allocator-level heap organization strategies. A feature to instrument all
allocation functions with a token ID can be enabled via the
``-fsanitize=alloc-token`` flag.
-
-- A new generic byte swap builtin function ``__builtin_bswapg`` that extends the existing
+
+- A new generic byte swap builtin function ``__builtin_bswapg`` that extends the existing
__builtin_bswap{16,32,64} function family to support all standard integer types.
- A builtin ``__builtin_infer_alloc_token(<args>, ...)`` is provided to allow
@@ -497,12 +497,12 @@ Improvements to Clang's diagnostics
Objective-C method and block declarations when calling format functions. It is part
of the format-nonliteral diagnostic (#GH60718)
-- Fixed a crash when enabling ``-fdiagnostics-format=sarif`` and the output
+- Fixed a crash when enabling ``-fdiagnostics-format=sarif`` and the output
carries messages like 'In file included from ...' or 'In module ...'.
Now the include/import locations are written into `sarif.run.result.relatedLocations`.
-- Clang now generates a fix-it for C++20 designated initializers when the
- initializers do not match the declaration order in the structure.
+- Clang now generates a fix-it for C++20 designated initializers when the
+ initializers do not match the declaration order in the structure.
Improvements to Clang's time-trace
----------------------------------
@@ -608,6 +608,7 @@ Bug Fixes to C++ Support
- Fixed a bug where our ``member-like constrained friend`` checking caused an incorrect analysis of lambda captures. (#GH156225)
- Fixed a crash when implicit conversions from initialize list to arrays of
unknown bound during constant evaluation. (#GH151716)
+- Instantiate constexpr functions as needed before they are evaluated. (#GH73232)
- Support the dynamic_cast to final class optimization with pointer
authentication enabled. (#GH152601)
- Fix the check for narrowing int-to-float conversions, so that they are detected in
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 68205dd1c1fd9..206e482237342 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -44,6 +44,7 @@
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/TinyPtrVector.h"
#include "llvm/Support/TypeSize.h"
+#include <memory>
#include <optional>
namespace llvm {
@@ -215,6 +216,16 @@ struct TypeInfoChars {
}
};
+/// Interface that allows constant evaluator to call Sema
+/// and mutate the AST.
+struct SemaProxy {
+ virtual ~SemaProxy() = default;
+
+ virtual void
+ InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
+ FunctionDecl *Function) = 0;
+};
+
/// Holds long-lived AST nodes (such as types and decls) that can be
/// referred to throughout the semantic analysis of a file.
class ASTContext : public RefCountedBase<ASTContext> {
@@ -786,6 +797,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Keeps track of the deallocated DeclListNodes for future reuse.
DeclListNode *ListNodeFreeList = nullptr;
+ std::unique_ptr<SemaProxy> SemaProxyPtr;
+
public:
IdentifierTable &Idents;
SelectorTable &Selectors;
@@ -1409,6 +1422,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// with this AST context, if any.
ASTMutationListener *getASTMutationListener() const { return Listener; }
+ SemaProxy *getSemaProxy();
+
+ void setSemaProxy(std::unique_ptr<SemaProxy> Proxy);
+
void PrintStats() const;
const SmallVectorImpl<Type *>& getTypes() const { return Types; }
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 654bebbaf8ddd..299024178175e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -909,6 +909,9 @@ class Sema final : public SemaBase {
/// initialized but before it parses anything.
void Initialize();
+ void RegisterSemaProxy();
+ void UnregisterSemaProxy();
+
/// This virtual key function only exists to limit the emission of debug info
/// describing the Sema class. GCC and Clang only emit debug info for a class
/// with a vtable when the vtable is emitted. Sema is final and not
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index f52470a4d7458..a2768a9dc06b1 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -926,6 +926,13 @@ ASTContext::setExternalSource(IntrusiveRefCntPtr<ExternalASTSource> Source) {
ExternalSource = std::move(Source);
}
+SemaProxy *ASTContext::getSemaProxy() { return SemaProxyPtr.get(); }
+
+void ASTContext::setSemaProxy(std::unique_ptr<SemaProxy> Proxy) {
+ assert((!SemaProxyPtr || !Proxy) && "SemaProxy already set");
+ SemaProxyPtr = std::move(Proxy);
+}
+
void ASTContext::PrintStats() const {
llvm::errs() << "\n*** AST Context Stats:\n";
llvm::errs() << " " << Types.size() << " types total.\n";
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 0a083a4990856..043796bbec35e 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Interp.h"
+#include "ByteCode/Source.h"
#include "Compiler.h"
#include "Function.h"
#include "InterpFrame.h"
@@ -1557,13 +1558,30 @@ bool CheckFunctionDecl(InterpState &S, CodePtr OpPC, const FunctionDecl *FD) {
return diagnoseCallableDecl(S, OpPC, FD);
}
-static void compileFunction(InterpState &S, const Function *Func) {
- const FunctionDecl *Definition = Func->getDecl()->getDefinition();
- if (!Definition)
+static void compileFunction(InterpState &S, const Function *Func,
+ SourceLocation Loc) {
+
+ const FunctionDecl *Fn = Func->getDecl();
+
+ // [C++26] [temp.inst] p5
+ // [...] the function template specialization is implicitly instantiated
+ // when the specialization is referenced in a context that requires a function
+ // definition to exist or if the existence of the definition affects the
+ // semantics of the program.
+ if (!Fn->isDefined() && Fn->isImplicitlyInstantiable() && Fn->isConstexpr() &&
+ S.inConstantContext() && !S.TryConstantInitialization &&
+ !S.checkingPotentialConstantExpression()) {
+ SemaProxy *SP = S.getASTContext().getSemaProxy();
+ if (!SP)
+ return;
+ SP->InstantiateFunctionDefinition(Loc, const_cast<FunctionDecl *>(Fn));
+ }
+ Fn = Fn->getDefinition();
+ if (!Fn)
return;
Compiler<ByteCodeEmitter>(S.getContext(), S.P)
- .compileFunc(Definition, const_cast<Function *>(Func));
+ .compileFunc(Fn, const_cast<Function *>(Func));
}
bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
@@ -1589,7 +1607,7 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
}
if (!Func->isFullyCompiled())
- compileFunction(S, Func);
+ compileFunction(S, Func, S.Current->getLocation(OpPC));
if (!CheckCallable(S, OpPC, Func))
return false;
@@ -1661,7 +1679,7 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
}
if (!Func->isFullyCompiled())
- compileFunction(S, Func);
+ compileFunction(S, Func, S.Current->getLocation(OpPC));
if (!CheckCallable(S, OpPC, Func))
return cleanup();
diff --git a/clang/lib/AST/ByteCode/InterpState.cpp b/clang/lib/AST/ByteCode/InterpState.cpp
index a95916cd63981..ff201cb6151b1 100644
--- a/clang/lib/AST/ByteCode/InterpState.cpp
+++ b/clang/lib/AST/ByteCode/InterpState.cpp
@@ -22,6 +22,7 @@ InterpState::InterpState(State &Parent, Program &P, InterpStack &Stk,
: Parent(Parent), M(M), P(P), Stk(Stk), Ctx(Ctx), BottomFrame(*this),
Current(&BottomFrame) {
InConstantContext = Parent.InConstantContext;
+ TryConstantInitialization = Parent.TryConstantInitialization;
CheckingPotentialConstantExpression =
Parent.CheckingPotentialConstantExpression;
CheckingForUndefinedBehavior = Parent.CheckingForUndefinedBehavior;
@@ -34,6 +35,7 @@ InterpState::InterpState(State &Parent, Program &P, InterpStack &Stk,
BottomFrame(*this, Func, nullptr, CodePtr(), Func->getArgSize()),
Current(&BottomFrame) {
InConstantContext = Parent.InConstantContext;
+ TryConstantInitialization = Parent.TryConstantInitialization;
CheckingPotentialConstantExpression =
Parent.CheckingPotentialConstantExpression;
CheckingForUndefinedBehavior = Parent.CheckingForUndefinedBehavior;
diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h
index 0695c61c07a05..376d120c3a9d9 100644
--- a/clang/lib/AST/ByteCode/State.h
+++ b/clang/lib/AST/ByteCode/State.h
@@ -168,6 +168,8 @@ class State {
/// is set; this is used when evaluating ICEs in C.
bool CheckingForUndefinedBehavior = false;
+ bool TryConstantInitialization = false;
+
EvaluationMode EvalMode;
private:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 8618979d1eba0..33215c730ca11 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -43,6 +43,7 @@
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/CurrentSourceLocExprScope.h"
+#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/InferAlloc.h"
#include "clang/AST/OSLog.h"
@@ -53,6 +54,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/TargetBuiltins.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
@@ -7064,6 +7066,28 @@ static bool handleTrivialCopy(EvalInfo &Info, const ParmVarDecl *Param,
CopyObjectRepresentation);
}
+static void InstantiateFunctionBeforeCall(const FunctionDecl *FD,
+ EvalInfo &Info, SourceLocation Loc) {
+
+ // [C++26] [temp.inst] p5
+ // [...] the function template specialization is implicitly instantiated
+ // when the specialization is referenced in a context that requires a function
+ // definition to exist or if the existence of the definition affects the
+ // semantics of the program.
+
+ if (!FD->isDefined() && FD->isImplicitlyInstantiable() && FD->isConstexpr() &&
+ Info.InConstantContext && !Info.TryConstantInitialization &&
+ !Info.checkingPotentialConstantExpression()) {
+
+ SemaProxy *SP = Info.getASTContext().getSemaProxy();
+ // Try to instantiate the definition if Sema is available
+ // (i.e during the initial parse of the TU).
+ if (SP) {
+ SP->InstantiateFunctionDefinition(Loc, const_cast<FunctionDecl *>(FD));
+ }
+ }
+}
+
/// Evaluate a function call.
static bool HandleFunctionCall(SourceLocation CallLoc,
const FunctionDecl *Callee,
@@ -7457,6 +7481,8 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange,
if (!Info.CheckCallLimit(CallRange.getBegin()))
return false;
+ InstantiateFunctionBeforeCall(DD, Info, CallRange.getBegin());
+
const FunctionDecl *Definition = nullptr;
const Stmt *Body = DD->getBody(Definition);
@@ -8922,10 +8948,13 @@ class ExprEvaluatorBase
CallScope.destroy();
}
- const FunctionDecl *Definition = nullptr;
- Stmt *Body = FD->getBody(Definition);
SourceLocation Loc = E->getExprLoc();
+ InstantiateFunctionBeforeCall(FD, Info, Loc);
+
+ const FunctionDecl *Definition = nullptr;
+ const Stmt *Body = FD->getBody(Definition);
+
// Treat the object argument as `this` when evaluating defaulted
// special menmber functions
if (FD->hasCXXExplicitFunctionObjectParameter())
@@ -11443,8 +11472,10 @@ bool RecordExprEvaluator::VisitCXXConstructExpr(const CXXConstructExpr *E,
return handleDefaultInitValue(T, Result);
}
+ InstantiateFunctionBeforeCall(FD, Info, E->getBeginLoc());
+
const FunctionDecl *Definition = nullptr;
- auto Body = FD->getBody(Definition);
+ const Stmt *Body = FD->getBody(Definition);
if (!CheckConstexprFunction(Info, E->getExprLoc(), FD, Definition, Body))
return false;
@@ -11484,8 +11515,9 @@ bool RecordExprEvaluator::VisitCXXInheritedCtorInitExpr(
if (FD->isInvalidDecl() || FD->getParent()->isInvalidDecl())
return false;
+ InstantiateFunctionBeforeCall(FD, Info, E->getBeginLoc());
const FunctionDecl *Definition = nullptr;
- auto Body = FD->getBody(Definition);
+ const Stmt *Body = FD->getBody(Definition);
if (!CheckConstexprFunction(Info, E->getExprLoc(), FD, Definition, Body))
return false;
@@ -20745,6 +20777,8 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
: EvaluationMode::ConstantFold);
Info.setEvaluatingDecl(VD, Value);
Info.InConstantContext = IsConstantInitialization;
+ Info.TryConstantInitialization =
+ !VD->isConstexpr() && !VD->hasAttr<ConstInitAttr>();
SourceLocation DeclLoc = VD->getLocation();
QualType DeclTy = VD->getType();
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp
index bf08911e23533..5a24342e73ca4 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -42,9 +42,13 @@ IncrementalParser::IncrementalParser(CompilerInstance &Instance,
External->StartTranslationUnit(Consumer);
P->Initialize();
+ S.RegisterSemaProxy();
}
-IncrementalParser::~IncrementalParser() { P.reset(); }
+IncrementalParser::~IncrementalParser() {
+ S.UnregisterSemaProxy();
+ P.reset();
+}
llvm::Expected<TranslationUnitDecl *>
IncrementalParser::ParseOrWrapTopLevelDecl() {
diff --git a/clang/lib/Parse/ParseAST.cpp b/clang/lib/Parse/ParseAST.cpp
index c8ee625eb57ad..1cea282f13a00 100644
--- a/clang/lib/Parse/ParseAST.cpp
+++ b/clang/lib/Parse/ParseAST.cpp
@@ -21,6 +21,7 @@
#include "clang/Sema/Sema.h"
#include "clang/Sema/SemaConsumer.h"
#include "clang/Sema/TemplateInstCallback.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/TimeProfiler.h"
#include <cstdio>
@@ -161,6 +162,11 @@ void clang::ParseAST(Sema &S, bool PrintStats, bool SkipFunctionBodies) {
return M;
});
P.Initialize();
+
+ auto Unregister =
+ llvm::make_scope_exit([&S]() { S.UnregisterSemaProxy(); });
+ S.RegisterSemaProxy();
+
Parser::DeclGroupPtrTy ADecl;
Sema::ModuleImportState ImportState;
EnterExpressionEvaluationContext PotentiallyEvaluated(
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 6eea8f6e9d97e..e148c23f15516 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -75,11 +75,14 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Support/TimeProfiler.h"
+#include <memory>
#include <optional>
using namespace clang;
using namespace sema;
+static std::unique_ptr<SemaProxy> getSemaProxyImplementation(Sema &SemaRef);
+
SourceLocation Sema::getLocForEndOfToken(SourceLocation Loc, unsigned Offset) {
return Lexer::getLocForEndOfToken(Loc, Offset, SourceMgr, LangOpts);
}
@@ -622,6 +625,15 @@ Sema::~Sema() {
SemaPPCallbackHandler->reset();
}
+void Sema::RegisterSemaProxy() {
+ // Let the AST context relies on Sema for
+ // ast mutations features that require semantic analysis
+ // (lazy instantiation, reflection, etc).
+ Context.setSemaProxy(getSemaProxyImplementation(*this));
+}
+
+void Sema::UnregisterSemaProxy() { Context.setSemaProxy({}); }
+
void Sema::runWithSufficientStackSpace(SourceLocation Loc,
llvm::function_ref<void()> Fn) {
StackHandler.runWithSufficientStackSpace(Loc, Fn);
@@ -2958,3 +2970,21 @@ Attr *Sema::CreateAnnotationAttr(const ParsedAttr &AL) {
return CreateAnnotationAttr(AL, Str, Args);
}
+
+class SemaProxyImplementation final : public SemaProxy {
+private:
+ Sema &SemaRef;
+
+public:
+ SemaProxyImplementation(Sema &SemaRef) : SemaRef(SemaRef) {}
+ void InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
+ FunctionDecl *Function) override {
+ SemaRef.InstantiateFunctionDefinition(
+ PointOfInstantiation, Function, /*Recursive=*/true,
+ /*DefinitionRequired=*/true, /*AtEndOfTU=*/false);
+ }
+};
+
+std::unique_ptr<SemaProxy> getSemaProxyImplementation(Sema &SemaRef) {
+ return std::make_unique<SemaProxyImplementation>(SemaRef);
+}
diff --git a/clang/test/SemaCXX/constexpr-late-instantiation.cpp b/clang/test/SemaCXX/constexpr-late-instantiation.cpp
index 9aec0c90e61dc..59cfbfdff0e03 100644
--- a/clang/test/SemaCXX/constexpr-late-instantiation.cpp
+++ b/clang/test/SemaCXX/constexpr-late-instantiation.cpp
@@ -1,16 +1,90 @@
-// RUN: %clang_cc1 %s -fsyntax-only -verify
-// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -fsyntax-only -verify
+// RUN: %clang_cc1 %s -std=c++14 -fsyntax-only -verify
+// RUN: %clang_cc1 %s -std=c++20 -fsyntax-only -verify
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -verify
+
+// RUN: %clang_cc1 %s -std=c++14 -fsyntax-only -fexperimental-new-constant-interpreter -verify
+// RUN: %clang_cc1 %s -std=c++20 -fsyntax-only -fexperimental-new-constant-interpreter -verify
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fexperimental-new-constant-interpreter -verify
template <typename T>
-constexpr T foo(T a); // expected-note {{declared here}}
+constexpr T foo(T a); // expected-note {{explicit instantiation refers here}}
int main() {
int k = foo<int>(5); // Ok
- constexpr int j = // expected-error {{constexpr variable 'j' must be initialized by a constant expression}}
- foo<int>(5); // expected-note {{undefined function 'foo<int>' cannot be used in a constant expression}}
+ constexpr int j =
+ foo<int>(5); // expected-error {{explicit instantiation of undefined function template 'foo'}} \
+ // expected-error {{constexpr variable 'j' must be initialized by a constant expression}}
}
template <typename T>
constexpr T foo(T a) {
return a;
}
+
+
+namespace GH73232 {
+namespace ex1 {
+template <typename T>
+constexpr void g(T);
+
+constexpr int f() {
+ g(0);
+ return 0;
+}
+
+template <typename T>
+constexpr void g(T) {}
+
+constexpr auto z = f();
+}
+
+namespace ex2 {
+template <typename> constexpr static void fromType();
+
+void registerConverter() { fromType<int>(); }
+template <typename> struct QMetaTypeId {};
+template <typename T> constexpr void fromType() {
+ (void)QMetaTypeId<T>{};
+}
+template <> struct QMetaTypeId<int> {};
+} // namespace ex2
+
+namespace ex3 {
+
+#if __cplusplus > 202302L
+struct A {
+ consteval A(int i) {
+ chk(i);
+ }
+ constexpr void chk(auto) {}
+};
+A a{1};
+
+#endif
+
+}
+
+} // namespace GH73232
+
+
+namespace GH156255 {
+
+class X
+{
+public:
+ constexpr int f( int x ) const
+ {
+ return g( x );
+ }
+
+private:
+
+ template<class T>
+ constexpr T g( T x ) const
+ {
+ return x;
+ }
+};
+
+constexpr int x = X().f( 1 );
+}
diff --git a/clang/test/SemaCXX/constexpr-subobj-initialization.cpp b/clang/test/SemaCXX/constexpr-subobj-initialization.cpp
index f0252df1e2ce1..c2e51d3920f6a 100644
--- a/clang/test/SemaCXX/constexpr-subobj-initialization.cpp
+++ b/clang/test/SemaCXX/constexpr-subobj-initialization.cpp
@@ -47,12 +47,13 @@ constexpr Bar bb; // expected-error {{must be initialized by a constant expressi
template <typename Ty>
struct Baz {
- constexpr Baz(); // expected-note {{declared here}}
+ constexpr Baz(); // expected-note {{explicit instantiation refers here}}
};
struct Quux : Baz<Foo>, private Bar {
int i;
- constexpr Quux() : i(12) {} // expected-note {{undefined constructor 'Baz' cannot be used in a constant expression}}
+ constexpr Quux() : i(12) {} // expected-error {{explicit instantiation of undefined member function 'Baz' of class template 'Baz<Foo>'}} \
+ // expected-note {{subexpression not valid in a constant expression}}
};
constexpr Quux qx; // expected-error {{must be initialized by a constant expression}} \
diff --git a/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
index 6da589dcf1b25..aa476ca4dacc3 100644
--- a/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
+++ b/clang/test/SemaCXX/cxx2b-consteval-propagate.cpp
@@ -75,10 +75,11 @@ struct a {
a aa(fdupe<int>((f<int>(7))));
template <typename T>
-constexpr int foo(T t); // expected-note {{declared here}}
+constexpr int foo(T t); // expected-note {{explicit instantiation refers here}}
a bb(f<int>(foo<int>(7))); // expected-error{{call to immediate function 'f<int>' is not a constant expression}} \
- // expected-note{{undefined function 'foo<int>' cannot be used in a constant expression}}
+ // expected-error{{explicit instantiation of undefined function template 'foo'}} \
+ // expected-note{{subexpression not valid in a constant expression}}
}
>From 80458e39f24a1dcfbe08fc98eed2825f96abc30d Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 2 Jan 2026 10:11:38 +0100
Subject: [PATCH 2/4] Address some review comments
---
clang/docs/ReleaseNotes.rst | 2 +-
clang/lib/AST/ByteCode/Interp.cpp | 12 ++---
clang/lib/AST/ByteCode/InterpState.cpp | 4 +-
clang/lib/AST/ByteCode/State.h | 4 +-
clang/lib/AST/ExprConstant.cpp | 4 +-
clang/lib/Sema/Sema.cpp | 5 +-
.../SemaCXX/constexpr-late-instantiation.cpp | 49 +++++++++++++++++++
7 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index f481e330f2972..09300a2926704 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -608,7 +608,7 @@ Bug Fixes to C++ Support
- Fixed a bug where our ``member-like constrained friend`` checking caused an incorrect analysis of lambda captures. (#GH156225)
- Fixed a crash when implicit conversions from initialize list to arrays of
unknown bound during constant evaluation. (#GH151716)
-- Instantiate constexpr functions as needed before they are evaluated. (#GH73232)
+- Instantiate constexpr functions as needed before they are evaluated. (#GH73232) (#GH35052) (#GH100897)
- Support the dynamic_cast to final class optimization with pointer
authentication enabled. (#GH152601)
- Fix the check for narrowing int-to-float conversions, so that they are detected in
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 043796bbec35e..2365f260ad603 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -7,7 +7,6 @@
//===----------------------------------------------------------------------===//
#include "Interp.h"
-#include "ByteCode/Source.h"
#include "Compiler.h"
#include "Function.h"
#include "InterpFrame.h"
@@ -1559,7 +1558,7 @@ bool CheckFunctionDecl(InterpState &S, CodePtr OpPC, const FunctionDecl *FD) {
}
static void compileFunction(InterpState &S, const Function *Func,
- SourceLocation Loc) {
+ CodePtr OpPC) {
const FunctionDecl *Fn = Func->getDecl();
@@ -1569,12 +1568,13 @@ static void compileFunction(InterpState &S, const Function *Func,
// definition to exist or if the existence of the definition affects the
// semantics of the program.
if (!Fn->isDefined() && Fn->isImplicitlyInstantiable() && Fn->isConstexpr() &&
- S.inConstantContext() && !S.TryConstantInitialization &&
+ S.inConstantContext() && !S.PerformingTrialEvaluation &&
!S.checkingPotentialConstantExpression()) {
SemaProxy *SP = S.getASTContext().getSemaProxy();
if (!SP)
return;
- SP->InstantiateFunctionDefinition(Loc, const_cast<FunctionDecl *>(Fn));
+ SP->InstantiateFunctionDefinition(S.Current->getLocation(OpPC),
+ const_cast<FunctionDecl *>(Fn));
}
Fn = Fn->getDefinition();
if (!Fn)
@@ -1607,7 +1607,7 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
}
if (!Func->isFullyCompiled())
- compileFunction(S, Func, S.Current->getLocation(OpPC));
+ compileFunction(S, Func, OpPC);
if (!CheckCallable(S, OpPC, Func))
return false;
@@ -1679,7 +1679,7 @@ bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
}
if (!Func->isFullyCompiled())
- compileFunction(S, Func, S.Current->getLocation(OpPC));
+ compileFunction(S, Func, OpPC);
if (!CheckCallable(S, OpPC, Func))
return cleanup();
diff --git a/clang/lib/AST/ByteCode/InterpState.cpp b/clang/lib/AST/ByteCode/InterpState.cpp
index ff201cb6151b1..e57ff32f646a4 100644
--- a/clang/lib/AST/ByteCode/InterpState.cpp
+++ b/clang/lib/AST/ByteCode/InterpState.cpp
@@ -22,7 +22,7 @@ InterpState::InterpState(State &Parent, Program &P, InterpStack &Stk,
: Parent(Parent), M(M), P(P), Stk(Stk), Ctx(Ctx), BottomFrame(*this),
Current(&BottomFrame) {
InConstantContext = Parent.InConstantContext;
- TryConstantInitialization = Parent.TryConstantInitialization;
+ PerformingTrialEvaluation = Parent.PerformingTrialEvaluation;
CheckingPotentialConstantExpression =
Parent.CheckingPotentialConstantExpression;
CheckingForUndefinedBehavior = Parent.CheckingForUndefinedBehavior;
@@ -35,7 +35,7 @@ InterpState::InterpState(State &Parent, Program &P, InterpStack &Stk,
BottomFrame(*this, Func, nullptr, CodePtr(), Func->getArgSize()),
Current(&BottomFrame) {
InConstantContext = Parent.InConstantContext;
- TryConstantInitialization = Parent.TryConstantInitialization;
+ PerformingTrialEvaluation = Parent.PerformingTrialEvaluation;
CheckingPotentialConstantExpression =
Parent.CheckingPotentialConstantExpression;
CheckingForUndefinedBehavior = Parent.CheckingForUndefinedBehavior;
diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h
index 376d120c3a9d9..28850206b2467 100644
--- a/clang/lib/AST/ByteCode/State.h
+++ b/clang/lib/AST/ByteCode/State.h
@@ -168,7 +168,9 @@ class State {
/// is set; this is used when evaluating ICEs in C.
bool CheckingForUndefinedBehavior = false;
- bool TryConstantInitialization = false;
+ /// Whether we are performing trial evaluation, i.e when evaluating the
+ /// initializer of a constant-initialized variable.
+ bool PerformingTrialEvaluation = false;
EvaluationMode EvalMode;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 33215c730ca11..fa4b958512422 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -7076,7 +7076,7 @@ static void InstantiateFunctionBeforeCall(const FunctionDecl *FD,
// semantics of the program.
if (!FD->isDefined() && FD->isImplicitlyInstantiable() && FD->isConstexpr() &&
- Info.InConstantContext && !Info.TryConstantInitialization &&
+ Info.InConstantContext && !Info.PerformingTrialEvaluation &&
!Info.checkingPotentialConstantExpression()) {
SemaProxy *SP = Info.getASTContext().getSemaProxy();
@@ -20777,7 +20777,7 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
: EvaluationMode::ConstantFold);
Info.setEvaluatingDecl(VD, Value);
Info.InConstantContext = IsConstantInitialization;
- Info.TryConstantInitialization =
+ Info.PerformingTrialEvaluation =
!VD->isConstexpr() && !VD->hasAttr<ConstInitAttr>();
SourceLocation DeclLoc = VD->getLocation();
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index e148c23f15516..cf8631eff85c6 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -75,7 +75,6 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Support/TimeProfiler.h"
-#include <memory>
#include <optional>
using namespace clang;
@@ -626,8 +625,8 @@ Sema::~Sema() {
}
void Sema::RegisterSemaProxy() {
- // Let the AST context relies on Sema for
- // ast mutations features that require semantic analysis
+ // Let the AST context rely on Sema for
+ // AST mutation features that require semantic analysis
// (lazy instantiation, reflection, etc).
Context.setSemaProxy(getSemaProxyImplementation(*this));
}
diff --git a/clang/test/SemaCXX/constexpr-late-instantiation.cpp b/clang/test/SemaCXX/constexpr-late-instantiation.cpp
index 59cfbfdff0e03..cbfd26587c128 100644
--- a/clang/test/SemaCXX/constexpr-late-instantiation.cpp
+++ b/clang/test/SemaCXX/constexpr-late-instantiation.cpp
@@ -88,3 +88,52 @@ class X
constexpr int x = X().f( 1 );
}
+
+
+namespace GH35052 {
+
+template <typename F>
+constexpr int func(F f) {
+ if constexpr (f(1UL)) {
+ return 1;
+ }
+ return 0;
+}
+
+int test() {
+ auto predicate = [](auto v) constexpr -> bool { return v == 1; };
+ return func(predicate);
+}
+
+} // namespace GH35052
+
+namespace GH115118 {
+
+struct foo {
+ foo(const foo&) = default;
+ foo(auto)
+ requires([]<int = 0>() -> bool { return true; }())
+ {}
+};
+
+struct bar {
+ foo x;
+};
+
+} // namespace GH115118
+
+namespace GH100897 {
+
+template <typename>
+constexpr auto foo() noexcept {
+ constexpr auto extract_size = []<typename argument_t>() constexpr -> int {
+ return 1;
+ };
+
+ constexpr int result = extract_size.template operator()<int>();
+ return result;
+}
+
+void test() { foo<void>(); }
+
+} // namespace GH100897
>From 864be9a39b82b7b8d73e037a453c6d7d7701ddb1 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 2 Jan 2026 13:15:57 +0100
Subject: [PATCH 3/4] fix tests
---
clang/test/SemaCXX/constexpr-late-instantiation.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang/test/SemaCXX/constexpr-late-instantiation.cpp b/clang/test/SemaCXX/constexpr-late-instantiation.cpp
index cbfd26587c128..24df761cf0a52 100644
--- a/clang/test/SemaCXX/constexpr-late-instantiation.cpp
+++ b/clang/test/SemaCXX/constexpr-late-instantiation.cpp
@@ -89,6 +89,7 @@ class X
constexpr int x = X().f( 1 );
}
+#if __cplusplus > 202002L
namespace GH35052 {
@@ -105,6 +106,7 @@ int test() {
return func(predicate);
}
+
} // namespace GH35052
namespace GH115118 {
@@ -122,6 +124,7 @@ struct bar {
} // namespace GH115118
+
namespace GH100897 {
template <typename>
@@ -137,3 +140,5 @@ constexpr auto foo() noexcept {
void test() { foo<void>(); }
} // namespace GH100897
+
+#endif
>From 67c3a9a3e8d03443a762c3864af295ca6c153cce Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 9 Jan 2026 10:50:19 +0100
Subject: [PATCH 4/4] address more feedback
---
clang/docs/ReleaseNotes.rst | 10 +++++-----
clang/include/clang/Sema/Sema.h | 4 ++--
clang/lib/AST/ExprConstant.cpp | 3 +--
clang/lib/Interpreter/IncrementalParser.cpp | 4 ++--
clang/lib/Parse/ParseAST.cpp | 5 ++---
clang/lib/Sema/Sema.cpp | 4 ++--
6 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 09300a2926704..b04a3284920a9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -337,8 +337,8 @@ Non-comprehensive list of changes in this release
allocator-level heap organization strategies. A feature to instrument all
allocation functions with a token ID can be enabled via the
``-fsanitize=alloc-token`` flag.
-
-- A new generic byte swap builtin function ``__builtin_bswapg`` that extends the existing
+
+- A new generic byte swap builtin function ``__builtin_bswapg`` that extends the existing
__builtin_bswap{16,32,64} function family to support all standard integer types.
- A builtin ``__builtin_infer_alloc_token(<args>, ...)`` is provided to allow
@@ -497,12 +497,12 @@ Improvements to Clang's diagnostics
Objective-C method and block declarations when calling format functions. It is part
of the format-nonliteral diagnostic (#GH60718)
-- Fixed a crash when enabling ``-fdiagnostics-format=sarif`` and the output
+- Fixed a crash when enabling ``-fdiagnostics-format=sarif`` and the output
carries messages like 'In file included from ...' or 'In module ...'.
Now the include/import locations are written into `sarif.run.result.relatedLocations`.
-- Clang now generates a fix-it for C++20 designated initializers when the
- initializers do not match the declaration order in the structure.
+- Clang now generates a fix-it for C++20 designated initializers when the
+ initializers do not match the declaration order in the structure.
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 299024178175e..e8e076e5fc059 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -909,8 +909,8 @@ class Sema final : public SemaBase {
/// initialized but before it parses anything.
void Initialize();
- void RegisterSemaProxy();
- void UnregisterSemaProxy();
+ void registerSemaProxy();
+ void unregisterSemaProxy();
/// This virtual key function only exists to limit the emission of debug info
/// describing the Sema class. GCC and Clang only emit debug info for a class
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index fa4b958512422..2d0cf3830166c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -7082,9 +7082,8 @@ static void InstantiateFunctionBeforeCall(const FunctionDecl *FD,
SemaProxy *SP = Info.getASTContext().getSemaProxy();
// Try to instantiate the definition if Sema is available
// (i.e during the initial parse of the TU).
- if (SP) {
+ if (SP)
SP->InstantiateFunctionDefinition(Loc, const_cast<FunctionDecl *>(FD));
- }
}
}
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp
index 5a24342e73ca4..5630b96927c38 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -42,11 +42,11 @@ IncrementalParser::IncrementalParser(CompilerInstance &Instance,
External->StartTranslationUnit(Consumer);
P->Initialize();
- S.RegisterSemaProxy();
+ S.registerSemaProxy();
}
IncrementalParser::~IncrementalParser() {
- S.UnregisterSemaProxy();
+ S.unregisterSemaProxy();
P.reset();
}
diff --git a/clang/lib/Parse/ParseAST.cpp b/clang/lib/Parse/ParseAST.cpp
index 1cea282f13a00..e83c1e8ec292b 100644
--- a/clang/lib/Parse/ParseAST.cpp
+++ b/clang/lib/Parse/ParseAST.cpp
@@ -163,9 +163,8 @@ void clang::ParseAST(Sema &S, bool PrintStats, bool SkipFunctionBodies) {
});
P.Initialize();
- auto Unregister =
- llvm::make_scope_exit([&S]() { S.UnregisterSemaProxy(); });
- S.RegisterSemaProxy();
+ llvm::scope_exit _([&S]() { S.unregisterSemaProxy(); });
+ S.registerSemaProxy();
Parser::DeclGroupPtrTy ADecl;
Sema::ModuleImportState ImportState;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index cf8631eff85c6..faaacc8738c12 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -624,14 +624,14 @@ Sema::~Sema() {
SemaPPCallbackHandler->reset();
}
-void Sema::RegisterSemaProxy() {
+void Sema::registerSemaProxy() {
// Let the AST context rely on Sema for
// AST mutation features that require semantic analysis
// (lazy instantiation, reflection, etc).
Context.setSemaProxy(getSemaProxyImplementation(*this));
}
-void Sema::UnregisterSemaProxy() { Context.setSemaProxy({}); }
+void Sema::unregisterSemaProxy() { Context.setSemaProxy({}); }
void Sema::runWithSufficientStackSpace(SourceLocation Loc,
llvm::function_ref<void()> Fn) {
More information about the cfe-commits
mailing list