[clang] [clang-repl] Keep earlier declarations alive when an input fails (PR #218149)
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)
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)
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)
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)
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)
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)
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)
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)
@@ -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)
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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
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)
@@ -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)
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)
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)
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)
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)
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
