[Lldb-commits] [lldb] 3c2dc28 - [lldb] Also apply Fix-Its in "note:" diagnostics that belong to an error diagnostic

Raphael Isemann via lldb-commits lldb-commits at lists.llvm.org
Mon Apr 6 01:37:55 PDT 2020


Author: Raphael Isemann
Date: 2020-04-06T10:37:33+02:00
New Revision: 3c2dc28d812c917e01f46b3bcf5b5e0a2a803276

URL: https://github.com/llvm/llvm-project/commit/3c2dc28d812c917e01f46b3bcf5b5e0a2a803276
DIFF: https://github.com/llvm/llvm-project/commit/3c2dc28d812c917e01f46b3bcf5b5e0a2a803276.diff

LOG: [lldb] Also apply Fix-Its in "note:" diagnostics that belong to an error diagnostic

Summary:
LLDB currently applies Fix-Its if they are attached to a Clang diagnostic that has the
severity "error". Fix-Its connected to warnings and other severities are supposed to
be ignored as LLDB doesn't seem to trust Clang Fix-Its in these situations.

However, LLDB also ignores all Fix-Its coming from "note:" diagnostics. These diagnostics
are usually emitted alongside other diagnostics (both warnings and errors), either to keep
a single diagnostic message shorter or because the Fix-It is in a different source line. As they
are technically their own (non-error) diagnostics, we currently are ignoring all Fix-Its associated with them.

For example, this is a possible Clang diagnostic with a Fix-It that is currently ignored:
```
error: <user expression 1>:2:10: too many arguments provided to function-like macro invocation
ToStr(0, {,})
         ^
<user expression 1>:1:9: macro 'ToStr' defined here
#define ToStr(x) #x
        ^
<user expression 1>:2:1: cannot use initializer list at the beginning of a macro argument
ToStr(0, {,})
^        ~~~~
```

We also don't store "note:" diagnostics at all, as LLDB's abstraction around the whole diagnostic
concept doesn't have such a concept. The text of "note:" diagnostics is instead
appended to the last non-note diagnostic (which is causing that there is no "note:" text in the
diagnostic above, as all the "note:" diagnostics have been appended to the first "error: ..." text).

This patch fixes the ignored Fix-Its in note-diagnostics by appending them to the last non-note
diagnostic, similar to the way we handle the text in these diagnostics.

Reviewers: JDevlieghere, jingham

Reviewed By: JDevlieghere

Subscribers: abidh

Differential Revision: https://reviews.llvm.org/D77055

Added: 
    

Modified: 
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
    lldb/test/API/commands/expression/fixits/TestFixIts.py

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index e5de4b4651df..9996f2608e31 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -147,6 +147,14 @@ class ClangExpressionParser::LLDBPreprocessorCallbacks : public PPCallbacks {
   llvm::StringRef getErrorString() { return m_error_stream.GetString(); }
 };
 
+static void AddAllFixIts(ClangDiagnostic *diag, const clang::Diagnostic &Info) {
+  for (auto &fix_it : Info.getFixItHints()) {
+    if (fix_it.isNull())
+      continue;
+    diag->AddFixitHint(fix_it);
+  }
+}
+
 class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
 public:
   ClangDiagnosticManagerAdapter(DiagnosticOptions &opts) {
@@ -162,6 +170,17 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
     m_manager = manager;
   }
 
+  /// Returns the last ClangDiagnostic message that the DiagnosticManager
+  /// received or a nullptr if the DiagnosticMangager hasn't seen any
+  /// Clang diagnostics yet.
+  ClangDiagnostic *MaybeGetLastClangDiag() const {
+    if (m_manager->Diagnostics().empty())
+      return nullptr;
+    lldb_private::Diagnostic *diag = m_manager->Diagnostics().back().get();
+    ClangDiagnostic *clang_diag = dyn_cast<ClangDiagnostic>(diag);
+    return clang_diag;
+  }
+
   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
                         const clang::Diagnostic &Info) override {
     if (!m_manager) {
@@ -204,6 +223,23 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
     case DiagnosticsEngine::Level::Note:
       m_manager->AppendMessageToDiagnostic(m_output);
       make_new_diagnostic = false;
+
+      // 'note:' diagnostics for errors and warnings can also contain Fix-Its.
+      // We add these Fix-Its to the last error diagnostic to make sure
+      // that we later have all Fix-Its related to an 'error' diagnostic when
+      // we apply them to the user expression.
+      auto *clang_diag = MaybeGetLastClangDiag();
+      // If we don't have a previous diagnostic there is nothing to do.
+      // If the previous diagnostic already has its own Fix-Its, assume that
+      // the 'note:' Fix-It is just an alternative way to solve the issue and
+      // ignore these Fix-Its.
+      if (!clang_diag || clang_diag->HasFixIts())
+        break;
+      // Ignore all Fix-Its that are not associated with an error.
+      if (clang_diag->GetSeverity() != eDiagnosticSeverityError)
+        break;
+      AddAllFixIts(clang_diag, Info);
+      break;
     }
     if (make_new_diagnostic) {
       // ClangDiagnostic messages are expected to have no whitespace/newlines
@@ -218,13 +254,8 @@ class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
       // enough context in an expression for the warning to be useful.
       // FIXME: Should we try to filter out FixIts that apply to our generated
       // code, and not the user's expression?
-      if (severity == eDiagnosticSeverityError) {
-        for (const clang::FixItHint &fixit : Info.getFixItHints()) {
-          if (fixit.isNull())
-            continue;
-          new_diagnostic->AddFixitHint(fixit);
-        }
-      }
+      if (severity == eDiagnosticSeverityError)
+        AddAllFixIts(new_diagnostic.get(), Info);
 
       m_manager->AddDiagnostic(std::move(new_diagnostic));
     }

diff  --git a/lldb/test/API/commands/expression/fixits/TestFixIts.py b/lldb/test/API/commands/expression/fixits/TestFixIts.py
index eb1dd97aa9a9..8ccb08ebbaa2 100644
--- a/lldb/test/API/commands/expression/fixits/TestFixIts.py
+++ b/lldb/test/API/commands/expression/fixits/TestFixIts.py
@@ -61,6 +61,14 @@ def test_with_target(self):
         self.assertTrue(value.GetError().Success())
         self.assertEquals(value.GetValueAsUnsigned(), 20)
 
+        # Try a Fix-It that is stored in the 'note:' diagnostic of an error.
+        # The Fix-It here is adding parantheses around the ToStr parameters.
+        fixit_in_note_expr ="#define ToStr(x) #x\nToStr(0 {, })"
+        value = frame.EvaluateExpression(fixit_in_note_expr, options)
+        self.assertTrue(value.IsValid())
+        self.assertTrue(value.GetError().Success(), value.GetError())
+        self.assertEquals(value.GetSummary(), '"(0 {, })"')
+
         # Now turn off the fixits, and the expression should fail:
         options.SetAutoApplyFixIts(False)
         value = frame.EvaluateExpression(two_error_expression, options)


        


More information about the lldb-commits mailing list