]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/diagnostic-show-locus.c
[Ada] Define the -fdump-scos option in lang.opt
[thirdparty/gcc.git] / gcc / diagnostic-show-locus.c
index cf9e5c202feb33d898a5e82c12dfdf3295d3b036..4d563dda8f420d22e97836a95be6b3709d0e90dc 100644 (file)
@@ -1,5 +1,5 @@
 /* Diagnostic subroutines for printing source-code
-   Copyright (C) 1999-2018 Free Software Foundation, Inc.
+   Copyright (C) 1999-2019 Free Software Foundation, Inc.
    Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
 
 This file is part of GCC.
@@ -39,6 +39,13 @@ along with GCC; see the file COPYING3.  If not see
 # 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
@@ -126,16 +133,20 @@ class layout_range
  public:
   layout_range (const expanded_location *start_exploc,
                const expanded_location *finish_exploc,
-               bool show_caret_p,
-               const expanded_location *caret_exploc);
+               enum range_display_kind range_display_kind,
+               const expanded_location *caret_exploc,
+               unsigned original_idx,
+               const range_label *label);
 
   bool contains_point (linenum_type row, int column) const;
   bool intersects_line_p (linenum_type row) const;
 
   layout_point m_start;
   layout_point m_finish;
-  bool m_show_caret_p;
+  enum range_display_kind m_range_display_kind;
   layout_point m_caret;
+  unsigned m_original_idx;
+  const range_label *m_label;
 };
 
 /* A struct for use by layout::print_source_line for telling
@@ -153,8 +164,9 @@ struct line_bounds
    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)
   {
@@ -234,11 +246,13 @@ class layout
          diagnostic_t diagnostic_kind);
 
   bool maybe_add_location_range (const location_range *loc_range,
+                                unsigned original_idx,
                                 bool restrict_to_current_line_spans);
 
   int get_num_line_spans () const { return m_line_spans.length (); }
   const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; }
 
+  void print_gap_in_line_numbering ();
   bool print_heading_for_line_span_index_p (int line_span_idx) const;
 
   expanded_location get_expanded_location (const line_span *) const;
@@ -251,7 +265,9 @@ class layout
   void print_source_line (linenum_type row, const char *line, int line_width,
                          line_bounds *lbounds_out);
   bool should_print_annotation_line_p (linenum_type row) const;
+  void start_annotation_line (char margin_char = ' ') const;
   void print_annotation_line (linenum_type row, const line_bounds lbounds);
+  void print_any_labels (linenum_type row);
   void print_trailing_fixits (linenum_type row);
 
   bool annotation_line_showed_range_p (linenum_type line, int start_column,
@@ -276,19 +292,21 @@ class layout
                       int last_non_ws);
 
   void
-  move_to_column (int *column, int dest_column);
+  move_to_column (int *column, int dest_column, bool add_left_margin);
 
  private:
   diagnostic_context *m_context;
   pretty_printer *m_pp;
-  diagnostic_t m_diagnostic_kind;
   location_t m_primary_loc;
   expanded_location m_exploc;
   colorizer m_colorizer;
   bool m_colorize_source_p;
+  bool m_show_labels_p;
+  bool m_show_line_numbers_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
+  int m_linenum_width;
   int m_x_offset;
 };
 
@@ -404,12 +422,16 @@ colorizer::get_color_by_name (const char *name)
 
 layout_range::layout_range (const expanded_location *start_exploc,
                            const expanded_location *finish_exploc,
-                           bool show_caret_p,
-                           const expanded_location *caret_exploc)
+                           enum range_display_kind range_display_kind,
+                           const expanded_location *caret_exploc,
+                           unsigned original_idx,
+                           const range_label *label)
 : m_start (*start_exploc),
   m_finish (*finish_exploc),
-  m_show_caret_p (show_caret_p),
-  m_caret (*caret_exploc)
+  m_range_display_kind (range_display_kind),
+  m_caret (*caret_exploc),
+  m_original_idx (original_idx),
+  m_label (label)
 {
 }
 
@@ -535,8 +557,8 @@ make_range (int start_line, int start_col, int end_line, int end_col)
     = {"test.c", start_line, start_col, NULL, false};
   const expanded_location finish_exploc
     = {"test.c", end_line, end_col, NULL, false};
-  return layout_range (&start_exploc, &finish_exploc, false,
-                      &start_exploc);
+  return layout_range (&start_exploc, &finish_exploc, SHOW_RANGE_WITHOUT_CARET,
+                      &start_exploc, 0, NULL);
 }
 
 /* Selftests for layout_range::contains_point and
@@ -763,11 +785,11 @@ compatible_locations_p (location_t loc_a, location_t loc_b)
          /* 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);
@@ -821,14 +843,16 @@ layout::layout (diagnostic_context * context,
                diagnostic_t diagnostic_kind)
 : m_context (context),
   m_pp (context->printer),
-  m_diagnostic_kind (diagnostic_kind),
   m_primary_loc (richloc->get_range (0)->m_loc),
   m_exploc (richloc->get_expanded_location (0)),
   m_colorizer (context, diagnostic_kind),
   m_colorize_source_p (context->colorize_source_p),
+  m_show_labels_p (context->show_labels_p),
+  m_show_line_numbers_p (context->show_line_numbers_p),
   m_layout_ranges (richloc->get_num_locations ()),
   m_fixit_hints (richloc->get_num_fixit_hints ()),
   m_line_spans (1 + richloc->get_num_locations ()),
+  m_linenum_width (0),
   m_x_offset (0)
 {
   for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++)
@@ -836,7 +860,7 @@ layout::layout (diagnostic_context * context,
       /* This diagnostic printer can only cope with "sufficiently sane" ranges.
         Ignore any ranges that are awkward to handle.  */
       const location_range *loc_range = richloc->get_range (idx);
-      maybe_add_location_range (loc_range, false);
+      maybe_add_location_range (loc_range, idx, false);
     }
 
   /* Populate m_fixit_hints, filtering to only those that are in the
@@ -854,6 +878,20 @@ layout::layout (diagnostic_context * context,
   /* Populate m_line_spans.  */
   calculate_line_spans ();
 
+  /* Determine m_linenum_width.  */
+  gcc_assert (m_line_spans.length () > 0);
+  const line_span *last_span = &m_line_spans[m_line_spans.length () - 1];
+  int highest_line = last_span->m_last_line;
+  if (highest_line < 0)
+    highest_line = 0;
+  m_linenum_width = num_digits (highest_line);
+  /* If we're showing jumps in the line-numbering, allow at least 3 chars.  */
+  if (m_line_spans.length () > 1)
+    m_linenum_width = MAX (m_linenum_width, 3);
+  /* If there's a minimum margin width, apply it (subtracting 1 for the space
+     after the line number.  */
+  m_linenum_width = MAX (m_linenum_width, context->min_margin_width - 1);
+
   /* Adjust m_x_offset.
      Center the primary caret to fit in max_width; all columns
      will be adjusted accordingly.  */
@@ -863,6 +901,8 @@ layout::layout (diagnostic_context * context,
     {
       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)
@@ -877,6 +917,9 @@ layout::layout (diagnostic_context * context,
 /* Attempt to add LOC_RANGE to m_layout_ranges, filtering them to
    those that we can sanely print.
 
+   ORIGINAL_IDX is the index of LOC_RANGE within its rich_location,
+   (for use as extrinsic state by label ranges FIXME).
+
    If RESTRICT_TO_CURRENT_LINE_SPANS is true, then LOC_RANGE is also
    filtered against this layout instance's current line spans: it
    will only be added if the location is fully within the lines
@@ -886,6 +929,7 @@ layout::layout (diagnostic_context * context,
 
 bool
 layout::maybe_add_location_range (const location_range *loc_range,
+                                 unsigned original_idx,
                                  bool restrict_to_current_line_spans)
 {
   gcc_assert (loc_range);
@@ -910,13 +954,13 @@ layout::maybe_add_location_range (const location_range *loc_range,
     return false;
   if (finish.file != m_exploc.file)
     return false;
-  if (loc_range->m_show_caret_p)
+  if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
     if (caret.file != m_exploc.file)
       return false;
 
   /* Sanitize the caret location for non-primary ranges.  */
   if (m_layout_ranges.length () > 0)
-    if (loc_range->m_show_caret_p)
+    if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
       if (!compatible_locations_p (loc_range->m_loc, m_primary_loc))
        /* Discard any non-primary ranges that can't be printed
           sanely relative to the primary location.  */
@@ -924,7 +968,8 @@ layout::maybe_add_location_range (const location_range *loc_range,
 
   /* Everything is now known to be in the correct source file,
      but it may require further sanitization.  */
-  layout_range ri (&start, &finish, loc_range->m_show_caret_p, &caret);
+  layout_range ri (&start, &finish, loc_range->m_range_display_kind, &caret,
+                  original_idx, loc_range->m_label);
 
   /* If we have a range that finishes before it starts (perhaps
      from something built via macro expansion), printing the
@@ -960,7 +1005,7 @@ layout::maybe_add_location_range (const location_range *loc_range,
        return false;
       if (!will_show_line_p (finish.line))
        return false;
-      if (loc_range->m_show_caret_p)
+      if (loc_range->m_range_display_kind == SHOW_RANGE_WITH_CARET)
        if (!will_show_line_p (caret.line))
          return false;
     }
@@ -986,6 +1031,20 @@ layout::will_show_line_p (linenum_type row) const
   return false;
 }
 
+/* Print a line showing a gap in the line numbers, for showing the boundary
+   between two line spans.  */
+
+void
+layout::print_gap_in_line_numbering ()
+{
+  gcc_assert (m_show_line_numbers_p);
+
+  for (int i = 0; i < m_linenum_width + 1; i++)
+    pp_character (m_pp, '.');
+
+  pp_newline (m_pp);
+}
+
 /* Return true iff we should print a heading when starting the
    line span with the given index.  */
 
@@ -1068,7 +1127,16 @@ static line_span
 get_line_span_for_fixit_hint (const fixit_hint *hint)
 {
   gcc_assert (hint);
-  return line_span (LOCATION_LINE (hint->get_start_loc ()),
+
+  int start_line = LOCATION_LINE (hint->get_start_loc ());
+
+  /* For line-insertion fix-it hints, add the previous line to the
+     span, to give the user more context on the proposed change.  */
+  if (hint->ends_with_newline_p ())
+    if (start_line > 1)
+      start_line--;
+
+  return line_span (start_line,
                    LOCATION_LINE (hint->get_next_loc ()));
 }
 
@@ -1083,21 +1151,34 @@ get_line_span_for_fixit_hint (const fixit_hint *hint)
    This function populates m_line_spans with an ordered, disjoint list of
    the line spans of interest.
 
-   For example, if the primary caret location is on line 7, with ranges
-   covering lines 5-6 and lines 9-12:
+   Printing a gap between line spans takes one line, so, when printing
+   line numbers, we allow a gap of up to one line between spans when
+   merging, since it makes more sense to print the source line rather than a
+   "gap-in-line-numbering" line.  When not printing line numbers, it's
+   better to be more explicit about what's going on, so keeping them as
+   separate spans is preferred.
+
+   For example, if the primary range is on lines 8-10, with secondary ranges
+   covering lines 5-6 and lines 13-15:
 
      004
-     005                   |RANGE 0
-     006                   |RANGE 0
-     007  |PRIMARY CARET
-     008
-     009                                |RANGE 1
-     010                                |RANGE 1
-     011                                |RANGE 1
-     012                                |RANGE 1
-     013
-
-   then we want two spans: lines 5-7 and lines 9-12.  */
+     005                   |RANGE 1
+     006                   |RANGE 1
+     007
+     008  |PRIMARY RANGE
+     009  |PRIMARY CARET
+     010  |PRIMARY RANGE
+     011
+     012
+     013                                |RANGE 2
+     014                                |RANGE 2
+     015                                |RANGE 2
+     016
+
+   With line numbering on, we want two spans: lines 5-10 and lines 13-15.
+
+   With line numbering off (with span headers), we want three spans: lines 5-6,
+   lines 8-10, and lines 13-15.  */
 
 void
 layout::calculate_line_spans ()
@@ -1137,7 +1218,9 @@ layout::calculate_line_spans ()
       line_span *current = &m_line_spans[m_line_spans.length () - 1];
       const line_span *next = &tmp_spans[i];
       gcc_assert (next->m_first_line >= current->m_first_line);
-      if (next->m_first_line <= current->m_last_line + 1)
+      const int merger_distance = m_show_line_numbers_p ? 1 : 0;
+      if ((linenum_arith_t)next->m_first_line
+         <= (linenum_arith_t)current->m_last_line + 1 + merger_distance)
        {
          /* We can merge them. */
          if (next->m_last_line > current->m_last_line)
@@ -1183,7 +1266,15 @@ layout::print_source_line (linenum_type row, const char *line, int line_width,
                                                           line_width);
   line += m_x_offset;
 
-  pp_space (m_pp);
+  if (m_show_line_numbers_p)
+    {
+      int width = num_digits (row);
+      for (int i = 0; i < m_linenum_width - width; i++)
+       pp_space (m_pp);
+      pp_printf (m_pp, "%i | ", row);
+    }
+  else
+    pp_space (m_pp);
   int first_non_ws = INT_MAX;
   int last_non_ws = 0;
   int column;
@@ -1240,11 +1331,34 @@ layout::should_print_annotation_line_p (linenum_type row) const
   layout_range *range;
   int i;
   FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
-    if (range->intersects_line_p (row))
-      return true;
+    {
+      if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
+       return false;
+      if (range->intersects_line_p (row))
+       return true;
+    }
   return false;
 }
 
+/* Begin an annotation line.  If m_show_line_numbers_p, print the left
+   margin, which is empty for annotation lines.  Otherwise, do nothing.  */
+
+void
+layout::start_annotation_line (char margin_char) const
+{
+  if (m_show_line_numbers_p)
+    {
+      /* Print the margin.  If MARGIN_CHAR != ' ', then print up to 3
+        of it, right-aligned, padded with spaces.  */
+      int i;
+      for (i = 0; i < m_linenum_width - 3; i++)
+       pp_space (m_pp);
+      for (; i < m_linenum_width; i++)
+       pp_character (m_pp, margin_char);
+      pp_string (m_pp, " |");
+    }
+}
+
 /* Print a line consisting of the caret/underlines for the given
    source line.  */
 
@@ -1254,7 +1368,9 @@ layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
   int x_bound = get_x_bound_for_row (row, m_exploc.column,
                                     lbounds.m_last_non_ws);
 
+  start_annotation_line ();
   pp_space (m_pp);
+
   for (int column = 1 + m_x_offset; column < x_bound; column++)
     {
       bool in_range_p;
@@ -1290,6 +1406,181 @@ layout::print_annotation_line (linenum_type row, const line_bounds lbounds)
   print_newline ();
 }
 
+/* Implementation detail of layout::print_any_labels.
+
+   A label within the given row of source.  */
+
+class line_label
+{
+public:
+  line_label (int state_idx, int column, label_text text)
+  : m_state_idx (state_idx), m_column (column),
+    m_text (text), m_length (strlen (text.m_buffer)),
+    m_label_line (0)
+  {}
+
+  /* Sorting is primarily by column, then by state index.  */
+  static int comparator (const void *p1, const void *p2)
+  {
+    const line_label *ll1 = (const line_label *)p1;
+    const line_label *ll2 = (const line_label *)p2;
+    int column_cmp = compare (ll1->m_column, ll2->m_column);
+    if (column_cmp)
+      return column_cmp;
+    return compare (ll1->m_state_idx, ll2->m_state_idx);
+  }
+
+  int m_state_idx;
+  int m_column;
+  label_text m_text;
+  size_t m_length;
+  int m_label_line;
+};
+
+/* Print any labels in this row.  */
+void
+layout::print_any_labels (linenum_type row)
+{
+  int i;
+  auto_vec<line_label> labels;
+
+  /* Gather the labels that are to be printed into "labels".  */
+  {
+    layout_range *range;
+    FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+      {
+       /* Most ranges don't have labels, so reject this first.  */
+       if (range->m_label == NULL)
+         continue;
+
+       /* The range's caret must be on this line.  */
+       if (range->m_caret.m_line != row)
+         continue;
+
+       /* Reject labels that aren't fully visible due to clipping
+          by m_x_offset.  */
+       if (range->m_caret.m_column <= m_x_offset)
+         continue;
+
+       label_text text;
+       text = range->m_label->get_text (range->m_original_idx);
+
+       /* Allow for labels that return NULL from their get_text
+          implementation (so e.g. such labels can control their own
+          visibility).  */
+       if (text.m_buffer == NULL)
+         continue;
+
+       labels.safe_push (line_label (i, range->m_caret.m_column, text));
+      }
+  }
+
+  /* Bail out if there are no labels on this row.  */
+  if (labels.length () == 0)
+    return;
+
+  /* Sort them.  */
+  labels.qsort(line_label::comparator);
+
+  /* Figure out how many "label lines" we need, and which
+     one each label is printed in.
+
+     For example, if the labels aren't too densely packed,
+     we can fit them on the same line, giving two "label lines":
+
+       foo + bar
+       ~~~   ~~~
+       |     |        : label line 0
+       l0    l1       : label line 1
+
+     If they would touch each other or overlap, then we need
+     additional "label lines":
+
+       foo + bar
+       ~~~   ~~~
+       |     |             : label line 0
+       |     label 1       : label line 1
+       label 0             : label line 2
+
+     Place the final label on label line 1, and work backwards, adding
+     label lines as needed.
+
+     If multiple labels are at the same place, put them on separate
+     label lines:
+
+       foo + bar
+           ^               : label line 0
+           |               : label line 1
+           label 1         : label line 2
+           label 0         : label line 3.  */
+
+  int max_label_line = 1;
+  {
+    int next_column = INT_MAX;
+    line_label *label;
+    FOR_EACH_VEC_ELT_REVERSE (labels, i, label)
+      {
+       /* Would this label "touch" or overlap the next label?  */
+       if (label->m_column + label->m_length >= (size_t)next_column)
+         max_label_line++;
+
+       label->m_label_line = max_label_line;
+       next_column = label->m_column;
+      }
+  }
+
+  /* Print the "label lines".  For each label within the line, print
+     either a vertical bar ('|') for the labels that are lower down, or the
+     labels themselves once we've reached their line.  */
+  {
+    /* Keep track of in which column we last printed a vertical bar.
+       This allows us to suppress duplicate vertical bars for the case
+       where multiple labels are on one column.  */
+    int last_vbar = 0;
+    for (int label_line = 0; label_line <= max_label_line; label_line++)
+      {
+       start_annotation_line ();
+       pp_space (m_pp);
+       int column = 1 + m_x_offset;
+       line_label *label;
+       FOR_EACH_VEC_ELT (labels, i, label)
+         {
+           if (label_line > label->m_label_line)
+             /* We've printed all the labels for this label line.  */
+             break;
+
+           if (label_line == label->m_label_line)
+             {
+               gcc_assert (column <= label->m_column);
+               move_to_column (&column, label->m_column, true);
+               m_colorizer.set_range (label->m_state_idx);
+               pp_string (m_pp, label->m_text.m_buffer);
+               m_colorizer.set_normal_text ();
+               column += label->m_length;
+             }
+           else if (label->m_column != last_vbar)
+             {
+               gcc_assert (column <= label->m_column);
+               move_to_column (&column, label->m_column, true);
+               m_colorizer.set_range (label->m_state_idx);
+               pp_character (m_pp, '|');
+               m_colorizer.set_normal_text ();
+               last_vbar = column;
+               column++;
+             }
+         }
+       print_newline ();
+      }
+    }
+
+  /* Clean up.  */
+  {
+    line_label *label;
+    FOR_EACH_VEC_ELT (labels, i, label)
+      label->m_text.maybe_free ();
+  }
+}
+
 /* If there are any fixit hints inserting new lines before source line ROW,
    print them.
 
@@ -1316,6 +1607,7 @@ layout::print_leading_fixits (linenum_type row)
             helps them stand out from each other, and from
             the surrounding text.  */
          m_colorizer.set_normal_text ();
+         start_annotation_line ('+');
          pp_character (m_pp, '+');
          m_colorizer.set_fixit_insert ();
          /* Print all but the trailing newline of the fix-it hint.
@@ -1433,8 +1725,9 @@ layout::annotation_line_showed_range_p (linenum_type line, int start_column,
 
 /* A range of columns within a line.  */
 
-struct column_range
+class column_range
 {
+public:
   column_range (int start_, int finish_) : start (start_), finish (finish_)
   {
     /* We must have either a range, or an insertion.  */
@@ -1486,8 +1779,9 @@ get_printed_columns (const fixit_hint *hint)
    instances that affected the line, potentially consolidating hints
    into corrections to make the result easier for the user to read.  */
 
-struct correction
+class correction
 {
+public:
   correction (column_range affected_columns,
              column_range printed_columns,
              const char *new_text, size_t new_text_len)
@@ -1564,8 +1858,9 @@ correction::ensure_terminated ()
    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)
   {}
@@ -1591,8 +1886,9 @@ line_corrections::~line_corrections ()
 /* 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); }
@@ -1712,6 +2008,9 @@ layout::print_trailing_fixits (linenum_type row)
   correction *c;
   int column = m_x_offset;
 
+  if (!corrections.m_corrections.is_empty ())
+    start_annotation_line ();
+
   FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
     {
       /* For now we assume each fixit hint can only touch one line.  */
@@ -1719,7 +2018,7 @@ layout::print_trailing_fixits (linenum_type row)
        {
          /* This assumes the insertion just affects one line.  */
          int start_column = c->m_printed_columns.start;
-         move_to_column (&column, start_column);
+         move_to_column (&column, start_column, true);
          m_colorizer.set_fixit_insert ();
          pp_string (m_pp, c->m_text);
          m_colorizer.set_normal_text ();
@@ -1737,7 +2036,7 @@ layout::print_trailing_fixits (linenum_type row)
                                               finish_column)
              || c->m_len == 0)
            {
-             move_to_column (&column, start_column);
+             move_to_column (&column, start_column, true);
              m_colorizer.set_fixit_delete ();
              for (; column <= finish_column; column++)
                pp_character (m_pp, '-');
@@ -1748,7 +2047,7 @@ layout::print_trailing_fixits (linenum_type row)
             a new line) if we have actual replacement text.  */
          if (c->m_len > 0)
            {
-             move_to_column (&column, start_column);
+             move_to_column (&column, start_column, true);
              m_colorizer.set_fixit_insert ();
              pp_string (m_pp, c->m_text);
              m_colorizer.set_normal_text ();
@@ -1758,7 +2057,7 @@ layout::print_trailing_fixits (linenum_type row)
     }
 
   /* Add a trailing newline, if necessary.  */
-  move_to_column (&column, 0);
+  move_to_column (&column, 0, false);
 }
 
 /* Disable any colorization and emit a newline.  */
@@ -1786,13 +2085,18 @@ layout::get_state_at_point (/* Inputs.  */
   int i;
   FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
     {
+      if (range->m_range_display_kind == SHOW_LINES_WITHOUT_RANGE)
+       /* Bail out early, so that such ranges don't affect underlining or
+          source colorization.  */
+       continue;
+
       if (range->contains_point (row, column))
        {
          out_state->range_idx = i;
 
          /* Are we at the range's caret?  is it visible? */
          out_state->draw_caret_p = false;
-         if (range->m_show_caret_p
+         if (range->m_range_display_kind == SHOW_RANGE_WITH_CARET
              && row == range->m_caret.m_line
              && column == range->m_caret.m_column)
            out_state->draw_caret_p = true;
@@ -1855,15 +2159,18 @@ layout::get_x_bound_for_row (linenum_type row, int caret_column,
 
 /* Given *COLUMN as an x-coordinate, print spaces to position
    successive output at DEST_COLUMN, printing a newline if necessary,
-   and updating *COLUMN.  */
+   and updating *COLUMN.  If ADD_LEFT_MARGIN, then print the (empty)
+   left margin after any newline.  */
 
 void
-layout::move_to_column (int *column, int dest_column)
+layout::move_to_column (int *column, int dest_column, bool add_left_margin)
 {
   /* Start a new line if we need to.  */
   if (*column > dest_column)
     {
       print_newline ();
+      if (add_left_margin)
+       start_annotation_line ();
       *column = m_x_offset;
     }
 
@@ -1883,6 +2190,7 @@ layout::show_ruler (int max_column) const
   /* Hundreds.  */
   if (max_column > 99)
     {
+      start_annotation_line ();
       pp_space (m_pp);
       for (int column = 1 + m_x_offset; column <= max_column; column++)
        if (column % 10 == 0)
@@ -1893,6 +2201,7 @@ layout::show_ruler (int max_column) const
     }
 
   /* Tens.  */
+  start_annotation_line ();
   pp_space (m_pp);
   for (int column = 1 + m_x_offset; column <= max_column; column++)
     if (column % 10 == 0)
@@ -1902,6 +2211,7 @@ layout::show_ruler (int max_column) const
   pp_newline (m_pp);
 
   /* Units.  */
+  start_annotation_line ();
   pp_space (m_pp);
   for (int column = 1 + m_x_offset; column <= max_column; column++)
     pp_character (m_pp, '0' + (column % 10));
@@ -1924,6 +2234,8 @@ layout::print_line (linenum_type row)
   print_source_line (row, line.get_buffer (), line.length (), &lbounds);
   if (should_print_annotation_line_p (row))
     print_annotation_line (row, lbounds);
+  if (m_show_labels_p)
+    print_any_labels (row);
   print_trailing_fixits (row);
 }
 
@@ -1943,11 +2255,11 @@ gcc_rich_location::add_location_if_nearby (location_t loc)
   layout layout (global_dc, this, DK_ERROR);
   location_range loc_range;
   loc_range.m_loc = loc;
-  loc_range.m_show_caret_p = false;
-  if (!layout.maybe_add_location_range (&loc_range, true))
+  loc_range.m_range_display_kind = SHOW_RANGE_WITHOUT_CARET;
+  if (!layout.maybe_add_location_range (&loc_range, 0, true))
     return false;
 
-  add_range (loc, false);
+  add_range (loc);
   return true;
 }
 
@@ -1986,13 +2298,27 @@ diagnostic_show_locus (diagnostic_context * context,
        line_span_idx++)
     {
       const line_span *line_span = layout.get_line_span (line_span_idx);
-      if (layout.print_heading_for_line_span_index_p (line_span_idx))
+      if (context->show_line_numbers_p)
        {
-         expanded_location exploc = layout.get_expanded_location (line_span);
-         context->start_span (context, exploc);
+         /* With line numbers, we should show whenever the line-numbering
+            "jumps".  */
+         if (line_span_idx > 0)
+           layout.print_gap_in_line_numbering ();
        }
-      linenum_type last_line = line_span->get_last_line ();
-      for (linenum_type row = line_span->get_first_line ();
+      else
+       {
+         /* Without line numbers, we print headings for some line spans.  */
+         if (layout.print_heading_for_line_span_index_p (line_span_idx))
+           {
+             expanded_location exploc
+               = layout.get_expanded_location (line_span);
+             context->start_span (context, exploc);
+           }
+       }
+      /* Iterate over the lines within this span (using linenum_arith_t to
+        avoid overflow with 0xffffffff causing an infinite loop).  */
+      linenum_arith_t last_line = line_span->get_last_line ();
+      for (linenum_arith_t row = line_span->get_first_line ();
           row <= last_line; row++)
        layout.print_line (row);
     }
@@ -2085,8 +2411,8 @@ test_one_liner_multiple_carets_and_ranges ()
   dc.caret_chars[2] = 'C';
 
   rich_location richloc (line_table, foo);
-  richloc.add_range (bar, true);
-  richloc.add_range (field, true);
+  richloc.add_range (bar, SHOW_RANGE_WITH_CARET);
+  richloc.add_range (field, SHOW_RANGE_WITH_CARET);
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   ASSERT_STREQ ("\n"
                " foo = bar.field;\n"
@@ -2207,7 +2533,7 @@ test_one_liner_fixit_replace_equal_secondary_range ()
   location_t finish = linemap_position_for_column (line_table, 15);
   rich_location richloc (line_table, equals);
   location_t field = make_location (start, start, finish);
-  richloc.add_range (field, false);
+  richloc.add_range (field);
   richloc.add_fixit_replace (field, "m_field");
   diagnostic_show_locus (&dc, &richloc, DK_ERROR);
   /* The replacement range is indicated in the annotation line,
@@ -2330,6 +2656,157 @@ test_one_liner_many_fixits_2 ()
                pp_formatted_text (dc.printer));
 }
 
+/* Test of labeling the ranges within a rich_location.  */
+
+static void
+test_one_liner_labels ()
+{
+  location_t foo
+    = make_location (linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 1),
+                    linemap_position_for_column (line_table, 3));
+  location_t bar
+    = make_location (linemap_position_for_column (line_table, 7),
+                    linemap_position_for_column (line_table, 7),
+                    linemap_position_for_column (line_table, 9));
+  location_t field
+    = make_location (linemap_position_for_column (line_table, 11),
+                    linemap_position_for_column (line_table, 11),
+                    linemap_position_for_column (line_table, 15));
+
+  /* Example where all the labels fit on one line.  */
+  {
+    text_range_label label0 ("0");
+    text_range_label label1 ("1");
+    text_range_label label2 ("2");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    {
+      test_diagnostic_context dc;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ ("\n"
+                   " foo = bar.field;\n"
+                   " ^~~   ~~~ ~~~~~\n"
+                   " |     |   |\n"
+                   " 0     1   2\n",
+                   pp_formatted_text (dc.printer));
+    }
+
+    /* Verify that we can disable label-printing.  */
+    {
+      test_diagnostic_context dc;
+      dc.show_labels_p = false;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ ("\n"
+                   " foo = bar.field;\n"
+                   " ^~~   ~~~ ~~~~~\n",
+                   pp_formatted_text (dc.printer));
+    }
+  }
+
+  /* Example where the labels need extra lines.  */
+  {
+    text_range_label label0 ("label 0");
+    text_range_label label1 ("label 1");
+    text_range_label label2 ("label 2");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 " foo = bar.field;\n"
+                 " ^~~   ~~~ ~~~~~\n"
+                 " |     |   |\n"
+                 " |     |   label 2\n"
+                 " |     label 1\n"
+                 " label 0\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of boundary conditions: label 0 and 1 have just enough clearance,
+     but label 1 just touches label 2.  */
+  {
+    text_range_label label0 ("aaaaa");
+    text_range_label label1 ("bbbb");
+    text_range_label label2 ("c");
+    gcc_rich_location richloc (foo, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 " foo = bar.field;\n"
+                 " ^~~   ~~~ ~~~~~\n"
+                 " |     |   |\n"
+                 " |     |   c\n"
+                 " aaaaa bbbb\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Example of out-of-order ranges (thus requiring a sort).  */
+  {
+    text_range_label label0 ("0");
+    text_range_label label1 ("1");
+    text_range_label label2 ("2");
+    gcc_rich_location richloc (field, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 " foo = bar.field;\n"
+                 " ~~~   ~~~ ^~~~~\n"
+                 " |     |   |\n"
+                 " 2     1   0\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Ensure we don't ICE if multiple ranges with labels are on
+     the same point.  */
+  {
+    text_range_label label0 ("label 0");
+    text_range_label label1 ("label 1");
+    text_range_label label2 ("label 2");
+    gcc_rich_location richloc (bar, &label0);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
+    richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label2);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 " foo = bar.field;\n"
+                 "       ^~~\n"
+                 "       |\n"
+                 "       label 2\n"
+                 "       label 1\n"
+                 "       label 0\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* Verify that a NULL result from range_label::get_text is
+     handled gracefully.  */
+  {
+    text_range_label label (NULL);
+    gcc_rich_location richloc (bar, &label);
+
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 " foo = bar.field;\n"
+                 "       ^~~\n",
+                 pp_formatted_text (dc.printer));
+   }
+
+  /* TODO: example of formatted printing (needs to be in
+     gcc-rich-location.c due to Makefile.in issues).  */
+}
+
 /* Run the various one-liner tests.  */
 
 static void
@@ -2366,6 +2843,7 @@ test_diagnostic_show_locus_one_liner (const line_table_case &case_)
   test_one_liner_fixit_validation_adhoc_locations ();
   test_one_liner_many_fixits_1 ();
   test_one_liner_many_fixits_2 ();
+  test_one_liner_labels ();
 }
 
 /* Verify that gcc_rich_location::add_location_if_nearby works.  */
@@ -2508,6 +2986,29 @@ test_diagnostic_show_locus_fixit_lines (const line_table_case &case_)
                  "                         =\n",
                  pp_formatted_text (dc.printer));
   }
+
+  /* As above, but verify the behavior of multiple line spans
+     with line-numbering enabled.  */
+  {
+    const location_t y
+      = linemap_position_for_line_and_column (line_table, ord_map, 3, 24);
+    const location_t colon
+      = linemap_position_for_line_and_column (line_table, ord_map, 6, 25);
+    rich_location richloc (line_table, colon);
+    richloc.add_fixit_insert_before (y, ".");
+    richloc.add_fixit_replace (colon, "=");
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 "    3 |                        y\n"
+                 "      |                        .\n"
+                 "......\n"
+                 "    6 |                         : 0.0};\n"
+                 "      |                         ^\n"
+                 "      |                         =\n",
+                 pp_formatted_text (dc.printer));
+  }
 }
 
 
@@ -2977,13 +3478,31 @@ test_fixit_insert_containing_newline (const line_table_case &case_)
   {
     rich_location richloc (line_table, case_loc);
     richloc.add_fixit_insert_before (line_start, "      break;\n");
-    test_diagnostic_context dc;
-    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-    ASSERT_STREQ ("\n"
-                 "+      break;\n"
-                 "     case 'b':\n"
-                 "     ^~~~~~~~~\n",
-                 pp_formatted_text (dc.printer));
+
+    /* Without line numbers.  */
+    {
+      test_diagnostic_context dc;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ ("\n"
+                   "       x = a;\n"
+                   "+      break;\n"
+                   "     case 'b':\n"
+                   "     ^~~~~~~~~\n",
+                   pp_formatted_text (dc.printer));
+    }
+
+    /* With line numbers.  */
+    {
+      test_diagnostic_context dc;
+      dc.show_line_numbers_p = true;
+      diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+      ASSERT_STREQ ("\n"
+                   "    2 |       x = a;\n"
+                   "  +++ |+      break;\n"
+                   "    3 |     case 'b':\n"
+                   "      |     ^~~~~~~~~\n",
+                   pp_formatted_text (dc.printer));
+    }
   }
 
   /* Verify that attempts to add text with a newline fail when the
@@ -3040,16 +3559,33 @@ test_fixit_insert_containing_newline_2 (const line_table_case &case_)
   if (putchar_finish > LINE_MAP_MAX_LOCATION_WITH_COLS)
     return;
 
-  test_diagnostic_context dc;
-  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
-  ASSERT_STREQ ("\n"
-               "FILENAME:1:1:\n"
-               "+#include <stdio.h>\n"
-               " test (int ch)\n"
-               "FILENAME:3:2:\n"
-               "  putchar (ch);\n"
-               "  ^~~~~~~\n",
-               pp_formatted_text (dc.printer));
+  {
+    test_diagnostic_context dc;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 "FILENAME:1:1:\n"
+                 "+#include <stdio.h>\n"
+                 " test (int ch)\n"
+                 "FILENAME:3:2:\n"
+                 "  putchar (ch);\n"
+                 "  ^~~~~~~\n",
+                 pp_formatted_text (dc.printer));
+  }
+
+  /* With line-numbering, the line spans are close enough to be
+     consolidated, since it makes little sense to skip line 2.  */
+  {
+    test_diagnostic_context dc;
+    dc.show_line_numbers_p = true;
+    diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+    ASSERT_STREQ ("\n"
+                 "  +++ |+#include <stdio.h>\n"
+                 "    1 | test (int ch)\n"
+                 "    2 | {\n"
+                 "    3 |  putchar (ch);\n"
+                 "      |  ^~~~~~~\n",
+                 pp_formatted_text (dc.printer));
+  }
 }
 
 /* Replacement fix-it hint containing a newline.
@@ -3139,6 +3675,52 @@ test_fixit_deletion_affecting_newline (const line_table_case &case_)
                pp_formatted_text (dc.printer));
 }
 
+/* Verify that line numbers are correctly printed for the case of
+   a multiline range in which the width of the line numbers changes
+   (e.g. from "9" to "10").  */
+
+static void
+test_line_numbers_multiline_range ()
+{
+  /* Create a tempfile and write some text to it.  */
+  pretty_printer pp;
+  for (int i = 0; i < 20; i++)
+    /* .........0000000001111111.
+   .............1234567890123456.  */
+    pp_printf (&pp, "this is line %i\n", i + 1);
+  temp_source_file tmp (SELFTEST_LOCATION, ".txt", pp_formatted_text (&pp));
+  line_table_test ltt;
+
+  const line_map_ordinary *ord_map = linemap_check_ordinary
+    (linemap_add (line_table, LC_ENTER, false, tmp.get_filename (), 0));
+  linemap_line_start (line_table, 1, 100);
+
+  /* Create a multi-line location, starting at the "line" of line 9, with
+     a caret on the "is" of line 10, finishing on the "this" line 11.  */
+
+  location_t start
+    = linemap_position_for_line_and_column (line_table, ord_map, 9, 9);
+  location_t caret
+    = linemap_position_for_line_and_column (line_table, ord_map, 10, 6);
+  location_t finish
+    = linemap_position_for_line_and_column (line_table, ord_map, 11, 4);
+  location_t loc = make_location (caret, start, finish);
+
+  test_diagnostic_context dc;
+  dc.show_line_numbers_p = true;
+  dc.min_margin_width = 0;
+  gcc_rich_location richloc (loc);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ ("\n"
+               " 9 | this is line 9\n"
+               "   |         ~~~~~~\n"
+               "10 | this is line 10\n"
+               "   | ~~~~~^~~~~~~~~~\n"
+               "11 | this is line 11\n"
+               "   | ~~~~  \n",
+               pp_formatted_text (dc.printer));
+}
+
 /* Run all of the selftests within this file.  */
 
 void
@@ -3164,8 +3746,14 @@ diagnostic_show_locus_c_tests ()
   for_each_line_table_case (test_fixit_insert_containing_newline_2);
   for_each_line_table_case (test_fixit_replace_containing_newline);
   for_each_line_table_case (test_fixit_deletion_affecting_newline);
+
+  test_line_numbers_multiline_range ();
 }
 
 } // namespace selftest
 
 #endif /* #if CHECKING_P */
+
+#if __GNUC__ >= 10
+#  pragma GCC diagnostic pop
+#endif