[clang] [clang][Modules] Add explicit PrivateModuleFragmentDecl AST node (PR #223044)

Mahmoud Ahmed via cfe-commits cfe-commits at lists.llvm.org
Fri Sep 11 13:03:50 PDT 2026


https://github.com/anondeveg created https://github.com/llvm/llvm-project/pull/223044

Fixes 219950 

>From e4d8a86a2c40bf02283838266cf56f3bfdbb0321 Mon Sep 17 00:00:00 2001
From: Anondev <anondeveg at gmail.com>
Date: Sat, 29 Aug 2026 08:32:26 +0300
Subject: [PATCH 1/3] [clang][Parse] Improve diagnostics for '>>>' closing
 template parameter lists in CUDA.

---
 clang/docs/ReleaseNotes.md                        |  2 ++
 clang/lib/Parse/ParseTemplate.cpp                 | 11 ++++++++---
 clang/test/Parser/cuda-template-angle-brackets.cu |  3 +++
 3 files changed, 13 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Parser/cuda-template-angle-brackets.cu

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0dbfa2af229..3e2d835583e4d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -416,6 +416,8 @@ features cannot lower the translation-unit ABI level;
 - `-Wc++98-compat` now diagnoses explicit conversion functions in C++20 and
   later, matching the behavior in C++11 through C++17. (#GH161689)
 
+- Fixed incorrect diagnostics for nested template parameter lists containing ``>>>`` when parsing in CUDA mode.
+
 ### Improvements to Clang's time-trace
 
 ### Improvements to Coverage Mapping
diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp
index 735a9bd1f9f1c..8d4dbf9db4a56 100644
--- a/clang/lib/Parse/ParseTemplate.cpp
+++ b/clang/lib/Parse/ParseTemplate.cpp
@@ -327,8 +327,8 @@ bool Parser::ParseTemplateParameters(
 
   // Try to parse the template parameter list.
   bool Failed = false;
-  // FIXME: Missing greatergreatergreater support.
-  if (!Tok.is(tok::greater) && !Tok.is(tok::greatergreater)) {
+  if (!Tok.is(tok::greater) && !Tok.is(tok::greatergreater) &&
+      !Tok.is(tok::greatergreatergreater)) {
     TemplateScopes.Enter(Scope::TemplateParamScope);
     Failed = ParseTemplateParameterList(Depth, TemplateParams);
   }
@@ -342,6 +342,10 @@ bool Parser::ParseTemplateParameters(
     Tok.setKind(tok::greater);
     RAngleLoc = Tok.getLocation();
     Tok.setLocation(Tok.getLocation().getLocWithOffset(1));
+  } else if (Tok.is(tok::greatergreatergreater)) {
+    Tok.setKind(tok::greatergreater);
+    RAngleLoc = Tok.getLocation();
+    Tok.setLocation(Tok.getLocation().getLocWithOffset(1));
   } else if (!TryConsumeToken(tok::greater, RAngleLoc) && Failed) {
     Diag(Tok.getLocation(), diag::err_expected) << tok::greater;
     return true;
@@ -367,7 +371,8 @@ Parser::ParseTemplateParameterList(const unsigned Depth,
     // Did we find a comma or the end of the template parameter list?
     if (Tok.is(tok::comma)) {
       ConsumeToken();
-    } else if (Tok.isOneOf(tok::greater, tok::greatergreater)) {
+    } else if (Tok.isOneOf(tok::greater, tok::greatergreater,
+                           tok::greatergreatergreater)) {
       // Don't consume this... that's done by template parser.
       break;
     } else {
diff --git a/clang/test/Parser/cuda-template-angle-brackets.cu b/clang/test/Parser/cuda-template-angle-brackets.cu
new file mode 100644
index 0000000000000..38d5a48e6b279
--- /dev/null
+++ b/clang/test/Parser/cuda-template-angle-brackets.cu
@@ -0,0 +1,3 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+template <template <template <int>>> struct S; // expected-error 2 {{template template parameter requires 'class' or 'typename' after the parameter list}}

>From 59387dc7d6c4068e6ce7d24d51cb8bb6078b52fe Mon Sep 17 00:00:00 2001
From: Anondev <anondeveg at gmail.com>
Date: Tue, 1 Sep 2026 05:58:02 +0300
Subject: [PATCH 2/3] [Clang] Add test for version control conflict markers

this exercises the CUDA tokenization where >>> is treated
as a single token, ensuring that the <<<< and >>>> sequences from
conflict markers are not incorrectly interpreted as C++/CUDA syntax.
---
 clang/test/Parser/cuda-version-control-conflict.cu | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 clang/test/Parser/cuda-version-control-conflict.cu

diff --git a/clang/test/Parser/cuda-version-control-conflict.cu b/clang/test/Parser/cuda-version-control-conflict.cu
new file mode 100644
index 0000000000000..5b2e56c771e6e
--- /dev/null
+++ b/clang/test/Parser/cuda-version-control-conflict.cu
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// this exercises the CUDA tokenization where >>> is treated
+// as a single token, ensuring that the <<<< and >>>> sequences from
+// conflict markers are not incorrectly interpreted as C++/CUDA syntax. 
+
+// expected-error at +1 {{version control conflict marker in file}}
+<<<<<<< HEAD 
+int x = 5;
+=======
+int y = 0;
+>>>>>>> other-branch

>From 90a2875db449a5a32eca9c1783b54da56568c975 Mon Sep 17 00:00:00 2001
From: Anondev <anondeveg at gmail.com>
Date: Fri, 11 Sep 2026 22:51:40 +0300
Subject: [PATCH 3/3] [clang][Modules] Add explicit PrivateModuleFragmentDecl
 AST node

---
 clang/include/clang/AST/Decl.h                | 32 +++++++++++++++++++
 clang/include/clang/AST/RecursiveASTVisitor.h |  1 +
 clang/include/clang/Basic/DeclNodes.td        |  1 +
 clang/include/clang/Sema/Template.h           |  5 +--
 .../include/clang/Serialization/ASTBitCodes.h |  5 ++-
 clang/lib/AST/Decl.cpp                        |  9 ++++++
 clang/lib/AST/DeclBase.cpp                    |  1 +
 clang/lib/CodeGen/CGDecl.cpp                  |  1 +
 clang/lib/Parse/Parser.cpp                    |  6 +++-
 clang/lib/Sema/SemaModule.cpp                 |  6 ++--
 clang/lib/Serialization/ASTCommon.cpp         |  1 +
 clang/lib/Serialization/ASTReaderDecl.cpp     | 11 +++++++
 clang/lib/Serialization/ASTWriterDecl.cpp     |  8 +++++
 13 files changed, 81 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 92050d635e2e0..adbbf75111b08 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -5309,6 +5309,38 @@ class ExportDecl final : public Decl, public DeclContext {
   }
 };
 
+class PrivateModuleFragmentDecl final : public Decl {
+  PrivateModuleFragmentDecl(DeclContext *DC, SourceLocation ModuleLoc,
+                            SourceLocation PrivateLoc, Module *Fragment)
+      : Decl(PrivateModuleFragment, DC, ModuleLoc), Fragment(Fragment),
+        PrivateLoc(PrivateLoc) {}
+  PrivateModuleFragmentDecl(EmptyShell Empty)
+      : Decl(PrivateModuleFragment, Empty) {}
+
+public:
+  Module *Fragment;
+  SourceLocation PrivateLoc;
+
+  static PrivateModuleFragmentDecl *Create(ASTContext &C, DeclContext *DC,
+                                           SourceLocation ModuleLoc,
+                                           SourceLocation PrivateLoc,
+                                           Module *Fragment) {
+    return new (C, DC)
+        PrivateModuleFragmentDecl(DC, ModuleLoc, PrivateLoc, Fragment);
+  }
+  static PrivateModuleFragmentDecl *CreateDeserialized(ASTContext &C,
+                                                       GlobalDeclID ID);
+
+  Module *getFragment() const { return Fragment; }
+  SourceLocation getPrivateLoc() const { return PrivateLoc; }
+  SourceRange getSourceRange() const override LLVM_READONLY {
+    return SourceRange(getLocation(), PrivateLoc);
+  }
+  static bool classof(const Decl *D) {
+    return D->getKind() == Decl::PrivateModuleFragment;
+  }
+};
+
 /// Represents an empty-declaration.
 class EmptyDecl : public Decl {
   EmptyDecl(DeclContext *DC, SourceLocation L) : Decl(Empty, DC, L) {}
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 1f7c8d762e1b5..24371d148a0a6 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1723,6 +1723,7 @@ DEF_TRAVERSE_DECL(FileScopeAsmDecl,
 DEF_TRAVERSE_DECL(TopLevelStmtDecl, { TRY_TO(TraverseStmt(D->getStmt())); })
 
 DEF_TRAVERSE_DECL(ImportDecl, {})
+DEF_TRAVERSE_DECL(PrivateModuleFragmentDecl, {})
 
 DEF_TRAVERSE_DECL(FriendDecl, {
   // Friend is either decl or a type.
diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td
index 114c6ae5282ef..0e81a32d19d25 100644
--- a/clang/include/clang/Basic/DeclNodes.td
+++ b/clang/include/clang/Basic/DeclNodes.td
@@ -107,6 +107,7 @@ def Block : DeclNode<Decl, "blocks">, DeclContext;
 def OutlinedFunction : DeclNode<Decl>, DeclContext;
 def Captured : DeclNode<Decl>, DeclContext;
 def Import : DeclNode<Decl>;
+def PrivateModuleFragment : DeclNode<Decl>;
 def OMPThreadPrivate : DeclNode<Decl>;
 def OMPGroupPrivate : DeclNode<Decl>;
 def OMPAllocate : DeclNode<Decl>;
diff --git a/clang/include/clang/Sema/Template.h b/clang/include/clang/Sema/Template.h
index 50e950e56c6ca..ab1584ee0f1b3 100644
--- a/clang/include/clang/Sema/Template.h
+++ b/clang/include/clang/Sema/Template.h
@@ -634,6 +634,7 @@ enum class TemplateSubstitutionKind : char {
 #define FILESCOPEASM(DERIVED, BASE)
 #define TOPLEVELSTMT(DERIVED, BASE)
 #define IMPORT(DERIVED, BASE)
+#define PRIVATEMODULEFRAGMENT(DERIVED, BASE)
 #define EXPORT(DERIVED, BASE)
 #define LINKAGESPEC(DERIVED, BASE)
 #define OBJCCOMPATIBLEALIAS(DERIVED, BASE)
@@ -645,10 +646,10 @@ enum class TemplateSubstitutionKind : char {
 #define EMPTY(DERIVED, BASE)
 #define LIFETIMEEXTENDEDTEMPORARY(DERIVED, BASE)
 
-// Decls which never appear inside a template.
+  // Decls which never appear inside a template.
 #define OUTLINEDFUNCTION(DERIVED, BASE)
 
-// Decls which use special-case instantiation code.
+  // Decls which use special-case instantiation code.
 #define BLOCK(DERIVED, BASE)
 #define CAPTURED(DERIVED, BASE)
 #define IMPLICITPARAM(DERIVED, BASE)
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 6a52a9e4fa780..722bca1975e40 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1492,7 +1492,10 @@ enum DeclCode {
   /// An ImportDecl recording a module import.
   DECL_IMPORT,
 
-  /// An OMPThreadPrivateDecl record.
+  /// A PrivateModuleFragmentDecl record.
+  DECL_PRIVATE_MODULE_FRAGMENT,
+
+  /// An OMPThreadPrivateDecl Record.
   DECL_OMP_THREADPRIVATE,
 
   /// An OMPRequiresDecl record.
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 0894097333d73..01af3f08e418f 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -6192,3 +6192,12 @@ bool clang::hasArmZT0State(const FunctionDecl *FD) {
                    FunctionType::ARM_None) ||
          (FD->hasAttr<ArmNewAttr>() && FD->getAttr<ArmNewAttr>()->isNewZT0());
 }
+
+//===----------------------------------------------------------------------===//
+// PrivateModuleFragmentDecl Implementation
+//===----------------------------------------------------------------------===//
+
+PrivateModuleFragmentDecl *
+PrivateModuleFragmentDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
+  return new (C, ID) PrivateModuleFragmentDecl(EmptyShell());
+}
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 70f61fa57a682..e89ee3e0f3170 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -986,6 +986,7 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) {
     case Friend:
     case FriendTemplate:
     case AccessSpec:
+    case PrivateModuleFragment:
     case LinkageSpec:
     case Export:
     case FileScopeAsm:
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index b8fae352d41d7..b5a8f9db06de0 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -128,6 +128,7 @@ void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
   case Decl::ExplicitInstantiation:
   case Decl::Label:        // __label__ x;
   case Decl::Import:
+  case Decl::PrivateModuleFragment:
   case Decl::MSGuid:    // __declspec(uuid("..."))
   case Decl::UnnamedGlobalConstant:
   case Decl::TemplateParamObject:
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index bad81ea92cd2d..7e7efa0df7cfe 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -2360,10 +2360,14 @@ Parser::ParseModuleDecl(Sema::ModuleImportState &ImportState) {
     SourceLocation PrivateLoc = ConsumeToken();
     DiagnoseAndSkipCXX11Attributes();
     ExpectAndConsumeSemi(diag::err_private_module_fragment_expected_semi);
+    auto Result = Actions.ActOnPrivateModuleFragmentDecl(ModuleLoc, PrivateLoc);
+
+    if (!Result)
+      return nullptr;
     ImportState = ImportState == Sema::ModuleImportState::ImportAllowed
                       ? Sema::ModuleImportState::PrivateFragmentImportAllowed
                       : Sema::ModuleImportState::PrivateFragmentImportFinished;
-    return Actions.ActOnPrivateModuleFragmentDecl(ModuleLoc, PrivateLoc);
+    return Result;
   }
 
   SmallVector<IdentifierLoc, 2> Path;
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index d7a182fe5654c..91983e4dcbb2b 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -565,8 +565,10 @@ Sema::ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc,
   TU->setModuleOwnershipKind(Decl::ModuleOwnershipKind::ModulePrivate);
   TU->setLocalOwningModule(PrivateModuleFragment);
 
-  // FIXME: Consider creating an explicit representation of this declaration.
-  return nullptr;
+  auto *PMF = PrivateModuleFragmentDecl::Create(
+      Context, CurContext, ModuleLoc, PrivateLoc, PrivateModuleFragment);
+  CurContext->addDecl(PMF);
+  return ConvertDeclToDeclGroup(PMF);
 }
 
 DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc,
diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp
index ca7993adb7d2c..9b2eed7d74b65 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -446,6 +446,7 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) {
   case Decl::OutlinedFunction:
   case Decl::Captured:
   case Decl::Import:
+  case Decl::PrivateModuleFragment:
   case Decl::OMPThreadPrivate:
   case Decl::OMPGroupPrivate:
   case Decl::OMPAllocate:
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index e973b7ae71954..00032bd11e790 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -402,6 +402,7 @@ class ASTDeclReader : public DeclVisitor<ASTDeclReader, void> {
   void VisitTopLevelStmtDecl(TopLevelStmtDecl *D);
   void VisitImportDecl(ImportDecl *D);
   void VisitAccessSpecDecl(AccessSpecDecl *D);
+  void VisitPrivateModuleFragmentDecl(PrivateModuleFragmentDecl *D);
   void VisitFriendDecl(FriendDecl *D);
   void VisitFriendTemplateDecl(FriendTemplateDecl *D);
   void VisitStaticAssertDecl(StaticAssertDecl *D);
@@ -2397,6 +2398,13 @@ void ASTDeclReader::VisitImportDecl(ImportDecl *D) {
   Record.skipInts(1); // The number of stored source locations.
 }
 
+void ASTDeclReader::VisitPrivateModuleFragmentDecl(
+    PrivateModuleFragmentDecl *D) {
+  VisitDecl(D);
+  D->Fragment = readModule();
+  D->PrivateLoc = readSourceLocation();
+}
+
 void ASTDeclReader::VisitAccessSpecDecl(AccessSpecDecl *D) {
   VisitDecl(D);
   D->setColonLoc(readSourceLocation());
@@ -4278,6 +4286,9 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
     // locations.
     D = ImportDecl::CreateDeserialized(Context, ID, Record.back());
     break;
+  case DECL_PRIVATE_MODULE_FRAGMENT:
+    D = PrivateModuleFragmentDecl::CreateDeserialized(Context, ID);
+    break;
   case DECL_OMP_THREADPRIVATE: {
     Record.skipInts(1);
     unsigned NumChildren = Record.readInt();
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 6f67acf9a6e7e..d75eebf24a287 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -140,6 +140,7 @@ namespace clang {
     void VisitFileScopeAsmDecl(FileScopeAsmDecl *D);
     void VisitTopLevelStmtDecl(TopLevelStmtDecl *D);
     void VisitImportDecl(ImportDecl *D);
+    void VisitPrivateModuleFragmentDecl(PrivateModuleFragmentDecl *D);
     void VisitAccessSpecDecl(AccessSpecDecl *D);
     void VisitFriendDecl(FriendDecl *D);
     void VisitFriendTemplateDecl(FriendTemplateDecl *D);
@@ -1829,6 +1830,13 @@ void ASTDeclWriter::VisitAccessSpecDecl(AccessSpecDecl *D) {
   Record.AddSourceLocation(D->getColonLoc());
   Code = serialization::DECL_ACCESS_SPEC;
 }
+void ASTDeclWriter::VisitPrivateModuleFragmentDecl(
+    PrivateModuleFragmentDecl *D) {
+  VisitDecl(D);
+  Record.push_back(Writer.getSubmoduleID(D->getFragment()));
+  Record.AddSourceLocation(D->getPrivateLoc());
+  Code = serialization::DECL_PRIVATE_MODULE_FRAGMENT;
+}
 
 void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
   VisitDecl(D);



More information about the cfe-commits mailing list