41fbacdd10
PR c/99323 describes an ICE due to a failed assertion deep inside the fix-it printing machinery, where the fix-it hints on one line have not been properly sorted in layout's constructor. The underlying issue occurs when multiple fix-it hints affect a line wider that LINE_MAP_MAX_COLUMN_NUMBER, where the location_t values for characters after that threshold fall back to having column zero. It's not meaningful to try to handle fix-it hints without column information, so this patch rejects them as they are added to the rich_location, falling back to the "no fix-it hints on this diagnostic" case, fixing the crash. gcc/ChangeLog: PR c/99323 * diagnostic-show-locus.c (selftest::test_one_liner_many_fixits_2): Fix accidental usage of column 0. gcc/testsuite/ChangeLog: PR c/99323 * gcc.dg/pr99323-1.c: New test. * gcc.dg/pr99323-2.c: New test. libcpp/ChangeLog: PR c/99323 * line-map.c (rich_location::maybe_add_fixit): Reject fix-it hints at column 0.
5286 lines
176 KiB
C
5286 lines
176 KiB
C
/* Diagnostic subroutines for printing source-code
|
|
Copyright (C) 1999-2021 Free Software Foundation, Inc.
|
|
Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
|
|
|
|
This file is part of GCC.
|
|
|
|
GCC is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU General Public License as published by the Free
|
|
Software Foundation; either version 3, or (at your option) any later
|
|
version.
|
|
|
|
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GCC; see the file COPYING3. If not see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "version.h"
|
|
#include "demangle.h"
|
|
#include "intl.h"
|
|
#include "backtrace.h"
|
|
#include "diagnostic.h"
|
|
#include "diagnostic-color.h"
|
|
#include "gcc-rich-location.h"
|
|
#include "selftest.h"
|
|
#include "selftest-diagnostic.h"
|
|
#include "cpplib.h"
|
|
|
|
#ifdef HAVE_TERMIOS_H
|
|
# include <termios.h>
|
|
#endif
|
|
|
|
#ifdef GWINSZ_IN_SYS_IOCTL
|
|
# 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
|
|
"class colorizer" and "class layout_range" to get things done. */
|
|
|
|
namespace {
|
|
|
|
/* The state at a given point of the source code, assuming that we're
|
|
in a range: which range are we in, and whether we should draw a caret at
|
|
this point. */
|
|
|
|
struct point_state
|
|
{
|
|
int range_idx;
|
|
bool draw_caret_p;
|
|
};
|
|
|
|
/* A class to inject colorization codes when printing the diagnostic locus.
|
|
|
|
It has one kind of colorization for each of:
|
|
- normal text
|
|
- range 0 (the "primary location")
|
|
- range 1
|
|
- range 2
|
|
|
|
The class caches the lookup of the color codes for the above.
|
|
|
|
The class also has responsibility for tracking which of the above is
|
|
active, filtering out unnecessary changes. This allows
|
|
layout::print_source_line and layout::print_annotation_line
|
|
to simply request a colorization code for *every* character they print,
|
|
via this class, and have the filtering be done for them here. */
|
|
|
|
class colorizer
|
|
{
|
|
public:
|
|
colorizer (diagnostic_context *context,
|
|
diagnostic_t diagnostic_kind);
|
|
~colorizer ();
|
|
|
|
void set_range (int range_idx)
|
|
{
|
|
/* Normally we emphasize the primary location, then alternate between
|
|
two colors for the secondary locations.
|
|
But if we're printing a run of events in a diagnostic path, that
|
|
makes no sense, so print all of them with the same colorization. */
|
|
if (m_diagnostic_kind == DK_DIAGNOSTIC_PATH)
|
|
set_state (0);
|
|
else
|
|
set_state (range_idx);
|
|
}
|
|
void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
|
|
void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
|
|
void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
|
|
|
|
private:
|
|
void set_state (int state);
|
|
void begin_state (int state);
|
|
void finish_state (int state);
|
|
const char *get_color_by_name (const char *);
|
|
|
|
private:
|
|
static const int STATE_NORMAL_TEXT = -1;
|
|
static const int STATE_FIXIT_INSERT = -2;
|
|
static const int STATE_FIXIT_DELETE = -3;
|
|
|
|
diagnostic_context *m_context;
|
|
diagnostic_t m_diagnostic_kind;
|
|
int m_current_state;
|
|
const char *m_range1;
|
|
const char *m_range2;
|
|
const char *m_fixit_insert;
|
|
const char *m_fixit_delete;
|
|
const char *m_stop_color;
|
|
};
|
|
|
|
/* In order to handle multibyte sources properly, all of this logic needs to be
|
|
aware of the distinction between the number of bytes and the number of
|
|
display columns occupied by a character, which are not the same for non-ASCII
|
|
characters. For example, the Unicode pi symbol, U+03C0, is encoded in UTF-8
|
|
as "\xcf\x80", and thus occupies 2 bytes of space while only occupying 1
|
|
display column when it is output. A typical emoji, such as U+1F602 (in
|
|
UTF-8, "\xf0\x9f\x98\x82"), requires 4 bytes and has a display width of 2.
|
|
|
|
The below example line, which is also used for selftests below, shows how the
|
|
display column and byte column are related:
|
|
|
|
0000000001111111111222222 display
|
|
1234567890123456789012345 columns
|
|
SS_foo = P_bar.SS_fieldP;
|
|
0000000111111111222222223 byte
|
|
1356789012456789134567891 columns
|
|
|
|
Here SS represents the two display columns for the U+1F602 emoji, and P
|
|
represents the one display column for the U+03C0 pi symbol. As an example, a
|
|
diagnostic pointing to the final P on this line is at byte column 29 and
|
|
display column 24. This reflects the fact that the three extended characters
|
|
before the final P occupy cumulatively 5 more bytes than they do display
|
|
columns (a difference of 2 for each of the two SSs, and one for the other P).
|
|
|
|
One or the other of the two column units is more useful depending on the
|
|
context. For instance, in order to output the caret at the correct location,
|
|
we need to count display columns; in order to colorize a source line, we need
|
|
to count the bytes. All locations are provided to us as byte counts, which
|
|
we augment with the display column on demand so that it can be used when
|
|
needed. This is not the most efficient way to do things since it requires
|
|
looping over the whole line each time, but it should be fine for the purpose
|
|
of outputting diagnostics.
|
|
|
|
In order to keep straight which units (byte or display) are in use at a
|
|
given time, the following enum lets us specify that explicitly. */
|
|
|
|
enum column_unit {
|
|
/* Measured in raw bytes. */
|
|
CU_BYTES = 0,
|
|
|
|
/* Measured in display units. */
|
|
CU_DISPLAY_COLS,
|
|
|
|
/* For arrays indexed by column_unit. */
|
|
CU_NUM_UNITS
|
|
};
|
|
|
|
/* Utility class to augment an exploc with the corresponding display column. */
|
|
|
|
class exploc_with_display_col : public expanded_location
|
|
{
|
|
public:
|
|
exploc_with_display_col (const expanded_location &exploc, int tabstop)
|
|
: expanded_location (exploc),
|
|
m_display_col (location_compute_display_column (exploc, tabstop))
|
|
{}
|
|
|
|
int m_display_col;
|
|
};
|
|
|
|
|
|
/* A point within a layout_range; similar to an exploc_with_display_col,
|
|
but after filtering on file. */
|
|
|
|
class layout_point
|
|
{
|
|
public:
|
|
layout_point (const exploc_with_display_col &exploc)
|
|
: m_line (exploc.line)
|
|
{
|
|
m_columns[CU_BYTES] = exploc.column;
|
|
m_columns[CU_DISPLAY_COLS] = exploc.m_display_col;
|
|
}
|
|
|
|
linenum_type m_line;
|
|
int m_columns[CU_NUM_UNITS];
|
|
};
|
|
|
|
/* A class for use by "class layout" below: a filtered location_range. */
|
|
|
|
class layout_range
|
|
{
|
|
public:
|
|
layout_range (const exploc_with_display_col &start_exploc,
|
|
const exploc_with_display_col &finish_exploc,
|
|
enum range_display_kind range_display_kind,
|
|
const exploc_with_display_col &caret_exploc,
|
|
unsigned original_idx,
|
|
const range_label *label);
|
|
|
|
bool contains_point (linenum_type row, int column,
|
|
enum column_unit col_unit) const;
|
|
bool intersects_line_p (linenum_type row) const;
|
|
|
|
layout_point m_start;
|
|
layout_point m_finish;
|
|
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
|
|
layout::print_annotation_line the extents of the source line that
|
|
it printed, so that underlines can be clipped appropriately. Units
|
|
are 1-based display columns. */
|
|
|
|
struct line_bounds
|
|
{
|
|
int m_first_non_ws_disp_col;
|
|
int m_last_non_ws_disp_col;
|
|
|
|
line_bounds ()
|
|
{
|
|
m_first_non_ws_disp_col = INT_MAX;
|
|
m_last_non_ws_disp_col = 0;
|
|
}
|
|
};
|
|
|
|
/* A range of contiguous source lines within a layout (e.g. "lines 5-10"
|
|
or "line 23"). During the layout ctor, layout::calculate_line_spans
|
|
splits the pertinent source lines into a list of disjoint line_span
|
|
instances (e.g. lines 5-10, lines 15-20, line 23). */
|
|
|
|
class line_span
|
|
{
|
|
public:
|
|
line_span (linenum_type first_line, linenum_type last_line)
|
|
: m_first_line (first_line), m_last_line (last_line)
|
|
{
|
|
gcc_assert (first_line <= last_line);
|
|
}
|
|
linenum_type get_first_line () const { return m_first_line; }
|
|
linenum_type get_last_line () const { return m_last_line; }
|
|
|
|
bool contains_line_p (linenum_type line) const
|
|
{
|
|
return line >= m_first_line && line <= m_last_line;
|
|
}
|
|
|
|
static int comparator (const void *p1, const void *p2)
|
|
{
|
|
const line_span *ls1 = (const line_span *)p1;
|
|
const line_span *ls2 = (const line_span *)p2;
|
|
int first_line_cmp = compare (ls1->m_first_line, ls2->m_first_line);
|
|
if (first_line_cmp)
|
|
return first_line_cmp;
|
|
return compare (ls1->m_last_line, ls2->m_last_line);
|
|
}
|
|
|
|
linenum_type m_first_line;
|
|
linenum_type m_last_line;
|
|
};
|
|
|
|
#if CHECKING_P
|
|
|
|
/* Selftests for line_span. */
|
|
|
|
static void
|
|
test_line_span ()
|
|
{
|
|
line_span line_one (1, 1);
|
|
ASSERT_EQ (1, line_one.get_first_line ());
|
|
ASSERT_EQ (1, line_one.get_last_line ());
|
|
ASSERT_FALSE (line_one.contains_line_p (0));
|
|
ASSERT_TRUE (line_one.contains_line_p (1));
|
|
ASSERT_FALSE (line_one.contains_line_p (2));
|
|
|
|
line_span lines_1_to_3 (1, 3);
|
|
ASSERT_EQ (1, lines_1_to_3.get_first_line ());
|
|
ASSERT_EQ (3, lines_1_to_3.get_last_line ());
|
|
ASSERT_TRUE (lines_1_to_3.contains_line_p (1));
|
|
ASSERT_TRUE (lines_1_to_3.contains_line_p (3));
|
|
|
|
ASSERT_EQ (0, line_span::comparator (&line_one, &line_one));
|
|
ASSERT_GT (line_span::comparator (&lines_1_to_3, &line_one), 0);
|
|
ASSERT_LT (line_span::comparator (&line_one, &lines_1_to_3), 0);
|
|
|
|
/* A linenum > 2^31. */
|
|
const linenum_type LARGEST_LINE = 0xffffffff;
|
|
line_span largest_line (LARGEST_LINE, LARGEST_LINE);
|
|
ASSERT_EQ (LARGEST_LINE, largest_line.get_first_line ());
|
|
ASSERT_EQ (LARGEST_LINE, largest_line.get_last_line ());
|
|
|
|
ASSERT_GT (line_span::comparator (&largest_line, &line_one), 0);
|
|
ASSERT_LT (line_span::comparator (&line_one, &largest_line), 0);
|
|
}
|
|
|
|
#endif /* #if CHECKING_P */
|
|
|
|
/* A class to control the overall layout when printing a diagnostic.
|
|
|
|
The layout is determined within the constructor.
|
|
It is then printed by repeatedly calling the "print_source_line",
|
|
"print_annotation_line" and "print_any_fixits" methods.
|
|
|
|
We assume we have disjoint ranges. */
|
|
|
|
class layout
|
|
{
|
|
public:
|
|
layout (diagnostic_context *context,
|
|
rich_location *richloc,
|
|
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]; }
|
|
|
|
int get_linenum_width () const { return m_linenum_width; }
|
|
int get_x_offset_display () const { return m_x_offset_display; }
|
|
|
|
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_line (linenum_type row);
|
|
|
|
private:
|
|
bool will_show_line_p (linenum_type row) const;
|
|
void print_leading_fixits (linenum_type row);
|
|
line_bounds print_source_line (linenum_type row, const char *line,
|
|
int line_bytes);
|
|
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 finish_column) const;
|
|
void show_ruler (int max_column) const;
|
|
|
|
bool validate_fixit_hint_p (const fixit_hint *hint);
|
|
|
|
void calculate_line_spans ();
|
|
void calculate_linenum_width ();
|
|
void calculate_x_offset_display ();
|
|
|
|
void print_newline ();
|
|
|
|
bool
|
|
get_state_at_point (/* Inputs. */
|
|
linenum_type row, int column,
|
|
int first_non_ws, int last_non_ws,
|
|
enum column_unit col_unit,
|
|
/* Outputs. */
|
|
point_state *out_state);
|
|
|
|
int
|
|
get_x_bound_for_row (linenum_type row, int caret_column,
|
|
int last_non_ws);
|
|
|
|
void
|
|
move_to_column (int *column, int dest_column, bool add_left_margin);
|
|
|
|
private:
|
|
diagnostic_context *m_context;
|
|
pretty_printer *m_pp;
|
|
location_t m_primary_loc;
|
|
exploc_with_display_col m_exploc;
|
|
colorizer m_colorizer;
|
|
bool m_colorize_source_p;
|
|
bool m_show_labels_p;
|
|
bool m_show_line_numbers_p;
|
|
bool m_diagnostic_path_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_display;
|
|
};
|
|
|
|
/* Implementation of "class colorizer". */
|
|
|
|
/* The constructor for "colorizer". Lookup and store color codes for the
|
|
different kinds of things we might need to print. */
|
|
|
|
colorizer::colorizer (diagnostic_context *context,
|
|
diagnostic_t diagnostic_kind) :
|
|
m_context (context),
|
|
m_diagnostic_kind (diagnostic_kind),
|
|
m_current_state (STATE_NORMAL_TEXT)
|
|
{
|
|
m_range1 = get_color_by_name ("range1");
|
|
m_range2 = get_color_by_name ("range2");
|
|
m_fixit_insert = get_color_by_name ("fixit-insert");
|
|
m_fixit_delete = get_color_by_name ("fixit-delete");
|
|
m_stop_color = colorize_stop (pp_show_color (context->printer));
|
|
}
|
|
|
|
/* The destructor for "colorize". If colorization is on, print a code to
|
|
turn it off. */
|
|
|
|
colorizer::~colorizer ()
|
|
{
|
|
finish_state (m_current_state);
|
|
}
|
|
|
|
/* Update state, printing color codes if necessary if there's a state
|
|
change. */
|
|
|
|
void
|
|
colorizer::set_state (int new_state)
|
|
{
|
|
if (m_current_state != new_state)
|
|
{
|
|
finish_state (m_current_state);
|
|
m_current_state = new_state;
|
|
begin_state (new_state);
|
|
}
|
|
}
|
|
|
|
/* Turn on any colorization for STATE. */
|
|
|
|
void
|
|
colorizer::begin_state (int state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case STATE_NORMAL_TEXT:
|
|
break;
|
|
|
|
case STATE_FIXIT_INSERT:
|
|
pp_string (m_context->printer, m_fixit_insert);
|
|
break;
|
|
|
|
case STATE_FIXIT_DELETE:
|
|
pp_string (m_context->printer, m_fixit_delete);
|
|
break;
|
|
|
|
case 0:
|
|
/* Make range 0 be the same color as the "kind" text
|
|
(error vs warning vs note). */
|
|
pp_string
|
|
(m_context->printer,
|
|
colorize_start (pp_show_color (m_context->printer),
|
|
diagnostic_get_color_for_kind (m_diagnostic_kind)));
|
|
break;
|
|
|
|
case 1:
|
|
pp_string (m_context->printer, m_range1);
|
|
break;
|
|
|
|
case 2:
|
|
pp_string (m_context->printer, m_range2);
|
|
break;
|
|
|
|
default:
|
|
/* For ranges beyond 2, alternate between color 1 and color 2. */
|
|
{
|
|
gcc_assert (state > 2);
|
|
pp_string (m_context->printer,
|
|
state % 2 ? m_range1 : m_range2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Turn off any colorization for STATE. */
|
|
|
|
void
|
|
colorizer::finish_state (int state)
|
|
{
|
|
if (state != STATE_NORMAL_TEXT)
|
|
pp_string (m_context->printer, m_stop_color);
|
|
}
|
|
|
|
/* Get the color code for NAME (or the empty string if
|
|
colorization is disabled). */
|
|
|
|
const char *
|
|
colorizer::get_color_by_name (const char *name)
|
|
{
|
|
return colorize_start (pp_show_color (m_context->printer), name);
|
|
}
|
|
|
|
/* Implementation of class layout_range. */
|
|
|
|
/* The constructor for class layout_range.
|
|
Initialize various layout_point fields from expanded_location
|
|
equivalents; we've already filtered on file. */
|
|
|
|
layout_range::layout_range (const exploc_with_display_col &start_exploc,
|
|
const exploc_with_display_col &finish_exploc,
|
|
enum range_display_kind range_display_kind,
|
|
const exploc_with_display_col &caret_exploc,
|
|
unsigned original_idx,
|
|
const range_label *label)
|
|
: m_start (start_exploc),
|
|
m_finish (finish_exploc),
|
|
m_range_display_kind (range_display_kind),
|
|
m_caret (caret_exploc),
|
|
m_original_idx (original_idx),
|
|
m_label (label)
|
|
{
|
|
}
|
|
|
|
/* Is (column, row) within the given range?
|
|
We've already filtered on the file.
|
|
|
|
Ranges are closed (both limits are within the range).
|
|
|
|
Example A: a single-line range:
|
|
start: (col=22, line=2)
|
|
finish: (col=38, line=2)
|
|
|
|
|00000011111111112222222222333333333344444444444
|
|
|34567890123456789012345678901234567890123456789
|
|
--+-----------------------------------------------
|
|
01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
02|bbbbbbbbbbbbbbbbbbbSwwwwwwwwwwwwwwwFaaaaaaaaaaa
|
|
03|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
|
|
Example B: a multiline range with
|
|
start: (col=14, line=3)
|
|
finish: (col=08, line=5)
|
|
|
|
|00000011111111112222222222333333333344444444444
|
|
|34567890123456789012345678901234567890123456789
|
|
--+-----------------------------------------------
|
|
01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
02|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
03|bbbbbbbbbbbSwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
|
|
04|wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
|
|
05|wwwwwFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
06|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
--+-----------------------------------------------
|
|
|
|
Legend:
|
|
- 'b' indicates a point *before* the range
|
|
- 'S' indicates the start of the range
|
|
- 'w' indicates a point within the range
|
|
- 'F' indicates the finish of the range (which is
|
|
within it).
|
|
- 'a' indicates a subsequent point *after* the range.
|
|
|
|
COL_UNIT controls whether we check the byte column or
|
|
the display column; one or the other is more convenient
|
|
depending on the context. */
|
|
|
|
bool
|
|
layout_range::contains_point (linenum_type row, int column,
|
|
enum column_unit col_unit) const
|
|
{
|
|
gcc_assert (m_start.m_line <= m_finish.m_line);
|
|
/* ...but the equivalent isn't true for the columns;
|
|
consider example B in the comment above. */
|
|
|
|
if (row < m_start.m_line)
|
|
/* Points before the first line of the range are
|
|
outside it (corresponding to line 01 in example A
|
|
and lines 01 and 02 in example B above). */
|
|
return false;
|
|
|
|
if (row == m_start.m_line)
|
|
/* On same line as start of range (corresponding
|
|
to line 02 in example A and line 03 in example B). */
|
|
{
|
|
if (column < m_start.m_columns[col_unit])
|
|
/* Points on the starting line of the range, but
|
|
before the column in which it begins. */
|
|
return false;
|
|
|
|
if (row < m_finish.m_line)
|
|
/* This is a multiline range; the point
|
|
is within it (corresponds to line 03 in example B
|
|
from column 14 onwards) */
|
|
return true;
|
|
else
|
|
{
|
|
/* This is a single-line range. */
|
|
gcc_assert (row == m_finish.m_line);
|
|
return column <= m_finish.m_columns[col_unit];
|
|
}
|
|
}
|
|
|
|
/* The point is in a line beyond that containing the
|
|
start of the range: lines 03 onwards in example A,
|
|
and lines 04 onwards in example B. */
|
|
gcc_assert (row > m_start.m_line);
|
|
|
|
if (row > m_finish.m_line)
|
|
/* The point is beyond the final line of the range
|
|
(lines 03 onwards in example A, and lines 06 onwards
|
|
in example B). */
|
|
return false;
|
|
|
|
if (row < m_finish.m_line)
|
|
{
|
|
/* The point is in a line that's fully within a multiline
|
|
range (e.g. line 04 in example B). */
|
|
gcc_assert (m_start.m_line < m_finish.m_line);
|
|
return true;
|
|
}
|
|
|
|
gcc_assert (row == m_finish.m_line);
|
|
|
|
return column <= m_finish.m_columns[col_unit];
|
|
}
|
|
|
|
/* Does this layout_range contain any part of line ROW? */
|
|
|
|
bool
|
|
layout_range::intersects_line_p (linenum_type row) const
|
|
{
|
|
gcc_assert (m_start.m_line <= m_finish.m_line);
|
|
if (row < m_start.m_line)
|
|
return false;
|
|
if (row > m_finish.m_line)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
/* Default for when we don't care what the tab expansion is set to. */
|
|
static const int def_tabstop = 8;
|
|
|
|
/* Create some expanded locations for testing layout_range. The filename
|
|
member of the explocs is set to the empty string. This member will only be
|
|
inspected by the calls to location_compute_display_column() made from the
|
|
layout_point constructors. That function will check for an empty filename
|
|
argument and not attempt to open it, rather treating the non-existent data
|
|
as if the display width were the same as the byte count. Tests exercising a
|
|
real difference between byte count and display width are performed later,
|
|
e.g. in test_diagnostic_show_locus_one_liner_utf8(). */
|
|
|
|
static layout_range
|
|
make_range (int start_line, int start_col, int end_line, int end_col)
|
|
{
|
|
const expanded_location start_exploc
|
|
= {"", start_line, start_col, NULL, false};
|
|
const expanded_location finish_exploc
|
|
= {"", end_line, end_col, NULL, false};
|
|
return layout_range (exploc_with_display_col (start_exploc, def_tabstop),
|
|
exploc_with_display_col (finish_exploc, def_tabstop),
|
|
SHOW_RANGE_WITHOUT_CARET,
|
|
exploc_with_display_col (start_exploc, def_tabstop),
|
|
0, NULL);
|
|
}
|
|
|
|
/* Selftests for layout_range::contains_point and
|
|
layout_range::intersects_line_p. */
|
|
|
|
/* Selftest for layout_range, where the layout_range
|
|
is a range with start==end i.e. a single point. */
|
|
|
|
static void
|
|
test_layout_range_for_single_point ()
|
|
{
|
|
layout_range point = make_range (7, 10, 7, 10);
|
|
|
|
/* Tests for layout_range::contains_point. */
|
|
|
|
for (int i = 0; i != CU_NUM_UNITS; ++i)
|
|
{
|
|
const enum column_unit col_unit = (enum column_unit) i;
|
|
|
|
/* Before the line. */
|
|
ASSERT_FALSE (point.contains_point (6, 1, col_unit));
|
|
|
|
/* On the line, but before start. */
|
|
ASSERT_FALSE (point.contains_point (7, 9, col_unit));
|
|
|
|
/* At the point. */
|
|
ASSERT_TRUE (point.contains_point (7, 10, col_unit));
|
|
|
|
/* On the line, after the point. */
|
|
ASSERT_FALSE (point.contains_point (7, 11, col_unit));
|
|
|
|
/* After the line. */
|
|
ASSERT_FALSE (point.contains_point (8, 1, col_unit));
|
|
}
|
|
|
|
/* Tests for layout_range::intersects_line_p. */
|
|
ASSERT_FALSE (point.intersects_line_p (6));
|
|
ASSERT_TRUE (point.intersects_line_p (7));
|
|
ASSERT_FALSE (point.intersects_line_p (8));
|
|
}
|
|
|
|
/* Selftest for layout_range, where the layout_range
|
|
is the single-line range shown as "Example A" above. */
|
|
|
|
static void
|
|
test_layout_range_for_single_line ()
|
|
{
|
|
layout_range example_a = make_range (2, 22, 2, 38);
|
|
|
|
/* Tests for layout_range::contains_point. */
|
|
|
|
for (int i = 0; i != CU_NUM_UNITS; ++i)
|
|
{
|
|
const enum column_unit col_unit = (enum column_unit) i;
|
|
|
|
/* Before the line. */
|
|
ASSERT_FALSE (example_a.contains_point (1, 1, col_unit));
|
|
|
|
/* On the line, but before start. */
|
|
ASSERT_FALSE (example_a.contains_point (2, 21, col_unit));
|
|
|
|
/* On the line, at the start. */
|
|
ASSERT_TRUE (example_a.contains_point (2, 22, col_unit));
|
|
|
|
/* On the line, within the range. */
|
|
ASSERT_TRUE (example_a.contains_point (2, 23, col_unit));
|
|
|
|
/* On the line, at the end. */
|
|
ASSERT_TRUE (example_a.contains_point (2, 38, col_unit));
|
|
|
|
/* On the line, after the end. */
|
|
ASSERT_FALSE (example_a.contains_point (2, 39, col_unit));
|
|
|
|
/* After the line. */
|
|
ASSERT_FALSE (example_a.contains_point (2, 39, col_unit));
|
|
}
|
|
|
|
/* Tests for layout_range::intersects_line_p. */
|
|
ASSERT_FALSE (example_a.intersects_line_p (1));
|
|
ASSERT_TRUE (example_a.intersects_line_p (2));
|
|
ASSERT_FALSE (example_a.intersects_line_p (3));
|
|
}
|
|
|
|
/* Selftest for layout_range, where the layout_range
|
|
is the multi-line range shown as "Example B" above. */
|
|
|
|
static void
|
|
test_layout_range_for_multiple_lines ()
|
|
{
|
|
layout_range example_b = make_range (3, 14, 5, 8);
|
|
|
|
/* Tests for layout_range::contains_point. */
|
|
|
|
for (int i = 0; i != CU_NUM_UNITS; ++i)
|
|
{
|
|
const enum column_unit col_unit = (enum column_unit) i;
|
|
|
|
/* Before first line. */
|
|
ASSERT_FALSE (example_b.contains_point (1, 1, col_unit));
|
|
|
|
/* On the first line, but before start. */
|
|
ASSERT_FALSE (example_b.contains_point (3, 13, col_unit));
|
|
|
|
/* At the start. */
|
|
ASSERT_TRUE (example_b.contains_point (3, 14, col_unit));
|
|
|
|
/* On the first line, within the range. */
|
|
ASSERT_TRUE (example_b.contains_point (3, 15, col_unit));
|
|
|
|
/* On an interior line.
|
|
The column number should not matter; try various boundary
|
|
values. */
|
|
ASSERT_TRUE (example_b.contains_point (4, 1, col_unit));
|
|
ASSERT_TRUE (example_b.contains_point (4, 7, col_unit));
|
|
ASSERT_TRUE (example_b.contains_point (4, 8, col_unit));
|
|
ASSERT_TRUE (example_b.contains_point (4, 9, col_unit));
|
|
ASSERT_TRUE (example_b.contains_point (4, 13, col_unit));
|
|
ASSERT_TRUE (example_b.contains_point (4, 14, col_unit));
|
|
ASSERT_TRUE (example_b.contains_point (4, 15, col_unit));
|
|
|
|
/* On the final line, before the end. */
|
|
ASSERT_TRUE (example_b.contains_point (5, 7, col_unit));
|
|
|
|
/* On the final line, at the end. */
|
|
ASSERT_TRUE (example_b.contains_point (5, 8, col_unit));
|
|
|
|
/* On the final line, after the end. */
|
|
ASSERT_FALSE (example_b.contains_point (5, 9, col_unit));
|
|
|
|
/* After the line. */
|
|
ASSERT_FALSE (example_b.contains_point (6, 1, col_unit));
|
|
}
|
|
|
|
/* Tests for layout_range::intersects_line_p. */
|
|
ASSERT_FALSE (example_b.intersects_line_p (2));
|
|
ASSERT_TRUE (example_b.intersects_line_p (3));
|
|
ASSERT_TRUE (example_b.intersects_line_p (4));
|
|
ASSERT_TRUE (example_b.intersects_line_p (5));
|
|
ASSERT_FALSE (example_b.intersects_line_p (6));
|
|
}
|
|
|
|
#endif /* #if CHECKING_P */
|
|
|
|
/* Given a source line LINE of length LINE_BYTES bytes, determine the length
|
|
(still in bytes, not display cols) without any trailing whitespace. */
|
|
|
|
static int
|
|
get_line_bytes_without_trailing_whitespace (const char *line, int line_bytes)
|
|
{
|
|
int result = line_bytes;
|
|
while (result > 0)
|
|
{
|
|
char ch = line[result - 1];
|
|
if (ch == ' ' || ch == '\t' || ch == '\r')
|
|
result--;
|
|
else
|
|
break;
|
|
}
|
|
gcc_assert (result >= 0);
|
|
gcc_assert (result <= line_bytes);
|
|
gcc_assert (result == 0 ||
|
|
(line[result - 1] != ' '
|
|
&& line[result -1] != '\t'
|
|
&& line[result -1] != '\r'));
|
|
return result;
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
/* A helper function for testing get_line_bytes_without_trailing_whitespace. */
|
|
|
|
static void
|
|
assert_eq (const char *line, int expected_bytes)
|
|
{
|
|
int actual_value
|
|
= get_line_bytes_without_trailing_whitespace (line, strlen (line));
|
|
ASSERT_EQ (actual_value, expected_bytes);
|
|
}
|
|
|
|
/* Verify that get_line_bytes_without_trailing_whitespace is sane for
|
|
various inputs. It is not required to handle newlines. */
|
|
|
|
static void
|
|
test_get_line_bytes_without_trailing_whitespace ()
|
|
{
|
|
assert_eq ("", 0);
|
|
assert_eq (" ", 0);
|
|
assert_eq ("\t", 0);
|
|
assert_eq ("\r", 0);
|
|
assert_eq ("hello world", 11);
|
|
assert_eq ("hello world ", 11);
|
|
assert_eq ("hello world \t\t ", 11);
|
|
assert_eq ("hello world\r", 11);
|
|
}
|
|
|
|
#endif /* #if CHECKING_P */
|
|
|
|
/* Helper function for layout's ctor, for sanitizing locations relative
|
|
to the primary location within a diagnostic.
|
|
|
|
Compare LOC_A and LOC_B to see if it makes sense to print underlines
|
|
connecting their expanded locations. Doing so is only guaranteed to
|
|
make sense if the locations share the same macro expansion "history"
|
|
i.e. they can be traced through the same macro expansions, eventually
|
|
reaching an ordinary map.
|
|
|
|
This may be too strong a condition, but it effectively sanitizes
|
|
PR c++/70105, which has an example of printing an expression where the
|
|
final location of the expression is in a different macro, which
|
|
erroneously was leading to hundreds of lines of irrelevant source
|
|
being printed. */
|
|
|
|
static bool
|
|
compatible_locations_p (location_t loc_a, location_t loc_b)
|
|
{
|
|
if (IS_ADHOC_LOC (loc_a))
|
|
loc_a = get_location_from_adhoc_loc (line_table, loc_a);
|
|
if (IS_ADHOC_LOC (loc_b))
|
|
loc_b = get_location_from_adhoc_loc (line_table, loc_b);
|
|
|
|
/* If either location is one of the special locations outside of a
|
|
linemap, they are only compatible if they are equal. */
|
|
if (loc_a < RESERVED_LOCATION_COUNT
|
|
|| loc_b < RESERVED_LOCATION_COUNT)
|
|
return loc_a == loc_b;
|
|
|
|
const line_map *map_a = linemap_lookup (line_table, loc_a);
|
|
linemap_assert (map_a);
|
|
|
|
const line_map *map_b = linemap_lookup (line_table, loc_b);
|
|
linemap_assert (map_b);
|
|
|
|
/* Are they within the same map? */
|
|
if (map_a == map_b)
|
|
{
|
|
/* Are both within the same macro expansion? */
|
|
if (linemap_macro_expansion_map_p (map_a))
|
|
{
|
|
/* If so, then they're only compatible if either both are
|
|
from the macro definition, or both from the macro arguments. */
|
|
bool loc_a_from_defn
|
|
= linemap_location_from_macro_definition_p (line_table, loc_a);
|
|
bool loc_b_from_defn
|
|
= linemap_location_from_macro_definition_p (line_table, loc_b);
|
|
if (loc_a_from_defn != loc_b_from_defn)
|
|
return false;
|
|
|
|
/* Expand each location towards the spelling location, and
|
|
recurse. */
|
|
const line_map_macro *macro_map = linemap_check_macro (map_a);
|
|
location_t loc_a_toward_spelling
|
|
= linemap_macro_map_loc_unwind_toward_spelling (line_table,
|
|
macro_map,
|
|
loc_a);
|
|
location_t loc_b_toward_spelling
|
|
= linemap_macro_map_loc_unwind_toward_spelling (line_table,
|
|
macro_map,
|
|
loc_b);
|
|
return compatible_locations_p (loc_a_toward_spelling,
|
|
loc_b_toward_spelling);
|
|
}
|
|
|
|
/* Otherwise they are within the same ordinary map. */
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
/* Within different maps. */
|
|
|
|
/* If either is within a macro expansion, they are incompatible. */
|
|
if (linemap_macro_expansion_map_p (map_a)
|
|
|| linemap_macro_expansion_map_p (map_b))
|
|
return false;
|
|
|
|
/* Within two different ordinary maps; they are compatible iff they
|
|
are in the same file. */
|
|
const line_map_ordinary *ord_map_a = linemap_check_ordinary (map_a);
|
|
const line_map_ordinary *ord_map_b = linemap_check_ordinary (map_b);
|
|
return ord_map_a->to_file == ord_map_b->to_file;
|
|
}
|
|
}
|
|
|
|
/* Comparator for sorting fix-it hints. */
|
|
|
|
static int
|
|
fixit_cmp (const void *p_a, const void *p_b)
|
|
{
|
|
const fixit_hint * hint_a = *static_cast<const fixit_hint * const *> (p_a);
|
|
const fixit_hint * hint_b = *static_cast<const fixit_hint * const *> (p_b);
|
|
return hint_a->get_start_loc () - hint_b->get_start_loc ();
|
|
}
|
|
|
|
/* Implementation of class layout. */
|
|
|
|
/* Constructor for class layout.
|
|
|
|
Filter the ranges from the rich_location to those that we can
|
|
sanely print, populating m_layout_ranges and m_fixit_hints.
|
|
Determine the range of lines that we will print, splitting them
|
|
up into an ordered list of disjoint spans of contiguous line numbers.
|
|
Determine m_x_offset_display, to ensure that the primary caret
|
|
will fit within the max_width provided by the diagnostic_context. */
|
|
|
|
layout::layout (diagnostic_context * context,
|
|
rich_location *richloc,
|
|
diagnostic_t diagnostic_kind)
|
|
: m_context (context),
|
|
m_pp (context->printer),
|
|
m_primary_loc (richloc->get_range (0)->m_loc),
|
|
m_exploc (richloc->get_expanded_location (0), context->tabstop),
|
|
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_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
|
|
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_display (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, idx, false);
|
|
}
|
|
|
|
/* Populate m_fixit_hints, filtering to only those that are in the
|
|
same file. */
|
|
for (unsigned int i = 0; i < richloc->get_num_fixit_hints (); i++)
|
|
{
|
|
const fixit_hint *hint = richloc->get_fixit_hint (i);
|
|
if (validate_fixit_hint_p (hint))
|
|
m_fixit_hints.safe_push (hint);
|
|
}
|
|
|
|
/* Sort m_fixit_hints. */
|
|
m_fixit_hints.qsort (fixit_cmp);
|
|
|
|
/* Populate the indicated members. */
|
|
calculate_line_spans ();
|
|
calculate_linenum_width ();
|
|
calculate_x_offset_display ();
|
|
|
|
if (context->show_ruler_p)
|
|
show_ruler (m_x_offset_display + m_context->caret_max_width);
|
|
}
|
|
|
|
|
|
/* 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
|
|
already specified by other locations.
|
|
|
|
Return true iff LOC_RANGE was added. */
|
|
|
|
bool
|
|
layout::maybe_add_location_range (const location_range *loc_range,
|
|
unsigned original_idx,
|
|
bool restrict_to_current_line_spans)
|
|
{
|
|
gcc_assert (loc_range);
|
|
|
|
/* Split the "range" into caret and range information. */
|
|
source_range src_range = get_range_from_loc (line_table, loc_range->m_loc);
|
|
|
|
/* Expand the various locations. */
|
|
expanded_location start
|
|
= linemap_client_expand_location_to_spelling_point
|
|
(src_range.m_start, LOCATION_ASPECT_START);
|
|
expanded_location finish
|
|
= linemap_client_expand_location_to_spelling_point
|
|
(src_range.m_finish, LOCATION_ASPECT_FINISH);
|
|
expanded_location caret
|
|
= linemap_client_expand_location_to_spelling_point
|
|
(loc_range->m_loc, LOCATION_ASPECT_CARET);
|
|
|
|
/* If any part of the range isn't in the same file as the primary
|
|
location of this diagnostic, ignore the range. */
|
|
if (start.file != m_exploc.file)
|
|
return false;
|
|
if (finish.file != m_exploc.file)
|
|
return false;
|
|
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_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. */
|
|
return false;
|
|
|
|
/* Everything is now known to be in the correct source file,
|
|
but it may require further sanitization. */
|
|
layout_range ri (exploc_with_display_col (start, m_context->tabstop),
|
|
exploc_with_display_col (finish, m_context->tabstop),
|
|
loc_range->m_range_display_kind,
|
|
exploc_with_display_col (caret, m_context->tabstop),
|
|
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
|
|
range is likely to be nonsensical. Also, attempting to do so
|
|
breaks assumptions within the printing code (PR c/68473).
|
|
Similarly, don't attempt to print ranges if one or both ends
|
|
of the range aren't sane to print relative to the
|
|
primary location (PR c++/70105). */
|
|
if (start.line > finish.line
|
|
|| !compatible_locations_p (src_range.m_start, m_primary_loc)
|
|
|| !compatible_locations_p (src_range.m_finish, m_primary_loc))
|
|
{
|
|
/* Is this the primary location? */
|
|
if (m_layout_ranges.length () == 0)
|
|
{
|
|
/* We want to print the caret for the primary location, but
|
|
we must sanitize away m_start and m_finish. */
|
|
ri.m_start = ri.m_caret;
|
|
ri.m_finish = ri.m_caret;
|
|
}
|
|
else
|
|
/* This is a non-primary range; ignore it. */
|
|
return false;
|
|
}
|
|
|
|
/* Potentially filter to just the lines already specified by other
|
|
locations. This is for use by gcc_rich_location::add_location_if_nearby.
|
|
The layout ctor doesn't use it, and can't because m_line_spans
|
|
hasn't been set up at that point. */
|
|
if (restrict_to_current_line_spans)
|
|
{
|
|
if (!will_show_line_p (start.line))
|
|
return false;
|
|
if (!will_show_line_p (finish.line))
|
|
return false;
|
|
if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
|
|
if (!will_show_line_p (caret.line))
|
|
return false;
|
|
}
|
|
|
|
/* Passed all the tests; add the range to m_layout_ranges so that
|
|
it will be printed. */
|
|
m_layout_ranges.safe_push (ri);
|
|
return true;
|
|
}
|
|
|
|
/* Return true iff ROW is within one of the line spans for this layout. */
|
|
|
|
bool
|
|
layout::will_show_line_p (linenum_type row) const
|
|
{
|
|
for (int line_span_idx = 0; line_span_idx < get_num_line_spans ();
|
|
line_span_idx++)
|
|
{
|
|
const line_span *line_span = get_line_span (line_span_idx);
|
|
if (line_span->contains_line_p (row))
|
|
return true;
|
|
}
|
|
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);
|
|
|
|
pp_emit_prefix (m_pp);
|
|
|
|
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. */
|
|
|
|
bool
|
|
layout::print_heading_for_line_span_index_p (int line_span_idx) const
|
|
{
|
|
/* We print a heading for every change of line span, hence for every
|
|
line span after the initial one. */
|
|
if (line_span_idx > 0)
|
|
return true;
|
|
|
|
/* We also do it for the initial span if the primary location of the
|
|
diagnostic is in a different span. */
|
|
if (m_exploc.line > (int)get_line_span (0)->m_last_line)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Get an expanded_location for the first location of interest within
|
|
the given line_span.
|
|
Used when printing a heading to indicate a new line span. */
|
|
|
|
expanded_location
|
|
layout::get_expanded_location (const line_span *line_span) const
|
|
{
|
|
/* Whenever possible, use the caret location. */
|
|
if (line_span->contains_line_p (m_exploc.line))
|
|
return m_exploc;
|
|
|
|
/* Otherwise, use the start of the first range that's present
|
|
within the line_span. */
|
|
for (unsigned int i = 0; i < m_layout_ranges.length (); i++)
|
|
{
|
|
const layout_range *lr = &m_layout_ranges[i];
|
|
if (line_span->contains_line_p (lr->m_start.m_line))
|
|
{
|
|
expanded_location exploc = m_exploc;
|
|
exploc.line = lr->m_start.m_line;
|
|
exploc.column = lr->m_start.m_columns[CU_BYTES];
|
|
return exploc;
|
|
}
|
|
}
|
|
|
|
/* Otherwise, use the location of the first fixit-hint present within
|
|
the line_span. */
|
|
for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
|
|
{
|
|
const fixit_hint *hint = m_fixit_hints[i];
|
|
location_t loc = hint->get_start_loc ();
|
|
expanded_location exploc = expand_location (loc);
|
|
if (line_span->contains_line_p (exploc.line))
|
|
return exploc;
|
|
}
|
|
|
|
/* It should not be possible to have a line span that didn't
|
|
contain any of the layout_range or fixit_hint instances. */
|
|
gcc_unreachable ();
|
|
return m_exploc;
|
|
}
|
|
|
|
/* Determine if HINT is meaningful to print within this layout. */
|
|
|
|
bool
|
|
layout::validate_fixit_hint_p (const fixit_hint *hint)
|
|
{
|
|
if (LOCATION_FILE (hint->get_start_loc ()) != m_exploc.file)
|
|
return false;
|
|
if (LOCATION_FILE (hint->get_next_loc ()) != m_exploc.file)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Determine the range of lines affected by HINT.
|
|
This assumes that HINT has already been filtered by
|
|
validate_fixit_hint_p, and so affects the correct source file. */
|
|
|
|
static line_span
|
|
get_line_span_for_fixit_hint (const fixit_hint *hint)
|
|
{
|
|
gcc_assert (hint);
|
|
|
|
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 ()));
|
|
}
|
|
|
|
/* We want to print the pertinent source code at a diagnostic. The
|
|
rich_location can contain multiple locations. This will have been
|
|
filtered into m_exploc (the caret for the primary location) and
|
|
m_layout_ranges, for those ranges within the same source file.
|
|
|
|
We will print a subset of the lines within the source file in question,
|
|
as a collection of "spans" of lines.
|
|
|
|
This function populates m_line_spans with an ordered, disjoint list of
|
|
the line spans of interest.
|
|
|
|
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 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 ()
|
|
{
|
|
/* This should only be called once, by the ctor. */
|
|
gcc_assert (m_line_spans.length () == 0);
|
|
|
|
/* Populate tmp_spans with individual spans, for each of
|
|
m_exploc, and for m_layout_ranges. */
|
|
auto_vec<line_span> tmp_spans (1 + m_layout_ranges.length ());
|
|
tmp_spans.safe_push (line_span (m_exploc.line, m_exploc.line));
|
|
for (unsigned int i = 0; i < m_layout_ranges.length (); i++)
|
|
{
|
|
const layout_range *lr = &m_layout_ranges[i];
|
|
gcc_assert (lr->m_start.m_line <= lr->m_finish.m_line);
|
|
tmp_spans.safe_push (line_span (lr->m_start.m_line,
|
|
lr->m_finish.m_line));
|
|
}
|
|
|
|
/* Also add spans for any fix-it hints, in case they cover other lines. */
|
|
for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
|
|
{
|
|
const fixit_hint *hint = m_fixit_hints[i];
|
|
gcc_assert (hint);
|
|
tmp_spans.safe_push (get_line_span_for_fixit_hint (hint));
|
|
}
|
|
|
|
/* Sort them. */
|
|
tmp_spans.qsort(line_span::comparator);
|
|
|
|
/* Now iterate through tmp_spans, copying into m_line_spans, and
|
|
combining where possible. */
|
|
gcc_assert (tmp_spans.length () > 0);
|
|
m_line_spans.safe_push (tmp_spans[0]);
|
|
for (unsigned int i = 1; i < tmp_spans.length (); i++)
|
|
{
|
|
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);
|
|
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)
|
|
current->m_last_line = next->m_last_line;
|
|
}
|
|
else
|
|
{
|
|
/* No merger possible. */
|
|
m_line_spans.safe_push (*next);
|
|
}
|
|
}
|
|
|
|
/* Verify the result, in m_line_spans. */
|
|
gcc_assert (m_line_spans.length () > 0);
|
|
for (unsigned int i = 1; i < m_line_spans.length (); i++)
|
|
{
|
|
const line_span *prev = &m_line_spans[i - 1];
|
|
const line_span *next = &m_line_spans[i];
|
|
/* The individual spans must be sane. */
|
|
gcc_assert (prev->m_first_line <= prev->m_last_line);
|
|
gcc_assert (next->m_first_line <= next->m_last_line);
|
|
/* The spans must be ordered. */
|
|
gcc_assert (prev->m_first_line < next->m_first_line);
|
|
/* There must be a gap of at least one line between separate spans. */
|
|
gcc_assert ((prev->m_last_line + 1) < next->m_first_line);
|
|
}
|
|
}
|
|
|
|
/* Determine how many display columns need to be reserved for line numbers,
|
|
based on the largest line number that will be needed, and populate
|
|
m_linenum_width. */
|
|
|
|
void
|
|
layout::calculate_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, m_context->min_margin_width - 1);
|
|
}
|
|
|
|
/* Calculate m_x_offset_display, which improves readability in case the source
|
|
line of interest is longer than the user's display. All lines output will be
|
|
shifted to the left (so that their beginning is no longer displayed) by
|
|
m_x_offset_display display columns, so that the caret is in a reasonable
|
|
location. */
|
|
|
|
void
|
|
layout::calculate_x_offset_display ()
|
|
{
|
|
m_x_offset_display = 0;
|
|
|
|
const int max_width = m_context->caret_max_width;
|
|
if (!max_width)
|
|
{
|
|
/* Nothing to do, the width is not capped. */
|
|
return;
|
|
}
|
|
|
|
const char_span line = location_get_source_line (m_exploc.file,
|
|
m_exploc.line);
|
|
if (!line)
|
|
{
|
|
/* Nothing to do, we couldn't find the source line. */
|
|
return;
|
|
}
|
|
int caret_display_column = m_exploc.m_display_col;
|
|
const int line_bytes
|
|
= get_line_bytes_without_trailing_whitespace (line.get_buffer (),
|
|
line.length ());
|
|
int eol_display_column
|
|
= cpp_display_width (line.get_buffer (), line_bytes, m_context->tabstop);
|
|
if (caret_display_column > eol_display_column
|
|
|| !caret_display_column)
|
|
{
|
|
/* This does not make sense, so don't try to do anything in this case. */
|
|
return;
|
|
}
|
|
|
|
/* Adjust caret and eol positions to include the left margin. If we are
|
|
outputting line numbers, then the left margin is equal to m_linenum_width
|
|
plus three for the " | " which follows it. Otherwise the left margin width
|
|
is equal to 1, because layout::print_source_line() will prefix each line
|
|
with a space. */
|
|
const int source_display_cols = eol_display_column;
|
|
int left_margin_size = 1;
|
|
if (m_show_line_numbers_p)
|
|
left_margin_size = m_linenum_width + 3;
|
|
caret_display_column += left_margin_size;
|
|
eol_display_column += left_margin_size;
|
|
|
|
if (eol_display_column <= max_width)
|
|
{
|
|
/* Nothing to do, everything fits in the display. */
|
|
return;
|
|
}
|
|
|
|
/* The line is too long for the display. Calculate an offset such that the
|
|
caret is not too close to the right edge of the screen. It will be
|
|
CARET_LINE_MARGIN display columns from the right edge, unless it is closer
|
|
than that to the end of the source line anyway. */
|
|
int right_margin_size = CARET_LINE_MARGIN;
|
|
right_margin_size = MIN (eol_display_column - caret_display_column,
|
|
right_margin_size);
|
|
if (right_margin_size + left_margin_size >= max_width)
|
|
{
|
|
/* The max_width is very small, so anything we try to do will not be very
|
|
effective; just punt in this case and output with no offset. */
|
|
return;
|
|
}
|
|
const int max_caret_display_column = max_width - right_margin_size;
|
|
if (caret_display_column > max_caret_display_column)
|
|
{
|
|
m_x_offset_display = caret_display_column - max_caret_display_column;
|
|
/* Make sure we don't offset the line into oblivion. */
|
|
static const int min_cols_visible = 2;
|
|
if (source_display_cols - m_x_offset_display < min_cols_visible)
|
|
m_x_offset_display = 0;
|
|
}
|
|
}
|
|
|
|
/* Print line ROW of source code, potentially colorized at any ranges, and
|
|
return the line bounds. LINE is the source line (not necessarily
|
|
0-terminated) and LINE_BYTES is its length in bytes. In order to handle both
|
|
colorization and tab expansion, this function tracks the line position in
|
|
both byte and display column units. */
|
|
|
|
line_bounds
|
|
layout::print_source_line (linenum_type row, const char *line, int line_bytes)
|
|
{
|
|
m_colorizer.set_normal_text ();
|
|
|
|
pp_emit_prefix (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);
|
|
|
|
/* We will stop printing the source line at any trailing whitespace. */
|
|
line_bytes = get_line_bytes_without_trailing_whitespace (line,
|
|
line_bytes);
|
|
|
|
/* This object helps to keep track of which display column we are at, which is
|
|
necessary for computing the line bounds in display units, for doing
|
|
tab expansion, and for implementing m_x_offset_display. */
|
|
cpp_display_width_computation dw (line, line_bytes, m_context->tabstop);
|
|
|
|
/* Skip the first m_x_offset_display display columns. In case the leading
|
|
portion that will be skipped ends with a character with wcwidth > 1, then
|
|
it is possible we skipped too much, so account for that by padding with
|
|
spaces. Note that this does the right thing too in case a tab was the last
|
|
character to be skipped over; the tab is effectively replaced by the
|
|
correct number of trailing spaces needed to offset by the desired number of
|
|
display columns. */
|
|
for (int skipped_display_cols = dw.advance_display_cols (m_x_offset_display);
|
|
skipped_display_cols > m_x_offset_display; --skipped_display_cols)
|
|
pp_space (m_pp);
|
|
|
|
/* Print the line and compute the line_bounds. */
|
|
line_bounds lbounds;
|
|
while (!dw.done ())
|
|
{
|
|
/* Assuming colorization is enabled for the caret and underline
|
|
characters, we may also colorize the associated characters
|
|
within the source line.
|
|
|
|
For frontends that generate range information, we color the
|
|
associated characters in the source line the same as the
|
|
carets and underlines in the annotation line, to make it easier
|
|
for the reader to see the pertinent code.
|
|
|
|
For frontends that only generate carets, we don't colorize the
|
|
characters above them, since this would look strange (e.g.
|
|
colorizing just the first character in a token). */
|
|
if (m_colorize_source_p)
|
|
{
|
|
bool in_range_p;
|
|
point_state state;
|
|
const int start_byte_col = dw.bytes_processed () + 1;
|
|
in_range_p = get_state_at_point (row, start_byte_col,
|
|
0, INT_MAX,
|
|
CU_BYTES,
|
|
&state);
|
|
if (in_range_p)
|
|
m_colorizer.set_range (state.range_idx);
|
|
else
|
|
m_colorizer.set_normal_text ();
|
|
}
|
|
|
|
/* Get the display width of the next character to be output, expanding
|
|
tabs and replacing some control bytes with spaces as necessary. */
|
|
const char *c = dw.next_byte ();
|
|
const int start_disp_col = dw.display_cols_processed () + 1;
|
|
const int this_display_width = dw.process_next_codepoint ();
|
|
if (*c == '\t')
|
|
{
|
|
/* The returned display width is the number of spaces into which the
|
|
tab should be expanded. */
|
|
for (int i = 0; i != this_display_width; ++i)
|
|
pp_space (m_pp);
|
|
continue;
|
|
}
|
|
if (*c == '\0' || *c == '\r')
|
|
{
|
|
/* cpp_wcwidth() promises to return 1 for all control bytes, and we
|
|
want to output these as a single space too, so this case is
|
|
actually the same as the '\t' case. */
|
|
gcc_assert (this_display_width == 1);
|
|
pp_space (m_pp);
|
|
continue;
|
|
}
|
|
|
|
/* We have a (possibly multibyte) character to output; update the line
|
|
bounds if it is not whitespace. */
|
|
if (*c != ' ')
|
|
{
|
|
lbounds.m_last_non_ws_disp_col = dw.display_cols_processed ();
|
|
if (lbounds.m_first_non_ws_disp_col == INT_MAX)
|
|
lbounds.m_first_non_ws_disp_col = start_disp_col;
|
|
}
|
|
|
|
/* Output the character. */
|
|
while (c != dw.next_byte ()) pp_character (m_pp, *c++);
|
|
}
|
|
print_newline ();
|
|
return lbounds;
|
|
}
|
|
|
|
/* Determine if we should print an annotation line for ROW.
|
|
i.e. if any of m_layout_ranges contains ROW. */
|
|
|
|
bool
|
|
layout::should_print_annotation_line_p (linenum_type row) const
|
|
{
|
|
layout_range *range;
|
|
int i;
|
|
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
|
|
{
|
|
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
|
|
{
|
|
pp_emit_prefix (m_pp);
|
|
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. */
|
|
|
|
void
|
|
layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
|
|
{
|
|
int x_bound = get_x_bound_for_row (row, m_exploc.m_display_col,
|
|
lbounds.m_last_non_ws_disp_col);
|
|
|
|
start_annotation_line ();
|
|
pp_space (m_pp);
|
|
|
|
for (int column = 1 + m_x_offset_display; column < x_bound; column++)
|
|
{
|
|
bool in_range_p;
|
|
point_state state;
|
|
in_range_p = get_state_at_point (row, column,
|
|
lbounds.m_first_non_ws_disp_col,
|
|
lbounds.m_last_non_ws_disp_col,
|
|
CU_DISPLAY_COLS,
|
|
&state);
|
|
if (in_range_p)
|
|
{
|
|
/* Within a range. Draw either the caret or an underline. */
|
|
m_colorizer.set_range (state.range_idx);
|
|
if (state.draw_caret_p)
|
|
{
|
|
/* Draw the caret. */
|
|
char caret_char;
|
|
if (state.range_idx < rich_location::STATICALLY_ALLOCATED_RANGES)
|
|
caret_char = m_context->caret_chars[state.range_idx];
|
|
else
|
|
caret_char = '^';
|
|
pp_character (m_pp, caret_char);
|
|
}
|
|
else
|
|
pp_character (m_pp, '~');
|
|
}
|
|
else
|
|
{
|
|
/* Not in a range. */
|
|
m_colorizer.set_normal_text ();
|
|
pp_character (m_pp, ' ');
|
|
}
|
|
}
|
|
print_newline ();
|
|
}
|
|
|
|
/* Implementation detail of layout::print_any_labels.
|
|
|
|
A label within the given row of source. */
|
|
|
|
class line_label
|
|
{
|
|
public:
|
|
line_label (diagnostic_context *context, int state_idx, int column,
|
|
label_text text)
|
|
: m_state_idx (state_idx), m_column (column),
|
|
m_text (text), m_label_line (0), m_has_vbar (true)
|
|
{
|
|
const int bytes = strlen (text.m_buffer);
|
|
m_display_width
|
|
= cpp_display_width (text.m_buffer, bytes, context->tabstop);
|
|
}
|
|
|
|
/* 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;
|
|
/* Order by reverse state index, so that labels are printed
|
|
in order of insertion into the rich_location when the
|
|
sorted list is walked backwards. */
|
|
return -compare (ll1->m_state_idx, ll2->m_state_idx);
|
|
}
|
|
|
|
int m_state_idx;
|
|
int m_column;
|
|
label_text m_text;
|
|
size_t m_display_width;
|
|
int m_label_line;
|
|
bool m_has_vbar;
|
|
};
|
|
|
|
/* 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_display. */
|
|
const int disp_col = range->m_caret.m_columns[CU_DISPLAY_COLS];
|
|
if (disp_col <= m_x_offset_display)
|
|
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 (m_context, i, disp_col, 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 0 : label line 2
|
|
label 1 : 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_display_width >= (size_t)next_column)
|
|
{
|
|
max_label_line++;
|
|
|
|
/* If we've already seen labels with the same column, suppress the
|
|
vertical bar for subsequent ones in this backwards iteration;
|
|
hence only the one with the highest label_line has m_has_vbar set. */
|
|
if (label->m_column == next_column)
|
|
label->m_has_vbar = false;
|
|
}
|
|
|
|
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. */
|
|
{
|
|
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_display;
|
|
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);
|
|
/* Colorize the text, unless it's for events in a
|
|
diagnostic_path. */
|
|
if (!m_diagnostic_path_p)
|
|
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_display_width;
|
|
}
|
|
else if (label->m_has_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 ();
|
|
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.
|
|
|
|
They are printed on lines of their own, before the source line
|
|
itself, with a leading '+'. */
|
|
|
|
void
|
|
layout::print_leading_fixits (linenum_type row)
|
|
{
|
|
for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
|
|
{
|
|
const fixit_hint *hint = m_fixit_hints[i];
|
|
|
|
if (!hint->ends_with_newline_p ())
|
|
/* Not a newline fixit; print it in print_trailing_fixits. */
|
|
continue;
|
|
|
|
gcc_assert (hint->insertion_p ());
|
|
|
|
if (hint->affects_line_p (m_exploc.file, row))
|
|
{
|
|
/* Printing the '+' with normal colorization
|
|
and the inserted line with "insert" colorization
|
|
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.
|
|
We have to print the newline separately to avoid
|
|
getting additional pp prefixes printed. */
|
|
for (size_t i = 0; i < hint->get_length () - 1; i++)
|
|
pp_character (m_pp, hint->get_string ()[i]);
|
|
m_colorizer.set_normal_text ();
|
|
pp_newline (m_pp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Subroutine of layout::print_trailing_fixits.
|
|
|
|
Determine if the annotation line printed for LINE contained
|
|
the exact range from START_COLUMN to FINISH_COLUMN (in display units). */
|
|
|
|
bool
|
|
layout::annotation_line_showed_range_p (linenum_type line, int start_column,
|
|
int finish_column) const
|
|
{
|
|
layout_range *range;
|
|
int i;
|
|
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
|
|
if (range->m_start.m_line == line
|
|
&& range->m_start.m_columns[CU_DISPLAY_COLS] == start_column
|
|
&& range->m_finish.m_line == line
|
|
&& range->m_finish.m_columns[CU_DISPLAY_COLS] == finish_column)
|
|
return true;
|
|
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 (byte or display) columns within a line. */
|
|
|
|
class column_range
|
|
{
|
|
public:
|
|
column_range (int start_, int finish_) : start (start_), finish (finish_)
|
|
{
|
|
/* We must have either a range, or an insertion. */
|
|
gcc_assert (start <= finish || finish == start - 1);
|
|
}
|
|
|
|
bool operator== (const column_range &other) const
|
|
{
|
|
return start == other.start && finish == other.finish;
|
|
}
|
|
|
|
int start;
|
|
int finish;
|
|
};
|
|
|
|
/* Get the range of bytes or display columns that HINT would affect. */
|
|
static column_range
|
|
get_affected_range (diagnostic_context *context,
|
|
const fixit_hint *hint, enum column_unit col_unit)
|
|
{
|
|
expanded_location exploc_start = expand_location (hint->get_start_loc ());
|
|
expanded_location exploc_finish = expand_location (hint->get_next_loc ());
|
|
--exploc_finish.column;
|
|
|
|
int start_column;
|
|
int finish_column;
|
|
if (col_unit == CU_DISPLAY_COLS)
|
|
{
|
|
start_column
|
|
= location_compute_display_column (exploc_start, context->tabstop);
|
|
if (hint->insertion_p ())
|
|
finish_column = start_column - 1;
|
|
else
|
|
finish_column
|
|
= location_compute_display_column (exploc_finish, context->tabstop);
|
|
}
|
|
else
|
|
{
|
|
start_column = exploc_start.column;
|
|
finish_column = exploc_finish.column;
|
|
}
|
|
return column_range (start_column, finish_column);
|
|
}
|
|
|
|
/* Get the range of display columns that would be printed for HINT. */
|
|
|
|
static column_range
|
|
get_printed_columns (diagnostic_context *context, const fixit_hint *hint)
|
|
{
|
|
expanded_location exploc = expand_location (hint->get_start_loc ());
|
|
int start_column = location_compute_display_column (exploc, context->tabstop);
|
|
int hint_width = cpp_display_width (hint->get_string (), hint->get_length (),
|
|
context->tabstop);
|
|
int final_hint_column = start_column + hint_width - 1;
|
|
if (hint->insertion_p ())
|
|
{
|
|
return column_range (start_column, final_hint_column);
|
|
}
|
|
else
|
|
{
|
|
exploc = expand_location (hint->get_next_loc ());
|
|
--exploc.column;
|
|
int finish_column
|
|
= location_compute_display_column (exploc, context->tabstop);
|
|
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. */
|
|
|
|
class correction
|
|
{
|
|
public:
|
|
correction (column_range affected_bytes,
|
|
column_range affected_columns,
|
|
column_range printed_columns,
|
|
const char *new_text, size_t new_text_len,
|
|
int tabstop)
|
|
: m_affected_bytes (affected_bytes),
|
|
m_affected_columns (affected_columns),
|
|
m_printed_columns (printed_columns),
|
|
m_text (xstrdup (new_text)),
|
|
m_byte_length (new_text_len),
|
|
m_tabstop (tabstop),
|
|
m_alloc_sz (new_text_len + 1)
|
|
{
|
|
compute_display_cols ();
|
|
}
|
|
|
|
~correction () { free (m_text); }
|
|
|
|
bool insertion_p () const
|
|
{
|
|
return m_affected_bytes.start == m_affected_bytes.finish + 1;
|
|
}
|
|
|
|
void ensure_capacity (size_t len);
|
|
void ensure_terminated ();
|
|
|
|
void compute_display_cols ()
|
|
{
|
|
m_display_cols = cpp_display_width (m_text, m_byte_length, m_tabstop);
|
|
}
|
|
|
|
void overwrite (int dst_offset, const char_span &src_span)
|
|
{
|
|
gcc_assert (dst_offset >= 0);
|
|
gcc_assert (dst_offset + src_span.length () < m_alloc_sz);
|
|
memcpy (m_text + dst_offset, src_span.get_buffer (),
|
|
src_span.length ());
|
|
}
|
|
|
|
/* 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_bytes;
|
|
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_byte_length; /* Not including null-terminator. */
|
|
int m_display_cols;
|
|
int m_tabstop;
|
|
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_byte_length < m_alloc_sz);
|
|
m_text[m_byte_length] = '\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. */
|
|
|
|
class line_corrections
|
|
{
|
|
public:
|
|
line_corrections (diagnostic_context *context, const char *filename,
|
|
linenum_type row)
|
|
: m_context (context), m_filename (filename), m_row (row)
|
|
{}
|
|
~line_corrections ();
|
|
|
|
void add_hint (const fixit_hint *hint);
|
|
|
|
diagnostic_context *m_context;
|
|
const char *m_filename;
|
|
linenum_type 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;
|
|
}
|
|
|
|
/* A struct wrapping a particular source line, allowing
|
|
run-time bounds-checking of accesses in a checked build. */
|
|
|
|
class source_line
|
|
{
|
|
public:
|
|
source_line (const char *filename, int line);
|
|
|
|
char_span as_span () { return char_span (chars, width); }
|
|
|
|
const char *chars;
|
|
int width;
|
|
};
|
|
|
|
/* source_line's ctor. */
|
|
|
|
source_line::source_line (const char *filename, int line)
|
|
{
|
|
char_span span = location_get_source_line (filename, line);
|
|
chars = span.get_buffer ();
|
|
width = span.length ();
|
|
}
|
|
|
|
/* 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_bytes = get_affected_range (m_context, hint, CU_BYTES);
|
|
column_range affected_columns = get_affected_range (m_context, hint,
|
|
CU_DISPLAY_COLS);
|
|
column_range printed_columns = get_printed_columns (m_context, hint);
|
|
|
|
/* Potentially consolidate. */
|
|
if (!m_corrections.is_empty ())
|
|
{
|
|
correction *last_correction
|
|
= m_corrections[m_corrections.length () - 1];
|
|
|
|
/* The following consolidation code assumes that the fix-it hints
|
|
have been sorted by start (done within layout's ctor). */
|
|
gcc_assert (affected_bytes.start
|
|
>= last_correction->m_affected_bytes.start);
|
|
gcc_assert (printed_columns.start
|
|
>= last_correction->m_printed_columns.start);
|
|
|
|
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_bytes.finish + 1,
|
|
affected_bytes.start - 1);
|
|
|
|
/* Try to read the source. */
|
|
source_line line (m_filename, m_row);
|
|
if (line.chars && 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. */
|
|
int old_byte_len = last_correction->m_byte_length;
|
|
gcc_assert (old_byte_len >= 0);
|
|
int between_byte_len = between.finish + 1 - between.start;
|
|
gcc_assert (between_byte_len >= 0);
|
|
int new_byte_len
|
|
= old_byte_len + between_byte_len + hint->get_length ();
|
|
gcc_assert (new_byte_len >= 0);
|
|
last_correction->ensure_capacity (new_byte_len);
|
|
last_correction->overwrite
|
|
(old_byte_len,
|
|
line.as_span ().subspan (between.start - 1,
|
|
between.finish + 1 - between.start));
|
|
last_correction->overwrite (old_byte_len + between_byte_len,
|
|
char_span (hint->get_string (),
|
|
hint->get_length ()));
|
|
last_correction->m_byte_length = new_byte_len;
|
|
last_correction->ensure_terminated ();
|
|
last_correction->m_affected_bytes.finish
|
|
= affected_bytes.finish;
|
|
last_correction->m_affected_columns.finish
|
|
= affected_columns.finish;
|
|
int prev_display_cols = last_correction->m_display_cols;
|
|
last_correction->compute_display_cols ();
|
|
last_correction->m_printed_columns.finish
|
|
+= last_correction->m_display_cols - prev_display_cols;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If no consolidation happened, add a new correction instance. */
|
|
m_corrections.safe_push (new correction (affected_bytes,
|
|
affected_columns,
|
|
printed_columns,
|
|
hint->get_string (),
|
|
hint->get_length (),
|
|
m_context->tabstop));
|
|
}
|
|
|
|
/* 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.
|
|
Fix-it hints that insert new lines are handled separately,
|
|
in layout::print_leading_fixits. */
|
|
|
|
void
|
|
layout::print_trailing_fixits (linenum_type row)
|
|
{
|
|
/* Build a list of correction instances for the line,
|
|
potentially consolidating hints (for the sake of readability). */
|
|
line_corrections corrections (m_context, 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 = m_x_offset_display;
|
|
|
|
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. */
|
|
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, true);
|
|
m_colorizer.set_fixit_insert ();
|
|
pp_string (m_pp, c->m_text);
|
|
m_colorizer.set_normal_text ();
|
|
column += c->m_display_cols;
|
|
}
|
|
else
|
|
{
|
|
/* 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_byte_length == 0)
|
|
{
|
|
move_to_column (&column, start_column, true);
|
|
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 (c->m_byte_length > 0)
|
|
{
|
|
move_to_column (&column, start_column, true);
|
|
m_colorizer.set_fixit_insert ();
|
|
pp_string (m_pp, c->m_text);
|
|
m_colorizer.set_normal_text ();
|
|
column += c->m_display_cols;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add a trailing newline, if necessary. */
|
|
move_to_column (&column, 0, false);
|
|
}
|
|
|
|
/* Disable any colorization and emit a newline. */
|
|
|
|
void
|
|
layout::print_newline ()
|
|
{
|
|
m_colorizer.set_normal_text ();
|
|
pp_newline (m_pp);
|
|
}
|
|
|
|
/* Return true if (ROW/COLUMN) is within a range of the layout.
|
|
If it returns true, OUT_STATE is written to, with the
|
|
range index, and whether we should draw the caret at
|
|
(ROW/COLUMN) (as opposed to an underline). COL_UNIT controls
|
|
whether all inputs and outputs are in bytes or display column units. */
|
|
|
|
bool
|
|
layout::get_state_at_point (/* Inputs. */
|
|
linenum_type row, int column,
|
|
int first_non_ws, int last_non_ws,
|
|
enum column_unit col_unit,
|
|
/* Outputs. */
|
|
point_state *out_state)
|
|
{
|
|
layout_range *range;
|
|
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, col_unit))
|
|
{
|
|
out_state->range_idx = i;
|
|
|
|
/* Are we at the range's caret? is it visible? */
|
|
out_state->draw_caret_p = false;
|
|
if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET
|
|
&& row == range->m_caret.m_line
|
|
&& column == range->m_caret.m_columns[col_unit])
|
|
out_state->draw_caret_p = true;
|
|
|
|
/* Within a multiline range, don't display any underline
|
|
in any leading or trailing whitespace on a line.
|
|
We do display carets, however. */
|
|
if (!out_state->draw_caret_p)
|
|
if (column < first_non_ws || column > last_non_ws)
|
|
return false;
|
|
|
|
/* We are within a range. */
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Helper function for use by layout::print_line when printing the
|
|
annotation line under the source line.
|
|
Get the display column beyond the rightmost one that could contain a caret
|
|
or range marker, given that we stop rendering at trailing whitespace.
|
|
ROW is the source line within the given file.
|
|
CARET_COLUMN is the display column of range 0's caret.
|
|
LAST_NON_WS_COLUMN is the last display column containing a non-whitespace
|
|
character of source (as determined when printing the source line). */
|
|
|
|
int
|
|
layout::get_x_bound_for_row (linenum_type row, int caret_column,
|
|
int last_non_ws_column)
|
|
{
|
|
int result = caret_column + 1;
|
|
|
|
layout_range *range;
|
|
int i;
|
|
FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
|
|
{
|
|
if (row >= range->m_start.m_line)
|
|
{
|
|
if (range->m_finish.m_line == row)
|
|
{
|
|
/* On the final line within a range; ensure that
|
|
we render up to the end of the range. */
|
|
const int disp_col = range->m_finish.m_columns[CU_DISPLAY_COLS];
|
|
if (result <= disp_col)
|
|
result = disp_col + 1;
|
|
}
|
|
else if (row < range->m_finish.m_line)
|
|
{
|
|
/* Within a multiline range; ensure that we render up to the
|
|
last non-whitespace column. */
|
|
if (result <= last_non_ws_column)
|
|
result = last_non_ws_column + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Given *COLUMN as an x-coordinate, print spaces to position
|
|
successive output at DEST_COLUMN, printing a newline if necessary,
|
|
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, 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_display;
|
|
}
|
|
|
|
while (*column < dest_column)
|
|
{
|
|
pp_space (m_pp);
|
|
(*column)++;
|
|
}
|
|
}
|
|
|
|
/* For debugging layout issues, render a ruler giving column numbers
|
|
(after the 1-column indent). */
|
|
|
|
void
|
|
layout::show_ruler (int max_column) const
|
|
{
|
|
/* Hundreds. */
|
|
if (max_column > 99)
|
|
{
|
|
start_annotation_line ();
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset_display; column <= max_column; column++)
|
|
if (column % 10 == 0)
|
|
pp_character (m_pp, '0' + (column / 100) % 10);
|
|
else
|
|
pp_space (m_pp);
|
|
pp_newline (m_pp);
|
|
}
|
|
|
|
/* Tens. */
|
|
start_annotation_line ();
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset_display; column <= max_column; column++)
|
|
if (column % 10 == 0)
|
|
pp_character (m_pp, '0' + (column / 10) % 10);
|
|
else
|
|
pp_space (m_pp);
|
|
pp_newline (m_pp);
|
|
|
|
/* Units. */
|
|
start_annotation_line ();
|
|
pp_space (m_pp);
|
|
for (int column = 1 + m_x_offset_display; column <= max_column; column++)
|
|
pp_character (m_pp, '0' + (column % 10));
|
|
pp_newline (m_pp);
|
|
}
|
|
|
|
/* Print leading fix-its (for new lines inserted before the source line)
|
|
then the source line, followed by an annotation line
|
|
consisting of any caret/underlines, then any fixits.
|
|
If the source line can't be read, print nothing. */
|
|
void
|
|
layout::print_line (linenum_type row)
|
|
{
|
|
char_span line = location_get_source_line (m_exploc.file, row);
|
|
if (!line)
|
|
return;
|
|
|
|
print_leading_fixits (row);
|
|
const line_bounds lbounds
|
|
= print_source_line (row, line.get_buffer (), line.length ());
|
|
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);
|
|
}
|
|
|
|
} /* End of anonymous namespace. */
|
|
|
|
/* If LOC is within the spans of lines that will already be printed for
|
|
this gcc_rich_location, then add it as a secondary location and return true.
|
|
|
|
Otherwise return false. */
|
|
|
|
bool
|
|
gcc_rich_location::add_location_if_nearby (location_t loc,
|
|
bool restrict_to_current_line_spans,
|
|
const range_label *label)
|
|
{
|
|
/* Use the layout location-handling logic to sanitize LOC,
|
|
filtering it to the current line spans within a temporary
|
|
layout instance. */
|
|
layout layout (global_dc, this, DK_ERROR);
|
|
location_range loc_range;
|
|
loc_range.m_loc = loc;
|
|
loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
|
|
if (!layout.maybe_add_location_range (&loc_range, 0,
|
|
restrict_to_current_line_spans))
|
|
return false;
|
|
|
|
add_range (loc, SHOW_RANGE_WITHOUT_CARET, label);
|
|
return true;
|
|
}
|
|
|
|
/* Print the physical source code corresponding to the location of
|
|
this diagnostic, with additional annotations. */
|
|
|
|
void
|
|
diagnostic_show_locus (diagnostic_context * context,
|
|
rich_location *richloc,
|
|
diagnostic_t diagnostic_kind)
|
|
{
|
|
location_t loc = richloc->get_loc ();
|
|
/* Do nothing if source-printing has been disabled. */
|
|
if (!context->show_caret)
|
|
return;
|
|
|
|
/* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */
|
|
if (loc <= BUILTINS_LOCATION)
|
|
return;
|
|
|
|
/* Don't print the same source location twice in a row, unless we have
|
|
fix-it hints. */
|
|
if (loc == context->last_location
|
|
&& richloc->get_num_fixit_hints () == 0)
|
|
return;
|
|
|
|
context->last_location = loc;
|
|
|
|
layout layout (context, richloc, diagnostic_kind);
|
|
for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans ();
|
|
line_span_idx++)
|
|
{
|
|
const line_span *line_span = layout.get_line_span (line_span_idx);
|
|
if (context->show_line_numbers_p)
|
|
{
|
|
/* With line numbers, we should show whenever the line-numbering
|
|
"jumps". */
|
|
if (line_span_idx > 0)
|
|
layout.print_gap_in_line_numbering ();
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
#if CHECKING_P
|
|
|
|
namespace selftest {
|
|
|
|
/* Selftests for diagnostic_show_locus. */
|
|
|
|
/* For precise tests of the layout, make clear where the source line will
|
|
start. test_left_margin sets the total byte count from the left side of the
|
|
screen to the start of source lines, after the line number and the separator,
|
|
which consists of the three characters " | ". */
|
|
static const int test_linenum_sep = 3;
|
|
static const int test_left_margin = 7;
|
|
|
|
/* Helper function for test_layout_x_offset_display_utf8(). */
|
|
static void
|
|
test_offset_impl (int caret_byte_col, int max_width,
|
|
int expected_x_offset_display,
|
|
int left_margin = test_left_margin)
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.caret_max_width = max_width;
|
|
/* diagnostic_context::min_margin_width sets the minimum space reserved for
|
|
the line number plus one space after. */
|
|
dc.min_margin_width = left_margin - test_linenum_sep + 1;
|
|
dc.show_line_numbers_p = true;
|
|
rich_location richloc (line_table,
|
|
linemap_position_for_column (line_table,
|
|
caret_byte_col));
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
ASSERT_EQ (left_margin - test_linenum_sep,
|
|
test_layout.get_linenum_width ());
|
|
ASSERT_EQ (expected_x_offset_display,
|
|
test_layout.get_x_offset_display ());
|
|
}
|
|
|
|
/* Test that layout::calculate_x_offset_display() works. */
|
|
static void
|
|
test_layout_x_offset_display_utf8 (const line_table_case &case_)
|
|
{
|
|
|
|
const char *content
|
|
= "This line is very long, so that we can use it to test the logic for "
|
|
"clipping long lines. Also this: \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a "
|
|
"pair of emojis that occupies 8 bytes and 4 display columns, starting at "
|
|
"column #102.\n";
|
|
|
|
/* Number of bytes in the line, subtracting one to remove the newline. */
|
|
const int line_bytes = strlen (content) - 1;
|
|
|
|
/* Number of display columns occupied by the line; each of the 2 emojis
|
|
takes up 2 fewer display columns than it does bytes. */
|
|
const int line_display_cols = line_bytes - 2*2;
|
|
|
|
/* The column of the first emoji. Byte or display is the same as there are
|
|
no multibyte characters earlier on the line. */
|
|
const int emoji_col = 102;
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
|
|
line_table_test ltt (case_);
|
|
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
|
|
|
|
location_t line_end = linemap_position_for_column (line_table, line_bytes);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
|
|
ASSERT_EQ (1, LOCATION_LINE (line_end));
|
|
ASSERT_EQ (line_bytes, LOCATION_COLUMN (line_end));
|
|
|
|
char_span lspan = location_get_source_line (tmp.get_filename (), 1);
|
|
ASSERT_EQ (line_display_cols,
|
|
cpp_display_width (lspan.get_buffer (), lspan.length (),
|
|
def_tabstop));
|
|
ASSERT_EQ (line_display_cols,
|
|
location_compute_display_column (expand_location (line_end),
|
|
def_tabstop));
|
|
ASSERT_EQ (0, memcmp (lspan.get_buffer () + (emoji_col - 1),
|
|
"\xf0\x9f\x98\x82\xf0\x9f\x98\x82", 8));
|
|
|
|
/* (caret_byte, max_width, expected_x_offset_display, [left_margin]) */
|
|
|
|
/* No constraint on the width -> no offset. */
|
|
test_offset_impl (emoji_col, 0, 0);
|
|
|
|
/* Caret is before the beginning -> no offset. */
|
|
test_offset_impl (0, 100, 0);
|
|
|
|
/* Caret is past the end of the line -> no offset. */
|
|
test_offset_impl (line_bytes+1, 100, 0);
|
|
|
|
/* Line fits in the display -> no offset. */
|
|
test_offset_impl (line_bytes, line_display_cols + test_left_margin, 0);
|
|
test_offset_impl (emoji_col, line_display_cols + test_left_margin, 0);
|
|
|
|
/* Line is too long for the display but caret location is OK
|
|
anyway -> no offset. */
|
|
static const int small_width = 24;
|
|
test_offset_impl (1, small_width, 0);
|
|
|
|
/* Width constraint is very small -> no offset. */
|
|
test_offset_impl (emoji_col, CARET_LINE_MARGIN, 0);
|
|
|
|
/* Line would be offset, but due to large line numbers, offsetting
|
|
would remove the whole line -> no offset. */
|
|
static const int huge_left_margin = 100;
|
|
test_offset_impl (emoji_col, huge_left_margin, 0, huge_left_margin);
|
|
|
|
/* Line is the same length as the display, but the line number makes it too
|
|
long, so offset is required. Caret is at the end so padding on the right
|
|
is not in effect. */
|
|
for (int excess = 1; excess <= 3; ++excess)
|
|
test_offset_impl (line_bytes, line_display_cols + test_left_margin - excess,
|
|
excess);
|
|
|
|
/* Line is much too long for the display, caret is near the end ->
|
|
offset should be such that the line fits in the display and caret
|
|
remains the same distance from the end that it was. */
|
|
for (int caret_offset = 0, max_offset = MIN (CARET_LINE_MARGIN, 10);
|
|
caret_offset <= max_offset; ++caret_offset)
|
|
test_offset_impl (line_bytes - caret_offset, small_width,
|
|
line_display_cols + test_left_margin - small_width);
|
|
|
|
/* As previous case but caret is closer to the middle; now we want it to end
|
|
up CARET_LINE_MARGIN bytes from the end. */
|
|
ASSERT_GT (line_display_cols - emoji_col, CARET_LINE_MARGIN);
|
|
test_offset_impl (emoji_col, small_width,
|
|
emoji_col + test_left_margin
|
|
- (small_width - CARET_LINE_MARGIN));
|
|
|
|
/* Test that the source line is offset as expected when printed. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.caret_max_width = small_width - 6;
|
|
dc.min_margin_width = test_left_margin - test_linenum_sep + 1;
|
|
dc.show_line_numbers_p = true;
|
|
dc.show_ruler_p = true;
|
|
rich_location richloc (line_table,
|
|
linemap_position_for_column (line_table,
|
|
emoji_col));
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
test_layout.print_line (1);
|
|
ASSERT_STREQ (" | 1 \n"
|
|
" | 1 \n"
|
|
" | 234567890123456789\n"
|
|
" 1 | \xf0\x9f\x98\x82\xf0\x9f\x98\x82 is a pair of emojis "
|
|
"that occupies 8 bytes and 4 display columns, starting at "
|
|
"column #102.\n"
|
|
" | ^\n\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Similar to the previous example, but now the offset called for would split
|
|
the first emoji in the middle of the UTF-8 sequence. Check that we replace
|
|
it with a padding space in this case. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.caret_max_width = small_width - 5;
|
|
dc.min_margin_width = test_left_margin - test_linenum_sep + 1;
|
|
dc.show_line_numbers_p = true;
|
|
dc.show_ruler_p = true;
|
|
rich_location richloc (line_table,
|
|
linemap_position_for_column (line_table,
|
|
emoji_col + 2));
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
test_layout.print_line (1);
|
|
ASSERT_STREQ (" | 1 1 \n"
|
|
" | 1 2 \n"
|
|
" | 3456789012345678901\n"
|
|
" 1 | \xf0\x9f\x98\x82 is a pair of emojis "
|
|
"that occupies 8 bytes and 4 display columns, starting at "
|
|
"column #102.\n"
|
|
" | ^\n\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
test_layout_x_offset_display_tab (const line_table_case &case_)
|
|
{
|
|
const char *content
|
|
= "This line is very long, so that we can use it to test the logic for "
|
|
"clipping long lines. Also this: `\t' is a tab that occupies 1 byte and "
|
|
"a variable number of display columns, starting at column #103.\n";
|
|
|
|
/* Number of bytes in the line, subtracting one to remove the newline. */
|
|
const int line_bytes = strlen (content) - 1;
|
|
|
|
/* The column where the tab begins. Byte or display is the same as there are
|
|
no multibyte characters earlier on the line. */
|
|
const int tab_col = 103;
|
|
|
|
/* Effective extra size of the tab beyond what a single space would have taken
|
|
up, indexed by tabstop. */
|
|
static const int num_tabstops = 11;
|
|
int extra_width[num_tabstops];
|
|
for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
|
|
{
|
|
const int this_tab_size = tabstop - (tab_col - 1) % tabstop;
|
|
extra_width[tabstop] = this_tab_size - 1;
|
|
}
|
|
/* Example of this calculation: if tabstop is 10, the tab starting at column
|
|
#103 has to expand into 8 spaces, covering columns 103-110, so that the
|
|
next character is at column #111. So it takes up 7 more columns than
|
|
a space would have taken up. */
|
|
ASSERT_EQ (7, extra_width[10]);
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
|
|
line_table_test ltt (case_);
|
|
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
|
|
|
|
location_t line_end = linemap_position_for_column (line_table, line_bytes);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
/* Check that cpp_display_width handles the tabs as expected. */
|
|
char_span lspan = location_get_source_line (tmp.get_filename (), 1);
|
|
ASSERT_EQ ('\t', *(lspan.get_buffer () + (tab_col - 1)));
|
|
for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
|
|
{
|
|
ASSERT_EQ (line_bytes + extra_width[tabstop],
|
|
cpp_display_width (lspan.get_buffer (), lspan.length (),
|
|
tabstop));
|
|
ASSERT_EQ (line_bytes + extra_width[tabstop],
|
|
location_compute_display_column (expand_location (line_end),
|
|
tabstop));
|
|
}
|
|
|
|
/* Check that the tab is expanded to the expected number of spaces. */
|
|
rich_location richloc (line_table,
|
|
linemap_position_for_column (line_table,
|
|
tab_col + 1));
|
|
for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.tabstop = tabstop;
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
test_layout.print_line (1);
|
|
const char *out = pp_formatted_text (dc.printer);
|
|
ASSERT_EQ (NULL, strchr (out, '\t'));
|
|
const char *left_quote = strchr (out, '`');
|
|
const char *right_quote = strchr (out, '\'');
|
|
ASSERT_NE (NULL, left_quote);
|
|
ASSERT_NE (NULL, right_quote);
|
|
ASSERT_EQ (right_quote - left_quote, extra_width[tabstop] + 2);
|
|
}
|
|
|
|
/* Check that the line is offset properly and that the tab is broken up
|
|
into the expected number of spaces when it is the last character skipped
|
|
over. */
|
|
for (int tabstop = 1; tabstop != num_tabstops; ++tabstop)
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.tabstop = tabstop;
|
|
static const int small_width = 24;
|
|
dc.caret_max_width = small_width - 4;
|
|
dc.min_margin_width = test_left_margin - test_linenum_sep + 1;
|
|
dc.show_line_numbers_p = true;
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
test_layout.print_line (1);
|
|
|
|
/* We have arranged things so that two columns will be printed before
|
|
the caret. If the tab results in more than one space, this should
|
|
produce two spaces in the output; otherwise, it will be a single space
|
|
preceded by the opening quote before the tab character. */
|
|
const char *output1
|
|
= " 1 | ' is a tab that occupies 1 byte and a variable number of "
|
|
"display columns, starting at column #103.\n"
|
|
" | ^\n\n";
|
|
const char *output2
|
|
= " 1 | ` ' is a tab that occupies 1 byte and a variable number of "
|
|
"display columns, starting at column #103.\n"
|
|
" | ^\n\n";
|
|
const char *expected_output = (extra_width[tabstop] ? output1 : output2);
|
|
ASSERT_STREQ (expected_output, pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
|
|
/* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION. */
|
|
|
|
static void
|
|
test_diagnostic_show_locus_unknown_location ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
rich_location richloc (line_table, UNKNOWN_LOCATION);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("", pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Verify that diagnostic_show_locus works sanely for various
|
|
single-line cases.
|
|
|
|
All of these work on the following 1-line source file:
|
|
.0000000001111111
|
|
.1234567890123456
|
|
"foo = bar.field;\n"
|
|
which is set up by test_diagnostic_show_locus_one_liner and calls
|
|
them. */
|
|
|
|
/* Just a caret. */
|
|
|
|
static void
|
|
test_one_liner_simple_caret ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 10);
|
|
rich_location richloc (line_table, caret);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Caret and range. */
|
|
|
|
static void
|
|
test_one_liner_caret_and_range ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 10);
|
|
location_t start = linemap_position_for_column (line_table, 7);
|
|
location_t finish = linemap_position_for_column (line_table, 15);
|
|
location_t loc = make_location (caret, start, finish);
|
|
rich_location richloc (line_table, loc);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ~~~^~~~~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Multiple ranges and carets. */
|
|
|
|
static void
|
|
test_one_liner_multiple_carets_and_ranges ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t foo
|
|
= make_location (linemap_position_for_column (line_table, 2),
|
|
linemap_position_for_column (line_table, 1),
|
|
linemap_position_for_column (line_table, 3));
|
|
dc.caret_chars[0] = 'A';
|
|
|
|
location_t bar
|
|
= make_location (linemap_position_for_column (line_table, 8),
|
|
linemap_position_for_column (line_table, 7),
|
|
linemap_position_for_column (line_table, 9));
|
|
dc.caret_chars[1] = 'B';
|
|
|
|
location_t field
|
|
= make_location (linemap_position_for_column (line_table, 13),
|
|
linemap_position_for_column (line_table, 11),
|
|
linemap_position_for_column (line_table, 15));
|
|
dc.caret_chars[2] = 'C';
|
|
|
|
rich_location richloc (line_table, foo);
|
|
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 (" foo = bar.field;\n"
|
|
" ~A~ ~B~ ~~C~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding an "&" to the front of "bar.field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_insert_before ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 7);
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_insert_before ("&");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^\n"
|
|
" &\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding a "[0]" after "foo". */
|
|
|
|
static void
|
|
test_one_liner_fixit_insert_after ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t start = linemap_position_for_column (line_table, 1);
|
|
location_t finish = linemap_position_for_column (line_table, 3);
|
|
location_t foo = make_location (start, start, finish);
|
|
rich_location richloc (line_table, foo);
|
|
richloc.add_fixit_insert_after ("[0]");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^~~\n"
|
|
" [0]\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Removal fix-it hint: removal of the ".field".
|
|
Also verify the interaction of pp_set_prefix with rulers and
|
|
fix-it hints. */
|
|
|
|
static void
|
|
test_one_liner_fixit_remove ()
|
|
{
|
|
location_t start = linemap_position_for_column (line_table, 10);
|
|
location_t finish = linemap_position_for_column (line_table, 15);
|
|
location_t dot = make_location (start, start, finish);
|
|
rich_location richloc (line_table, dot);
|
|
richloc.add_fixit_remove ();
|
|
|
|
/* Normal. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^~~~~~\n"
|
|
" ------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Test of adding a prefix. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
|
|
pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n"
|
|
"TEST PREFIX: ^~~~~~\n"
|
|
"TEST PREFIX: ------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Normal, with ruler. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.show_ruler_p = true;
|
|
dc.caret_max_width = 104;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" 0 0 0 0 0 0 0 0 0 1 \n"
|
|
" 1 2 3 4 5 6 7 8 9 0 \n"
|
|
" 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\n"
|
|
" foo = bar.field;\n"
|
|
" ^~~~~~\n"
|
|
" ------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Test of adding a prefix, with ruler. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.show_ruler_p = true;
|
|
dc.caret_max_width = 50;
|
|
pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
|
|
pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("TEST PREFIX: 1 2 3 4 5\n"
|
|
"TEST PREFIX: 12345678901234567890123456789012345678901234567890\n"
|
|
"TEST PREFIX: foo = bar.field;\n"
|
|
"TEST PREFIX: ^~~~~~\n"
|
|
"TEST PREFIX: ------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Test of adding a prefix, with ruler and line numbers. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.show_ruler_p = true;
|
|
dc.caret_max_width = 50;
|
|
dc.show_line_numbers_p = true;
|
|
pp_prefixing_rule (dc.printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE;
|
|
pp_set_prefix (dc.printer, xstrdup ("TEST PREFIX:"));
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("TEST PREFIX: | 1 2 3 4 5\n"
|
|
"TEST PREFIX: | 12345678901234567890123456789012345678901234567890\n"
|
|
"TEST PREFIX: 1 | foo = bar.field;\n"
|
|
"TEST PREFIX: | ^~~~~~\n"
|
|
"TEST PREFIX: | ------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "field" with "m_field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t start = linemap_position_for_column (line_table, 11);
|
|
location_t finish = linemap_position_for_column (line_table, 15);
|
|
location_t field = make_location (start, start, finish);
|
|
rich_location richloc (line_table, field);
|
|
richloc.add_fixit_replace ("m_field");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^~~~~\n"
|
|
" m_field\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "field" with "m_field",
|
|
but where the caret was elsewhere. */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace_non_equal_range ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 5);
|
|
location_t start = linemap_position_for_column (line_table, 11);
|
|
location_t finish = linemap_position_for_column (line_table, 15);
|
|
rich_location richloc (line_table, equals);
|
|
source_range range;
|
|
range.m_start = start;
|
|
range.m_finish = finish;
|
|
richloc.add_fixit_replace (range, "m_field");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
/* The replacement range is not indicated in the annotation line, so
|
|
it should be indicated via an additional underline. */
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^\n"
|
|
" -----\n"
|
|
" m_field\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "field" with "m_field",
|
|
where the caret was elsewhere, but where a secondary range
|
|
exactly covers "field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace_equal_secondary_range ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 5);
|
|
location_t start = linemap_position_for_column (line_table, 11);
|
|
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);
|
|
richloc.add_fixit_replace (field, "m_field");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
/* The replacement range is indicated in the annotation line,
|
|
so it shouldn't be indicated via an additional underline. */
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^ ~~~~~\n"
|
|
" m_field\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Verify that we can use ad-hoc locations when adding fixits to a
|
|
rich_location. */
|
|
|
|
static void
|
|
test_one_liner_fixit_validation_adhoc_locations ()
|
|
{
|
|
/* Generate a range that's too long to be packed, so must
|
|
be stored as an ad-hoc location (given the defaults
|
|
of 5 bits or 0 bits of packed range); 41 columns > 2**5. */
|
|
const location_t c7 = linemap_position_for_column (line_table, 7);
|
|
const location_t c47 = linemap_position_for_column (line_table, 47);
|
|
const location_t loc = make_location (c7, c7, c47);
|
|
|
|
if (c47 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
ASSERT_TRUE (IS_ADHOC_LOC (loc));
|
|
|
|
/* Insert. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
richloc.add_fixit_insert_before (loc, "test");
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^~~~~~~~~~ \n"
|
|
" test\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Remove. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (loc, c47);
|
|
richloc.add_fixit_remove (range);
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^~~~~~~~~~ \n"
|
|
" -----------------------------------------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (loc, c47);
|
|
richloc.add_fixit_replace (range, "test");
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^~~~~~~~~~ \n"
|
|
" test\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Test of consolidating insertions at the same location. */
|
|
|
|
static void
|
|
test_one_liner_many_fixits_1 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 5);
|
|
rich_location richloc (line_table, equals);
|
|
for (int i = 0; i < 19; i++)
|
|
richloc.add_fixit_insert_before ("a");
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^\n"
|
|
" aaaaaaaaaaaaaaaaaaa\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Ensure that we can add an arbitrary number of fix-it hints to a
|
|
rich_location, even if they are not consolidated. */
|
|
|
|
static void
|
|
test_one_liner_many_fixits_2 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 5);
|
|
rich_location richloc (line_table, equals);
|
|
for (int i = 0; i < 19; i++)
|
|
{
|
|
location_t loc = linemap_position_for_column (line_table, (i * 2) + 1);
|
|
richloc.add_fixit_insert_before (loc, "a");
|
|
}
|
|
ASSERT_EQ (19, richloc.get_num_fixit_hints ());
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ^\n"
|
|
" a a a a a a a a a a a a a a a a a a a\n",
|
|
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 (" 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 (" 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 (" 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 (" 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 (" 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 (" foo = bar.field;\n"
|
|
" ^~~\n"
|
|
" |\n"
|
|
" label 0\n"
|
|
" label 1\n"
|
|
" label 2\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Example of out-of-order ranges (thus requiring a sort), where
|
|
they overlap, and there are multiple ranges on the same point. */
|
|
{
|
|
text_range_label label_0a ("label 0a");
|
|
text_range_label label_1a ("label 1a");
|
|
text_range_label label_2a ("label 2a");
|
|
text_range_label label_0b ("label 0b");
|
|
text_range_label label_1b ("label 1b");
|
|
text_range_label label_2b ("label 2b");
|
|
text_range_label label_0c ("label 0c");
|
|
text_range_label label_1c ("label 1c");
|
|
text_range_label label_2c ("label 2c");
|
|
gcc_rich_location richloc (field, &label_0a);
|
|
richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1a);
|
|
richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2a);
|
|
|
|
richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label_0b);
|
|
richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1b);
|
|
richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2b);
|
|
|
|
richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label_0c);
|
|
richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1c);
|
|
richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2c);
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar.field;\n"
|
|
" ~~~ ~~~ ^~~~~\n"
|
|
" | | |\n"
|
|
" | | label 0a\n"
|
|
" | | label 0b\n"
|
|
" | | label 0c\n"
|
|
" | label 1a\n"
|
|
" | label 1b\n"
|
|
" | label 1c\n"
|
|
" label 2a\n"
|
|
" label 2b\n"
|
|
" label 2c\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 (" 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_diagnostic_show_locus_one_liner (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
....................0000000001111111.
|
|
....................1234567890123456. */
|
|
const char *content = "foo = bar.field;\n";
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
|
|
line_table_test ltt (case_);
|
|
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
|
|
|
|
location_t line_end = linemap_position_for_column (line_table, 16);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
|
|
ASSERT_EQ (1, LOCATION_LINE (line_end));
|
|
ASSERT_EQ (16, LOCATION_COLUMN (line_end));
|
|
|
|
test_one_liner_simple_caret ();
|
|
test_one_liner_caret_and_range ();
|
|
test_one_liner_multiple_carets_and_ranges ();
|
|
test_one_liner_fixit_insert_before ();
|
|
test_one_liner_fixit_insert_after ();
|
|
test_one_liner_fixit_remove ();
|
|
test_one_liner_fixit_replace ();
|
|
test_one_liner_fixit_replace_non_equal_range ();
|
|
test_one_liner_fixit_replace_equal_secondary_range ();
|
|
test_one_liner_fixit_validation_adhoc_locations ();
|
|
test_one_liner_many_fixits_1 ();
|
|
test_one_liner_many_fixits_2 ();
|
|
test_one_liner_labels ();
|
|
}
|
|
|
|
/* Version of all one-liner tests exercising multibyte awareness. For
|
|
simplicity we stick to using two multibyte characters in the test, U+1F602
|
|
== "\xf0\x9f\x98\x82", which uses 4 bytes and 2 display columns, and U+03C0
|
|
== "\xcf\x80", which uses 2 bytes and 1 display column. Note: all of the
|
|
below asserts would be easier to read if we used UTF-8 directly in the
|
|
string constants, but it seems better not to demand the host compiler
|
|
support this, when it isn't otherwise necessary. Instead, whenever an
|
|
extended character appears in a string, we put a line break after it so that
|
|
all succeeding characters can appear visually at the correct display column.
|
|
|
|
All of these work on the following 1-line source file:
|
|
|
|
.0000000001111111111222222 display
|
|
.1234567890123456789012345 columns
|
|
"SS_foo = P_bar.SS_fieldP;\n"
|
|
.0000000111111111222222223 byte
|
|
.1356789012456789134567891 columns
|
|
|
|
which is set up by test_diagnostic_show_locus_one_liner and calls
|
|
them. Here SS represents the two display columns for the U+1F602 emoji and
|
|
P represents the one display column for the U+03C0 pi symbol. */
|
|
|
|
/* Just a caret. */
|
|
|
|
static void
|
|
test_one_liner_simple_caret_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 18);
|
|
rich_location richloc (line_table, caret);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Caret and range. */
|
|
static void
|
|
test_one_liner_caret_and_range_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 18);
|
|
location_t start = linemap_position_for_column (line_table, 12);
|
|
location_t finish = linemap_position_for_column (line_table, 30);
|
|
location_t loc = make_location (caret, start, finish);
|
|
rich_location richloc (line_table, loc);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ~~~~~^~~~~~~~~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Multiple ranges and carets. */
|
|
|
|
static void
|
|
test_one_liner_multiple_carets_and_ranges_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t foo
|
|
= make_location (linemap_position_for_column (line_table, 7),
|
|
linemap_position_for_column (line_table, 1),
|
|
linemap_position_for_column (line_table, 8));
|
|
dc.caret_chars[0] = 'A';
|
|
|
|
location_t bar
|
|
= make_location (linemap_position_for_column (line_table, 16),
|
|
linemap_position_for_column (line_table, 12),
|
|
linemap_position_for_column (line_table, 17));
|
|
dc.caret_chars[1] = 'B';
|
|
|
|
location_t field
|
|
= make_location (linemap_position_for_column (line_table, 26),
|
|
linemap_position_for_column (line_table, 19),
|
|
linemap_position_for_column (line_table, 30));
|
|
dc.caret_chars[2] = 'C';
|
|
rich_location richloc (line_table, foo);
|
|
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 (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ~~~~A~ ~~~B~ ~~~~~C~~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding an "&" to the front of "P_bar.field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_insert_before_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t caret = linemap_position_for_column (line_table, 12);
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_insert_before ("&");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^\n"
|
|
" &\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding a "[0]" after "SS_foo". */
|
|
|
|
static void
|
|
test_one_liner_fixit_insert_after_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t start = linemap_position_for_column (line_table, 1);
|
|
location_t finish = linemap_position_for_column (line_table, 8);
|
|
location_t foo = make_location (start, start, finish);
|
|
rich_location richloc (line_table, foo);
|
|
richloc.add_fixit_insert_after ("[0]");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~\n"
|
|
" [0]\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Removal fix-it hint: removal of the ".SS_fieldP". */
|
|
|
|
static void
|
|
test_one_liner_fixit_remove_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t start = linemap_position_for_column (line_table, 18);
|
|
location_t finish = linemap_position_for_column (line_table, 30);
|
|
location_t dot = make_location (start, start, finish);
|
|
rich_location richloc (line_table, dot);
|
|
richloc.add_fixit_remove ();
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~\n"
|
|
" ----------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP". */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t start = linemap_position_for_column (line_table, 19);
|
|
location_t finish = linemap_position_for_column (line_table, 30);
|
|
location_t field = make_location (start, start, finish);
|
|
rich_location richloc (line_table, field);
|
|
richloc.add_fixit_replace ("m_\xf0\x9f\x98\x82_field\xcf\x80");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~\n"
|
|
" m_\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP",
|
|
but where the caret was elsewhere. */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace_non_equal_range_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 10);
|
|
location_t start = linemap_position_for_column (line_table, 19);
|
|
location_t finish = linemap_position_for_column (line_table, 30);
|
|
rich_location richloc (line_table, equals);
|
|
source_range range;
|
|
range.m_start = start;
|
|
range.m_finish = finish;
|
|
richloc.add_fixit_replace (range, "m_\xf0\x9f\x98\x82_field\xcf\x80");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
/* The replacement range is not indicated in the annotation line, so
|
|
it should be indicated via an additional underline. */
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^\n"
|
|
" ---------\n"
|
|
" m_\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace fix-it hint: replacing "SS_fieldP" with "m_SSfieldP",
|
|
where the caret was elsewhere, but where a secondary range
|
|
exactly covers "field". */
|
|
|
|
static void
|
|
test_one_liner_fixit_replace_equal_secondary_range_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 10);
|
|
location_t start = linemap_position_for_column (line_table, 19);
|
|
location_t finish = linemap_position_for_column (line_table, 30);
|
|
rich_location richloc (line_table, equals);
|
|
location_t field = make_location (start, start, finish);
|
|
richloc.add_range (field);
|
|
richloc.add_fixit_replace (field, "m_\xf0\x9f\x98\x82_field\xcf\x80");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
/* The replacement range is indicated in the annotation line,
|
|
so it shouldn't be indicated via an additional underline. */
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^ ~~~~~~~~~\n"
|
|
" m_\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Verify that we can use ad-hoc locations when adding fixits to a
|
|
rich_location. */
|
|
|
|
static void
|
|
test_one_liner_fixit_validation_adhoc_locations_utf8 ()
|
|
{
|
|
/* Generate a range that's too long to be packed, so must
|
|
be stored as an ad-hoc location (given the defaults
|
|
of 5 bits or 0 bits of packed range); 41 columns > 2**5. */
|
|
const location_t c12 = linemap_position_for_column (line_table, 12);
|
|
const location_t c52 = linemap_position_for_column (line_table, 52);
|
|
const location_t loc = make_location (c12, c12, c52);
|
|
|
|
if (c52 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
ASSERT_TRUE (IS_ADHOC_LOC (loc));
|
|
|
|
/* Insert. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
richloc.add_fixit_insert_before (loc, "test");
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~~~~~~ \n"
|
|
" test\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Remove. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (loc, c52);
|
|
richloc.add_fixit_remove (range);
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~~~~~~ \n"
|
|
" -------------------------------------\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Replace. */
|
|
{
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (loc, c52);
|
|
richloc.add_fixit_replace (range, "test");
|
|
/* It should not have been discarded by the validator. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~~~~~~ \n"
|
|
" test\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Test of consolidating insertions at the same location. */
|
|
|
|
static void
|
|
test_one_liner_many_fixits_1_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 10);
|
|
rich_location richloc (line_table, equals);
|
|
for (int i = 0; i < 19; i++)
|
|
richloc.add_fixit_insert_before (i & 1 ? "@" : "\xcf\x80");
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^\n"
|
|
" \xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@"
|
|
"\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80@\xcf\x80\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Ensure that we can add an arbitrary number of fix-it hints to a
|
|
rich_location, even if they are not consolidated. */
|
|
|
|
static void
|
|
test_one_liner_many_fixits_2_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
location_t equals = linemap_position_for_column (line_table, 10);
|
|
rich_location richloc (line_table, equals);
|
|
const int nlocs = 19;
|
|
int locs[nlocs] = {1, 5, 7, 9, 11, 14, 16, 18, 23, 25, 27, 29, 32,
|
|
34, 36, 38, 40, 42, 44};
|
|
for (int i = 0; i != nlocs; ++i)
|
|
{
|
|
location_t loc = linemap_position_for_column (line_table, locs[i]);
|
|
richloc.add_fixit_insert_before (loc, i & 1 ? "@" : "\xcf\x80");
|
|
}
|
|
|
|
ASSERT_EQ (nlocs, richloc.get_num_fixit_hints ());
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^\n"
|
|
" \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @"
|
|
" \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80 @ \xcf\x80\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Test of labeling the ranges within a rich_location. */
|
|
|
|
static void
|
|
test_one_liner_labels_utf8 ()
|
|
{
|
|
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, 8));
|
|
location_t bar
|
|
= make_location (linemap_position_for_column (line_table, 12),
|
|
linemap_position_for_column (line_table, 12),
|
|
linemap_position_for_column (line_table, 17));
|
|
location_t field
|
|
= make_location (linemap_position_for_column (line_table, 19),
|
|
linemap_position_for_column (line_table, 19),
|
|
linemap_position_for_column (line_table, 30));
|
|
|
|
/* Example where all the labels fit on one line. */
|
|
{
|
|
/* These three labels contain multibyte characters such that their byte
|
|
lengths are respectively (12, 10, 18), but their display widths are only
|
|
(6, 5, 9). All three fit on the line when considering the display
|
|
widths, but not when considering the byte widths, so verify that we do
|
|
indeed put them all on one line. */
|
|
text_range_label label0
|
|
("\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80");
|
|
text_range_label label1
|
|
("\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80");
|
|
text_range_label label2
|
|
("\xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
|
|
"\xcf\x80");
|
|
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 (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~ ~~~~~ ~~~~~~~~~\n"
|
|
" | | |\n"
|
|
" \xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80\xcf\x80"
|
|
" \xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
|
|
" \xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82"
|
|
"\xf0\x9f\x98\x82\xcf\x80\xcf\x80\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
}
|
|
|
|
/* Example where the labels need extra lines. */
|
|
{
|
|
text_range_label label0 ("label 0\xf0\x9f\x98\x82");
|
|
text_range_label label1 ("label 1\xcf\x80");
|
|
text_range_label label2 ("label 2\xcf\x80");
|
|
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 (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~ ~~~~~ ~~~~~~~~~\n"
|
|
" | | |\n"
|
|
" | | label 2\xcf\x80\n"
|
|
" | label 1\xcf\x80\n"
|
|
" label 0\xf0\x9f\x98\x82\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\xf0\x9f\x98\x82\xcf\x80");
|
|
text_range_label label1 ("bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82");
|
|
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 (" \xf0\x9f\x98\x82"
|
|
"_foo = \xcf\x80"
|
|
"_bar.\xf0\x9f\x98\x82"
|
|
"_field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~ ~~~~~ ~~~~~~~~~\n"
|
|
" | | |\n"
|
|
" | | c\n"
|
|
" aaaaa\xf0\x9f\x98\x82\xcf\x80"
|
|
" bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Make sure that colorization codes don't interrupt a multibyte
|
|
sequence, which would corrupt it. */
|
|
static void
|
|
test_one_liner_colorized_utf8 ()
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.colorize_source_p = true;
|
|
diagnostic_color_init (&dc, DIAGNOSTICS_COLOR_YES);
|
|
const location_t pi = linemap_position_for_column (line_table, 12);
|
|
rich_location richloc (line_table, pi);
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
|
|
/* In order to avoid having the test depend on exactly how the colorization
|
|
was effected, just confirm there are two pi characters in the output. */
|
|
const char *result = pp_formatted_text (dc.printer);
|
|
const char *null_term = result + strlen (result);
|
|
const char *first_pi = strstr (result, "\xcf\x80");
|
|
ASSERT_TRUE (first_pi && first_pi <= null_term - 2);
|
|
ASSERT_STR_CONTAINS (first_pi + 2, "\xcf\x80");
|
|
}
|
|
|
|
/* Run the various one-liner tests. */
|
|
|
|
static void
|
|
test_diagnostic_show_locus_one_liner_utf8 (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it. */
|
|
const char *content
|
|
/* Display columns.
|
|
0000000000000000000000011111111111111111111111111111112222222222222
|
|
1111111122222222345678900000000123456666666677777777890123444444445 */
|
|
= "\xf0\x9f\x98\x82_foo = \xcf\x80_bar.\xf0\x9f\x98\x82_field\xcf\x80;\n";
|
|
/* 0000000000000000000001111111111111111111222222222222222222222233333
|
|
1111222233334444567890122223333456789999000011112222345678999900001
|
|
Byte columns. */
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
|
|
line_table_test ltt (case_);
|
|
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
|
|
|
|
location_t line_end = linemap_position_for_column (line_table, 31);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end));
|
|
ASSERT_EQ (1, LOCATION_LINE (line_end));
|
|
ASSERT_EQ (31, LOCATION_COLUMN (line_end));
|
|
|
|
char_span lspan = location_get_source_line (tmp.get_filename (), 1);
|
|
ASSERT_EQ (25, cpp_display_width (lspan.get_buffer (), lspan.length (),
|
|
def_tabstop));
|
|
ASSERT_EQ (25, location_compute_display_column (expand_location (line_end),
|
|
def_tabstop));
|
|
|
|
test_one_liner_simple_caret_utf8 ();
|
|
test_one_liner_caret_and_range_utf8 ();
|
|
test_one_liner_multiple_carets_and_ranges_utf8 ();
|
|
test_one_liner_fixit_insert_before_utf8 ();
|
|
test_one_liner_fixit_insert_after_utf8 ();
|
|
test_one_liner_fixit_remove_utf8 ();
|
|
test_one_liner_fixit_replace_utf8 ();
|
|
test_one_liner_fixit_replace_non_equal_range_utf8 ();
|
|
test_one_liner_fixit_replace_equal_secondary_range_utf8 ();
|
|
test_one_liner_fixit_validation_adhoc_locations_utf8 ();
|
|
test_one_liner_many_fixits_1_utf8 ();
|
|
test_one_liner_many_fixits_2_utf8 ();
|
|
test_one_liner_labels_utf8 ();
|
|
test_one_liner_colorized_utf8 ();
|
|
}
|
|
|
|
/* Verify that gcc_rich_location::add_location_if_nearby works. */
|
|
|
|
static void
|
|
test_add_location_if_nearby (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
...000000000111111111122222222223333333333.
|
|
...123456789012345678901234567890123456789. */
|
|
const char *content
|
|
= ("struct same_line { double x; double y; ;\n" /* line 1. */
|
|
"struct different_line\n" /* line 2. */
|
|
"{\n" /* line 3. */
|
|
" double x;\n" /* line 4. */
|
|
" double y;\n" /* line 5. */
|
|
";\n"); /* line 6. */
|
|
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, 7);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
/* Test of add_location_if_nearby on the same line as the
|
|
primary location. */
|
|
{
|
|
const location_t missing_close_brace_1_39
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 39);
|
|
const location_t matching_open_brace_1_18
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 18);
|
|
gcc_rich_location richloc (missing_close_brace_1_39);
|
|
bool added = richloc.add_location_if_nearby (matching_open_brace_1_18);
|
|
ASSERT_TRUE (added);
|
|
ASSERT_EQ (2, richloc.get_num_locations ());
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" struct same_line { double x; double y; ;\n"
|
|
" ~ ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Test of add_location_if_nearby on a different line to the
|
|
primary location. */
|
|
{
|
|
const location_t missing_close_brace_6_1
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 6, 1);
|
|
const location_t matching_open_brace_3_1
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 3, 1);
|
|
gcc_rich_location richloc (missing_close_brace_6_1);
|
|
bool added = richloc.add_location_if_nearby (matching_open_brace_3_1);
|
|
ASSERT_FALSE (added);
|
|
ASSERT_EQ (1, richloc.get_num_locations ());
|
|
}
|
|
}
|
|
|
|
/* Verify that we print fixits even if they only affect lines
|
|
outside those covered by the ranges in the rich_location. */
|
|
|
|
static void
|
|
test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
...000000000111111111122222222223333333333.
|
|
...123456789012345678901234567890123456789. */
|
|
const char *content
|
|
= ("struct point { double x; double y; };\n" /* line 1. */
|
|
"struct point origin = {x: 0.0,\n" /* line 2. */
|
|
" y\n" /* line 3. */
|
|
"\n" /* line 4. */
|
|
"\n" /* line 5. */
|
|
" : 0.0};\n"); /* line 6. */
|
|
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 pair of tests for modernizing the initializers to C99-style. */
|
|
|
|
/* The one-liner case (line 2). */
|
|
{
|
|
test_diagnostic_context dc;
|
|
const location_t x
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 2, 24);
|
|
const location_t colon
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 2, 25);
|
|
rich_location richloc (line_table, colon);
|
|
richloc.add_fixit_insert_before (x, ".");
|
|
richloc.add_fixit_replace (colon, "=");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" struct point origin = {x: 0.0,\n"
|
|
" ^\n"
|
|
" .=\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* The multiline case. The caret for the rich_location is on line 6;
|
|
verify that insertion fixit on line 3 is still printed (and that
|
|
span starts are printed due to the gap between the span at line 3
|
|
and that at line 6). */
|
|
{
|
|
test_diagnostic_context dc;
|
|
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, "=");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("FILENAME:3:24:\n"
|
|
" y\n"
|
|
" .\n"
|
|
"FILENAME:6:25:\n"
|
|
" : 0.0};\n"
|
|
" ^\n"
|
|
" =\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 (" 3 | y\n"
|
|
" | .\n"
|
|
"......\n"
|
|
" 6 | : 0.0};\n"
|
|
" | ^\n"
|
|
" | =\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
|
|
/* Verify that fix-it hints are appropriately consolidated.
|
|
|
|
If any fix-it hints in a rich_location involve locations beyond
|
|
LINE_MAP_MAX_LOCATION_WITH_COLS, then we can't reliably apply
|
|
the fix-it as a whole, so there should be none.
|
|
|
|
Otherwise, verify that consecutive "replace" and "remove" fix-its
|
|
are merged, and that other fix-its remain separate. */
|
|
|
|
static void
|
|
test_fixit_consolidation (const line_table_case &case_)
|
|
{
|
|
line_table_test ltt (case_);
|
|
|
|
linemap_add (line_table, LC_ENTER, false, "test.c", 1);
|
|
|
|
const location_t c10 = linemap_position_for_column (line_table, 10);
|
|
const location_t c15 = linemap_position_for_column (line_table, 15);
|
|
const location_t c16 = linemap_position_for_column (line_table, 16);
|
|
const location_t c17 = linemap_position_for_column (line_table, 17);
|
|
const location_t c20 = linemap_position_for_column (line_table, 20);
|
|
const location_t c21 = linemap_position_for_column (line_table, 21);
|
|
const location_t caret = c10;
|
|
|
|
/* Insert + insert. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_insert_before (c10, "foo");
|
|
richloc.add_fixit_insert_before (c15, "bar");
|
|
|
|
if (c15 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
/* They should not have been merged. */
|
|
ASSERT_EQ (2, richloc.get_num_fixit_hints ());
|
|
}
|
|
|
|
/* Insert + replace. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_insert_before (c10, "foo");
|
|
richloc.add_fixit_replace (source_range::from_locations (c15, c17),
|
|
"bar");
|
|
|
|
if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
/* They should not have been merged. */
|
|
ASSERT_EQ (2, richloc.get_num_fixit_hints ());
|
|
}
|
|
|
|
/* Replace + non-consecutive insert. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_replace (source_range::from_locations (c10, c15),
|
|
"bar");
|
|
richloc.add_fixit_insert_before (c17, "foo");
|
|
|
|
if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
/* They should not have been merged. */
|
|
ASSERT_EQ (2, richloc.get_num_fixit_hints ());
|
|
}
|
|
|
|
/* Replace + non-consecutive replace. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_replace (source_range::from_locations (c10, c15),
|
|
"foo");
|
|
richloc.add_fixit_replace (source_range::from_locations (c17, c20),
|
|
"bar");
|
|
|
|
if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
/* They should not have been merged. */
|
|
ASSERT_EQ (2, richloc.get_num_fixit_hints ());
|
|
}
|
|
|
|
/* Replace + consecutive replace. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_replace (source_range::from_locations (c10, c15),
|
|
"foo");
|
|
richloc.add_fixit_replace (source_range::from_locations (c16, c20),
|
|
"bar");
|
|
|
|
if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
{
|
|
/* They should have been merged into a single "replace". */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
const fixit_hint *hint = richloc.get_fixit_hint (0);
|
|
ASSERT_STREQ ("foobar", hint->get_string ());
|
|
ASSERT_EQ (c10, hint->get_start_loc ());
|
|
ASSERT_EQ (c21, hint->get_next_loc ());
|
|
}
|
|
}
|
|
|
|
/* Replace + consecutive removal. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_replace (source_range::from_locations (c10, c15),
|
|
"foo");
|
|
richloc.add_fixit_remove (source_range::from_locations (c16, c20));
|
|
|
|
if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
{
|
|
/* They should have been merged into a single replace, with the
|
|
range extended to cover that of the removal. */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
const fixit_hint *hint = richloc.get_fixit_hint (0);
|
|
ASSERT_STREQ ("foo", hint->get_string ());
|
|
ASSERT_EQ (c10, hint->get_start_loc ());
|
|
ASSERT_EQ (c21, hint->get_next_loc ());
|
|
}
|
|
}
|
|
|
|
/* Consecutive removals. */
|
|
{
|
|
rich_location richloc (line_table, caret);
|
|
richloc.add_fixit_remove (source_range::from_locations (c10, c15));
|
|
richloc.add_fixit_remove (source_range::from_locations (c16, c20));
|
|
|
|
if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
/* Bogus column info for 2nd fixit, so no fixits. */
|
|
ASSERT_EQ (0, richloc.get_num_fixit_hints ());
|
|
else
|
|
{
|
|
/* They should have been merged into a single "replace-with-empty". */
|
|
ASSERT_EQ (1, richloc.get_num_fixit_hints ());
|
|
const fixit_hint *hint = richloc.get_fixit_hint (0);
|
|
ASSERT_STREQ ("", hint->get_string ());
|
|
ASSERT_EQ (c10, hint->get_start_loc ());
|
|
ASSERT_EQ (c21, hint->get_next_loc ());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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 (" 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_range (&dc, hint_0, CU_BYTES));
|
|
ASSERT_EQ (column_range (12, 12),
|
|
get_affected_range (&dc, hint_0, CU_DISPLAY_COLS));
|
|
ASSERT_EQ (column_range (12, 22), get_printed_columns (&dc, hint_0));
|
|
const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
|
|
ASSERT_EQ (column_range (18, 18),
|
|
get_affected_range (&dc, hint_1, CU_BYTES));
|
|
ASSERT_EQ (column_range (18, 18),
|
|
get_affected_range (&dc, hint_1, CU_DISPLAY_COLS));
|
|
ASSERT_EQ (column_range (18, 20), get_printed_columns (&dc, hint_1));
|
|
const fixit_hint *hint_2 = richloc.get_fixit_hint (2);
|
|
ASSERT_EQ (column_range (29, 28),
|
|
get_affected_range (&dc, hint_2, CU_BYTES));
|
|
ASSERT_EQ (column_range (29, 28),
|
|
get_affected_range (&dc, hint_2, CU_DISPLAY_COLS));
|
|
ASSERT_EQ (column_range (29, 29), get_printed_columns (&dc, 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 (&dc, 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_bytes);
|
|
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_bytes);
|
|
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_bytes);
|
|
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 (" 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 (" 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 (" 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 (" 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 (" foo *f = (foo *)ptr->field;\n"
|
|
" ^~~~~~~~~~\n"
|
|
" -------\n"
|
|
" LONGER THAN THE CAST(foo *)TEST\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Multibyte-aware version of preceding tests. See comments above
|
|
test_one_liner_simple_caret_utf8() too, we use the same two multibyte
|
|
characters here. */
|
|
|
|
static void
|
|
test_overlapped_fixit_printing_utf8 (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it. */
|
|
|
|
const char *content
|
|
/* Display columns.
|
|
00000000000000000000000111111111111111111111111222222222222222223
|
|
12344444444555555556789012344444444555555556789012345678999999990 */
|
|
= " f\xf0\x9f\x98\x82 *f = (f\xf0\x9f\x98\x82 *)ptr->field\xcf\x80;\n";
|
|
/* 00000000000000000000011111111111111111111112222222222333333333333
|
|
12344445555666677778901234566667777888899990123456789012333344445
|
|
Byte columns. */
|
|
|
|
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, 50);
|
|
|
|
/* 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, 14);
|
|
const location_t close_paren
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 22);
|
|
const location_t expr_start
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 23);
|
|
const location_t expr_finish
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 34);
|
|
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 (" f\xf0\x9f\x98\x82"
|
|
" *f = (f\xf0\x9f\x98\x82"
|
|
" *)ptr->field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~\n"
|
|
" ------------------\n"
|
|
" const_cast<f\xf0\x9f\x98\x82"
|
|
" *> (ptr->field\xcf\x80"
|
|
")\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 (14, 14),
|
|
get_affected_range (&dc, hint_0, CU_BYTES));
|
|
ASSERT_EQ (column_range (12, 12),
|
|
get_affected_range (&dc, hint_0, CU_DISPLAY_COLS));
|
|
ASSERT_EQ (column_range (12, 22), get_printed_columns (&dc, hint_0));
|
|
const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
|
|
ASSERT_EQ (column_range (22, 22),
|
|
get_affected_range (&dc, hint_1, CU_BYTES));
|
|
ASSERT_EQ (column_range (18, 18),
|
|
get_affected_range (&dc, hint_1, CU_DISPLAY_COLS));
|
|
ASSERT_EQ (column_range (18, 20), get_printed_columns (&dc, hint_1));
|
|
const fixit_hint *hint_2 = richloc.get_fixit_hint (2);
|
|
ASSERT_EQ (column_range (35, 34),
|
|
get_affected_range (&dc, hint_2, CU_BYTES));
|
|
ASSERT_EQ (column_range (30, 29),
|
|
get_affected_range (&dc, hint_2, CU_DISPLAY_COLS));
|
|
ASSERT_EQ (column_range (30, 30), get_printed_columns (&dc, 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 (&dc, 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 (14, 14), lc.m_corrections[0]->m_affected_bytes);
|
|
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<f\xf0\x9f\x98\x82 *> (",
|
|
lc.m_corrections[0]->m_text);
|
|
ASSERT_EQ (column_range (14, 22), lc.m_corrections[0]->m_affected_bytes);
|
|
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<f\xf0\x9f\x98\x82 *> (ptr->field\xcf\x80)",
|
|
lc.m_corrections[0]->m_text);
|
|
ASSERT_EQ (1, lc.m_corrections.length ());
|
|
ASSERT_EQ (column_range (14, 34), lc.m_corrections[0]->m_affected_bytes);
|
|
ASSERT_EQ (column_range (12, 29), lc.m_corrections[0]->m_affected_columns);
|
|
ASSERT_EQ (column_range (12, 42), 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 (" f\xf0\x9f\x98\x82"
|
|
" *f = (f\xf0\x9f\x98\x82"
|
|
" *)ptr->field\xcf\x80"
|
|
";\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 (" f\xf0\x9f\x98\x82"
|
|
" *f = (f\xf0\x9f\x98\x82"
|
|
" *)ptr->field\xcf\x80"
|
|
";\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\xf0\x9f\x98\x82 *)");
|
|
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 (" f\xf0\x9f\x98\x82"
|
|
" *f = (f\xf0\x9f\x98\x82"
|
|
" *)ptr->field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~\n"
|
|
" -------\n"
|
|
" (bar\xf0\x9f\x98\x82"
|
|
" *)\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, "(long\xf0\x9f\x98\x82 *)");
|
|
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 (" f\xf0\x9f\x98\x82"
|
|
" *f = (f\xf0\x9f\x98\x82"
|
|
" *)ptr->field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~\n"
|
|
" ------------------\n"
|
|
" (long\xf0\x9f\x98\x82"
|
|
" *)(f\xf0\x9f\x98\x82"
|
|
" *)\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, "L\xf0\x9f\x98\x82NGER 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 (" f\xf0\x9f\x98\x82"
|
|
" *f = (f\xf0\x9f\x98\x82"
|
|
" *)ptr->field\xcf\x80"
|
|
";\n"
|
|
" ^~~~~~~~~~~\n"
|
|
" -------\n"
|
|
" L\xf0\x9f\x98\x82"
|
|
"NGER THAN THE CAST(f\xf0\x9f\x98\x82"
|
|
" *)TEST\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Verify that the line_corrections machinery correctly prints
|
|
overlapping fixit-hints that have been added in the wrong
|
|
order.
|
|
Adapted from PR c/81405 seen on gcc.dg/init-excess-1.c*/
|
|
|
|
static void
|
|
test_overlapped_fixit_printing_2 (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
...000000000111111111122222222223333333333.
|
|
...123456789012345678901234567890123456789. */
|
|
const char *content
|
|
= ("int a5[][0][0] = { 1, 2 };\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, 1, 100);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
if (final_line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
const location_t col_1
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 1);
|
|
const location_t col_20
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 20);
|
|
const location_t col_21
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 21);
|
|
const location_t col_23
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 23);
|
|
const location_t col_25
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 25);
|
|
|
|
/* Two insertions, in the wrong order. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
|
|
rich_location richloc (line_table, col_20);
|
|
richloc.add_fixit_insert_before (col_23, "{");
|
|
richloc.add_fixit_insert_before (col_21, "}");
|
|
|
|
/* These fixits should be accepted; they can't be consolidated. */
|
|
ASSERT_EQ (2, richloc.get_num_fixit_hints ());
|
|
const fixit_hint *hint_0 = richloc.get_fixit_hint (0);
|
|
ASSERT_EQ (column_range (23, 22),
|
|
get_affected_range (&dc, hint_0, CU_BYTES));
|
|
ASSERT_EQ (column_range (23, 23), get_printed_columns (&dc, hint_0));
|
|
const fixit_hint *hint_1 = richloc.get_fixit_hint (1);
|
|
ASSERT_EQ (column_range (21, 20),
|
|
get_affected_range (&dc, hint_1, CU_BYTES));
|
|
ASSERT_EQ (column_range (21, 21), get_printed_columns (&dc, hint_1));
|
|
|
|
/* Verify that they're printed correctly. */
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
|
|
" ^\n"
|
|
" } {\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Various overlapping insertions, some occurring "out of order"
|
|
(reproducing the fix-it hints from PR c/81405). */
|
|
{
|
|
test_diagnostic_context dc;
|
|
rich_location richloc (line_table, col_20);
|
|
|
|
richloc.add_fixit_insert_before (col_20, "{{");
|
|
richloc.add_fixit_insert_before (col_21, "}}");
|
|
richloc.add_fixit_insert_before (col_23, "{");
|
|
richloc.add_fixit_insert_before (col_21, "}");
|
|
richloc.add_fixit_insert_before (col_23, "{{");
|
|
richloc.add_fixit_insert_before (col_25, "}");
|
|
richloc.add_fixit_insert_before (col_21, "}");
|
|
richloc.add_fixit_insert_before (col_1, "{");
|
|
richloc.add_fixit_insert_before (col_25, "}");
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n"
|
|
" ^\n"
|
|
" { -----\n"
|
|
" {{1}}}}, {{{2 }}\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding a "break;" on a line by itself. */
|
|
|
|
static void
|
|
test_fixit_insert_containing_newline (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
.........................0000000001111111.
|
|
.........................1234567890123456. */
|
|
const char *old_content = (" case 'a':\n" /* line 1. */
|
|
" x = a;\n" /* line 2. */
|
|
" case 'b':\n" /* line 3. */
|
|
" x = b;\n");/* line 4. */
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
|
|
line_table_test ltt (case_);
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 3);
|
|
|
|
location_t case_start = linemap_position_for_column (line_table, 5);
|
|
location_t case_finish = linemap_position_for_column (line_table, 13);
|
|
location_t case_loc = make_location (case_start, case_start, case_finish);
|
|
location_t line_start = linemap_position_for_column (line_table, 1);
|
|
|
|
if (case_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
/* Add a "break;" on a line by itself before line 3 i.e. before
|
|
column 1 of line 3. */
|
|
{
|
|
rich_location richloc (line_table, case_loc);
|
|
richloc.add_fixit_insert_before (line_start, " break;\n");
|
|
|
|
/* Without line numbers. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" 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 (" 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
|
|
insertion point is *not* at the start of a line. */
|
|
{
|
|
rich_location richloc (line_table, case_loc);
|
|
richloc.add_fixit_insert_before (case_start, "break;\n");
|
|
ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" case 'b':\n"
|
|
" ^~~~~~~~~\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
}
|
|
|
|
/* Insertion fix-it hint: adding a "#include <stdio.h>\n" to the top
|
|
of the file, where the fix-it is printed in a different line-span
|
|
to the primary range of the diagnostic. */
|
|
|
|
static void
|
|
test_fixit_insert_containing_newline_2 (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
.........................0000000001111111.
|
|
.........................1234567890123456. */
|
|
const char *old_content = ("test (int ch)\n" /* line 1. */
|
|
"{\n" /* line 2. */
|
|
" putchar (ch);\n" /* line 3. */
|
|
"}\n"); /* line 4. */
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", old_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);
|
|
|
|
/* The primary range is the "putchar" token. */
|
|
location_t putchar_start
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 3, 2);
|
|
location_t putchar_finish
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 3, 8);
|
|
location_t putchar_loc
|
|
= make_location (putchar_start, putchar_start, putchar_finish);
|
|
rich_location richloc (line_table, putchar_loc);
|
|
|
|
/* Add a "#include <stdio.h>" on a line by itself at the top of the file. */
|
|
location_t file_start
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 1);
|
|
richloc.add_fixit_insert_before (file_start, "#include <stdio.h>\n");
|
|
|
|
if (putchar_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
{
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ ("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 (" +++ |+#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.
|
|
This will fail, as newlines are only supported when inserting at the
|
|
beginning of a line. */
|
|
|
|
static void
|
|
test_fixit_replace_containing_newline (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
.........................0000000001111.
|
|
.........................1234567890123. */
|
|
const char *old_content = "foo = bar ();\n";
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", old_content);
|
|
line_table_test ltt (case_);
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
|
|
|
|
/* Replace the " = " with "\n = ", as if we were reformatting an
|
|
overly long line. */
|
|
location_t start = linemap_position_for_column (line_table, 4);
|
|
location_t finish = linemap_position_for_column (line_table, 6);
|
|
location_t loc = linemap_position_for_column (line_table, 13);
|
|
rich_location richloc (line_table, loc);
|
|
source_range range = source_range::from_locations (start, finish);
|
|
richloc.add_fixit_replace (range, "\n =");
|
|
|
|
/* Arbitrary newlines are not yet supported within fix-it hints, so
|
|
the fix-it should not be displayed. */
|
|
ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
|
|
|
|
if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar ();\n"
|
|
" ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Fix-it hint, attempting to delete a newline.
|
|
This will fail, as we currently only support fix-it hints that
|
|
affect one line at a time. */
|
|
|
|
static void
|
|
test_fixit_deletion_affecting_newline (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it.
|
|
..........................0000000001111.
|
|
..........................1234567890123. */
|
|
const char *old_content = ("foo = bar (\n"
|
|
" );\n");
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", old_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);
|
|
|
|
/* Attempt to delete the " (\n...)". */
|
|
location_t start
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 10);
|
|
location_t caret
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 1, 11);
|
|
location_t finish
|
|
= linemap_position_for_line_and_column (line_table, ord_map, 2, 7);
|
|
location_t loc = make_location (caret, start, finish);
|
|
rich_location richloc (line_table, loc);
|
|
richloc. add_fixit_remove ();
|
|
|
|
/* Fix-it hints that affect more than one line are not yet supported, so
|
|
the fix-it should not be displayed. */
|
|
ASSERT_TRUE (richloc.seen_impossible_fixit_p ());
|
|
|
|
if (finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
test_diagnostic_context dc;
|
|
diagnostic_show_locus (&dc, &richloc, DK_ERROR);
|
|
ASSERT_STREQ (" foo = bar (\n"
|
|
" ~^\n"
|
|
" );\n"
|
|
" ~ \n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
static void
|
|
test_tab_expansion (const line_table_case &case_)
|
|
{
|
|
/* Create a tempfile and write some text to it. This example uses a tabstop
|
|
of 8, as the column numbers attempt to indicate:
|
|
|
|
.....................000.01111111111.22222333333 display
|
|
.....................123.90123456789.56789012345 columns */
|
|
const char *content = " \t This: `\t' is a tab.\n";
|
|
/* ....................000 00000011111 11111222222 byte
|
|
....................123 45678901234 56789012345 columns */
|
|
|
|
const int tabstop = 8;
|
|
const int first_non_ws_byte_col = 7;
|
|
const int right_quote_byte_col = 15;
|
|
const int last_byte_col = 25;
|
|
ASSERT_EQ (35, cpp_display_width (content, last_byte_col, tabstop));
|
|
|
|
temp_source_file tmp (SELFTEST_LOCATION, ".c", content);
|
|
line_table_test ltt (case_);
|
|
linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1);
|
|
|
|
/* Don't attempt to run the tests if column data might be unavailable. */
|
|
location_t line_end = linemap_position_for_column (line_table, last_byte_col);
|
|
if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS)
|
|
return;
|
|
|
|
/* Check that the leading whitespace with mixed tabs and spaces is expanded
|
|
into 11 spaces. Recall that print_line() also puts one space before
|
|
everything too. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.tabstop = tabstop;
|
|
rich_location richloc (line_table,
|
|
linemap_position_for_column (line_table,
|
|
first_non_ws_byte_col));
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
test_layout.print_line (1);
|
|
ASSERT_STREQ (" This: ` ' is a tab.\n"
|
|
" ^\n",
|
|
pp_formatted_text (dc.printer));
|
|
}
|
|
|
|
/* Confirm the display width was tracked correctly across the internal tab
|
|
as well. */
|
|
{
|
|
test_diagnostic_context dc;
|
|
dc.tabstop = tabstop;
|
|
rich_location richloc (line_table,
|
|
linemap_position_for_column (line_table,
|
|
right_quote_byte_col));
|
|
layout test_layout (&dc, &richloc, DK_ERROR);
|
|
test_layout.print_line (1);
|
|
ASSERT_STREQ (" This: ` ' is a tab.\n"
|
|
" ^\n",
|
|
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 (" 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
|
|
diagnostic_show_locus_c_tests ()
|
|
{
|
|
test_line_span ();
|
|
|
|
test_layout_range_for_single_point ();
|
|
test_layout_range_for_single_line ();
|
|
test_layout_range_for_multiple_lines ();
|
|
|
|
for_each_line_table_case (test_layout_x_offset_display_utf8);
|
|
for_each_line_table_case (test_layout_x_offset_display_tab);
|
|
|
|
test_get_line_bytes_without_trailing_whitespace ();
|
|
|
|
test_diagnostic_show_locus_unknown_location ();
|
|
|
|
for_each_line_table_case (test_diagnostic_show_locus_one_liner);
|
|
for_each_line_table_case (test_diagnostic_show_locus_one_liner_utf8);
|
|
for_each_line_table_case (test_add_location_if_nearby);
|
|
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_overlapped_fixit_printing_utf8);
|
|
for_each_line_table_case (test_overlapped_fixit_printing_2);
|
|
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);
|
|
for_each_line_table_case (test_fixit_deletion_affecting_newline);
|
|
for_each_line_table_case (test_tab_expansion);
|
|
|
|
test_line_numbers_multiline_range ();
|
|
}
|
|
|
|
} // namespace selftest
|
|
|
|
#endif /* #if CHECKING_P */
|
|
|
|
#if __GNUC__ >= 10
|
|
# pragma GCC diagnostic pop
|
|
#endif
|