[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
This commit is contained in:
parent
9a764ffeb6
commit
fee3cccc6c
|
@ -11141,6 +11141,8 @@ def err_export_using_internal : Error<
|
||||||
def err_export_not_in_module_interface : Error<
|
def err_export_not_in_module_interface : Error<
|
||||||
"export declaration can only be used within a module interface unit"
|
"export declaration can only be used within a module interface unit"
|
||||||
"%select{ after the module declaration|}0">;
|
"%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<
|
def err_export_partition_impl : Error<
|
||||||
"module partition implementations cannot be exported">;
|
"module partition implementations cannot be exported">;
|
||||||
def err_export_in_private_module_fragment : Error<
|
def err_export_in_private_module_fragment : Error<
|
||||||
|
|
|
@ -2258,6 +2258,11 @@ private:
|
||||||
/// Namespace definitions that we will export when they finish.
|
/// Namespace definitions that we will export when they finish.
|
||||||
llvm::SmallPtrSet<const NamespaceDecl*, 8> DeferredExportedNamespaces;
|
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.
|
/// Helper function to judge if we are in module purview.
|
||||||
/// Return false if we are not in a module.
|
/// Return false if we are not in a module.
|
||||||
bool isCurrentModulePurview() const {
|
bool isCurrentModulePurview() const {
|
||||||
|
|
|
@ -1251,6 +1251,33 @@ void Sema::ActOnEndOfTranslationUnit() {
|
||||||
emitAndClearUnusedLocalTypedefWarnings();
|
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:
|
// C99 6.9.2p2:
|
||||||
// A declaration of an identifier for an object that has file
|
// A declaration of an identifier for an object that has file
|
||||||
// scope without an initializer, and without a storage-class
|
// scope without an initializer, and without a storage-class
|
||||||
|
|
|
@ -9838,6 +9838,19 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
|
||||||
NewFD->setType(Context.getFunctionType(
|
NewFD->setType(Context.getFunctionType(
|
||||||
FPT->getReturnType(), FPT->getParamTypes(),
|
FPT->getReturnType(), FPT->getParamTypes(),
|
||||||
FPT->getExtProtoInfo().withExceptionSpec(EST_BasicNoexcept)));
|
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.
|
// Filter out previous declarations that don't match the scope.
|
||||||
|
|
|
@ -5691,7 +5691,7 @@ void Sema::diagnoseMissingImport(SourceLocation UseLoc, NamedDecl *Decl,
|
||||||
llvm::SmallVector<Module*, 8> UniqueModules;
|
llvm::SmallVector<Module*, 8> UniqueModules;
|
||||||
llvm::SmallDenseSet<Module*, 8> UniqueModuleSet;
|
llvm::SmallDenseSet<Module*, 8> UniqueModuleSet;
|
||||||
for (auto *M : Modules) {
|
for (auto *M : Modules) {
|
||||||
if (M->Kind == Module::GlobalModuleFragment)
|
if (M->isGlobalModule() || M->isPrivateModule())
|
||||||
continue;
|
continue;
|
||||||
if (UniqueModuleSet.insert(M).second)
|
if (UniqueModuleSet.insert(M).second)
|
||||||
UniqueModules.push_back(M);
|
UniqueModules.push_back(M);
|
||||||
|
|
|
@ -910,6 +910,17 @@ Decl *Sema::ActOnFinishExportDecl(Scope *S, Decl *D, SourceLocation RBraceLoc) {
|
||||||
diagExportedUnnamedDecl(*this, UnnamedDeclKind::Context, Child,
|
diagExportedUnnamedDecl(*this, UnnamedDeclKind::Context, Child,
|
||||||
BlockStart);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,25 @@
|
||||||
// RUN: mkdir -p %t
|
// RUN: mkdir -p %t
|
||||||
// RUN: split-file %s %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 %t/Private.cppm -emit-module-interface \
|
||||||
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/Use.cpp -verify -fsyntax-only
|
// 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
|
//--- Private.cppm
|
||||||
export module Private;
|
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@Private.cppm:13 {{private module fragment begins here}}
|
||||||
|
#endif
|
||||||
static void fn_s();
|
static void fn_s();
|
||||||
export struct X;
|
export struct X;
|
||||||
|
|
||||||
export void g(X *x) {
|
export void g(X *x) {
|
||||||
fn_s(); // OK, call to static function in same translation unit
|
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
|
export X *factory(); // OK
|
||||||
|
|
||||||
|
@ -30,10 +37,8 @@ void fn_s() {}
|
||||||
//--- Use.cpp
|
//--- Use.cpp
|
||||||
import Private;
|
import Private;
|
||||||
void foo() {
|
void foo() {
|
||||||
X x; // expected-error {{definition of 'X' must be imported from module 'Private.<private>' before it is required}}
|
X x; // expected-error 1+{{missing '#include'; 'X' must be defined before it is used}}
|
||||||
// expected-error@-1 {{definition of 'X' must be imported from module 'Private.<private>' before it is required}}
|
// expected-note@Private.cppm:18 1+{{definition here is not reachable}}
|
||||||
// expected-note@* {{definition here is not reachable}}
|
|
||||||
// expected-note@* {{definition here is not reachable}}
|
|
||||||
auto _ = factory();
|
auto _ = factory();
|
||||||
auto *__ = factory();
|
auto *__ = factory();
|
||||||
X *___ = factory();
|
X *___ = factory();
|
||||||
|
|
|
@ -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@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@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@std-10-5-ex1-interface.cpp:22 1+{{definition here is not reachable}}
|
||||||
|
X *p = factory();
|
||||||
|
}
|
Loading…
Reference in New Issue