https://github.com/conrade-ctc created 
https://github.com/llvm/llvm-project/pull/226253

When an input fails, Sema still performs the implicit instantiations that the 
input asked for, and marks them done. The consumers then drop them: 
`InProcessPrintingASTConsumer::HandleTopLevelDecl`, and CodeGen's 
`HandleTopLevelDecl` and `HandleCXXStaticMemberVarInstantiation`, return early 
after any error. Sema does not instantiate them again, so a later input gets 
only a declaration:

```
template <class T> T twice(T x) { return x + x; }
extern "C" double fail(double *p) { return twice(*p) + no_such_name; } // error
extern "C" double ok(double x) { return twice(x); }
// JIT session error: Symbols not found: [ _Z5twiceIdET_S0_ ]
```

In incremental mode, this PR lets a valid implicit instantiation reach CodeGen 
after an error. CodeGen only records it as a deferred decl, so no IR is emitted 
until a later input uses it. The failed input's own declarations still stay 
out. I think CodeGen is the right place for this, because a normal compile 
drops the module after an error.

Tests: `FailedInputInstantiationTest.*` (function templates and static data 
members fail without the fix; the other cases are regression tests), undo tests 
around a failed input, and a lit test.

Not in this PR: a declaration emitted before the error in the same input still 
reaches the module. A later redefinition then reports `definition with same 
mangled name`. See #226252.

🤖 Done with the help of [Claude Code](https://claude.com/claude-code) (Opus 
5.5, human in the loop)


>From 9ff912d4456930d9a364d7b5045ecdd723f81fe5 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] Keep implicit instantiations of a failed input

When an input fails, Sema still performs the implicit instantiations
that the input asked for, and marks them done. The consumers drop them:
the value-printing consumer and CodeGen return early after any error.
Sema does not instantiate them again, so a later input that uses them
gets only a declaration, and the JIT reports "Symbols not found".

In incremental mode, let a valid implicit instantiation reach CodeGen
after an error. CodeGen only records it as a deferred decl, so no IR is
emitted until a later input uses it. The failed input's own
declarations still stay out. A normal compile drops the module after an
error, so it does not change.

Add tests for function templates and static data members, which failed
before, and for class template members, variable templates and vtables,
which already worked. Add undo tests around a failed input.

Co-developed-with-the-help-of: Claude Code (Opus 5.5, human in the loop)
---
 clang/lib/CodeGen/ModuleBuilder.cpp           |  20 ++-
 clang/lib/Interpreter/IncrementalAction.cpp   |  14 +-
 .../failed-input-keeps-instantiations.cpp     |  24 +++
 .../unittests/Interpreter/InterpreterTest.cpp | 137 ++++++++++++++++++
 4 files changed, 191 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-instantiations.cpp

diff --git a/clang/lib/CodeGen/ModuleBuilder.cpp 
b/clang/lib/CodeGen/ModuleBuilder.cpp
index 0b00362487d2a..279a6b3ad867f 100644
--- a/clang/lib/CodeGen/ModuleBuilder.cpp
+++ b/clang/lib/CodeGen/ModuleBuilder.cpp
@@ -178,8 +178,22 @@ namespace {
         Builder->AppendLinkerOptions(Opt);
     }
 
+    /// In incremental mode an error drops the failed input, not the module.
+    /// Sema instantiates a specialization only once, so a valid implicit
+    /// instantiation must reach CodeGen even after an error. If not, a later
+    /// input that uses it gets only a declaration.
+    bool isKeptAfterError(const Decl *D) const {
+      if (!Ctx->getLangOpts().IncrementalExtensions || D->isInvalidDecl())
+        return false;
+      if (const auto *FD = dyn_cast<FunctionDecl>(D))
+        return FD->getTemplateSpecializationKind() == 
TSK_ImplicitInstantiation;
+      if (const auto *VD = dyn_cast<VarDecl>(D))
+        return VD->getTemplateSpecializationKind() == 
TSK_ImplicitInstantiation;
+      return false;
+    }
+
     void HandleCXXStaticMemberVarInstantiation(VarDecl *VD) override {
-      if (Diags.hasErrorOccurred())
+      if (Diags.hasErrorOccurred() && !isKeptAfterError(VD))
         return;
 
       Builder->HandleCXXStaticMemberVarInstantiation(VD);
@@ -191,7 +205,9 @@ namespace {
         return true; // We can't CodeGen more but pass to other consumers.
 
       // FIXME: Why not return false and abort parsing?
-      if (Diags.hasUnrecoverableErrorOccurred())
+      if (Diags.hasUnrecoverableErrorOccurred() &&
+          !llvm::all_of(DG,
+                        [this](const Decl *D) { return isKeptAfterError(D); }))
         return true;
 
       HandlingTopLevelDeclRAII HandlingDecl(*this);
diff --git a/clang/lib/Interpreter/IncrementalAction.cpp 
b/clang/lib/Interpreter/IncrementalAction.cpp
index 85f00c36dd5aa..46d1c9862f1ce 100644
--- a/clang/lib/Interpreter/IncrementalAction.cpp
+++ b/clang/lib/Interpreter/IncrementalAction.cpp
@@ -132,14 +132,24 @@ 
InProcessPrintingASTConsumer::InProcessPrintingASTConsumer(
     std::unique_ptr<ASTConsumer> C, Interpreter &I)
     : MultiplexConsumer(std::move(C)), Interp(I) {}
 
+static bool isImplicitInstantiation(const Decl *D) {
+  const auto *FD = dyn_cast<FunctionDecl>(D);
+  return FD && FD->getTemplateSpecializationKind() == 
TSK_ImplicitInstantiation;
+}
+
 bool InProcessPrintingASTConsumer::HandleTopLevelDecl(DeclGroupRef DGR) {
   if (DGR.isNull())
     return true;
 
   CompilerInstance *CI = Interp.getCompilerInstance();
   DiagnosticsEngine &Diags = CI->getDiagnostics();
-  if (Diags.hasErrorOccurred())
-    return true;
+  if (Diags.hasErrorOccurred()) {
+    // Keep the failed input's own declarations out of CodeGen. Sema does not
+    // repeat the implicit instantiations it made, so pass those on now.
+    if (!llvm::all_of(DGR, isImplicitInstantiation))
+      return true;
+    return MultiplexConsumer::HandleTopLevelDecl(DGR);
+  }
 
   for (Decl *D : DGR)
     if (auto *TLSD = llvm::dyn_cast<TopLevelStmtDecl>(D))
diff --git a/clang/test/Interpreter/failed-input-keeps-instantiations.cpp 
b/clang/test/Interpreter/failed-input-keeps-instantiations.cpp
new file mode 100644
index 0000000000000..7cb3cd3f3a92e
--- /dev/null
+++ b/clang/test/Interpreter/failed-input-keeps-instantiations.cpp
@@ -0,0 +1,24 @@
+// 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. Sema
+// does not repeat them, so a later input must still get their definitions.
+
+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
+
+%quit
diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp 
b/clang/unittests/Interpreter/InterpreterTest.cpp
index 3becd00c12820..4bf2422d1fe07 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,92 @@ TEST_F(InterpreterTest, InstantiateTemplate) {
   EXPECT_EQ(42, fn(NewA.getPtr()));
 }
 
+// A failed input must keep the implicit instantiations it made. A later input
+// that uses them must get their definitions.
+struct FailedInputInstantiationTest : InterpreterTest {
+  std::unique_ptr<Interpreter> Interp;
+
+  void SetUp() override {
+    InterpreterTest::SetUp();
+    // FIXME: We cannot yet handle delayed template parsing.
+    Interp = createInterpreter({"-fno-delayed-template-parsing"});
+  }
+
+  void ExpectParseFails(llvm::StringRef Code) {
+    llvm::Error Err = Interp->Parse(Code).takeError();
+    EXPECT_EQ("Parsing failed.", llvm::toString(std::move(Err)));
+  }
+
+  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));
+}
+
 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

Reply via email to