Expand Up
@@ -13,7 +13,6 @@
#include " clang/Lex/HeaderSearch.h"
#include " clang/Lex/Preprocessor.h"
#include " clang/Parse/ParseAST.h"
#include " clang/Sema/ExternalSemaSource.h"
#include " clang/Sema/Sema.h"
#include " llvm/Support/Debug.h"
#include " llvm/Support/raw_ostream.h"
Expand All
@@ -25,19 +24,17 @@ using namespace clang;
namespace clang {
namespace include_fixer {
namespace {
// / Manages the parse, gathers include suggestions.
class Action : public clang ::ASTFrontendAction,
public clang::ExternalSemaSource {
class Action : public clang ::ASTFrontendAction {
public:
explicit Action (SymbolIndexManager &SymbolIndexMgr, bool MinimizeIncludePaths)
: SymbolIndexMgr (SymbolIndexMgr) ,
MinimizeIncludePaths(MinimizeIncludePaths ) {}
: SemaSource (SymbolIndexMgr, MinimizeIncludePaths ,
/* GenerateDiagnostics= */ false ) {}
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer (clang::CompilerInstance &Compiler,
StringRef InFile) override {
FilePath = InFile;
SemaSource. setFilePath ( InFile) ;
return llvm::make_unique<clang::ASTConsumer>();
}
Expand All
@@ -55,254 +52,21 @@ class Action : public clang::ASTFrontendAction,
CompletionConsumer = &Compiler->getCodeCompletionConsumer ();
Compiler->createSema (getTranslationUnitKind (), CompletionConsumer);
Compiler->getSema ().addExternalSource (this );
SemaSource.setCompilerInstance (Compiler);
Compiler->getSema ().addExternalSource (&SemaSource);
clang::ParseAST (Compiler->getSema (), Compiler->getFrontendOpts ().ShowStats ,
Compiler->getFrontendOpts ().SkipFunctionBodies );
}
// / Callback for incomplete types. If we encounter a forward declaration we
// / have the fully qualified name ready. Just query that.
bool MaybeDiagnoseMissingCompleteType (clang::SourceLocation Loc,
clang::QualType T) override {
// Ignore spurious callbacks from SFINAE contexts.
if (getCompilerInstance ().getSema ().isSFINAEContext ())
return false ;
clang::ASTContext &context = getCompilerInstance ().getASTContext ();
std::string QueryString =
T.getUnqualifiedType ().getAsString (context.getPrintingPolicy ());
DEBUG (llvm::dbgs () << " Query missing complete type '" << QueryString
<< " '" );
// Pass an empty range here since we don't add qualifier in this case.
query (QueryString, " " , tooling::Range ());
return false ;
}
// / Callback for unknown identifiers. Try to piece together as much
// / qualification as we can get and do a query.
clang::TypoCorrection CorrectTypo (const DeclarationNameInfo &Typo,
int LookupKind, Scope *S, CXXScopeSpec *SS,
CorrectionCandidateCallback &CCC,
DeclContext *MemberContext,
bool EnteringContext,
const ObjCObjectPointerType *OPT) override {
// Ignore spurious callbacks from SFINAE contexts.
if (getCompilerInstance ().getSema ().isSFINAEContext ())
return clang::TypoCorrection ();
// We currently ignore the unidentified symbol which is not from the
// main file.
//
// However, this is not always true due to templates in a non-self contained
// header, consider the case:
//
// // header.h
// template <typename T>
// class Foo {
// T t;
// };
//
// // test.cc
// // We need to add <bar.h> in test.cc instead of header.h.
// class Bar;
// Foo<Bar> foo;
//
// FIXME: Add the missing header to the header file where the symbol comes
// from.
if (!getCompilerInstance ().getSourceManager ().isWrittenInMainFile (
Typo.getLoc ()))
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;
}
}
}
auto ExtendNestedNameSpecifier = [this ](CharSourceRange Range) {
StringRef Source =
Lexer::getSourceText (Range, getCompilerInstance ().getSourceManager (),
getCompilerInstance ().getLangOpts ());
// Skip forward until we find a character that's neither identifier nor
// colon. This is a bit of a hack around the fact that we will only get a
// single callback for a long nested name if a part of the beginning is
// unknown. For example:
//
// llvm::sys::path::parent_path(...)
// ^~~~ ^~~
// known
// ^~~~
// unknown, last callback
// ^~~~~~~~~~~
// no callback
//
// With the extension we get the full nested name specifier including
// parent_path.
// FIXME: Don't rely on source text.
const char *End = Source.end ();
while (isIdentifierBody (*End) || *End == ' :' )
++End;
return std::string (Source.begin (), End);
};
// / If we have a scope specification, use that to get more precise results.
std::string QueryString;
tooling::Range SymbolRange;
const auto &SM = getCompilerInstance ().getSourceManager ();
auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
return tooling::Range (SM.getDecomposedLoc (BeginLoc).second ,
QueryString.size ());
};
if (SS && SS->getRange ().isValid ()) {
auto Range = CharSourceRange::getTokenRange (SS->getRange ().getBegin (),
Typo.getLoc ());
QueryString = ExtendNestedNameSpecifier (Range);
SymbolRange = CreateToolingRange (Range.getBegin ());
} else if (Typo.getName ().isIdentifier () && !Typo.getLoc ().isMacroID ()) {
auto Range =
CharSourceRange::getTokenRange (Typo.getBeginLoc (), Typo.getEndLoc ());
QueryString = ExtendNestedNameSpecifier (Range);
SymbolRange = CreateToolingRange (Range.getBegin ());
} else {
QueryString = Typo.getAsString ();
SymbolRange = CreateToolingRange (Typo.getLoc ());
}
DEBUG (llvm::dbgs () << " TypoScopeQualifiers: " << TypoScopeString << " \n " );
query (QueryString, TypoScopeString, SymbolRange);
// 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 ();
}
// / Get the minimal include for a given path.
std::string minimizeInclude (StringRef Include,
const clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch) {
if (!MinimizeIncludePaths)
return Include;
// Get the FileEntry for the include.
StringRef StrippedInclude = Include.trim (" \" <>" );
const FileEntry *Entry =
SourceManager.getFileManager ().getFile (StrippedInclude);
// If the file doesn't exist return the path from the database.
// FIXME: This should never happen.
if (!Entry)
return Include;
bool IsSystem;
std::string Suggestion =
HeaderSearch.suggestPathToFileForDiagnostics (Entry, &IsSystem);
return IsSystem ? ' <' + Suggestion + ' >' : ' "' + Suggestion + ' "' ;
}
// / Get the include fixer context for the queried symbol.
IncludeFixerContext
getIncludeFixerContext (const clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch) {
std::vector<find_all_symbols::SymbolInfo> SymbolCandidates;
for (const auto &Symbol : MatchedSymbols) {
std::string FilePath = Symbol.getFilePath ().str ();
std::string MinimizedFilePath = minimizeInclude (
((FilePath[0 ] == ' "' || FilePath[0 ] == ' <' ) ? FilePath
: " \" " + FilePath + " \" " ),
SourceManager, HeaderSearch);
SymbolCandidates.emplace_back (Symbol.getName (), Symbol.getSymbolKind (),
MinimizedFilePath, Symbol.getLineNumber (),
Symbol.getContexts (),
Symbol.getNumOccurrences ());
}
return IncludeFixerContext (FilePath, QuerySymbolInfos, SymbolCandidates);
clang::HeaderSearch &HeaderSearch) const {
return SemaSource.getIncludeFixerContext (SourceManager, HeaderSearch);
}
private:
// / Query the database for a given identifier.
bool query (StringRef Query, StringRef ScopedQualifiers,
tooling::Range Range) {
assert (!Query.empty () && " Empty query!" );
// Save all instances of an unidentified symbol.
//
// We use conservative behavior for detecting the same unidentified symbol
// here. The symbols which have the same ScopedQualifier and RawIdentifier
// are considered equal. So that include-fixer avoids false positives, and
// always adds missing qualifiers to correct symbols.
if (!QuerySymbolInfos.empty ()) {
if (ScopedQualifiers == QuerySymbolInfos.front ().ScopedQualifiers &&
Query == QuerySymbolInfos.front ().RawIdentifier ) {
QuerySymbolInfos.push_back ({Query.str (), ScopedQualifiers, Range});
}
return false ;
}
DEBUG (llvm::dbgs () << " Looking up '" << Query << " ' at " );
DEBUG (getCompilerInstance ()
.getSourceManager ()
.getLocForStartOfFile (
getCompilerInstance ().getSourceManager ().getMainFileID ())
.getLocWithOffset (Range.getOffset ())
.print (llvm::dbgs (), getCompilerInstance ().getSourceManager ()));
DEBUG (llvm::dbgs () << " ..." );
QuerySymbolInfos.push_back ({Query.str (), ScopedQualifiers, Range});
// Query the symbol based on C++ name Lookup rules.
// Firstly, lookup the identifier with scoped namespace contexts;
// If that fails, falls back to look up the identifier directly.
//
// For example:
//
// namespace a {
// b::foo f;
// }
//
// 1. lookup a::b::foo.
// 2. lookup b::foo.
std::string QueryString = ScopedQualifiers.str () + Query.str ();
// It's unsafe to do nested search for the identifier with scoped namespace
// context, it might treat the identifier as a nested class of the scoped
// namespace.
MatchedSymbols = SymbolIndexMgr.search (QueryString, /* IsNestedSearch=*/ false );
if (MatchedSymbols.empty ())
MatchedSymbols = SymbolIndexMgr.search (Query);
DEBUG (llvm::dbgs () << " Having found " << MatchedSymbols.size ()
<< " symbols\n " );
return !MatchedSymbols.empty ();
}
// / The client to use to find cross-references.
SymbolIndexManager &SymbolIndexMgr;
// / The information of the symbols being queried.
std::vector<IncludeFixerContext::QuerySymbolInfo> QuerySymbolInfos;
// / All symbol candidates which match QuerySymbol. We only include the first
// / discovered identifier to avoid getting caught in results from error
// / recovery.
std::vector<find_all_symbols::SymbolInfo> MatchedSymbols;
// / The file path to the file being processed.
std::string FilePath;
// / Whether we should use the smallest possible include path.
bool MinimizeIncludePaths = true ;
IncludeFixerSemaSource SemaSource;
};
} // namespace
Expand Down
Expand Up
@@ -352,6 +116,273 @@ bool IncludeFixerActionFactory::runInvocation(
return !Compiler.getDiagnostics ().hasFatalErrorOccurred ();
}
static void addDiagnosticsForContext (TypoCorrection &Correction,
const IncludeFixerContext &Context,
StringRef Code, SourceLocation StartOfFile,
ASTContext &Ctx) {
auto Reps = createIncludeFixerReplacements (
Code, Context, format::getLLVMStyle (), /* AddQualifiers=*/ false );
if (!Reps)
return ;
unsigned DiagID = Ctx.getDiagnostics ().getCustomDiagID (
DiagnosticsEngine::Note, " Add '#include %0' to provide the missing "
" declaration [clang-include-fixer]" );
// FIXME: Currently we only generate a diagnostic for the first header. Give
// the user choices.
assert (Reps->size () == 1 && " Expected exactly one replacement" );
const tooling::Replacement &Placed = *Reps->begin ();
auto Begin = StartOfFile.getLocWithOffset (Placed.getOffset ());
auto End = Begin.getLocWithOffset (Placed.getLength ());
PartialDiagnostic PD (DiagID, Ctx.getDiagAllocator ());
PD << Context.getHeaderInfos ().front ().Header
<< FixItHint::CreateReplacement (SourceRange (Begin, End),
Placed.getReplacementText ());
Correction.addExtraDiagnostic (std::move (PD));
}
// / Callback for incomplete types. If we encounter a forward declaration we
// / have the fully qualified name ready. Just query that.
bool IncludeFixerSemaSource::MaybeDiagnoseMissingCompleteType (
clang::SourceLocation Loc, clang::QualType T) {
// Ignore spurious callbacks from SFINAE contexts.
if (CI->getSema ().isSFINAEContext ())
return false ;
clang::ASTContext &context = CI->getASTContext ();
std::string QueryString =
T.getUnqualifiedType ().getAsString (context.getPrintingPolicy ());
DEBUG (llvm::dbgs () << " Query missing complete type '" << QueryString << " '" );
// Pass an empty range here since we don't add qualifier in this case.
query (QueryString, " " , tooling::Range ());
if (GenerateDiagnostics) {
TypoCorrection Correction;
FileID FID = CI->getSourceManager ().getFileID (Loc);
StringRef Code = CI->getSourceManager ().getBufferData (FID);
SourceLocation StartOfFile =
CI->getSourceManager ().getLocForStartOfFile (FID);
addDiagnosticsForContext (
Correction,
getIncludeFixerContext (CI->getSourceManager (),
CI->getPreprocessor ().getHeaderSearchInfo ()),
Code, StartOfFile, CI->getASTContext ());
for (const PartialDiagnostic &PD : Correction.getExtraDiagnostics ())
CI->getSema ().Diag (Loc, PD);
}
return true ;
}
// / Callback for unknown identifiers. Try to piece together as much
// / qualification as we can get and do a query.
clang::TypoCorrection IncludeFixerSemaSource::CorrectTypo (
const DeclarationNameInfo &Typo, int LookupKind, Scope *S, CXXScopeSpec *SS,
CorrectionCandidateCallback &CCC, DeclContext *MemberContext,
bool EnteringContext, const ObjCObjectPointerType *OPT) {
// Ignore spurious callbacks from SFINAE contexts.
if (CI->getSema ().isSFINAEContext ())
return clang::TypoCorrection ();
// We currently ignore the unidentified symbol which is not from the
// main file.
//
// However, this is not always true due to templates in a non-self contained
// header, consider the case:
//
// // header.h
// template <typename T>
// class Foo {
// T t;
// };
//
// // test.cc
// // We need to add <bar.h> in test.cc instead of header.h.
// class Bar;
// Foo<Bar> foo;
//
// FIXME: Add the missing header to the header file where the symbol comes
// from.
if (!CI->getSourceManager ().isWrittenInMainFile (Typo.getLoc ()))
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;
}
}
}
auto ExtendNestedNameSpecifier = [this ](CharSourceRange Range) {
StringRef Source =
Lexer::getSourceText (Range, CI->getSourceManager (), CI->getLangOpts ());
// Skip forward until we find a character that's neither identifier nor
// colon. This is a bit of a hack around the fact that we will only get a
// single callback for a long nested name if a part of the beginning is
// unknown. For example:
//
// llvm::sys::path::parent_path(...)
// ^~~~ ^~~
// known
// ^~~~
// unknown, last callback
// ^~~~~~~~~~~
// no callback
//
// With the extension we get the full nested name specifier including
// parent_path.
// FIXME: Don't rely on source text.
const char *End = Source.end ();
while (isIdentifierBody (*End) || *End == ' :' )
++End;
return std::string (Source.begin (), End);
};
// / If we have a scope specification, use that to get more precise results.
std::string QueryString;
tooling::Range SymbolRange;
const auto &SM = CI->getSourceManager ();
auto CreateToolingRange = [&QueryString, &SM](SourceLocation BeginLoc) {
return tooling::Range (SM.getDecomposedLoc (BeginLoc).second ,
QueryString.size ());
};
if (SS && SS->getRange ().isValid ()) {
auto Range = CharSourceRange::getTokenRange (SS->getRange ().getBegin (),
Typo.getLoc ());
QueryString = ExtendNestedNameSpecifier (Range);
SymbolRange = CreateToolingRange (Range.getBegin ());
} else if (Typo.getName ().isIdentifier () && !Typo.getLoc ().isMacroID ()) {
auto Range =
CharSourceRange::getTokenRange (Typo.getBeginLoc (), Typo.getEndLoc ());
QueryString = ExtendNestedNameSpecifier (Range);
SymbolRange = CreateToolingRange (Range.getBegin ());
} else {
QueryString = Typo.getAsString ();
SymbolRange = CreateToolingRange (Typo.getLoc ());
}
DEBUG (llvm::dbgs () << " TypoScopeQualifiers: " << TypoScopeString << " \n " );
query (QueryString, TypoScopeString, SymbolRange);
clang::TypoCorrection Correction (Typo.getName ());
Correction.setCorrectionRange (SS, Typo);
if (GenerateDiagnostics) {
FileID FID = SM.getFileID (Typo.getLoc ());
StringRef Code = SM.getBufferData (FID);
SourceLocation StartOfFile = SM.getLocForStartOfFile (FID);
addDiagnosticsForContext (
Correction,
getIncludeFixerContext (SM, CI->getPreprocessor ().getHeaderSearchInfo ()),
Code, StartOfFile, CI->getASTContext ());
}
return Correction;
}
// / Get the minimal include for a given path.
std::string IncludeFixerSemaSource::minimizeInclude (
StringRef Include, const clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch) const {
if (!MinimizeIncludePaths)
return Include;
// Get the FileEntry for the include.
StringRef StrippedInclude = Include.trim (" \" <>" );
const FileEntry *Entry =
SourceManager.getFileManager ().getFile (StrippedInclude);
// If the file doesn't exist return the path from the database.
// FIXME: This should never happen.
if (!Entry)
return Include;
bool IsSystem;
std::string Suggestion =
HeaderSearch.suggestPathToFileForDiagnostics (Entry, &IsSystem);
return IsSystem ? ' <' + Suggestion + ' >' : ' "' + Suggestion + ' "' ;
}
// / Get the include fixer context for the queried symbol.
IncludeFixerContext IncludeFixerSemaSource::getIncludeFixerContext (
const clang::SourceManager &SourceManager,
clang::HeaderSearch &HeaderSearch) const {
std::vector<find_all_symbols::SymbolInfo> SymbolCandidates;
for (const auto &Symbol : MatchedSymbols) {
std::string FilePath = Symbol.getFilePath ().str ();
std::string MinimizedFilePath = minimizeInclude (
((FilePath[0 ] == ' "' || FilePath[0 ] == ' <' ) ? FilePath
: " \" " + FilePath + " \" " ),
SourceManager, HeaderSearch);
SymbolCandidates.emplace_back (Symbol.getName (), Symbol.getSymbolKind (),
MinimizedFilePath, Symbol.getLineNumber (),
Symbol.getContexts (),
Symbol.getNumOccurrences ());
}
return IncludeFixerContext (FilePath, QuerySymbolInfos, SymbolCandidates);
}
bool IncludeFixerSemaSource::query (StringRef Query, StringRef ScopedQualifiers,
tooling::Range Range) {
assert (!Query.empty () && " Empty query!" );
// Save all instances of an unidentified symbol.
//
// We use conservative behavior for detecting the same unidentified symbol
// here. The symbols which have the same ScopedQualifier and RawIdentifier
// are considered equal. So that include-fixer avoids false positives, and
// always adds missing qualifiers to correct symbols.
if (!QuerySymbolInfos.empty ()) {
if (ScopedQualifiers == QuerySymbolInfos.front ().ScopedQualifiers &&
Query == QuerySymbolInfos.front ().RawIdentifier ) {
QuerySymbolInfos.push_back ({Query.str (), ScopedQualifiers, Range});
}
return false ;
}
DEBUG (llvm::dbgs () << " Looking up '" << Query << " ' at " );
DEBUG (CI->getSourceManager ()
.getLocForStartOfFile (CI->getSourceManager ().getMainFileID ())
.getLocWithOffset (Range.getOffset ())
.print (llvm::dbgs (), CI->getSourceManager ()));
DEBUG (llvm::dbgs () << " ..." );
QuerySymbolInfos.push_back ({Query.str (), ScopedQualifiers, Range});
// Query the symbol based on C++ name Lookup rules.
// Firstly, lookup the identifier with scoped namespace contexts;
// If that fails, falls back to look up the identifier directly.
//
// For example:
//
// namespace a {
// b::foo f;
// }
//
// 1. lookup a::b::foo.
// 2. lookup b::foo.
std::string QueryString = ScopedQualifiers.str () + Query.str ();
// It's unsafe to do nested search for the identifier with scoped namespace
// context, it might treat the identifier as a nested class of the scoped
// namespace.
MatchedSymbols = SymbolIndexMgr.search (QueryString, /* IsNestedSearch=*/ false );
if (MatchedSymbols.empty ())
MatchedSymbols = SymbolIndexMgr.search (Query);
DEBUG (llvm::dbgs () << " Having found " << MatchedSymbols.size ()
<< " symbols\n " );
return !MatchedSymbols.empty ();
}
llvm::Expected<tooling::Replacements> createIncludeFixerReplacements (
StringRef Code, const IncludeFixerContext &Context,
const clang::format::FormatStyle &Style , bool AddQualifiers) {
Expand Down