diff gcc/diagnostic-show-locus.c @ 131:84e7813d76e9

gcc-8.2
author mir3636
date Thu, 25 Oct 2018 07:37:49 +0900
parents 04ced10e8804
children 1830386684a0
line wrap: on
line diff
--- a/gcc/diagnostic-show-locus.c	Fri Oct 27 22:46:09 2017 +0900
+++ b/gcc/diagnostic-show-locus.c	Thu Oct 25 07:37:49 2018 +0900
@@ -1,5 +1,5 @@
 /* Diagnostic subroutines for printing source-code
-   Copyright (C) 1999-2017 Free Software Foundation, Inc.
+   Copyright (C) 1999-2018 Free Software Foundation, Inc.
    Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
 
 This file is part of GCC.
@@ -29,6 +29,7 @@
 #include "diagnostic-color.h"
 #include "gcc-rich-location.h"
 #include "selftest.h"
+#include "selftest-diagnostic.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -114,7 +115,7 @@
   : m_line (exploc.line),
     m_column (exploc.column) {}
 
-  int m_line;
+  linenum_type m_line;
   int m_column;
 };
 
@@ -125,16 +126,20 @@
  public:
   layout_range (const expanded_location *start_exploc,
 		const expanded_location *finish_exploc,
-		bool show_caret_p,
-		const expanded_location *caret_exploc);
-
-  bool contains_point (int row, int column) const;
-  bool intersects_line_p (int row) const;
+		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
@@ -171,16 +176,52 @@
   {
     const line_span *ls1 = (const line_span *)p1;
     const line_span *ls2 = (const line_span *)p2;
-    int first_line_diff = (int)ls1->m_first_line - (int)ls2->m_first_line;
-    if (first_line_diff)
-      return first_line_diff;
-    return (int)ls1->m_last_line - (int)ls2->m_last_line;
+    int first_line_cmp = compare (ls1->m_first_line, ls2->m_first_line);
+    if (first_line_cmp)
+      return first_line_cmp;
+    return compare (ls1->m_last_line, ls2->m_last_line);
   }
 
   linenum_type m_first_line;
   linenum_type m_last_line;
 };
 
+#if CHECKING_P
+
+/* Selftests for line_span.  */
+
+static void
+test_line_span ()
+{
+  line_span line_one (1, 1);
+  ASSERT_EQ (1, line_one.get_first_line ());
+  ASSERT_EQ (1, line_one.get_last_line ());
+  ASSERT_FALSE (line_one.contains_line_p (0));
+  ASSERT_TRUE (line_one.contains_line_p (1));
+  ASSERT_FALSE (line_one.contains_line_p (2));
+
+  line_span lines_1_to_3 (1, 3);
+  ASSERT_EQ (1, lines_1_to_3.get_first_line ());
+  ASSERT_EQ (3, lines_1_to_3.get_last_line ());
+  ASSERT_TRUE (lines_1_to_3.contains_line_p (1));
+  ASSERT_TRUE (lines_1_to_3.contains_line_p (3));
+
+  ASSERT_EQ (0, line_span::comparator (&line_one, &line_one));
+  ASSERT_GT (line_span::comparator (&lines_1_to_3, &line_one), 0);
+  ASSERT_LT (line_span::comparator (&line_one, &lines_1_to_3), 0);
+
+  /* A linenum > 2^31.  */
+  const linenum_type LARGEST_LINE = 0xffffffff;
+  line_span largest_line (LARGEST_LINE, LARGEST_LINE);
+  ASSERT_EQ (LARGEST_LINE, largest_line.get_first_line ());
+  ASSERT_EQ (LARGEST_LINE, largest_line.get_last_line ());
+
+  ASSERT_GT (line_span::comparator (&largest_line, &line_one), 0);
+  ASSERT_LT (line_span::comparator (&line_one, &largest_line), 0);
+}
+
+#endif /* #if CHECKING_P */
+
 /* A class to control the overall layout when printing a diagnostic.
 
    The layout is determined within the constructor.
@@ -197,27 +238,31 @@
 	  diagnostic_t diagnostic_kind);
 
   bool maybe_add_location_range (const location_range *loc_range,
+				 unsigned original_idx,
 				 bool restrict_to_current_line_spans);
 
   int get_num_line_spans () const { return m_line_spans.length (); }
   const line_span *get_line_span (int idx) const { return &m_line_spans[idx]; }
 
+  void print_gap_in_line_numbering ();
   bool print_heading_for_line_span_index_p (int line_span_idx) const;
 
   expanded_location get_expanded_location (const line_span *) const;
 
-  void print_line (int row);
+  void print_line (linenum_type row);
 
  private:
-  bool will_show_line_p (int row) const;
-  void print_leading_fixits (int row);
-  void print_source_line (int row, const char *line, int line_width,
+  bool will_show_line_p (linenum_type row) const;
+  void print_leading_fixits (linenum_type row);
+  void print_source_line (linenum_type row, const char *line, int line_width,
 			  line_bounds *lbounds_out);
-  bool should_print_annotation_line_p (int row) const;
-  void print_annotation_line (int row, const line_bounds lbounds);
-  void print_trailing_fixits (int row);
-
-  bool annotation_line_showed_range_p (int line, int start_column,
+  bool should_print_annotation_line_p (linenum_type row) const;
+  void start_annotation_line (char margin_char = ' ') const;
+  void print_annotation_line (linenum_type row, const line_bounds lbounds);
+  void print_any_labels (linenum_type row);
+  void print_trailing_fixits (linenum_type row);
+
+  bool annotation_line_showed_range_p (linenum_type line, int start_column,
 				       int finish_column) const;
   void show_ruler (int max_column) const;
 
@@ -229,29 +274,31 @@
 
   bool
   get_state_at_point (/* Inputs.  */
-		      int row, int column,
+		      linenum_type row, int column,
 		      int first_non_ws, int last_non_ws,
 		      /* Outputs.  */
 		      point_state *out_state);
 
   int
-  get_x_bound_for_row (int row, int caret_column,
+  get_x_bound_for_row (linenum_type row, int caret_column,
 		       int last_non_ws);
 
   void
-  move_to_column (int *column, int dest_column);
+  move_to_column (int *column, int dest_column, bool add_left_margin);
 
  private:
   diagnostic_context *m_context;
   pretty_printer *m_pp;
-  diagnostic_t m_diagnostic_kind;
   location_t m_primary_loc;
   expanded_location m_exploc;
   colorizer m_colorizer;
   bool m_colorize_source_p;
+  bool m_show_labels_p;
+  bool m_show_line_numbers_p;
   auto_vec <layout_range> m_layout_ranges;
   auto_vec <const fixit_hint *> m_fixit_hints;
   auto_vec <line_span> m_line_spans;
+  int m_linenum_width;
   int m_x_offset;
 };
 
@@ -367,12 +414,16 @@
 
 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)
 {
 }
 
@@ -416,7 +467,7 @@
    - 'a' indicates a subsequent point *after* the range.  */
 
 bool
-layout_range::contains_point (int row, int column) const
+layout_range::contains_point (linenum_type row, int column) const
 {
   gcc_assert (m_start.m_line <= m_finish.m_line);
   /* ...but the equivalent isn't true for the columns;
@@ -477,7 +528,7 @@
 /* Does this layout_range contain any part of line ROW?  */
 
 bool
-layout_range::intersects_line_p (int row) const
+layout_range::intersects_line_p (linenum_type row) const
 {
   gcc_assert (m_start.m_line <= m_finish.m_line);
   if (row < m_start.m_line)
@@ -498,8 +549,8 @@
     = {"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
@@ -638,7 +689,7 @@
   while (result > 0)
     {
       char ch = line[result - 1];
-      if (ch == ' ' || ch == '\t')
+      if (ch == ' ' || ch == '\t' || ch == '\r')
 	result--;
       else
 	break;
@@ -647,7 +698,8 @@
   gcc_assert (result <= line_width);
   gcc_assert (result == 0 ||
 	      (line[result - 1] != ' '
-	       && line[result -1] != '\t'));
+	       && line[result -1] != '\t'
+	       && line[result -1] != '\r'));
   return result;
 }
 
@@ -672,9 +724,11 @@
   assert_eq ("", 0);
   assert_eq (" ", 0);
   assert_eq ("\t", 0);
+  assert_eq ("\r", 0);
   assert_eq ("hello world", 11);
   assert_eq ("hello world     ", 11);
   assert_eq ("hello world     \t\t  ", 11);
+  assert_eq ("hello world\r", 11);
 }
 
 #endif /* #if CHECKING_P */
@@ -765,6 +819,56 @@
   return hint_a->get_start_loc () - hint_b->get_start_loc ();
 }
 
+/* Get the number of digits in the decimal representation
+   of VALUE.  */
+
+static int
+num_digits (int value)
+{
+  /* Perhaps simpler to use log10 for this, but doing it this way avoids
+     using floating point.  */
+  gcc_assert (value >= 0);
+
+  if (value == 0)
+    return 1;
+
+  int digits = 0;
+  while (value > 0)
+    {
+      digits++;
+      value /= 10;
+    }
+  return digits;
+}
+
+
+#if CHECKING_P
+
+/* Selftest for num_digits.  */
+
+static void
+test_num_digits ()
+{
+  ASSERT_EQ (1, num_digits (0));
+  ASSERT_EQ (1, num_digits (9));
+  ASSERT_EQ (2, num_digits (10));
+  ASSERT_EQ (2, num_digits (99));
+  ASSERT_EQ (3, num_digits (100));
+  ASSERT_EQ (3, num_digits (999));
+  ASSERT_EQ (4, num_digits (1000));
+  ASSERT_EQ (4, num_digits (9999));
+  ASSERT_EQ (5, num_digits (10000));
+  ASSERT_EQ (5, num_digits (99999));
+  ASSERT_EQ (6, num_digits (100000));
+  ASSERT_EQ (6, num_digits (999999));
+  ASSERT_EQ (7, num_digits (1000000));
+  ASSERT_EQ (7, num_digits (9999999));
+  ASSERT_EQ (8, num_digits (10000000));
+  ASSERT_EQ (8, num_digits (99999999));
+}
+
+#endif /* #if CHECKING_P */
+
 /* Implementation of class layout.  */
 
 /* Constructor for class layout.
@@ -781,14 +885,16 @@
 		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++)
@@ -796,7 +902,7 @@
       /* 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
@@ -814,20 +920,34 @@
   /* 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.  */
-  int max_width = m_context->caret_max_width;
-  int line_width;
-  const char *line = location_get_source_line (m_exploc.file, m_exploc.line,
-					       &line_width);
-  if (line && m_exploc.column <= line_width)
+  size_t max_width = m_context->caret_max_width;
+  char_span line = location_get_source_line (m_exploc.file, m_exploc.line);
+  if (line && (size_t)m_exploc.column <= line.length ())
     {
-      int right_margin = CARET_LINE_MARGIN;
-      int column = m_exploc.column;
-      right_margin = MIN (line_width - column, right_margin);
+      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_width >= max_width && column > right_margin)
+      if (line.length () >= max_width && column > right_margin)
 	m_x_offset = column - right_margin;
       gcc_assert (m_x_offset >= 0);
     }
@@ -839,6 +959,9 @@
 /* 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
@@ -848,6 +971,7 @@
 
 bool
 layout::maybe_add_location_range (const location_range *loc_range,
+				  unsigned original_idx,
 				  bool restrict_to_current_line_spans)
 {
   gcc_assert (loc_range);
@@ -872,13 +996,13 @@
     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.  */
@@ -886,7 +1010,8 @@
 
   /* 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
@@ -922,7 +1047,7 @@
 	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;
     }
@@ -936,7 +1061,7 @@
 /* Return true iff ROW is within one of the line spans for this layout.  */
 
 bool
-layout::will_show_line_p (int row) const
+layout::will_show_line_p (linenum_type row) const
 {
   for (int line_span_idx = 0; line_span_idx < get_num_line_spans ();
        line_span_idx++)
@@ -948,6 +1073,20 @@
   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.  */
 
@@ -1030,7 +1169,16 @@
 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 ()));
 }
 
@@ -1045,21 +1193,34 @@
    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 ()
@@ -1099,7 +1260,8 @@
       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 (next->m_first_line <= current->m_last_line + 1 + merger_distance)
 	{
 	  /* We can merge them. */
 	  if (next->m_last_line > current->m_last_line)
@@ -1134,7 +1296,7 @@
    is its width.  */
 
 void
-layout::print_source_line (int row, const char *line, int line_width,
+layout::print_source_line (linenum_type row, const char *line, int line_width,
 			   line_bounds *lbounds_out)
 {
   m_colorizer.set_normal_text ();
@@ -1145,7 +1307,15 @@
 							   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;
@@ -1175,8 +1345,8 @@
 	  else
 	    m_colorizer.set_normal_text ();
 	}
-      char c = *line == '\t' ? ' ' : *line;
-      if (c == '\0')
+      char c = *line;
+      if (c == '\0' || c == '\t' || c == '\r')
 	c = ' ';
       if (c != ' ')
 	{
@@ -1197,26 +1367,51 @@
    i.e. if any of m_layout_ranges contains ROW.  */
 
 bool
-layout::should_print_annotation_line_p (int row) const
+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.  */
 
 void
-layout::print_annotation_line (int row, const line_bounds lbounds)
+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;
@@ -1252,6 +1447,180 @@
   print_newline ();
 }
 
+/* Implementation detail of layout::print_any_labels.
+
+   A label within the given row of source.  */
+
+struct line_label
+{
+  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.
 
@@ -1259,7 +1628,7 @@
    itself, with a leading '+'.  */
 
 void
-layout::print_leading_fixits (int row)
+layout::print_leading_fixits (linenum_type row)
 {
   for (unsigned int i = 0; i < m_fixit_hints.length (); i++)
     {
@@ -1278,6 +1647,7 @@
 	     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.
@@ -1297,7 +1667,7 @@
    the exact range from START_COLUMN to FINISH_COLUMN.  */
 
 bool
-layout::annotation_line_showed_range_p (int line, int start_column,
+layout::annotation_line_showed_range_p (linenum_type line, int start_column,
 					int finish_column) const
 {
   layout_range *range;
@@ -1443,26 +1813,6 @@
     }
 }
 
-/* A struct capturing the bounds of a buffer, to allow for run-time
-   bounds-checking in a checked build.  */
-
-struct char_span
-{
-  char_span (const char *ptr, size_t n_elts) : m_ptr (ptr), m_n_elts (n_elts) {}
-
-  char_span subspan (int offset, int n_elts)
-  {
-    gcc_assert (offset >= 0);
-    gcc_assert (offset < (int)m_n_elts);
-    gcc_assert (n_elts >= 0);
-    gcc_assert (offset + n_elts <= (int)m_n_elts);
-    return char_span (m_ptr + offset, n_elts);
-  }
-
-  const char *m_ptr;
-  size_t m_n_elts;
-};
-
 /* A correction on a particular line.
    This describes a plan for how to print one or more fixit_hint
    instances that affected the line, potentially consolidating hints
@@ -1494,9 +1844,9 @@
   void overwrite (int dst_offset, const char_span &src_span)
   {
     gcc_assert (dst_offset >= 0);
-    gcc_assert (dst_offset + src_span.m_n_elts < m_alloc_sz);
-    memcpy (m_text + dst_offset, src_span.m_ptr,
-	    src_span.m_n_elts);
+    gcc_assert (dst_offset + src_span.length () < m_alloc_sz);
+    memcpy (m_text + dst_offset, src_span.get_buffer (),
+	    src_span.length ());
   }
 
   /* If insert, then start: the column before which the text
@@ -1548,7 +1898,7 @@
 
 struct line_corrections
 {
-  line_corrections (const char *filename, int row)
+  line_corrections (const char *filename, linenum_type row)
   : m_filename (filename), m_row (row)
   {}
   ~line_corrections ();
@@ -1556,7 +1906,7 @@
   void add_hint (const fixit_hint *hint);
 
   const char *m_filename;
-  int m_row;
+  linenum_type m_row;
   auto_vec <correction *> m_corrections;
 };
 
@@ -1587,7 +1937,9 @@
 
 source_line::source_line (const char *filename, int line)
 {
-  chars = location_get_source_line (filename, line, &width);
+  char_span span = location_get_source_line (filename, line);
+  chars = span.get_buffer ();
+  width = span.length ();
 }
 
 /* Add HINT to the corrections for this line.
@@ -1670,7 +2022,7 @@
    in layout::print_leading_fixits.  */
 
 void
-layout::print_trailing_fixits (int row)
+layout::print_trailing_fixits (linenum_type row)
 {
   /* Build a list of correction instances for the line,
      potentially consolidating hints (for the sake of readability).  */
@@ -1690,7 +2042,10 @@
   /* Now print the corrections.  */
   unsigned i;
   correction *c;
-  int column = 0;
+  int column = m_x_offset;
+
+  if (!corrections.m_corrections.is_empty ())
+    start_annotation_line ();
 
   FOR_EACH_VEC_ELT (corrections.m_corrections, i, c)
     {
@@ -1699,7 +2054,7 @@
 	{
 	  /* 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 ();
@@ -1717,7 +2072,7 @@
 					       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, '-');
@@ -1728,7 +2083,7 @@
 	     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 ();
@@ -1738,7 +2093,7 @@
     }
 
   /* Add a trailing newline, if necessary.  */
-  move_to_column (&column, 0);
+  move_to_column (&column, 0, false);
 }
 
 /* Disable any colorization and emit a newline.  */
@@ -1757,7 +2112,7 @@
 
 bool
 layout::get_state_at_point (/* Inputs.  */
-			    int row, int column,
+			    linenum_type row, int column,
 			    int first_non_ws, int last_non_ws,
 			    /* Outputs.  */
 			    point_state *out_state)
@@ -1766,13 +2121,18 @@
   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;
@@ -1802,7 +2162,7 @@
    character of source (as determined when printing the source line).  */
 
 int
-layout::get_x_bound_for_row (int row, int caret_column,
+layout::get_x_bound_for_row (linenum_type row, int caret_column,
 			     int last_non_ws_column)
 {
   int result = caret_column + 1;
@@ -1835,16 +2195,19 @@
 
 /* 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 ();
-      *column = 0;
+      if (add_left_margin)
+	start_annotation_line ();
+      *column = m_x_offset;
     }
 
   while (*column < dest_column)
@@ -1863,9 +2226,10 @@
   /* Hundreds.  */
   if (max_column > 99)
     {
+      start_annotation_line ();
       pp_space (m_pp);
       for (int column = 1 + m_x_offset; column <= max_column; column++)
-	if (0 == column % 10)
+	if (column % 10 == 0)
 	  pp_character (m_pp, '0' + (column / 100) % 10);
 	else
 	  pp_space (m_pp);
@@ -1873,15 +2237,17 @@
     }
 
   /* Tens.  */
+  start_annotation_line ();
   pp_space (m_pp);
   for (int column = 1 + m_x_offset; column <= max_column; column++)
-    if (0 == column % 10)
+    if (column % 10 == 0)
       pp_character (m_pp, '0' + (column / 10) % 10);
     else
       pp_space (m_pp);
   pp_newline (m_pp);
 
   /* Units.  */
+  start_annotation_line ();
   pp_space (m_pp);
   for (int column = 1 + m_x_offset; column <= max_column; column++)
     pp_character (m_pp, '0' + (column % 10));
@@ -1893,19 +2259,19 @@
    consisting of any caret/underlines, then any fixits.
    If the source line can't be read, print nothing.  */
 void
-layout::print_line (int row)
+layout::print_line (linenum_type row)
 {
-  int line_width;
-  const char *line = location_get_source_line (m_exploc.file, row,
-					       &line_width);
+  char_span line = location_get_source_line (m_exploc.file, row);
   if (!line)
     return;
 
   line_bounds lbounds;
   print_leading_fixits (row);
-  print_source_line (row, line, line_width, &lbounds);
+  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);
 }
 
@@ -1925,11 +2291,11 @@
   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;
 }
 
@@ -1960,7 +2326,7 @@
 
   context->last_location = loc;
 
-  const char *saved_prefix = pp_get_prefix (context->printer);
+  char *saved_prefix = pp_take_prefix (context->printer);
   pp_set_prefix (context->printer, NULL);
 
   layout layout (context, richloc, diagnostic_kind);
@@ -1968,13 +2334,26 @@
        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)
+	{
+	  /* With line numbers, we should show whenever the line-numbering
+	     "jumps".  */
+	  if (line_span_idx > 0)
+	    layout.print_gap_in_line_numbering ();
+	}
+      else
 	{
-	  expanded_location exploc = layout.get_expanded_location (line_span);
-	  context->start_span (context, exploc);
+	  /* 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);
+	    }
 	}
-      int last_line = line_span->get_last_line ();
-      for (int row = line_span->get_first_line (); row <= last_line; row++)
+      linenum_type last_line = line_span->get_last_line ();
+      for (linenum_type row = line_span->get_first_line ();
+	   row <= last_line; row++)
 	layout.print_line (row);
     }
 
@@ -1987,34 +2366,6 @@
 
 /* Selftests for diagnostic_show_locus.  */
 
-/* Convenience subclass of diagnostic_context for testing
-   diagnostic_show_locus.  */
-
-class test_diagnostic_context : public diagnostic_context
-{
- public:
-  test_diagnostic_context ()
-  {
-    diagnostic_initialize (this, 0);
-    show_caret = true;
-    show_column = true;
-    start_span = start_span_cb;
-  }
-  ~test_diagnostic_context ()
-  {
-    diagnostic_finish (this);
-  }
-
-  /* Implementation of diagnostic_start_span_fn, hiding the
-     real filename (to avoid printing the names of tempfiles).  */
-  static void
-  start_span_cb (diagnostic_context *context, expanded_location exploc)
-  {
-    exploc.file = "FILENAME";
-    default_diagnostic_start_span_fn (context, exploc);
-  }
-};
-
 /* Verify that diagnostic_show_locus works sanely on UNKNOWN_LOCATION.  */
 
 static void
@@ -2094,8 +2445,8 @@
   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"
@@ -2216,7 +2567,7 @@
   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,
@@ -2339,6 +2690,157 @@
 		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
@@ -2375,6 +2877,7 @@
   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.  */
@@ -2517,6 +3020,29 @@
 		  "                         =\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));
+  }
 }
 
 
@@ -2986,13 +3512,31 @@
   {
     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
@@ -3049,16 +3593,33 @@
   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.
@@ -3148,11 +3709,60 @@
 		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
 diagnostic_show_locus_c_tests ()
 {
+  test_line_span ();
+  test_num_digits ();
+
   test_layout_range_for_single_point ();
   test_layout_range_for_single_line ();
   test_layout_range_for_multiple_lines ();
@@ -3171,6 +3781,8 @@
   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