diff --git a/plugins/clang/duchain/clangproblem.h b/plugins/clang/duchain/clangproblem.h --- a/plugins/clang/duchain/clangproblem.h +++ b/plugins/clang/duchain/clangproblem.h @@ -71,6 +71,9 @@ using Ptr = QExplicitlySharedDataPointer; using ConstPtr = QExplicitlySharedDataPointer; + // Creates an empty ClangProblem. + ClangProblem(); + /** * Import @p diagnostic into a ClangProblem object * diff --git a/plugins/clang/duchain/clangproblem.cpp b/plugins/clang/duchain/clangproblem.cpp --- a/plugins/clang/duchain/clangproblem.cpp +++ b/plugins/clang/duchain/clangproblem.cpp @@ -96,6 +96,8 @@ return debug; } +ClangProblem::ClangProblem() {} + ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -199,6 +199,70 @@ }) != includePaths.end(); } +bool isRequestedFromFile(CXDiagnostic diagnostic, CXFile file) +{ + auto childDiagnostics = clang_getChildDiagnostics(diagnostic); + auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); + for (uint j = 0; j < numChildDiagnostics; ++j) { + auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); + CXSourceLocation childLocation = clang_getDiagnosticLocation(childDiagnostic); + CXFile childDiagnosticFile; + clang_getFileLocation(childLocation, &childDiagnosticFile, nullptr, nullptr, nullptr); + if (childDiagnosticFile == file) { + QString description = ClangString(clang_getDiagnosticSpelling(childDiagnostic)).toString(); + if (description.endsWith(QLatin1String("requested here"))) { + return true; + } + } + } + return false; +} + +void makeExternalProblem(KDevelop::Problem* problem, CXFile file) +{ + // Insert a copy of the parent problem as the first + // subproblem to preserve its location. + ClangProblem* problemCopy = new ClangProblem(); + problemCopy->setSource(problem->source()); + problemCopy->setFinalLocation(problem->finalLocation()); + problemCopy->setFinalLocationMode(problem->finalLocationMode()); + problemCopy->setDescription(problem->description()); + problemCopy->setExplanation(problem->explanation()); + problemCopy->setSeverity(problem->severity()); + + auto subproblems = problem->diagnostics(); + subproblems.prepend(IProblem::Ptr(problemCopy)); + problem->setDiagnostics(subproblems); + + // Override the problem's finalLocation with that of the "requested here" + // subproblem in this document, or with an empty range in this document in case + // such a subproblem does not exist. This is required to make the problem show up + // in the problem reporter for this file, since it filters by finalLocation. + // It will also lead the user to the correct location when clicking the problem + // and cause proper error highlighting. Also prepend a statement to the problem + // description to clarify that the actual error was reported in another file. + const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); + const IndexedString indexedPath(path); + bool foundRequestedHere = false; + for (auto& subproblem : problem->diagnostics()) { + if (subproblem->finalLocation().document == indexedPath && + subproblem->description().endsWith(QLatin1String("requested here"))) { + problem->setFinalLocation(subproblem->finalLocation()); + problem->setDescription(i18n("Requested here: ") + problem->description()); + foundRequestedHere = true; + break; + } + } + + if (!foundRequestedHere) { + // Since we did not find a "requested here" subproblem within this document, + // generate an empty range for the problem within this document. + const KTextEditor::Range problemRange(0, 0, 0, 0); + problem->setFinalLocation(DocumentRange{indexedPath, problemRange}); + problem->setDescription(i18n("In included file: ") + problem->description()); + } +} + } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, @@ -448,15 +512,24 @@ CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); + // missing-include problems are so severe in clang that we always propagate // them to this document, to ensure that the user will see the error. - if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) { + // We also include problems from other files that have child problems in + // this document which end on "requested here". + if (diagnosticFile != file + && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem + && !isRequestedFromFile(diagnostic, file)) { continue; } auto& problem = d->m_diagnosticsCache[i]; if (!problem) { problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit); + + if (diagnosticFile != file) { + makeExternalProblem(problem.data(), file); + } } problems << problem; diff --git a/plugins/clang/tests/test_duchain.h b/plugins/clang/tests/test_duchain.h --- a/plugins/clang/tests/test_duchain.h +++ b/plugins/clang/tests/test_duchain.h @@ -97,6 +97,9 @@ void testTemplateFunctionParameterName(); void testFriendDeclaration(); void testVariadicTemplateArguments(); + void testProblemRequestedHere(); + void testProblemRequestedHereSameFile(); + void testProblemRequestedHereChain(); void testGccCompatibility(); void testQtIntegration(); diff --git a/plugins/clang/tests/test_duchain.cpp b/plugins/clang/tests/test_duchain.cpp --- a/plugins/clang/tests/test_duchain.cpp +++ b/plugins/clang/tests/test_duchain.cpp @@ -1930,6 +1930,131 @@ } } +void TestDUChain::testProblemRequestedHere() +{ + auto headerCode = QStringLiteral(R"( +#pragma once + +template +T AddObjects(const T& a, const T& b) +{ + return a + b; +} + )"); + TestFile header(headerCode, QStringLiteral("h")); + + QString sourceCode = "#include \"" + header.url().str() + "\"\n"; + sourceCode += QStringLiteral(R"( +struct A {}; + +int main() +{ + A a, b; + AddObjects(a, b); + return 0; +} + )"); + TestFile impl(sourceCode, QStringLiteral("cpp"), &header); + QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); + + DUChainReadLocker lock; + + auto top = impl.topContext(); + QVERIFY(top); + + TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); + QVERIFY(headerCtx); + QCOMPARE(headerCtx->url(), header.url()); + + QCOMPARE(headerCtx->problems().count(), 1); + QCOMPARE(headerCtx->localDeclarations().count(), 1); + + // Verify that the problem is reported for the source file. + QCOMPARE(top->problems().count(), 1); + QCOMPARE(top->localDeclarations().count(), 2); +} + +void TestDUChain::testProblemRequestedHereSameFile() +{ + auto sourceCode = QStringLiteral(R"( +struct A {}; + +template +T AddObjects(const T& a, const T& b) +{ + return a + b; +} + +int main() +{ + A a, b; + AddObjects(a, b); + return 0; +} + )"); + TestFile impl(sourceCode, QStringLiteral("cpp")); + QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); + + DUChainReadLocker lock; + + auto top = impl.topContext(); + QVERIFY(top); + + QEXPECT_FAIL("", "Currently no problem is reported for the function call if it is in the same file as the function", Continue); + QCOMPARE(top->problems().count(), 2); +} + +void TestDUChain::testProblemRequestedHereChain() +{ + auto headerCode = QStringLiteral(R"( +#pragma once + +template +T AddObjects(const T& a, const T& b) +{ + return a + b; +} + )"); + TestFile header(headerCode, QStringLiteral("h")); + + QString sourceCode = "#include \"" + header.url().str() + "\"\n"; + sourceCode += QStringLiteral(R"( +struct A {}; + +template +T AddObjects2(const T& a, const T& b) +{ + return AddObjects(a, b); +} + +int main() +{ + A a, b; + AddObjects2(a, b); + return 0; +} + )"); + TestFile impl(sourceCode, QStringLiteral("cpp"), &header); + QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); + + DUChainReadLocker lock; + + auto top = impl.topContext(); + QVERIFY(top); + + TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); + QVERIFY(headerCtx); + QCOMPARE(headerCtx->url(), header.url()); + + QCOMPARE(headerCtx->problems().count(), 1); + QCOMPARE(headerCtx->localDeclarations().count(), 1); + + // Verify that the problem is reported for the source file. + QEXPECT_FAIL("", "Currently only one problem is reported for an external problem that has multiple 'requested here' lines in the current file", Continue); + QCOMPARE(top->problems().count(), 2); + QCOMPARE(top->localDeclarations().count(), 3); +} + void TestDUChain::testGccCompatibility() { // TODO: make it easier to change the compiler provider for testing purposes