Skip to content

Commit 4d9251b

Browse files
committed
[C++20] [Modules] Merge same concept decls in global module fragment
According to [basic.def.odr]p14, the same redeclarations in different TU but not attached to a named module are allowed. But we didn't take care of concept decl for this condition. This patch tries to fix this problem. Reviewed By: ilya-biryukov Differention Revision: https://reviews.llvm.org/D130614
1 parent a35c64c commit 4d9251b

File tree

5 files changed

+269
-2
lines changed

5 files changed

+269
-2
lines changed

clang/include/clang/Basic/Module.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,11 @@ class Module {
525525
Parent->SubModules.push_back(this);
526526
}
527527

528+
/// Is this module have similar semantics as headers.
529+
bool isHeaderLikeModule() const {
530+
return isModuleMapModule() || isHeaderUnit();
531+
}
532+
528533
/// Is this a module partition.
529534
bool isModulePartition() const {
530535
return Kind == ModulePartitionInterface ||

clang/include/clang/Sema/Sema.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4479,6 +4479,8 @@ class Sema final {
44794479
bool CheckRedeclarationModuleOwnership(NamedDecl *New, NamedDecl *Old);
44804480
bool CheckRedeclarationExported(NamedDecl *New, NamedDecl *Old);
44814481
bool CheckRedeclarationInModule(NamedDecl *New, NamedDecl *Old);
4482+
bool IsRedefinitionInModule(const NamedDecl *New,
4483+
const NamedDecl *Old) const;
44824484

44834485
void DiagnoseAmbiguousLookup(LookupResult &Result);
44844486
//@}

clang/lib/Sema/SemaDecl.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,80 @@ bool Sema::CheckRedeclarationInModule(NamedDecl *New, NamedDecl *Old) {
17191719
return false;
17201720
}
17211721

1722+
// Check the redefinition in C++20 Modules.
1723+
//
1724+
// [basic.def.odr]p14:
1725+
// For any definable item D with definitions in multiple translation units,
1726+
// - if D is a non-inline non-templated function or variable, or
1727+
// - if the definitions in different translation units do not satisfy the
1728+
// following requirements,
1729+
// the program is ill-formed; a diagnostic is required only if the definable
1730+
// item is attached to a named module and a prior definition is reachable at
1731+
// the point where a later definition occurs.
1732+
// - Each such definition shall not be attached to a named module
1733+
// ([module.unit]).
1734+
// - Each such definition shall consist of the same sequence of tokens, ...
1735+
// ...
1736+
//
1737+
// Return true if the redefinition is not allowed. Return false otherwise.
1738+
bool Sema::IsRedefinitionInModule(const NamedDecl *New,
1739+
const NamedDecl *Old) const {
1740+
assert(getASTContext().isSameEntity(New, Old) &&
1741+
"New and Old are not the same definition, we should diagnostic it "
1742+
"immediately instead of checking it.");
1743+
assert(const_cast<Sema *>(this)->isReachable(New) &&
1744+
const_cast<Sema *>(this)->isReachable(Old) &&
1745+
"We shouldn't see unreachable definitions here.");
1746+
1747+
Module *NewM = New->getOwningModule();
1748+
Module *OldM = Old->getOwningModule();
1749+
1750+
// We only checks for named modules here. The header like modules is skipped.
1751+
// FIXME: This is not right if we import the header like modules in the module
1752+
// purview.
1753+
//
1754+
// For example, assuming "header.h" provides definition for `D`.
1755+
// ```C++
1756+
// //--- M.cppm
1757+
// export module M;
1758+
// import "header.h"; // or #include "header.h" but import it by clang modules
1759+
// actually.
1760+
//
1761+
// //--- Use.cpp
1762+
// import M;
1763+
// import "header.h"; // or uses clang modules.
1764+
// ```
1765+
//
1766+
// In this case, `D` has multiple definitions in multiple TU (M.cppm and
1767+
// Use.cpp) and `D` is attached to a named module `M`. The compiler should
1768+
// reject it. But the current implementation couldn't detect the case since we
1769+
// don't record the information about the importee modules.
1770+
//
1771+
// But this might not be painful in practice. Since the design of C++20 Named
1772+
// Modules suggests us to use headers in global module fragment instead of
1773+
// module purview.
1774+
if (NewM && NewM->isHeaderLikeModule())
1775+
NewM = nullptr;
1776+
if (OldM && OldM->isHeaderLikeModule())
1777+
OldM = nullptr;
1778+
1779+
if (!NewM && !OldM)
1780+
return true;
1781+
1782+
// [basic.def.odr]p14.3
1783+
// Each such definition shall not be attached to a named module
1784+
// ([module.unit]).
1785+
if ((NewM && NewM->isModulePurview()) || (OldM && OldM->isModulePurview()))
1786+
return true;
1787+
1788+
// Then New and Old lives in the same TU if their share one same module unit.
1789+
if (NewM)
1790+
NewM = NewM->getTopLevelModule();
1791+
if (OldM)
1792+
OldM = OldM->getTopLevelModule();
1793+
return OldM == NewM;
1794+
}
1795+
17221796
static bool isUsingDecl(NamedDecl *D) {
17231797
return isa<UsingShadowDecl>(D) ||
17241798
isa<UnresolvedUsingTypenameDecl>(D) ||

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8724,7 +8724,7 @@ void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
87248724
if (Previous.empty())
87258725
return;
87268726

8727-
auto *OldConcept = dyn_cast<ConceptDecl>(Previous.getRepresentativeDecl());
8727+
auto *OldConcept = dyn_cast<ConceptDecl>(Previous.getRepresentativeDecl()->getUnderlyingDecl());
87288728
if (!OldConcept) {
87298729
auto *Old = Previous.getRepresentativeDecl();
87308730
Diag(NewDecl->getLocation(), diag::err_redefinition_different_kind)
@@ -8742,7 +8742,8 @@ void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
87428742
AddToScope = false;
87438743
return;
87448744
}
8745-
if (hasReachableDefinition(OldConcept)) {
8745+
if (hasReachableDefinition(OldConcept) &&
8746+
IsRedefinitionInModule(NewDecl, OldConcept)) {
87468747
Diag(NewDecl->getLocation(), diag::err_redefinition)
87478748
<< NewDecl->getDeclName();
87488749
notePreviousDefinition(OldConcept, NewDecl->getLocation());
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// RUN: rm -rf %t
2+
// RUN: mkdir %t
3+
// RUN: split-file %s %t
4+
//
5+
// RUN: %clang_cc1 -std=c++20 -emit-module-interface -I%t %t/A.cppm -o %t/A.pcm
6+
// RUN: %clang_cc1 -std=c++20 -emit-module-interface -I%t %t/B.cppm -o %t/B.pcm
7+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -verify -fsyntax-only
8+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use2.cpp -verify -fsyntax-only
9+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use3.cpp -verify -fsyntax-only
10+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use4.cpp -verify -fsyntax-only
11+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/C.cppm -verify -fsyntax-only
12+
// RUN: %clang_cc1 -std=c++20 -I%t %t/D.cppm -verify -fsyntax-only
13+
// RUN: %clang_cc1 -std=c++20 -I%t %t/E.cppm -verify -fsyntax-only
14+
// RUN: %clang_cc1 -std=c++20 -I%t %t/F.cppm -verify -fsyntax-only
15+
//
16+
// Testing header units for coverity.
17+
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header %t/foo.h -o %t/foo.pcm
18+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -fmodule-file=%t/foo.pcm %t/Use5.cpp -verify -fsyntax-only
19+
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -fmodule-file=%t/foo.pcm %t/Use6.cpp -verify -fsyntax-only
20+
//
21+
// Testing with module map modules. It is unclear about the relation ship between Clang modules and
22+
// C++20 Named Modules. Will they coexist? Or will they be mutually exclusive?
23+
// The test here is for primarily coverity.
24+
//
25+
// RUN: rm -f %t/foo.pcm
26+
// RUN: %clang_cc1 -std=c++20 -fmodules -fmodules-cache-path=%t -fprebuilt-module-path=%t \
27+
// RUN: -fmodule-map-file=%t/module.map %t/Use7.cpp -verify -fsyntax-only
28+
// RUN: %clang_cc1 -std=c++20 -fmodules -fmodules-cache-path=%t -fprebuilt-module-path=%t \
29+
// RUN: -fmodule-map-file=%t/module.map %t/Use7.cpp -verify -fsyntax-only
30+
// Testing module map modules with named modules.
31+
// RUN: %clang_cc1 -std=c++20 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%t/module.map \
32+
// RUN: %t/A.cppm -o %t/A.pcm
33+
// RUN: %clang_cc1 -std=c++20 -fmodules -fmodules-cache-path=%t -fprebuilt-module-path=%t \
34+
// RUN: -fmodule-map-file=%t/module.map %t/Use7.cpp -verify -fsyntax-only
35+
// RUN: %clang_cc1 -std=c++20 -fmodules -fmodules-cache-path=%t -fprebuilt-module-path=%t \
36+
// RUN: -fmodule-map-file=%t/module.map %t/Use7.cpp -verify -fsyntax-only
37+
38+
//
39+
//--- foo.h
40+
#ifndef FOO_H
41+
#define FOO_H
42+
template <class T, class U>
43+
concept same_as = __is_same(T, U);
44+
#endif
45+
46+
// The compiler would warn if we include foo_h twice without guard.
47+
//--- redecl.h
48+
#ifndef REDECL_H
49+
#define REDECL_H
50+
template <class T, class U>
51+
concept same_as = __is_same(T, U);
52+
#endif
53+
54+
//--- A.cppm
55+
module;
56+
#include "foo.h"
57+
export module A;
58+
export using ::same_as;
59+
60+
//--- B.cppm
61+
module;
62+
#include "foo.h"
63+
export module B;
64+
export using ::same_as;
65+
66+
//--- Use.cpp
67+
// expected-no-diagnostics
68+
import A;
69+
import B;
70+
71+
template <class T> void foo()
72+
requires same_as<T, int>
73+
{}
74+
75+
//--- Use2.cpp
76+
// expected-no-diagnostics
77+
#include "foo.h"
78+
import A;
79+
80+
template <class T> void foo()
81+
requires same_as<T, int>
82+
{}
83+
84+
//--- Use3.cpp
85+
// expected-no-diagnostics
86+
import A;
87+
#include "foo.h"
88+
89+
template <class T> void foo()
90+
requires same_as<T, int>
91+
{}
92+
93+
//--- Use4.cpp
94+
// expected-no-diagnostics
95+
import A;
96+
import B;
97+
#include "foo.h"
98+
99+
template <class T> void foo()
100+
requires same_as<T, int>
101+
{}
102+
103+
//--- C.cppm
104+
// expected-no-diagnostics
105+
module;
106+
#include "foo.h"
107+
export module C;
108+
import A;
109+
import B;
110+
111+
template <class T> void foo()
112+
requires same_as<T, int>
113+
{}
114+
115+
//--- D.cppm
116+
module;
117+
#include "foo.h"
118+
#include "redecl.h"
119+
export module D;
120+
export using ::same_as;
121+
122+
// expected-error@* {{redefinition of 'same_as'}}
123+
// expected-note@* 1+{{previous definition is here}}
124+
125+
//--- E.cppm
126+
module;
127+
#include "foo.h"
128+
export module E;
129+
export template <class T, class U>
130+
concept same_as = __is_same(T, U);
131+
132+
// expected-error@* {{redefinition of 'same_as'}}
133+
// expected-note@* 1+{{previous definition is here}}
134+
135+
//--- F.cppm
136+
export module F;
137+
template <class T, class U>
138+
concept same_as = __is_same(T, U);
139+
template <class T, class U>
140+
concept same_as = __is_same(T, U);
141+
142+
// expected-error@* {{redefinition of 'same_as'}}
143+
// expected-note@* 1+{{previous definition is here}}
144+
145+
//--- Use5.cpp
146+
// expected-no-diagnostics
147+
import "foo.h";
148+
import A;
149+
150+
template <class T> void foo()
151+
requires same_as<T, int>
152+
{}
153+
154+
//--- Use6.cpp
155+
// expected-no-diagnostics
156+
import A;
157+
import "foo.h";
158+
159+
template <class T> void foo()
160+
requires same_as<T, int>
161+
{}
162+
163+
//--- module.map
164+
module "foo" {
165+
export *
166+
header "foo.h"
167+
}
168+
169+
//--- Use7.cpp
170+
// expected-no-diagnostics
171+
#include "foo.h"
172+
import A;
173+
174+
template <class T> void foo()
175+
requires same_as<T, int>
176+
{}
177+
178+
//--- Use8.cpp
179+
// expected-no-diagnostics
180+
import A;
181+
#include "foo.h"
182+
183+
template <class T> void foo()
184+
requires same_as<T, int>
185+
{}

0 commit comments

Comments
 (0)