Mercurial > hg > CbC > CbC_gcc
diff gcc/diagnostic-show-locus.c @ 145:1830386684a0
gcc-9.2.0
author | anatofuz |
---|---|
date | Thu, 13 Feb 2020 11:34:05 +0900 |
parents | 84e7813d76e9 |
children |
line wrap: on
line diff
--- a/gcc/diagnostic-show-locus.c Thu Oct 25 07:37:49 2018 +0900 +++ b/gcc/diagnostic-show-locus.c Thu Feb 13 11:34:05 2020 +0900 @@ -1,5 +1,5 @@ /* Diagnostic subroutines for printing source-code - Copyright (C) 1999-2018 Free Software Foundation, Inc. + Copyright (C) 1999-2020 Free Software Foundation, Inc. Contributed by Gabriel Dos Reis <gdr@codesourcery.com> This file is part of GCC. @@ -30,6 +30,7 @@ #include "gcc-rich-location.h" #include "selftest.h" #include "selftest-diagnostic.h" +#include "cpplib.h" #ifdef HAVE_TERMIOS_H # include <termios.h> @@ -39,6 +40,13 @@ # 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 @@ -79,7 +87,17 @@ diagnostic_t diagnostic_kind); ~colorizer (); - void set_range (int range_idx) { set_state (range_idx); } + 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); } @@ -105,18 +123,81 @@ const char *m_stop_color; }; -/* A point within a layout_range; similar to an expanded_location, +/* 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) + : expanded_location (exploc), + m_display_col (location_compute_display_column (exploc)) {} + + 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 expanded_location &exploc) - : m_line (exploc.line), - m_column (exploc.column) {} + : m_line (exploc.line) + { + m_columns[CU_BYTES] = exploc.column; + m_columns[CU_DISPLAY_COLS] = location_compute_display_column (exploc); + } linenum_type m_line; - int m_column; + int m_columns[CU_NUM_UNITS]; }; /* A class for use by "class layout" below: a filtered location_range. */ @@ -131,7 +212,8 @@ unsigned original_idx, const range_label *label); - bool contains_point (linenum_type row, int column) const; + 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; @@ -150,6 +232,17 @@ { int m_first_non_ws; int m_last_non_ws; + + void convert_to_display_cols (char_span line) + { + m_first_non_ws = cpp_byte_column_to_display_column (line.get_buffer (), + line.length (), + m_first_non_ws); + + m_last_non_ws = cpp_byte_column_to_display_column (line.get_buffer (), + line.length (), + m_last_non_ws); + } }; /* A range of contiguous source lines within a layout (e.g. "lines 5-10" @@ -157,8 +250,9 @@ splits the pertinent source lines into a list of disjoint line_span instances (e.g. lines 5-10, lines 15-20, line 23). */ -struct line_span +class line_span { +public: line_span (linenum_type first_line, linenum_type last_line) : m_first_line (first_line), m_last_line (last_line) { @@ -244,6 +338,9 @@ 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; @@ -254,7 +351,7 @@ private: bool will_show_line_p (linenum_type row) const; void print_leading_fixits (linenum_type row); - void print_source_line (linenum_type row, const char *line, int line_width, + void print_source_line (linenum_type row, const char *line, int line_bytes, line_bounds *lbounds_out); bool should_print_annotation_line_p (linenum_type row) const; void start_annotation_line (char margin_char = ' ') const; @@ -269,6 +366,8 @@ 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 (); @@ -276,6 +375,7 @@ 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); @@ -290,16 +390,17 @@ diagnostic_context *m_context; pretty_printer *m_pp; location_t m_primary_loc; - expanded_location m_exploc; + 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; + int m_x_offset_display; }; /* Implementation of "class colorizer". */ @@ -464,10 +565,15 @@ - '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. */ + - '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) const +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; @@ -483,7 +589,7 @@ /* 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_column) + 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; @@ -497,7 +603,7 @@ { /* This is a single-line range. */ gcc_assert (row == m_finish.m_line); - return column <= m_finish.m_column; + return column <= m_finish.m_columns[col_unit]; } } @@ -522,7 +628,7 @@ gcc_assert (row == m_finish.m_line); - return column <= m_finish.m_column; + return column <= m_finish.m_columns[col_unit]; } /* Does this layout_range contain any part of line ROW? */ @@ -540,15 +646,22 @@ #if CHECKING_P -/* A helper function for testing layout_range. */ +/* 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 - = {"test.c", start_line, start_col, NULL, false}; + = {"", start_line, start_col, NULL, false}; const expanded_location finish_exploc - = {"test.c", end_line, end_col, NULL, false}; + = {"", end_line, end_col, NULL, false}; return layout_range (&start_exploc, &finish_exploc, SHOW_RANGE_WITHOUT_CARET, &start_exploc, 0, NULL); } @@ -566,20 +679,25 @@ /* Tests for layout_range::contains_point. */ - /* Before the line. */ - ASSERT_FALSE (point.contains_point (6, 1)); - - /* On the line, but before start. */ - ASSERT_FALSE (point.contains_point (7, 9)); - - /* At the point. */ - ASSERT_TRUE (point.contains_point (7, 10)); - - /* On the line, after the point. */ - ASSERT_FALSE (point.contains_point (7, 11)); - - /* After the line. */ - ASSERT_FALSE (point.contains_point (8, 1)); + 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)); @@ -597,26 +715,31 @@ /* Tests for layout_range::contains_point. */ - /* Before the line. */ - ASSERT_FALSE (example_a.contains_point (1, 1)); - - /* On the line, but before start. */ - ASSERT_FALSE (example_a.contains_point (2, 21)); - - /* On the line, at the start. */ - ASSERT_TRUE (example_a.contains_point (2, 22)); - - /* On the line, within the range. */ - ASSERT_TRUE (example_a.contains_point (2, 23)); - - /* On the line, at the end. */ - ASSERT_TRUE (example_a.contains_point (2, 38)); - - /* On the line, after the end. */ - ASSERT_FALSE (example_a.contains_point (2, 39)); - - /* After the line. */ - ASSERT_FALSE (example_a.contains_point (2, 39)); + 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)); @@ -634,40 +757,45 @@ /* Tests for layout_range::contains_point. */ - /* Before first line. */ - ASSERT_FALSE (example_b.contains_point (1, 1)); - - /* On the first line, but before start. */ - ASSERT_FALSE (example_b.contains_point (3, 13)); - - /* At the start. */ - ASSERT_TRUE (example_b.contains_point (3, 14)); - - /* On the first line, within the range. */ - ASSERT_TRUE (example_b.contains_point (3, 15)); - - /* On an interior line. - The column number should not matter; try various boundary - values. */ - ASSERT_TRUE (example_b.contains_point (4, 1)); - ASSERT_TRUE (example_b.contains_point (4, 7)); - ASSERT_TRUE (example_b.contains_point (4, 8)); - ASSERT_TRUE (example_b.contains_point (4, 9)); - ASSERT_TRUE (example_b.contains_point (4, 13)); - ASSERT_TRUE (example_b.contains_point (4, 14)); - ASSERT_TRUE (example_b.contains_point (4, 15)); - - /* On the final line, before the end. */ - ASSERT_TRUE (example_b.contains_point (5, 7)); - - /* On the final line, at the end. */ - ASSERT_TRUE (example_b.contains_point (5, 8)); - - /* On the final line, after the end. */ - ASSERT_FALSE (example_b.contains_point (5, 9)); - - /* After the line. */ - ASSERT_FALSE (example_b.contains_point (6, 1)); + 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)); @@ -679,13 +807,13 @@ #endif /* #if CHECKING_P */ -/* Given a source line LINE of length LINE_WIDTH, determine the width - without any trailing whitespace. */ +/* 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_width_without_trailing_whitespace (const char *line, int line_width) +get_line_bytes_without_trailing_whitespace (const char *line, int line_bytes) { - int result = line_width; + int result = line_bytes; while (result > 0) { char ch = line[result - 1]; @@ -695,7 +823,7 @@ break; } gcc_assert (result >= 0); - gcc_assert (result <= line_width); + gcc_assert (result <= line_bytes); gcc_assert (result == 0 || (line[result - 1] != ' ' && line[result -1] != '\t' @@ -705,21 +833,21 @@ #if CHECKING_P -/* A helper function for testing get_line_width_without_trailing_whitespace. */ +/* A helper function for testing get_line_bytes_without_trailing_whitespace. */ static void -assert_eq (const char *line, int expected_width) +assert_eq (const char *line, int expected_bytes) { int actual_value - = get_line_width_without_trailing_whitespace (line, strlen (line)); - ASSERT_EQ (actual_value, expected_width); + = get_line_bytes_without_trailing_whitespace (line, strlen (line)); + ASSERT_EQ (actual_value, expected_bytes); } -/* Verify that get_line_width_without_trailing_whitespace is sane for +/* 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_width_without_trailing_whitespace () +test_get_line_bytes_without_trailing_whitespace () { assert_eq ("", 0); assert_eq (" ", 0); @@ -777,11 +905,11 @@ /* Expand each location towards the spelling location, and recurse. */ const line_map_macro *macro_map = linemap_check_macro (map_a); - source_location loc_a_toward_spelling + location_t loc_a_toward_spelling = linemap_macro_map_loc_unwind_toward_spelling (line_table, macro_map, loc_a); - source_location loc_b_toward_spelling + location_t loc_b_toward_spelling = linemap_macro_map_loc_unwind_toward_spelling (line_table, macro_map, loc_b); @@ -819,56 +947,6 @@ return hint_a->get_start_loc () - hint_b->get_start_loc (); } -/* Get the number of digits in the decimal representation - of VALUE. */ - -static int -num_digits (int value) -{ - /* Perhaps simpler to use log10 for this, but doing it this way avoids - using floating point. */ - gcc_assert (value >= 0); - - if (value == 0) - return 1; - - int digits = 0; - while (value > 0) - { - digits++; - value /= 10; - } - return digits; -} - - -#if CHECKING_P - -/* Selftest for num_digits. */ - -static void -test_num_digits () -{ - ASSERT_EQ (1, num_digits (0)); - ASSERT_EQ (1, num_digits (9)); - ASSERT_EQ (2, num_digits (10)); - ASSERT_EQ (2, num_digits (99)); - ASSERT_EQ (3, num_digits (100)); - ASSERT_EQ (3, num_digits (999)); - ASSERT_EQ (4, num_digits (1000)); - ASSERT_EQ (4, num_digits (9999)); - ASSERT_EQ (5, num_digits (10000)); - ASSERT_EQ (5, num_digits (99999)); - ASSERT_EQ (6, num_digits (100000)); - ASSERT_EQ (6, num_digits (999999)); - ASSERT_EQ (7, num_digits (1000000)); - ASSERT_EQ (7, num_digits (9999999)); - ASSERT_EQ (8, num_digits (10000000)); - ASSERT_EQ (8, num_digits (99999999)); -} - -#endif /* #if CHECKING_P */ - /* Implementation of class layout. */ /* Constructor for class layout. @@ -877,7 +955,7 @@ 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, to ensure that the primary caret + 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, @@ -891,11 +969,12 @@ 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 (0) + m_x_offset_display (0) { for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++) { @@ -917,45 +996,16 @@ /* Sort m_fixit_hints. */ m_fixit_hints.qsort (fixit_cmp); - /* Populate m_line_spans. */ + /* Populate the indicated members. */ calculate_line_spans (); - - /* Determine m_linenum_width. */ - gcc_assert (m_line_spans.length () > 0); - const line_span *last_span = &m_line_spans[m_line_spans.length () - 1]; - int highest_line = last_span->m_last_line; - if (highest_line < 0) - highest_line = 0; - m_linenum_width = num_digits (highest_line); - /* If we're showing jumps in the line-numbering, allow at least 3 chars. */ - if (m_line_spans.length () > 1) - m_linenum_width = MAX (m_linenum_width, 3); - /* If there's a minimum margin width, apply it (subtracting 1 for the space - after the line number. */ - m_linenum_width = MAX (m_linenum_width, context->min_margin_width - 1); - - /* Adjust m_x_offset. - Center the primary caret to fit in max_width; all columns - will be adjusted accordingly. */ - size_t max_width = m_context->caret_max_width; - char_span line = location_get_source_line (m_exploc.file, m_exploc.line); - if (line && (size_t)m_exploc.column <= line.length ()) - { - size_t right_margin = CARET_LINE_MARGIN; - size_t column = m_exploc.column; - if (m_show_line_numbers_p) - column += m_linenum_width + 2; - right_margin = MIN (line.length () - column, right_margin); - right_margin = max_width - right_margin; - if (line.length () >= max_width && column > right_margin) - m_x_offset = column - right_margin; - gcc_assert (m_x_offset >= 0); - } + calculate_linenum_width (); + calculate_x_offset_display (); if (context->show_ruler_p) - show_ruler (m_x_offset + max_width); + 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. @@ -1081,6 +1131,8 @@ { 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, '.'); @@ -1126,7 +1178,7 @@ { expanded_location exploc = m_exploc; exploc.line = lr->m_start.m_line; - exploc.column = lr->m_start.m_column; + exploc.column = lr->m_start.m_columns[CU_BYTES]; return exploc; } } @@ -1261,7 +1313,8 @@ 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 (next->m_first_line <= current->m_last_line + 1 + merger_distance) + 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) @@ -1290,23 +1343,122 @@ } } +/* 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); + 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 populate *LBOUNDS_OUT. - LINE is the source line (not necessarily 0-terminated) and LINE_WIDTH - is its width. */ + LINE is the source line (not necessarily 0-terminated) and LINE_BYTES + is its length in bytes. + This function deals only with byte offsets, not display columns, so + m_x_offset_display must be converted from display to byte units. In + particular, LINE_BYTES and LBOUNDS_OUT are in bytes. */ void -layout::print_source_line (linenum_type row, const char *line, int line_width, +layout::print_source_line (linenum_type row, const char *line, int line_bytes, line_bounds *lbounds_out) { m_colorizer.set_normal_text (); - /* We will stop printing the source line at any trailing - whitespace. */ - line_width = get_line_width_without_trailing_whitespace (line, - line_width); - line += m_x_offset; - + pp_emit_prefix (m_pp); if (m_show_line_numbers_p) { int width = num_digits (row); @@ -1316,10 +1468,31 @@ } else pp_space (m_pp); + + /* We will stop printing the source line at any trailing whitespace, and start + printing it as per m_x_offset_display. */ + line_bytes = get_line_bytes_without_trailing_whitespace (line, + line_bytes); + int x_offset_bytes = 0; + if (m_x_offset_display) + { + x_offset_bytes = cpp_display_column_to_byte_column (line, line_bytes, + m_x_offset_display); + /* In case the leading portion of the line that will be skipped over ends + with a character with wcwidth > 1, then it is possible we skipped too + much, so account for that by padding with spaces. */ + const int overage + = cpp_byte_column_to_display_column (line, line_bytes, x_offset_bytes) + - m_x_offset_display; + for (int column = 0; column < overage; ++column) + pp_space (m_pp); + line += x_offset_bytes; + } + + /* Print the line. */ int first_non_ws = INT_MAX; int last_non_ws = 0; - int column; - for (column = 1 + m_x_offset; column <= line_width; column++) + for (int col_byte = 1 + x_offset_bytes; col_byte <= line_bytes; col_byte++) { /* Assuming colorization is enabled for the caret and underline characters, we may also colorize the associated characters @@ -1337,8 +1510,9 @@ { bool in_range_p; point_state state; - in_range_p = get_state_at_point (row, column, + in_range_p = get_state_at_point (row, col_byte, 0, INT_MAX, + CU_BYTES, &state); if (in_range_p) m_colorizer.set_range (state.range_idx); @@ -1350,9 +1524,9 @@ c = ' '; if (c != ' ') { - last_non_ws = column; + last_non_ws = col_byte; if (first_non_ws == INT_MAX) - first_non_ws = column; + first_non_ws = col_byte; } pp_character (m_pp, c); line++; @@ -1387,6 +1561,7 @@ 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 @@ -1401,24 +1576,26 @@ } /* Print a line consisting of the caret/underlines for the given - source line. */ + source line. This function works with display columns, rather than byte + counts; in particular, LBOUNDS should be in display column units. */ void layout::print_annotation_line (linenum_type row, const line_bounds lbounds) { - int x_bound = get_x_bound_for_row (row, m_exploc.column, + int x_bound = get_x_bound_for_row (row, m_exploc.m_display_col, lbounds.m_last_non_ws); start_annotation_line (); pp_space (m_pp); - for (int column = 1 + m_x_offset; column < x_bound; column++) + 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, lbounds.m_last_non_ws, + CU_DISPLAY_COLS, &state); if (in_range_p) { @@ -1451,13 +1628,16 @@ A label within the given row of source. */ -struct line_label +class line_label { +public: line_label (int state_idx, int column, label_text text) : m_state_idx (state_idx), m_column (column), - m_text (text), m_length (strlen (text.m_buffer)), - m_label_line (0) - {} + 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); + } /* Sorting is primarily by column, then by state index. */ static int comparator (const void *p1, const void *p2) @@ -1467,14 +1647,18 @@ int column_cmp = compare (ll1->m_column, ll2->m_column); if (column_cmp) return column_cmp; - return compare (ll1->m_state_idx, ll2->m_state_idx); + /* 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_length; + size_t m_display_width; int m_label_line; + bool m_has_vbar; }; /* Print any labels in this row. */ @@ -1498,8 +1682,9 @@ continue; /* Reject labels that aren't fully visible due to clipping - by m_x_offset. */ - if (range->m_caret.m_column <= m_x_offset) + 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; @@ -1511,7 +1696,7 @@ if (text.m_buffer == NULL) continue; - labels.safe_push (line_label (i, range->m_caret.m_column, text)); + labels.safe_push (line_label (i, disp_col, text)); } } @@ -1551,8 +1736,8 @@ foo + bar ^ : label line 0 | : label line 1 - label 1 : label line 2 - label 0 : label line 3. */ + label 0 : label line 2 + label 1 : label line 3. */ int max_label_line = 1; { @@ -1561,8 +1746,16 @@ FOR_EACH_VEC_ELT_REVERSE (labels, i, label) { /* Would this label "touch" or overlap the next label? */ - if (label->m_column + label->m_length >= (size_t)next_column) - max_label_line++; + 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; @@ -1573,15 +1766,11 @@ either a vertical bar ('|') for the labels that are lower down, or the labels themselves once we've reached their line. */ { - /* Keep track of in which column we last printed a vertical bar. - This allows us to suppress duplicate vertical bars for the case - where multiple labels are on one column. */ - int last_vbar = 0; for (int label_line = 0; label_line <= max_label_line; label_line++) { start_annotation_line (); pp_space (m_pp); - int column = 1 + m_x_offset; + int column = 1 + m_x_offset_display; line_label *label; FOR_EACH_VEC_ELT (labels, i, label) { @@ -1593,19 +1782,21 @@ { gcc_assert (column <= label->m_column); move_to_column (&column, label->m_column, true); - m_colorizer.set_range (label->m_state_idx); + /* 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_length; + column += label->m_display_width; } - else if (label->m_column != last_vbar) + 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 (); - last_vbar = column; column++; } } @@ -1664,7 +1855,7 @@ /* Subroutine of layout::print_trailing_fixits. Determine if the annotation line printed for LINE contained - the exact range from START_COLUMN to FINISH_COLUMN. */ + 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, @@ -1674,9 +1865,9 @@ int i; FOR_EACH_VEC_ELT (m_layout_ranges, i, range) if (range->m_start.m_line == line - && range->m_start.m_column == start_column + && range->m_start.m_columns[CU_DISPLAY_COLS] == start_column && range->m_finish.m_line == line - && range->m_finish.m_column == finish_column) + && range->m_finish.m_columns[CU_DISPLAY_COLS] == finish_column) return true; return false; } @@ -1763,10 +1954,11 @@ and is thus printed as desired. */ -/* A range of columns within a line. */ - -struct column_range +/* 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. */ @@ -1782,32 +1974,51 @@ int finish; }; -/* Get the range of columns that HINT would affect. */ - +/* Get the range of bytes or display columns that HINT would affect. */ static column_range -get_affected_columns (const fixit_hint *hint) +get_affected_range (const fixit_hint *hint, enum column_unit col_unit) { - int start_column = LOCATION_COLUMN (hint->get_start_loc ()); - int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1; - + 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); + if (hint->insertion_p ()) + finish_column = start_column - 1; + else + finish_column = location_compute_display_column (exploc_finish); + } + else + { + start_column = exploc_start.column; + finish_column = exploc_finish.column; + } return column_range (start_column, finish_column); } -/* Get the range of columns that would be printed for HINT. */ +/* Get the range of display columns that would be printed for HINT. */ static column_range get_printed_columns (const fixit_hint *hint) { - int start_column = LOCATION_COLUMN (hint->get_start_loc ()); - int final_hint_column = start_column + hint->get_length () - 1; + expanded_location exploc = expand_location (hint->get_start_loc ()); + int start_column = location_compute_display_column (exploc); + int hint_width = cpp_display_width (hint->get_string (), + hint->get_length ()); + int final_hint_column = start_column + hint_width - 1; if (hint->insertion_p ()) { return column_range (start_column, final_hint_column); } else { - int finish_column = LOCATION_COLUMN (hint->get_next_loc ()) - 1; - + exploc = expand_location (hint->get_next_loc ()); + --exploc.column; + int finish_column = location_compute_display_column (exploc); return column_range (start_column, MAX (finish_column, final_hint_column)); } @@ -1818,29 +2029,38 @@ instances that affected the line, potentially consolidating hints into corrections to make the result easier for the user to read. */ -struct correction +class correction { - correction (column_range affected_columns, +public: + correction (column_range affected_bytes, + column_range affected_columns, column_range printed_columns, const char *new_text, size_t new_text_len) - : m_affected_columns (affected_columns), + : m_affected_bytes (affected_bytes), + m_affected_columns (affected_columns), m_printed_columns (printed_columns), m_text (xstrdup (new_text)), - m_len (new_text_len), + m_byte_length (new_text_len), m_alloc_sz (new_text_len + 1) { + compute_display_cols (); } ~correction () { free (m_text); } bool insertion_p () const { - return m_affected_columns.start == m_affected_columns.finish + 1; + 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); + } + void overwrite (int dst_offset, const char_span &src_span) { gcc_assert (dst_offset >= 0); @@ -1853,6 +2073,7 @@ 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 @@ -1863,7 +2084,8 @@ /* The text to be inserted/used as replacement. */ char *m_text; - size_t m_len; + size_t m_byte_length; /* Not including null-terminator. */ + int m_display_cols; size_t m_alloc_sz; }; @@ -1888,16 +2110,17 @@ correction::ensure_terminated () { /* 0-terminate the buffer. */ - gcc_assert (m_len < m_alloc_sz); - m_text[m_len] = '\0'; + 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. */ -struct line_corrections +class line_corrections { +public: line_corrections (const char *filename, linenum_type row) : m_filename (filename), m_row (row) {} @@ -1923,8 +2146,9 @@ /* A struct wrapping a particular source line, allowing run-time bounds-checking of accesses in a checked build. */ -struct source_line +class source_line { +public: source_line (const char *filename, int line); char_span as_span () { return char_span (chars, width); } @@ -1949,7 +2173,8 @@ void line_corrections::add_hint (const fixit_hint *hint) { - column_range affected_columns = get_affected_columns (hint); + column_range affected_bytes = get_affected_range (hint, CU_BYTES); + column_range affected_columns = get_affected_range (hint, CU_DISPLAY_COLS); column_range printed_columns = get_printed_columns (hint); /* Potentially consolidate. */ @@ -1960,8 +2185,8 @@ /* The following consolidation code assumes that the fix-it hints have been sorted by start (done within layout's ctor). */ - gcc_assert (affected_columns.start - >= last_correction->m_affected_columns.start); + gcc_assert (affected_bytes.start + >= last_correction->m_affected_bytes.start); gcc_assert (printed_columns.start >= last_correction->m_printed_columns.start); @@ -1973,8 +2198,8 @@ Attempt to inject a "replace" correction from immediately after the end of the last hint to immediately before the start of the next hint. */ - column_range between (last_correction->m_affected_columns.finish + 1, - printed_columns.start - 1); + 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); @@ -1983,33 +2208,39 @@ /* Consolidate into the last correction: add a no-op "replace" of the "between" text, and add the text from the new hint. */ - int old_len = last_correction->m_len; - gcc_assert (old_len >= 0); - int between_len = between.finish + 1 - between.start; - gcc_assert (between_len >= 0); - int new_len = old_len + between_len + hint->get_length (); - gcc_assert (new_len >= 0); - last_correction->ensure_capacity (new_len); + 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_len, + (old_byte_len, line.as_span ().subspan (between.start - 1, between.finish + 1 - between.start)); - last_correction->overwrite (old_len + between_len, + last_correction->overwrite (old_byte_len + between_byte_len, char_span (hint->get_string (), hint->get_length ())); - last_correction->m_len = new_len; + 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 - += between_len + hint->get_length (); + += 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_columns, + m_corrections.safe_push (new correction (affected_bytes, + affected_columns, printed_columns, hint->get_string (), hint->get_length ())); @@ -2042,7 +2273,7 @@ /* Now print the corrections. */ unsigned i; correction *c; - int column = m_x_offset; + int column = m_x_offset_display; if (!corrections.m_corrections.is_empty ()) start_annotation_line (); @@ -2058,7 +2289,7 @@ m_colorizer.set_fixit_insert (); pp_string (m_pp, c->m_text); m_colorizer.set_normal_text (); - column += c->m_len; + column += c->m_display_cols; } else { @@ -2070,7 +2301,7 @@ int finish_column = c->m_affected_columns.finish; if (!annotation_line_showed_range_p (row, start_column, finish_column) - || c->m_len == 0) + || c->m_byte_length == 0) { move_to_column (&column, start_column, true); m_colorizer.set_fixit_delete (); @@ -2081,13 +2312,13 @@ /* Print the replacement text. REPLACE also covers removals, so only do this extra work (potentially starting a new line) if we have actual replacement text. */ - if (c->m_len > 0) + 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_len; + column += c->m_display_cols; } } } @@ -2108,12 +2339,14 @@ /* 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). */ + (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) { @@ -2126,7 +2359,7 @@ source colorization. */ continue; - if (range->contains_point (row, column)) + if (range->contains_point (row, column, col_unit)) { out_state->range_idx = i; @@ -2134,7 +2367,7 @@ 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_column) + && column == range->m_caret.m_columns[col_unit]) out_state->draw_caret_p = true; /* Within a multiline range, don't display any underline @@ -2154,11 +2387,11 @@ /* Helper function for use by layout::print_line when printing the annotation line under the source line. - Get the column beyond the rightmost one that could contain a caret or - range marker, given that we stop rendering at trailing whitespace. + 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 column of range 0's caret. - LAST_NON_WS_COLUMN is the last column containing a non-whitespace + 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 @@ -2177,8 +2410,9 @@ { /* On the final line within a range; ensure that we render up to the end of the range. */ - if (result <= range->m_finish.m_column) - result = range->m_finish.m_column + 1; + 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) { @@ -2207,7 +2441,7 @@ print_newline (); if (add_left_margin) start_annotation_line (); - *column = m_x_offset; + *column = m_x_offset_display; } while (*column < dest_column) @@ -2228,7 +2462,7 @@ { start_annotation_line (); pp_space (m_pp); - for (int column = 1 + m_x_offset; column <= max_column; column++) + 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 @@ -2239,7 +2473,7 @@ /* Tens. */ start_annotation_line (); pp_space (m_pp); - for (int column = 1 + m_x_offset; column <= max_column; column++) + 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 @@ -2249,7 +2483,7 @@ /* Units. */ start_annotation_line (); pp_space (m_pp); - for (int column = 1 + m_x_offset; column <= max_column; column++) + for (int column = 1 + m_x_offset_display; column <= max_column; column++) pp_character (m_pp, '0' + (column % 10)); pp_newline (m_pp); } @@ -2269,7 +2503,11 @@ print_leading_fixits (row); print_source_line (row, line.get_buffer (), line.length (), &lbounds); if (should_print_annotation_line_p (row)) - print_annotation_line (row, lbounds); + { + if (lbounds.m_first_non_ws != INT_MAX) + lbounds.convert_to_display_cols (line); + print_annotation_line (row, lbounds); + } if (m_show_labels_p) print_any_labels (row); print_trailing_fixits (row); @@ -2283,7 +2521,9 @@ Otherwise return false. */ bool -gcc_rich_location::add_location_if_nearby (location_t loc) +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 @@ -2292,10 +2532,11 @@ 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, true)) + if (!layout.maybe_add_location_range (&loc_range, 0, + restrict_to_current_line_spans)) return false; - add_range (loc); + add_range (loc, SHOW_RANGE_WITHOUT_CARET, label); return true; } @@ -2307,8 +2548,6 @@ rich_location *richloc, diagnostic_t diagnostic_kind) { - pp_newline (context->printer); - location_t loc = richloc->get_loc (); /* Do nothing if source-printing has been disabled. */ if (!context->show_caret) @@ -2326,9 +2565,6 @@ context->last_location = loc; - char *saved_prefix = pp_take_prefix (context->printer); - pp_set_prefix (context->printer, NULL); - layout layout (context, richloc, diagnostic_kind); for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans (); line_span_idx++) @@ -2351,13 +2587,13 @@ context->start_span (context, exploc); } } - linenum_type last_line = line_span->get_last_line (); - for (linenum_type row = line_span->get_first_line (); + /* 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); } - - pp_set_prefix (context->printer, saved_prefix); } #if CHECKING_P @@ -2366,6 +2602,178 @@ /* 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 ())); + ASSERT_EQ (line_display_cols, + location_compute_display_column (expand_location (line_end))); + 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)); + } + +} + /* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION. */ static void @@ -2374,7 +2782,7 @@ test_diagnostic_context dc; rich_location richloc (line_table, UNKNOWN_LOCATION); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n", pp_formatted_text (dc.printer)); + ASSERT_STREQ ("", pp_formatted_text (dc.printer)); } /* Verify that diagnostic_show_locus works sanely for various @@ -2396,8 +2804,7 @@ 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 ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^\n", pp_formatted_text (dc.printer)); } @@ -2414,8 +2821,7 @@ location_t loc = make_location (caret, start, finish); rich_location richloc (line_table, loc); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ~~~^~~~~~\n", pp_formatted_text (dc.printer)); } @@ -2448,8 +2854,7 @@ richloc.add_range (bar, SHOW_RANGE_WITH_CARET); richloc.add_range (field, SHOW_RANGE_WITH_CARET); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ~A~ ~B~ ~~C~~\n", pp_formatted_text (dc.printer)); } @@ -2464,8 +2869,7 @@ rich_location richloc (line_table, caret); richloc.add_fixit_insert_before ("&"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^\n" " &\n", pp_formatted_text (dc.printer)); @@ -2483,30 +2887,94 @@ rich_location richloc (line_table, foo); richloc.add_fixit_insert_after ("[0]"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~\n" " [0]\n", pp_formatted_text (dc.printer)); } -/* Removal fix-it hint: removal of the ".field". */ +/* 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 () { - test_diagnostic_context dc; 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 (); - diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" - " ^~~~~~\n" - " ------\n", - pp_formatted_text (dc.printer)); + + /* 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". */ @@ -2521,8 +2989,7 @@ rich_location richloc (line_table, field); richloc.add_fixit_replace ("m_field"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~~~\n" " m_field\n", pp_formatted_text (dc.printer)); @@ -2546,8 +3013,7 @@ 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 ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^\n" " -----\n" " m_field\n", @@ -2572,8 +3038,7 @@ 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 ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^ ~~~~~\n" " m_field\n", pp_formatted_text (dc.printer)); @@ -2606,8 +3071,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~~~~~~~~ \n" " test\n", pp_formatted_text (dc.printer)); @@ -2623,8 +3087,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~~~~~~~~ \n" " -----------------------------------------\n", pp_formatted_text (dc.printer)); @@ -2640,8 +3103,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~~~~~~~~ \n" " test\n", pp_formatted_text (dc.printer)); @@ -2660,8 +3122,7 @@ richloc.add_fixit_insert_before ("a"); ASSERT_EQ (1, richloc.get_num_fixit_hints ()); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^\n" " aaaaaaaaaaaaaaaaaaa\n", pp_formatted_text (dc.printer)); @@ -2683,8 +3144,7 @@ } ASSERT_EQ (19, richloc.get_num_fixit_hints ()); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + 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)); @@ -2720,8 +3180,7 @@ { test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~ ~~~ ~~~~~\n" " | | |\n" " 0 1 2\n", @@ -2733,8 +3192,7 @@ test_diagnostic_context dc; dc.show_labels_p = false; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~ ~~~ ~~~~~\n", pp_formatted_text (dc.printer)); } @@ -2751,8 +3209,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~ ~~~ ~~~~~\n" " | | |\n" " | | label 2\n" @@ -2773,8 +3230,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~ ~~~ ~~~~~\n" " | | |\n" " | | c\n" @@ -2793,8 +3249,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ~~~ ~~~ ^~~~~\n" " | | |\n" " 2 1 0\n", @@ -2813,13 +3268,53 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~\n" " |\n" - " label 2\n" + " label 0\n" " label 1\n" - " label 0\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)); } @@ -2831,8 +3326,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar.field;\n" + ASSERT_STREQ (" foo = bar.field;\n" " ^~~\n", pp_formatted_text (dc.printer)); } @@ -2880,6 +3374,534 @@ 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)); + } +} + +/* 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 ())); + ASSERT_EQ (25, location_compute_display_column (expand_location (line_end))); + + 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 (); +} + /* Verify that gcc_rich_location::add_location_if_nearby works. */ static void @@ -2924,8 +3946,7 @@ ASSERT_EQ (2, richloc.get_num_locations ()); test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " struct same_line { double x; double y; ;\n" + ASSERT_STREQ (" struct same_line { double x; double y; ;\n" " ~ ^\n", pp_formatted_text (dc.printer)); } @@ -2989,8 +4010,7 @@ richloc.add_fixit_insert_before (x, "."); richloc.add_fixit_replace (colon, "="); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " struct point origin = {x: 0.0,\n" + ASSERT_STREQ (" struct point origin = {x: 0.0,\n" " ^\n" " .=\n", pp_formatted_text (dc.printer)); @@ -3010,8 +4030,7 @@ richloc.add_fixit_insert_before (y, "."); richloc.add_fixit_replace (colon, "="); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - "FILENAME:3:24:\n" + ASSERT_STREQ ("FILENAME:3:24:\n" " y\n" " .\n" "FILENAME:6:25:\n" @@ -3034,8 +4053,7 @@ test_diagnostic_context dc; dc.show_line_numbers_p = true; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " 3 | y\n" + ASSERT_STREQ (" 3 | y\n" " | .\n" "......\n" " 6 | : 0.0};\n" @@ -3245,8 +4263,7 @@ richloc.add_fixit_insert_after (")"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo *f = (foo *)ptr->field;\n" + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" " ^~~~~~~~~~\n" " -----------------\n" " const_cast<foo *> (ptr->field)\n", @@ -3255,13 +4272,19 @@ /* Unit-test the line_corrections machinery. */ ASSERT_EQ (3, richloc.get_num_fixit_hints ()); const fixit_hint *hint_0 = richloc.get_fixit_hint (0); - ASSERT_EQ (column_range (12, 12), get_affected_columns (hint_0)); + ASSERT_EQ (column_range (12, 12), get_affected_range (hint_0, CU_BYTES)); + ASSERT_EQ (column_range (12, 12), + get_affected_range (hint_0, CU_DISPLAY_COLS)); ASSERT_EQ (column_range (12, 22), get_printed_columns (hint_0)); const fixit_hint *hint_1 = richloc.get_fixit_hint (1); - ASSERT_EQ (column_range (18, 18), get_affected_columns (hint_1)); + ASSERT_EQ (column_range (18, 18), get_affected_range (hint_1, CU_BYTES)); + ASSERT_EQ (column_range (18, 18), + get_affected_range (hint_1, CU_DISPLAY_COLS)); ASSERT_EQ (column_range (18, 20), get_printed_columns (hint_1)); const fixit_hint *hint_2 = richloc.get_fixit_hint (2); - ASSERT_EQ (column_range (29, 28), get_affected_columns (hint_2)); + ASSERT_EQ (column_range (29, 28), get_affected_range (hint_2, CU_BYTES)); + ASSERT_EQ (column_range (29, 28), + get_affected_range (hint_2, CU_DISPLAY_COLS)); ASSERT_EQ (column_range (29, 29), get_printed_columns (hint_2)); /* Add each hint in turn to a line_corrections instance, @@ -3272,6 +4295,7 @@ /* 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); @@ -3281,6 +4305,7 @@ 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); @@ -3290,6 +4315,7 @@ 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); } @@ -3303,8 +4329,7 @@ richloc.add_fixit_insert_after (")"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo *f = (foo *)ptr->field;\n" + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" " ^~~~~~~~~~\n" " -\n" " CAST (-\n" @@ -3321,8 +4346,7 @@ richloc.add_fixit_insert_after (")"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo *f = (foo *)ptr->field;\n" + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" " ^~~~~~~~~~\n" " -\n" " CST ( -\n" @@ -3343,8 +4367,7 @@ ASSERT_EQ (1, richloc.get_num_fixit_hints ()); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo *f = (foo *)ptr->field;\n" + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" " ^~~~~~~~~~\n" " -------\n" " (bar *)\n", @@ -3364,8 +4387,7 @@ /* But the corrections are. */ diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo *f = (foo *)ptr->field;\n" + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" " ^~~~~~~~~~\n" " -----------------\n" " (longer *)(foo *)\n", @@ -3383,8 +4405,7 @@ it would overlap with the second. Verify that they are printed as a single replacement. */ diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo *f = (foo *)ptr->field;\n" + ASSERT_STREQ (" foo *f = (foo *)ptr->field;\n" " ^~~~~~~~~~\n" " -------\n" " LONGER THAN THE CAST(foo *)TEST\n", @@ -3392,6 +4413,240 @@ } } +/* 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 (hint_0, CU_BYTES)); + ASSERT_EQ (column_range (12, 12), + get_affected_range (hint_0, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (12, 22), get_printed_columns (hint_0)); + const fixit_hint *hint_1 = richloc.get_fixit_hint (1); + ASSERT_EQ (column_range (22, 22), get_affected_range (hint_1, CU_BYTES)); + ASSERT_EQ (column_range (18, 18), + get_affected_range (hint_1, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (18, 20), get_printed_columns (hint_1)); + const fixit_hint *hint_2 = richloc.get_fixit_hint (2); + ASSERT_EQ (column_range (35, 34), get_affected_range (hint_2, CU_BYTES)); + ASSERT_EQ (column_range (30, 29), + get_affected_range (hint_2, CU_DISPLAY_COLS)); + ASSERT_EQ (column_range (30, 30), get_printed_columns (hint_2)); + + /* Add each hint in turn to a line_corrections instance, + and verify that they are consolidated into one correction instance + as expected. */ + line_corrections lc (tmp.get_filename (), 1); + + /* The first replace hint by itself. */ + lc.add_hint (hint_0); + ASSERT_EQ (1, lc.m_corrections.length ()); + ASSERT_EQ (column_range (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. @@ -3441,17 +4696,16 @@ /* 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_columns (hint_0)); + ASSERT_EQ (column_range (23, 22), get_affected_range (hint_0, CU_BYTES)); ASSERT_EQ (column_range (23, 23), get_printed_columns (hint_0)); const fixit_hint *hint_1 = richloc.get_fixit_hint (1); - ASSERT_EQ (column_range (21, 20), get_affected_columns (hint_1)); + ASSERT_EQ (column_range (21, 20), get_affected_range (hint_1, CU_BYTES)); ASSERT_EQ (column_range (21, 21), get_printed_columns (hint_1)); /* Verify that they're printed correctly. */ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " int a5[][0][0] = { 1, 2 };\n" + ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n" " ^\n" " } {\n", pp_formatted_text (dc.printer)); @@ -3473,8 +4727,7 @@ richloc.add_fixit_insert_before (col_1, "{"); richloc.add_fixit_insert_before (col_25, "}"); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " int a5[][0][0] = { 1, 2 };\n" + ASSERT_STREQ (" int a5[][0][0] = { 1, 2 };\n" " ^\n" " { -----\n" " {{1}}}}, {{{2 }}\n", @@ -3517,8 +4770,7 @@ { test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " x = a;\n" + ASSERT_STREQ (" x = a;\n" "+ break;\n" " case 'b':\n" " ^~~~~~~~~\n", @@ -3530,8 +4782,7 @@ test_diagnostic_context dc; dc.show_line_numbers_p = true; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " 2 | x = a;\n" + ASSERT_STREQ (" 2 | x = a;\n" " +++ |+ break;\n" " 3 | case 'b':\n" " | ^~~~~~~~~\n", @@ -3547,8 +4798,7 @@ ASSERT_TRUE (richloc.seen_impossible_fixit_p ()); test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " case 'b':\n" + ASSERT_STREQ (" case 'b':\n" " ^~~~~~~~~\n", pp_formatted_text (dc.printer)); } @@ -3596,8 +4846,7 @@ { test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - "FILENAME:1:1:\n" + ASSERT_STREQ ("FILENAME:1:1:\n" "+#include <stdio.h>\n" " test (int ch)\n" "FILENAME:3:2:\n" @@ -3612,8 +4861,7 @@ test_diagnostic_context dc; dc.show_line_numbers_p = true; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " +++ |+#include <stdio.h>\n" + ASSERT_STREQ (" +++ |+#include <stdio.h>\n" " 1 | test (int ch)\n" " 2 | {\n" " 3 | putchar (ch);\n" @@ -3656,8 +4904,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar ();\n" + ASSERT_STREQ (" foo = bar ();\n" " ^\n", pp_formatted_text (dc.printer)); } @@ -3701,8 +4948,7 @@ test_diagnostic_context dc; diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " foo = bar (\n" + ASSERT_STREQ (" foo = bar (\n" " ~^\n" " );\n" " ~ \n", @@ -3745,8 +4991,7 @@ dc.min_margin_width = 0; gcc_rich_location richloc (loc); diagnostic_show_locus (&dc, &richloc, DK_ERROR); - ASSERT_STREQ ("\n" - " 9 | this is line 9\n" + ASSERT_STREQ (" 9 | this is line 9\n" " | ~~~~~~\n" "10 | this is line 10\n" " | ~~~~~^~~~~~~~~~\n" @@ -3761,21 +5006,24 @@ diagnostic_show_locus_c_tests () { test_line_span (); - test_num_digits (); test_layout_range_for_single_point (); test_layout_range_for_single_line (); test_layout_range_for_multiple_lines (); - test_get_line_width_without_trailing_whitespace (); + for_each_line_table_case (test_layout_x_offset_display_utf8); + + 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); @@ -3788,3 +5036,7 @@ } // namespace selftest #endif /* #if CHECKING_P */ + +#if __GNUC__ >= 10 +# pragma GCC diagnostic pop +#endif