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 @@ -448,15 +448,84 @@ 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. + // 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) { - continue; + bool keepDiagnostic = false; + 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) { + // Only keep diagnostics that end on "requested here". Otherwise, we might add lots of + // diagnostics to unrelated intermediate include files in the chain between the requesting + // file and the file where the problem occurs. + QString description = ClangString(clang_getDiagnosticSpelling(childDiagnostic)).toString(); + if (description.endsWith(QLatin1String("requested here"))) { + keepDiagnostic = true; + break; + } + } + } + + if (!keepDiagnostic) { + continue; + } } auto& problem = d->m_diagnosticsCache[i]; if (!problem) { problem = ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit); + + if (diagnosticFile != 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()); + } + } } problems << problem;