kadircet created this revision. kadircet added a reviewer: sammccall. Herald added a subscriber: arphaman. Herald added a project: All. kadircet requested review of this revision. Herald added subscribers: cfe-commits, MaskRay, ilya-biryukov. Herald added a project: clang-tools-extra.
This patch tries to just preserve diagnostics that are contained to lines containing #include or #define directives and moves them around with the new file contents. It's granularity is per-line, it doesn't handle column-wide changes. Depends on D143095 <https://reviews.llvm.org/D143095> Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D143096 Files: clang-tools-extra/clangd/ParsedAST.cpp clang-tools-extra/clangd/Preamble.cpp clang-tools-extra/clangd/Preamble.h clang-tools-extra/clangd/unittests/PreambleTests.cpp
Index: clang-tools-extra/clangd/unittests/PreambleTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/PreambleTests.cpp +++ clang-tools-extra/clangd/unittests/PreambleTests.cpp @@ -637,17 +637,16 @@ Annotations NewCode("[[# include \"foo.h\"]]"); auto AST = createPatchedAST(Annotations(BaselinePreamble).code(), NewCode.code(), AdditionalFiles); - // FIXME: Should be pointing at the range inside the Newcode. - EXPECT_THAT(mainFileDiagRanges(*AST), IsEmpty()); + EXPECT_THAT(mainFileDiagRanges(*AST), + UnorderedElementsAreArray(NewCode.ranges())); } { // Check with additions to preamble. Annotations Code(BaselinePreamble); Annotations NewCode(("[[#include \"barx.h\"]]\n" + BaselinePreamble).str()); auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles); - // FIXME: Should preserve existing diagnostics. EXPECT_THAT(mainFileDiagRanges(*AST), - UnorderedElementsAre(NewCode.ranges()[0])); + UnorderedElementsAreArray(NewCode.ranges())); } { llvm::StringLiteral BaselinePreamble = "#define [[FOO]] 1\n"; @@ -673,25 +672,21 @@ Annotations Code(BaselinePreamble); Annotations NewCode(("#define BAR\n" + BaselinePreamble).str()); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should point at the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } { // Check with removals from preamble. Annotations Code(("#define BAR\n" + BaselinePreamble).str()); Annotations NewCode(BaselinePreamble); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should point at the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } { // Drop line with diags. Annotations Code(BaselinePreamble); Annotations NewCode("#define BAR\n#include [[<bar>]]\n"); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should only have ranges from the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), - ElementsAreArray({Code.range(), NewCode.range()})); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } } @@ -710,25 +705,21 @@ Annotations Code(BaselinePreamble); Annotations NewCode(("#define BAR\n" + BaselinePreamble).str()); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should point at the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } { // Check with removals from preamble. Annotations Code(("#define BAR\n" + BaselinePreamble).str()); Annotations NewCode(BaselinePreamble); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should point at the NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAre(Code.range())); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } { // Drop line with diags. Annotations Code(BaselinePreamble); Annotations NewCode("#define BAR\n#include [[<bar>]]\n#include <baz>\n"); auto AST = createPatchedAST(Code.code(), NewCode.code()); - // FIXME: We should only have the range from NewCode. - EXPECT_THAT(mainFileDiagRanges(*AST), - ElementsAreArray({Code.range(), NewCode.range()})); + EXPECT_THAT(mainFileDiagRanges(*AST), ElementsAreArray(NewCode.ranges())); } } } // namespace Index: clang-tools-extra/clangd/Preamble.h =================================================================== --- clang-tools-extra/clangd/Preamble.h +++ clang-tools-extra/clangd/Preamble.h @@ -157,6 +157,9 @@ /// Whether diagnostics generated using this patch are trustable. bool preserveDiagnostics() const; + /// Returns diag locations for Modified contents, only contains diags attached + /// to an #include or #define directive. + std::vector<Diag> patchedDiags() const { return PatchedDiags; } static constexpr llvm::StringLiteral HeaderName = "__preamble_patch__.h"; private: @@ -168,9 +171,11 @@ PreamblePatch() = default; std::string PatchContents; std::string PatchFileName; - /// Includes that are present in both \p Baseline and \p Modified. Used for - /// patching includes of baseline preamble. + // Includes that are present in both \p Baseline and \p Modified. Used for + // patching includes of baseline preamble. std::vector<Inclusion> PreambleIncludes; + // Diags that were attached to an #include or a #define directive. + std::vector<Diag> PatchedDiags; PreambleBounds ModifiedBounds = {0, false}; }; Index: clang-tools-extra/clangd/Preamble.cpp =================================================================== --- clang-tools-extra/clangd/Preamble.cpp +++ clang-tools-extra/clangd/Preamble.cpp @@ -9,6 +9,7 @@ #include "Preamble.h" #include "Compiler.h" #include "Config.h" +#include "Diagnostics.h" #include "Headers.h" #include "SourceCode.h" #include "clang-include-cleaner/Record.h" @@ -35,6 +36,8 @@ #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" @@ -43,7 +46,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/VirtualFileSystem.h" #include "llvm/Support/raw_ostream.h" -#include <iterator> +#include <map> #include <memory> #include <string> #include <system_error> @@ -465,6 +468,19 @@ WallTimer Timer; }; +// Represents an include as spelled in the source code, without taking resolved +// path or location into account. +struct IncludeKey { + tok::PPKeywordKind Directive; + llvm::StringRef Written; + + bool operator<(const IncludeKey &RHS) const { + return std::tie(Directive, Written) < std::tie(RHS.Directive, RHS.Written); + } + bool operator==(const IncludeKey &RHS) const { + return std::tie(Directive, Written) == std::tie(RHS.Directive, RHS.Written); + } +}; } // namespace std::shared_ptr<const PreambleData> @@ -612,6 +628,70 @@ } } +// Checks if all pointers in \p D are for the same line of the main file. +static bool diagReferencesMultipleLines(const Diag &D) { + int Line = D.Range.start.line; + if (D.Range.end.line != Line) + return true; + bool NotePointsToOutside = llvm::any_of(D.Notes, [&](const Note &N) { + return N.File == D.File && + (N.Range.start.line != Line || N.Range.end.line != Line); + }); + if (NotePointsToOutside) + return true; + bool FixPointsToOutside = llvm::any_of(D.Fixes, [&](const Fix &F) { + for (auto &E : F.Edits) + if (E.range.start.line != Line || E.range.end.line != Line) + return true; + return false; + }); + if (FixPointsToOutside) + return true; + return false; +} + +// Move all references in \p D to \p NewLine. This assumes none of the +// references to the main file are multi-line. +static Diag translateDiag(const Diag &D, int NewLine) { + assert(!diagReferencesMultipleLines(D)); + Diag Translated = D; + Translated.Range.start.line = Translated.Range.end.line = NewLine; + for (auto &N : Translated.Notes) { + if (N.File != D.File) + continue; + N.Range.start.line = N.Range.end.line = NewLine; + } + for (auto &F : Translated.Fixes) { + for (auto &E : F.Edits) + E.range.start.line = E.range.end.line = NewLine; + } + return Translated; +} + +static llvm::DenseMap<int, llvm::SmallVector<const Diag *>> +mapDiagsToLines(llvm::ArrayRef<Diag> Diags) { + llvm::DenseMap<int, llvm::SmallVector<const Diag *>> LineToDiags; + for (auto &D : Diags) { + if (diagReferencesMultipleLines(D)) + continue; + LineToDiags[D.Range.start.line].emplace_back(&D); + } + return LineToDiags; +} + +static std::map<IncludeKey, const Inclusion *> +getExistingIncludes(const ScannedPreamble &BaselineScan, + llvm::ArrayRef<Inclusion> MFI) { + std::map<IncludeKey, const Inclusion *> ExistingIncludes; + // There might be includes coming from disabled regions, record these for + // exclusion. note that we don't have resolved paths for those. + for (const auto &Inc : BaselineScan.Includes) + ExistingIncludes[{Inc.Directive, Inc.Written}] = nullptr; + for (const auto &Inc : MFI) + ExistingIncludes[{Inc.Directive, Inc.Written}] = &Inc; + return ExistingIncludes; +} + PreamblePatch PreamblePatch::create(llvm::StringRef FileName, const ParseInputs &Modified, const PreambleData &Baseline, @@ -665,26 +745,37 @@ escapeBackslashAndQuotes(FileName, Patch); Patch << "\"\n"; + // List of diagnostics associated to a particular line in the baseline. + auto LineToDiags = mapDiagsToLines(Baseline.Diags); + auto MoveDiagsBetweenLines = [&LineToDiags, &PP](int OldLine, int NewLine) { + auto DiagsForInclude = LineToDiags.find(OldLine); + if (DiagsForInclude == LineToDiags.end()) + return; + for (auto *D : DiagsForInclude->second) + PP.PatchedDiags.emplace_back(translateDiag(*D, NewLine)); + }; + // Map from an include to its line in the Modified contents. + std::map<IncludeKey, int> IncludeToPatchedLine; if (IncludesChanged && PatchType == PatchType::All) { // We are only interested in newly added includes, record the ones in // Baseline for exclusion. - llvm::DenseMap<std::pair<tok::PPKeywordKind, llvm::StringRef>, - /*Resolved=*/llvm::StringRef> - ExistingIncludes; - for (const auto &Inc : Baseline.Includes.MainFileIncludes) - ExistingIncludes[{Inc.Directive, Inc.Written}] = Inc.Resolved; - // There might be includes coming from disabled regions, record these for - // exclusion too. note that we don't have resolved paths for those. - for (const auto &Inc : BaselineScan->Includes) - ExistingIncludes.try_emplace({Inc.Directive, Inc.Written}); + auto ExistingIncludes = + getExistingIncludes(*BaselineScan, Baseline.Includes.MainFileIncludes); // Calculate extra includes that needs to be inserted. for (auto &Inc : ModifiedScan->Includes) { auto It = ExistingIncludes.find({Inc.Directive, Inc.Written}); // Include already present in the baseline preamble. Set resolved path and // put into preamble includes. if (It != ExistingIncludes.end()) { - Inc.Resolved = It->second.str(); + if (It->second) { + // Copy everything from existing include, apart from the location. + Inc.Resolved = It->second->Resolved; + Inc.HeaderID = It->second->HeaderID; + Inc.BehindPragmaKeep = It->second->BehindPragmaKeep; + Inc.FileKind = It->second->FileKind; + } PP.PreambleIncludes.push_back(Inc); + IncludeToPatchedLine[It->first] = Inc.HashLine; continue; } // Include is new in the modified preamble. Inject it into the patch and @@ -694,8 +785,29 @@ Patch << llvm::formatv( "#{0} {1}\n", spellingForIncDirective(Inc.Directive), Inc.Written); } + } else { + // Copy include information from baseline preamble if nothing has changed. + PP.PreambleIncludes = Baseline.Includes.MainFileIncludes; + } + + // Patch locations for diagnostics on baseline includes. + for (const auto &Inc : Baseline.Includes.MainFileIncludes) { + // Use existing locations if includes haven't changed. + size_t NewLine = Inc.HashLine; + if (IncludesChanged) { + auto PatchedLine = + IncludeToPatchedLine.find({Inc.Directive, Inc.Written}); + // Ignore diags for deleted includes. + if (PatchedLine == IncludeToPatchedLine.end()) + continue; + NewLine = PatchedLine->second; + } + MoveDiagsBetweenLines(Inc.HashLine, NewLine); } + // Maps a directive from its spelling to its location in the Modified + // contents. + llvm::StringMap<int> DirectiveToPatchedLine; if (DirectivesChanged) { // We need to patch all the directives, since they are order dependent. e.g: // #define BAR(X) NEW(X) // Newly introduced in Modified @@ -715,8 +827,23 @@ Patch << "#undef " << TD.MacroName << '\n'; Patch << "#line " << TD.DirectiveLine << '\n'; Patch << TD.Text << '\n'; + DirectiveToPatchedLine[TD.Text] = TD.DirectiveLine; } + } else { + // Take existing directives as-is, if they're the same. + for (const auto &TD : BaselineScan->TextualDirectives) + DirectiveToPatchedLine[TD.Text] = TD.DirectiveLine; } + + // Patch locations for diagnositcs on baseline macros. + for (const auto &TD : BaselineScan->TextualDirectives) { + // Textual directive lines are 1-based. + auto NewLine = DirectiveToPatchedLine.find(TD.Text); + if (NewLine == DirectiveToPatchedLine.end()) + continue; + MoveDiagsBetweenLines(TD.DirectiveLine - 1, NewLine->second - 1); + } + dlog("Created preamble patch: {0}", Patch.str()); Patch.flush(); return PP; @@ -758,6 +885,7 @@ PreamblePatch PP; PP.PreambleIncludes = Preamble.Includes.MainFileIncludes; PP.ModifiedBounds = Preamble.Preamble.getBounds(); + PP.PatchedDiags = Preamble.Diags; return PP; } Index: clang-tools-extra/clangd/ParsedAST.cpp =================================================================== --- clang-tools-extra/clangd/ParsedAST.cpp +++ clang-tools-extra/clangd/ParsedAST.cpp @@ -674,9 +674,10 @@ if (PreserveDiags) { Diags = CompilerInvocationDiags; // Add diagnostics from the preamble, if any. - if (Preamble) - Diags->insert(Diags->end(), Preamble->Diags.begin(), - Preamble->Diags.end()); + if (Preamble) { + auto PDiags = Patch->patchedDiags(); + Diags->insert(Diags->end(), PDiags.begin(), PDiags.end()); + } // Finally, add diagnostics coming from the AST. { std::vector<Diag> D = ASTDiags.take(&*CTContext);
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits