As of r247522, fix-it-hints can suggest the insertion of new lines.

This patch updates -Wimplicit-fallthrough to provide suggestions
with fix-it hints, showing the user where to insert "break;" or
fallthrough attributes.

For example:

 test.c: In function 'set_x':
 test.c:15:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
        x = a;
        ~~^~~
 test.c:22:5: note: here
      case 'b':
      ^~~~
 test.c:22:5: note: insert '__attribute__ ((fallthrough));' to silence this 
warning
 +        __attribute__ ((fallthrough));
      case 'b':
      ^~~~
 test.c:22:5: note: insert 'break;' to avoid fall-through
 +        break;
      case 'b':
      ^~~~

The idea is that if an IDE supports -fdiagnostics-parseable-fixits, the
user can fix these issues by clicking on them.

It's loosely based on part of Marek's patch:
  https://gcc.gnu.org/ml/gcc-patches/2016-08/msg01948.html
but with extra logic for locating a suitable place to insert the
fix-it hint, so that, if possible, it is on a line by itself before
the "case", indented the same as the prior statement.  If that's not
possible, no fix-it hint is emitted.

In Marek's patch he wrote:
  /* For C++17, we'd recommend [[fallthrough]];, but we're not
     there yet.  For C++11, recommend [[gnu::fallthrough]];.  */
"[[fallthrough]]" appears to work, but it appears that lang_hooks.name
doesn't expose C++17 yet, so the patch recommends [[gnu::fallthrough]
for C++17.


Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu.

OK for trunk?

gcc/ChangeLog:
        PR c/7652
        * diagnostic.c (diagnostic_report_diagnostic): Only add fixits
        to the edit_context if they can be auto-applied.
        * gimplify.c (add_newline_fixit_with_indentation): New function.
        (warn_implicit_fallthrough_r): Add suggestions on how to fix
        -Wimplicit-fallthrough.

gcc/testsuite/ChangeLog:
        PR c/7652
        * g++.dg/Wimplicit-fallthrough-fixit-c++11.C: New test case.
        * g++.dg/Wimplicit-fallthrough-fixit-c++98.C: New test case.
        * gcc.dg/Wimplicit-fallthrough-fixit.c: New test case.

libcpp/ChangeLog:
        PR c/7652
        * include/line-map.h
        (rich_location::fixits_cannot_be_auto_applied): New method.
        (rich_location::fixits_can_be_auto_applied_p): New accessor.
        (rich_location::m_fixits_cannot_be_auto_applied): New field.
        * line-map.c (rich_location::rich_location): Initialize new field.
---
 gcc/diagnostic.c                                   |  3 +-
 gcc/gimplify.c                                     | 90 +++++++++++++++++++++-
 .../g++.dg/Wimplicit-fallthrough-fixit-c++11.C     | 43 +++++++++++
 .../g++.dg/Wimplicit-fallthrough-fixit-c++98.C     | 43 +++++++++++
 gcc/testsuite/gcc.dg/Wimplicit-fallthrough-fixit.c | 73 ++++++++++++++++++
 libcpp/include/line-map.h                          | 22 ++++++
 libcpp/line-map.c                                  |  3 +-
 7 files changed, 274 insertions(+), 3 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++11.C
 create mode 100644 gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++98.C
 create mode 100644 gcc/testsuite/gcc.dg/Wimplicit-fallthrough-fixit.c

diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index dc81755..40509f1 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -942,7 +942,8 @@ diagnostic_report_diagnostic (diagnostic_context *context,
   diagnostic->x_data = NULL;
 
   if (context->edit_context_ptr)
-    context->edit_context_ptr->add_fixits (diagnostic->richloc);
+    if (diagnostic->richloc->fixits_can_be_auto_applied_p ())
+      context->edit_context_ptr->add_fixits (diagnostic->richloc);
 
   context->lock--;
 
diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index fd27eb1..19dd6dc 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -2038,6 +2038,60 @@ should_warn_for_implicit_fallthrough 
(gimple_stmt_iterator *gsi_p, tree label)
   return true;
 }
 
+/* Attempt to add a fix-it hint to RICHLOC, suggesting the insertion
+   of a new line containing LINE_CONTENT (which does not need
+   a '\n' character).
+   The new line is to be inserted immediately before the
+   line containing NEXT, using the location of PREV to determine
+   indentation.
+   The new line will only be inserted if there is nothing on
+   NEXT's line before NEXT, and we can determine the indentation
+   of PREV.  */
+
+static void
+add_newline_fixit_with_indentation (rich_location *richloc,
+                                   location_t prev,
+                                   location_t next,
+                                   const char *line_content)
+{
+  /* Check that the line containing NEXT is blank, up to NEXT.  */
+  int line_width;
+  expanded_location exploc_next = expand_location (next);
+  const char *line
+    = location_get_source_line (exploc_next.file, exploc_next.line,
+                               &line_width);
+  if (!line)
+    return;
+  if (line_width < exploc_next.column)
+    return;
+  /* Columns are 1-based.  */
+  for (int column = 1; column < exploc_next.column; ++column)
+    if (!ISSPACE (line[column - 1]))
+      return;
+
+  /* Locate the start of the line containing NEXT.  */
+  const line_map *map = linemap_lookup (line_table, next);
+  if (linemap_macro_expansion_map_p (map))
+    return;
+  const line_map_ordinary *ordmap = linemap_check_ordinary (map);
+  location_t start_of_line
+    = linemap_position_for_line_and_column (line_table, ordmap,
+                                           exploc_next.line, 1);
+
+  /* Generate an insertion string, indenting by the amount PREV
+     was indented.  */
+  int prev_column = LOCATION_COLUMN (prev);
+  pretty_printer tmp_pp;
+  pretty_printer *pp = &tmp_pp;
+  /* Columns are 1-based.  */
+  for (int column = 1; column < prev_column; ++column)
+    pp_space (pp);
+  pp_string (pp, line_content);
+  pp_newline (pp);
+
+  richloc->add_fixit_insert_before (start_of_line, pp_formatted_text (pp));
+}
+
 /* Callback for walk_gimple_seq.  */
 
 static tree
@@ -2111,7 +2165,41 @@ warn_implicit_fallthrough_r (gimple_stmt_iterator 
*gsi_p, bool *handled_ops_p,
                                     OPT_Wimplicit_fallthrough_,
                                     "this statement may fall through");
            if (warned_p)
-             inform (gimple_location (next), "here");
+             {
+               inform (gimple_location (next), "here");
+
+               /* Suggestion one: add attribute fallthrough.  */
+               rich_location rloc_attr (line_table, gimple_location (next));
+               const char *attr_hint;
+               /* For C++17, we'd recommend [[fallthrough]];, but we're not
+                  there yet.  For C++11, recommend [[gnu::fallthrough]];.  */
+               if (strncmp (lang_hooks.name, "GNU C++14", 9) == 0
+                   || strncmp (lang_hooks.name, "GNU C++11", 9) == 0)
+                 attr_hint = "[[gnu::fallthrough]];";
+               else
+                 /* Otherwise, recommend __attribute__ ((fallthrough));.  */
+                 attr_hint = "__attribute__ ((fallthrough));";
+
+               add_newline_fixit_with_indentation (&rloc_attr,
+                                                   gimple_location (prev),
+                                                   gimple_location (next),
+                                                   attr_hint);
+               rloc_attr.fixits_cannot_be_auto_applied ();
+               inform_at_rich_loc (&rloc_attr,
+                                   "insert %qs to silence this warning",
+                                   attr_hint);
+
+               /* Suggestion two: add "break;".  */
+               rich_location rloc_break (line_table, gimple_location (next));
+               add_newline_fixit_with_indentation (&rloc_break,
+                                                   gimple_location (prev),
+                                                   gimple_location (next),
+                                                   "break;");
+               rloc_break.fixits_cannot_be_auto_applied ();
+               inform_at_rich_loc (&rloc_break,
+                                   "insert %qs to avoid fall-through",
+                                   "break;");
+             }
 
            /* Mark this label as processed so as to prevent multiple
               warnings in nested switches.  */
diff --git a/gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++11.C 
b/gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++11.C
new file mode 100644
index 0000000..afc808e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++11.C
@@ -0,0 +1,43 @@
+/* PR c/7652 */
+/* { dg-do compile } */
+/* { dg-options "-Wimplicit-fallthrough -std=c++11 -fdiagnostics-show-caret 
-fdiagnostics-generate-patch" } */
+/* We specify -fdiagnostics-generate-patch, but no patch should be
+   generated, since user-intervention is required to choose between
+   two different fix-it hints.  */
+
+int x;
+
+void set_x (char ch, int a, int b)
+{
+  switch (ch)
+    {
+    case 'a':
+      x = a; /* { dg-warning "statement may fall through" } */
+      /* { dg-begin-multiline-output "" }
+       x = a;
+       ~~^~~
+       { dg-end-multiline-output "" } */
+
+    case 'b':    /* { dg-line second_case } */
+      x = b;
+      /* { dg-message "here" "" { target *-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* { dg-message "insert '..gnu::fallthrough..;' to silence this warning" 
"" { target *-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
++        [[gnu::fallthrough]];
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* { dg-message "insert 'break;' to avoid fall-through" "" { target 
*-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
++        break;
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+    }
+}
diff --git a/gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++98.C 
b/gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++98.C
new file mode 100644
index 0000000..8730b4c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/Wimplicit-fallthrough-fixit-c++98.C
@@ -0,0 +1,43 @@
+/* PR c/7652 */
+/* { dg-do compile } */
+/* { dg-options "-Wimplicit-fallthrough -std=c++98 -fdiagnostics-show-caret 
-fdiagnostics-generate-patch" } */
+/* We specify -fdiagnostics-generate-patch, but no patch should be
+   generated, since user-intervention is required to choose between
+   two different fix-it hints.  */
+
+int x;
+
+void set_x (char ch, int a, int b)
+{
+  switch (ch)
+    {
+    case 'a':
+      x = a; /* { dg-warning "statement may fall through" } */
+      /* { dg-begin-multiline-output "" }
+       x = a;
+       ~~^~~
+       { dg-end-multiline-output "" } */
+
+    case 'b':    /* { dg-line second_case } */
+      x = b;
+      /* { dg-message "here" "" { target *-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* { dg-message "insert '__attribute__ \\(\\(fallthrough\\)\\);' to 
silence this warning" "" { target *-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
++        __attribute__ ((fallthrough));
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* { dg-message "insert 'break;' to avoid fall-through" "" { target 
*-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
++        break;
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/Wimplicit-fallthrough-fixit.c 
b/gcc/testsuite/gcc.dg/Wimplicit-fallthrough-fixit.c
new file mode 100644
index 0000000..53daca6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wimplicit-fallthrough-fixit.c
@@ -0,0 +1,73 @@
+/* PR c/7652 */
+/* { dg-do compile } */
+/* { dg-options "-Wimplicit-fallthrough -fdiagnostics-show-caret 
-fdiagnostics-generate-patch" } */
+/* We specify -fdiagnostics-generate-patch, but no patch should be
+   generated, since user-intervention is required to choose between
+   two different fix-it hints.  */
+
+int x;
+
+void set_x (char ch, int a, int b)
+{
+  switch (ch)
+    {
+    case 'a':
+      x = a; /* { dg-warning "statement may fall through" } */
+      /* { dg-begin-multiline-output "" }
+       x = a;
+       ~~^~~
+       { dg-end-multiline-output "" } */
+
+
+    case 'b':    /* { dg-line second_case } */
+      x = b;
+      /* { dg-message "here" "" { target *-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* { dg-message "insert '__attribute__ \\(\\(fallthrough\\)\\);' to 
silence this warning" "" { target *-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
++        __attribute__ ((fallthrough));
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* { dg-message "insert 'break;' to avoid fall-through" "" { target 
*-*-* } second_case } */
+      /* { dg-begin-multiline-output "" }
++        break;
+     case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+    }
+}
+
+#define FOO
+
+void set_x_2 (char ch, int a, int b)
+{
+  switch (ch)
+    {
+    case 'a':
+      x = a; /* { dg-warning "statement may fall through" } */
+      /* { dg-begin-multiline-output "" }
+       x = a;
+       ~~^~~
+       { dg-end-multiline-output "" } */
+
+FOO case 'b':    /* { dg-line case_2 } */
+      x = b;
+      /* { dg-message "here" "" { target *-*-* } case_2 } */
+      /* { dg-begin-multiline-output "" }
+ FOO case 'b':
+     ^~~~
+        { dg-end-multiline-output "" } */
+
+      /* This case isn't the first thing on its line, so the suggestions
+        should not provide fix-it hints.  */
+
+      /* { dg-message "insert '__attribute__ \\(\\(fallthrough\\)\\);' to 
silence this warning" "" { target *-*-* } case_2 } */
+      /* { dg-message "insert 'break;' to avoid fall-through" "" { target 
*-*-* } case_2 } */
+    }
+}
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 90d65d7..be3041d 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1663,6 +1663,27 @@ class rich_location
   fixit_hint *get_last_fixit_hint () const;
   bool seen_impossible_fixit_p () const { return m_seen_impossible_fixit; }
 
+  /* Set this if the fix-it hints are not suitable to be
+     automatically applied.
+
+     For example, if you are suggesting more than one
+     mutually exclusive solution to a problem, then
+     it doesn't make sense to apply all of the solutions;
+     manual intervention is required.
+
+     If set, then the fix-it hints in the rich_location will
+     be printed, but will not be added to generated patches,
+     or affect the modified version of the file.  */
+  void fixits_cannot_be_auto_applied ()
+  {
+    m_fixits_cannot_be_auto_applied = true;
+  }
+
+  bool fixits_can_be_auto_applied_p () const
+  {
+    return !m_fixits_cannot_be_auto_applied;
+  }
+
 private:
   bool reject_impossible_fixit (source_location where);
   void stop_supporting_fixits ();
@@ -1686,6 +1707,7 @@ protected:
   semi_embedded_vec <fixit_hint *, MAX_STATIC_FIXIT_HINTS> m_fixit_hints;
 
   bool m_seen_impossible_fixit;
+  bool m_fixits_cannot_be_auto_applied;
 };
 
 /* A fix-it hint: a suggested insertion, replacement, or deletion of text.
diff --git a/libcpp/line-map.c b/libcpp/line-map.c
index c4b7cb2..5caaf6b 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -2012,7 +2012,8 @@ rich_location::rich_location (line_maps *set, 
source_location loc) :
   m_column_override (0),
   m_have_expanded_location (false),
   m_fixit_hints (),
-  m_seen_impossible_fixit (false)
+  m_seen_impossible_fixit (false),
+  m_fixits_cannot_be_auto_applied (false)
 {
   add_range (loc, true);
 }
-- 
1.8.5.3

Reply via email to