Add new --cxx17ns option

This opts in for the more concise syntax introduced in C++17: namespace
a::b { ... }.

Usage of this is especially useful in codebases where existing forward
declarations in nested namespaces already use this form: so the IWYU
suggestion for new forward declarations can be consistent with the
existing ones.

fix_includes.py already handled this, but add a test to maintain this
behavior, too.
This commit is contained in:
Miklos Vajna 2019-01-31 17:22:54 +01:00 committed by Kim Gräsman
parent 8b63d319e9
commit 353a6da9b7
7 changed files with 163 additions and 7 deletions

View File

@ -384,6 +384,49 @@ The full include-list for empty_namespace:
self.RegisterFileContents({'empty_namespace': infile})
self.ProcessAndTest(iwyu_output)
def testCXX17NS(self):
"""Tests handling of output using the --cxx17ns switch."""
infile = """\
#include "cxx17ns-i1.h"///-
///+
///+namespace a::b::c {
///+struct One;
///+} // namespace a::b::c
///+namespace a::b {
///+struct One2;
///+} // namespace a::b
///+namespace a {
///+struct One4;
///+struct One3;
///+} // namespace a
struct Two {
Two(a::b::c::One& one);
Two(a::b::One2& one);
Two(a::One3& one);
Two(a::One4& one);
};
"""
iwyu_output = """\
cxx17ns.cc should add these lines:
namespace a { namespace { struct One4; } }
namespace a { struct One3; }
namespace a::b { struct One2; }
namespace a::b::c { struct One; }
cxx17ns.cc should remove these lines:
- #include "cxx17ns-i1.h" // lines 1-1
The full include-list for cxx17ns.cc:
namespace a { namespace { struct One4; } }
namespace a { struct One3; }
namespace a::b { struct One2; }
namespace a::b::c { struct One; }
---
"""
self.RegisterFileContents({'cxx17ns.cc': infile})
self.ProcessAndTest(iwyu_output)
def testRemovePartOfEmptyNamespace(self):
"""Tests we remove a namespace if empty, but not enclosing namespaces."""
infile = """\

View File

@ -96,6 +96,7 @@ static void PrintHelp(const char* extra_msg) {
" --verbose=<level>: the higher the level, the more output.\n"
" --quoted_includes_first: when sorting includes, place quoted\n"
" ones first.\n"
" --cxx17ns: suggests the more concise syntax introduced in C++17\n"
"\n"
"In addition to IWYU-specific options you can specify the following\n"
"options without -Xiwyu prefix:\n"
@ -165,7 +166,8 @@ CommandlineFlags::CommandlineFlags()
pch_in_code(false),
no_comments(false),
no_fwd_decls(false),
quoted_includes_first(false) {
quoted_includes_first(false),
cxx17ns(false) {
}
int CommandlineFlags::ParseArgv(int argc, char** argv) {
@ -183,6 +185,7 @@ int CommandlineFlags::ParseArgv(int argc, char** argv) {
{"no_comments", optional_argument, nullptr, 'o'},
{"no_fwd_decls", optional_argument, nullptr, 'f'},
{"quoted_includes_first", no_argument, nullptr, 'q' },
{"cxx17ns", no_argument, nullptr, 'C'},
{nullptr, 0, nullptr, 0}
};
static const char shortopts[] = "d::p:v:c:m:n";
@ -215,6 +218,7 @@ int CommandlineFlags::ParseArgv(int argc, char** argv) {
CHECK_((max_line_length >= 0) && "Max line length must be positive");
break;
case 'q': quoted_includes_first = true; break;
case 'C': cxx17ns = true; break;
case -1: return optind; // means 'no more input'
default:
PrintHelp("FATAL ERROR: unknown flag.");

View File

@ -102,6 +102,7 @@ struct CommandlineFlags {
bool no_comments; // Disable 'why' comments. No short option.
bool no_fwd_decls; // Disable forward declarations.
bool quoted_includes_first; // Place quoted includes first in sort order.
bool cxx17ns; // -C: C++17 nested namespace syntax
};
const CommandlineFlags& GlobalFlags();

View File

@ -346,12 +346,27 @@ string PrintablePtr(const void* ptr) {
// `-- TagDecl (class, struct, union, enum)
// `-- RecordDecl (class, struct, union)
// Determines if a NamedDecl has any parent namespace, which is anonymous.
bool HasAnonymousNamespace(const NamedDecl* decl) {
for (const DeclContext* ctx = decl->getDeclContext();
ctx && isa<NamedDecl>(ctx); ctx = ctx->getParent()) {
if (const NamespaceDecl* ns = DynCastFrom(ctx)) {
if (ns->isAnonymousNamespace()) {
return true;
}
}
}
return false;
}
// Given a NamedDecl that presents a (possibly template) record
// (i.e. class, struct, or union) type declaration, and the print-out
// of its (possible) template parameters and kind (e.g. "template
// <typename T> struct"), returns its forward declaration line.
string PrintForwardDeclare(const NamedDecl* decl,
const string& tpl_params_and_kind) {
const string& tpl_params_and_kind,
bool cxx17ns) {
// We need to short-circuit the logic for testing.
if (const FakeNamedDecl* fake = FakeNamedDeclIfItIsOne(decl)) {
return tpl_params_and_kind + " " + fake->qual_name() + ";";
@ -362,19 +377,39 @@ string PrintForwardDeclare(const NamedDecl* decl,
std::string fwd_decl = std::string(decl->getName()) + ";";
bool seen_namespace = false;
// Anonymous namespaces are not using the more concise syntax.
bool concat_namespaces = cxx17ns && !HasAnonymousNamespace(decl);
for (const DeclContext* ctx = decl->getDeclContext();
ctx && isa<NamedDecl>(ctx); ctx = ctx->getParent()) {
if (const RecordDecl* rec = DynCastFrom(ctx)) {
fwd_decl = std::string(rec->getName()) + "::" + fwd_decl;
} else if (const NamespaceDecl* ns = DynCastFrom(ctx)) {
bool first = !seen_namespace;
if (!seen_namespace) {
seen_namespace = true;
fwd_decl = tpl_params_and_kind + " " + fwd_decl;
}
const std::string ns_name = ns->isAnonymousNamespace() ?
"" : (std::string(ns->getName()) + " ");
fwd_decl = "namespace " + ns_name + "{ " + fwd_decl + " }";
if (concat_namespaces) {
std::string ns_name = std::string(ns->getName());
std::string prefix = ns_name;
std::string suffix;
if (first) {
first = false;
prefix = prefix + " { ";
}
if (ctx->getParent() && isa<NamedDecl>(ctx->getParent())) {
prefix = "::" + prefix;
} else {
prefix = "namespace " + prefix;
suffix = " }";
}
fwd_decl = prefix + fwd_decl + suffix;
} else {
std::string ns_name = ns->isAnonymousNamespace() ?
std::string() : (std::string(ns->getName()) + " ");
fwd_decl = "namespace " + ns_name + "{ " + fwd_decl + " }";
}
} else if (const FunctionDecl* fn = DynCastFrom(ctx)) {
// A local class (class defined inside a function).
fwd_decl = std::string(fn->getName()) + "::" + fwd_decl;
@ -392,7 +427,7 @@ string PrintForwardDeclare(const NamedDecl* decl,
// Given a RecordDecl, return the line that could be put in source
// code to forward-declare the record type, e.g. "namespace ns { class Foo; }".
string MungedForwardDeclareLineForNontemplates(const RecordDecl* decl) {
return PrintForwardDeclare(decl, GetKindName(decl));
return PrintForwardDeclare(decl, GetKindName(decl), GlobalFlags().cxx17ns);
}
// Given a TemplateDecl representing a class|struct|union template
@ -421,7 +456,7 @@ string MungedForwardDeclareLineForTemplates(const TemplateDecl* decl) {
// argument is inclusive, so substract one to get past the end-space.
const string::size_type name = line.rfind(' ', endpos - 1);
CHECK_(name != string::npos && "Unexpected printable template-type");
return PrintForwardDeclare(decl, line.substr(0, name));
return PrintForwardDeclare(decl, line.substr(0, name), GlobalFlags().cxx17ns);
}
string MungedForwardDeclareLine(const NamedDecl* decl) {

View File

@ -85,6 +85,7 @@ class OneIwyuTest(unittest.TestCase):
'prefix_header_includes_remove.cc': ['--prefix_header_includes=remove'],
'prefix_header_operator_new.cc': ['--prefix_header_includes=remove'],
'quoted_includes_first.cc': ['--pch_in_code', '--quoted_includes_first'],
'cxx17ns.cc': ['--cxx17ns'],
}
prefix_headers = [self.Include('prefix_header_includes-d1.h'),
self.Include('prefix_header_includes-d2.h'),
@ -113,6 +114,7 @@ class OneIwyuTest(unittest.TestCase):
'range_for.cc': ['-std=c++11'],
'typedef_in_template.cc': ['-std=c++11'],
'inheriting_ctor.cc': ['-std=c++11'],
'cxx17ns.cc': ['-std=c++17'],
}
include_map = {
'alias_template.cc': ['.'],
@ -193,6 +195,7 @@ class OneIwyuTest(unittest.TestCase):
'using_aliased_symbol_unused.cc': ['.'],
'varargs_and_references.cc': ['.'],
'virtual_tpl_method.cc': ['.'],
'cxx17ns.cc': ['.'],
}
# Internally, we like it when the paths start with rootdir.
self._iwyu_flags_map = dict((posixpath.join(self.rootdir, k), v)

34
tests/cxx/cxx17ns-i1.h Normal file
View File

@ -0,0 +1,34 @@
//===--- cxx17ns-i1.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.
//
//===----------------------------------------------------------------------===//
#ifndef INCLUDE_WHAT_YOU_USE_TESTS_CXX_CXX17NS_I1_H_
#define INCLUDE_WHAT_YOU_USE_TESTS_CXX_CXX17NS_I1_H_
namespace a {
namespace b {
namespace c {
struct One {
One();
};
} // namespace c
struct One2 {
One2();
};
} // namespace b
struct One3 {
One3();
};
namespace {
struct One4 {
One4();
};
}
} // namespace a
#endif // INCLUDE_WHAT_YOU_USE_TESTS_CXX_CXX17NS_I1_H_

36
tests/cxx/cxx17ns.cc Normal file
View File

@ -0,0 +1,36 @@
//===--- cxx17ns.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.
//
//===----------------------------------------------------------------------===//
#include "tests/cxx/cxx17ns-i1.h"
struct Two {
Two(a::b::c::One& one);
Two(a::b::One2& one);
Two(a::One3& one);
Two(a::One4& one);
};
/**** IWYU_SUMMARY
tests/cxx/cxx17ns.cc should add these lines:
namespace a { namespace { struct One4; } }
namespace a { struct One3; }
namespace a::b { struct One2; }
namespace a::b::c { struct One; }
tests/cxx/cxx17ns.cc should remove these lines:
- #include "tests/cxx/cxx17ns-i1.h" // lines XX-XX
The full include-list for tests/cxx/cxx17ns.cc:
namespace a { namespace { struct One4; } }
namespace a { struct One3; }
namespace a::b { struct One2; }
namespace a::b::c { struct One; }
***** IWYU_SUMMARY */