/* Diagnostic subroutines for printing source-code
- Copyright (C) 1999-2018 Free Software Foundation, Inc.
+ Copyright (C) 1999-2019 Free Software Foundation, Inc.
Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
This file is part of GCC.
# include <sys/ioctl.h>
#endif
+/* Disable warnings about quoting issues in the pp_xxx calls below
+ that (intentionally) don't follow GCC diagnostic conventions. */
+#if __GNUC__ >= 10
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-diag"
+#endif
+
/* Classes for rendering source code and diagnostics, within an
anonymous namespace.
The work is done by "class layout", which embeds and uses
public:
layout_range (const expanded_location *start_exploc,
const expanded_location *finish_exploc,
- bool show_caret_p,
- const expanded_location *caret_exploc);
+ enum range_display_kind range_display_kind,
+ const expanded_location *caret_exploc,
+ unsigned original_idx,
+ const range_label *label);
bool contains_point (linenum_type row, int column) const;
bool intersects_line_p (linenum_type row) const;
layout_point m_start;
layout_point m_finish;
- bool m_show_caret_p;
+ enum range_display_kind m_range_display_kind;
layout_point m_caret;
+ unsigned m_original_idx;
+ const range_label *m_label;
};
/* A struct for use by layout::print_source_line for telling
splits the pertinent source lines into a list of disjoint line_span
instances (e.g. lines 5-10, lines 15-20, line 23). */
-struct line_span
+class line_span
{
+public:
line_span (linenum_type first_line, linenum_type last_line)
: m_first_line (first_line), m_last_line (last_line)
{
diagnostic_t diagnostic_kind);
bool maybe_add_location_range (const location_range *loc_range,
+ unsigned original_idx,
bool restrict_to_current_line_spans);
int get_num_line_spans () const { return m_line_spans.length (); }
const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; }
+ void print_gap_in_line_numbering ();
bool print_heading_for_line_span_index_p (int line_span_idx) const;
expanded_location get_expanded_location (const line_span *) const;
void print_source_line (linenum_type row, const char *line, int line_width,
line_bounds *lbounds_out);
bool should_print_annotation_line_p (linenum_type row) const;
+ void start_annotation_line (char margin_char = ' ') const;
void print_annotation_line (linenum_type row, const line_bounds lbounds);
+ void print_any_labels (linenum_type row);
void print_trailing_fixits (linenum_type row);
bool annotation_line_showed_range_p (linenum_type line, int start_column,
int last_non_ws);
void
- move_to_column (int *column, int dest_column);
+ move_to_column (int *column, int dest_column, bool add_left_margin);
private:
diagnostic_context *m_context;
pretty_printer *m_pp;
- diagnostic_t m_diagnostic_kind;
location_t m_primary_loc;
expanded_location m_exploc;
colorizer m_colorizer;
bool m_colorize_source_p;
+ bool m_show_labels_p;
+ bool m_show_line_numbers_p;
auto_vec <layout_range> m_layout_ranges;
auto_vec <const fixit_hint *> m_fixit_hints;
auto_vec <line_span> m_line_spans;
+ int m_linenum_width;
int m_x_offset;
};
layout_range::layout_range (const expanded_location *start_exploc,
const expanded_location *finish_exploc,
- bool show_caret_p,
- const expanded_location *caret_exploc)
+ enum range_display_kind range_display_kind,
+ const expanded_location *caret_exploc,
+ unsigned original_idx,
+ const range_label *label)
: m_start (*start_exploc),
m_finish (*finish_exploc),
- m_show_caret_p (show_caret_p),
- m_caret (*caret_exploc)
+ m_range_display_kind (range_display_kind),
+ m_caret (*caret_exploc),
+ m_original_idx (original_idx),
+ m_label (label)
{
}
= {"test.c", start_line, start_col, NULL, false};
const expanded_location finish_exploc
= {"test.c", end_line, end_col, NULL, false};
- return layout_range (&start_exploc, &finish_exploc, false,
- &start_exploc);
+ return layout_range (&start_exploc, &finish_exploc, SHOW_RANGE_WITHOUT_CARET,
+ &start_exploc, 0, NULL);
}
/* Selftests for layout_range::contains_point and
/* Expand each location towards the spelling location, and
recurse. */
const line_map_macro *macro_map = linemap_check_macro (map_a);
- source_location loc_a_toward_spelling
+ location_t loc_a_toward_spelling
= linemap_macro_map_loc_unwind_toward_spelling (line_table,
macro_map,
loc_a);
- source_location loc_b_toward_spelling
+ location_t loc_b_toward_spelling
= linemap_macro_map_loc_unwind_toward_spelling (line_table,
macro_map,
loc_b);
diagnostic_t diagnostic_kind)
: m_context (context),
m_pp (context->printer),
- m_diagnostic_kind (diagnostic_kind),
m_primary_loc (richloc->get_range (0)->m_loc),
m_exploc (richloc->get_expanded_location (0)),
m_colorizer (context, diagnostic_kind),
m_colorize_source_p (context->colorize_source_p),
+ m_show_labels_p (context->show_labels_p),
+ m_show_line_numbers_p (context->show_line_numbers_p),
m_layout_ranges (richloc->get_num_locations ()),
m_fixit_hints (richloc->get_num_fixit_hints ()),
m_line_spans (1 + richloc->get_num_locations ()),
+ m_linenum_width (0),
m_x_offset (0)
{
for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++)
/* This diagnostic printer can only cope with "sufficiently sane" ranges.
Ignore any ranges that are awkward to handle. */
const location_range *loc_range = richloc->get_range (idx);
- maybe_add_location_range (loc_range, false);
+ maybe_add_location_range (loc_range, idx, false);
}
/* Populate m_fixit_hints, filtering to only those that are in the
/* Populate m_line_spans. */
calculate_line_spans ();
+ /* Determine m_linenum_width. */
+ gcc_assert (m_line_spans.length () > 0);
+ const line_span *last_span = &m_line_spans[m_line_spans.length () - 1];
+ int highest_line = last_span->m_last_line;
+ if (highest_line < 0)
+ highest_line = 0;
+ m_linenum_width = num_digits (highest_line);
+ /* If we're showing jumps in the line-numbering, allow at least 3 chars. */
+ if (m_line_spans.length () > 1)
+ m_linenum_width = MAX (m_linenum_width, 3);
+ /* If there's a minimum margin width, apply it (subtracting 1 for the space
+ after the line number. */
+ m_linenum_width = MAX (m_linenum_width, context->min_margin_width - 1);
+
/* Adjust m_x_offset.
Center the primary caret to fit in max_width; all columns
will be adjusted accordingly. */
{
size_t right_margin = CARET_LINE_MARGIN;
size_t column = m_exploc.column;
+ if (m_show_line_numbers_p)
+ column += m_linenum_width + 2;
right_margin = MIN (line.length () - column, right_margin);
right_margin = max_width - right_margin;
if (line.length () >= max_width && column > right_margin)
/* Attempt to add LOC_RANGE to m_layout_ranges, filtering them to
those that we can sanely print.
+ ORIGINAL_IDX is the index of LOC_RANGE within its rich_location,
+ (for use as extrinsic state by label ranges FIXME).
+
If RESTRICT_TO_CURRENT_LINE_SPANS is true, then LOC_RANGE is also
filtered against this layout instance's current line spans: it
will only be added if the location is fully within the lines
bool
layout::maybe_add_location_range (const location_range *loc_range,
+ unsigned original_idx,
bool restrict_to_current_line_spans)
{
gcc_assert (loc_range);
return false;
if (finish.file != m_exploc.file)
return false;
- if (loc_range->m_show_caret_p)
+ if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
if (caret.file != m_exploc.file)
return false;
/* Sanitize the caret location for non-primary ranges. */
if (m_layout_ranges.length () > 0)
- if (loc_range->m_show_caret_p)
+ if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
if (!compatible_locations_p (loc_range->m_loc, m_primary_loc))
/* Discard any non-primary ranges that can't be printed
sanely relative to the primary location. */
/* Everything is now known to be in the correct source file,
but it may require further sanitization. */
- layout_range ri (&start, &finish, loc_range->m_show_caret_p, &caret);
+ layout_range ri (&start, &finish, loc_range->m_range_display_kind, &caret,
+ original_idx, loc_range->m_label);
/* If we have a range that finishes before it starts (perhaps
from something built via macro expansion), printing the
return false;
if (!will_show_line_p (finish.line))
return false;
- if (loc_range->m_show_caret_p)
+ if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
if (!will_show_line_p (caret.line))
return false;
}
return false;
}
+/* Print a line showing a gap in the line numbers, for showing the boundary
+ between two line spans. */
+
+void
+layout::print_gap_in_line_numbering ()
+{
+ gcc_assert (m_show_line_numbers_p);
+
+ for (int i = 0; i < m_linenum_width + 1; i++)
+ pp_character (m_pp, '.');
+
+ pp_newline (m_pp);
+}
+
/* Return true iff we should print a heading when starting the
line span with the given index. */
get_line_span_for_fixit_hint (const fixit_hint *hint)
{
gcc_assert (hint);
- return line_span (LOCATION_LINE (hint->get_start_loc ()),
+
+ int start_line = LOCATION_LINE (hint->get_start_loc ());
+
+ /* For line-insertion fix-it hints, add the previous line to the
+ span, to give the user more context on the proposed change. */
+ if (hint->ends_with_newline_p ())
+ if (start_line > 1)
+ start_line--;
+
+ return line_span (start_line,
LOCATION_LINE (hint->get_next_loc ()));
}
This function populates m_line_spans with an ordered, disjoint list of
the line spans of interest.
- For example, if the primary caret location is on line 7, with ranges
- covering lines 5-6 and lines 9-12:
+ Printing a gap between line spans takes one line, so, when printing
+ line numbers, we allow a gap of up to one line between spans when
+ merging, since it makes more sense to print the source line rather than a
+ "gap-in-line-numbering" line. When not printing line numbers, it's
+ better to be more explicit about what's going on, so keeping them as
+ separate spans is preferred.
+
+ For example, if the primary range is on lines 8-10, with secondary ranges
+ covering lines 5-6 and lines 13-15:
004
- 005 |RANGE 0
- 006 |RANGE 0
- 007 |PRIMARY CARET
- 008
- 009 |RANGE 1
- 010 |RANGE 1
- 011 |RANGE 1
- 012 |RANGE 1
- 013
-
- then we want two spans: lines 5-7 and lines 9-12. */
+ 005 |RANGE 1
+ 006 |RANGE 1
+ 007
+ 008 |PRIMARY RANGE
+ 009 |PRIMARY CARET
+ 010 |PRIMARY RANGE
+ 011
+ 012
+ 013 |RANGE 2
+ 014 |RANGE 2
+ 015 |RANGE 2
+ 016
+
+ With line numbering on, we want two spans: lines 5-10 and lines 13-15.
+
+ With line numbering off (with span headers), we want three spans: lines 5-6,
+ lines 8-10, and lines 13-15. */
void
layout::calculate_line_spans ()
line_span *current = &m_line_spans[m_line_spans.length () - 1];
const line_span *next = &tmp_spans[i];
gcc_assert (next->m_first_line >= current->m_first_line);
- if (next->m_first_line <= current->m_last_line + 1)
+ const int merger_distance = m_show_line_numbers_p ? 1 : 0;
+ if ((linenum_arith_t)next->m_first_line
+ <= (linenum_arith_t)current->m_last_line + 1 + merger_distance)
{
/* We can merge them. */
if (next->m_last_line > current->m_last_line)
line_width);
line += m_x_offset;
- pp_space (m_pp);
+ if (m_show_line_numbers_p)
+ {
+ int width = num_digits (row);
+ for (int i = 0; i < m_linenum_width - width; i++)
+ pp_space (m_pp);
+ pp_printf (m_pp, "%i | ", row);
+ }
+ else
+ pp_space (m_pp);
int first_non_ws = INT_MAX;
int last_non_ws = 0;
int column;
layout_range *range;
int i;
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
- if (range->intersects_line_p (row))
- return true;
+ {
+ if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
+ return false;
+ if (range->intersects_line_p (row))
+ return true;
+ }
return false;
}
+/* Begin an annotation line. If m_show_line_numbers_p, print the left
+ margin, which is empty for annotation lines. Otherwise, do nothing. */
+
+void
+layout::start_annotation_line (char margin_char) const
+{
+ if (m_show_line_numbers_p)
+ {
+ /* Print the margin. If MARGIN_CHAR != ' ', then print up to 3
+ of it, right-aligned, padded with spaces. */
+ int i;
+ for (i = 0; i < m_linenum_width - 3; i++)
+ pp_space (m_pp);
+ for (; i < m_linenum_width; i++)
+ pp_character (m_pp, margin_char);
+ pp_string (m_pp, " |");
+ }
+}
+
/* Print a line consisting of the caret/underlines for the given
source line. */
int x_bound = get_x_bound_for_row (row, m_exploc.column,
lbounds.m_last_non_ws);
+ start_annotation_line ();
pp_space (m_pp);
+
for (int column = 1 + m_x_offset; column < x_bound; column++)
{
bool in_range_p;
print_newline ();
}
+/* Implementation detail of layout::print_any_labels.
+
+ A label within the given row of source. */
+
+class line_label
+{
+public:
+ line_label (int state_idx, int column, label_text text)
+ : m_state_idx (state_idx), m_column (column),
+ m_text (text), m_length (strlen (text.m_buffer)),
+ m_label_line (0)
+ {}
+
+ /* Sorting is primarily by column, then by state index. */
+ static int comparator (const void *p1, const void *p2)
+ {
+ const line_label *ll1 = (const line_label *)p1;
+ const line_label *ll2 = (const line_label *)p2;
+ int column_cmp = compare (ll1->m_column, ll2->m_column);
+ if (column_cmp)
+ return column_cmp;
+ return compare (ll1->m_state_idx, ll2->m_state_idx);
+ }
+
+ int m_state_idx;
+ int m_column;
+ label_text m_text;
+ size_t m_length;
+ int m_label_line;
+};
+
+/* Print any labels in this row. */
+void
+layout::print_any_labels (linenum_type row)
+{
+ int i;
+ auto_vec<line_label> labels;
+
+ /* Gather the labels that are to be printed into "labels". */
+ {
+ layout_range *range;
+ FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+ {
+ /* Most ranges don't have labels, so reject this first. */
+ if (range->m_label == NULL)
+ continue;
+
+ /* The range's caret must be on this line. */
+ if (range->m_caret.m_line != row)
+ continue;
+
+ /* Reject labels that aren't fully visible due to clipping
+ by m_x_offset. */
+ if (range->m_caret.m_column <= m_x_offset)
+ continue;
+
+ label_text text;
+ text = range->m_label->get_text (range->m_original_idx);
+
+ /* Allow for labels that return NULL from their get_text
+ implementation (so e.g. such labels can control their own
+ visibility). */
+ if (text.m_buffer == NULL)
+ continue;
+
+ labels.safe_push (line_label (i, range->m_caret.m_column, text));
+ }
+ }
+
+ /* Bail out if there are no labels on this row. */
+ if (labels.length () == 0)
+ return;
+
+ /* Sort them. */
+ labels.qsort(line_label::comparator);
+
+ /* Figure out how many "label lines" we need, and which
+ one each label is printed in.
+
+ For example, if the labels aren't too densely packed,
+ we can fit them on the same line, giving two "label lines":
+
+ foo + bar
+ ~~~ ~~~
+ | | : label line 0
+ l0 l1 : label line 1
+
+ If they would touch each other or overlap, then we need
+ additional "label lines":
+
+ foo + bar
+ ~~~ ~~~
+ | | : label line 0
+ | label 1 : label line 1
+ label 0 : label line 2
+
+ Place the final label on label line 1, and work backwards, adding
+ label lines as needed.
+
+ If multiple labels are at the same place, put them on separate
+ label lines:
+
+ foo + bar
+ ^ : label line 0
+ | : label line 1
+ label 1 : label line 2
+ label 0 : label line 3. */
+
+ int max_label_line = 1;
+ {
+ int next_column = INT_MAX;
+ line_label *label;
+ FOR_EACH_VEC_ELT_REVERSE (labels, i, label)
+ {
+ /* Would this label "touch" or overlap the next label? */
+ if (label->m_column + label->m_length >= (size_t)next_column)
+ max_label_line++;
+
+ label->m_label_line = max_label_line;
+ next_column = label->m_column;
+ }
+ }
+
+ /* Print the "label lines". For each label within the line, print
+ either a vertical bar ('|') for the labels that are lower down, or the
+ labels themselves once we've reached their line. */
+ {
+ /* Keep track of in which column we last printed a vertical bar.
+ This allows us to suppress duplicate vertical bars for the case
+ where multiple labels are on one column. */
+ int last_vbar = 0;
+ for (int label_line = 0; label_line <= max_label_line; label_line++)
+ {
+ start_annotation_line ();
+ pp_space (m_pp);
+ int column = 1 + m_x_offset;
+ line_label *label;
+ FOR_EACH_VEC_ELT (labels, i, label)
+ {
+ if (label_line > label->m_label_line)
+ /* We've printed all the labels for this label line. */
+ break;
+
+ if (label_line == label->m_label_line)
+ {
+ gcc_assert (column <= label->m_column);
+ move_to_column (&column, label->m_column, true);
+ m_colorizer.set_range (label->m_state_idx);
+ pp_string (m_pp, label->m_text.m_buffer);
+ m_colorizer.set_normal_text ();
+ column += label->m_length;
+ }
+ else if (label->m_column != last_vbar)
+ {
+ gcc_assert (column <= label->m_column);
+ move_to_column (&column, label->m_column, true);
+ m_colorizer.set_range (label->m_state_idx);
+ pp_character (m_pp, '|');
+ m_colorizer.set_normal_text ();
+ last_vbar = column;
+ column++;
+ }
+ }
+ print_newline ();
+ }
+ }
+
+ /* Clean up. */
+ {
+ line_label *label;
+ FOR_EACH_VEC_ELT (labels, i, label)
+ label->m_text.maybe_free ();
+ }
+}
+
/* If there are any fixit hints inserting new lines before source line ROW,
print them.
helps them stand out from each other, and from
the surrounding text. */
m_colorizer.set_normal_text ();
+ start_annotation_line ('+');
pp_character (m_pp, '+');
m_colorizer.set_fixit_insert ();
/* Print all but the trailing newline of the fix-it hint.
/* A range of columns within a line. */
-struct column_range
+class column_range
{
+public:
column_range (int start_, int finish_) : start (start_), finish (finish_)
{
/* We must have either a range, or an insertion. */
instances that affected the line, potentially consolidating hints
into corrections to make the result easier for the user to read. */
-struct correction
+class correction
{
+public:
correction (column_range affected_columns,
column_range printed_columns,
const char *new_text, size_t new_text_len)
This is used by layout::print_trailing_fixits for planning
how to print the fix-it hints affecting the line. */
-struct line_corrections
+class line_corrections
{
+public:
line_corrections (const char *filename, linenum_type row)
: m_filename (filename), m_row (row)
{}
/* A struct wrapping a particular source line, allowing
run-time bounds-checking of accesses in a checked build. */
-struct source_line
+class source_line
{
+public:
source_line (const char *filename, int line);
char_span as_span () { return char_span (chars, width); }
correction *c;
int column = m_x_offset;
+ if (!corrections.m_corrections.is_empty ())
+ start_annotation_line ();
+
FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
{
/* For now we assume each fixit hint can only touch one line. */
{
/* This assumes the insertion just affects one line. */
int start_column = c->m_printed_columns.start;
- move_to_column (&column, start_column);
+ move_to_column (&column, start_column, true);
m_colorizer.set_fixit_insert ();
pp_string (m_pp, c->m_text);
m_colorizer.set_normal_text ();
finish_column)
|| c->m_len == 0)
{
- move_to_column (&column, start_column);
+ move_to_column (&column, start_column, true);
m_colorizer.set_fixit_delete ();
for (; column <= finish_column; column++)
pp_character (m_pp, '-');
a new line) if we have actual replacement text. */
if (c->m_len > 0)
{
- move_to_column (&column, start_column);
+ move_to_column (&column, start_column, true);
m_colorizer.set_fixit_insert ();
pp_string (m_pp, c->m_text);
m_colorizer.set_normal_text ();
}
/* Add a trailing newline, if necessary. */
- move_to_column (&column, 0);
+ move_to_column (&column, 0, false);
}
/* Disable any colorization and emit a newline. */
int i;
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
{
+ if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
+ /* Bail out early, so that such ranges don't affect underlining or
+ source colorization. */
+ continue;
+
if (range->contains_point (row, column))
{
out_state->range_idx = i;
/* Are we at the range's caret? is it visible? */
out_state->draw_caret_p = false;
- if (range->m_show_caret_p
+ if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET
&& row == range->m_caret.m_line
&& column == range->m_caret.m_column)
out_state->draw_caret_p = true;
/* Given *COLUMN as an x-coordinate, print spaces to position
successive output at DEST_COLUMN, printing a newline if necessary,
- and updating *COLUMN. */
+ and updating *COLUMN. If ADD_LEFT_MARGIN, then print the (empty)
+ left margin after any newline. */
void
-layout::move_to_column (int *column, int dest_column)
+layout::move_to_column (int *column, int dest_column, bool add_left_margin)
{
/* Start a new line if we need to. */
if (*column > dest_column)
{
print_newline ();
+ if (add_left_margin)
+ start_annotation_line ();
*column = m_x_offset;
}
/* Hundreds. */
if (max_column > 99)
{
+ start_annotation_line ();
pp_space (m_pp);
for (int column = 1 + m_x_offset; column <= max_column; column++)
if (column % 10 == 0)
}
/* Tens. */
+ start_annotation_line ();
pp_space (m_pp);
for (int column = 1 + m_x_offset; column <= max_column; column++)
if (column % 10 == 0)
pp_newline (m_pp);
/* Units. */
+ start_annotation_line ();
pp_space (m_pp);
for (int column = 1 + m_x_offset; column <= max_column; column++)
pp_character (m_pp, '0' + (column % 10));
print_source_line (row, line.get_buffer (), line.length (), &lbounds);
if (should_print_annotation_line_p (row))
print_annotation_line (row, lbounds);
+ if (m_show_labels_p)
+ print_any_labels (row);
print_trailing_fixits (row);
}
layout layout (global_dc, this, DK_ERROR);
location_range loc_range;
loc_range.m_loc = loc;
- loc_range.m_show_caret_p = false;
- if (!layout.maybe_add_location_range (&loc_range, true))
+ loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
+ if (!layout.maybe_add_location_range (&loc_range, 0, true))
return false;
- add_range (loc, false);
+ add_range (loc);
return true;
}
line_span_idx++)
{
const line_span *line_span = layout.get_line_span (line_span_idx);
- if (layout.print_heading_for_line_span_index_p (line_span_idx))
+ if (context->show_line_numbers_p)
{
- expanded_location exploc = layout.get_expanded_location (line_span);
- context->start_span (context, exploc);
+ /* With line numbers, we should show whenever the line-numbering
+ "jumps". */
+ if (line_span_idx > 0)
+ layout.print_gap_in_line_numbering ();
}
- linenum_type last_line = line_span->get_last_line ();
- for (linenum_type row = line_span->get_first_line ();
+ else
+ {
+ /* Without line numbers, we print headings for some line spans. */
+ if (layout.print_heading_for_line_span_index_p (line_span_idx))
+ {
+ expanded_location exploc
+ = layout.get_expanded_location (line_span);
+ context->start_span (context, exploc);
+ }
+ }
+ /* Iterate over the lines within this span (using linenum_arith_t to
+ avoid overflow with 0xffffffff causing an infinite loop). */
+ linenum_arith_t last_line = line_span->get_last_line ();
+ for (linenum_arith_t row = line_span->get_first_line ();
row <= last_line; row++)
layout.print_line (row);
}
dc.caret_chars[2] = 'C';
rich_location richloc (line_table, foo);
- richloc.add_range (bar, true);
- richloc.add_range (field, true);
+ richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
+ richloc.add_range (field, SHOW_RANGE_WITH_CARET);
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
ASSERT_STREQ ("\n"
" foo = bar.field;\n"
location_t finish = linemap_position_for_column (line_table, 15);
rich_location richloc (line_table, equals);
location_t field = make_location (start, start, finish);
- richloc.add_range (field, false);
+ richloc.add_range (field);
richloc.add_fixit_replace (field, "m_field");
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
/* The replacement range is indicated in the annotation line,
pp_formatted_text (dc.printer));
}
+/* Test of labeling the ranges within a rich_location. */
+
+static void
+test_one_liner_labels ()
+{
+ location_t foo
+ = make_location (linemap_position_for_column (line_table, 1),
+ linemap_position_for_column (line_table, 1),
+ linemap_position_for_column (line_table, 3));
+ location_t bar
+ = make_location (linemap_position_for_column (line_table, 7),
+ linemap_position_for_column (line_table, 7),
+ linemap_position_for_column (line_table, 9));
+ location_t field
+ = make_location (linemap_position_for_column (line_table, 11),
+ linemap_position_for_column (line_table, 11),
+ linemap_position_for_column (line_table, 15));
+
+ /* Example where all the labels fit on one line. */
+ {
+ text_range_label label0 ("0");
+ text_range_label label1 ("1");
+ text_range_label label2 ("2");
+ gcc_rich_location richloc (foo, &label0);
+ richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+ richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+ {
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ^~~ ~~~ ~~~~~\n"
+ " | | |\n"
+ " 0 1 2\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Verify that we can disable label-printing. */
+ {
+ test_diagnostic_context dc;
+ dc.show_labels_p = false;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ^~~ ~~~ ~~~~~\n",
+ pp_formatted_text (dc.printer));
+ }
+ }
+
+ /* Example where the labels need extra lines. */
+ {
+ text_range_label label0 ("label 0");
+ text_range_label label1 ("label 1");
+ text_range_label label2 ("label 2");
+ gcc_rich_location richloc (foo, &label0);
+ richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+ richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ^~~ ~~~ ~~~~~\n"
+ " | | |\n"
+ " | | label 2\n"
+ " | label 1\n"
+ " label 0\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Example of boundary conditions: label 0 and 1 have just enough clearance,
+ but label 1 just touches label 2. */
+ {
+ text_range_label label0 ("aaaaa");
+ text_range_label label1 ("bbbb");
+ text_range_label label2 ("c");
+ gcc_rich_location richloc (foo, &label0);
+ richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+ richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ^~~ ~~~ ~~~~~\n"
+ " | | |\n"
+ " | | c\n"
+ " aaaaa bbbb\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Example of out-of-order ranges (thus requiring a sort). */
+ {
+ text_range_label label0 ("0");
+ text_range_label label1 ("1");
+ text_range_label label2 ("2");
+ gcc_rich_location richloc (field, &label0);
+ richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+ richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ~~~ ~~~ ^~~~~\n"
+ " | | |\n"
+ " 2 1 0\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Ensure we don't ICE if multiple ranges with labels are on
+ the same point. */
+ {
+ text_range_label label0 ("label 0");
+ text_range_label label1 ("label 1");
+ text_range_label label2 ("label 2");
+ gcc_rich_location richloc (bar, &label0);
+ richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+ richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ^~~\n"
+ " |\n"
+ " label 2\n"
+ " label 1\n"
+ " label 0\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* Verify that a NULL result from range_label::get_text is
+ handled gracefully. */
+ {
+ text_range_label label (NULL);
+ gcc_rich_location richloc (bar, &label);
+
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " foo = bar.field;\n"
+ " ^~~\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* TODO: example of formatted printing (needs to be in
+ gcc-rich-location.c due to Makefile.in issues). */
+}
+
/* Run the various one-liner tests. */
static void
test_one_liner_fixit_validation_adhoc_locations ();
test_one_liner_many_fixits_1 ();
test_one_liner_many_fixits_2 ();
+ test_one_liner_labels ();
}
/* Verify that gcc_rich_location::add_location_if_nearby works. */
" =\n",
pp_formatted_text (dc.printer));
}
+
+ /* As above, but verify the behavior of multiple line spans
+ with line-numbering enabled. */
+ {
+ const location_t y
+ = linemap_position_for_line_and_column (line_table, ord_map, 3, 24);
+ const location_t colon
+ = linemap_position_for_line_and_column (line_table, ord_map, 6, 25);
+ rich_location richloc (line_table, colon);
+ richloc.add_fixit_insert_before (y, ".");
+ richloc.add_fixit_replace (colon, "=");
+ test_diagnostic_context dc;
+ dc.show_line_numbers_p = true;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " 3 | y\n"
+ " | .\n"
+ "......\n"
+ " 6 | : 0.0};\n"
+ " | ^\n"
+ " | =\n",
+ pp_formatted_text (dc.printer));
+ }
}
{
rich_location richloc (line_table, case_loc);
richloc.add_fixit_insert_before (line_start, " break;\n");
- test_diagnostic_context dc;
- diagnostic_show_locus (&dc, &richloc, DK_ERROR);
- ASSERT_STREQ ("\n"
- "+ break;\n"
- " case 'b':\n"
- " ^~~~~~~~~\n",
- pp_formatted_text (dc.printer));
+
+ /* Without line numbers. */
+ {
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " x = a;\n"
+ "+ break;\n"
+ " case 'b':\n"
+ " ^~~~~~~~~\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* With line numbers. */
+ {
+ test_diagnostic_context dc;
+ dc.show_line_numbers_p = true;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " 2 | x = a;\n"
+ " +++ |+ break;\n"
+ " 3 | case 'b':\n"
+ " | ^~~~~~~~~\n",
+ pp_formatted_text (dc.printer));
+ }
}
/* Verify that attempts to add text with a newline fail when the
if (putchar_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
return;
- test_diagnostic_context dc;
- diagnostic_show_locus (&dc, &richloc, DK_ERROR);
- ASSERT_STREQ ("\n"
- "FILENAME:1:1:\n"
- "+#include <stdio.h>\n"
- " test (int ch)\n"
- "FILENAME:3:2:\n"
- " putchar (ch);\n"
- " ^~~~~~~\n",
- pp_formatted_text (dc.printer));
+ {
+ test_diagnostic_context dc;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ "FILENAME:1:1:\n"
+ "+#include <stdio.h>\n"
+ " test (int ch)\n"
+ "FILENAME:3:2:\n"
+ " putchar (ch);\n"
+ " ^~~~~~~\n",
+ pp_formatted_text (dc.printer));
+ }
+
+ /* With line-numbering, the line spans are close enough to be
+ consolidated, since it makes little sense to skip line 2. */
+ {
+ test_diagnostic_context dc;
+ dc.show_line_numbers_p = true;
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " +++ |+#include <stdio.h>\n"
+ " 1 | test (int ch)\n"
+ " 2 | {\n"
+ " 3 | putchar (ch);\n"
+ " | ^~~~~~~\n",
+ pp_formatted_text (dc.printer));
+ }
}
/* Replacement fix-it hint containing a newline.
pp_formatted_text (dc.printer));
}
+/* Verify that line numbers are correctly printed for the case of
+ a multiline range in which the width of the line numbers changes
+ (e.g. from "9" to "10"). */
+
+static void
+test_line_numbers_multiline_range ()
+{
+ /* Create a tempfile and write some text to it. */
+ pretty_printer pp;
+ for (int i = 0; i < 20; i++)
+ /* .........0000000001111111.
+ .............1234567890123456. */
+ pp_printf (&pp, "this is line %i\n", i + 1);
+ temp_source_file tmp (SELFTEST_LOCATION, ".txt", pp_formatted_text (&pp));
+ line_table_test ltt;
+
+ 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);
+
+ /* Create a multi-line location, starting at the "line" of line 9, with
+ a caret on the "is" of line 10, finishing on the "this" line 11. */
+
+ location_t start
+ = linemap_position_for_line_and_column (line_table, ord_map, 9, 9);
+ location_t caret
+ = linemap_position_for_line_and_column (line_table, ord_map, 10, 6);
+ location_t finish
+ = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
+ location_t loc = make_location (caret, start, finish);
+
+ test_diagnostic_context dc;
+ dc.show_line_numbers_p = true;
+ dc.min_margin_width = 0;
+ gcc_rich_location richloc (loc);
+ diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+ ASSERT_STREQ ("\n"
+ " 9 | this is line 9\n"
+ " | ~~~~~~\n"
+ "10 | this is line 10\n"
+ " | ~~~~~^~~~~~~~~~\n"
+ "11 | this is line 11\n"
+ " | ~~~~ \n",
+ pp_formatted_text (dc.printer));
+}
+
/* Run all of the selftests within this file. */
void
for_each_line_table_case (test_fixit_insert_containing_newline_2);
for_each_line_table_case (test_fixit_replace_containing_newline);
for_each_line_table_case (test_fixit_deletion_affecting_newline);
+
+ test_line_numbers_multiline_range ();
}
} // namespace selftest
#endif /* #if CHECKING_P */
+
+#if __GNUC__ >= 10
+# pragma GCC diagnostic pop
+#endif