Support 'IWYU pragma: export' for forward declarations

Covers both begin_export/end_export blocks and single-line export
pragmas.

Like with 'IWYU pragma: keep' marks the forward decl as automatically
desired to avoid removing manually exported but unused decls.

Add a simple testcase and update documentation.
This commit is contained in:
Kim Gräsman 2022-12-11 16:50:33 +01:00
parent 2dc0645882
commit bd305afe7d
9 changed files with 138 additions and 8 deletions

View File

@ -31,31 +31,37 @@ In the provided case nothing within the bounds of `begin_keep` and `end_keep` wi
## IWYU pragma: export ##
This pragma applies to a single `#include` directive. It says that the current file is to be considered the provider of any symbol from the included file.
This pragma applies to a single `#include` directive or forward declaration. It says that the current file is to be considered the provider of any symbol from the included file or declaration.
facade.h:
#include "detail/constants.h" // IWYU pragma: export
#include "detail/types.h" // IWYU pragma: export
#include <vector> // don't export stuff from <vector>
class Other; // IWYU pragma: export
main.cc:
#include "facade.h"
// Assuming Thing comes from detail/types.h and MAX_THINGS from detail/constants.h
std::vector<Thing> things(MAX_THINGS);
Here, since `detail/constants.h` and `detail/types.h` have both been exported, IWYU is happy with the `facade.h` include for `Thing` and `MAX_THINGS`.
// Satisfied with forward-declaration from facade.h
void foo(Other* thing);
Here, since `detail/constants.h`, `detail/types.h` and `Other` have all been exported, IWYU is happy with the `facade.h` include for `Thing` and `MAX_THINGS` and does not suggest a local forward declaration for `Other`.
In contrast, since `<vector>` has not been exported from `facade.h`, it will be suggested as an additional include.
## IWYU pragma: begin_exports/end_exports ##
This pragma applies to a set of `#include` directives. It declares that the including file is to be considered the provider of any symbol from these included files. This is the same as decorating every `#include` directive with `IWYU pragma: export`.
This pragma applies to a set of `#include` directives or forward declarations. It declares that the including file is to be considered the provider any symbol from contained included files or declarations. This is the same as decorating every line with `IWYU pragma: export`.
facade.h:
// IWYU pragma: begin_exports
#include "detail/constants.h"
#include "detail/types.h"
class Other;
// IWYU pragma: end_exports
#include <vector> // don't export stuff from <vector>

View File

@ -251,13 +251,13 @@ and forward declarations instead of a single line.
.B // IWYU pragma: export
Used after an
.B #include
directive it indicates that the current file is considered to be a provider of
the included file.
directive or forward declaration it indicates that the current file is
considered to be a provider of any symbol from the included file or declaration.
.TP
.BR "// IWYU pragma: begin_exports" , " // IWYU pragma: end_exports"
Has the same effect as the previous pragma comment, but applies to a range of
.BR #include s
instead of a single line.
or forward declarations instead of a single line.
.TP
.BR "// IWYU pragma: private" [ ", include \fIheader" ]
Indicates that the current file is considered private,

View File

@ -3989,6 +3989,8 @@ class IwyuAstConsumer
}
} else if (preprocessor_info().ForwardDeclareIsMarkedKeep(decl)) {
definitely_keep_fwd_decl = true;
} else if (preprocessor_info().ForwardDeclareIsExported(decl)) {
definitely_keep_fwd_decl = true;
}
preprocessor_info().FileInfoFor(CurrentFileEntry())->AddForwardDeclare(

View File

@ -981,7 +981,9 @@ set<string> CalculateMinimalIncludes(
// A6) If any of the redeclarations of this declaration is in the same
// file as the use (and before it), and is actually a definition,
// discard the forward-declare use.
// A7) If --no_fwd_decls has been passed, recategorize as a full use.
// A7) If any redeclaration is marked with IWYU pragma: export, mark as a
// full use of this decl to keep its containing file included.
// A8) If --no_fwd_decls has been passed, recategorize as a full use.
// Trimming symbol uses (1st pass):
// B1) If the definition of a full use comes after the use, change the
@ -1143,7 +1145,20 @@ void ProcessForwardDeclare(OneUse* use,
}
}
// (A7) If --no_fwd_decls has been passed, and a decl can be found in one of
// (A7) If any arbitrary redeclaration is marked with IWYU pragma: export,
// reset use as a full use of this decl to keep its containing file included.
if (!use->is_full_use()) {
for (const Decl* redecl : use->decl()->redecls()) {
const auto* decl = cast<NamedDecl>(redecl);
if (preprocessor_info->ForwardDeclareIsExported(decl)) {
use->reset_decl(decl);
use->set_full_use();
break;
}
}
}
// (A8) If --no_fwd_decls has been passed, and a decl can be found in one of
// the headers, suggest that header, and recategorize as a full use. If we can
// only find a decl in this file, it must be a self-sufficent decl being used,
// so we can just let IWYU do its work, and there is no need to recategorize.

View File

@ -209,7 +209,11 @@ void IwyuPreprocessorInfo::HandlePragmaComment(SourceRange comment_range) {
if (HasOpenBeginExports(this_file_entry)) {
if (MatchOneToken(tokens, "end_exports", 1, begin_loc)) {
ERRSYM(this_file_entry) << "end_exports pragma seen\n";
SourceLocation export_loc_begin = begin_exports_location_stack_.top();
begin_exports_location_stack_.pop();
SourceRange export_range(export_loc_begin, begin_loc);
export_location_ranges_.insert(
std::make_pair(this_file_entry, export_range));
} else {
// No pragma allowed within "begin_exports" - "end_exports"
Warn(begin_loc, "Expected end_exports pragma");
@ -1142,4 +1146,22 @@ bool IwyuPreprocessorInfo::ForwardDeclareIsMarkedKeep(
return (LineHasText(loc, "// IWYU pragma: keep") ||
LineHasText(loc, "/* IWYU pragma: keep"));
}
bool IwyuPreprocessorInfo::ForwardDeclareIsExported(
const NamedDecl* decl) const {
// Use end-location so that any trailing comments match only on the last line.
SourceLocation loc = decl->getEndLoc();
// Is the decl part of a begin_exports/end_exports block?
const FileEntry* file = GetFileEntry(loc);
auto export_ranges = export_location_ranges_.equal_range(file);
for (auto it = export_ranges.first; it != export_ranges.second; ++it) {
if (it->second.fullyContains(loc)) {
return true;
}
}
// Is the declaration itself marked with trailing comment?
return (LineHasText(loc, "// IWYU pragma: export") ||
LineHasText(loc, "/* IWYU pragma: export"));
}
} // namespace include_what_you_use

View File

@ -172,6 +172,9 @@ class IwyuPreprocessorInfo : public clang::PPCallbacks,
// Return true if the fwd decl is marked with "IWYU pragma: keep".
bool ForwardDeclareIsMarkedKeep(const clang::NamedDecl* decl) const;
// Return true if the fwd decl is marked with "IWYU pragma: export".
bool ForwardDeclareIsExported(const clang::NamedDecl* decl) const;
protected:
// Preprocessor event handlers called by Clang.
void MacroExpands(const clang::Token& macro_use_token,
@ -365,6 +368,10 @@ class IwyuPreprocessorInfo : public clang::PPCallbacks,
// every keep range.
multimap<const clang::FileEntry*, clang::SourceRange> keep_location_ranges_;
// For processing forward decls. It is a multimap containing the bounds of
// every export range.
multimap<const clang::FileEntry*, clang::SourceRange> export_location_ranges_;
// For processing associated pragma. It is the current open
// "associated" pragma.
clang::SourceLocation associated_pragma_location_;

View File

@ -0,0 +1,14 @@
//===--- pragma_export_fwd-d1.h - test input file for iwyu ----------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
class FwdDecl3; // IWYU pragma: export
// IWYU pragma: begin_exports
class FwdDecl4;
// IWYU pragma: end_exports

View File

@ -0,0 +1,31 @@
//===--- pragma_export_fwd.cc - test input file for iwyu ------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// IWYU_ARGS: -I .
#include "tests/cxx/pragma_export_fwd.h"
// Uses do not trigger warnings as they are already provided for.
void a(const FwdDecl1&);
void b(const FwdDecl2*);
void c(const FwdDecl3&);
void d(const FwdDecl4*);
/**** IWYU_SUMMARY
tests/cxx/pragma_export_fwd.cc should add these lines:
#include "tests/cxx/pragma_export_fwd-d1.h"
tests/cxx/pragma_export_fwd.cc should remove these lines:
The full include-list for tests/cxx/pragma_export_fwd.cc:
#include "tests/cxx/pragma_export_fwd.h"
#include "tests/cxx/pragma_export_fwd-d1.h" // for FwdDecl3, FwdDecl4
***** IWYU_SUMMARY */

View File

@ -0,0 +1,33 @@
//===--- pragma_export_fwd.h - test input file for iwyu -------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Forward declarations exported from this header are not used here, so it will
// be suggested for removal.
#include "tests/cxx/pragma_export_fwd-d1.h"
// Unused forward declarations in associated header would normally be moved to
// main source file. Make sure they are left alone when exported.
class FwdDecl1; // IWYU pragma: export
// IWYU pragma: begin_exports
class FwdDecl2;
// IWYU pragma: end_exports
/**** IWYU_SUMMARY
tests/cxx/pragma_export_fwd.h should add these lines:
tests/cxx/pragma_export_fwd.h should remove these lines:
- #include "tests/cxx/pragma_export_fwd-d1.h" // lines XX-XX
The full include-list for tests/cxx/pragma_export_fwd.h:
class FwdDecl1; // lines XX-XX
class FwdDecl2; // lines XX-XX
***** IWYU_SUMMARY */