blob: 0f4988846539445412dcca04f44f355c38e04498 [file] [log] [blame]
//===--- IncludeFixer.cpp ----------------------------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "IncludeFixer.h"
#include "AST.h"
#include "Diagnostics.h"
#include "Logger.h"
#include "SourceCode.h"
#include "Trace.h"
#include "index/Index.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticSema.h"
#include "llvm/ADT/None.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
namespace clang {
namespace clangd {
std::vector<Fix> IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) const {
if (IndexRequestCount >= IndexRequestLimit)
return {}; // Avoid querying index too many times in a single parse.
switch (Info.getID()) {
case diag::err_incomplete_type:
case diag::err_incomplete_member_access:
case diag::err_incomplete_base_class:
// Incomplete type diagnostics should have a QualType argument for the
// incomplete type.
for (unsigned Idx = 0; Idx < Info.getNumArgs(); ++Idx) {
if (Info.getArgKind(Idx) == DiagnosticsEngine::ak_qualtype) {
auto QT = QualType::getFromOpaquePtr((void *)Info.getRawArg(Idx));
if (const Type *T = QT.getTypePtrOrNull())
if (T->isIncompleteType())
return fixIncompleteType(*T);
}
}
}
return {};
}
std::vector<Fix> IncludeFixer::fixIncompleteType(const Type &T) const {
// Only handle incomplete TagDecl type.
const TagDecl *TD = T.getAsTagDecl();
if (!TD)
return {};
std::string TypeName = printQualifiedName(*TD);
trace::Span Tracer("Fix include for incomplete type");
SPAN_ATTACH(Tracer, "type", TypeName);
vlog("Trying to fix include for incomplete type {0}", TypeName);
auto ID = getSymbolID(TD);
if (!ID)
return {};
++IndexRequestCount;
// FIXME: consider batching the requests for all diagnostics.
// FIXME: consider caching the lookup results.
LookupRequest Req;
Req.IDs.insert(*ID);
llvm::Optional<Symbol> Matched;
Index.lookup(Req, [&](const Symbol &Sym) {
if (Matched)
return;
Matched = Sym;
});
if (!Matched || Matched->IncludeHeaders.empty() || !Matched->Definition ||
Matched->CanonicalDeclaration.FileURI != Matched->Definition.FileURI)
return {};
return fixesForSymbol(*Matched);
}
std::vector<Fix> IncludeFixer::fixesForSymbol(const Symbol &Sym) const {
auto Inserted = [&](llvm::StringRef Header)
-> llvm::Expected<std::pair<std::string, bool>> {
auto ResolvedDeclaring =
toHeaderFile(Sym.CanonicalDeclaration.FileURI, File);
if (!ResolvedDeclaring)
return ResolvedDeclaring.takeError();
auto ResolvedInserted = toHeaderFile(Header, File);
if (!ResolvedInserted)
return ResolvedInserted.takeError();
return std::make_pair(
Inserter->calculateIncludePath(*ResolvedDeclaring, *ResolvedInserted),
Inserter->shouldInsertInclude(*ResolvedDeclaring, *ResolvedInserted));
};
std::vector<Fix> Fixes;
for (const auto &Inc : getRankedIncludes(Sym)) {
if (auto ToInclude = Inserted(Inc)) {
if (ToInclude->second)
if (auto Edit = Inserter->insert(ToInclude->first))
Fixes.push_back(
Fix{llvm::formatv("Add include {0} for symbol {1}{2}",
ToInclude->first, Sym.Scope, Sym.Name),
{std::move(*Edit)}});
} else {
vlog("Failed to calculate include insertion for {0} into {1}: {2}", File,
Inc, ToInclude.takeError());
}
}
return Fixes;
}
} // namespace clangd
} // namespace clang