Skip to content

Commit

Permalink
[include-fixer] Use scope contexts information to improve query.
Browse files Browse the repository at this point in the history
Reviewers: bkramer

Subscribers: cfe-commits

Differential Revision: http://reviews.llvm.org/D20205

llvm-svn: 269430
  • Loading branch information
hokein committed May 13, 2016
1 parent a4bc53a commit 57cdcb0
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 12 deletions.
42 changes: 34 additions & 8 deletions clang-tools-extra/include-fixer/IncludeFixer.cpp
Expand Up @@ -116,6 +116,19 @@ class Action : public clang::ASTFrontendAction,
if (getCompilerInstance().getSema().isSFINAEContext())
return clang::TypoCorrection();

std::string TypoScopeString;
if (S) {
// FIXME: Currently we only use namespace contexts. Use other context
// types for query.
for (const auto *Context = S->getEntity(); Context;
Context = Context->getParent()) {
if (const auto *ND = dyn_cast<NamespaceDecl>(Context)) {
if (!ND->getName().empty())
TypoScopeString = ND->getNameAsString() + "::" + TypoScopeString;
}
}
}

/// If we have a scope specification, use that to get more precise results.
std::string QueryString;
if (SS && SS->getRange().isValid()) {
Expand Down Expand Up @@ -150,7 +163,23 @@ class Action : public clang::ASTFrontendAction,
QueryString = Typo.getAsString();
}

return query(QueryString, Typo.getLoc());
// Follow C++ Lookup rules. Firstly, lookup the identifier with scoped
// namespace contexts. If fails, falls back to identifier.
// For example:
//
// namespace a {
// b::foo f;
// }
//
// 1. lookup a::b::foo.
// 2. lookup b::foo.
if (!query(TypoScopeString + QueryString, Typo.getLoc()))
query(QueryString, Typo.getLoc());

// FIXME: We should just return the name we got as input here and prevent
// clang from trying to correct the typo by itself. That may change the
// identifier to something that's not wanted by the user.
return clang::TypoCorrection();
}

StringRef filename() const { return Filename; }
Expand Down Expand Up @@ -235,12 +264,12 @@ class Action : public clang::ASTFrontendAction,

private:
/// Query the database for a given identifier.
clang::TypoCorrection query(StringRef Query, SourceLocation Loc) {
bool query(StringRef Query, SourceLocation Loc) {
assert(!Query.empty() && "Empty query!");

// Save database lookups by not looking up identifiers multiple times.
if (!SeenQueries.insert(Query).second)
return clang::TypoCorrection();
return true;

DEBUG(llvm::dbgs() << "Looking up '" << Query << "' at ");
DEBUG(Loc.print(llvm::dbgs(), getCompilerInstance().getSourceManager()));
Expand All @@ -250,16 +279,13 @@ class Action : public clang::ASTFrontendAction,
auto SearchReply = SymbolIndexMgr.search(Query);
DEBUG(llvm::dbgs() << SearchReply.size() << " replies\n");
if (SearchReply.empty())
return clang::TypoCorrection();
return false;

// Add those files to the set of includes to try out.
// FIXME: Rank the results and pick the best one instead of the first one.
TryInclude(Query, SearchReply[0]);

// FIXME: We should just return the name we got as input here and prevent
// clang from trying to correct the typo by itself. That may change the
// identifier to something that's not wanted by the user.
return clang::TypoCorrection();
return true;
}

/// The client to use to find cross-references.
Expand Down
10 changes: 6 additions & 4 deletions clang-tools-extra/include-fixer/SymbolIndexManager.cpp
Expand Up @@ -39,18 +39,20 @@ SymbolIndexManager::search(llvm::StringRef Identifier) const {
if (Symbol.getName() == Names.back()) {
bool IsMatched = true;
auto SymbolContext = Symbol.getContexts().begin();
auto IdentiferContext = Names.rbegin() + 1; // Skip identifier name;
// Match the remaining context names.
for (auto IdentiferContext = Names.rbegin() + 1;
IdentiferContext != Names.rend() &&
SymbolContext != Symbol.getContexts().end();
for (; IdentiferContext != Names.rend() &&
SymbolContext != Symbol.getContexts().end();
++IdentiferContext, ++SymbolContext) {
if (SymbolContext->second != *IdentiferContext) {
IsMatched = false;
break;
}
}

if (IsMatched) {
// FIXME: Support full match. At this point, we only find symbols in
// database which end with the same contexts with the identifier.
if (IsMatched && IdentiferContext == Names.rend()) {
// FIXME: file path should never be in the form of <...> or "...", but
// the unit test with fixed database use <...> file path, which might
// need to be changed.
Expand Down
17 changes: 17 additions & 0 deletions clang-tools-extra/unittests/include-fixer/IncludeFixerTest.cpp
Expand Up @@ -59,6 +59,9 @@ static std::string runIncludeFixer(
SymbolInfo("foo", SymbolInfo::SymbolKind::Class, "\"dir/otherdir/qux.h\"",
1, {{SymbolInfo::ContextType::Namespace, "b"},
{SymbolInfo::ContextType::Namespace, "a"}}),
SymbolInfo("bar", SymbolInfo::SymbolKind::Class, "\"bar.h\"",
1, {{SymbolInfo::ContextType::Namespace, "b"},
{SymbolInfo::ContextType::Namespace, "a"}}),
};
auto SymbolIndexMgr = llvm::make_unique<include_fixer::SymbolIndexManager>();
SymbolIndexMgr->addSymbolIndex(
Expand Down Expand Up @@ -137,6 +140,20 @@ TEST(IncludeFixer, MultipleMissingSymbols) {
runIncludeFixer("std::string bar;\nstd::sting foo;\n"));
}

TEST(IncludeFixer, ScopedNamespaceSymbols) {
EXPECT_EQ("#include \"bar.h\"\nnamespace a { b::bar b; }\n",
runIncludeFixer("namespace a { b::bar b; }\n"));
EXPECT_EQ("#include \"bar.h\"\nnamespace A { a::b::bar b; }\n",
runIncludeFixer("namespace A { a::b::bar b; }\n"));
EXPECT_EQ("#include \"bar.h\"\nnamespace a { void func() { b::bar b; } }\n",
runIncludeFixer("namespace a { void func() { b::bar b; } }\n"));
EXPECT_EQ("namespace A { c::b::bar b; }\n",
runIncludeFixer("namespace A { c::b::bar b; }\n"));
// FIXME: The header should not be added here. Remove this after we support
// full match.
EXPECT_EQ("#include \"bar.h\"\nnamespace A { b::bar b; }\n",
runIncludeFixer("namespace A { b::bar b; }\n"));
}
} // namespace
} // namespace include_fixer
} // namespace clang

0 comments on commit 57cdcb0

Please sign in to comment.