[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-23 Thread via cfe-commits

llvmbot wrote:

/pull-request llvm/llvm-project#225686

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-23 Thread Vipul Cariappa via cfe-commits

Vipul-Cariappa wrote:

/cherry-pick 37e2844bc4681c69f61cbb55376a29ae0445c18c

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-23 Thread Vassil Vassilev via cfe-commits

https://github.com/vgvassilev closed 
https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-23 Thread Vassil Vassilev via cfe-commits

https://github.com/vgvassilev updated 
https://github.com/llvm/llvm-project/pull/218149

>From bbf94c5e3a349c4eb8950484dfbf4a6d87d1b9b8 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Wed, 2 Sep 2026 11:08:40 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 131 +-
 clang/lib/Interpreter/IncrementalParser.h |  21 +++
 .../failed-input-keeps-redecls.cpp|  69 +
 4 files changed, 219 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index 6941f0179647a6..2e6f689af6d970 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 12beb542572d73..e2cdeebbe8de7a 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,13 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclFriend.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclOpenACC.h"
+#include "clang/AST/DeclOpenMP.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -191,23 +197,140 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+template 
+void IncrementalParser::unlinkRedeclChain(Redeclarable *DBase,
+  NamedDecl *PrevND) {
+  auto *Latest = static_cast(DBase);
+  auto *Survivor = cast(PrevND);
+
+  // Rebuild First -> ... -> Survivor -> ... -> Latest as
+  // First -> ... -> Survivor.
+  Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+  // The chain is circular: a withdrawn declaration still linked into it can
+  // never walk back around to itself, so redecls() on one would not terminate.
+  // Give each withdrawn declaration a chain of its own.
+  ASTContext &C = S.getASTContext();
+  for (DeclT *Dead = Latest; Dead != Survivor;) {
+DeclT *Next = Dead->getPreviousDecl();
+Dead->First = Dead;
+Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+Dead = Next;
+  }
+}
+
+template 
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // A class keeps its definition outside the redeclaration chain.
+  // If the definition was provided in the DiscardedTU, drop it.
+  auto *RD = dyn_cast(Prev);
+  if (!RD)
+return;
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  dropContainingMembers(static_cast(D));
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(
+Redeclarable *D, NamedDecl *Prev,
+TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // The pattern a template declares keeps a redeclaration chain of its own,
+  // running alongside the template's.
+  auto *RTD = static_cast(D);
+  auto *PrevRTD = cast(Prev);
+  withdrawRedecl(RTD->getTemplatedDecl(), 

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-23 Thread Vassil Vassilev via cfe-commits

https://github.com/vgvassilev milestoned 
https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From a42dee959f3ab37e335157945cb8a7769aa53498 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Wed, 2 Sep 2026 11:08:40 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 131 +-
 clang/lib/Interpreter/IncrementalParser.h |  21 +++
 .../failed-input-keeps-redecls.cpp|  69 +
 4 files changed, 219 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index ff2223070dc157..3673738408e554 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 12beb542572d73..e2cdeebbe8de7a 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,13 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclFriend.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclOpenACC.h"
+#include "clang/AST/DeclOpenMP.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -191,23 +197,140 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+template 
+void IncrementalParser::unlinkRedeclChain(Redeclarable *DBase,
+  NamedDecl *PrevND) {
+  auto *Latest = static_cast(DBase);
+  auto *Survivor = cast(PrevND);
+
+  // Rebuild First -> ... -> Survivor -> ... -> Latest as
+  // First -> ... -> Survivor.
+  Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+  // The chain is circular: a withdrawn declaration still linked into it can
+  // never walk back around to itself, so redecls() on one would not terminate.
+  // Give each withdrawn declaration a chain of its own.
+  ASTContext &C = S.getASTContext();
+  for (DeclT *Dead = Latest; Dead != Survivor;) {
+DeclT *Next = Dead->getPreviousDecl();
+Dead->First = Dead;
+Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+Dead = Next;
+  }
+}
+
+template 
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // A class keeps its definition outside the redeclaration chain.
+  // If the definition was provided in the DiscardedTU, drop it.
+  auto *RD = dyn_cast(Prev);
+  if (!RD)
+return;
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  dropContainingMembers(static_cast(D));
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(
+Redeclarable *D, NamedDecl *Prev,
+TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // The pattern a template declares keeps a redeclaration chain of its own,
+  // running alongside the template's.
+  auto *RTD = static_cast(D);
+  auto *PrevRTD = cast(Prev);
+  

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From 6c457a43df5d235a0271f17af4c87423d8520a09 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Wed, 2 Sep 2026 11:08:40 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 131 +-
 clang/lib/Interpreter/IncrementalParser.h |  21 +++
 .../failed-input-keeps-redecls.cpp|  70 ++
 4 files changed, 220 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index ff2223070dc15..3673738408e55 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 12beb542572d7..e2cdeebbe8de7 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,13 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclFriend.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclOpenACC.h"
+#include "clang/AST/DeclOpenMP.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -191,23 +197,140 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+template 
+void IncrementalParser::unlinkRedeclChain(Redeclarable *DBase,
+  NamedDecl *PrevND) {
+  auto *Latest = static_cast(DBase);
+  auto *Survivor = cast(PrevND);
+
+  // Rebuild First -> ... -> Survivor -> ... -> Latest as
+  // First -> ... -> Survivor.
+  Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+  // The chain is circular: a withdrawn declaration still linked into it can
+  // never walk back around to itself, so redecls() on one would not terminate.
+  // Give each withdrawn declaration a chain of its own.
+  ASTContext &C = S.getASTContext();
+  for (DeclT *Dead = Latest; Dead != Survivor;) {
+DeclT *Next = Dead->getPreviousDecl();
+Dead->First = Dead;
+Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+Dead = Next;
+  }
+}
+
+template 
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // A class keeps its definition outside the redeclaration chain.
+  // If the definition was provided in the DiscardedTU, drop it.
+  auto *RD = dyn_cast(Prev);
+  if (!RD)
+return;
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  dropContainingMembers(static_cast(D));
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(
+Redeclarable *D, NamedDecl *Prev,
+TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // The pattern a template declares keeps a redeclaration chain of its own,
+  // running alongside the template's.
+  auto *RTD = static_cast(D);
+  auto *PrevRTD = cast(Prev);
+  withdrawRedecl(RTD->getTemplatedDecl(), 

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From 6c457a43df5d235a0271f17af4c87423d8520a09 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Wed, 2 Sep 2026 11:08:40 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 131 +-
 clang/lib/Interpreter/IncrementalParser.h |  21 +++
 .../failed-input-keeps-redecls.cpp|  70 ++
 4 files changed, 220 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index ff2223070dc157..3673738408e554 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 12beb542572d73..e2cdeebbe8de7a 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,13 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclFriend.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclOpenACC.h"
+#include "clang/AST/DeclOpenMP.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -191,23 +197,140 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+template 
+void IncrementalParser::unlinkRedeclChain(Redeclarable *DBase,
+  NamedDecl *PrevND) {
+  auto *Latest = static_cast(DBase);
+  auto *Survivor = cast(PrevND);
+
+  // Rebuild First -> ... -> Survivor -> ... -> Latest as
+  // First -> ... -> Survivor.
+  Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+  // The chain is circular: a withdrawn declaration still linked into it can
+  // never walk back around to itself, so redecls() on one would not terminate.
+  // Give each withdrawn declaration a chain of its own.
+  ASTContext &C = S.getASTContext();
+  for (DeclT *Dead = Latest; Dead != Survivor;) {
+DeclT *Next = Dead->getPreviousDecl();
+Dead->First = Dead;
+Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+Dead = Next;
+  }
+}
+
+template 
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // A class keeps its definition outside the redeclaration chain.
+  // If the definition was provided in the DiscardedTU, drop it.
+  auto *RD = dyn_cast(Prev);
+  if (!RD)
+return;
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  dropContainingMembers(static_cast(D));
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(
+Redeclarable *D, NamedDecl *Prev,
+TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // The pattern a template declares keeps a redeclaration chain of its own,
+  // running alongside the template's.
+  auto *RTD = static_cast(D);
+  auto *PrevRTD = cast(Prev);
+  

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vassil Vassilev via cfe-commits


@@ -191,30 +195,150 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Removes decls introduced in the discarding PTU and restores the
+/// redeclaration chain to previous state.
+class ASTDeclUnmerger : public DeclVisitor {
+  ASTContext &Ctx;
+  TranslationUnitDecl *DiscardedTU;
+
+public:
+  template  void withdraw(Redeclarable *DBase) {
+if (NamedDecl *Prev = findSurvivor(static_cast(DBase)))
+  unlinkRedeclChain(Ctx, DBase, Prev);
+  }
+
+  /// The newest declaration of whatever D redeclares that still lives outside
+  /// the DiscardedTU, or null if DiscardedTU introduced the name.
+  NamedDecl *findSurvivor(NamedDecl *D) const {
+for (Decl *Prev = D->getPreviousDecl(); Prev;
+ Prev = Prev->getPreviousDecl())
+  if (Prev->getTranslationUnitDecl() != DiscardedTU)
+return dyn_cast(Prev);
+return nullptr;
+  }
+
+  template 
+  void unlinkRedeclChain(ASTContext &C, Redeclarable *DBase,
+ NamedDecl *PrevND) {
+auto *Latest = static_cast(DBase);
+auto *Survivor = cast(PrevND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+// The chain is circular: a withdrawn declaration still linked into it can
+// never walk back around to itself, so redecls() on one would not
+// terminate. Give each withdrawn declaration a chain of its own.
+for (DeclT *Dead = Latest; Dead != Survivor;) {
+  DeclT *Next = Dead->getPreviousDecl();
+  Dead->First = Dead;
+  Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+  Dead = Next;
+}
+  }
+
+  ASTDeclUnmerger(ASTContext &Ctx, TranslationUnitDecl *DiscardedTU)
+  : Ctx(Ctx), DiscardedTU(DiscardedTU) {}
+
+  // Kinds that are not redeclarable have no chain to repair.
+  void VisitDecl(Decl *) {}
+
+  void VisitFunctionDecl(FunctionDecl *D) { withdraw(D); }
+  void VisitNamespaceAliasDecl(NamespaceAliasDecl *D) { withdraw(D); }
+  void VisitTypedefNameDecl(TypedefNameDecl *D) { withdraw(D); }
+  void VisitUsingShadowDecl(UsingShadowDecl *D) { withdraw(D); }
+  void VisitVarDecl(VarDecl *D) { withdraw(D); }
+
+  void VisitTagDecl(TagDecl *D) {
+NamedDecl *Prev = findSurvivor(D);
+if (!Prev)
+  return;
+unlinkRedeclChain(Ctx, D, Prev);
+
+// A class definition kept in DefinitionData outside the redeclaration 
chain
+auto *RD = dyn_cast(Prev);
+if (!RD)
+  return;
+if (CXXRecordDecl *Def = RD->getDefinition();
+Def && Def->getTranslationUnitDecl() == DiscardedTU)
+  for (auto *R : RD->redecls())
+cast(R)->DefinitionData = nullptr;
+  }
+
+  void VisitRedeclarableTemplateDecl(RedeclarableTemplateDecl *D) {
+withdraw(D);
+Visit(D->getTemplatedDecl());
+  }
+
+  void VisitNamespaceDecl(NamespaceDecl *D) {
+// Handle cases of nested redeclarations like:
+// PTU1: namespace outer { namespace ns { class Foo; } }
+// PTU2: namespace outer { namespace ns { class Foo { ... }; error; } }
+// Foo's redeclaration needs to be restored
+llvm::SmallVector Survivors;
+for (Decl *M : D->decls()) {
+  if (auto *ND = dyn_cast(M))
+if (NamedDecl *Prev = findSurvivor(ND))
+  Survivors.push_back(Prev);
+  Visit(M);
+  D->removeDecl(M);
+}
+
+// A re-opened namespace makes its members visible in the namespace's
+// primary context, which outlives the discarded unit
+DeclContext *Primary = D->getPrimaryContext();
+for (NamedDecl *Prev : Survivors)
+  Primary->makeDeclVisibleInContext(Prev);
+
+withdraw(D);
+  }
+};
+
 void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
+  ASTDeclUnmerger Unmerger(S.getASTContext(), MostRecentTU);
+
   if (StoredDeclsMap *Map = MostRecentTU->getPrimaryContext()->getLookupPtr()) 
{

vgvassilev wrote:

All this logic here needs to move to in the visitor where we iterate over the 
content of `MostRecentTU` and decide how to handle each declaration depending 
on its kind. That's how cling currently does it.

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From 6c457a43df5d235a0271f17af4c87423d8520a09 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Wed, 2 Sep 2026 11:08:40 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 131 +-
 clang/lib/Interpreter/IncrementalParser.h |  21 +++
 .../failed-input-keeps-redecls.cpp|  70 ++
 4 files changed, 220 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index ff2223070dc15..3673738408e55 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 12beb542572d7..e2cdeebbe8de7 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,13 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclFriend.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclOpenACC.h"
+#include "clang/AST/DeclOpenMP.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -191,23 +197,140 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+template 
+void IncrementalParser::unlinkRedeclChain(Redeclarable *DBase,
+  NamedDecl *PrevND) {
+  auto *Latest = static_cast(DBase);
+  auto *Survivor = cast(PrevND);
+
+  // Rebuild First -> ... -> Survivor -> ... -> Latest as
+  // First -> ... -> Survivor.
+  Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+  // The chain is circular: a withdrawn declaration still linked into it can
+  // never walk back around to itself, so redecls() on one would not terminate.
+  // Give each withdrawn declaration a chain of its own.
+  ASTContext &C = S.getASTContext();
+  for (DeclT *Dead = Latest; Dead != Survivor;) {
+DeclT *Next = Dead->getPreviousDecl();
+Dead->First = Dead;
+Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+Dead = Next;
+  }
+}
+
+template 
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // A class keeps its definition outside the redeclaration chain.
+  // If the definition was provided in the DiscardedTU, drop it.
+  auto *RD = dyn_cast(Prev);
+  if (!RD)
+return;
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  dropContainingMembers(static_cast(D));
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(
+Redeclarable *D, NamedDecl *Prev,
+TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // The pattern a template declares keeps a redeclaration chain of its own,
+  // running alongside the template's.
+  auto *RTD = static_cast(D);
+  auto *PrevRTD = cast(Prev);
+  withdrawRedecl(RTD->getTemplatedDecl(), 

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vipul Cariappa via cfe-commits


@@ -25,7 +25,11 @@ class ASTConsumer;
 class CompilerInstance;
 class Parser;
 class Sema;
+class ASTContext;
+class CXXRecordDecl;
+class NamedDecl;
 class TranslationUnitDecl;
+template  class Redeclarable;

Vipul-Cariappa wrote:

Remnants from the previous approach. I have removed it now. Sorry about it.

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vassil Vassilev via cfe-commits


@@ -25,7 +25,11 @@ class ASTConsumer;
 class CompilerInstance;
 class Parser;
 class Sema;
+class ASTContext;
+class CXXRecordDecl;
+class NamedDecl;
 class TranslationUnitDecl;
+template  class Redeclarable;

vgvassilev wrote:

Why do we need these new forward declarations in the header file?

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From 6c457a43df5d235a0271f17af4c87423d8520a09 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Wed, 2 Sep 2026 11:08:40 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 131 +-
 clang/lib/Interpreter/IncrementalParser.h |  21 +++
 .../failed-input-keeps-redecls.cpp|  70 ++
 4 files changed, 220 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index ff2223070dc15..3673738408e55 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 12beb542572d7..e2cdeebbe8de7 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,13 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclFriend.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/DeclOpenACC.h"
+#include "clang/AST/DeclOpenMP.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -191,23 +197,140 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+template 
+void IncrementalParser::unlinkRedeclChain(Redeclarable *DBase,
+  NamedDecl *PrevND) {
+  auto *Latest = static_cast(DBase);
+  auto *Survivor = cast(PrevND);
+
+  // Rebuild First -> ... -> Survivor -> ... -> Latest as
+  // First -> ... -> Survivor.
+  Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+  // The chain is circular: a withdrawn declaration still linked into it can
+  // never walk back around to itself, so redecls() on one would not terminate.
+  // Give each withdrawn declaration a chain of its own.
+  ASTContext &C = S.getASTContext();
+  for (DeclT *Dead = Latest; Dead != Survivor;) {
+DeclT *Next = Dead->getPreviousDecl();
+Dead->First = Dead;
+Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+Dead = Next;
+  }
+}
+
+template 
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // A class keeps its definition outside the redeclaration chain.
+  // If the definition was provided in the DiscardedTU, drop it.
+  auto *RD = dyn_cast(Prev);
+  if (!RD)
+return;
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(Redeclarable *D,
+   NamedDecl *Prev,
+   TranslationUnitDecl *) {
+  dropContainingMembers(static_cast(D));
+  unlinkRedeclChain(D, Prev);
+}
+
+template <>
+void IncrementalParser::withdrawRedeclImpl(
+Redeclarable *D, NamedDecl *Prev,
+TranslationUnitDecl *DiscardedTU) {
+  unlinkRedeclChain(D, Prev);
+
+  // The pattern a template declares keeps a redeclaration chain of its own,
+  // running alongside the template's.
+  auto *RTD = static_cast(D);
+  auto *PrevRTD = cast(Prev);
+  withdrawRedecl(RTD->getTemplatedDecl(), 

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-06 Thread via cfe-commits


@@ -191,30 +197,149 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// Returns newest declaration of whatever D redeclares that still lives 
outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it.
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);

SahilPatidar wrote:

I think there may be a gap in `withdrawRedeclImpl` when a 
discarded reopening contains a member that is itself a redeclaration.

For example:
```cpp
PTU1: namespace ns { class Foo; }
PTU2: namespace ns { class Foo { ... }; }
```
```cpp
PTU1: namespace outer { namespace ns { class Foo; } }
PTU2: namespace outer { namespace ns { class Foo { ... }; } }
```
When PTU2 is withdrawn:
```cpp
dropContainingMembers(ND);
unlinkRedeclChain(D, Prev);
```
This seems to correctly repair the ns redeclaration chain, but I think Foo 
itself still needs some recovery:

1. Foo(2)’s RedeclLink does not seem to be repaired, so the chain still point 
to the discarded Foo(2) instead of Foo(1).
2. `removeDecl(Foo(2))` removes Foo(2) from the "Foo" lookup entry, but does 
not restore Foo(1). 

This looks similar to what `DeclContextRepairer::repairLookupEntry` was trying 
to handle. 

We have several cases like this where fixing each part separately will keep 
adding more special cases. This is one of the main reasons I’m working on a 
centralized error-recovery model, separate from `IncrementalParser`, to handle 
these rollback cases in one place across redecl chains, lookup entries, and 
other related AST/Decl state.

For the current issue, I think we should move the redeclaration-related 
recovery logic into a separate component, independent of IncrementalParser.

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-09-02 Thread via cfe-commits


@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;

SahilPatidar wrote:

This is the DC repair I was developing as part of the new error-recovery model: 
https://gist.github.com/SahilPatidar/cb8dcf89a7fcb2a5834c47ac7bea8e74

It’s still under construction, and I hadn’t checked whether it was working 
correctly yet. Given the current design changes, I may need to adjust it, but 
this is what I have at the moment.

For the current issue, I think we can keep this implementation as it is for 
now. The broader error-recovery work will eventually cover this.


https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-25 Thread Vassil Vassilev via cfe-commits


@@ -120,6 +120,8 @@ std::unique_ptr 
IncrementalAction::GenModule() {
   return nullptr;
 }
 
+void IncrementalAction::discardModule() { GenModule(); }

vgvassilev wrote:

I also think this is suboptimal -- we are forcing a module to be removed but 
that's only the last module. What happens if we are undoing a past module? Also 
we need to free the machine code produced by the jit...

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-25 Thread Vassil Vassilev via cfe-commits


@@ -191,39 +199,136 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+bool IncrementalParser::withdrawRedecl(NamedDecl *D, NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  ASTContext &C = S.getASTContext();
+
+  auto Unlink = [&C](auto *Latest, NamedDecl *SurvivorND) {
+using T = std::remove_pointer_t;
+auto *Survivor = cast(SurvivorND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+// The chain is circular: a withdrawn declaration still linked into it can
+// never walk back around to itself, so redecls() on one would not
+// terminate. Give each a chain of its own.
+for (T *Dead = Latest; Dead != Survivor;) {
+  T *Next = Dead->getPreviousDecl();
+  Dead->First = Dead;
+  Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+  Dead = Next;
+}
+  };
+
+  if (auto *TD = dyn_cast(D)) {
+Unlink(TD, Prev);
+// A class keeps its definition outside the redeclaration chain, in the
+// DefinitionData that startDefinition() hands to every redeclaration.
+// Unlinking leaves the survivors pointing at the discarded definition, so
+// drop it; a later definition allocates a fresh one for the whole chain.
+if (auto *RD = dyn_cast(Prev))
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+return true;
+  }
+  if (auto *FD = dyn_cast(D)) {
+Unlink(FD, Prev);
+return true;
+  }
+  if (auto *VD = dyn_cast(D)) {
+Unlink(VD, Prev);
+return true;
+  }
+  if (auto *ND = dyn_cast(D)) {
+dropContainingMembers(ND);
+Unlink(ND, Prev);
+return true;
+  }
+  if (auto *TND = dyn_cast(D)) {
+Unlink(TND, Prev);
+return true;

vgvassilev wrote:

I still think this should become a tiny visitor in this file modelling how 
things in the ASTReader are done.

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-24 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From a3386428ad025c57212b4c3c7fc0cf96372dc0bc Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Fri, 21 Aug 2026 23:56:37 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm/llvm-project#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalAction.cpp   |   2 +
 clang/lib/Interpreter/IncrementalAction.h |   4 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 118 +-
 clang/lib/Interpreter/IncrementalParser.h |   9 ++
 .../failed-input-keeps-redecls.cpp| 101 +++
 6 files changed, 232 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index a42884be71d68..11d3db5d99293 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalAction.cpp 
b/clang/lib/Interpreter/IncrementalAction.cpp
index d22031c8fa893..10ca3d3a753c5 100644
--- a/clang/lib/Interpreter/IncrementalAction.cpp
+++ b/clang/lib/Interpreter/IncrementalAction.cpp
@@ -120,6 +120,8 @@ std::unique_ptr 
IncrementalAction::GenModule() {
   return nullptr;
 }
 
+void IncrementalAction::discardModule() { GenModule(); }
+
 CodeGenerator *IncrementalAction::getCodeGen() const {
   FrontendAction *WrappedAct = getWrapped();
   if (!WrappedAct || !WrappedAct->hasIRSupport())
diff --git a/clang/lib/Interpreter/IncrementalAction.h 
b/clang/lib/Interpreter/IncrementalAction.h
index 725cdd0c27cf4..31547cd785967 100644
--- a/clang/lib/Interpreter/IncrementalAction.h
+++ b/clang/lib/Interpreter/IncrementalAction.h
@@ -74,6 +74,10 @@ class IncrementalAction : public WrapperFrontendAction {
 
   /// Generate an LLVM module for the most recent parsed input.
   std::unique_ptr GenModule();
+
+  /// Throw away what CodeGen emitted for an input that failed to parse and
+  /// start a fresh module.
+  void discardModule();
 };
 
 class InProcessPrintingASTConsumer final : public MultiplexConsumer {
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..a30c720873abb 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,9 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -87,6 +89,12 @@ IncrementalParser::ParseOrWrapTopLevelDecl() {
   if (Diags.hasErrorOccurred()) {
 CleanUpPTU(C.getTranslationUnitDecl());
 
+// Whatever got through before the error was already handed to CodeGen. It
+// belongs to no PTU and will never be executed, but it stays in the module
+// the next input emits into and collides with the definitions that input
+// provides. Drop it and start a fresh module.
+Act->discardModule();
+
 Diags.Reset(/*soft=*/true);
 Diags.getClient()->clear();
 return llvm::make_error("Parsing failed.",
@@ -191,30 +199,134 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+bool IncrementalParser::withdrawRedecl(NamedDecl *D, NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  ASTContext &C = S.getASTContext();
+
+  auto Unlink = [&C](auto *Latest, NamedDecl *SurvivorND) {
+using T = std::remove_pointer_t;
+auto *Survivor = cast(SurvivorND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-24 Thread Vipul Cariappa via cfe-commits


@@ -191,39 +199,136 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+bool IncrementalParser::withdrawRedecl(NamedDecl *D, NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  ASTContext &C = S.getASTContext();
+
+  auto Unlink = [&C](auto *Latest, NamedDecl *SurvivorND) {
+using T = std::remove_pointer_t;
+auto *Survivor = cast(SurvivorND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+// The chain is circular: a withdrawn declaration still linked into it can
+// never walk back around to itself, so redecls() on one would not
+// terminate. Give each a chain of its own.
+for (T *Dead = Latest; Dead != Survivor;) {
+  T *Next = Dead->getPreviousDecl();
+  Dead->First = Dead;
+  Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+  Dead = Next;
+}
+  };
+
+  if (auto *TD = dyn_cast(D)) {
+Unlink(TD, Prev);
+// A class keeps its definition outside the redeclaration chain, in the
+// DefinitionData that startDefinition() hands to every redeclaration.
+// Unlinking leaves the survivors pointing at the discarded definition, so
+// drop it; a later definition allocates a fresh one for the whole chain.
+if (auto *RD = dyn_cast(Prev))
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+return true;
+  }
+  if (auto *FD = dyn_cast(D)) {
+Unlink(FD, Prev);
+return true;
+  }
+  if (auto *VD = dyn_cast(D)) {
+Unlink(VD, Prev);
+return true;
+  }
+  if (auto *ND = dyn_cast(D)) {
+dropContainingMembers(ND);
+Unlink(ND, Prev);
+return true;
+  }
+  if (auto *TND = dyn_cast(D)) {
+Unlink(TND, Prev);
+return true;

Vipul-Cariappa wrote:

If this is regarding the duplication. I have tried fixing it.
Note that `dyn_cast(...)` is not possible; therefore, we 
need the multiple `dyn_clas(...)`s.

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-24 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From a3386428ad025c57212b4c3c7fc0cf96372dc0bc Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Fri, 21 Aug 2026 23:56:37 +0530
Subject: [PATCH 1/2] [clang-repl] Keep earlier declarations alive when an
 input fails

Fixes llvm/llvm-project#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalAction.cpp   |   2 +
 clang/lib/Interpreter/IncrementalAction.h |   4 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 118 +-
 clang/lib/Interpreter/IncrementalParser.h |   9 ++
 .../failed-input-keeps-redecls.cpp| 101 +++
 6 files changed, 232 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index a42884be71d68..11d3db5d99293 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalAction.cpp 
b/clang/lib/Interpreter/IncrementalAction.cpp
index d22031c8fa893..10ca3d3a753c5 100644
--- a/clang/lib/Interpreter/IncrementalAction.cpp
+++ b/clang/lib/Interpreter/IncrementalAction.cpp
@@ -120,6 +120,8 @@ std::unique_ptr 
IncrementalAction::GenModule() {
   return nullptr;
 }
 
+void IncrementalAction::discardModule() { GenModule(); }
+
 CodeGenerator *IncrementalAction::getCodeGen() const {
   FrontendAction *WrappedAct = getWrapped();
   if (!WrappedAct || !WrappedAct->hasIRSupport())
diff --git a/clang/lib/Interpreter/IncrementalAction.h 
b/clang/lib/Interpreter/IncrementalAction.h
index 725cdd0c27cf4..31547cd785967 100644
--- a/clang/lib/Interpreter/IncrementalAction.h
+++ b/clang/lib/Interpreter/IncrementalAction.h
@@ -74,6 +74,10 @@ class IncrementalAction : public WrapperFrontendAction {
 
   /// Generate an LLVM module for the most recent parsed input.
   std::unique_ptr GenModule();
+
+  /// Throw away what CodeGen emitted for an input that failed to parse and
+  /// start a fresh module.
+  void discardModule();
 };
 
 class InProcessPrintingASTConsumer final : public MultiplexConsumer {
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..a30c720873abb 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,9 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -87,6 +89,12 @@ IncrementalParser::ParseOrWrapTopLevelDecl() {
   if (Diags.hasErrorOccurred()) {
 CleanUpPTU(C.getTranslationUnitDecl());
 
+// Whatever got through before the error was already handed to CodeGen. It
+// belongs to no PTU and will never be executed, but it stays in the module
+// the next input emits into and collides with the definitions that input
+// provides. Drop it and start a fresh module.
+Act->discardModule();
+
 Diags.Reset(/*soft=*/true);
 Diags.getClient()->clear();
 return llvm::make_error("Parsing failed.",
@@ -191,30 +199,134 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+bool IncrementalParser::withdrawRedecl(NamedDecl *D, NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  ASTContext &C = S.getASTContext();
+
+  auto Unlink = [&C](auto *Latest, NamedDecl *SurvivorND) {
+using T = std::remove_pointer_t;
+auto *Survivor = cast(SurvivorND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-23 Thread Vassil Vassilev via cfe-commits


@@ -191,39 +199,136 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+bool IncrementalParser::withdrawRedecl(NamedDecl *D, NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  ASTContext &C = S.getASTContext();
+
+  auto Unlink = [&C](auto *Latest, NamedDecl *SurvivorND) {
+using T = std::remove_pointer_t;
+auto *Survivor = cast(SurvivorND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+Latest->getFirstDecl()->RedeclLink.setLatest(Survivor);
+
+// The chain is circular: a withdrawn declaration still linked into it can
+// never walk back around to itself, so redecls() on one would not
+// terminate. Give each a chain of its own.
+for (T *Dead = Latest; Dead != Survivor;) {
+  T *Next = Dead->getPreviousDecl();
+  Dead->First = Dead;
+  Dead->RedeclLink = Redeclarable::LatestDeclLink(C);
+  Dead = Next;
+}
+  };
+
+  if (auto *TD = dyn_cast(D)) {
+Unlink(TD, Prev);
+// A class keeps its definition outside the redeclaration chain, in the
+// DefinitionData that startDefinition() hands to every redeclaration.
+// Unlinking leaves the survivors pointing at the discarded definition, so
+// drop it; a later definition allocates a fresh one for the whole chain.
+if (auto *RD = dyn_cast(Prev))
+  if (CXXRecordDecl *Def = RD->getDefinition();
+  Def && Def->getTranslationUnitDecl() == DiscardedTU)
+for (auto *R : RD->redecls())
+  cast(R)->DefinitionData = nullptr;
+return true;
+  }
+  if (auto *FD = dyn_cast(D)) {
+Unlink(FD, Prev);
+return true;
+  }
+  if (auto *VD = dyn_cast(D)) {
+Unlink(VD, Prev);
+return true;
+  }
+  if (auto *ND = dyn_cast(D)) {
+dropContainingMembers(ND);
+Unlink(ND, Prev);
+return true;
+  }
+  if (auto *TND = dyn_cast(D)) {
+Unlink(TND, Prev);
+return true;

vgvassilev wrote:

Maybe look how `void ASTDeclMerger::mergeRedeclarableImpl` and `void 
ASTDeclReader::attachPreviousDeclImpl` work for redeclarable decls.

https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-23 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From a3386428ad025c57212b4c3c7fc0cf96372dc0bc Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Fri, 21 Aug 2026 23:56:37 +0530
Subject: [PATCH] [clang-repl] Keep earlier declarations alive when an input
 fails

Fixes llvm/llvm-project#201844.
---
 clang/include/clang/AST/DeclCXX.h |   1 +
 clang/lib/Interpreter/IncrementalAction.cpp   |   2 +
 clang/lib/Interpreter/IncrementalAction.h |   4 +
 clang/lib/Interpreter/IncrementalParser.cpp   | 118 +-
 clang/lib/Interpreter/IncrementalParser.h |   9 ++
 .../failed-input-keeps-redecls.cpp| 101 +++
 6 files changed, 232 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/include/clang/AST/DeclCXX.h 
b/clang/include/clang/AST/DeclCXX.h
index a42884be71d68..11d3db5d99293 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -264,6 +264,7 @@ class CXXRecordDecl : public RecordDecl {
   friend class ASTRecordWriter;
   friend class ASTWriter;
   friend class DeclContext;
+  friend class IncrementalParser;
   friend class LambdaExpr;
   friend class ODRDiagsEmitter;
 
diff --git a/clang/lib/Interpreter/IncrementalAction.cpp 
b/clang/lib/Interpreter/IncrementalAction.cpp
index d22031c8fa893..10ca3d3a753c5 100644
--- a/clang/lib/Interpreter/IncrementalAction.cpp
+++ b/clang/lib/Interpreter/IncrementalAction.cpp
@@ -120,6 +120,8 @@ std::unique_ptr 
IncrementalAction::GenModule() {
   return nullptr;
 }
 
+void IncrementalAction::discardModule() { GenModule(); }
+
 CodeGenerator *IncrementalAction::getCodeGen() const {
   FrontendAction *WrappedAct = getWrapped();
   if (!WrappedAct || !WrappedAct->hasIRSupport())
diff --git a/clang/lib/Interpreter/IncrementalAction.h 
b/clang/lib/Interpreter/IncrementalAction.h
index 725cdd0c27cf4..31547cd785967 100644
--- a/clang/lib/Interpreter/IncrementalAction.h
+++ b/clang/lib/Interpreter/IncrementalAction.h
@@ -74,6 +74,10 @@ class IncrementalAction : public WrapperFrontendAction {
 
   /// Generate an LLVM module for the most recent parsed input.
   std::unique_ptr GenModule();
+
+  /// Throw away what CodeGen emitted for an input that failed to parse and
+  /// start a fresh module.
+  void discardModule();
 };
 
 class InProcessPrintingASTConsumer final : public MultiplexConsumer {
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..a30c720873abb 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -15,7 +15,9 @@
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclContextInternals.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Interpreter/PartialTranslationUnit.h"
 #include "clang/Parse/Parser.h"
@@ -87,6 +89,12 @@ IncrementalParser::ParseOrWrapTopLevelDecl() {
   if (Diags.hasErrorOccurred()) {
 CleanUpPTU(C.getTranslationUnitDecl());
 
+// Whatever got through before the error was already handed to CodeGen. It
+// belongs to no PTU and will never be executed, but it stays in the module
+// the next input emits into and collides with the definitions that input
+// provides. Drop it and start a fresh module.
+Act->discardModule();
+
 Diags.Reset(/*soft=*/true);
 Diags.getClient()->clear();
 return llvm::make_error("Parsing failed.",
@@ -191,30 +199,134 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
+bool IncrementalParser::withdrawRedecl(NamedDecl *D, NamedDecl *Prev,
+   TranslationUnitDecl *DiscardedTU) {
+  ASTContext &C = S.getASTContext();
+
+  auto Unlink = [&C](auto *Latest, NamedDecl *SurvivorND) {
+using T = std::remove_pointer_t;
+auto *Survivor = cast(SurvivorND);
+
+// Rebuild First -> ... -> Survivor -> ... -> Latest as
+// First -> ... -> Survivor.
+

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa updated 
https://github.com/llvm/llvm-project/pull/218149

>From d4bb1f45213088a7dab7eaaa43f58ddffc38e416 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Fri, 21 Aug 2026 23:56:37 +0530
Subject: [PATCH] [clang-repl] Keep earlier declarations alive when an input
 fails

Fixes llvm/llvm-project#201844.
---
 clang/lib/Interpreter/IncrementalParser.cpp   | 53 +--
 .../failed-input-keeps-redecls.cpp| 41 ++
 2 files changed, 90 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..1daf24f0ed1c8 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -191,23 +191,62 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
 void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
   if (StoredDeclsMap *Map = MostRecentTU->getPrimaryContext()->getLookupPtr()) 
{
 // Collect the keys to erase: erasing during iteration invalidates the map
 // iterator under backward-shift deletion.
 llvm::SmallVector KeysToErase;
+// Decls with a previous declaration that needs to be restored, instead of
+// discarding.
+llvm::SmallVector, 4>
+DeclsToRestore;
+// List of namespaces to whose members needs to be dropped,
+// but not the namespace itself, as the this is a redeclaration of it.
+llvm::SmallVector NamespacesToDrain;
 for (auto &&[Key, List] : *Map) {
   DeclContextLookupResult R = List.getLookupResult();
   std::vector NamedDeclsToRemove;
   bool RemoveAll = true;
   for (NamedDecl *D : R) {
-if (D->getTranslationUnitDecl() == MostRecentTU)
-  NamedDeclsToRemove.push_back(D);
-else
+if (D->getTranslationUnitDecl() != MostRecentTU) {
   RemoveAll = false;
+  continue;
+}
+NamedDeclsToRemove.push_back(D);
+if (auto *NS = dyn_cast(D);
+NS &&
+NS->getCanonicalDecl()->getTranslationUnitDecl() != MostRecentTU)
+  NamespacesToDrain.push_back(NS);
   }
   if (LLVM_LIKELY(RemoveAll)) {
-KeysToErase.push_back(Key);
+bool Restored = false;
+for (NamedDecl *D : NamedDeclsToRemove)
+  if (NamedDecl *Prev = findSurvivingPrevDecl(D, MostRecentTU)) {
+DeclsToRestore.emplace_back(Key, Prev);
+Restored = true;
+  }
+if (!Restored)
+  KeysToErase.push_back(Key);
   } else {
 for (NamedDecl *D : NamedDeclsToRemove)
   List.remove(D);
@@ -215,6 +254,12 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl 
*MostRecentTU) {
 }
 for (DeclarationName Key : KeysToErase)
   Map->erase(Key);
+for (auto &[Key, Prev] : DeclsToRestore) {
+  Map->erase(Key);
+  (*Map)[Key].addOrReplaceDecl(Prev);
+}
+for (NamespaceDecl *ND : NamespacesToDrain)
+  dropContainingMembers(ND);
   }
 
   ExternCContextDecl *ECCD = S.getASTContext().getExternCContextDecl();
diff --git a/clang/test/Interpreter/failed-input-keeps-redecls.cpp 
b/clang/test/Interpreter/failed-input-keeps-redecls.cpp
new file mode 100644
index 0..97ebd151700b3
--- /dev/null
+++ b/clang/test/Interpreter/failed-input-keeps-redecls.cpp
@@ -0,0 +1,41 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl 2>&1 | FileCheck %s
+
+// A failed input must not take earlier declarations down with it.
+
+extern "C" int printf(const char *, ...);
+
+namespace N { struct S { int v; }; void foo() { printf("foo\n"); } }
+
+namespace N { void bar() { printf("bar\n" } }
+// CHECK-DAG: error: expected ')'
+
+// Everything N held before the failed input is still reachable.
+N::foo();
+// CHECK-DAG: foo
+N::S s; s.v = 7; printf("s.v = %d\n", s.v);
+// CHECK-DAG: s.v = 7
+
+// N is still open for business, and bar is free to be defined properly.
+namespace N { void bar() { printf("bar\n"); } }
+N::bar();
+// CHECK-DAG: bar
+
+namespace N { 

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-22 Thread via cfe-commits

github-actions[bot] wrote:




:warning: C/C++ code formatter, clang-format found issues in your code. 
:warning:



You can test this locally with the following command:


``bash
git-clang-format --diff origin/main HEAD --extensions cpp -- 
clang/test/Interpreter/failed-input-keeps-redecls.cpp 
clang/lib/Interpreter/IncrementalParser.cpp --diff_from_common_commit
``

:warning:
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing `origin/main` to the base branch/commit you want to compare against.
:warning:





View the diff from clang-format here.


``diff
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 6ad23ad89..1daf24f0e 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -234,8 +234,8 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl 
*MostRecentTU) {
 }
 NamedDeclsToRemove.push_back(D);
 if (auto *NS = dyn_cast(D);
-NS && NS->getCanonicalDecl()->getTranslationUnitDecl() !=
-  MostRecentTU)
+NS &&
+NS->getCanonicalDecl()->getTranslationUnitDecl() != MostRecentTU)
   NamespacesToDrain.push_back(NS);
   }
   if (LLVM_LIKELY(RemoveAll)) {

``




https://github.com/llvm/llvm-project/pull/218149
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-22 Thread via cfe-commits

llvmorg-github-actions[bot] wrote:




@llvm/pr-subscribers-clang

Author: Vipul Cariappa (Vipul-Cariappa)


Changes

Fixes #201844

@vgvassilev, this is required for the new overload resolution. We 
should try to land this in the LLVM 23 release.

---
Full diff: https://github.com/llvm/llvm-project/pull/218149.diff


2 Files Affected:

- (modified) clang/lib/Interpreter/IncrementalParser.cpp (+49-4) 
- (added) clang/test/Interpreter/failed-input-keeps-redecls.cpp (+41) 


``diff
diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..6ad23ad89066e 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -191,23 +191,62 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
 void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
   if (StoredDeclsMap *Map = MostRecentTU->getPrimaryContext()->getLookupPtr()) 
{
 // Collect the keys to erase: erasing during iteration invalidates the map
 // iterator under backward-shift deletion.
 llvm::SmallVector KeysToErase;
+// Decls with a previous declaration that needs to be restored, instead of
+// discarding.
+llvm::SmallVector, 4>
+DeclsToRestore;
+// List of namespaces to whose members needs to be dropped,
+// but not the namespace itself, as the this is a redeclaration of it.
+llvm::SmallVector NamespacesToDrain;
 for (auto &&[Key, List] : *Map) {
   DeclContextLookupResult R = List.getLookupResult();
   std::vector NamedDeclsToRemove;
   bool RemoveAll = true;
   for (NamedDecl *D : R) {
-if (D->getTranslationUnitDecl() == MostRecentTU)
-  NamedDeclsToRemove.push_back(D);
-else
+if (D->getTranslationUnitDecl() != MostRecentTU) {
   RemoveAll = false;
+  continue;
+}
+NamedDeclsToRemove.push_back(D);
+if (auto *NS = dyn_cast(D);
+NS && NS->getCanonicalDecl()->getTranslationUnitDecl() !=
+  MostRecentTU)
+  NamespacesToDrain.push_back(NS);
   }
   if (LLVM_LIKELY(RemoveAll)) {
-KeysToErase.push_back(Key);
+bool Restored = false;
+for (NamedDecl *D : NamedDeclsToRemove)
+  if (NamedDecl *Prev = findSurvivingPrevDecl(D, MostRecentTU)) {
+DeclsToRestore.emplace_back(Key, Prev);
+Restored = true;
+  }
+if (!Restored)
+  KeysToErase.push_back(Key);
   } else {
 for (NamedDecl *D : NamedDeclsToRemove)
   List.remove(D);
@@ -215,6 +254,12 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl 
*MostRecentTU) {
 }
 for (DeclarationName Key : KeysToErase)
   Map->erase(Key);
+for (auto &[Key, Prev] : DeclsToRestore) {
+  Map->erase(Key);
+  (*Map)[Key].addOrReplaceDecl(Prev);
+}
+for (NamespaceDecl *ND : NamespacesToDrain)
+  dropContainingMembers(ND);
   }
 
   ExternCContextDecl *ECCD = S.getASTContext().getExternCContextDecl();
diff --git a/clang/test/Interpreter/failed-input-keeps-redecls.cpp 
b/clang/test/Interpreter/failed-input-keeps-redecls.cpp
new file mode 100644
index 0..97ebd151700b3
--- /dev/null
+++ b/clang/test/Interpreter/failed-input-keeps-redecls.cpp
@@ -0,0 +1,41 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl 2>&1 | FileCheck %s
+
+// A failed input must not take earlier declarations down with it.
+
+extern "C" int printf(const char *, ...);
+
+namespace N { struct S { int v; }; void foo() { printf("foo\n"); } }
+
+namespace N { void bar() { printf("bar\n" } }
+// CHECK-DAG: error: expected ')'
+
+// Everything N held before the failed input is still reachable.
+N::foo();
+// CHECK-DAG: foo
+N::S s; s.v = 7; printf("s.v = %d\n", s.v);
+// CHECK-DAG: s.v = 7
+
+// N is still open for business, and bar is free to be defined properly.
+namespace N { void bar() { printf("bar\n"); } }
+N::bar();
+// CHECK-DAG: bar
+
+namespace N { void baz() { printf("baz\n"); } }
+N::baz();
+// CHECK-DAG: baz
+
+// A name that only ever existed in a 

[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)

2026-08-22 Thread Vipul Cariappa via cfe-commits

https://github.com/Vipul-Cariappa created 
https://github.com/llvm/llvm-project/pull/218149

Fixes #201844

@vgvassilev, this is required for the new overload resolution. We should try to 
land this in the LLVM 23 release.

>From 6f6cc793c6ba48353b2ee65771b9ae777b396c87 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa 
Date: Fri, 21 Aug 2026 23:56:37 +0530
Subject: [PATCH] [clang-repl] Keep earlier declarations alive when an input
 fails

Fixes llvm/llvm-project#201844.
---
 clang/lib/Interpreter/IncrementalParser.cpp   | 53 +--
 .../failed-input-keeps-redecls.cpp| 41 ++
 2 files changed, 90 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/Interpreter/failed-input-keeps-redecls.cpp

diff --git a/clang/lib/Interpreter/IncrementalParser.cpp 
b/clang/lib/Interpreter/IncrementalParser.cpp
index 59018907056b8..6ad23ad89066e 100644
--- a/clang/lib/Interpreter/IncrementalParser.cpp
+++ b/clang/lib/Interpreter/IncrementalParser.cpp
@@ -191,23 +191,62 @@ void IncrementalParser::withdrawMostRecentTU(
   C.TUDecl = Prev;
 }
 
+/// The newest declaration of whatever D redeclares that still lives outside
+/// DiscardedTU
+static NamedDecl *findSurvivingPrevDecl(NamedDecl *D,
+TranslationUnitDecl *DiscardedTU) {
+  for (Decl *Prev = D->getPreviousDecl(); Prev; Prev = Prev->getPreviousDecl())
+if (Prev->getTranslationUnitDecl() != DiscardedTU)
+  return dyn_cast(Prev);
+  return nullptr;
+}
+
+/// Unlink everything a discarded re-opening of a namespace put into it. 
Members
+/// are made visible in the namespace's primary context, which outlives the
+/// discarded PTU, so leaving them behind would keep half-parsed declarations
+/// reachable
+static void dropContainingMembers(NamespaceDecl *ND) {
+  llvm::SmallVector Members(ND->decls());
+  for (Decl *M : Members)
+ND->removeDecl(M);
+}
+
 void IncrementalParser::CleanUpPTU(TranslationUnitDecl *MostRecentTU) {
   if (StoredDeclsMap *Map = MostRecentTU->getPrimaryContext()->getLookupPtr()) 
{
 // Collect the keys to erase: erasing during iteration invalidates the map
 // iterator under backward-shift deletion.
 llvm::SmallVector KeysToErase;
+// Decls with a previous declaration that needs to be restored, instead of
+// discarding.
+llvm::SmallVector, 4>
+DeclsToRestore;
+// List of namespaces to whose members needs to be dropped,
+// but not the namespace itself, as the this is a redeclaration of it.
+llvm::SmallVector NamespacesToDrain;
 for (auto &&[Key, List] : *Map) {
   DeclContextLookupResult R = List.getLookupResult();
   std::vector NamedDeclsToRemove;
   bool RemoveAll = true;
   for (NamedDecl *D : R) {
-if (D->getTranslationUnitDecl() == MostRecentTU)
-  NamedDeclsToRemove.push_back(D);
-else
+if (D->getTranslationUnitDecl() != MostRecentTU) {
   RemoveAll = false;
+  continue;
+}
+NamedDeclsToRemove.push_back(D);
+if (auto *NS = dyn_cast(D);
+NS && NS->getCanonicalDecl()->getTranslationUnitDecl() !=
+  MostRecentTU)
+  NamespacesToDrain.push_back(NS);
   }
   if (LLVM_LIKELY(RemoveAll)) {
-KeysToErase.push_back(Key);
+bool Restored = false;
+for (NamedDecl *D : NamedDeclsToRemove)
+  if (NamedDecl *Prev = findSurvivingPrevDecl(D, MostRecentTU)) {
+DeclsToRestore.emplace_back(Key, Prev);
+Restored = true;
+  }
+if (!Restored)
+  KeysToErase.push_back(Key);
   } else {
 for (NamedDecl *D : NamedDeclsToRemove)
   List.remove(D);
@@ -215,6 +254,12 @@ void IncrementalParser::CleanUpPTU(TranslationUnitDecl 
*MostRecentTU) {
 }
 for (DeclarationName Key : KeysToErase)
   Map->erase(Key);
+for (auto &[Key, Prev] : DeclsToRestore) {
+  Map->erase(Key);
+  (*Map)[Key].addOrReplaceDecl(Prev);
+}
+for (NamespaceDecl *ND : NamespacesToDrain)
+  dropContainingMembers(ND);
   }
 
   ExternCContextDecl *ECCD = S.getASTContext().getExternCContextDecl();
diff --git a/clang/test/Interpreter/failed-input-keeps-redecls.cpp 
b/clang/test/Interpreter/failed-input-keeps-redecls.cpp
new file mode 100644
index 0..97ebd151700b3
--- /dev/null
+++ b/clang/test/Interpreter/failed-input-keeps-redecls.cpp
@@ -0,0 +1,41 @@
+// REQUIRES: host-supports-jit
+// RUN: cat %s | clang-repl 2>&1 | FileCheck %s
+
+// A failed input must not take earlier declarations down with it.
+
+extern "C" int printf(const char *, ...);
+
+namespace N { struct S { int v; }; void foo() { printf("foo\n"); } }
+
+namespace N { void bar() { printf("bar\n" } }
+// CHECK-DAG: error: expected ')'
+
+// Everything N held before the failed input is still reachable.
+N::foo();
+// CHECK-DAG: foo
+N::S s; s.v = 7; printf("s.v = %d\n", s.v);
+// CHECK-DAG: s.v = 7
+
+// N is still open for