summaryrefslogtreecommitdiff
path: root/gcc/diagnostic-show-locus.c
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2017-05-03 13:11:21 +0000
committerDavid Malcolm <dmalcolm@gcc.gnu.org>2017-05-03 13:11:21 +0000
commitd1b5f5cc3cfd148e8c479a7c05928e0289ccfadc (patch)
tree4d9a58d881b7cd4c1d8f046cd647dd9de26df3ab /gcc/diagnostic-show-locus.c
parent5bb64c4183643680e4a1b4ccfe920d48c248cb8a (diff)
New fix-it printer
The existing fix-it printer can lead to difficult-to-read output when fix-it hints are near each other. For example, in a recent patch to add fix-it hints to the C++ frontend's -Wold-style-cast, e.g. for: foo *f = (foo *)ptr->field; ^~~~~ the fix-it hints: replace the open paren with "const_cast<" replace the close paren with "> (" insert ")" after the "ptr->field" would be printed in this odd-looking way: foo *f = (foo *)ptr->field; ^~~~~ - const_cast< - > ( ) class rich_location consolidates adjacent fix-it hints, which helps somewhat, but the underlying problem is that the existing printer simply walks through the list of hints printing them, starting newlines as necessary. This patch reimplements fix-it printing by introducing a planning stage: a new class line_corrections "plans" how to print the fix-it hints affecting a line, generating a vec of "correction" instances. Hints that are sufficiently close to each other are consolidated at this stage. This leads to the much more reasonable output for the above case: foo *f = (foo *)ptr->field; ^~~~~ ----------------- const_cast<foo *> (ptr->field); where the 3 hints are consolidated into one "correction" at printing. gcc/ChangeLog: * diagnostic-show-locus.c (struct column_range): New struct. (get_affected_columns): New function. (get_printed_columns): New function. (struct correction): New struct. (correction::ensure_capacity): New function. (correction::ensure_terminated): New function. (struct line_corrections): New struct. (line_corrections::~line_corrections): New dtor. (line_corrections::add_hint): New function. (layout::print_trailing_fixits): Reimplement in terms of the new classes. (selftest::test_overlapped_fixit_printing): New function. (selftest::diagnostic_show_locus_c_tests): Call it. From-SVN: r247548
Diffstat (limited to 'gcc/diagnostic-show-locus.c')
-rw-r--r--gcc/diagnostic-show-locus.c572
1 files changed, 534 insertions, 38 deletions
diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
index b91c9d55da3..f410a324b4b 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -1242,6 +1242,295 @@ layout::annotation_line_showed_range_p (int line, int start_column,
return false;
}
+/* Classes for printing trailing fix-it hints i.e. those that
+ don't add new lines.
+
+ For insertion, these can look like:
+
+ new_text
+
+ For replacement, these can look like:
+
+ ------------- : underline showing affected range
+ new_text
+
+ For deletion, these can look like:
+
+ ------------- : underline showing affected range
+
+ This can become confusing if they overlap, and so we need
+ to do some preprocessing to decide what to print.
+ We use the list of fixit_hint instances affecting the line
+ to build a list of "correction" instances, and print the
+ latter.
+
+ For example, consider a set of fix-its for converting
+ a C-style cast to a C++ const_cast.
+
+ Given:
+
+ ..000000000111111111122222222223333333333.
+ ..123456789012345678901234567890123456789.
+ foo *f = (foo *)ptr->field;
+ ^~~~~
+
+ and the fix-it hints:
+ - replace col 10 (the open paren) with "const_cast<"
+ - replace col 16 (the close paren) with "> ("
+ - insert ")" before col 27
+
+ then we would get odd-looking output:
+
+ foo *f = (foo *)ptr->field;
+ ^~~~~
+ -
+ const_cast<
+ -
+ > ( )
+
+ It would be better to detect when fixit hints are going to
+ overlap (those that require new lines), and to consolidate
+ the printing of such fixits, giving something like:
+
+ foo *f = (foo *)ptr->field;
+ ^~~~~
+ -----------------
+ const_cast<foo *> (ptr->field)
+
+ This works by detecting when the printing would overlap, and
+ effectively injecting no-op replace hints into the gaps between
+ such fix-its, so that the printing joins up.
+
+ In the above example, the overlap of:
+ - replace col 10 (the open paren) with "const_cast<"
+ and:
+ - replace col 16 (the close paren) with "> ("
+ is fixed by injecting a no-op:
+ - replace cols 11-15 with themselves ("foo *")
+ and consolidating these, making:
+ - replace cols 10-16 with "const_cast<" + "foo *" + "> ("
+ i.e.:
+ - replace cols 10-16 with "const_cast<foo *> ("
+
+ This overlaps with the final fix-it hint:
+ - insert ")" before col 27
+ and so we repeat the consolidation process, by injecting
+ a no-op:
+ - replace cols 17-26 with themselves ("ptr->field")
+ giving:
+ - replace cols 10-26 with "const_cast<foo *> (" + "ptr->field" + ")"
+ i.e.:
+ - replace cols 10-26 with "const_cast<foo *> (ptr->field)"
+
+ and is thus printed as desired. */
+
+/* A range of columns within a line. */
+
+struct column_range
+{
+ column_range (int start_, int finish_) : start (start_), finish (finish_) {}
+
+ bool operator== (const column_range &other) const
+ {
+ return start == other.start && finish == other.finish;
+ }
+
+ int start;
+ int finish;
+};
+
+/* Get the range of columns that HINT would affect. */
+
+static column_range
+get_affected_columns (const fixit_hint *hint)
+{
+ int start_column = LOCATION_COLUMN (hint->get_start_loc ());
+ int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1;
+
+ return column_range (start_column, finish_column);
+}
+
+/* Get the range of columns that would be printed for HINT. */
+
+static column_range
+get_printed_columns (const fixit_hint *hint)
+{
+ int start_column = LOCATION_COLUMN (hint->get_start_loc ());
+ int final_hint_column = start_column + hint->get_length () - 1;
+ if (hint->insertion_p ())
+ {
+ return column_range (start_column, final_hint_column);
+ }
+ else
+ {
+ int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1;
+
+ return column_range (start_column,
+ MAX (finish_column, final_hint_column));
+ }
+}
+
+/* A correction on a particular line.
+ This describes a plan for how to print one or more fixit_hint
+ instances that affected the line, potentially consolidating hints
+ into corrections to make the result easier for the user to read. */
+
+struct correction
+{
+ correction (column_range affected_columns,
+ column_range printed_columns,
+ const char *new_text, size_t new_text_len)
+ : m_affected_columns (affected_columns),
+ m_printed_columns (printed_columns),
+ m_text (xstrdup (new_text)),
+ m_len (new_text_len),
+ m_alloc_sz (new_text_len + 1)
+ {
+ }
+
+ ~correction () { free (m_text); }
+
+ bool insertion_p () const
+ {
+ return m_affected_columns.start == m_affected_columns.finish + 1;
+ }
+
+ void ensure_capacity (size_t len);
+ void ensure_terminated ();
+
+ /* If insert, then start: the column before which the text
+ is to be inserted, and finish is offset by the length of
+ the replacement.
+ If replace, then the range of columns affected. */
+ column_range m_affected_columns;
+
+ /* If insert, then start: the column before which the text
+ is to be inserted, and finish is offset by the length of
+ the replacement.
+ If replace, then the range of columns affected. */
+ column_range m_printed_columns;
+
+ /* The text to be inserted/used as replacement. */
+ char *m_text;
+ size_t m_len;
+ size_t m_alloc_sz;
+};
+
+/* Ensure that m_text can hold a string of length LEN
+ (plus 1 for 0-termination). */
+
+void
+correction::ensure_capacity (size_t len)
+{
+ /* Allow 1 extra byte for 0-termination. */
+ if (m_alloc_sz < (len + 1))
+ {
+ size_t new_alloc_sz = (len + 1) * 2;
+ m_text = (char *)xrealloc (m_text, new_alloc_sz);
+ m_alloc_sz = new_alloc_sz;
+ }
+}
+
+/* Ensure that m_text is 0-terminated. */
+
+void
+correction::ensure_terminated ()
+{
+ /* 0-terminate the buffer. */
+ gcc_assert (m_len < m_alloc_sz);
+ m_text[m_len] = '\0';
+}
+
+/* A list of corrections affecting a particular line.
+ This is used by layout::print_trailing_fixits for planning
+ how to print the fix-it hints affecting the line. */
+
+struct line_corrections
+{
+ line_corrections (const char *filename, int row)
+ : m_filename (filename), m_row (row)
+ {}
+ ~line_corrections ();
+
+ void add_hint (const fixit_hint *hint);
+
+ const char *m_filename;
+ int m_row;
+ auto_vec <correction *> m_corrections;
+};
+
+/* struct line_corrections. */
+
+line_corrections::~line_corrections ()
+{
+ unsigned i;
+ correction *c;
+ FOR_EACH_VEC_ELT (m_corrections, i, c)
+ delete c;
+}
+
+/* Add HINT to the corrections for this line.
+ Attempt to consolidate nearby hints so that they will not
+ overlap with printed. */
+
+void
+line_corrections::add_hint (const fixit_hint *hint)
+{
+ column_range affected_columns = get_affected_columns (hint);
+ column_range printed_columns = get_printed_columns (hint);
+
+ /* Potentially consolidate. */
+ if (!m_corrections.is_empty ())
+ {
+ correction *last_correction
+ = m_corrections[m_corrections.length () - 1];
+ if (printed_columns.start <= last_correction->m_printed_columns.finish)
+ {
+ /* We have two hints for which the printed forms of the hints
+ would touch or overlap, so we need to consolidate them to avoid
+ confusing the user.
+ Attempt to inject a "replace" correction from immediately
+ after the end of the last hint to immediately before the start
+ of the next hint. */
+ column_range between (last_correction->m_affected_columns.finish + 1,
+ printed_columns.start - 1);
+
+ /* Try to read the source. */
+ int line_width;
+ const char *line = location_get_source_line (m_filename, m_row,
+ &line_width);
+ if (line && between.finish < line_width)
+ {
+ /* Consolidate into the last correction:
+ add a no-op "replace" of the "between" text, and
+ add the text from the new hint. */
+ size_t old_len = last_correction->m_len;
+ size_t between_len = between.finish + 1 - between.start;
+ size_t new_len = old_len + between_len + hint->get_length ();
+ last_correction->ensure_capacity (new_len);
+ memcpy (last_correction->m_text + old_len,
+ line + between.start - 1,
+ between.finish + 1 - between.start);
+ memcpy (last_correction->m_text + old_len + between_len,
+ hint->get_string (), hint->get_length ());
+ last_correction->m_len = new_len;
+ last_correction->ensure_terminated ();
+ last_correction->m_affected_columns.finish
+ = affected_columns.finish;
+ last_correction->m_printed_columns.finish
+ += between_len + hint->get_length ();
+ return;
+ }
+ }
+ }
+
+ /* If no consolidation happened, add a new correction instance. */
+ m_corrections.safe_push (new correction (affected_columns,
+ printed_columns,
+ hint->get_string (),
+ hint->get_length ()));
+}
+
/* If there are any fixit hints on source line ROW, print them.
They are printed in order, attempting to combine them onto lines, but
starting new lines if necessary.
@@ -1251,58 +1540,67 @@ layout::annotation_line_showed_range_p (int line, int start_column,
void
layout::print_trailing_fixits (int row)
{
- int column = 0;
+ /* Build a list of correction instances for the line,
+ potentially consolidating hints (for the sake of readability). */
+ line_corrections corrections (m_exploc.file, row);
for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
{
const fixit_hint *hint = m_fixit_hints[i];
+ /* Newline fixits are handled by layout::print_leading_fixits. */
if (hint->ends_with_newline_p ())
continue;
if (hint->affects_line_p (m_exploc.file, row))
+ corrections.add_hint (hint);
+ }
+
+ /* Now print the corrections. */
+ unsigned i;
+ correction *c;
+ int column = 0;
+
+ FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
+ {
+ /* For now we assume each fixit hint can only touch one line. */
+ if (c->insertion_p ())
+ {
+ /* This assumes the insertion just affects one line. */
+ int start_column = c->m_printed_columns.start;
+ move_to_column (&column, start_column);
+ m_colorizer.set_fixit_insert ();
+ pp_string (m_pp, c->m_text);
+ m_colorizer.set_normal_text ();
+ column += c->m_len;
+ }
+ else
{
- /* For now we assume each fixit hint can only touch one line. */
- if (hint->insertion_p ())
+ /* If the range of the replacement wasn't printed in the
+ annotation line, then print an extra underline to
+ indicate exactly what is being replaced.
+ Always show it for removals. */
+ int start_column = c->m_affected_columns.start;
+ int finish_column = c->m_affected_columns.finish;
+ if (!annotation_line_showed_range_p (row, start_column,
+ finish_column)
+ || c->m_len == 0)
{
- /* This assumes the insertion just affects one line. */
- int start_column = LOCATION_COLUMN (hint->get_start_loc ());
move_to_column (&column, start_column);
- m_colorizer.set_fixit_insert ();
- pp_string (m_pp, hint->get_string ());
+ m_colorizer.set_fixit_delete ();
+ for (; column <= finish_column; column++)
+ pp_character (m_pp, '-');
m_colorizer.set_normal_text ();
- column += hint->get_length ();
}
- else
+ /* Print the replacement text. REPLACE also covers
+ removals, so only do this extra work (potentially starting
+ a new line) if we have actual replacement text. */
+ if (c->m_len > 0)
{
- int line = LOCATION_LINE (hint->get_start_loc ());
- int start_column = LOCATION_COLUMN (hint->get_start_loc ());
- int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1;
-
- /* If the range of the replacement wasn't printed in the
- annotation line, then print an extra underline to
- indicate exactly what is being replaced.
- Always show it for removals. */
- if (!annotation_line_showed_range_p (line, start_column,
- finish_column)
- || hint->get_length () == 0)
- {
- move_to_column (&column, start_column);
- m_colorizer.set_fixit_delete ();
- for (; column <= finish_column; column++)
- pp_character (m_pp, '-');
- m_colorizer.set_normal_text ();
- }
- /* Print the replacement text. REPLACE also covers
- removals, so only do this extra work (potentially starting
- a new line) if we have actual replacement text. */
- if (hint->get_length () > 0)
- {
- move_to_column (&column, start_column);
- m_colorizer.set_fixit_insert ();
- pp_string (m_pp, hint->get_string ());
- m_colorizer.set_normal_text ();
- column += hint->get_length ();
- }
+ move_to_column (&column, start_column);
+ m_colorizer.set_fixit_insert ();
+ pp_string (m_pp, c->m_text);
+ m_colorizer.set_normal_text ();
+ column += c->m_len;
}
}
}
@@ -2153,6 +2451,203 @@ test_fixit_consolidation (const line_table_case &case_)
}
}
+/* Verify that the line_corrections machinery correctly prints
+ overlapping fixit-hints. */
+
+static void
+test_overlapped_fixit_printing (const line_table_case &case_)
+{
+ /* Create a tempfile and write some text to it.
+ ...000000000111111111122222222223333333333.
+ ...123456789012345678901234567890123456789. */
+ const char *content
+ = (" foo *f = (foo *)ptr->field;\n");
+ temp_source_file tmp (SELFTEST_LOCATION, ".C", content);
+ line_table_test ltt (case_);
+
+ const line_map_ordinary *ord_map
+ = linemap_check_ordinary (linemap_add (line_table, LC_ENTER, false,
+ tmp.get_filename (), 0));
+
+ linemap_line_start (line_table, 1, 100);
+
+ const location_t final_line_end
+ = linemap_position_for_line_and_column (line_table, ord_map, 6, 36);
+
+ /* Don't attempt to run the tests if column data might be unavailable. */
+ if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
+ return;
+
+ /* A test for converting a C-style cast to a C++-style cast. */
+ const location_t open_paren
+ = linemap_position_for_line_and_column (line_table, ord_map, 1, 12);
+ const location_t close_paren
+ = linemap_position_for_line_and_column (line_table, ord_map, 1, 18);
+ const location_t expr_start
+ = linemap_position_for_line_and_column (line_table, ord_map, 1, 19);
+ const location_t expr_finish
+ = linemap_position_for_line_and_column (line_table, ord_map, 1, 28);
+ const location_t expr = make_location (expr_start, expr_start, expr_finish);
+
+ /* Various examples of fix-it hints that aren't themselves consolidated,
+ but for which the *printing* may need consolidation. */
+
+ /* Example where 3 fix-it hints are printed as one. */
+ {
+ test_diagnostic_context dc;
+ rich_location richloc (line_table, expr);
+ richloc.add_fixit_replace (open_paren, "const_cast<");
+ richloc.add_fixit_replace (close_paren, "> (");
+ richloc.add_fixit_insert_after (")");
+
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo *f = (foo *)ptr->field;\n"
+ " ^~~~~~~~~~\n"
+ " -----------------\n"
+ " const_cast<foo *> (ptr->field)\n",
+ pp_formatted_text (dc.printer));
+
+ /* Unit-test the line_corrections machinery. */
+ ASSERT_EQ (3, richloc.get_num_fixit_hints ());
+ const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
+ ASSERT_EQ (column_range (12, 12), get_affected_columns (hint_0));
+ ASSERT_EQ (column_range (12, 22), get_printed_columns (hint_0));
+ const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
+ ASSERT_EQ (column_range (18, 18), get_affected_columns (hint_1));
+ ASSERT_EQ (column_range (18, 20), get_printed_columns (hint_1));
+ const fixit_hint *hint_2 = richloc.get_fixit_hint (2);
+ ASSERT_EQ (column_range (29, 28), get_affected_columns (hint_2));
+ ASSERT_EQ (column_range (29, 29), get_printed_columns (hint_2));
+
+ /* Add each hint in turn to a line_corrections instance,
+ and verify that they are consolidated into one correction instance
+ as expected. */
+ line_corrections lc (tmp.get_filename (), 1);
+
+ /* The first replace hint by itself. */
+ lc.add_hint (hint_0);
+ ASSERT_EQ (1, lc.m_corrections.length ());
+ ASSERT_EQ (column_range (12, 12), lc.m_corrections[0]->m_affected_columns);
+ ASSERT_EQ (column_range (12, 22), lc.m_corrections[0]->m_printed_columns);
+ ASSERT_STREQ ("const_cast<", lc.m_corrections[0]->m_text);
+
+ /* After the second replacement hint, they are printed together
+ as a replacement (along with the text between them). */
+ lc.add_hint (hint_1);
+ ASSERT_EQ (1, lc.m_corrections.length ());
+ ASSERT_STREQ ("const_cast<foo *> (", lc.m_corrections[0]->m_text);
+ ASSERT_EQ (column_range (12, 18), lc.m_corrections[0]->m_affected_columns);
+ ASSERT_EQ (column_range (12, 30), lc.m_corrections[0]->m_printed_columns);
+
+ /* After the final insertion hint, they are all printed together
+ as a replacement (along with the text between them). */
+ lc.add_hint (hint_2);
+ ASSERT_STREQ ("const_cast<foo *> (ptr->field)",
+ lc.m_corrections[0]->m_text);
+ ASSERT_EQ (1, lc.m_corrections.length ());
+ ASSERT_EQ (column_range (12, 28), lc.m_corrections[0]->m_affected_columns);
+ ASSERT_EQ (column_range (12, 41), lc.m_corrections[0]->m_printed_columns);
+ }
+
+ /* Example where two are consolidated during printing. */
+ {
+ test_diagnostic_context dc;
+ rich_location richloc (line_table, expr);
+ richloc.add_fixit_replace (open_paren, "CAST (");
+ richloc.add_fixit_replace (close_paren, ") (");
+ richloc.add_fixit_insert_after (")");
+
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo *f = (foo *)ptr->field;\n"
+ " ^~~~~~~~~~\n"
+ " -\n"
+ " CAST (-\n"
+ " ) ( )\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Example where none are consolidated during printing. */
+ {
+ test_diagnostic_context dc;
+ rich_location richloc (line_table, expr);
+ richloc.add_fixit_replace (open_paren, "CST (");
+ richloc.add_fixit_replace (close_paren, ") (");
+ richloc.add_fixit_insert_after (")");
+
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo *f = (foo *)ptr->field;\n"
+ " ^~~~~~~~~~\n"
+ " -\n"
+ " CST ( -\n"
+ " ) ( )\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Example of deletion fix-it hints. */
+ {
+ test_diagnostic_context dc;
+ rich_location richloc (line_table, expr);
+ richloc.add_fixit_insert_before (open_paren, "(bar *)");
+ source_range victim = {open_paren, close_paren};
+ richloc.add_fixit_remove (victim);
+
+ /* This case is actually handled by fixit-consolidation,
+ rather than by line_corrections. */
+ ASSERT_EQ (1, richloc.get_num_fixit_hints ());
+
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo *f = (foo *)ptr->field;\n"
+ " ^~~~~~~~~~\n"
+ " -------\n"
+ " (bar *)\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Example of deletion fix-it hints that would overlap. */
+ {
+ test_diagnostic_context dc;
+ rich_location richloc (line_table, expr);
+ richloc.add_fixit_insert_before (open_paren, "(longer *)");
+ source_range victim = {expr_start, expr_finish};
+ richloc.add_fixit_remove (victim);
+
+ /* These fixits are not consolidated. */
+ ASSERT_EQ (2, richloc.get_num_fixit_hints ());
+
+ /* But the corrections are. */
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo *f = (foo *)ptr->field;\n"
+ " ^~~~~~~~~~\n"
+ " -----------------\n"
+ " (longer *)(foo *)\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Example of insertion fix-it hints that would overlap. */
+ {
+ test_diagnostic_context dc;
+ rich_location richloc (line_table, expr);
+ richloc.add_fixit_insert_before (open_paren, "LONGER THAN THE CAST");
+ richloc.add_fixit_insert_after (close_paren, "TEST");
+
+ /* The first insertion is long enough that if printed naively,
+ it would overlap with the second.
+ Verify that they are printed as a single replacement. */
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo *f = (foo *)ptr->field;\n"
+ " ^~~~~~~~~~\n"
+ " -------\n"
+ " LONGER THAN THE CAST(foo *)TEST\n",
+ pp_formatted_text (dc.printer));
+ }
+}
+
/* Insertion fix-it hint: adding a "break;" on a line by itself. */
static void
@@ -2314,6 +2809,7 @@ diagnostic_show_locus_c_tests ()
for_each_line_table_case (test_diagnostic_show_locus_one_liner);
for_each_line_table_case (test_diagnostic_show_locus_fixit_lines);
for_each_line_table_case (test_fixit_consolidation);
+ for_each_line_table_case (test_overlapped_fixit_printing);
for_each_line_table_case (test_fixit_insert_containing_newline);
for_each_line_table_case (test_fixit_insert_containing_newline_2);
for_each_line_table_case (test_fixit_replace_containing_newline);