]>
Commit | Line | Data |
---|---|---|
57eb2d70 | 1 | /* Diagnostic subroutines for printing source-code |
818ab71a | 2 | Copyright (C) 1999-2016 Free Software Foundation, Inc. |
57eb2d70 DM |
3 | Contributed by Gabriel Dos Reis <gdr@codesourcery.com> |
4 | ||
5 | This file is part of GCC. | |
6 | ||
7 | GCC is free software; you can redistribute it and/or modify it under | |
8 | the terms of the GNU General Public License as published by the Free | |
9 | Software Foundation; either version 3, or (at your option) any later | |
10 | version. | |
11 | ||
12 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY | |
13 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
14 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
15 | for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with GCC; see the file COPYING3. If not see | |
19 | <http://www.gnu.org/licenses/>. */ | |
20 | ||
21 | #include "config.h" | |
22 | #include "system.h" | |
23 | #include "coretypes.h" | |
24 | #include "version.h" | |
25 | #include "demangle.h" | |
26 | #include "intl.h" | |
27 | #include "backtrace.h" | |
28 | #include "diagnostic.h" | |
29 | #include "diagnostic-color.h" | |
d9b950dd | 30 | #include "selftest.h" |
57eb2d70 DM |
31 | |
32 | #ifdef HAVE_TERMIOS_H | |
33 | # include <termios.h> | |
34 | #endif | |
35 | ||
36 | #ifdef GWINSZ_IN_SYS_IOCTL | |
37 | # include <sys/ioctl.h> | |
38 | #endif | |
39 | ||
8a645150 DM |
40 | /* Classes for rendering source code and diagnostics, within an |
41 | anonymous namespace. | |
42 | The work is done by "class layout", which embeds and uses | |
43 | "class colorizer" and "class layout_range" to get things done. */ | |
44 | ||
45 | namespace { | |
46 | ||
47 | /* The state at a given point of the source code, assuming that we're | |
48 | in a range: which range are we in, and whether we should draw a caret at | |
49 | this point. */ | |
50 | ||
51 | struct point_state | |
52 | { | |
53 | int range_idx; | |
54 | bool draw_caret_p; | |
55 | }; | |
56 | ||
57 | /* A class to inject colorization codes when printing the diagnostic locus. | |
58 | ||
59 | It has one kind of colorization for each of: | |
60 | - normal text | |
61 | - range 0 (the "primary location") | |
62 | - range 1 | |
63 | - range 2 | |
64 | ||
65 | The class caches the lookup of the color codes for the above. | |
66 | ||
67 | The class also has responsibility for tracking which of the above is | |
68 | active, filtering out unnecessary changes. This allows | |
69 | layout::print_source_line and layout::print_annotation_line | |
70 | to simply request a colorization code for *every* character they print, | |
71 | via this class, and have the filtering be done for them here. */ | |
72 | ||
73 | class colorizer | |
74 | { | |
75 | public: | |
76 | colorizer (diagnostic_context *context, | |
cc015f3a | 77 | diagnostic_t diagnostic_kind); |
8a645150 DM |
78 | ~colorizer (); |
79 | ||
80 | void set_range (int range_idx) { set_state (range_idx); } | |
81 | void set_normal_text () { set_state (STATE_NORMAL_TEXT); } | |
d41e76cf DM |
82 | void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); } |
83 | void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); } | |
8a645150 DM |
84 | |
85 | private: | |
86 | void set_state (int state); | |
87 | void begin_state (int state); | |
88 | void finish_state (int state); | |
d41e76cf | 89 | const char *get_color_by_name (const char *); |
8a645150 DM |
90 | |
91 | private: | |
92 | static const int STATE_NORMAL_TEXT = -1; | |
d41e76cf DM |
93 | static const int STATE_FIXIT_INSERT = -2; |
94 | static const int STATE_FIXIT_DELETE = -3; | |
8a645150 DM |
95 | |
96 | diagnostic_context *m_context; | |
cc015f3a | 97 | diagnostic_t m_diagnostic_kind; |
8a645150 | 98 | int m_current_state; |
d41e76cf DM |
99 | const char *m_caret; |
100 | const char *m_range1; | |
101 | const char *m_range2; | |
102 | const char *m_fixit_insert; | |
103 | const char *m_fixit_delete; | |
104 | const char *m_stop_color; | |
8a645150 DM |
105 | }; |
106 | ||
107 | /* A point within a layout_range; similar to an expanded_location, | |
108 | but after filtering on file. */ | |
109 | ||
110 | class layout_point | |
111 | { | |
112 | public: | |
113 | layout_point (const expanded_location &exploc) | |
114 | : m_line (exploc.line), | |
115 | m_column (exploc.column) {} | |
116 | ||
117 | int m_line; | |
118 | int m_column; | |
119 | }; | |
120 | ||
121 | /* A class for use by "class layout" below: a filtered location_range. */ | |
122 | ||
123 | class layout_range | |
124 | { | |
125 | public: | |
40499f81 DM |
126 | layout_range (const expanded_location *start_exploc, |
127 | const expanded_location *finish_exploc, | |
128 | bool show_caret_p, | |
129 | const expanded_location *caret_exploc); | |
8a645150 DM |
130 | |
131 | bool contains_point (int row, int column) const; | |
132 | ||
133 | layout_point m_start; | |
134 | layout_point m_finish; | |
135 | bool m_show_caret_p; | |
136 | layout_point m_caret; | |
137 | }; | |
138 | ||
139 | /* A struct for use by layout::print_source_line for telling | |
140 | layout::print_annotation_line the extents of the source line that | |
141 | it printed, so that underlines can be clipped appropriately. */ | |
142 | ||
143 | struct line_bounds | |
144 | { | |
145 | int m_first_non_ws; | |
146 | int m_last_non_ws; | |
147 | }; | |
148 | ||
876217ae DM |
149 | /* A range of contiguous source lines within a layout (e.g. "lines 5-10" |
150 | or "line 23"). During the layout ctor, layout::calculate_line_spans | |
151 | splits the pertinent source lines into a list of disjoint line_span | |
152 | instances (e.g. lines 5-10, lines 15-20, line 23). */ | |
153 | ||
154 | struct line_span | |
155 | { | |
156 | line_span (linenum_type first_line, linenum_type last_line) | |
157 | : m_first_line (first_line), m_last_line (last_line) | |
158 | { | |
159 | gcc_assert (first_line <= last_line); | |
160 | } | |
161 | linenum_type get_first_line () const { return m_first_line; } | |
162 | linenum_type get_last_line () const { return m_last_line; } | |
163 | ||
164 | bool contains_line_p (linenum_type line) const | |
165 | { | |
166 | return line >= m_first_line && line <= m_last_line; | |
167 | } | |
168 | ||
169 | static int comparator (const void *p1, const void *p2) | |
170 | { | |
171 | const line_span *ls1 = (const line_span *)p1; | |
172 | const line_span *ls2 = (const line_span *)p2; | |
173 | int first_line_diff = (int)ls1->m_first_line - (int)ls2->m_first_line; | |
174 | if (first_line_diff) | |
175 | return first_line_diff; | |
176 | return (int)ls1->m_last_line - (int)ls2->m_last_line; | |
177 | } | |
178 | ||
179 | linenum_type m_first_line; | |
180 | linenum_type m_last_line; | |
181 | }; | |
182 | ||
8a645150 DM |
183 | /* A class to control the overall layout when printing a diagnostic. |
184 | ||
185 | The layout is determined within the constructor. | |
a87a86e1 DM |
186 | It is then printed by repeatedly calling the "print_source_line", |
187 | "print_annotation_line" and "print_any_fixits" methods. | |
8a645150 DM |
188 | |
189 | We assume we have disjoint ranges. */ | |
190 | ||
191 | class layout | |
192 | { | |
193 | public: | |
194 | layout (diagnostic_context *context, | |
cc015f3a DM |
195 | rich_location *richloc, |
196 | diagnostic_t diagnostic_kind); | |
8a645150 | 197 | |
876217ae DM |
198 | int get_num_line_spans () const { return m_line_spans.length (); } |
199 | const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; } | |
200 | ||
201 | bool print_heading_for_line_span_index_p (int line_span_idx) const; | |
202 | ||
203 | expanded_location get_expanded_location (const line_span *) const; | |
8a645150 DM |
204 | |
205 | bool print_source_line (int row, line_bounds *lbounds_out); | |
206 | void print_annotation_line (int row, const line_bounds lbounds); | |
2ffe0809 DM |
207 | bool annotation_line_showed_range_p (int line, int start_column, |
208 | int finish_column) const; | |
a87a86e1 | 209 | void print_any_fixits (int row, const rich_location *richloc); |
8a645150 | 210 | |
f3352cab DM |
211 | void show_ruler (int max_column) const; |
212 | ||
8a645150 | 213 | private: |
876217ae DM |
214 | void calculate_line_spans (); |
215 | ||
01e1dea3 DM |
216 | void print_newline (); |
217 | ||
8a645150 DM |
218 | bool |
219 | get_state_at_point (/* Inputs. */ | |
220 | int row, int column, | |
221 | int first_non_ws, int last_non_ws, | |
222 | /* Outputs. */ | |
223 | point_state *out_state); | |
224 | ||
225 | int | |
226 | get_x_bound_for_row (int row, int caret_column, | |
227 | int last_non_ws); | |
228 | ||
a87a86e1 DM |
229 | void |
230 | move_to_column (int *column, int dest_column); | |
231 | ||
8a645150 DM |
232 | private: |
233 | diagnostic_context *m_context; | |
234 | pretty_printer *m_pp; | |
235 | diagnostic_t m_diagnostic_kind; | |
236 | expanded_location m_exploc; | |
237 | colorizer m_colorizer; | |
238 | bool m_colorize_source_p; | |
239 | auto_vec <layout_range> m_layout_ranges; | |
876217ae | 240 | auto_vec <line_span> m_line_spans; |
8a645150 DM |
241 | int m_x_offset; |
242 | }; | |
243 | ||
244 | /* Implementation of "class colorizer". */ | |
245 | ||
246 | /* The constructor for "colorizer". Lookup and store color codes for the | |
247 | different kinds of things we might need to print. */ | |
248 | ||
249 | colorizer::colorizer (diagnostic_context *context, | |
cc015f3a | 250 | diagnostic_t diagnostic_kind) : |
8a645150 | 251 | m_context (context), |
cc015f3a | 252 | m_diagnostic_kind (diagnostic_kind), |
8a645150 DM |
253 | m_current_state (STATE_NORMAL_TEXT) |
254 | { | |
d41e76cf DM |
255 | m_range1 = get_color_by_name ("range1"); |
256 | m_range2 = get_color_by_name ("range2"); | |
257 | m_fixit_insert = get_color_by_name ("fixit-insert"); | |
258 | m_fixit_delete = get_color_by_name ("fixit-delete"); | |
259 | m_stop_color = colorize_stop (pp_show_color (context->printer)); | |
8a645150 DM |
260 | } |
261 | ||
262 | /* The destructor for "colorize". If colorization is on, print a code to | |
263 | turn it off. */ | |
264 | ||
265 | colorizer::~colorizer () | |
266 | { | |
267 | finish_state (m_current_state); | |
268 | } | |
269 | ||
270 | /* Update state, printing color codes if necessary if there's a state | |
271 | change. */ | |
272 | ||
273 | void | |
274 | colorizer::set_state (int new_state) | |
275 | { | |
276 | if (m_current_state != new_state) | |
57eb2d70 | 277 | { |
8a645150 DM |
278 | finish_state (m_current_state); |
279 | m_current_state = new_state; | |
280 | begin_state (new_state); | |
57eb2d70 | 281 | } |
57eb2d70 DM |
282 | } |
283 | ||
8a645150 DM |
284 | /* Turn on any colorization for STATE. */ |
285 | ||
57eb2d70 | 286 | void |
8a645150 | 287 | colorizer::begin_state (int state) |
57eb2d70 | 288 | { |
8a645150 DM |
289 | switch (state) |
290 | { | |
291 | case STATE_NORMAL_TEXT: | |
292 | break; | |
57eb2d70 | 293 | |
d41e76cf DM |
294 | case STATE_FIXIT_INSERT: |
295 | pp_string (m_context->printer, m_fixit_insert); | |
296 | break; | |
297 | ||
298 | case STATE_FIXIT_DELETE: | |
299 | pp_string (m_context->printer, m_fixit_delete); | |
300 | break; | |
301 | ||
8a645150 DM |
302 | case 0: |
303 | /* Make range 0 be the same color as the "kind" text | |
304 | (error vs warning vs note). */ | |
305 | pp_string | |
306 | (m_context->printer, | |
307 | colorize_start (pp_show_color (m_context->printer), | |
cc015f3a | 308 | diagnostic_get_color_for_kind (m_diagnostic_kind))); |
8a645150 | 309 | break; |
57eb2d70 | 310 | |
8a645150 | 311 | case 1: |
d41e76cf | 312 | pp_string (m_context->printer, m_range1); |
8a645150 | 313 | break; |
57eb2d70 | 314 | |
8a645150 | 315 | case 2: |
d41e76cf | 316 | pp_string (m_context->printer, m_range2); |
8a645150 DM |
317 | break; |
318 | ||
319 | default: | |
320 | /* We don't expect more than 3 ranges per diagnostic. */ | |
321 | gcc_unreachable (); | |
322 | break; | |
323 | } | |
57eb2d70 DM |
324 | } |
325 | ||
8a645150 DM |
326 | /* Turn off any colorization for STATE. */ |
327 | ||
57eb2d70 | 328 | void |
8a645150 DM |
329 | colorizer::finish_state (int state) |
330 | { | |
d41e76cf DM |
331 | if (state != STATE_NORMAL_TEXT) |
332 | pp_string (m_context->printer, m_stop_color); | |
333 | } | |
8a645150 | 334 | |
d41e76cf DM |
335 | /* Get the color code for NAME (or the empty string if |
336 | colorization is disabled). */ | |
8a645150 | 337 | |
d41e76cf DM |
338 | const char * |
339 | colorizer::get_color_by_name (const char *name) | |
340 | { | |
341 | return colorize_start (pp_show_color (m_context->printer), name); | |
8a645150 DM |
342 | } |
343 | ||
344 | /* Implementation of class layout_range. */ | |
345 | ||
346 | /* The constructor for class layout_range. | |
347 | Initialize various layout_point fields from expanded_location | |
348 | equivalents; we've already filtered on file. */ | |
349 | ||
40499f81 DM |
350 | layout_range::layout_range (const expanded_location *start_exploc, |
351 | const expanded_location *finish_exploc, | |
352 | bool show_caret_p, | |
353 | const expanded_location *caret_exploc) | |
354 | : m_start (*start_exploc), | |
355 | m_finish (*finish_exploc), | |
356 | m_show_caret_p (show_caret_p), | |
357 | m_caret (*caret_exploc) | |
8a645150 DM |
358 | { |
359 | } | |
360 | ||
361 | /* Is (column, row) within the given range? | |
362 | We've already filtered on the file. | |
363 | ||
364 | Ranges are closed (both limits are within the range). | |
365 | ||
366 | Example A: a single-line range: | |
367 | start: (col=22, line=2) | |
368 | finish: (col=38, line=2) | |
369 | ||
370 | |00000011111111112222222222333333333344444444444 | |
371 | |34567890123456789012345678901234567890123456789 | |
372 | --+----------------------------------------------- | |
373 | 01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | |
374 | 02|bbbbbbbbbbbbbbbbbbbSwwwwwwwwwwwwwwwFaaaaaaaaaaa | |
375 | 03|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | |
376 | ||
377 | Example B: a multiline range with | |
378 | start: (col=14, line=3) | |
379 | finish: (col=08, line=5) | |
380 | ||
381 | |00000011111111112222222222333333333344444444444 | |
382 | |34567890123456789012345678901234567890123456789 | |
383 | --+----------------------------------------------- | |
384 | 01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | |
385 | 02|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | |
386 | 03|bbbbbbbbbbbSwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww | |
387 | 04|wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww | |
388 | 05|wwwwwFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | |
389 | 06|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | |
390 | --+----------------------------------------------- | |
391 | ||
392 | Legend: | |
393 | - 'b' indicates a point *before* the range | |
394 | - 'S' indicates the start of the range | |
395 | - 'w' indicates a point within the range | |
396 | - 'F' indicates the finish of the range (which is | |
397 | within it). | |
398 | - 'a' indicates a subsequent point *after* the range. */ | |
399 | ||
400 | bool | |
401 | layout_range::contains_point (int row, int column) const | |
402 | { | |
403 | gcc_assert (m_start.m_line <= m_finish.m_line); | |
404 | /* ...but the equivalent isn't true for the columns; | |
405 | consider example B in the comment above. */ | |
406 | ||
407 | if (row < m_start.m_line) | |
408 | /* Points before the first line of the range are | |
409 | outside it (corresponding to line 01 in example A | |
410 | and lines 01 and 02 in example B above). */ | |
411 | return false; | |
412 | ||
413 | if (row == m_start.m_line) | |
414 | /* On same line as start of range (corresponding | |
415 | to line 02 in example A and line 03 in example B). */ | |
416 | { | |
417 | if (column < m_start.m_column) | |
418 | /* Points on the starting line of the range, but | |
419 | before the column in which it begins. */ | |
420 | return false; | |
421 | ||
422 | if (row < m_finish.m_line) | |
423 | /* This is a multiline range; the point | |
424 | is within it (corresponds to line 03 in example B | |
425 | from column 14 onwards) */ | |
426 | return true; | |
427 | else | |
428 | { | |
429 | /* This is a single-line range. */ | |
430 | gcc_assert (row == m_finish.m_line); | |
431 | return column <= m_finish.m_column; | |
432 | } | |
433 | } | |
434 | ||
435 | /* The point is in a line beyond that containing the | |
436 | start of the range: lines 03 onwards in example A, | |
437 | and lines 04 onwards in example B. */ | |
438 | gcc_assert (row > m_start.m_line); | |
439 | ||
440 | if (row > m_finish.m_line) | |
441 | /* The point is beyond the final line of the range | |
442 | (lines 03 onwards in example A, and lines 06 onwards | |
443 | in example B). */ | |
444 | return false; | |
445 | ||
446 | if (row < m_finish.m_line) | |
447 | { | |
448 | /* The point is in a line that's fully within a multiline | |
449 | range (e.g. line 04 in example B). */ | |
450 | gcc_assert (m_start.m_line < m_finish.m_line); | |
451 | return true; | |
452 | } | |
453 | ||
454 | gcc_assert (row == m_finish.m_line); | |
455 | ||
456 | return column <= m_finish.m_column; | |
457 | } | |
458 | ||
d9b950dd DM |
459 | #if CHECKING_P |
460 | ||
461 | /* A helper function for testing layout_range::contains_point. */ | |
462 | ||
463 | static layout_range | |
464 | make_range (int start_line, int start_col, int end_line, int end_col) | |
465 | { | |
466 | const expanded_location start_exploc | |
467 | = {"test.c", start_line, start_col, NULL, false}; | |
468 | const expanded_location finish_exploc | |
469 | = {"test.c", end_line, end_col, NULL, false}; | |
470 | return layout_range (&start_exploc, &finish_exploc, false, | |
471 | &start_exploc); | |
472 | } | |
473 | ||
474 | /* Selftests for layout_range::contains_point. */ | |
475 | ||
476 | /* Selftest for layout_range::contains_point where the layout_range | |
477 | is a range with start==end i.e. a single point. */ | |
478 | ||
479 | static void | |
480 | test_range_contains_point_for_single_point () | |
481 | { | |
482 | layout_range point = make_range (7, 10, 7, 10); | |
483 | ||
484 | /* Before the line. */ | |
485 | ASSERT_FALSE (point.contains_point (6, 1)); | |
486 | ||
487 | /* On the line, but before start. */ | |
488 | ASSERT_FALSE (point.contains_point (7, 9)); | |
489 | ||
490 | /* At the point. */ | |
491 | ASSERT_TRUE (point.contains_point (7, 10)); | |
492 | ||
493 | /* On the line, after the point. */ | |
494 | ASSERT_FALSE (point.contains_point (7, 11)); | |
495 | ||
496 | /* After the line. */ | |
497 | ASSERT_FALSE (point.contains_point (8, 1)); | |
498 | } | |
499 | ||
500 | /* Selftest for layout_range::contains_point where the layout_range | |
501 | is the single-line range shown as "Example A" above. */ | |
502 | ||
503 | static void | |
504 | test_range_contains_point_for_single_line () | |
505 | { | |
506 | layout_range example_a = make_range (2, 22, 2, 38); | |
507 | ||
508 | /* Before the line. */ | |
509 | ASSERT_FALSE (example_a.contains_point (1, 1)); | |
510 | ||
511 | /* On the line, but before start. */ | |
512 | ASSERT_FALSE (example_a.contains_point (2, 21)); | |
513 | ||
514 | /* On the line, at the start. */ | |
515 | ASSERT_TRUE (example_a.contains_point (2, 22)); | |
516 | ||
517 | /* On the line, within the range. */ | |
518 | ASSERT_TRUE (example_a.contains_point (2, 23)); | |
519 | ||
520 | /* On the line, at the end. */ | |
521 | ASSERT_TRUE (example_a.contains_point (2, 38)); | |
522 | ||
523 | /* On the line, after the end. */ | |
524 | ASSERT_FALSE (example_a.contains_point (2, 39)); | |
525 | ||
526 | /* After the line. */ | |
527 | ASSERT_FALSE (example_a.contains_point (2, 39)); | |
528 | } | |
529 | ||
530 | /* Selftest for layout_range::contains_point where the layout_range | |
531 | is the multi-line range shown as "Example B" above. */ | |
532 | ||
533 | static void | |
534 | test_range_contains_point_for_multiple_lines () | |
535 | { | |
536 | layout_range example_b = make_range (3, 14, 5, 8); | |
537 | ||
538 | /* Before first line. */ | |
539 | ASSERT_FALSE (example_b.contains_point (1, 1)); | |
540 | ||
541 | /* On the first line, but before start. */ | |
542 | ASSERT_FALSE (example_b.contains_point (3, 13)); | |
543 | ||
544 | /* At the start. */ | |
545 | ASSERT_TRUE (example_b.contains_point (3, 14)); | |
546 | ||
547 | /* On the first line, within the range. */ | |
548 | ASSERT_TRUE (example_b.contains_point (3, 15)); | |
549 | ||
550 | /* On an interior line. | |
551 | The column number should not matter; try various boundary | |
552 | values. */ | |
553 | ASSERT_TRUE (example_b.contains_point (4, 1)); | |
554 | ASSERT_TRUE (example_b.contains_point (4, 7)); | |
555 | ASSERT_TRUE (example_b.contains_point (4, 8)); | |
556 | ASSERT_TRUE (example_b.contains_point (4, 9)); | |
557 | ASSERT_TRUE (example_b.contains_point (4, 13)); | |
558 | ASSERT_TRUE (example_b.contains_point (4, 14)); | |
559 | ASSERT_TRUE (example_b.contains_point (4, 15)); | |
560 | ||
561 | /* On the final line, before the end. */ | |
562 | ASSERT_TRUE (example_b.contains_point (5, 7)); | |
563 | ||
564 | /* On the final line, at the end. */ | |
565 | ASSERT_TRUE (example_b.contains_point (5, 8)); | |
566 | ||
567 | /* On the final line, after the end. */ | |
568 | ASSERT_FALSE (example_b.contains_point (5, 9)); | |
569 | ||
570 | /* After the line. */ | |
571 | ASSERT_FALSE (example_b.contains_point (6, 1)); | |
572 | } | |
573 | ||
574 | #endif /* #if CHECKING_P */ | |
575 | ||
8a645150 DM |
576 | /* Given a source line LINE of length LINE_WIDTH, determine the width |
577 | without any trailing whitespace. */ | |
578 | ||
579 | static int | |
580 | get_line_width_without_trailing_whitespace (const char *line, int line_width) | |
581 | { | |
582 | int result = line_width; | |
583 | while (result > 0) | |
584 | { | |
585 | char ch = line[result - 1]; | |
586 | if (ch == ' ' || ch == '\t') | |
587 | result--; | |
588 | else | |
589 | break; | |
590 | } | |
591 | gcc_assert (result >= 0); | |
592 | gcc_assert (result <= line_width); | |
593 | gcc_assert (result == 0 || | |
594 | (line[result - 1] != ' ' | |
595 | && line[result -1] != '\t')); | |
596 | return result; | |
597 | } | |
598 | ||
d9b950dd DM |
599 | #if CHECKING_P |
600 | ||
601 | /* A helper function for testing get_line_width_without_trailing_whitespace. */ | |
602 | ||
603 | static void | |
604 | assert_eq (const char *line, int expected_width) | |
605 | { | |
606 | int actual_value | |
607 | = get_line_width_without_trailing_whitespace (line, strlen (line)); | |
608 | ASSERT_EQ (actual_value, expected_width); | |
609 | } | |
610 | ||
611 | /* Verify that get_line_width_without_trailing_whitespace is sane for | |
612 | various inputs. It is not required to handle newlines. */ | |
613 | ||
614 | static void | |
615 | test_get_line_width_without_trailing_whitespace () | |
616 | { | |
617 | assert_eq ("", 0); | |
618 | assert_eq (" ", 0); | |
619 | assert_eq ("\t", 0); | |
620 | assert_eq ("hello world", 11); | |
621 | assert_eq ("hello world ", 11); | |
622 | assert_eq ("hello world \t\t ", 11); | |
623 | } | |
624 | ||
625 | #endif /* #if CHECKING_P */ | |
626 | ||
b4f3232d DM |
627 | /* Helper function for layout's ctor, for sanitizing locations relative |
628 | to the primary location within a diagnostic. | |
629 | ||
630 | Compare LOC_A and LOC_B to see if it makes sense to print underlines | |
631 | connecting their expanded locations. Doing so is only guaranteed to | |
632 | make sense if the locations share the same macro expansion "history" | |
633 | i.e. they can be traced through the same macro expansions, eventually | |
634 | reaching an ordinary map. | |
635 | ||
636 | This may be too strong a condition, but it effectively sanitizes | |
637 | PR c++/70105, which has an example of printing an expression where the | |
638 | final location of the expression is in a different macro, which | |
639 | erroneously was leading to hundreds of lines of irrelevant source | |
640 | being printed. */ | |
641 | ||
642 | static bool | |
643 | compatible_locations_p (location_t loc_a, location_t loc_b) | |
644 | { | |
645 | if (IS_ADHOC_LOC (loc_a)) | |
646 | loc_a = get_location_from_adhoc_loc (line_table, loc_a); | |
647 | if (IS_ADHOC_LOC (loc_b)) | |
648 | loc_b = get_location_from_adhoc_loc (line_table, loc_b); | |
649 | ||
ded60913 DM |
650 | /* If either location is one of the special locations outside of a |
651 | linemap, they are only compatible if they are equal. */ | |
652 | if (loc_a < RESERVED_LOCATION_COUNT | |
653 | || loc_b < RESERVED_LOCATION_COUNT) | |
654 | return loc_a == loc_b; | |
655 | ||
b4f3232d DM |
656 | const line_map *map_a = linemap_lookup (line_table, loc_a); |
657 | linemap_assert (map_a); | |
658 | ||
659 | const line_map *map_b = linemap_lookup (line_table, loc_b); | |
660 | linemap_assert (map_b); | |
661 | ||
662 | /* Are they within the same map? */ | |
663 | if (map_a == map_b) | |
664 | { | |
665 | /* Are both within the same macro expansion? */ | |
666 | if (linemap_macro_expansion_map_p (map_a)) | |
667 | { | |
668 | /* Expand each location towards the spelling location, and | |
669 | recurse. */ | |
670 | const line_map_macro *macro_map = linemap_check_macro (map_a); | |
671 | source_location loc_a_toward_spelling | |
672 | = linemap_macro_map_loc_unwind_toward_spelling (line_table, | |
673 | macro_map, | |
674 | loc_a); | |
675 | source_location loc_b_toward_spelling | |
676 | = linemap_macro_map_loc_unwind_toward_spelling (line_table, | |
677 | macro_map, | |
678 | loc_b); | |
679 | return compatible_locations_p (loc_a_toward_spelling, | |
680 | loc_b_toward_spelling); | |
681 | } | |
682 | ||
683 | /* Otherwise they are within the same ordinary map. */ | |
684 | return true; | |
685 | } | |
686 | else | |
687 | { | |
688 | /* Within different maps. */ | |
689 | ||
690 | /* If either is within a macro expansion, they are incompatible. */ | |
691 | if (linemap_macro_expansion_map_p (map_a) | |
692 | || linemap_macro_expansion_map_p (map_b)) | |
693 | return false; | |
694 | ||
695 | /* Within two different ordinary maps; they are compatible iff they | |
696 | are in the same file. */ | |
697 | const line_map_ordinary *ord_map_a = linemap_check_ordinary (map_a); | |
698 | const line_map_ordinary *ord_map_b = linemap_check_ordinary (map_b); | |
699 | return ord_map_a->to_file == ord_map_b->to_file; | |
700 | } | |
701 | } | |
702 | ||
8a645150 DM |
703 | /* Implementation of class layout. */ |
704 | ||
705 | /* Constructor for class layout. | |
706 | ||
707 | Filter the ranges from the rich_location to those that we can | |
708 | sanely print, populating m_layout_ranges. | |
876217ae DM |
709 | Determine the range of lines that we will print, splitting them |
710 | up into an ordered list of disjoint spans of contiguous line numbers. | |
8a645150 DM |
711 | Determine m_x_offset, to ensure that the primary caret |
712 | will fit within the max_width provided by the diagnostic_context. */ | |
713 | ||
714 | layout::layout (diagnostic_context * context, | |
cc015f3a DM |
715 | rich_location *richloc, |
716 | diagnostic_t diagnostic_kind) | |
8a645150 DM |
717 | : m_context (context), |
718 | m_pp (context->printer), | |
cc015f3a DM |
719 | m_diagnostic_kind (diagnostic_kind), |
720 | m_exploc (richloc->get_expanded_location (0)), | |
721 | m_colorizer (context, diagnostic_kind), | |
8a645150 DM |
722 | m_colorize_source_p (context->colorize_source_p), |
723 | m_layout_ranges (rich_location::MAX_RANGES), | |
876217ae | 724 | m_line_spans (1 + rich_location::MAX_RANGES), |
8a645150 DM |
725 | m_x_offset (0) |
726 | { | |
b4f3232d DM |
727 | source_location primary_loc = richloc->get_range (0)->m_loc; |
728 | ||
8a645150 DM |
729 | for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++) |
730 | { | |
731 | /* This diagnostic printer can only cope with "sufficiently sane" ranges. | |
732 | Ignore any ranges that are awkward to handle. */ | |
070856cc | 733 | const location_range *loc_range = richloc->get_range (idx); |
8a645150 | 734 | |
40499f81 DM |
735 | /* Split the "range" into caret and range information. */ |
736 | source_range src_range = get_range_from_loc (line_table, loc_range->m_loc); | |
737 | ||
738 | /* Expand the various locations. */ | |
739 | expanded_location start | |
740 | = linemap_client_expand_location_to_spelling_point (src_range.m_start); | |
741 | expanded_location finish | |
742 | = linemap_client_expand_location_to_spelling_point (src_range.m_finish); | |
743 | expanded_location caret | |
744 | = linemap_client_expand_location_to_spelling_point (loc_range->m_loc); | |
745 | ||
8a645150 DM |
746 | /* If any part of the range isn't in the same file as the primary |
747 | location of this diagnostic, ignore the range. */ | |
40499f81 | 748 | if (start.file != m_exploc.file) |
8a645150 | 749 | continue; |
40499f81 | 750 | if (finish.file != m_exploc.file) |
8a645150 DM |
751 | continue; |
752 | if (loc_range->m_show_caret_p) | |
40499f81 | 753 | if (caret.file != m_exploc.file) |
8a645150 DM |
754 | continue; |
755 | ||
b4f3232d DM |
756 | /* Sanitize the caret location for non-primary ranges. */ |
757 | if (m_layout_ranges.length () > 0) | |
758 | if (loc_range->m_show_caret_p) | |
759 | if (!compatible_locations_p (loc_range->m_loc, primary_loc)) | |
760 | /* Discard any non-primary ranges that can't be printed | |
761 | sanely relative to the primary location. */ | |
762 | continue; | |
763 | ||
070856cc DM |
764 | /* Everything is now known to be in the correct source file, |
765 | but it may require further sanitization. */ | |
40499f81 | 766 | layout_range ri (&start, &finish, loc_range->m_show_caret_p, &caret); |
070856cc DM |
767 | |
768 | /* If we have a range that finishes before it starts (perhaps | |
769 | from something built via macro expansion), printing the | |
770 | range is likely to be nonsensical. Also, attempting to do so | |
b4f3232d DM |
771 | breaks assumptions within the printing code (PR c/68473). |
772 | Similarly, don't attempt to print ranges if one or both ends | |
773 | of the range aren't sane to print relative to the | |
774 | primary location (PR c++/70105). */ | |
775 | if (start.line > finish.line | |
776 | || !compatible_locations_p (src_range.m_start, primary_loc) | |
777 | || !compatible_locations_p (src_range.m_finish, primary_loc)) | |
070856cc DM |
778 | { |
779 | /* Is this the primary location? */ | |
780 | if (m_layout_ranges.length () == 0) | |
781 | { | |
782 | /* We want to print the caret for the primary location, but | |
783 | we must sanitize away m_start and m_finish. */ | |
784 | ri.m_start = ri.m_caret; | |
785 | ri.m_finish = ri.m_caret; | |
786 | } | |
787 | else | |
788 | /* This is a non-primary range; ignore it. */ | |
789 | continue; | |
790 | } | |
791 | ||
8a645150 DM |
792 | /* Passed all the tests; add the range to m_layout_ranges so that |
793 | it will be printed. */ | |
8a645150 | 794 | m_layout_ranges.safe_push (ri); |
8a645150 DM |
795 | } |
796 | ||
876217ae DM |
797 | /* Populate m_line_spans. */ |
798 | calculate_line_spans (); | |
799 | ||
8a645150 DM |
800 | /* Adjust m_x_offset. |
801 | Center the primary caret to fit in max_width; all columns | |
802 | will be adjusted accordingly. */ | |
803 | int max_width = m_context->caret_max_width; | |
57eb2d70 | 804 | int line_width; |
8a645150 | 805 | const char *line = location_get_source_line (m_exploc.file, m_exploc.line, |
57eb2d70 | 806 | &line_width); |
8a645150 DM |
807 | if (line && m_exploc.column <= line_width) |
808 | { | |
809 | int right_margin = CARET_LINE_MARGIN; | |
810 | int column = m_exploc.column; | |
811 | right_margin = MIN (line_width - column, right_margin); | |
812 | right_margin = max_width - right_margin; | |
813 | if (line_width >= max_width && column > right_margin) | |
814 | m_x_offset = column - right_margin; | |
815 | gcc_assert (m_x_offset >= 0); | |
816 | } | |
f3352cab DM |
817 | |
818 | if (context->show_ruler_p) | |
819 | show_ruler (m_x_offset + max_width); | |
8a645150 | 820 | } |
57eb2d70 | 821 | |
876217ae DM |
822 | /* Return true iff we should print a heading when starting the |
823 | line span with the given index. */ | |
824 | ||
825 | bool | |
826 | layout::print_heading_for_line_span_index_p (int line_span_idx) const | |
827 | { | |
828 | /* We print a heading for every change of line span, hence for every | |
829 | line span after the initial one. */ | |
830 | if (line_span_idx > 0) | |
831 | return true; | |
832 | ||
833 | /* We also do it for the initial span if the primary location of the | |
834 | diagnostic is in a different span. */ | |
835 | if (m_exploc.line > (int)get_line_span (0)->m_last_line) | |
836 | return true; | |
837 | ||
838 | return false; | |
839 | } | |
840 | ||
841 | /* Get an expanded_location for the first location of interest within | |
842 | the given line_span. | |
843 | Used when printing a heading to indicate a new line span. */ | |
844 | ||
845 | expanded_location | |
846 | layout::get_expanded_location (const line_span *line_span) const | |
847 | { | |
848 | /* Whenever possible, use the caret location. */ | |
849 | if (line_span->contains_line_p (m_exploc.line)) | |
850 | return m_exploc; | |
851 | ||
852 | /* Otherwise, use the start of the first range that's present | |
853 | within the line_span. */ | |
854 | for (unsigned int i = 0; i < m_layout_ranges.length (); i++) | |
855 | { | |
856 | const layout_range *lr = &m_layout_ranges[i]; | |
857 | if (line_span->contains_line_p (lr->m_start.m_line)) | |
858 | { | |
859 | expanded_location exploc = m_exploc; | |
860 | exploc.line = lr->m_start.m_line; | |
861 | exploc.column = lr->m_start.m_column; | |
862 | return exploc; | |
863 | } | |
864 | } | |
865 | ||
866 | /* It should not be possible to have a line span that didn't | |
867 | contain any of the layout_range instances. */ | |
868 | gcc_unreachable (); | |
869 | return m_exploc; | |
870 | } | |
871 | ||
872 | /* We want to print the pertinent source code at a diagnostic. The | |
873 | rich_location can contain multiple locations. This will have been | |
874 | filtered into m_exploc (the caret for the primary location) and | |
875 | m_layout_ranges, for those ranges within the same source file. | |
876 | ||
877 | We will print a subset of the lines within the source file in question, | |
878 | as a collection of "spans" of lines. | |
879 | ||
880 | This function populates m_line_spans with an ordered, disjoint list of | |
881 | the line spans of interest. | |
882 | ||
883 | For example, if the primary caret location is on line 7, with ranges | |
884 | covering lines 5-6 and lines 9-12: | |
885 | ||
886 | 004 | |
887 | 005 |RANGE 0 | |
888 | 006 |RANGE 0 | |
889 | 007 |PRIMARY CARET | |
890 | 008 | |
891 | 009 |RANGE 1 | |
892 | 010 |RANGE 1 | |
893 | 011 |RANGE 1 | |
894 | 012 |RANGE 1 | |
895 | 013 | |
896 | ||
897 | then we want two spans: lines 5-7 and lines 9-12. */ | |
898 | ||
899 | void | |
900 | layout::calculate_line_spans () | |
901 | { | |
902 | /* This should only be called once, by the ctor. */ | |
903 | gcc_assert (m_line_spans.length () == 0); | |
904 | ||
905 | /* Populate tmp_spans with individual spans, for each of | |
906 | m_exploc, and for m_layout_ranges. */ | |
907 | auto_vec<line_span> tmp_spans (1 + rich_location::MAX_RANGES); | |
908 | tmp_spans.safe_push (line_span (m_exploc.line, m_exploc.line)); | |
909 | for (unsigned int i = 0; i < m_layout_ranges.length (); i++) | |
910 | { | |
911 | const layout_range *lr = &m_layout_ranges[i]; | |
912 | gcc_assert (lr->m_start.m_line <= lr->m_finish.m_line); | |
913 | tmp_spans.safe_push (line_span (lr->m_start.m_line, | |
914 | lr->m_finish.m_line)); | |
915 | } | |
916 | ||
917 | /* Sort them. */ | |
918 | tmp_spans.qsort(line_span::comparator); | |
919 | ||
920 | /* Now iterate through tmp_spans, copying into m_line_spans, and | |
921 | combining where possible. */ | |
922 | gcc_assert (tmp_spans.length () > 0); | |
923 | m_line_spans.safe_push (tmp_spans[0]); | |
924 | for (unsigned int i = 1; i < tmp_spans.length (); i++) | |
925 | { | |
926 | line_span *current = &m_line_spans[m_line_spans.length () - 1]; | |
927 | const line_span *next = &tmp_spans[i]; | |
928 | gcc_assert (next->m_first_line >= current->m_first_line); | |
929 | if (next->m_first_line <= current->m_last_line + 1) | |
930 | { | |
931 | /* We can merge them. */ | |
932 | if (next->m_last_line > current->m_last_line) | |
933 | current->m_last_line = next->m_last_line; | |
934 | } | |
935 | else | |
936 | { | |
937 | /* No merger possible. */ | |
938 | m_line_spans.safe_push (*next); | |
939 | } | |
940 | } | |
941 | ||
942 | /* Verify the result, in m_line_spans. */ | |
943 | gcc_assert (m_line_spans.length () > 0); | |
944 | for (unsigned int i = 1; i < m_line_spans.length (); i++) | |
945 | { | |
946 | const line_span *prev = &m_line_spans[i - 1]; | |
947 | const line_span *next = &m_line_spans[i]; | |
948 | /* The individual spans must be sane. */ | |
949 | gcc_assert (prev->m_first_line <= prev->m_last_line); | |
950 | gcc_assert (next->m_first_line <= next->m_last_line); | |
951 | /* The spans must be ordered. */ | |
952 | gcc_assert (prev->m_first_line < next->m_first_line); | |
953 | /* There must be a gap of at least one line between separate spans. */ | |
954 | gcc_assert ((prev->m_last_line + 1) < next->m_first_line); | |
955 | } | |
956 | } | |
957 | ||
8a645150 DM |
958 | /* Attempt to print line ROW of source code, potentially colorized at any |
959 | ranges. | |
960 | Return true if the line was printed, populating *LBOUNDS_OUT. | |
961 | Return false if the source line could not be read, leaving *LBOUNDS_OUT | |
962 | untouched. */ | |
963 | ||
964 | bool | |
965 | layout::print_source_line (int row, line_bounds *lbounds_out) | |
966 | { | |
967 | int line_width; | |
968 | const char *line = location_get_source_line (m_exploc.file, row, | |
969 | &line_width); | |
970 | if (!line) | |
971 | return false; | |
972 | ||
8a645150 DM |
973 | m_colorizer.set_normal_text (); |
974 | ||
975 | /* We will stop printing the source line at any trailing | |
976 | whitespace. */ | |
977 | line_width = get_line_width_without_trailing_whitespace (line, | |
978 | line_width); | |
83eb5a03 | 979 | line += m_x_offset; |
8a645150 DM |
980 | |
981 | pp_space (m_pp); | |
982 | int first_non_ws = INT_MAX; | |
983 | int last_non_ws = 0; | |
984 | int column; | |
985 | for (column = 1 + m_x_offset; column <= line_width; column++) | |
57eb2d70 | 986 | { |
8a645150 DM |
987 | /* Assuming colorization is enabled for the caret and underline |
988 | characters, we may also colorize the associated characters | |
989 | within the source line. | |
990 | ||
991 | For frontends that generate range information, we color the | |
992 | associated characters in the source line the same as the | |
993 | carets and underlines in the annotation line, to make it easier | |
994 | for the reader to see the pertinent code. | |
995 | ||
996 | For frontends that only generate carets, we don't colorize the | |
997 | characters above them, since this would look strange (e.g. | |
998 | colorizing just the first character in a token). */ | |
999 | if (m_colorize_source_p) | |
1000 | { | |
1001 | bool in_range_p; | |
1002 | point_state state; | |
1003 | in_range_p = get_state_at_point (row, column, | |
1004 | 0, INT_MAX, | |
1005 | &state); | |
1006 | if (in_range_p) | |
1007 | m_colorizer.set_range (state.range_idx); | |
1008 | else | |
1009 | m_colorizer.set_normal_text (); | |
1010 | } | |
57eb2d70 DM |
1011 | char c = *line == '\t' ? ' ' : *line; |
1012 | if (c == '\0') | |
1013 | c = ' '; | |
8a645150 DM |
1014 | if (c != ' ') |
1015 | { | |
1016 | last_non_ws = column; | |
1017 | if (first_non_ws == INT_MAX) | |
1018 | first_non_ws = column; | |
1019 | } | |
1020 | pp_character (m_pp, c); | |
57eb2d70 DM |
1021 | line++; |
1022 | } | |
01e1dea3 | 1023 | print_newline (); |
8a645150 DM |
1024 | |
1025 | lbounds_out->m_first_non_ws = first_non_ws; | |
1026 | lbounds_out->m_last_non_ws = last_non_ws; | |
1027 | return true; | |
1028 | } | |
1029 | ||
1030 | /* Print a line consisting of the caret/underlines for the given | |
1031 | source line. */ | |
57eb2d70 | 1032 | |
8a645150 DM |
1033 | void |
1034 | layout::print_annotation_line (int row, const line_bounds lbounds) | |
1035 | { | |
1036 | int x_bound = get_x_bound_for_row (row, m_exploc.column, | |
1037 | lbounds.m_last_non_ws); | |
1038 | ||
1039 | pp_space (m_pp); | |
1040 | for (int column = 1 + m_x_offset; column < x_bound; column++) | |
1041 | { | |
1042 | bool in_range_p; | |
1043 | point_state state; | |
1044 | in_range_p = get_state_at_point (row, column, | |
1045 | lbounds.m_first_non_ws, | |
1046 | lbounds.m_last_non_ws, | |
1047 | &state); | |
1048 | if (in_range_p) | |
1049 | { | |
1050 | /* Within a range. Draw either the caret or an underline. */ | |
1051 | m_colorizer.set_range (state.range_idx); | |
1052 | if (state.draw_caret_p) | |
1053 | /* Draw the caret. */ | |
1054 | pp_character (m_pp, m_context->caret_chars[state.range_idx]); | |
1055 | else | |
1056 | pp_character (m_pp, '~'); | |
1057 | } | |
1058 | else | |
1059 | { | |
1060 | /* Not in a range. */ | |
1061 | m_colorizer.set_normal_text (); | |
1062 | pp_character (m_pp, ' '); | |
1063 | } | |
1064 | } | |
01e1dea3 | 1065 | print_newline (); |
8a645150 DM |
1066 | } |
1067 | ||
2ffe0809 DM |
1068 | /* Subroutine of layout::print_any_fixits. |
1069 | ||
1070 | Determine if the annotation line printed for LINE contained | |
1071 | the exact range from START_COLUMN to FINISH_COLUMN. */ | |
1072 | ||
1073 | bool | |
1074 | layout::annotation_line_showed_range_p (int line, int start_column, | |
1075 | int finish_column) const | |
1076 | { | |
1077 | layout_range *range; | |
1078 | int i; | |
1079 | FOR_EACH_VEC_ELT (m_layout_ranges, i, range) | |
1080 | if (range->m_start.m_line == line | |
1081 | && range->m_start.m_column == start_column | |
1082 | && range->m_finish.m_line == line | |
1083 | && range->m_finish.m_column == finish_column) | |
1084 | return true; | |
1085 | return false; | |
1086 | } | |
1087 | ||
a87a86e1 DM |
1088 | /* If there are any fixit hints on source line ROW within RICHLOC, print them. |
1089 | They are printed in order, attempting to combine them onto lines, but | |
1090 | starting new lines if necessary. */ | |
1091 | ||
1092 | void | |
1093 | layout::print_any_fixits (int row, const rich_location *richloc) | |
1094 | { | |
1095 | int column = 0; | |
1096 | for (unsigned int i = 0; i < richloc->get_num_fixit_hints (); i++) | |
1097 | { | |
1098 | fixit_hint *hint = richloc->get_fixit_hint (i); | |
1099 | if (hint->affects_line_p (m_exploc.file, row)) | |
1100 | { | |
1101 | /* For now we assume each fixit hint can only touch one line. */ | |
1102 | switch (hint->get_kind ()) | |
1103 | { | |
1104 | case fixit_hint::INSERT: | |
1105 | { | |
1106 | fixit_insert *insert = static_cast <fixit_insert *> (hint); | |
1107 | /* This assumes the insertion just affects one line. */ | |
1108 | int start_column | |
1109 | = LOCATION_COLUMN (insert->get_location ()); | |
1110 | move_to_column (&column, start_column); | |
d41e76cf | 1111 | m_colorizer.set_fixit_insert (); |
a87a86e1 DM |
1112 | pp_string (m_pp, insert->get_string ()); |
1113 | m_colorizer.set_normal_text (); | |
1114 | column += insert->get_length (); | |
1115 | } | |
1116 | break; | |
1117 | ||
2ffe0809 | 1118 | case fixit_hint::REPLACE: |
a87a86e1 | 1119 | { |
2ffe0809 DM |
1120 | fixit_replace *replace = static_cast <fixit_replace *> (hint); |
1121 | source_range src_range = replace->get_range (); | |
1122 | int line = LOCATION_LINE (src_range.m_start); | |
a87a86e1 DM |
1123 | int start_column = LOCATION_COLUMN (src_range.m_start); |
1124 | int finish_column = LOCATION_COLUMN (src_range.m_finish); | |
2ffe0809 DM |
1125 | |
1126 | /* If the range of the replacement wasn't printed in the | |
1127 | annotation line, then print an extra underline to | |
1128 | indicate exactly what is being replaced. | |
1129 | Always show it for removals. */ | |
1130 | if (!annotation_line_showed_range_p (line, start_column, | |
1131 | finish_column) | |
1132 | || replace->get_length () == 0) | |
a87a86e1 | 1133 | { |
2ffe0809 | 1134 | move_to_column (&column, start_column); |
d41e76cf | 1135 | m_colorizer.set_fixit_delete (); |
2ffe0809 DM |
1136 | for (; column <= finish_column; column++) |
1137 | pp_character (m_pp, '-'); | |
a87a86e1 DM |
1138 | m_colorizer.set_normal_text (); |
1139 | } | |
2ffe0809 DM |
1140 | /* Print the replacement text. REPLACE also covers |
1141 | removals, so only do this extra work (potentially starting | |
1142 | a new line) if we have actual replacement text. */ | |
1143 | if (replace->get_length () > 0) | |
1144 | { | |
1145 | move_to_column (&column, start_column); | |
d41e76cf | 1146 | m_colorizer.set_fixit_insert (); |
2ffe0809 DM |
1147 | pp_string (m_pp, replace->get_string ()); |
1148 | m_colorizer.set_normal_text (); | |
1149 | column += replace->get_length (); | |
1150 | } | |
a87a86e1 DM |
1151 | } |
1152 | break; | |
1153 | ||
1154 | default: | |
1155 | gcc_unreachable (); | |
1156 | } | |
1157 | } | |
1158 | } | |
01e1dea3 DM |
1159 | |
1160 | /* Add a trailing newline, if necessary. */ | |
1161 | move_to_column (&column, 0); | |
1162 | } | |
1163 | ||
1164 | /* Disable any colorization and emit a newline. */ | |
1165 | ||
1166 | void | |
1167 | layout::print_newline () | |
1168 | { | |
1169 | m_colorizer.set_normal_text (); | |
1170 | pp_newline (m_pp); | |
a87a86e1 DM |
1171 | } |
1172 | ||
8a645150 DM |
1173 | /* Return true if (ROW/COLUMN) is within a range of the layout. |
1174 | If it returns true, OUT_STATE is written to, with the | |
1175 | range index, and whether we should draw the caret at | |
1176 | (ROW/COLUMN) (as opposed to an underline). */ | |
1177 | ||
1178 | bool | |
1179 | layout::get_state_at_point (/* Inputs. */ | |
1180 | int row, int column, | |
1181 | int first_non_ws, int last_non_ws, | |
1182 | /* Outputs. */ | |
1183 | point_state *out_state) | |
1184 | { | |
1185 | layout_range *range; | |
57eb2d70 | 1186 | int i; |
8a645150 DM |
1187 | FOR_EACH_VEC_ELT (m_layout_ranges, i, range) |
1188 | { | |
1189 | if (range->contains_point (row, column)) | |
1190 | { | |
1191 | out_state->range_idx = i; | |
1192 | ||
1193 | /* Are we at the range's caret? is it visible? */ | |
1194 | out_state->draw_caret_p = false; | |
0afbb81b JJ |
1195 | if (range->m_show_caret_p |
1196 | && row == range->m_caret.m_line | |
8a645150 | 1197 | && column == range->m_caret.m_column) |
0afbb81b | 1198 | out_state->draw_caret_p = true; |
57eb2d70 | 1199 | |
8a645150 DM |
1200 | /* Within a multiline range, don't display any underline |
1201 | in any leading or trailing whitespace on a line. | |
1202 | We do display carets, however. */ | |
1203 | if (!out_state->draw_caret_p) | |
1204 | if (column < first_non_ws || column > last_non_ws) | |
1205 | return false; | |
1206 | ||
1207 | /* We are within a range. */ | |
1208 | return true; | |
1209 | } | |
1210 | } | |
1211 | ||
1212 | return false; | |
1213 | } | |
1214 | ||
1215 | /* Helper function for use by layout::print_line when printing the | |
1216 | annotation line under the source line. | |
1217 | Get the column beyond the rightmost one that could contain a caret or | |
1218 | range marker, given that we stop rendering at trailing whitespace. | |
1219 | ROW is the source line within the given file. | |
1220 | CARET_COLUMN is the column of range 0's caret. | |
1221 | LAST_NON_WS_COLUMN is the last column containing a non-whitespace | |
1222 | character of source (as determined when printing the source line). */ | |
1223 | ||
1224 | int | |
1225 | layout::get_x_bound_for_row (int row, int caret_column, | |
1226 | int last_non_ws_column) | |
1227 | { | |
1228 | int result = caret_column + 1; | |
1229 | ||
1230 | layout_range *range; | |
1231 | int i; | |
1232 | FOR_EACH_VEC_ELT (m_layout_ranges, i, range) | |
57eb2d70 | 1233 | { |
8a645150 DM |
1234 | if (row >= range->m_start.m_line) |
1235 | { | |
1236 | if (range->m_finish.m_line == row) | |
1237 | { | |
1238 | /* On the final line within a range; ensure that | |
1239 | we render up to the end of the range. */ | |
1240 | if (result <= range->m_finish.m_column) | |
1241 | result = range->m_finish.m_column + 1; | |
1242 | } | |
1243 | else if (row < range->m_finish.m_line) | |
1244 | { | |
1245 | /* Within a multiline range; ensure that we render up to the | |
1246 | last non-whitespace column. */ | |
1247 | if (result <= last_non_ws_column) | |
1248 | result = last_non_ws_column + 1; | |
1249 | } | |
1250 | } | |
57eb2d70 | 1251 | } |
8a645150 DM |
1252 | |
1253 | return result; | |
1254 | } | |
1255 | ||
a87a86e1 DM |
1256 | /* Given *COLUMN as an x-coordinate, print spaces to position |
1257 | successive output at DEST_COLUMN, printing a newline if necessary, | |
1258 | and updating *COLUMN. */ | |
1259 | ||
1260 | void | |
1261 | layout::move_to_column (int *column, int dest_column) | |
1262 | { | |
1263 | /* Start a new line if we need to. */ | |
1264 | if (*column > dest_column) | |
1265 | { | |
01e1dea3 | 1266 | print_newline (); |
a87a86e1 DM |
1267 | *column = 0; |
1268 | } | |
1269 | ||
1270 | while (*column < dest_column) | |
1271 | { | |
1272 | pp_space (m_pp); | |
1273 | (*column)++; | |
1274 | } | |
1275 | } | |
1276 | ||
f3352cab DM |
1277 | /* For debugging layout issues, render a ruler giving column numbers |
1278 | (after the 1-column indent). */ | |
1279 | ||
1280 | void | |
1281 | layout::show_ruler (int max_column) const | |
1282 | { | |
1283 | /* Hundreds. */ | |
1284 | if (max_column > 99) | |
1285 | { | |
1286 | pp_space (m_pp); | |
1287 | for (int column = 1 + m_x_offset; column <= max_column; column++) | |
1288 | if (0 == column % 10) | |
1289 | pp_character (m_pp, '0' + (column / 100) % 10); | |
1290 | else | |
1291 | pp_space (m_pp); | |
1292 | pp_newline (m_pp); | |
1293 | } | |
1294 | ||
1295 | /* Tens. */ | |
1296 | pp_space (m_pp); | |
1297 | for (int column = 1 + m_x_offset; column <= max_column; column++) | |
1298 | if (0 == column % 10) | |
1299 | pp_character (m_pp, '0' + (column / 10) % 10); | |
1300 | else | |
1301 | pp_space (m_pp); | |
1302 | pp_newline (m_pp); | |
1303 | ||
1304 | /* Units. */ | |
1305 | pp_space (m_pp); | |
1306 | for (int column = 1 + m_x_offset; column <= max_column; column++) | |
1307 | pp_character (m_pp, '0' + (column % 10)); | |
1308 | pp_newline (m_pp); | |
1309 | } | |
1310 | ||
8a645150 DM |
1311 | } /* End of anonymous namespace. */ |
1312 | ||
1313 | /* Print the physical source code corresponding to the location of | |
1314 | this diagnostic, with additional annotations. */ | |
1315 | ||
1316 | void | |
1317 | diagnostic_show_locus (diagnostic_context * context, | |
cc015f3a DM |
1318 | rich_location *richloc, |
1319 | diagnostic_t diagnostic_kind) | |
8a645150 | 1320 | { |
01e1dea3 DM |
1321 | pp_newline (context->printer); |
1322 | ||
cc015f3a | 1323 | location_t loc = richloc->get_loc (); |
7c8f7eaa DM |
1324 | /* Do nothing if source-printing has been disabled. */ |
1325 | if (!context->show_caret) | |
1326 | return; | |
1327 | ||
1328 | /* Don't attempt to print source for UNKNOWN_LOCATION and for builtins. */ | |
cc015f3a | 1329 | if (loc <= BUILTINS_LOCATION) |
7c8f7eaa DM |
1330 | return; |
1331 | ||
1332 | /* Don't print the same source location twice in a row, unless we have | |
1333 | fix-it hints. */ | |
cc015f3a DM |
1334 | if (loc == context->last_location |
1335 | && richloc->get_num_fixit_hints () == 0) | |
8a645150 DM |
1336 | return; |
1337 | ||
cc015f3a | 1338 | context->last_location = loc; |
8a645150 | 1339 | |
8a645150 DM |
1340 | const char *saved_prefix = pp_get_prefix (context->printer); |
1341 | pp_set_prefix (context->printer, NULL); | |
1342 | ||
cc015f3a | 1343 | layout layout (context, richloc, diagnostic_kind); |
876217ae DM |
1344 | for (int line_span_idx = 0; line_span_idx < layout.get_num_line_spans (); |
1345 | line_span_idx++) | |
01e1dea3 | 1346 | { |
876217ae DM |
1347 | const line_span *line_span = layout.get_line_span (line_span_idx); |
1348 | if (layout.print_heading_for_line_span_index_p (line_span_idx)) | |
1349 | { | |
1350 | expanded_location exploc = layout.get_expanded_location (line_span); | |
1351 | context->start_span (context, exploc); | |
1352 | } | |
1353 | int last_line = line_span->get_last_line (); | |
1354 | for (int row = line_span->get_first_line (); row <= last_line; row++) | |
01e1dea3 | 1355 | { |
876217ae DM |
1356 | /* Print the source line, followed by an annotation line |
1357 | consisting of any caret/underlines, then any fixits. | |
1358 | If the source line can't be read, print nothing. */ | |
1359 | line_bounds lbounds; | |
1360 | if (layout.print_source_line (row, &lbounds)) | |
1361 | { | |
1362 | layout.print_annotation_line (row, lbounds); | |
cc015f3a | 1363 | layout.print_any_fixits (row, richloc); |
876217ae | 1364 | } |
01e1dea3 DM |
1365 | } |
1366 | } | |
8a645150 | 1367 | |
57eb2d70 | 1368 | pp_set_prefix (context->printer, saved_prefix); |
57eb2d70 | 1369 | } |
d9b950dd DM |
1370 | |
1371 | #if CHECKING_P | |
1372 | ||
1373 | namespace selftest { | |
1374 | ||
cc015f3a DM |
1375 | /* Selftests for diagnostic_show_locus. */ |
1376 | ||
1377 | /* Convenience subclass of diagnostic_context for testing | |
1378 | diagnostic_show_locus. */ | |
1379 | ||
1380 | class test_diagnostic_context : public diagnostic_context | |
1381 | { | |
1382 | public: | |
1383 | test_diagnostic_context () | |
1384 | { | |
1385 | diagnostic_initialize (this, 0); | |
1386 | show_caret = true; | |
1387 | } | |
1388 | ~test_diagnostic_context () | |
1389 | { | |
1390 | diagnostic_finish (this); | |
1391 | } | |
1392 | }; | |
1393 | ||
1394 | /* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION. */ | |
1395 | ||
1396 | static void | |
1397 | test_diagnostic_show_locus_unknown_location () | |
1398 | { | |
1399 | test_diagnostic_context dc; | |
1400 | rich_location richloc (line_table, UNKNOWN_LOCATION); | |
1401 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1402 | ASSERT_STREQ ("\n", pp_formatted_text (dc.printer)); | |
1403 | } | |
1404 | ||
1405 | /* Verify that diagnostic_show_locus works sanely for various | |
1406 | single-line cases. | |
1407 | ||
1408 | All of these work on the following 1-line source file: | |
1409 | .0000000001111111 | |
1410 | .1234567890123456 | |
1411 | "foo = bar.field;\n" | |
1412 | which is set up by test_diagnostic_show_locus_one_liner and calls | |
1413 | them. */ | |
1414 | ||
1415 | /* Just a caret. */ | |
1416 | ||
1417 | static void | |
1418 | test_one_liner_simple_caret () | |
1419 | { | |
1420 | test_diagnostic_context dc; | |
1421 | location_t caret = linemap_position_for_column (line_table, 10); | |
1422 | rich_location richloc (line_table, caret); | |
1423 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1424 | ASSERT_STREQ ("\n" | |
1425 | " foo = bar.field;\n" | |
1426 | " ^\n", | |
1427 | pp_formatted_text (dc.printer)); | |
1428 | } | |
1429 | ||
1430 | /* Caret and range. */ | |
1431 | ||
1432 | static void | |
1433 | test_one_liner_caret_and_range () | |
1434 | { | |
1435 | test_diagnostic_context dc; | |
1436 | location_t caret = linemap_position_for_column (line_table, 10); | |
1437 | location_t start = linemap_position_for_column (line_table, 7); | |
1438 | location_t finish = linemap_position_for_column (line_table, 15); | |
1439 | location_t loc = make_location (caret, start, finish); | |
1440 | rich_location richloc (line_table, loc); | |
1441 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1442 | ASSERT_STREQ ("\n" | |
1443 | " foo = bar.field;\n" | |
1444 | " ~~~^~~~~~\n", | |
1445 | pp_formatted_text (dc.printer)); | |
1446 | } | |
1447 | ||
1448 | /* Multiple ranges and carets. */ | |
1449 | ||
1450 | static void | |
1451 | test_one_liner_multiple_carets_and_ranges () | |
1452 | { | |
1453 | test_diagnostic_context dc; | |
1454 | location_t foo | |
1455 | = make_location (linemap_position_for_column (line_table, 2), | |
1456 | linemap_position_for_column (line_table, 1), | |
1457 | linemap_position_for_column (line_table, 3)); | |
1458 | dc.caret_chars[0] = 'A'; | |
1459 | ||
1460 | location_t bar | |
1461 | = make_location (linemap_position_for_column (line_table, 8), | |
1462 | linemap_position_for_column (line_table, 7), | |
1463 | linemap_position_for_column (line_table, 9)); | |
1464 | dc.caret_chars[1] = 'B'; | |
1465 | ||
1466 | location_t field | |
1467 | = make_location (linemap_position_for_column (line_table, 13), | |
1468 | linemap_position_for_column (line_table, 11), | |
1469 | linemap_position_for_column (line_table, 15)); | |
1470 | dc.caret_chars[2] = 'C'; | |
1471 | ||
1472 | rich_location richloc (line_table, foo); | |
1473 | richloc.add_range (bar, true); | |
1474 | richloc.add_range (field, true); | |
1475 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1476 | ASSERT_STREQ ("\n" | |
1477 | " foo = bar.field;\n" | |
1478 | " ~A~ ~B~ ~~C~~\n", | |
1479 | pp_formatted_text (dc.printer)); | |
1480 | } | |
1481 | ||
1482 | /* Insertion fix-it hint: adding an "&" to the front of "bar.field". */ | |
1483 | ||
1484 | static void | |
1485 | test_one_liner_fixit_insert () | |
1486 | { | |
1487 | test_diagnostic_context dc; | |
1488 | location_t caret = linemap_position_for_column (line_table, 7); | |
1489 | rich_location richloc (line_table, caret); | |
f9087798 | 1490 | richloc.add_fixit_insert ("&"); |
cc015f3a DM |
1491 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); |
1492 | ASSERT_STREQ ("\n" | |
1493 | " foo = bar.field;\n" | |
1494 | " ^\n" | |
1495 | " &\n", | |
1496 | pp_formatted_text (dc.printer)); | |
1497 | } | |
1498 | ||
1499 | /* Removal fix-it hint: removal of the ".field". */ | |
1500 | ||
1501 | static void | |
1502 | test_one_liner_fixit_remove () | |
1503 | { | |
1504 | test_diagnostic_context dc; | |
1505 | location_t start = linemap_position_for_column (line_table, 10); | |
1506 | location_t finish = linemap_position_for_column (line_table, 15); | |
1507 | location_t dot = make_location (start, start, finish); | |
1508 | rich_location richloc (line_table, dot); | |
f9087798 | 1509 | richloc.add_fixit_remove (); |
cc015f3a DM |
1510 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); |
1511 | ASSERT_STREQ ("\n" | |
1512 | " foo = bar.field;\n" | |
1513 | " ^~~~~~\n" | |
1514 | " ------\n", | |
1515 | pp_formatted_text (dc.printer)); | |
1516 | } | |
1517 | ||
1518 | /* Replace fix-it hint: replacing "field" with "m_field". */ | |
1519 | ||
1520 | static void | |
1521 | test_one_liner_fixit_replace () | |
1522 | { | |
1523 | test_diagnostic_context dc; | |
1524 | location_t start = linemap_position_for_column (line_table, 11); | |
1525 | location_t finish = linemap_position_for_column (line_table, 15); | |
1526 | location_t field = make_location (start, start, finish); | |
1527 | rich_location richloc (line_table, field); | |
f9087798 | 1528 | richloc.add_fixit_replace ("m_field"); |
cc015f3a DM |
1529 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); |
1530 | ASSERT_STREQ ("\n" | |
1531 | " foo = bar.field;\n" | |
1532 | " ^~~~~\n" | |
1533 | " m_field\n", | |
1534 | pp_formatted_text (dc.printer)); | |
1535 | } | |
1536 | ||
2ffe0809 DM |
1537 | /* Replace fix-it hint: replacing "field" with "m_field", |
1538 | but where the caret was elsewhere. */ | |
1539 | ||
1540 | static void | |
1541 | test_one_liner_fixit_replace_non_equal_range () | |
1542 | { | |
1543 | test_diagnostic_context dc; | |
1544 | location_t equals = linemap_position_for_column (line_table, 5); | |
1545 | location_t start = linemap_position_for_column (line_table, 11); | |
1546 | location_t finish = linemap_position_for_column (line_table, 15); | |
1547 | rich_location richloc (line_table, equals); | |
1548 | source_range range; | |
1549 | range.m_start = start; | |
1550 | range.m_finish = finish; | |
1551 | richloc.add_fixit_replace (range, "m_field"); | |
1552 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1553 | /* The replacement range is not indicated in the annotation line, so | |
1554 | it should be indicated via an additional underline. */ | |
1555 | ASSERT_STREQ ("\n" | |
1556 | " foo = bar.field;\n" | |
1557 | " ^\n" | |
1558 | " -----\n" | |
1559 | " m_field\n", | |
1560 | pp_formatted_text (dc.printer)); | |
1561 | } | |
1562 | ||
1563 | /* Replace fix-it hint: replacing "field" with "m_field", | |
1564 | where the caret was elsewhere, but where a secondary range | |
1565 | exactly covers "field". */ | |
1566 | ||
1567 | static void | |
1568 | test_one_liner_fixit_replace_equal_secondary_range () | |
1569 | { | |
1570 | test_diagnostic_context dc; | |
1571 | location_t equals = linemap_position_for_column (line_table, 5); | |
1572 | location_t start = linemap_position_for_column (line_table, 11); | |
1573 | location_t finish = linemap_position_for_column (line_table, 15); | |
1574 | rich_location richloc (line_table, equals); | |
1575 | location_t field = make_location (start, start, finish); | |
1576 | richloc.add_range (field, false); | |
f9087798 | 1577 | richloc.add_fixit_replace (field, "m_field"); |
2ffe0809 DM |
1578 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); |
1579 | /* The replacement range is indicated in the annotation line, | |
1580 | so it shouldn't be indicated via an additional underline. */ | |
1581 | ASSERT_STREQ ("\n" | |
1582 | " foo = bar.field;\n" | |
1583 | " ^ ~~~~~\n" | |
1584 | " m_field\n", | |
1585 | pp_formatted_text (dc.printer)); | |
1586 | } | |
1587 | ||
2aa51413 DM |
1588 | /* Verify that we can use ad-hoc locations when adding fixits to a |
1589 | rich_location. */ | |
1590 | ||
1591 | static void | |
1592 | test_one_liner_fixit_validation_adhoc_locations () | |
1593 | { | |
1594 | /* Generate a range that's too long to be packed, so must | |
1595 | be stored as an ad-hoc location (given the defaults | |
1596 | of 5 bits or 0 bits of packed range); 41 columns > 2**5. */ | |
1597 | const location_t c7 = linemap_position_for_column (line_table, 7); | |
1598 | const location_t c47 = linemap_position_for_column (line_table, 47); | |
1599 | const location_t loc = make_location (c7, c7, c47); | |
1600 | ||
1601 | if (c47 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1602 | return; | |
1603 | ||
1604 | ASSERT_TRUE (IS_ADHOC_LOC (loc)); | |
1605 | ||
1606 | /* Insert. */ | |
1607 | { | |
1608 | rich_location richloc (line_table, loc); | |
1609 | richloc.add_fixit_insert (loc, "test"); | |
1610 | /* It should not have been discarded by the validator. */ | |
1611 | ASSERT_EQ (1, richloc.get_num_fixit_hints ()); | |
1612 | ||
1613 | test_diagnostic_context dc; | |
1614 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1615 | ASSERT_STREQ ("\n" | |
1616 | " foo = bar.field;\n" | |
1617 | " ^~~~~~~~~~ \n" | |
1618 | " test\n", | |
1619 | pp_formatted_text (dc.printer)); | |
1620 | } | |
1621 | ||
1622 | /* Remove. */ | |
1623 | { | |
1624 | rich_location richloc (line_table, loc); | |
1625 | source_range range = source_range::from_locations (loc, c47); | |
1626 | richloc.add_fixit_remove (range); | |
1627 | /* It should not have been discarded by the validator. */ | |
1628 | ASSERT_EQ (1, richloc.get_num_fixit_hints ()); | |
1629 | ||
1630 | test_diagnostic_context dc; | |
1631 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1632 | ASSERT_STREQ ("\n" | |
1633 | " foo = bar.field;\n" | |
1634 | " ^~~~~~~~~~ \n" | |
1635 | " -----------------------------------------\n", | |
1636 | pp_formatted_text (dc.printer)); | |
1637 | } | |
1638 | ||
1639 | /* Replace. */ | |
1640 | { | |
1641 | rich_location richloc (line_table, loc); | |
1642 | source_range range = source_range::from_locations (loc, c47); | |
1643 | richloc.add_fixit_replace (range, "test"); | |
1644 | /* It should not have been discarded by the validator. */ | |
1645 | ASSERT_EQ (1, richloc.get_num_fixit_hints ()); | |
1646 | ||
1647 | test_diagnostic_context dc; | |
1648 | diagnostic_show_locus (&dc, &richloc, DK_ERROR); | |
1649 | ASSERT_STREQ ("\n" | |
1650 | " foo = bar.field;\n" | |
1651 | " ^~~~~~~~~~ \n" | |
1652 | " test\n", | |
1653 | pp_formatted_text (dc.printer)); | |
1654 | } | |
1655 | } | |
1656 | ||
cc015f3a DM |
1657 | /* Run the various one-liner tests. */ |
1658 | ||
1659 | static void | |
1660 | test_diagnostic_show_locus_one_liner (const line_table_case &case_) | |
1661 | { | |
1662 | /* Create a tempfile and write some text to it. | |
1663 | ....................0000000001111111. | |
1664 | ....................1234567890123456. */ | |
1665 | const char *content = "foo = bar.field;\n"; | |
1666 | temp_source_file tmp (SELFTEST_LOCATION, ".c", content); | |
1667 | line_table_test ltt (case_); | |
1668 | ||
1669 | linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 1); | |
1670 | ||
1671 | location_t line_end = linemap_position_for_column (line_table, 16); | |
1672 | ||
1673 | /* Don't attempt to run the tests if column data might be unavailable. */ | |
1674 | if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1675 | return; | |
1676 | ||
1677 | ASSERT_STREQ (tmp.get_filename (), LOCATION_FILE (line_end)); | |
1678 | ASSERT_EQ (1, LOCATION_LINE (line_end)); | |
1679 | ASSERT_EQ (16, LOCATION_COLUMN (line_end)); | |
1680 | ||
1681 | test_one_liner_simple_caret (); | |
1682 | test_one_liner_caret_and_range (); | |
1683 | test_one_liner_multiple_carets_and_ranges (); | |
1684 | test_one_liner_fixit_insert (); | |
1685 | test_one_liner_fixit_remove (); | |
1686 | test_one_liner_fixit_replace (); | |
2ffe0809 DM |
1687 | test_one_liner_fixit_replace_non_equal_range (); |
1688 | test_one_liner_fixit_replace_equal_secondary_range (); | |
2aa51413 | 1689 | test_one_liner_fixit_validation_adhoc_locations (); |
cc015f3a DM |
1690 | } |
1691 | ||
ee908516 DM |
1692 | /* Verify that fix-it hints are appropriately consolidated. |
1693 | ||
1694 | If any fix-it hints in a rich_location involve locations beyond | |
1695 | LINE_MAP_MAX_LOCATION_WITH_COLS, then we can't reliably apply | |
1696 | the fix-it as a whole, so there should be none. | |
1697 | ||
1698 | Otherwise, verify that consecutive "replace" and "remove" fix-its | |
1699 | are merged, and that other fix-its remain separate. */ | |
1700 | ||
1701 | static void | |
1702 | test_fixit_consolidation (const line_table_case &case_) | |
1703 | { | |
1704 | line_table_test ltt (case_); | |
1705 | ||
1706 | linemap_add (line_table, LC_ENTER, false, "test.c", 1); | |
1707 | ||
1708 | const location_t c10 = linemap_position_for_column (line_table, 10); | |
1709 | const location_t c15 = linemap_position_for_column (line_table, 15); | |
1710 | const location_t c16 = linemap_position_for_column (line_table, 16); | |
1711 | const location_t c17 = linemap_position_for_column (line_table, 17); | |
1712 | const location_t c20 = linemap_position_for_column (line_table, 20); | |
1713 | const location_t caret = c10; | |
1714 | ||
1715 | /* Insert + insert. */ | |
1716 | { | |
1717 | rich_location richloc (line_table, caret); | |
1718 | richloc.add_fixit_insert (c10, "foo"); | |
1719 | richloc.add_fixit_insert (c15, "bar"); | |
1720 | ||
1721 | if (c15 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1722 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1723 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1724 | else | |
1725 | /* They should not have been merged. */ | |
1726 | ASSERT_EQ (2, richloc.get_num_fixit_hints ()); | |
1727 | } | |
1728 | ||
1729 | /* Insert + replace. */ | |
1730 | { | |
1731 | rich_location richloc (line_table, caret); | |
1732 | richloc.add_fixit_insert (c10, "foo"); | |
1733 | richloc.add_fixit_replace (source_range::from_locations (c15, c17), | |
1734 | "bar"); | |
1735 | ||
1736 | if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1737 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1738 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1739 | else | |
1740 | /* They should not have been merged. */ | |
1741 | ASSERT_EQ (2, richloc.get_num_fixit_hints ()); | |
1742 | } | |
1743 | ||
1744 | /* Replace + non-consecutive insert. */ | |
1745 | { | |
1746 | rich_location richloc (line_table, caret); | |
1747 | richloc.add_fixit_replace (source_range::from_locations (c10, c15), | |
1748 | "bar"); | |
1749 | richloc.add_fixit_insert (c17, "foo"); | |
1750 | ||
1751 | if (c17 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1752 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1753 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1754 | else | |
1755 | /* They should not have been merged. */ | |
1756 | ASSERT_EQ (2, richloc.get_num_fixit_hints ()); | |
1757 | } | |
1758 | ||
1759 | /* Replace + non-consecutive replace. */ | |
1760 | { | |
1761 | rich_location richloc (line_table, caret); | |
1762 | richloc.add_fixit_replace (source_range::from_locations (c10, c15), | |
1763 | "foo"); | |
1764 | richloc.add_fixit_replace (source_range::from_locations (c17, c20), | |
1765 | "bar"); | |
1766 | ||
1767 | if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1768 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1769 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1770 | else | |
1771 | /* They should not have been merged. */ | |
1772 | ASSERT_EQ (2, richloc.get_num_fixit_hints ()); | |
1773 | } | |
1774 | ||
1775 | /* Replace + consecutive replace. */ | |
1776 | { | |
1777 | rich_location richloc (line_table, caret); | |
1778 | richloc.add_fixit_replace (source_range::from_locations (c10, c15), | |
1779 | "foo"); | |
1780 | richloc.add_fixit_replace (source_range::from_locations (c16, c20), | |
1781 | "bar"); | |
1782 | ||
1783 | if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1784 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1785 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1786 | else | |
1787 | { | |
1788 | /* They should have been merged into a single "replace". */ | |
1789 | ASSERT_EQ (1, richloc.get_num_fixit_hints ()); | |
1790 | const fixit_hint *hint = richloc.get_fixit_hint (0); | |
1791 | ASSERT_EQ (fixit_hint::REPLACE, hint->get_kind ()); | |
1792 | const fixit_replace *replace = (const fixit_replace *)hint; | |
1793 | ASSERT_STREQ ("foobar", replace->get_string ()); | |
1794 | ASSERT_EQ (c10, replace->get_range ().m_start); | |
1795 | ASSERT_EQ (c20, replace->get_range ().m_finish); | |
1796 | } | |
1797 | } | |
1798 | ||
1799 | /* Replace + consecutive removal. */ | |
1800 | { | |
1801 | rich_location richloc (line_table, caret); | |
1802 | richloc.add_fixit_replace (source_range::from_locations (c10, c15), | |
1803 | "foo"); | |
1804 | richloc.add_fixit_remove (source_range::from_locations (c16, c20)); | |
1805 | ||
1806 | if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1807 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1808 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1809 | else | |
1810 | { | |
1811 | /* They should have been merged into a single replace, with the | |
1812 | range extended to cover that of the removal. */ | |
1813 | ASSERT_EQ (1, richloc.get_num_fixit_hints ()); | |
1814 | const fixit_hint *hint = richloc.get_fixit_hint (0); | |
1815 | ASSERT_EQ (fixit_hint::REPLACE, hint->get_kind ()); | |
1816 | const fixit_replace *replace = (const fixit_replace *)hint; | |
1817 | ASSERT_STREQ ("foo", replace->get_string ()); | |
1818 | ASSERT_EQ (c10, replace->get_range ().m_start); | |
1819 | ASSERT_EQ (c20, replace->get_range ().m_finish); | |
1820 | } | |
1821 | } | |
1822 | ||
1823 | /* Consecutive removals. */ | |
1824 | { | |
1825 | rich_location richloc (line_table, caret); | |
1826 | richloc.add_fixit_remove (source_range::from_locations (c10, c15)); | |
1827 | richloc.add_fixit_remove (source_range::from_locations (c16, c20)); | |
1828 | ||
1829 | if (c20 > LINE_MAP_MAX_LOCATION_WITH_COLS) | |
1830 | /* Bogus column info for 2nd fixit, so no fixits. */ | |
1831 | ASSERT_EQ (0, richloc.get_num_fixit_hints ()); | |
1832 | else | |
1833 | { | |
1834 | /* They should have been merged into a single "replace-with-empty". */ | |
1835 | ASSERT_EQ (1, richloc.get_num_fixit_hints ()); | |
1836 | const fixit_hint *hint = richloc.get_fixit_hint (0); | |
1837 | ASSERT_EQ (fixit_hint::REPLACE, hint->get_kind ()); | |
1838 | const fixit_replace *replace = (const fixit_replace *)hint; | |
1839 | ASSERT_STREQ ("", replace->get_string ()); | |
1840 | ASSERT_EQ (c10, replace->get_range ().m_start); | |
1841 | ASSERT_EQ (c20, replace->get_range ().m_finish); | |
1842 | } | |
1843 | } | |
1844 | } | |
1845 | ||
d9b950dd DM |
1846 | /* Run all of the selftests within this file. */ |
1847 | ||
1848 | void | |
1849 | diagnostic_show_locus_c_tests () | |
1850 | { | |
1851 | test_range_contains_point_for_single_point (); | |
1852 | test_range_contains_point_for_single_line (); | |
1853 | test_range_contains_point_for_multiple_lines (); | |
1854 | ||
1855 | test_get_line_width_without_trailing_whitespace (); | |
cc015f3a DM |
1856 | |
1857 | test_diagnostic_show_locus_unknown_location (); | |
1858 | ||
1859 | for_each_line_table_case (test_diagnostic_show_locus_one_liner); | |
ee908516 | 1860 | for_each_line_table_case (test_fixit_consolidation); |
d9b950dd DM |
1861 | } |
1862 | ||
1863 | } // namespace selftest | |
1864 | ||
1865 | #endif /* #if CHECKING_P */ |