[clang] fee3ccc - [C++20][Modules] Improve handing of Private Module Fragment diagnostics.

Iain Sandoe via cfe-commits cfe-commits at lists.llvm.org
Sun Aug 21 02:20:45 PDT 2022


Author: Iain Sandoe
Date: 2022-08-21T10:19:46+01:00
New Revision: fee3cccc6cdabdeddc688cf7a15b144c814dd93d

URL: https://github.com/llvm/llvm-project/commit/fee3cccc6cdabdeddc688cf7a15b144c814dd93d
DIFF: https://github.com/llvm/llvm-project/commit/fee3cccc6cdabdeddc688cf7a15b144c814dd93d.diff

LOG: [C++20][Modules] Improve handing of Private Module Fragment diagnostics.

This adds a check for exported inline functions, that there is a definition in
the definition domain (which, in practice, can only be the module purview but
before any PMF starts) since the PMF definition domain cannot contain exports.

This is:
[dcl.inline]/7
If an inline function or variable that is attached to a named module is declared in
a definition domain, it shall be defined in that domain.

The patch also amends diagnostic output by excluding the PMF sub-module from the
set considered as sources of missing decls.  There is no point in telling the user
that the import of a PMF object is missing - since such objects are never reachable
to an importer.  We still show the definition (as unreachable), to help point out
this.

Differential Revision: https://reviews.llvm.org/D128328

Added: 
    clang/test/Modules/cxx20-10-5-ex1.cpp

Modified: 
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Sema.h
    clang/lib/Sema/Sema.cpp
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaLookup.cpp
    clang/lib/Sema/SemaModule.cpp
    clang/test/Modules/Reachability-Private.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e4e7d7b7338a4..791ef9d71dec8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -11141,6 +11141,8 @@ def err_export_using_internal : Error<
 def err_export_not_in_module_interface : Error<
   "export declaration can only be used within a module interface unit"
   "%select{ after the module declaration|}0">;
+def err_export_inline_not_defined : Error<
+  "inline function not defined%select{| before the private module fragment}0">;
 def err_export_partition_impl : Error<
   "module partition implementations cannot be exported">;
 def err_export_in_private_module_fragment : Error<

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 28a47a2eea8b8..14bb2b1d2beed 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2258,6 +2258,11 @@ class Sema final {
   /// Namespace definitions that we will export when they finish.
   llvm::SmallPtrSet<const NamespaceDecl*, 8> DeferredExportedNamespaces;
 
+  /// In a C++ standard module, inline declarations require a definition to be
+  /// present at the end of a definition domain.  This set holds the decls to
+  /// be checked at the end of the TU.
+  llvm::SmallPtrSet<const FunctionDecl *, 8> PendingInlineFuncDecls;
+
   /// Helper function to judge if we are in module purview.
   /// Return false if we are not in a module.
   bool isCurrentModulePurview() const {

diff  --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 1d27f74f00e56..04100324c786e 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1251,6 +1251,33 @@ void Sema::ActOnEndOfTranslationUnit() {
     emitAndClearUnusedLocalTypedefWarnings();
   }
 
+  // C++ standard modules. Diagnose cases where a function is declared inline
+  // in the module purview but has no definition before the end of the TU or
+  // the start of a Private Module Fragment (if one is present).
+  if (!PendingInlineFuncDecls.empty()) {
+    for (auto *D : PendingInlineFuncDecls) {
+      if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+        bool DefInPMF = false;
+        if (auto *FDD = FD->getDefinition()) {
+          assert(FDD->getOwningModule() &&
+                 FDD->getOwningModule()->isModulePurview());
+          DefInPMF = FDD->getOwningModule()->isPrivateModule();
+          if (!DefInPMF)
+            continue;
+        }
+        Diag(FD->getLocation(), diag::err_export_inline_not_defined)
+            << DefInPMF;
+        // If we have a PMF it should be at the end of the ModuleScopes.
+        if (DefInPMF &&
+            ModuleScopes.back().Module->Kind == Module::PrivateModuleFragment) {
+          Diag(ModuleScopes.back().BeginLoc,
+               diag::note_private_module_fragment);
+        }
+      }
+    }
+    PendingInlineFuncDecls.clear();
+  }
+
   // C99 6.9.2p2:
   //   A declaration of an identifier for an object that has file
   //   scope without an initializer, and without a storage-class

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 33b49482c6e55..004c1ffd81b9f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -9838,6 +9838,19 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
       NewFD->setType(Context.getFunctionType(
           FPT->getReturnType(), FPT->getParamTypes(),
           FPT->getExtProtoInfo().withExceptionSpec(EST_BasicNoexcept)));
+
+    // C++20 [dcl.inline]/7
+    // If an inline function or variable that is attached to a named module
+    // is declared in a definition domain, it shall be defined in that
+    // domain.
+    // So, if the current declaration does not have a definition, we must
+    // check at the end of the TU (or when the PMF starts) to see that we
+    // have a definition at that point.
+    if (isInline && !D.isFunctionDefinition() && getLangOpts().CPlusPlus20 &&
+        NewFD->hasOwningModule() &&
+        NewFD->getOwningModule()->isModulePurview()) {
+      PendingInlineFuncDecls.insert(NewFD);
+    }
   }
 
   // Filter out previous declarations that don't match the scope.

diff  --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 1d8df945a4fe2..22d72c3251a0c 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -5691,7 +5691,7 @@ void Sema::diagnoseMissingImport(SourceLocation UseLoc, NamedDecl *Decl,
   llvm::SmallVector<Module*, 8> UniqueModules;
   llvm::SmallDenseSet<Module*, 8> UniqueModuleSet;
   for (auto *M : Modules) {
-    if (M->Kind == Module::GlobalModuleFragment)
+    if (M->isGlobalModule() || M->isPrivateModule())
       continue;
     if (UniqueModuleSet.insert(M).second)
       UniqueModules.push_back(M);

diff  --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index 757c611d61ba9..b205fd914f9d3 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -910,6 +910,17 @@ Decl *Sema::ActOnFinishExportDecl(Scope *S, Decl *D, SourceLocation RBraceLoc) {
         diagExportedUnnamedDecl(*this, UnnamedDeclKind::Context, Child,
                                 BlockStart);
       }
+      if (auto *FD = dyn_cast<FunctionDecl>(Child)) {
+        // [dcl.inline]/7
+        // If an inline function or variable that is attached to a named module
+        // is declared in a definition domain, it shall be defined in that
+        // domain.
+        // So, if the current declaration does not have a definition, we must
+        // check at the end of the TU (or when the PMF starts) to see that we
+        // have a definition at that point.
+        if (FD->isInlineSpecified() && !FD->isDefined())
+          PendingInlineFuncDecls.insert(FD);
+      }
     }
   }
 

diff  --git a/clang/test/Modules/Reachability-Private.cpp b/clang/test/Modules/Reachability-Private.cpp
index fdaf5c349f8ae..9a7c3ba231f17 100644
--- a/clang/test/Modules/Reachability-Private.cpp
+++ b/clang/test/Modules/Reachability-Private.cpp
@@ -4,18 +4,25 @@
 // RUN: mkdir -p %t
 // RUN: split-file %s %t
 //
-// RUN: %clang_cc1 -std=c++20 %t/Private.cppm -emit-module-interface -o %t/Private.pcm
-// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++20 %t/Private.cppm -emit-module-interface \
+// RUN: -o %t/Private.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp \
+// RUN: -DTEST_BADINLINE -verify -fsyntax-only
 
 //--- Private.cppm
 export module Private;
-inline void fn_m(); // OK, module-linkage inline function
+#ifdef TEST_BADINLINE
+inline void fn_m(); // expected-error {{un-exported inline function not defined before the private module fragment}}
+                    // expected-note at Private.cppm:13 {{private module fragment begins here}}
+#endif
 static void fn_s();
 export struct X;
 
 export void g(X *x) {
   fn_s(); // OK, call to static function in same translation unit
-  fn_m(); // OK, call to module-linkage inline function
+#ifdef TEST_BADINLINE
+  fn_m(); // fn_m is not OK.
+#endif
 }
 export X *factory(); // OK
 
@@ -30,10 +37,8 @@ void fn_s() {}
 //--- Use.cpp
 import Private;
 void foo() {
-  X x; // expected-error {{definition of 'X' must be imported from module 'Private.<private>' before it is required}}
-       // expected-error at -1 {{definition of 'X' must be imported from module 'Private.<private>' before it is required}}
-       // expected-note@* {{definition here is not reachable}}
-       // expected-note@* {{definition here is not reachable}}
+  X x; // expected-error 1+{{missing '#include'; 'X' must be defined before it is used}}
+       // expected-note at Private.cppm:18 1+{{definition here is not reachable}}
   auto _ = factory();
   auto *__ = factory();
   X *___ = factory();

diff  --git a/clang/test/Modules/cxx20-10-5-ex1.cpp b/clang/test/Modules/cxx20-10-5-ex1.cpp
new file mode 100644
index 0000000000000..c24fb5fe1fea5
--- /dev/null
+++ b/clang/test/Modules/cxx20-10-5-ex1.cpp
@@ -0,0 +1,53 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t
+
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface std-10-5-ex1-interface.cpp \
+// RUN: -DBAD_FWD_DECL  -fsyntax-only -verify
+
+// RUN: %clang_cc1 -std=c++20 -emit-module-interface std-10-5-ex1-interface.cpp \
+// RUN: -o A.pcm
+
+// RUN: %clang_cc1 -std=c++20 std-10-5-ex1-use.cpp  -fmodule-file=A.pcm \
+// RUN:    -fsyntax-only -verify
+
+//--- std-10-5-ex1-interface.cpp
+
+export module A;
+#ifdef BAD_FWD_DECL
+export inline void fn_e(); // expected-error {{inline function not defined before the private module fragment}}
+                           // expected-note at std-10-5-ex1-interface.cpp:21 {{private module fragment begins here}}
+#endif
+export inline void ok_fn() {}
+export inline void ok_fn2();
+#ifdef BAD_FWD_DECL
+inline void fn_m(); // expected-error {{inline function not defined before the private module fragment}}
+                    // expected-note at std-10-5-ex1-interface.cpp:21 {{private module fragment begins here}}
+#endif
+static void fn_s();
+export struct X;
+export void g(X *x) {
+  fn_s();
+}
+export X *factory();
+void ok_fn2() {}
+
+module :private;
+struct X {};
+X *factory() {
+  return new X();
+}
+
+void fn_e() {}
+void fn_m() {}
+void fn_s() {}
+
+//--- std-10-5-ex1-use.cpp
+
+import A;
+
+void foo() {
+  X x; // expected-error 1+{{missing '#include'; 'X' must be defined before it is used}}
+       // expected-note at std-10-5-ex1-interface.cpp:22 1+{{definition here is not reachable}}
+  X *p = factory();
+}


        


More information about the cfe-commits mailing list