ken-matsui updated this revision to Diff 428395.
ken-matsui added a comment.

Fix the test for FindSimilarStr


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-c2x-cpp2b.c
  clang/test/Preprocessor/suggest-typoed-directive.c
  llvm/include/llvm/ADT/StringRef.h
  llvm/lib/Support/StringRef.cpp
  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,28 @@
                                        "people soiled our green "));
 }
 
+TEST(StringRefTest, FindSimilarStr) {
+  {
+    std::vector<StringRef> Candidates{"b", "c"};
+    EXPECT_EQ(None, StringRef("a").find_similar_str(Candidates));
+  }
+  { // macros
+    std::vector<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
+    std::vector<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/lib/Support/StringRef.cpp
===================================================================
--- llvm/lib/Support/StringRef.cpp
+++ llvm/lib/Support/StringRef.cpp
@@ -98,6 +98,43 @@
       AllowReplacements, MaxEditDistance);
 }
 
+// Find a similar string in `Candidates`.
+Optional<std::string> StringRef::find_similar_str(const std::vector<StringRef> &Candidates, size_t Dist) 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 the LHS size is less than 3, use the LHS size minus 1
+  // and if not, use the LHS 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;
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // String Operations
 //===----------------------------------------------------------------------===//
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,23 @@
     unsigned edit_distance(StringRef Other, bool AllowReplacements = true,
                            unsigned MaxEditDistance = 0) const;
 
+    /// Find a similar string in `Candidates`.
+    ///
+    /// \param Candidates the candidates to find a similar string.
+    ///
+    /// \param Dist Maximum distance to elect as similar strings. Eventually,
+    /// a string with the smallest distance (= most similar string) among them
+    /// will be returned.
+    /// If non-zero, use it as maximum distance; otherwise, if the LHS size is
+    /// less than 3, use the LHS size minus 1 and if not, use the LHS size
+    /// divided by 3.
+    ///
+    /// \returns a similar string if exists. If no similar string exists,
+    /// returns None.
+    Optional<std::string> find_similar_str(
+        const std::vector<StringRef> &Candidates,
+        size_t Dist = 0) const;
+
     /// 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,28 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// id:       not suggested to '#if'
+// ifd:      expected-warning@+11 {{invalid preprocessing directive, did you mean '#if'?}}
+// ifde:     expected-warning@+11 {{invalid preprocessing directive, did you mean '#ifdef'?}}
+// elify:    expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// elsif:    expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// elseif:   expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// elfidef:  not suggested to '#elifdef'
+// elfindef: not suggested to '#elifndef'
+// elses:    expected-warning@+11 {{invalid preprocessing directive, did you mean '#else'?}}
+// endi:     expected-warning@+11 {{invalid preprocessing directive, did you mean '#endif'?}}
+#ifdef UNDEFINED
+#id
+#ifd
+#ifde
+#elify
+#elsif
+#elseif
+#elfidef
+#elfindef
+#elses
+#endi
+#endif
+
+#if special_compiler
+#special_compiler_directive // no diagnostic
+#endif
Index: clang/test/Preprocessor/suggest-typoed-directive-c2x-cpp2b.c
===================================================================
--- /dev/null
+++ clang/test/Preprocessor/suggest-typoed-directive-c2x-cpp2b.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -std=c2x -fsyntax-only -verify %s
+// RUN: %clang_cc1 -x c++ -std=c++2b -fsyntax-only -verify %s
+
+// id:       not suggested to '#if'
+// ifd:      expected-warning@+11 {{invalid preprocessing directive, did you mean '#if'?}}
+// ifde:     expected-warning@+11 {{invalid preprocessing directive, did you mean '#ifdef'?}}
+// elify:    expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// elsif:    expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// elseif:   expected-warning@+11 {{invalid preprocessing directive, did you mean '#elif'?}}
+// elfidef:  expected-warning@+11 {{invalid preprocessing directive, did you mean '#elifdef'?}}
+// elfindef: expected-warning@+11 {{invalid preprocessing directive, did you mean '#elifndef'?}}
+// elses:    expected-warning@+11 {{invalid preprocessing directive, did you mean '#else'?}}
+// endi:     expected-warning@+11 {{invalid preprocessing directive, did you mean '#endif'?}}
+#ifdef UNDEFINED
+#id
+#ifd
+#ifde
+#elify
+#elsif
+#elseif
+#elfidef
+#elfindef
+#elses
+#endi
+#endif
+
+#if special_compiler
+#special_compiler_directive // no diagnostic
+#endif
Index: clang/lib/Lex/PPDirectives.cpp
===================================================================
--- clang/lib/Lex/PPDirectives.cpp
+++ clang/lib/Lex/PPDirectives.cpp
@@ -433,6 +433,27 @@
   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) {
+  std::vector<StringRef> Candidates = {
+      "if", "ifdef", "ifndef", "elif", "else", "endif"
+  };
+  if (LangOpts.C2x || LangOpts.CPlusPlus2b) {
+    Candidates.insert(Candidates.end(), {"elifdef", "elifndef"});
+  }
+
+  if (Optional<std::string> 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 +577,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 +728,11 @@
             break;
           }
         }
+      } else {
+        SuggestTypoedDirective(Tok, Directive, endLoc);
       }
+    } else {
+      SuggestTypoedDirective(Tok, Directive, endLoc);
     }
 
     CurPPLexer->ParsingPreprocessorDirective = false;
@@ -1182,7 +1209,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