[C++20] [Modules] Improve the import-and-include pattern

The import and include problem is a long-standing issue with the use of
C++20 modules. This patch tried to improve this by skipping parsing
class and functions if their declaration is already defined in modules.

The scale of the patch itself is small as the patch reuses previous
optimization. Maybe we can skip parsing other declarations in the
future. But the patch itself should be good.
This commit is contained in:
Chuanqi Xu 2025-08-19 23:27:10 +08:00
parent 78e3ab306f
commit 2db239acd1
6 changed files with 117 additions and 26 deletions

View File

@ -15328,6 +15328,16 @@ public:
NamedDecl *Hidden; NamedDecl *Hidden;
return hasVisibleDefinition(const_cast<NamedDecl *>(D), &Hidden); return hasVisibleDefinition(const_cast<NamedDecl *>(D), &Hidden);
} }
/// Determine if \p D has a definition which allows we redefine it in current
/// TU. \p Suggested is the definition that should be made visible to expose
/// the definition.
bool isRedefinitionAllowedFor(NamedDecl *D, NamedDecl **Suggested,
bool &Visible);
bool isRedefinitionAllowedFor(const NamedDecl *D, bool &Visible) {
NamedDecl *Hidden;
return isRedefinitionAllowedFor(const_cast<NamedDecl *>(D), &Hidden,
Visible);
}
/// Determine if \p D has a reachable definition. If not, suggest a /// Determine if \p D has a reachable definition. If not, suggest a
/// declaration that should be made reachable to expose the definition. /// declaration that should be made reachable to expose the definition.

View File

@ -15837,17 +15837,18 @@ Sema::CheckForFunctionRedefinition(FunctionDecl *FD,
if (TypoCorrectedFunctionDefinitions.count(Definition)) if (TypoCorrectedFunctionDefinitions.count(Definition))
return; return;
// If we don't have a visible definition of the function, and it's inline or bool DefinitionVisible = false;
// a template, skip the new definition. if (SkipBody && isRedefinitionAllowedFor(Definition, DefinitionVisible) &&
if (SkipBody && !hasVisibleDefinition(Definition) &&
(Definition->getFormalLinkage() == Linkage::Internal || (Definition->getFormalLinkage() == Linkage::Internal ||
Definition->isInlined() || Definition->getDescribedFunctionTemplate() || Definition->isInlined() || Definition->getDescribedFunctionTemplate() ||
Definition->getNumTemplateParameterLists())) { Definition->getNumTemplateParameterLists())) {
SkipBody->ShouldSkip = true; SkipBody->ShouldSkip = true;
SkipBody->Previous = const_cast<FunctionDecl*>(Definition); SkipBody->Previous = const_cast<FunctionDecl*>(Definition);
if (!DefinitionVisible) {
if (auto *TD = Definition->getDescribedFunctionTemplate()) if (auto *TD = Definition->getDescribedFunctionTemplate())
makeMergedDefinitionVisible(TD); makeMergedDefinitionVisible(TD);
makeMergedDefinitionVisible(const_cast<FunctionDecl *>(Definition)); makeMergedDefinitionVisible(const_cast<FunctionDecl *>(Definition));
}
return; return;
} }
@ -18196,7 +18197,9 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
// However, ensure the decl passes the structural compatibility // However, ensure the decl passes the structural compatibility
// check in C11 6.2.7/1 (or 6.1.2.6/1 in C89). // check in C11 6.2.7/1 (or 6.1.2.6/1 in C89).
NamedDecl *Hidden = nullptr; NamedDecl *Hidden = nullptr;
if (SkipBody && (!hasVisibleDefinition(Def, &Hidden) || bool HiddenDefVisible = false;
if (SkipBody &&
(isRedefinitionAllowedFor(Def, &Hidden, HiddenDefVisible) ||
getLangOpts().C23)) { getLangOpts().C23)) {
// There is a definition of this tag, but it is not visible. // There is a definition of this tag, but it is not visible.
// We explicitly make use of C++'s one definition rule here, // We explicitly make use of C++'s one definition rule here,
@ -18213,13 +18216,15 @@ Sema::ActOnTag(Scope *S, unsigned TagSpec, TagUseKind TUK, SourceLocation KWLoc,
ProcessDeclAttributeList(S, SkipBody->New, Attrs); ProcessDeclAttributeList(S, SkipBody->New, Attrs);
return Def; return Def;
} else { }
SkipBody->ShouldSkip = true; SkipBody->ShouldSkip = true;
SkipBody->Previous = Def; SkipBody->Previous = Def;
if (!HiddenDefVisible && Hidden)
makeMergedDefinitionVisible(Hidden); makeMergedDefinitionVisible(Hidden);
// Carry on and handle it like a normal definition. We'll // Carry on and handle it like a normal definition. We'll
// skip starting the definition later. // skip starting the definition later.
}
} else if (!IsExplicitSpecializationAfterInstantiation) { } else if (!IsExplicitSpecializationAfterInstantiation) {
// A redeclaration in function prototype scope in C isn't // A redeclaration in function prototype scope in C isn't
// visible elsewhere, so merely issue a warning. // visible elsewhere, so merely issue a warning.
@ -20842,3 +20847,13 @@ bool Sema::shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee) {
return LangOpts.CUDA && !LangOpts.CUDAIsDevice && return LangOpts.CUDA && !LangOpts.CUDAIsDevice &&
CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global; CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global;
} }
bool Sema::isRedefinitionAllowedFor(NamedDecl *D, NamedDecl **Suggested,
bool &Visible) {
Visible = hasVisibleDefinition(D, Suggested);
// The redefinition of D in the **current** TU is allowed if D is invisible or
// D is defined in the global module of other module units. We didn't check if
// it is in global module as, we'll check the redefinition in named module
// later with better diagnostic message.
return D->isInAnotherModuleUnit() || !Visible;
}

View File

@ -2078,14 +2078,19 @@ DeclResult Sema::CheckClassTemplate(
// If we have a prior definition that is not visible, treat this as // If we have a prior definition that is not visible, treat this as
// simply making that previous definition visible. // simply making that previous definition visible.
NamedDecl *Hidden = nullptr; NamedDecl *Hidden = nullptr;
if (SkipBody && !hasVisibleDefinition(Def, &Hidden)) { bool HiddenDefVisible = false;
if (SkipBody &&
isRedefinitionAllowedFor(Def, &Hidden, HiddenDefVisible)) {
SkipBody->ShouldSkip = true; SkipBody->ShouldSkip = true;
SkipBody->Previous = Def; SkipBody->Previous = Def;
auto *Tmpl = cast<CXXRecordDecl>(Hidden)->getDescribedClassTemplate(); if (!HiddenDefVisible && Hidden) {
auto *Tmpl =
cast<CXXRecordDecl>(Hidden)->getDescribedClassTemplate();
assert(Tmpl && "original definition of a class template is not a " assert(Tmpl && "original definition of a class template is not a "
"class template?"); "class template?");
makeMergedDefinitionVisible(Hidden); makeMergedDefinitionVisible(Hidden);
makeMergedDefinitionVisible(Tmpl); makeMergedDefinitionVisible(Tmpl);
}
} else { } else {
Diag(NameLoc, diag::err_redefinition) << Name; Diag(NameLoc, diag::err_redefinition) << Name;
Diag(Def->getLocation(), diag::note_previous_definition); Diag(Def->getLocation(), diag::note_previous_definition);
@ -8956,9 +8961,12 @@ DeclResult Sema::ActOnClassTemplateSpecialization(
if (TUK == TagUseKind::Definition) { if (TUK == TagUseKind::Definition) {
RecordDecl *Def = Specialization->getDefinition(); RecordDecl *Def = Specialization->getDefinition();
NamedDecl *Hidden = nullptr; NamedDecl *Hidden = nullptr;
if (Def && SkipBody && !hasVisibleDefinition(Def, &Hidden)) { bool HiddenDefVisible = false;
if (Def && SkipBody &&
isRedefinitionAllowedFor(Def, &Hidden, HiddenDefVisible)) {
SkipBody->ShouldSkip = true; SkipBody->ShouldSkip = true;
SkipBody->Previous = Def; SkipBody->Previous = Def;
if (!HiddenDefVisible && Hidden)
makeMergedDefinitionVisible(Hidden); makeMergedDefinitionVisible(Hidden);
} else if (Def) { } else if (Def) {
SourceRange Range(TemplateNameLoc, RAngleLoc); SourceRange Range(TemplateNameLoc, RAngleLoc);

View File

@ -46,8 +46,8 @@ extern S2 *q2;
// FIXME: This is well-formed, because [basic.def.odr]/15 is satisfied. // FIXME: This is well-formed, because [basic.def.odr]/15 is satisfied.
struct S3 {}; struct S3 {};
// since-cxx20-error@-1 {{redefinition of 'S3'}} // since-cxx20-error@-1 {{declaration of 'S3' in the global module follows declaration in module cwg279_A}}
// since-cxx20-note@cwg279_A.cppm:23 {{previous definition is here}} // since-cxx20-note@cwg279_A.cppm:23 {{previous declaration is here}}
extern S3 *q3; extern S3 *q3;
// since-cxx20-error@-1 {{declaration of 'q3' in the global module follows declaration in module cwg279_A}} // since-cxx20-error@-1 {{declaration of 'q3' in the global module follows declaration in module cwg279_A}}
// since-cxx20-note@cwg279_A.cppm:24 {{previous declaration is here}} // since-cxx20-note@cwg279_A.cppm:24 {{previous declaration is here}}

View File

@ -33,8 +33,8 @@ int v2; // expected-error {{declaration of 'v2' in the global module follows dec
// expected-note@foo.cppm:6 {{previous declaration is here}} // expected-note@foo.cppm:6 {{previous declaration is here}}
template <typename T> template <typename T>
class my_array {}; // expected-error {{redefinition of 'my_array'}} class my_array {}; // expected-error {{declaration of 'my_array' in the global module follows declaration in module foo}}
// expected-note@foo.cppm:9 {{previous definition is here}} // expected-note@foo.cppm:9 {{previous declaration is here}}
template <template <typename> typename C = my_array> template <template <typename> typename C = my_array>
int v3; // expected-error {{declaration of 'v3' in the global module follows declaration in module foo}} int v3; // expected-error {{declaration of 'v3' in the global module follows declaration in module foo}}

View File

@ -0,0 +1,58 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: %clang_cc1 -std=c++20 %t/a.cppm -emit-reduced-module-interface -o %t/a.pcm
// RUN: %clang_cc1 -std=c++20 %t/a.cpp -fmodule-file=a=%t/a.pcm -ast-dump | FileCheck %s
// RUN: %clang_cc1 -std=c++20 %t/a.cppm -emit-module-interface -o %t/a.pcm
// RUN: %clang_cc1 -std=c++20 %t/a.cpp -fmodule-file=a=%t/a.pcm -ast-dump | FileCheck %s
//--- a.h
namespace a {
class A {
public:
int aaaa;
int get() {
return aaaa;
}
};
template <class T>
class B {
public:
B(T t): t(t) {}
T t;
};
using BI = B<int>;
inline int get(A a, BI b) {
return a.get() + b.t;
}
}
//--- a.cppm
export module a;
export extern "C++" {
#include "a.h"
}
//--- a.cpp
import a;
#include "a.h"
int test() {
a::A aa;
a::BI bb(43);
return get(aa, bb);
}
// CHECK-NOT: DefinitionData
// CHECK: FunctionDecl {{.*}} get 'int (A, BI)' {{.*}}
// CHECK-NOT: CompoundStmt
// CHECK: FunctionDecl {{.*}} test {{.*}}