https://github.com/cor3ntin created https://github.com/llvm/llvm-project/pull/173537
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 >From 919f452cc5e523db8d325d82964de815a13b08ca Mon Sep 17 00:00:00 2001 From: Corentin Jabot <[email protected]> Date: Wed, 24 Dec 2025 14:30:29 +0100 Subject: [PATCH] [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 2319ff13f7864..2718dd61c139d 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -331,8 +331,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 @@ -491,12 +491,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 ---------------------------------- @@ -592,6 +592,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 c9ad6860dc625..bd1277a271906 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 889ac1e1a9a7e..f8e10ec2c9d39 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" @@ -1526,13 +1527,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, @@ -1558,7 +1576,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; @@ -1630,7 +1648,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 6ccd57bdc4df8..d9c1c8d6d6b8a 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; @@ -20707,6 +20739,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 ff104243a9735..bff9ac164a7ac 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}} } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
