https://github.com/conrade-ctc updated https://github.com/llvm/llvm-project/pull/226253
>From 9f885a367ec1f3fc738cc63e162f5e49eae47597 Mon Sep 17 00:00:00 2001 From: Emery Conrad <[email protected]> Date: Thu, 24 Sep 2026 11:19:22 -0500 Subject: [PATCH] [clang-repl] Reset the implicit instantiations of a failed input When an input fails, Sema still performs the implicit instantiations it asked for and marks them done. The consumers drop them after the error, and Sema does not instantiate them again, so a later input gets only a declaration. Keeping them is not safe either: their bodies can bind to functions of the failed input, which the interpreter withdraws. Record the implicit instantiations of each input with an ASTMutationListener. When the input fails, reset them: drop the instantiated body or initializer, the deduced return type, the invalid flag and the point of instantiation. Withdraw the out-of-line definition of a static data member. Forget the vtable use that instantiated virtual functions. The next use then instantiates them again, without the declarations of the failed input. Notify the listener when a member specialization gets its point of instantiation. Let VarTemplateSpecializationDecl take an invalid point of instantiation and clear its complete-definition flag. Class template specializations keep their definition, because their layout is cached by declaration. Co-developed-with-the-help-of: Claude Code (Opus 5.5, human in the loop) --- clang/include/clang/AST/DeclTemplate.h | 6 +- clang/lib/Interpreter/IncrementalAction.cpp | 23 +- clang/lib/Interpreter/IncrementalAction.h | 54 +++- clang/lib/Interpreter/IncrementalParser.cpp | 102 +++++++ clang/lib/Interpreter/IncrementalParser.h | 4 + clang/lib/Sema/SemaExpr.cpp | 14 +- .../failed-input-resets-instantiations.cpp | 37 +++ .../unittests/Interpreter/InterpreterTest.cpp | 280 ++++++++++++++++++ 8 files changed, 501 insertions(+), 19 deletions(-) create mode 100644 clang/test/Interpreter/failed-input-resets-instantiations.cpp diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h index d32b044ef40e2..5ff62ec63e756 100644 --- a/clang/include/clang/AST/DeclTemplate.h +++ b/clang/include/clang/AST/DeclTemplate.h @@ -2741,12 +2741,14 @@ class VarTemplateSpecializationDecl : public VarDecl, return PointOfInstantiation; } + /// An invalid location means that no instantiation was requested yet. void setPointOfInstantiation(SourceLocation Loc) { - assert(Loc.isValid() && "point of instantiation must be valid!"); PointOfInstantiation = Loc; } - void setCompleteDefinition() { IsCompleteDefinition = true; } + void setCompleteDefinition(bool Complete = true) { + IsCompleteDefinition = Complete; + } /// If this variable template specialization is an instantiation of /// a template (rather than an explicit specialization), return the diff --git a/clang/lib/Interpreter/IncrementalAction.cpp b/clang/lib/Interpreter/IncrementalAction.cpp index 85f00c36dd5aa..880e9bf5ee5ac 100644 --- a/clang/lib/Interpreter/IncrementalAction.cpp +++ b/clang/lib/Interpreter/IncrementalAction.cpp @@ -59,17 +59,20 @@ IncrementalAction::IncrementalAction(CompilerInstance &Instance, std::unique_ptr<ASTConsumer> IncrementalAction::CreateASTConsumer(CompilerInstance & /*CI*/, StringRef InFile) { - std::unique_ptr<ASTConsumer> C = - WrapperFrontendAction::CreateASTConsumer(this->CI, InFile); - - if (Consumer) { - std::vector<std::unique_ptr<ASTConsumer>> Cs; + std::vector<std::unique_ptr<ASTConsumer>> Cs; + bool HasCustomConsumer = Consumer != nullptr; + if (HasCustomConsumer) Cs.push_back(std::move(Consumer)); - Cs.push_back(std::move(C)); + Cs.push_back(WrapperFrontendAction::CreateASTConsumer(this->CI, InFile)); + + auto Recorder = std::make_unique<ImplicitInstantiationRecorder>(); + Instantiations = Recorder.get(); + Cs.push_back(std::move(Recorder)); + + if (HasCustomConsumer) return std::make_unique<MultiplexConsumer>(std::move(Cs)); - } - return std::make_unique<InProcessPrintingASTConsumer>(std::move(C), Interp); + return std::make_unique<InProcessPrintingASTConsumer>(std::move(Cs), Interp); } void IncrementalAction::ExecuteAction() { @@ -129,8 +132,8 @@ CodeGenerator *IncrementalAction::getCodeGen() const { } InProcessPrintingASTConsumer::InProcessPrintingASTConsumer( - std::unique_ptr<ASTConsumer> C, Interpreter &I) - : MultiplexConsumer(std::move(C)), Interp(I) {} + std::vector<std::unique_ptr<ASTConsumer>> Cs, Interpreter &I) + : MultiplexConsumer(std::move(Cs)), Interp(I) {} bool InProcessPrintingASTConsumer::HandleTopLevelDecl(DeclGroupRef DGR) { if (DGR.isNull()) diff --git a/clang/lib/Interpreter/IncrementalAction.h b/clang/lib/Interpreter/IncrementalAction.h index 2893ff7b5baa6..2a2ffa1d21ee4 100644 --- a/clang/lib/Interpreter/IncrementalAction.h +++ b/clang/lib/Interpreter/IncrementalAction.h @@ -9,8 +9,12 @@ #ifndef LLVM_CLANG_INTERPRETER_INCREMENTALACTION_H #define LLVM_CLANG_INTERPRETER_INCREMENTALACTION_H +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTMutationListener.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/MultiplexConsumer.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" namespace llvm { class LLVMContext; @@ -22,6 +26,45 @@ namespace clang { class Interpreter; class CodeGenerator; +/// Records the implicit instantiations of the current input. If the input +/// fails, \p IncrementalParser resets them, and the next use instantiates them +/// again. +class ImplicitInstantiationRecorder : public ASTConsumer, + public ASTMutationListener { +public: + /// Specializations that got a point of instantiation. + llvm::SmallSetVector<ValueDecl *, 8> Requested; + /// Functions that got an instantiated definition. + llvm::SmallSetVector<FunctionDecl *, 8> Functions; + /// Variables that got an instantiated definition or initializer. + llvm::SmallSetVector<VarDecl *, 8> Variables; + /// Functions whose return type was deduced from the instantiated definition. + llvm::SmallPtrSet<const FunctionDecl *, 8> DeducedReturnTypes; + + ASTMutationListener *GetASTMutationListener() override { return this; } + + // The listener gets const decls. The reset changes them. + void InstantiationRequested(const ValueDecl *D) override { + Requested.insert(const_cast<ValueDecl *>(D)); + } + void FunctionDefinitionInstantiated(const FunctionDecl *D) override { + Functions.insert(const_cast<FunctionDecl *>(D)); + } + void VariableDefinitionInstantiated(const VarDecl *D) override { + Variables.insert(const_cast<VarDecl *>(D)); + } + void DeducedReturnType(const FunctionDecl *FD, QualType ReturnType) override { + DeducedReturnTypes.insert(FD); + } + + void clear() { + Requested.clear(); + Functions.clear(); + Variables.clear(); + DeducedReturnTypes.clear(); + } +}; + /// A custom action enabling the incremental processing functionality. /// /// The usual \p FrontendAction expects one call to ExecuteAction and once it @@ -38,6 +81,9 @@ class IncrementalAction : public WrapperFrontendAction { [[maybe_unused]] CompilerInstance &CI; std::unique_ptr<ASTConsumer> Consumer; + /// Owned by the consumer chain of the CompilerInstance. + ImplicitInstantiationRecorder *Instantiations = nullptr; + /// When CodeGen is created the first llvm::Module gets cached in many places /// and we must keep it alive. std::unique_ptr<llvm::Module> CachedInCodeGenModule; @@ -73,6 +119,11 @@ class IncrementalAction : public WrapperFrontendAction { /// Access the current code generator. CodeGenerator *getCodeGen() const; + /// The implicit instantiations of the current input. + ImplicitInstantiationRecorder &getImplicitInstantiations() { + return *Instantiations; + } + /// Generate an LLVM module for the most recent parsed input. std::unique_ptr<llvm::Module> GenModule(); }; @@ -81,7 +132,8 @@ class InProcessPrintingASTConsumer final : public MultiplexConsumer { Interpreter &Interp; public: - InProcessPrintingASTConsumer(std::unique_ptr<ASTConsumer> C, Interpreter &I); + InProcessPrintingASTConsumer(std::vector<std::unique_ptr<ASTConsumer>> Cs, + Interpreter &I); bool HandleTopLevelDecl(DeclGroupRef DGR) override; }; diff --git a/clang/lib/Interpreter/IncrementalParser.cpp b/clang/lib/Interpreter/IncrementalParser.cpp index 3cfabb5e5daa1..b29ac7b04c652 100644 --- a/clang/lib/Interpreter/IncrementalParser.cpp +++ b/clang/lib/Interpreter/IncrementalParser.cpp @@ -63,6 +63,7 @@ IncrementalParser::ParseOrWrapTopLevelDecl() { // Add a new PTU. ASTContext &C = S.getASTContext(); C.addTranslationUnitDecl(); + Act->getImplicitInstantiations().clear(); // Skip previous eof due to last incremental input. if (P->getCurToken().is(tok::annot_repl_input_end)) { @@ -88,6 +89,7 @@ IncrementalParser::ParseOrWrapTopLevelDecl() { DiagnosticsEngine &Diags = S.getDiagnostics(); if (Diags.hasErrorOccurred()) { + resetImplicitInstantiations(C.getTranslationUnitDecl()); CleanUpPTU(C.getTranslationUnitDecl()); Diags.Reset(/*soft=*/true); @@ -322,8 +324,108 @@ class ASTDeclUnmerger : public DeclVisitor<ASTDeclUnmerger> { } void VisitTranslationUnitDecl(TranslationUnitDecl *D) { VisitDeclContext(D); } + + /// Withdraw a redeclaration that the discarded PTU added to a context that + /// survives, such as the instantiated definition of a static data member. + void withdrawRedeclaration(VarDecl *D) { + VarDecl *Prev = D->getPreviousDecl(); + assert(Prev && "not a redeclaration"); + unlinkRedeclChain(S.getASTContext(), D, Prev); + + // removeDecl also drops D from the lookup table of its context. + DeclContext *LexicalDC = D->getLexicalDeclContext(); + if (LexicalDC->containsDecl(D)) + LexicalDC->removeDecl(D); + removeFromLookups(D); + D->getDeclContext()->getPrimaryContext()->makeDeclVisibleInContext(Prev); + } }; +/// An implicit instantiation. An explicit instantiation declaration still +/// instantiates inline functions and constant variables implicitly. +static bool isImplicitlyInstantiated(TemplateSpecializationKind TSK) { + return TSK == TSK_ImplicitInstantiation || + TSK == TSK_ExplicitInstantiationDeclaration; +} + +static void resetPointOfInstantiation(FunctionDecl *FD) { + if (auto *Info = FD->getTemplateSpecializationInfo()) + Info->setPointOfInstantiation(SourceLocation()); + else if (auto *MSI = FD->getMemberSpecializationInfo()) + MSI->setPointOfInstantiation(SourceLocation()); + FD->setInstantiationIsPending(false); +} + +static void resetPointOfInstantiation(VarDecl *VD) { + if (auto *Spec = dyn_cast<VarTemplateSpecializationDecl>(VD)) + Spec->setPointOfInstantiation(SourceLocation()); + else if (auto *MSI = VD->getMemberSpecializationInfo()) + MSI->setPointOfInstantiation(SourceLocation()); +} + +void IncrementalParser::resetImplicitInstantiations( + TranslationUnitDecl *FailedTU) { + ImplicitInstantiationRecorder &R = Act->getImplicitInstantiations(); + ASTContext &C = S.getASTContext(); + ASTDeclUnmerger Unmerger(S, FailedTU); + + for (FunctionDecl *FD : R.Functions) { + if (!isImplicitlyInstantiated( + FD->getTemplateSpecializationKindForInstantiation()) || + FD->isDefaulted()) + continue; + FD->setBody(nullptr); + FD->setWillHaveBody(false); + FD->setInvalidDecl(false); + if (R.DeducedReturnTypes.contains(FD)) + C.adjustDeducedFunctionResultType(FD, FD->getDeclaredReturnType()); + resetPointOfInstantiation(FD); + // The first use of a vtable instantiates its virtual functions. Forget the + // use, so the next one instantiates them again. + if (auto *MD = dyn_cast<CXXMethodDecl>(FD); MD && MD->isVirtual()) + S.VTablesUsed.erase(MD->getParent()->getCanonicalDecl()); + } + + for (VarDecl *VD : R.Variables) { + // Skip the local variables of instantiated functions. + if (!VD->isStaticDataMember() && !isa<VarTemplateSpecializationDecl>(VD)) + continue; + if (!isImplicitlyInstantiated( + VD->getTemplateSpecializationKindForInstantiation())) + continue; + // An out-of-line definition is a new declaration. + if (VarDecl *Decl = VD->getPreviousDecl()) { + Unmerger.withdrawRedeclaration(VD); + resetPointOfInstantiation(Decl); + continue; + } + VD->setInit(nullptr); + // Undo a type deduced or completed by the initializer. + if (TypeSourceInfo *TSI = VD->getTypeSourceInfo()) + VD->setType(TSI->getType()); + if (auto *Spec = dyn_cast<VarTemplateSpecializationDecl>(VD)) + Spec->setCompleteDefinition(false); + VD->setInvalidDecl(false); + resetPointOfInstantiation(VD); + } + + // A request with no definition yet (a template declared, not defined) must + // come again too. + for (ValueDecl *D : R.Requested) { + if (auto *FD = dyn_cast<FunctionDecl>(D)) { + if (isImplicitlyInstantiated( + FD->getTemplateSpecializationKindForInstantiation())) + resetPointOfInstantiation(FD); + } else if (auto *VD = dyn_cast<VarDecl>(D)) { + if (isImplicitlyInstantiated( + VD->getTemplateSpecializationKindForInstantiation())) + resetPointOfInstantiation(VD); + } + } + + R.clear(); +} + void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) { ASTDeclUnmerger(S, MostRecentTU).Visit(MostRecentTU); diff --git a/clang/lib/Interpreter/IncrementalParser.h b/clang/lib/Interpreter/IncrementalParser.h index b626cebaafcd7..dbf647c2d4ed2 100644 --- a/clang/lib/Interpreter/IncrementalParser.h +++ b/clang/lib/Interpreter/IncrementalParser.h @@ -73,6 +73,10 @@ class IncrementalParser { /// Rebuild the translation unit redeclaration chain without \p MostRecentTU, /// making its predecessor the current unit again. void withdrawMostRecentTU(TranslationUnitDecl *MostRecentTU); + + /// Reset the implicit instantiations of the failed unit \p FailedTU, so the + /// next use instantiates them again. + void resetImplicitInstantiations(TranslationUnitDecl *FailedTU); }; } // end namespace clang diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index be1dc9f85d4f7..c32fa9173cd3c 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -19329,10 +19329,11 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func, bool FirstInstantiation = PointOfInstantiation.isInvalid(); if (FirstInstantiation) { PointOfInstantiation = Loc; - if (auto *MSI = Func->getMemberSpecializationInfo()) + if (auto *MSI = Func->getMemberSpecializationInfo()) { MSI->setPointOfInstantiation(Loc); - // FIXME: Notify listener. - else + if (ASTMutationListener *L = getASTMutationListener()) + L->InstantiationRequested(Func); + } else Func->setTemplateSpecializationKind(TSK, PointOfInstantiation); } else if (TSK != TSK_ImplicitInstantiation) { // Use the point of use as the point of instantiation, instead of the @@ -20959,10 +20960,11 @@ static void DoMarkVarDeclReferenced( bool FirstInstantiation = PointOfInstantiation.isInvalid(); if (FirstInstantiation) { PointOfInstantiation = Loc; - if (MSI) + if (MSI) { MSI->setPointOfInstantiation(PointOfInstantiation); - // FIXME: Notify listener. - else + if (ASTMutationListener *L = SemaRef.getASTMutationListener()) + L->InstantiationRequested(Var); + } else Var->setTemplateSpecializationKind(TSK, PointOfInstantiation); } diff --git a/clang/test/Interpreter/failed-input-resets-instantiations.cpp b/clang/test/Interpreter/failed-input-resets-instantiations.cpp new file mode 100644 index 0000000000000..8a0bc95ebef78 --- /dev/null +++ b/clang/test/Interpreter/failed-input-resets-instantiations.cpp @@ -0,0 +1,37 @@ +// REQUIRES: host-supports-jit +// UNSUPPORTED: system-aix +// RUN: cat %s | clang-repl 2>&1 | FileCheck %s + +// A failed input still triggers the implicit instantiations it uses. The +// interpreter resets them, so a later input instantiates them again without +// the declarations of the failed input. + +extern "C" int printf(const char *, ...); + +template <class T> T twice(T x) { return x + x; } +extern "C" int twice_fail(int *p) { return twice(*p) + no_such_name; } +// CHECK-DAG: error: use of undeclared identifier 'no_such_name' +extern "C" int twice_after(int x) { return twice(x); } +printf("twice_after = %d\n", twice_after(21)); +// CHECK-DAG: twice_after = 42 + +template <class T> struct Holder { static T value; }; +template <class T> T Holder<T>::value = T(7); +extern "C" int holder_fail() { return Holder<int>::value + no_such_name; } +// CHECK-DAG: error: use of undeclared identifier 'no_such_name' +printf("Holder<int>::value = %d\n", Holder<int>::value); +// CHECK-DAG: Holder<int>::value = 7 + +// The instantiation of readv<S> binds value(t) to a function of the failed +// input. A later use must not keep that binding. +template <class T> int readv(T t) { return value(t); } +struct S {}; +int value(S) { return no_such_name; } extern "C" int readv_fail() { return readv(S{}); } +// CHECK-DAG: error: use of undeclared identifier 'no_such_name' +extern "C" int readv_again() { return readv(S{}); } +// CHECK-DAG: error: use of undeclared identifier 'value' +int value(const S &) { return 7; } +printf("readv = %d\n", readv(S{})); +// CHECK-DAG: readv = 7 + +%quit diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp index 3becd00c12820..e1efb0a3268a9 100644 --- a/clang/unittests/Interpreter/InterpreterTest.cpp +++ b/clang/unittests/Interpreter/InterpreterTest.cpp @@ -248,6 +248,57 @@ TEST_F(InterpreterTest, UndoCommand) { EXPECT_FALSE(Err12); } +// A failed input is not a PTU, so Undo acts on the last good input. +TEST_F(InterpreterTest, UndoAfterFailedInput) { +#ifdef __EMSCRIPTEN__ + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif + std::unique_ptr<Interpreter> Interp = createInterpreter(); + + // Nothing to undo: a failed input does not count. + auto Err1 = Interp->Parse("int bad = ;").takeError(); + EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err1))); + auto Err2 = Interp->Undo(); + EXPECT_EQ("Operation failed. No input left to undo", + llvm::toString(std::move(Err2))); + + // Undo after a failed input removes the last good input. + cantFail(Interp->Parse("int kept = 1;")); + auto Err3 = Interp->Parse("int bad = kept + ;").takeError(); + EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err3))); + cantFail(Interp->Undo()); + auto Err4 = Interp->Parse("int use = kept;").takeError(); + EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err4))); + auto Err5 = Interp->Undo(); + EXPECT_EQ("Operation failed. No input left to undo", + llvm::toString(std::move(Err5))); + + // The name is free again. + cantFail(Interp->Parse("double kept = 2.0;")); +} + +// Undo frees a C-linkage name, so the same definition can come back. +TEST_F(InterpreterTest, UndoExternCDefinition) { +#ifdef __EMSCRIPTEN__ + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif + std::unique_ptr<Interpreter> Interp = createInterpreter(); + + cantFail(Interp->Parse("extern \"C\" int f() { return 1; }")); + cantFail(Interp->Undo()); + cantFail(Interp->Parse("extern \"C\" int f() { return 1; }")); + + cantFail(Interp->ParseAndExecute("extern \"C\" int g() { return 1; }")); + cantFail(Interp->Undo()); + cantFail(Interp->ParseAndExecute("extern \"C\" int g() { return 2; }")); + auto G = cantFail(Interp->getSymbolAddress("g")).toPtr<int (*)()>(); + EXPECT_EQ(2, G()); + + cantFail(Interp->Parse("extern \"C\" { int h() { return 1; } }")); + cantFail(Interp->Undo()); + cantFail(Interp->Parse("extern \"C\" { int h() { return 1; } }")); +} + static std::string MangleName(NamedDecl *ND) { ASTContext &C = ND->getASTContext(); std::unique_ptr<MangleContext> MangleC(C.createMangleContext()); @@ -350,6 +401,235 @@ TEST_F(InterpreterTest, InstantiateTemplate) { EXPECT_EQ(42, fn(NewA.getPtr())); } +// A failed input must not leave its implicit instantiations behind. A later +// input that uses them must instantiate them again, without the declarations +// of the failed input. +struct FailedInputInstantiationTest : InterpreterTest { + std::string DiagText; + llvm::raw_string_ostream DiagOS{DiagText}; + DiagnosticOptions DiagOpts; + TextDiagnosticPrinter DiagPrinter{DiagOS, DiagOpts}; + std::unique_ptr<Interpreter> Interp; + + void SetUp() override { + InterpreterTest::SetUp(); + // FIXME: We cannot yet handle delayed template parsing. + Interp = createInterpreter({"-fno-delayed-template-parsing"}, &DiagPrinter); + } + + /// The diagnostics of a failed input. + std::string ParseFails(llvm::StringRef Code) { + DiagText.clear(); + llvm::Error Err = Interp->Parse(Code).takeError(); + EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err))); + return DiagText; + } + + void ExpectParseFails(llvm::StringRef Code) { ParseFails(Code); } + + template <typename Fn> Fn *Lookup(llvm::StringRef Name) { + return cantFail(Interp->getSymbolAddress(Name)).toPtr<Fn *>(); + } +}; + +TEST_F(FailedInputInstantiationTest, FunctionTemplate) { + cantFail(Interp->ParseAndExecute( + "template <class T> T twice(T x) { return x + x; }")); + ExpectParseFails("extern \"C\" double twice_fail(double *p) {" + " return twice(*p) + no_such_name; }"); + cantFail(Interp->ParseAndExecute( + "extern \"C\" double twice_after(double x) { return twice(x); }")); + EXPECT_EQ(6.0, Lookup<double(double)>("twice_after")(3.0)); +} + +TEST_F(FailedInputInstantiationTest, MemberOfClassTemplate) { + cantFail(Interp->ParseAndExecute( + "template <class T> struct Box { T v; T twice() { return v + v; } };")); + ExpectParseFails("extern \"C\" int box_fail(Box<int> *b) {" + " return b->twice() + no_such_name; }"); + cantFail(Interp->ParseAndExecute("extern \"C\" int box_after(int v) { " + "Box<int> b{v}; return b.twice(); }")); + EXPECT_EQ(8, Lookup<int(int)>("box_after")(4)); +} + +TEST_F(FailedInputInstantiationTest, StaticDataMemberOfClassTemplate) { + cantFail(Interp->ParseAndExecute( + "template <class T> struct Holder { static T value; };" + "template <class T> T Holder<T>::value = T(7);")); + ExpectParseFails("extern \"C\" int holder_fail() {" + " return Holder<int>::value + no_such_name; }"); + cantFail(Interp->ParseAndExecute( + "extern \"C\" int holder_after() { return Holder<int>::value; }")); + EXPECT_EQ(7, Lookup<int()>("holder_after")()); +} + +TEST_F(FailedInputInstantiationTest, VariableTemplate) { + cantFail(Interp->ParseAndExecute("template <class T> T five = T(5);")); + ExpectParseFails( + "extern \"C\" int five_fail() { return five<int> + no_such_name; }"); + cantFail(Interp->ParseAndExecute( + "extern \"C\" int five_after() { return five<int>; }")); + EXPECT_EQ(5, Lookup<int()>("five_after")()); +} + +TEST_F(FailedInputInstantiationTest, VTable) { + cantFail( + Interp->ParseAndExecute("struct V { virtual int f() { return 3; } };")); + ExpectParseFails( + "extern \"C\" int vtable_fail() { V v; return v.f() + no_such_name; }"); + cantFail(Interp->ParseAndExecute( + "extern \"C\" int vtable_after() { V v; return v.f(); }")); + EXPECT_EQ(3, Lookup<int()>("vtable_after")()); +} + +TEST_F(FailedInputInstantiationTest, UndoThenReuseTemplate) { + cantFail(Interp->ParseAndExecute( + "template <class T> T thrice(T x) { return x + x + x; }")); + cantFail(Interp->ParseAndExecute( + "extern \"C\" int thrice_a(int x) { return thrice(x); }")); + ExpectParseFails("extern \"C\" int thrice_fail(int x) { return thrice(x) + " + "no_such_name; }"); + // The failed input is not a PTU, so this undoes thrice_a. + cantFail(Interp->Undo()); + ExpectParseFails("extern \"C\" int thrice_b(int x) { return thrice_a(x); }"); + cantFail(Interp->ParseAndExecute( + "extern \"C\" int thrice_a(int x) { return thrice(x) + 1; }")); + EXPECT_EQ(7, Lookup<int(int)>("thrice_a")(2)); +} + +// The instantiations below bind a dependent call to a function of the failed +// input. The failed input withdraws the function, so the instantiation must +// go too. A later use then finds nothing, or a new overload that the stale +// instantiation would not call: it takes its argument by reference. +using ::testing::HasSubstr; + +TEST_F(FailedInputInstantiationTest, PoisonedFunctionTemplate) { + // The unevaluated use declares readv<S> before the failed input. + cantFail(Interp->ParseAndExecute( + "template <class T> int readv(T t) { return value(t); } struct S {};" + "using R = decltype(readv(S{}));")); + ExpectParseFails("int value(S) { return no_such_name; }" + "extern \"C\" int bad() { return readv(S{}); }"); + EXPECT_THAT(ParseFails("extern \"C\" int still_bad() { return readv(S{}); }"), + HasSubstr("undeclared identifier 'value'")); + cantFail(Interp->ParseAndExecute( + "int value(const S &) { return 7; }" + "extern \"C\" int good() { return readv(S{}); }")); + EXPECT_EQ(7, Lookup<int()>("good")()); +} + +// A constexpr function is instantiated at once, not at the end of the input. +TEST_F(FailedInputInstantiationTest, PoisonedConstexprFunctionTemplate) { + cantFail(Interp->ParseAndExecute( + "template <class T> constexpr int readk(T t) { return kval(t) + 1; }" + "struct S {};")); + ExpectParseFails("constexpr int kval(S) { return no_such_name; }" + "constexpr int bad = readk(S{});"); + EXPECT_THAT(ParseFails("extern \"C\" int still_bad() { return readk(S{}); }"), + HasSubstr("undeclared identifier 'kval'")); + cantFail(Interp->ParseAndExecute( + "constexpr int kval(const S &) { return 4; }" + "extern \"C\" int good() { return readk(S{}); }")); + EXPECT_EQ(5, Lookup<int()>("good")()); +} + +TEST_F(FailedInputInstantiationTest, PoisonedDeducedReturnType) { + cantFail(Interp->ParseAndExecute("template <class T> auto deduced(T t) { " + "return value(t); } struct S {};")); + ExpectParseFails("int value(S) { return no_such_name; }" + "extern \"C\" int bad() { return deduced(S{}); }"); + // A stale 'int' return type would truncate the result. + cantFail(Interp->ParseAndExecute( + "double value(const S &) { return 2.5; }" + "extern \"C\" double good() { return deduced(S{}); }")); + EXPECT_EQ(2.5, Lookup<double()>("good")()); +} + +TEST_F(FailedInputInstantiationTest, PoisonedMemberFunction) { + cantFail(Interp->ParseAndExecute( + "template <class T> struct Box { int get() { return init(T{}); } };" + "struct S {};")); + ExpectParseFails("int init(S) { return no_such_name; }" + "extern \"C\" int bad() { return Box<S>{}.get(); }"); + EXPECT_THAT( + ParseFails("extern \"C\" int still_bad() { return Box<S>{}.get(); }"), + HasSubstr("undeclared identifier 'init'")); + cantFail(Interp->ParseAndExecute( + "int init(const S &) { return 3; }" + "extern \"C\" int good() { return Box<S>{}.get(); }")); + EXPECT_EQ(3, Lookup<int()>("good")()); +} + +// The first use of a vtable instantiates all virtual functions of the class. +TEST_F(FailedInputInstantiationTest, PoisonedVirtualFunction) { + cantFail(Interp->ParseAndExecute( + "template <class T> struct VB { virtual int f() { return init(T{}); } };" + "struct S {};")); + ExpectParseFails("int init(S) { return no_such_name; }" + "extern \"C\" int bad() { VB<S> v; return v.f(); }"); + EXPECT_THAT( + ParseFails("extern \"C\" int still_bad() { VB<S> v; return v.f(); }"), + HasSubstr("undeclared identifier 'init'")); + cantFail(Interp->ParseAndExecute( + "int init(const S &) { return 4; }" + "extern \"C\" int good() { VB<S> v; return v.f(); }")); + EXPECT_EQ(4, Lookup<int()>("good")()); +} + +TEST_F(FailedInputInstantiationTest, PoisonedStaticDataMember) { + cantFail(Interp->ParseAndExecute( + "template <class T> struct Holder { static int value; };" + "template <class T> int Holder<T>::value = init(T{}); struct S {};")); + ExpectParseFails("int init(S) { return no_such_name; }" + "extern \"C\" int bad() { return Holder<S>::value; }"); + EXPECT_THAT( + ParseFails("extern \"C\" int still_bad() { return Holder<S>::value; }"), + HasSubstr("undeclared identifier 'init'")); + cantFail(Interp->ParseAndExecute( + "int init(const S &) { return 9; }" + "extern \"C\" int good() { return Holder<S>::value; }")); + EXPECT_EQ(9, Lookup<int()>("good")()); +} + +TEST_F(FailedInputInstantiationTest, PoisonedVariableTemplate) { + cantFail(Interp->ParseAndExecute( + "template <class T> int vt = init(T{}); struct S {};")); + ExpectParseFails("int init(S) { return no_such_name; }" + "extern \"C\" int bad() { return vt<S>; }"); + EXPECT_THAT(ParseFails("extern \"C\" int still_bad() { return vt<S>; }"), + HasSubstr("undeclared identifier 'init'")); + cantFail( + Interp->ParseAndExecute("int init(const S &) { return 8; }" + "extern \"C\" int good() { return vt<S>; }")); + EXPECT_EQ(8, Lookup<int()>("good")()); +} + +// A type of the failed input as template argument. The new type is a new +// argument, so a later input gets a new specialization. The old one reached +// CodeGen before the error, with the same mangled name. +TEST_F(FailedInputInstantiationTest, TypeOfFailedInputAsArgument) { + cantFail(Interp->ParseAndExecute( + "template <class T> constexpr int tag(T) { return T::id; }")); + ExpectParseFails("struct Tg { static constexpr int id = 1; };" + "constexpr int old_tag = tag(Tg{}); int e = no_such_name;"); + cantFail( + Interp->ParseAndExecute("struct Tg { static constexpr int id = 2; };" + "extern \"C\" int good() { return tag(Tg{}); }")); + EXPECT_EQ(2, Lookup<int()>("good")()); +} + +// A request without a definition. The template gets its definition later, so +// the next use must request the instantiation again. +TEST_F(FailedInputInstantiationTest, DeclaredThenDefinedTemplate) { + cantFail(Interp->ParseAndExecute("template <class T> T later(T);")); + ExpectParseFails( + "extern \"C\" int bad() { return later(1) + no_such_name; }"); + cantFail(Interp->ParseAndExecute( + "template <class T> T later(T x) { return x + 1; }" + "extern \"C\" int good() { return later(1); }")); + EXPECT_EQ(2, Lookup<int()>("good")()); +} + TEST_F(InterpreterTest, Value) { std::vector<const char *> Args = {"-fno-sized-deallocation"}; std::unique_ptr<Interpreter> Interp = createInterpreter(Args); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
