Author: Chuanqi Xu
Date: 2025-06-23T14:39:08+08:00
New Revision: fccc6ee7021811a27ab1303d19407f703853ab92

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

LOG: [C++20] [Modules] Don't make enum constant members always visible

Close https://github.com/llvm/llvm-project/issues/131058

See the comments in
ASTWriter.cpp:ASTDeclContextNameLookupTrait::getLookupVisibility and
SemaLookup.cpp:Sema::makeMergedDefinitionVisible for details.

Added: 
    clang/test/Modules/include-after-imports-enums.cppm
    clang/test/Modules/pr131058.cppm

Modified: 
    clang/lib/Sema/SemaLookup.cpp
    clang/lib/Serialization/ASTWriter.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 5ad9dd8ed0d3e..aa7191d2814fe 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -1565,6 +1565,21 @@ void Sema::makeMergedDefinitionVisible(NamedDecl *ND) {
   if (auto *TD = dyn_cast<TemplateDecl>(ND))
     for (auto *Param : *TD->getTemplateParameters())
       makeMergedDefinitionVisible(Param);
+
+  // If we import a named module which contains a header, and then we include a
+  // header which contains a definition of enums, we will skip parsing the 
enums
+  // in the current TU. But we need to ensure the visibility of the enum
+  // contants, since they are able to be found with the parents of their
+  // parents.
+  if (auto *ED = dyn_cast<EnumDecl>(ND);
+      ED && ED->isFromGlobalModule() && !ED->isScoped()) {
+    for (auto *ECD : ED->enumerators()) {
+      ECD->setVisibleDespiteOwningModule();
+      DeclContext *RedeclCtx = ED->getDeclContext()->getRedeclContext();
+      if (RedeclCtx->lookup(ECD->getDeclName()).empty())
+        RedeclCtx->makeDeclVisibleInContext(ECD);
+    }
+  }
 }
 
 /// Find the module in which the given declaration was defined.
@@ -2185,22 +2200,10 @@ bool LookupResult::isAvailableForLookup(Sema &SemaRef, 
NamedDecl *ND) {
   // Class and enumeration member names can be found by name lookup in any
   // context in which a definition of the type is reachable.
   //
-  // FIXME: The current implementation didn't consider about scope. For 
example,
-  // ```
-  // // m.cppm
-  // export module m;
-  // enum E1 { e1 };
-  // // Use.cpp
-  // import m;
-  // void test() {
-  //   auto a = E1::e1; // Error as expected.
-  //   auto b = e1; // Should be error. namespace-scope name e1 is not visible
-  // }
-  // ```
-  // For the above example, the current implementation would emit error for `a`
-  // correctly. However, the implementation wouldn't diagnose about `b` now.
-  // Since we only check the reachability for the parent only.
-  // See clang/test/CXX/module/module.interface/p7.cpp for example.
+  // NOTE: The above wording may be problematic. See
+  // https://github.com/llvm/llvm-project/issues/131058 But it is much complext
+  // to adjust it in Sema's lookup process. Now we hacked it in ASTWriter. See
+  // the comments in ASTDeclContextNameLookupTrait::getLookupVisibility.
   if (auto *TD = dyn_cast<TagDecl>(DC))
     return SemaRef.hasReachableDefinition(TD);
 

diff  --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index af7229d748872..c6487c5366a29 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4331,9 +4331,36 @@ class ASTDeclContextNameLookupTrait
       return LookupVisibility::ModuleLocalVisible;
     if (isTULocalInNamedModules(D))
       return LookupVisibility::TULocal;
+
+    // A trick to handle enum constants. The enum constants is special since
+    // they can be found directly without their parent context. This makes it
+    // tricky to decide if an EnumConstantDecl is visible or not by their own
+    // visibilities. E.g., for a class member, we can assume it is visible if
+    // the user get its parent somehow. But for an enum constant, the users may
+    // access if without its parent context. Although we can fix the problem in
+    // Sema lookup process, it might be too complex, we just make a trick here.
+    // Note that we only removes enum constant from the lookup table from its
+    // parent of parent. We DON'T remove the enum constant from its parent. So
+    // we don't need to care about merging problems here.
+    if (auto *ECD = dyn_cast<EnumConstantDecl>(D);
+        ECD && DC.isFileContext() && ECD->getOwningModule() &&
+        ECD->getTopLevelOwningNamedModule()->isNamedModule()) {
+      if (llvm::all_of(
+              DC.noload_lookup(
+                  cast<EnumDecl>(ECD->getDeclContext())->getDeclName()),
+              [](auto *Found) {
+                return Found->isInvisibleOutsideTheOwningModule();
+              }))
+        return ECD->isFromExplicitGlobalModule() ||
+                       ECD->isInAnonymousNamespace()
+                   ? LookupVisibility::TULocal
+                   : LookupVisibility::ModuleLocalVisible;
+    }
+
     return LookupVisibility::GenerallyVisibile;
   }
 
+  DeclContext &DC;
   ModuleLevelDeclsMapTy ModuleLocalDeclsMap;
   TULocalDeclsMapTy TULocalDeclsMap;
 
@@ -4341,6 +4368,9 @@ class ASTDeclContextNameLookupTrait
   using ASTDeclContextNameTrivialLookupTrait::
       ASTDeclContextNameTrivialLookupTrait;
 
+  ASTDeclContextNameLookupTrait(ASTWriter &Writer, DeclContext &DC)
+      : ASTDeclContextNameTrivialLookupTrait(Writer), DC(DC) {}
+
   template <typename Coll> data_type getData(const Coll &Decls) {
     unsigned Start = DeclIDs.size();
     for (NamedDecl *D : Decls) {
@@ -4612,7 +4642,7 @@ void ASTWriter::GenerateNameLookupTable(
   MultiOnDiskHashTableGenerator<reader::ASTDeclContextNameLookupTrait,
                                 ASTDeclContextNameLookupTrait>
       Generator;
-  ASTDeclContextNameLookupTrait Trait(*this);
+  ASTDeclContextNameLookupTrait Trait(*this, *DC);
 
   // The first step is to collect the declaration names which we need to
   // serialize into the name lookup table, and to collect them in a stable

diff  --git a/clang/test/Modules/include-after-imports-enums.cppm 
b/clang/test/Modules/include-after-imports-enums.cppm
new file mode 100644
index 0000000000000..00affd98e299f
--- /dev/null
+++ b/clang/test/Modules/include-after-imports-enums.cppm
@@ -0,0 +1,25 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t -verify 
-fsyntax-only
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-reduced-module-interface -o 
%t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t -verify 
-fsyntax-only
+
+//--- enum.h
+enum E { Value };
+
+//--- M.cppm
+module;
+#include "enum.h"
+export module M;
+auto e = Value;
+
+//--- use.cpp
+// expected-no-diagnostics
+import M;
+#include "enum.h"
+
+auto e = Value;

diff  --git a/clang/test/Modules/pr131058.cppm 
b/clang/test/Modules/pr131058.cppm
new file mode 100644
index 0000000000000..c5a626103373e
--- /dev/null
+++ b/clang/test/Modules/pr131058.cppm
@@ -0,0 +1,85 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-reduced-module-interface -o 
%t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+// RUN: %clang_cc1 -std=c++20 %t/M0.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t -DMODULE_LOCAL
+// RUN: %clang_cc1 -std=c++20 %t/M0.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+// RUN: %clang_cc1 -std=c++20 %t/M0.cppm -emit-reduced-module-interface -o 
%t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t -DMODULE_LOCAL
+// RUN: %clang_cc1 -std=c++20 %t/M0.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+// RUN: %clang_cc1 -std=c++20 %t/M2.cppm -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+// RUN: %clang_cc1 -std=c++20 %t/M2.cppm -emit-reduced-module-interface -o 
%t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+// RUN: %clang_cc1 -std=c++20 %t/M3.cppm -emit-reduced-module-interface -o 
%t/M.pcm
+// RUN: %clang_cc1 -std=c++20 %t/use2.cpp -fsyntax-only -verify 
-fprebuilt-module-path=%t
+
+//--- enum.h
+enum {    SomeName,    };
+
+//--- M.cppm
+module;
+#include "enum.h"
+export module M;
+export auto e = SomeName;
+
+//--- M0.cppm
+export module M;
+enum {    SomeName,    };
+export auto e = SomeName;
+
+//--- M0.cpp
+// expected-no-diagnostics
+module M;
+auto a = SomeName;
+
+//--- use.cpp
+import M;
+auto a = SomeName; // expected-error {{use of undeclared identifier 
'SomeName'}}
+auto b = decltype(e)::SomeName;
+
+//--- enum1.h
+extern "C++" {
+enum {    SomeName,    };
+}
+
+//--- M2.cppm
+module;
+#include "enum1.h"
+export module M;
+export auto e = SomeName;
+
+//--- enums.h
+namespace nn {
+enum E { Value };
+enum E2 { VisibleEnum };
+enum AlwaysVisibleEnums { UnconditionallyVisible };
+}
+
+//--- M3.cppm
+module;
+#include "enums.h"
+export module M;
+export namespace nn {
+    using nn::E2::VisibleEnum;
+    using nn::AlwaysVisibleEnums;
+}
+auto e1 = nn::Value;
+auto e2 = nn::VisibleEnum;
+
+//--- use2.cpp
+import M;
+auto e = nn::Value1; // expected-error {{no member named 'Value1' in namespace 
'nn'}}
+auto e2 = nn::VisibleEnum;
+auto e3 = nn::UnconditionallyVisible;


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to