Skip to content

Commit fccc6ee

Browse files
committed
[C++20] [Modules] Don't make enum constant members always visible
Close #131058 See the comments in ASTWriter.cpp:ASTDeclContextNameLookupTrait::getLookupVisibility and SemaLookup.cpp:Sema::makeMergedDefinitionVisible for details.
1 parent 4be4b82 commit fccc6ee

File tree

4 files changed

+160
-17
lines changed

4 files changed

+160
-17
lines changed

clang/lib/Sema/SemaLookup.cpp

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,21 @@ void Sema::makeMergedDefinitionVisible(NamedDecl *ND) {
15651565
if (auto *TD = dyn_cast<TemplateDecl>(ND))
15661566
for (auto *Param : *TD->getTemplateParameters())
15671567
makeMergedDefinitionVisible(Param);
1568+
1569+
// If we import a named module which contains a header, and then we include a
1570+
// header which contains a definition of enums, we will skip parsing the enums
1571+
// in the current TU. But we need to ensure the visibility of the enum
1572+
// contants, since they are able to be found with the parents of their
1573+
// parents.
1574+
if (auto *ED = dyn_cast<EnumDecl>(ND);
1575+
ED && ED->isFromGlobalModule() && !ED->isScoped()) {
1576+
for (auto *ECD : ED->enumerators()) {
1577+
ECD->setVisibleDespiteOwningModule();
1578+
DeclContext *RedeclCtx = ED->getDeclContext()->getRedeclContext();
1579+
if (RedeclCtx->lookup(ECD->getDeclName()).empty())
1580+
RedeclCtx->makeDeclVisibleInContext(ECD);
1581+
}
1582+
}
15681583
}
15691584

15701585
/// Find the module in which the given declaration was defined.
@@ -2185,22 +2200,10 @@ bool LookupResult::isAvailableForLookup(Sema &SemaRef, NamedDecl *ND) {
21852200
// Class and enumeration member names can be found by name lookup in any
21862201
// context in which a definition of the type is reachable.
21872202
//
2188-
// FIXME: The current implementation didn't consider about scope. For example,
2189-
// ```
2190-
// // m.cppm
2191-
// export module m;
2192-
// enum E1 { e1 };
2193-
// // Use.cpp
2194-
// import m;
2195-
// void test() {
2196-
// auto a = E1::e1; // Error as expected.
2197-
// auto b = e1; // Should be error. namespace-scope name e1 is not visible
2198-
// }
2199-
// ```
2200-
// For the above example, the current implementation would emit error for `a`
2201-
// correctly. However, the implementation wouldn't diagnose about `b` now.
2202-
// Since we only check the reachability for the parent only.
2203-
// See clang/test/CXX/module/module.interface/p7.cpp for example.
2203+
// NOTE: The above wording may be problematic. See
2204+
// https://github.com/llvm/llvm-project/issues/131058 But it is much complext
2205+
// to adjust it in Sema's lookup process. Now we hacked it in ASTWriter. See
2206+
// the comments in ASTDeclContextNameLookupTrait::getLookupVisibility.
22042207
if (auto *TD = dyn_cast<TagDecl>(DC))
22052208
return SemaRef.hasReachableDefinition(TD);
22062209

clang/lib/Serialization/ASTWriter.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4331,16 +4331,46 @@ class ASTDeclContextNameLookupTrait
43314331
return LookupVisibility::ModuleLocalVisible;
43324332
if (isTULocalInNamedModules(D))
43334333
return LookupVisibility::TULocal;
4334+
4335+
// A trick to handle enum constants. The enum constants is special since
4336+
// they can be found directly without their parent context. This makes it
4337+
// tricky to decide if an EnumConstantDecl is visible or not by their own
4338+
// visibilities. E.g., for a class member, we can assume it is visible if
4339+
// the user get its parent somehow. But for an enum constant, the users may
4340+
// access if without its parent context. Although we can fix the problem in
4341+
// Sema lookup process, it might be too complex, we just make a trick here.
4342+
// Note that we only removes enum constant from the lookup table from its
4343+
// parent of parent. We DON'T remove the enum constant from its parent. So
4344+
// we don't need to care about merging problems here.
4345+
if (auto *ECD = dyn_cast<EnumConstantDecl>(D);
4346+
ECD && DC.isFileContext() && ECD->getOwningModule() &&
4347+
ECD->getTopLevelOwningNamedModule()->isNamedModule()) {
4348+
if (llvm::all_of(
4349+
DC.noload_lookup(
4350+
cast<EnumDecl>(ECD->getDeclContext())->getDeclName()),
4351+
[](auto *Found) {
4352+
return Found->isInvisibleOutsideTheOwningModule();
4353+
}))
4354+
return ECD->isFromExplicitGlobalModule() ||
4355+
ECD->isInAnonymousNamespace()
4356+
? LookupVisibility::TULocal
4357+
: LookupVisibility::ModuleLocalVisible;
4358+
}
4359+
43344360
return LookupVisibility::GenerallyVisibile;
43354361
}
43364362

4363+
DeclContext &DC;
43374364
ModuleLevelDeclsMapTy ModuleLocalDeclsMap;
43384365
TULocalDeclsMapTy TULocalDeclsMap;
43394366

43404367
public:
43414368
using ASTDeclContextNameTrivialLookupTrait::
43424369
ASTDeclContextNameTrivialLookupTrait;
43434370

4371+
ASTDeclContextNameLookupTrait(ASTWriter &Writer, DeclContext &DC)
4372+
: ASTDeclContextNameTrivialLookupTrait(Writer), DC(DC) {}
4373+
43444374
template <typename Coll> data_type getData(const Coll &Decls) {
43454375
unsigned Start = DeclIDs.size();
43464376
for (NamedDecl *D : Decls) {
@@ -4612,7 +4642,7 @@ void ASTWriter::GenerateNameLookupTable(
46124642
MultiOnDiskHashTableGenerator<reader::ASTDeclContextNameLookupTrait,
46134643
ASTDeclContextNameLookupTrait>
46144644
Generator;
4615-
ASTDeclContextNameLookupTrait Trait(*this);
4645+
ASTDeclContextNameLookupTrait Trait(*this, *DC);
46164646

46174647
// The first step is to collect the declaration names which we need to
46184648
// serialize into the name lookup table, and to collect them in a stable
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: split-file %s %t
4+
//
5+
// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
6+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t -verify -fsyntax-only
7+
//
8+
// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-reduced-module-interface -o %t/M.pcm
9+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fprebuilt-module-path=%t -verify -fsyntax-only
10+
11+
//--- enum.h
12+
enum E { Value };
13+
14+
//--- M.cppm
15+
module;
16+
#include "enum.h"
17+
export module M;
18+
auto e = Value;
19+
20+
//--- use.cpp
21+
// expected-no-diagnostics
22+
import M;
23+
#include "enum.h"
24+
25+
auto e = Value;

clang/test/Modules/pr131058.cppm

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir -p %t
3+
// RUN: split-file %s %t
4+
//
5+
// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -o %t/M.pcm
6+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
7+
8+
// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-reduced-module-interface -o %t/M.pcm
9+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
10+
11+
// RUN: %clang_cc1 -std=c++20 %t/M0.cppm -emit-module-interface -o %t/M.pcm
12+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify -fprebuilt-module-path=%t -DMODULE_LOCAL
13+
// RUN: %clang_cc1 -std=c++20 %t/M0.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
14+
15+
// RUN: %clang_cc1 -std=c++20 %t/M0.cppm -emit-reduced-module-interface -o %t/M.pcm
16+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify -fprebuilt-module-path=%t -DMODULE_LOCAL
17+
// RUN: %clang_cc1 -std=c++20 %t/M0.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
18+
19+
// RUN: %clang_cc1 -std=c++20 %t/M2.cppm -emit-module-interface -o %t/M.pcm
20+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
21+
22+
// RUN: %clang_cc1 -std=c++20 %t/M2.cppm -emit-reduced-module-interface -o %t/M.pcm
23+
// RUN: %clang_cc1 -std=c++20 %t/use.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
24+
25+
// RUN: %clang_cc1 -std=c++20 %t/M3.cppm -emit-reduced-module-interface -o %t/M.pcm
26+
// RUN: %clang_cc1 -std=c++20 %t/use2.cpp -fsyntax-only -verify -fprebuilt-module-path=%t
27+
28+
//--- enum.h
29+
enum { SomeName, };
30+
31+
//--- M.cppm
32+
module;
33+
#include "enum.h"
34+
export module M;
35+
export auto e = SomeName;
36+
37+
//--- M0.cppm
38+
export module M;
39+
enum { SomeName, };
40+
export auto e = SomeName;
41+
42+
//--- M0.cpp
43+
// expected-no-diagnostics
44+
module M;
45+
auto a = SomeName;
46+
47+
//--- use.cpp
48+
import M;
49+
auto a = SomeName; // expected-error {{use of undeclared identifier 'SomeName'}}
50+
auto b = decltype(e)::SomeName;
51+
52+
//--- enum1.h
53+
extern "C++" {
54+
enum { SomeName, };
55+
}
56+
57+
//--- M2.cppm
58+
module;
59+
#include "enum1.h"
60+
export module M;
61+
export auto e = SomeName;
62+
63+
//--- enums.h
64+
namespace nn {
65+
enum E { Value };
66+
enum E2 { VisibleEnum };
67+
enum AlwaysVisibleEnums { UnconditionallyVisible };
68+
}
69+
70+
//--- M3.cppm
71+
module;
72+
#include "enums.h"
73+
export module M;
74+
export namespace nn {
75+
using nn::E2::VisibleEnum;
76+
using nn::AlwaysVisibleEnums;
77+
}
78+
auto e1 = nn::Value;
79+
auto e2 = nn::VisibleEnum;
80+
81+
//--- use2.cpp
82+
import M;
83+
auto e = nn::Value1; // expected-error {{no member named 'Value1' in namespace 'nn'}}
84+
auto e2 = nn::VisibleEnum;
85+
auto e3 = nn::UnconditionallyVisible;

0 commit comments

Comments
 (0)