[clang] [clang][Modules] Add explicit PrivateModuleFragmentDecl AST node (PR #223044)
Mahmoud Ahmed via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 12 08:11:30 PDT 2026
https://github.com/anondeveg updated https://github.com/llvm/llvm-project/pull/223044
>From 29c0410bde70093aee39df1747a31d40a7b82f7d Mon Sep 17 00:00:00 2001
From: Anondev <anondeveg at gmail.com>
Date: Fri, 11 Sep 2026 22:51:40 +0300
Subject: [PATCH] [clang][Modules] Add explicit PrivateModuleFragmentDecl AST
node
---
clang/include/clang/AST/Decl.h | 43 +++++++++++++++++++
clang/include/clang/AST/RecursiveASTVisitor.h | 1 +
clang/include/clang/AST/TextNodeDumper.h | 1 +
clang/include/clang/Basic/DeclNodes.td | 1 +
clang/include/clang/Sema/Template.h | 1 +
.../include/clang/Serialization/ASTBitCodes.h | 5 ++-
clang/lib/AST/Decl.cpp | 9 ++++
clang/lib/AST/DeclBase.cpp | 1 +
clang/lib/AST/DeclPrinter.cpp | 5 +++
clang/lib/AST/TextNodeDumper.cpp | 5 +++
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 ++++
.../AST/ast-dump-private-module-fragment.cppm | 12 ++++++
.../mismatched_global_module_state.cppm | 5 +++
clang/tools/libclang/CIndex.cpp | 1 +
19 files changed, 119 insertions(+), 4 deletions(-)
create mode 100644 clang/test/AST/ast-dump-private-module-fragment.cppm
create mode 100644 clang/test/Modules/mismatched_global_module_state.cppm
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 92050d635e2e0a..3fab5e8515bd42 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -5309,6 +5309,49 @@ class ExportDecl final : public Decl, public DeclContext {
}
};
+/// Represents the private module fragment of a module unit.
+///
+/// For example:
+/// \code
+/// export module A;
+/// ...
+/// module :private; // <- this fragment
+/// int internal_only();
+/// \endcode
+class PrivateModuleFragmentDecl final : public Decl {
+ Module *Fragment;
+ SourceLocation PrivateLoc;
+
+ PrivateModuleFragmentDecl(DeclContext *DC, SourceLocation ModuleLoc,
+ SourceLocation PrivateLoc, Module *Fragment)
+ : Decl(PrivateModuleFragment, DC, ModuleLoc), Fragment(Fragment),
+ PrivateLoc(PrivateLoc) {}
+ PrivateModuleFragmentDecl(EmptyShell Empty)
+ : Decl(PrivateModuleFragment, Empty) {}
+
+public:
+ 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; }
+ void setPrivateLoc(SourceLocation Loc) { PrivateLoc = Loc; }
+ void setFragment(Module *Frag) { Fragment = Frag; }
+ 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 1f7c8d762e1b52..24371d148a0a60 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/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 1cdd8c37c7fc63..d0c97326f85f0f 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -373,6 +373,7 @@ class TextNodeDumper
void VisitBindingDecl(const BindingDecl *D);
void VisitCapturedDecl(const CapturedDecl *D);
void VisitImportDecl(const ImportDecl *D);
+ void VisitPrivateModuleFragmentDecl(const PrivateModuleFragmentDecl *D);
void VisitPragmaCommentDecl(const PragmaCommentDecl *D);
void VisitPragmaDetectMismatchDecl(const PragmaDetectMismatchDecl *D);
void VisitOMPExecutableDirective(const OMPExecutableDirective *D);
diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td
index 114c6ae5282ef0..0e81a32d19d25c 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 50e950e56c6cac..1018af059dd1ae 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)
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 6a52a9e4fa780f..722bca1975e406 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 0894097333d737..01af3f08e418fe 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 70f61fa57a682a..e89ee3e0f31709 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/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 1312ffd0803423..3e0ab4b41797c1 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -78,6 +78,7 @@ namespace {
void VisitFileScopeAsmDecl(FileScopeAsmDecl *D);
void VisitTopLevelStmtDecl(TopLevelStmtDecl *D);
void VisitImportDecl(ImportDecl *D);
+ void VisitPrivateModuleFragmentDecl(PrivateModuleFragmentDecl *D);
void VisitStaticAssertDecl(StaticAssertDecl *D);
void VisitNamespaceDecl(NamespaceDecl *D);
void VisitUsingDirectiveDecl(UsingDirectiveDecl *D);
@@ -1065,6 +1066,10 @@ void DeclPrinter::VisitImportDecl(ImportDecl *D) {
<< ";\n";
}
+void DeclPrinter::VisitPrivateModuleFragmentDecl(PrivateModuleFragmentDecl *D) {
+ Out << "module :private;\n";
+}
+
void DeclPrinter::VisitStaticAssertDecl(StaticAssertDecl *D) {
Out << "static_assert(";
D->getAssertExpr()->printPretty(Out, nullptr, Policy, Indentation, "\n",
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index f58cc4f5761b7b..60312419fa5596 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -2643,6 +2643,11 @@ void TextNodeDumper::VisitImportDecl(const ImportDecl *D) {
dumpDeclRef(InitD, "initializer");
}
+void TextNodeDumper::VisitPrivateModuleFragmentDecl(
+ const PrivateModuleFragmentDecl *D) {
+ OS << ' ' << D->getFragment()->getFullModuleName();
+}
+
void TextNodeDumper::VisitPragmaCommentDecl(const PragmaCommentDecl *D) {
OS << ' ';
switch (D->getCommentKind()) {
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index b8fae352d41d74..b5a8f9db06de0b 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 bad81ea92cd2db..7e7efa0df7cfe6 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 d7a182fe5654ce..91983e4dcbb2b7 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 ca7993adb7d2c5..9b2eed7d74b656 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 e973b7ae719540..d3dc6c33903da7 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->setFragment(readModule());
+ D->setPrivateLoc(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 6f67acf9a6e7e6..d75eebf24a287d 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);
diff --git a/clang/test/AST/ast-dump-private-module-fragment.cppm b/clang/test/AST/ast-dump-private-module-fragment.cppm
new file mode 100644
index 00000000000000..39b41af4031408
--- /dev/null
+++ b/clang/test/AST/ast-dump-private-module-fragment.cppm
@@ -0,0 +1,12 @@
+//RUN: %clang_cc1 -std=c++20 -ast-dump %s | FileCheck %s
+
+export module A;
+
+int inTheInterface();
+// CHECK: FunctionDecl 0x{{[0-9a-f]+}} <{{.*}}> {{.*}}inTheInterface 'int ()'
+
+module :private;
+// CHECK: PrivateModuleFragmentDecl 0x{{[0-9a-f]+}} <{{.*}}>
+
+int inThePrivateFragment();
+// CHECK: FunctionDecl 0x{{[0-9a-f]+}} <{{.*}}> {{.*}}inThePrivateFragment 'int ()'
diff --git a/clang/test/Modules/mismatched_global_module_state.cppm b/clang/test/Modules/mismatched_global_module_state.cppm
new file mode 100644
index 00000000000000..11cfeca5a96b47
--- /dev/null
+++ b/clang/test/Modules/mismatched_global_module_state.cppm
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 --std=c++23 -fsyntax-only -verify %s
+// see ISSUE 219950 and PR 223044
+module;
+module :private; // expected-error {{private module fragment declaration with no preceding module declaration}}
+export module Foo;
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 2c5e799d12f181..f9a7f7326b70e1 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -7299,6 +7299,7 @@ CXCursor clang_getCursorDefinition(CXCursor C) {
case Decl::Label: // FIXME: Is this right??
case Decl::CXXDeductionGuide:
case Decl::Import:
+ case Decl::PrivateModuleFragment:
case Decl::OMPThreadPrivate:
case Decl::OMPGroupPrivate:
case Decl::OMPAllocate:
More information about the cfe-commits
mailing list