Spelling suggestions for misspelled preprocessor directives
authorDavid Malcolm <dmalcolm@redhat.com>
Thu, 18 Aug 2016 18:52:43 +0000 (18:52 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Thu, 18 Aug 2016 18:52:43 +0000 (18:52 +0000)
This patch allows the preprocessor to offer suggestions for misspelled
directives, taking us from e.g.:

test.c:5:2: error: invalid preprocessing directive #endfi
 #endfi
  ^~~~~

to:

test.c:5:2: error: invalid preprocessing directive #endfi; did you mean #endif?
 #endfi
  ^~~~~
  endif

gcc/c-family/ChangeLog:
* c-common.c: Include "spellcheck.h".
(cb_get_suggestion): New function.
* c-common.h (cb_get_suggestion): New decl.
* c-lex.c (init_c_lex): Initialize cb->get_suggestion to
cb_get_suggestion.

gcc/testsuite/ChangeLog:
* gcc.dg/cpp/misspelled-directive-1.c: New testcase.
* gcc.dg/cpp/misspelled-directive-2.c: New testcase.

libcpp/ChangeLog:
* directives.c (directive_names): New array.
(_cpp_handle_directive): Offer spelling suggestions for misspelled
directives.
* errors.c (cpp_diagnostic_at_richloc): New function.
(cpp_error_at_richloc): New function.
* include/cpplib.h (struct cpp_callbacks): Add field
"get_suggestion".
(cpp_error_at_richloc): New decl.

From-SVN: r239585

gcc/c-family/ChangeLog
gcc/c-family/c-common.c
gcc/c-family/c-common.h
gcc/c-family/c-lex.c
gcc/testsuite/ChangeLog
gcc/testsuite/gcc.dg/cpp/misspelled-directive-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/cpp/misspelled-directive-2.c [new file with mode: 0644]
libcpp/ChangeLog
libcpp/directives.c
libcpp/errors.c
libcpp/include/cpplib.h

index cc823701fec53b17916913ea088cf8863a630229..1ed5268a91fa70a11c235e7d3fbd88392e2b0921 100644 (file)
@@ -1,3 +1,11 @@
+2016-08-18  David Malcolm  <dmalcolm@redhat.com>
+
+       * c-common.c: Include "spellcheck.h".
+       (cb_get_suggestion): New function.
+       * c-common.h (cb_get_suggestion): New decl.
+       * c-lex.c (init_c_lex): Initialize cb->get_suggestion to
+       cb_get_suggestion.
+
 2016-08-18  Marek Polacek  <polacek@redhat.com>
 
        PR c/71514
index 22e3844f43ebfca6178b0b5cada3459ce48623ef..9082883cb6852dc0db54ad1ab59aa9d0b8b04abd 100644 (file)
@@ -46,6 +46,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "opts.h"
 #include "gimplify.h"
 #include "substring-locations.h"
+#include "spellcheck.h"
 
 cpp_reader *parse_in;          /* Declared in c-pragma.h.  */
 
@@ -12948,6 +12949,22 @@ cb_get_source_date_epoch (cpp_reader *pfile ATTRIBUTE_UNUSED)
   return (time_t) epoch;
 }
 
+/* Callback for libcpp for offering spelling suggestions for misspelled
+   directives.  GOAL is an unrecognized string; CANDIDATES is a
+   NULL-terminated array of candidate strings.  Return the closest
+   match to GOAL within CANDIDATES, or NULL if none are good
+   suggestions.  */
+
+const char *
+cb_get_suggestion (cpp_reader *, const char *goal,
+                  const char *const *candidates)
+{
+  best_match<const char *, const char *> bm (goal);
+  while (*candidates)
+    bm.consider (*candidates++);
+  return bm.get_best_meaningful_candidate ();
+}
+
 /* Check and possibly warn if two declarations have contradictory
    attributes, such as always_inline vs. noinline.  */
 
index 4673123fa745b733b32fe06d9b78c6ea0fa805fc..31320bf7c877a028f30c8934c8c71808e7a9cb7d 100644 (file)
@@ -1110,6 +1110,11 @@ extern time_t cb_get_source_date_epoch (cpp_reader *pfile);
    __TIME__ can store.  */
 #define MAX_SOURCE_DATE_EPOCH HOST_WIDE_INT_C (253402300799)
 
+/* Callback for libcpp for offering spelling suggestions for misspelled
+   directives.  */
+extern const char *cb_get_suggestion (cpp_reader *, const char *,
+                                     const char *const *);
+
 extern GTY(()) string_concat_db *g_string_concat_db;
 
 /* libcpp can calculate location information about a range of characters
index 4c7e3852142ffe96cd385050add856840a2b17a9..c904ee67fc0b8eae333b4d0d513baf217f3777d2 100644 (file)
@@ -81,6 +81,7 @@ init_c_lex (void)
   cb->read_pch = c_common_read_pch;
   cb->has_attribute = c_common_has_attribute;
   cb->get_source_date_epoch = cb_get_source_date_epoch;
+  cb->get_suggestion = cb_get_suggestion;
 
   /* Set the debug callbacks if we can use them.  */
   if ((debug_info_level == DINFO_LEVEL_VERBOSE
index e37bff498e4d575873f90c8e05ac27fef5836cb7..21e97704db664e6b7e7c7ac029a8b45b71df2cf1 100644 (file)
@@ -1,3 +1,8 @@
+2016-08-18  David Malcolm  <dmalcolm@redhat.com>
+
+       * gcc.dg/cpp/misspelled-directive-1.c: New testcase.
+       * gcc.dg/cpp/misspelled-directive-2.c: New testcase.
+
 2016-08-18  Marek Polacek  <polacek@redhat.com>
 
        PR c/71514
diff --git a/gcc/testsuite/gcc.dg/cpp/misspelled-directive-1.c b/gcc/testsuite/gcc.dg/cpp/misspelled-directive-1.c
new file mode 100644 (file)
index 0000000..f79670a
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef SOME_GUARD /* { dg-error "unterminated" } */
+
+#if 1
+/* Typo here: "endfi" should have been "endif".  */
+#endfi /* { dg-error "invalid preprocessing directive #endfi; did you mean #endif?" } */
+
+int make_non_empty;
+
+/* Another transposition typo:  */
+#deifne FOO /* { dg-error "invalid preprocessing directive #deifne; did you mean #define?" } */ 
+
+#endif /* #ifndef SOME_GUARD */
diff --git a/gcc/testsuite/gcc.dg/cpp/misspelled-directive-2.c b/gcc/testsuite/gcc.dg/cpp/misspelled-directive-2.c
new file mode 100644 (file)
index 0000000..7ec5dee
--- /dev/null
@@ -0,0 +1,21 @@
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+#endfi /* { dg-error "invalid preprocessing directive #endfi; did you mean #endif?" } */
+
+/* Verify that we offer fix-it hints.  */
+/* { dg-begin-multiline-output "" }
+ #endfi
+  ^~~~~
+  endif
+  { dg-end-multiline-output "" } */
+
+/* Test coverage for the case of an unrecognized directive where no suggestion
+   is offered.  */
+
+#this_does_not_match_anything /* { dg-error "invalid preprocessing directive #this_does_not_match_anything" } */
+/* { dg-begin-multiline-output "" }
+ #this_does_not_match_anything
+  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  { dg-end-multiline-output "" } */
+
+int make_non_empty;
index e700dfe31e06cfa2f9877163d522be9188af8346..5d4a09450e0fe55b1718c9cb314fdb40383b1b8f 100644 (file)
@@ -1,3 +1,14 @@
+2016-08-18  David Malcolm  <dmalcolm@redhat.com>
+
+       * directives.c (directive_names): New array.
+       (_cpp_handle_directive): Offer spelling suggestions for misspelled
+       directives.
+       * errors.c (cpp_diagnostic_at_richloc): New function.
+       (cpp_error_at_richloc): New function.
+       * include/cpplib.h (struct cpp_callbacks): Add field
+       "get_suggestion".
+       (cpp_error_at_richloc): New decl.
+
 2016-08-18  Marek Polacek  <polacek@redhat.com>
 
        PR c/7652
index 772b835804bd997c3c7ea457bf3e9b5c379740bc..c0006a49cc489dcc5a39a3340d82f1b0901d1168 100644 (file)
@@ -188,6 +188,16 @@ static const directive dtable[] =
 DIRECTIVE_TABLE
 };
 #undef D
+
+/* A NULL-terminated array of directive names for use
+   when suggesting corrections for misspelled directives.  */
+#define D(name, t, origin, flags) #name,
+static const char * const directive_names[] = {
+DIRECTIVE_TABLE
+  NULL
+};
+#undef D
+
 #undef DIRECTIVE_TABLE
 
 /* Wrapper struct directive for linemarkers.
@@ -498,8 +508,35 @@ _cpp_handle_directive (cpp_reader *pfile, int indented)
       if (CPP_OPTION (pfile, lang) == CLK_ASM)
        skip = 0;
       else if (!pfile->state.skipping)
-       cpp_error (pfile, CPP_DL_ERROR, "invalid preprocessing directive #%s",
-                  cpp_token_as_text (pfile, dname));
+       {
+         const char *unrecognized
+           = (const char *)cpp_token_as_text (pfile, dname);
+         const char *hint = NULL;
+
+         /* Call back into gcc to get a spelling suggestion.  Ideally
+            we'd just use best_match from gcc/spellcheck.h (and filter
+            out the uncommon directives), but that requires moving it
+            to a support library.  */
+         if (pfile->cb.get_suggestion)
+           hint = pfile->cb.get_suggestion (pfile, unrecognized,
+                                            directive_names);
+
+         if (hint)
+           {
+             rich_location richloc (pfile->line_table, dname->src_loc);
+             source_range misspelled_token_range
+               = get_range_from_loc (pfile->line_table, dname->src_loc);
+             richloc.add_fixit_replace (misspelled_token_range, hint);
+             cpp_error_at_richloc (pfile, CPP_DL_ERROR, &richloc,
+                                   "invalid preprocessing directive #%s;"
+                                   " did you mean #%s?",
+                                   unrecognized, hint);
+           }
+         else
+           cpp_error (pfile, CPP_DL_ERROR,
+                      "invalid preprocessing directive #%s",
+                      unrecognized);
+       }
     }
 
   pfile->directive = dir;
index f7d411226eeec215c4f685ca1aa0dbbcfc2cb882..3b0a0b414612aa2cd0ae92a0b297b7009263b385 100644 (file)
@@ -29,6 +29,23 @@ along with this program; see the file COPYING3.  If not see
 
 /* Print a diagnostic at the given location.  */
 
+ATTRIBUTE_FPTR_PRINTF(5,0)
+static bool
+cpp_diagnostic_at_richloc (cpp_reader * pfile, int level, int reason,
+                          rich_location *richloc,
+                          const char *msgid, va_list *ap)
+{
+  bool ret;
+
+  if (!pfile->cb.error)
+    abort ();
+  ret = pfile->cb.error (pfile, level, reason, richloc, _(msgid), ap);
+
+  return ret;
+}
+
+/* Print a diagnostic at the given location.  */
+
 ATTRIBUTE_FPTR_PRINTF(5,0)
 static bool
 cpp_diagnostic_at (cpp_reader * pfile, int level, int reason,
@@ -255,6 +272,25 @@ cpp_error_at (cpp_reader * pfile, int level, source_location src_loc,
   return ret;
 }
 
+/* As cpp_error, but use RICHLOC as the location of the error, without
+   a column override.  */
+
+bool
+cpp_error_at_richloc (cpp_reader * pfile, int level, rich_location *richloc,
+                     const char *msgid, ...)
+{
+  va_list ap;
+  bool ret;
+
+  va_start (ap, msgid);
+
+  ret = cpp_diagnostic_at_richloc (pfile, level, CPP_W_NONE, richloc,
+                                  msgid, &ap);
+
+  va_end (ap);
+  return ret;
+}
+
 /* Print a warning or error, depending on the value of LEVEL.  Include
    information from errno.  */
 
index 659686bf2616ee03a1db13842569f1c223c70d88..a497811eec07b8fc0dcc3b807a62ba110f006268 100644 (file)
@@ -597,6 +597,9 @@ struct cpp_callbacks
 
   /* Callback to parse SOURCE_DATE_EPOCH from environment.  */
   time_t (*get_source_date_epoch) (cpp_reader *);
+
+  /* Callback for providing suggestions for misspelled directives.  */
+  const char *(*get_suggestion) (cpp_reader *, const char *, const char *const *);
 };
 
 #ifdef VMS
@@ -1066,6 +1069,11 @@ extern bool cpp_error_at (cpp_reader * pfile, int level,
                          source_location src_loc, const char *msgid, ...)
   ATTRIBUTE_PRINTF_4;
 
+extern bool cpp_error_at_richloc (cpp_reader * pfile, int level,
+                                 rich_location *richloc, const char *msgid,
+                                 ...)
+  ATTRIBUTE_PRINTF_4;
+
 /* In lex.c */
 extern int cpp_ideq (const cpp_token *, const char *);
 extern void cpp_output_line (cpp_reader *, FILE *);