ken-matsui updated this revision to Diff 427262.
ken-matsui added a comment.
Herald added a project: LLVM.
Herald added a subscriber: llvm-commits.

Update codes as reviewed


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D124726/new/

https://reviews.llvm.org/D124726

Files:
  clang/include/clang/Basic/DiagnosticLexKinds.td
  clang/include/clang/Lex/Preprocessor.h
  clang/lib/Lex/PPDirectives.cpp
  clang/test/Preprocessor/suggest-typoed-directive.c
  llvm/include/llvm/ADT/StringRef.h
  llvm/unittests/ADT/StringRefTest.cpp

Index: llvm/unittests/ADT/StringRefTest.cpp
===================================================================
--- llvm/unittests/ADT/StringRefTest.cpp
+++ llvm/unittests/ADT/StringRefTest.cpp
@@ -566,6 +566,13 @@
 }
 
 TEST(StringRefTest, EditDistance) {
+  EXPECT_EQ(0U, StringRef("aaaa").edit_distance("aaaa"));
+  EXPECT_EQ(1U, StringRef("aaaa").edit_distance("aaab"));
+  EXPECT_EQ(2U, StringRef("aabc").edit_distance("aacb"));
+  EXPECT_EQ(2U, StringRef("aabc").edit_distance("abca"));
+  EXPECT_EQ(3U, StringRef("aabc").edit_distance("adef"));
+  EXPECT_EQ(4U, StringRef("abcd").edit_distance("efgh"));
+
   StringRef Hello("hello");
   EXPECT_EQ(2U, Hello.edit_distance("hill"));
 
@@ -584,6 +591,40 @@
                                        "people soiled our green "));
 }
 
+TEST(StringRefTest, FindSimilarStr) {
+  {
+    constexpr StringRef Candidates[] = {"aaab", "aaabc"};
+    EXPECT_EQ(std::string("aaab"), StringRef("aaaa").find_similar_str(Candidates));
+  }
+  {
+    constexpr StringRef Candidates[] = {"aab", "abc"};
+    EXPECT_EQ(std::string("aab"), StringRef("aaa").find_similar_str(Candidates));
+  }
+  {
+    constexpr StringRef Candidates[] = {"ab", "bc"};
+    EXPECT_EQ(std::string("ab"), StringRef("aa").find_similar_str(Candidates));
+  }
+  {
+    constexpr StringRef Candidates[] = {"b", "c"};
+    EXPECT_EQ(None, StringRef("a").find_similar_str(Candidates));
+  }
+  { // macros
+    constexpr StringRef Candidates[] = {
+        "if", "ifdef", "ifndef", "elif", "elifdef", "elifndef", "else", "endif"
+    };
+    EXPECT_EQ(std::string("elifdef"), StringRef("elfidef").find_similar_str(Candidates));
+    EXPECT_EQ(std::string("elifdef"), StringRef("elifdef").find_similar_str(Candidates));
+    EXPECT_EQ(None, StringRef("special_compiler_directive").find_similar_str(Candidates));
+  }
+  { // case-insensitive
+    constexpr StringRef Candidates[] = {
+        "if", "ifdef", "ifndef", "elif", "elifdef", "elifndef", "else", "endif"
+    };
+    EXPECT_EQ(std::string("elifdef"), StringRef("elifdef").find_similar_str(Candidates));
+    EXPECT_EQ(std::string("elifdef"), StringRef("ELIFDEF").find_similar_str(Candidates));
+  }
+}
+
 TEST(StringRefTest, Misc) {
   std::string Storage;
   raw_string_ostream OS(Storage);
Index: llvm/include/llvm/ADT/StringRef.h
===================================================================
--- llvm/include/llvm/ADT/StringRef.h
+++ llvm/include/llvm/ADT/StringRef.h
@@ -10,6 +10,7 @@
 #define LLVM_ADT_STRINGREF_H
 
 #include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/Optional.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/iterator_range.h"
 #include "llvm/Support/Compiler.h"
@@ -24,6 +25,7 @@
 #endif
 #include <type_traits>
 #include <utility>
+#include <vector>
 
 // Declare the __builtin_strlen intrinsic for MSVC so it can be used in
 // constexpr context.
@@ -240,6 +242,46 @@
     unsigned edit_distance(StringRef Other, bool AllowReplacements = true,
                            unsigned MaxEditDistance = 0) const;
 
+    /// Find a similar string in `Candidates`.
+    template <size_t Size>
+    Optional<std::string> find_similar_str(const StringRef (&Candidates)[Size],
+                                           size_t Dist = 0) const {
+      // We need to check if `rng` has the exact case-insensitive string because the
+      // Levenshtein distance match does not care about it.
+      for (StringRef C : Candidates) {
+        if (equals_insensitive(C)) {
+          return C.str();
+        }
+      }
+
+      // Keep going with the Levenshtein distance match.
+      // If dist is given, use the dist for maxDist; otherwise, if word size is
+      // less than 3, use the word size minus 1 and if not, use the word size
+      // divided by 3.
+      size_t MaxDist = Dist != 0    ? Dist
+                       : Length < 3 ? Length - 1
+                                    : Length / 3;
+
+      std::vector<std::pair<std::string, size_t>> Cand;
+      for (StringRef C : Candidates) {
+        size_t CurDist = edit_distance(C, false);
+        if (CurDist <= MaxDist) {
+          Cand.emplace_back(C, CurDist);
+        }
+      }
+
+      if (Cand.empty()) {
+        return None;
+      } else if (Cand.size() == 1) {
+        return Cand[0].first;
+      } else {
+        auto SimilarStr = std::min_element(
+            Cand.cbegin(), Cand.cend(),
+            [](const auto &A, const auto &B) { return A.second < B.second; });
+        return SimilarStr->first;
+      }
+    }
+
     /// str - Get the contents as an std::string.
     LLVM_NODISCARD
     std::string str() const {
Index: clang/test/Preprocessor/suggest-typoed-directive.c
===================================================================
--- /dev/null
+++ clang/test/Preprocessor/suggest-typoed-directive.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c2x -fsyntax-only -verify %s
+
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#if'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#if'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#ifdef'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#elifdef'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#elifndef'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#else'?}}
+// expected-warning@+11 {{invalid preprocessing directive, did you mean '#endif'?}}
+#ifdef UNDEFINED
+#id
+#ifd
+#ifde
+#elid
+#elsif
+#elseif
+#elfidef
+#elfinndef
+#elsi
+#endi
+#endif
+
+#if special_compiler
+#special_compiler_directive // no diagnostics
+#endif
Index: clang/lib/Lex/PPDirectives.cpp
===================================================================
--- clang/lib/Lex/PPDirectives.cpp
+++ clang/lib/Lex/PPDirectives.cpp
@@ -33,16 +33,17 @@
 #include "clang/Lex/Token.h"
 #include "clang/Lex/VariadicMacroSupport.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/StringSwitch.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/AlignOf.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Path.h"
 #include <algorithm>
+#include <array>
 #include <cassert>
 #include <cstring>
 #include <new>
@@ -433,6 +434,24 @@
   return BytesToSkip - LengthDiff;
 }
 
+/// SuggestTypoedDirective - Provide a suggestion for a typoed directive. If
+/// there is no typo, then just skip suggesting.
+void Preprocessor::SuggestTypoedDirective(const Token &Tok, StringRef Directive,
+                                          const SourceLocation &EndLoc) {
+  constexpr StringRef Candidates[] = {
+      "if", "ifdef", "ifndef", "elif", "elifdef", "elifndef", "else", "endif"
+  };
+
+  if (auto Sugg = Directive.find_similar_str(Candidates)) {
+    CharSourceRange DirectiveRange =
+        CharSourceRange::getCharRange(Tok.getLocation(), EndLoc);
+    std::string SuggValue = Sugg.getValue();
+
+    auto Hint = FixItHint::CreateReplacement(DirectiveRange, "#" + SuggValue);
+    Diag(Tok, diag::warn_pp_invalid_directive) << 1 << SuggValue << Hint;
+  }
+}
+
 /// SkipExcludedConditionalBlock - We just read a \#if or related directive and
 /// decided that the subsequent tokens are in the \#if'd out portion of the
 /// file.  Lex the rest of the file, until we see an \#endif.  If
@@ -556,6 +575,8 @@
         CurPPLexer->pushConditionalLevel(Tok.getLocation(), /*wasskipping*/true,
                                        /*foundnonskip*/false,
                                        /*foundelse*/false);
+      } else {
+        SuggestTypoedDirective(Tok, Directive, endLoc);
       }
     } else if (Directive[0] == 'e') {
       StringRef Sub = Directive.substr(1);
@@ -705,7 +726,11 @@
             break;
           }
         }
+      } else {
+        SuggestTypoedDirective(Tok, Directive, endLoc);
       }
+    } else {
+      SuggestTypoedDirective(Tok, Directive, endLoc);
     }
 
     CurPPLexer->ParsingPreprocessorDirective = false;
@@ -1182,7 +1207,7 @@
   }
 
   // If we reached here, the preprocessing token is not valid!
-  Diag(Result, diag::err_pp_invalid_directive);
+  Diag(Result, diag::err_pp_invalid_directive) << 0;
 
   // Read the rest of the PP line.
   DiscardUntilEndOfDirective();
Index: clang/include/clang/Lex/Preprocessor.h
===================================================================
--- clang/include/clang/Lex/Preprocessor.h
+++ clang/include/clang/Lex/Preprocessor.h
@@ -2241,6 +2241,11 @@
   /// Return true if an error occurs parsing the arg list.
   bool ReadMacroParameterList(MacroInfo *MI, Token& LastTok);
 
+  /// Provide a suggestion for a typoed directive. If there is no typo, then
+  /// just skip suggesting.
+  void SuggestTypoedDirective(const Token &Tok, StringRef Directive,
+                              const SourceLocation &endLoc);
+
   /// We just read a \#if or related directive and decided that the
   /// subsequent tokens are in the \#if'd out portion of the
   /// file.  Lex the rest of the file, until we see an \#endif.  If \p
Index: clang/include/clang/Basic/DiagnosticLexKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticLexKinds.td
+++ clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -427,7 +427,12 @@
 def ext_pp_opencl_variadic_macros : Extension<
   "variadic macros are a Clang extension in OpenCL">;
 
-def err_pp_invalid_directive : Error<"invalid preprocessing directive">;
+def err_pp_invalid_directive : Error<
+  "invalid preprocessing directive%select{|, did you mean '#%1'?}0"
+>;
+def warn_pp_invalid_directive : Warning<
+  err_pp_invalid_directive.Text>, InGroup<DiagGroup<"unknown-directives">>;
+
 def err_pp_directive_required : Error<
   "%0 must be used within a preprocessing directive">;
 def err_pp_file_not_found : Error<"'%0' file not found">, DefaultFatal;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to