This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC, stage1] Richer source location information for gcc 6 (location ranges etc)


I've been experimenting with revamping our diagnostics to better show
source locations.  Some of the ideas are inspired by Clang's
diagnostics, but I'm going beyond what it does in some areas.

I'm attaching a patch (against r221423), which is very much a
work-in-progress, but in a "release early, release often" spirit I
thought I'd solicit feedback here (obviously this would be for our next
stage 1/gcc 6):

Examples of the output using the patch (some very long lines, sorry):

bitfld1.C:12:15: error: invalid operands of types âchar*â and âunsigned
char:1â to binary âoperator|â
 void *a = s.p | s.f;
           ~~~ ^ ~~~

multiline-rich-location-01.C:13:11: error: no match for âoperator
+â (operand types are âintâ and âsâ)
   return (first_function_with_a_very_long_name ()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           + second_function_with_a_very_long_name ());
           ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Going beyond pure ASCII, here's an example using unicode box-drawing
chars (and showing a macro expansion):

gcc.dg/20150304-01.c: In function âfâ:
gcc.dg/20150304-01.c:4:94: error: invalid operands to binary < (have âstruct mystructâ and âfloatâ)
 #define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
                                                                                          âââ â âââ
gcc.dg/20150304-01.c:11:7: note: in expansion of macro âMYMAXâ
   X = MYMAX(P, F);
       âââââââââââ

An example showing captions on ranges, in this case via right-hand
margins:

multiline-rich-location-02.C: In function âint test(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int)â:
multiline-rich-location-02.C:30:11: error: no match for âoperator+â (operand types are âintâ and âsâ)
   return (first_function_with_a_very_long_name (lorem, ipsum, dolor, sit, amet,â
           ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
                                                 consectetur, adipiscing, elit, â
                                                 ââââââââââââââââââââââââââââââ â
                                                 sed, eiusmod, tempor,          â
                                                 âââââââââââââââââââââ          âtype âintâ
                                                 incididunt, ut, labore, et,    â
                                                 âââââââââââââââââââââââââââ    â
                                                 dolore, magna, aliqua)         â
                                                 ââââââââââââââââââââââ         â
           + second_function_with_a_very_long_name (lorem, ipsum, dolor, sit, // { dg-error "no match" }â
           â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
                                                    amet, consectetur,                                  â
                                                    ââââââââââââââââââ                                  â
                                                    adipiscing, elit, sed,                              â
                                                    ââââââââââââââââââââââ                              âtype âsâ
                                                    eiusmod, tempor, incididunt,                        â
                                                    ââââââââââââââââââââââââââââ                        â
                                                    ut, labore, et, dolore,                             â
                                                    âââââââââââââââââââââââ                             â
                                                    magna, aliqua));                                    â
                                                    ââââââââââââââ                                      â

Potentially we can support locations within string literals, for -Wformat:

pr52952-spurious-trailing-percent.c: In function âfooâ:
pr52952-spurious-trailing-percent.c:7:23: warning: spurious trailing â%â in format [-Wformat=]
   printf("hello world %");
                       â

including ranges:

pr52952-mismatching-argument-type.c: In function âfooâ:
pr52952-mismatching-argument-type.c:7:17: warning: format â%iâ expects argument of type âintâ, but argument 2 has type âconst char *â [-Wformat=]
   printf("hello %i", msg);
                 ââ

potentially for multiline ones:

pr52952-multiline-format-string.c: In function âfâ:
pr52952-multiline-format-string.c:6:12: warning: format â%dâ expects a matching âintâ argument [-Wformat=]
           "%"
            ââ
           "d"
           ââ

You can see colorized versions of these at:
  https://dmalcolm.fedorapeople.org/gcc/2015-03-06/
  https://dmalcolm.fedorapeople.org/gcc/2015-03-09/
  https://dmalcolm.fedorapeople.org/gcc/2015-03-11/
The first two ranges in a diagnostic have distinct colors. I found that
the green of the caret was hard to read on a white background, so I've
changed the caret so that it no longer has its own color; instead its
color is that of the severity, so that e.g. the caret for an error
appears in red, whereas that of a note appears in cyan (see the examples
from 2015-03-09 onwards).  I also colorize the source code based on the
underlying caret/range.

As noted on https://gcc.gnu.org/wiki/Better_Diagnostics there are
several things that need to be done to support range information in
diagnostics:

  (1) Printing accurate column information.
  (2) Location(s) -> source strings interface and machinery.
  (3) Track beg/end locations.
  (4) Changes in the parser to pass down ranges.
  (5) Changes in the testsuite to enable testing ranges.
  (6) Changes in the diagnostics machinery to handle ranges.

The patch so far addresses (1), (5) and (6), and ignores (3) and (4),
(2) seems to already be done.

Proposed changes to diagnostic machinery
****************************************

The patch adds a new class "rich_location", which is a location_t, along
with 0 or more location_ranges.  We can also
have rich_location instances that aren't exactly at a location_t, and
are at a more specific expanded location.

Rich locations are assumed to live on the stack.

>From the POV of writing an error or warning, where you would have
written:

  warning_at (some_location_t, OPT_Wsomething,
             "can't walk whilst chewing gum");

you can now write:

  rich_location richloc (some_location_t);
  warning_at_rich_loc (&richloc, OPT_Wsomething,
                      "can't walk whilst chewing gum");

and indeed, warning_at etc become implemented in terms of
warning_at_rich_loc due to this key underlying change to
diagnostic-core.h:

struct diagnostic_info
 {
   text_info message;
-  location_t location;
+  rich_location *richloc;

Where it gets interesting is that you can add ranges to the richloc,
specifically, the ranges of expressions:

  rich_location richloc (some_location_t);
  richloc.add_expr (walk_expr);
  richloc.add_expr (gum_chewing_expr);
  warning_at_rich_loc (&richloc, OPT_Wsomething,
                      "can't walk whilst chewing gum");

and, optionally, these ranges can have optionally have captions, using
the same printing interface as for the rest of warnings:

  rich_location richloc (some_location_t);
  richloc.add_expr_with_caption (walk_expr,
                                 G_("type %qT"), TREE_TYPE (walk_expr));
  richloc.add_expr_with_caption (gum_chewing_expr,
                                 G_("type %qT"), TREE_TYPE (gum_chewing_expr));
  warning_at_rich_loc (&richloc, OPT_Wsomething,
                      "can't walk whilst chewing gum");

I'm deliberately *not* using overloading for functions using location_t
vs rich_location, instead giving them distinct names:
  warning_at
vs
  warning_at_rich_loc
so that it's easy to identify callsites of the former with a simple
grep.   The plan would be to gradually convert callsites of the former
into the latter, populating them with pertinent range information.

Proposed changes to testsuite
*****************************
We have pre-existing testcases like this:
  typedef struct _GMutex GMutex; // { dg-message "previously declared here"}

generating output like this:
  gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C:4:16: note: 'struct
_GMutex' was previously declared here
where the location of the dg-message determines the expected line at
which the error should be reported.

To handle rich error-reporting, we want to be able to verify that we get
output like this:
  gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C:4:16: note: 'struct _GMutex' was previously declared here
  typedef struct _GMutex GMutex; // { dg-message "previously declared here"}
                 ^~~~~~~
where the compiler's first line of output is as before, but in which it
then echoes the source lines, adding annotations.

We want to be able to write testcases that verify that the emitted
source-and-annotations are sane.

The patch adds a new file multiline.exp, adding directives
dg-begin-multiline-output and dg-end-multiline-output so that you can
write:

/* { dg-begin-multiline-output "" }
  typedef struct _GMutex GMutex;
                 ^~~~~~~
   { dg-end-multiline-output "" } */

A complication here is that the source lines contain comments containing
DejaGnu directives (such as the "dg-message" above).

We punt this somewhat by only matching the beginnings of lines within
the source-quoting lines, so that we don't have to quote the:
   // { dg-message "previously declared here"}
in the above sources.

Kludges and caveats
*******************
I've focused on the presentation and testsuite aspects of this idea.

I've not yet attempted to address generation and storage of the range
information.  Clearly we need some way to capture this without bloating
the time/memory usage of the compiler.

So as a crude "placeholder" workaround, currently the patch hardcodes
the relevant data on a per-testcase basis; it adds two files:
   gcc/location-hacks.c
   gcc/location-hacks.h
to isolate the awfulness.

Obviously I intend to look at doing it properly, but in the meantime,
how do the diagnostic API and the testsuite ideas look?

Other known issues:
  * no ChangeLog yet
  * the patch also currently ignores the ability for offsetting the
location by some column count (I believe this part of the diagnostic API
could be moved to the rich_location)
  * the patch doesn't support the existing logic for handling the
terminal's max width (if any) to ensure that the caret column is within
it; this would need rethinking in the light of range information.
  * color choices may be rather garish
  * various FIXMEs
  * I've only tested the testcases I've touched/added; haven't attempted
to run the full testsuite yet, or bootstrap
  * only attempted with C/C++ frontends so far

Thoughts?
Dave
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index de1f3b6..a0c6f0c 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1304,6 +1304,7 @@ OBJS = \
 	langhooks.o \
 	lcm.o \
 	lists.o \
+	location-hacks.o \
 	loop-doloop.o \
 	loop-init.o \
 	loop-invariant.o \
@@ -1354,6 +1355,7 @@ OBJS = \
 	reload1.o \
 	reorg.o \
 	resource.o \
+	rich-location.o \
 	rtl-chkp.o \
 	rtl-error.o \
 	rtl.o \
@@ -1495,7 +1497,7 @@ OBJS = \
 # Objects in libcommon.a, potentially used by all host binaries and with
 # no target dependencies.
 OBJS-libcommon = diagnostic.o diagnostic-color.o pretty-print.o intl.o \
-	vec.o  input.o version.o
+	vec.o  input.o version.o box-drawing.o
 
 # Objects in libcommon-target.a, used by drivers and by the core
 # compiler and containing target-dependent code.
diff --git a/gcc/box-drawing.c b/gcc/box-drawing.c
new file mode 100644
index 0000000..3dee787
--- /dev/null
+++ b/gcc/box-drawing.c
@@ -0,0 +1,99 @@
+/* Box-drawing, either using Unicode box-drawing chars, or as pure ASCII
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "box-drawing.h"
+
+/* Global singleton instance.  */
+box_drawing g_line_art;
+
+/* Strings for drawing the caret and ranges within diagnostics.
+   There are either UTF-8-encoded Unicode box-drawing characters,
+   or pure ASCII.  */
+
+/* Initializer for box_drawing.
+   This isn't a constructor since the singleton instance is
+   statically-allocated.  */
+void
+box_drawing::init (bool have_utf8)
+{
+  if (have_utf8)
+    {
+      /* For the caret, use:
+	   U+25B2 BLACK UP-POINTING TRIANGLE
+	 which in UTF-8 is: 0xE2 0x96 0xB2.  */
+      caret = "\xE2\x96\xB2";
+
+      /* Underlining text ranges.  */
+
+      /* The start of an underline: the south-western corner of a box:
+	   U+2514 BOX DRAWINGS LIGHT UP AND RIGHT
+	 in UTF-8: 0xE2 0x94 0x94.  */
+      underline_start = "\xE2\x94\x94";
+
+      /* Within an underline:
+	   U+2500 BOX DRAWINGS LIGHT HORIZONTAL
+	 which in UTF-8 is: 0xE2 0x94 0x80.  */
+      underline_hbar = "\xE2\x94\x80";
+
+      /* The end of an underline: the south-eastern corner of a box:
+	   U+2518 BOX DRAWINGS LIGHT UP AND LEFT
+	 UTF-8: 0xE2 0x94 0x98.  */
+      underline_end = "\xE2\x94\x98";
+
+      /* Vertical margins (when showing captions for ranges).  */
+
+      /* The top of a rmargin: the north-eastern corner of a box:
+	   U+2510 BOX DRAWINGS LIGHT DOWN AND LEFT
+	 UTF-8: 0xE2 0x94 0x90.  */
+      rmargin_start = "\xE2\x94\x90";
+
+      /* For vertical lines, use:
+	   U+2502 BOX DRAWINGS LIGHT VERTICAL
+	   which in UTF-8 is: 0xE2 0x94 0x82.  */
+      rmargin_vbar = "\xE2\x94\x82";
+
+      /* For the row within the rmargin containing the caption:
+	   U+251C BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+	 UTF-8: 0xE2 0x94 0x9C.  */
+      rmargin_caption_row = "\xE2\x94\x9C";
+
+      /* Unless its on the very last line, in which case:
+	   U+2534 BOX DRAWINGS LIGHT UP AND HORIZONTAL
+	 UTF-8: 0xE2 0x94 0xB4.  */
+      rmargin_caption_row_at_end = "\xE2\x94\xB4";
+
+      /* The end of a rmargin: the south-eastern corner of a box:
+	   U+2518 BOX DRAWINGS LIGHT UP AND LEFT
+	 UTF-8: 0xE2 0x94 0x98.  */
+      rmargin_end = "\xE2\x94\x98";
+    }
+  else
+    {
+      caret = "^";
+      underline_start = underline_hbar = underline_end = "~";
+
+      rmargin_start = "|";
+      rmargin_vbar = "|";
+      rmargin_caption_row = rmargin_caption_row_at_end = "+";
+      rmargin_end = "|";
+    }
+}
diff --git a/gcc/box-drawing.h b/gcc/box-drawing.h
new file mode 100644
index 0000000..4853976
--- /dev/null
+++ b/gcc/box-drawing.h
@@ -0,0 +1,43 @@
+/* Box-drawing, either using Unicode box-drawing chars, or as pure ASCII
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* Strings for drawing the caret and ranges within diagnostics.
+   There are either UTF-8-encoded Unicode box-drawing characters,
+   or pure ASCII.  */
+
+class box_drawing
+{
+ public:
+  void init (bool have_utf8);
+
+  const char *caret;
+  const char *underline_start;
+  const char *underline_hbar;
+  const char *underline_end;
+
+  /* Right-hand-side margins.  */
+  const char *rmargin_start;
+  const char *rmargin_vbar;
+  const char *rmargin_caption_row;
+  const char *rmargin_caption_row_at_end;
+  const char *rmargin_end;
+};
+
+/* Singleton instance.  */
+extern box_drawing g_line_art;
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 456c619..cdc78f9 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -4036,6 +4036,7 @@ c_register_builtin_type (tree type, const char* name)
 
 void
 binary_op_error (location_t location, enum tree_code code,
+		 tree orig_op0, tree orig_op1,
 		 tree type0, tree type1)
 {
   const char *opname;
@@ -4087,7 +4088,10 @@ binary_op_error (location_t location, enum tree_code code,
     default:
       gcc_unreachable ();
     }
-  error_at (location,
+  rich_location richloc (location);
+  richloc.add_expr (orig_op0);
+  richloc.add_expr (orig_op1);
+  error_at_rich_loc (&richloc,
 	    "invalid operands to binary %s (have %qT and %qT)", opname,
 	    type0, type1);
 }
@@ -10016,8 +10020,9 @@ c_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
     }
   if (done_lexing)
     location = input_location;
+  rich_location richloc (location);
   diagnostic_set_info_translated (&diagnostic, msg, ap,
-				  location, dlevel);
+				  &richloc, dlevel);
   if (column_override)
     diagnostic_override_column (&diagnostic, column_override);
   diagnostic_override_option_index (&diagnostic,
@@ -11528,7 +11533,12 @@ warn_for_div_by_zero (location_t loc, tree divisor)
      generating a NaN.  */
   if (c_inhibit_evaluation_warnings == 0
       && (integer_zerop (divisor) || fixed_zerop (divisor)))
-    warning_at (loc, OPT_Wdiv_by_zero, "division by zero");
+    {
+      rich_location richloc (loc);
+      richloc.add_expr (divisor);
+      warning_at_rich_loc (&richloc,
+			   OPT_Wdiv_by_zero, "division by zero");
+    }
 }
 
 /* Subroutine of build_binary_op. Give warnings for comparisons
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 5b2c5ab..cc7dc1a 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -799,7 +799,8 @@ extern tree c_sizeof_or_alignof_type (location_t, tree, bool, bool, int);
 extern tree c_alignof_expr (location_t, tree);
 /* Print an error message for invalid operands to arith operation CODE.
    NOP_EXPR is used as a special case (see truthvalue_conversion).  */
-extern void binary_op_error (location_t, enum tree_code, tree, tree);
+extern void binary_op_error (location_t, enum tree_code, tree, tree,
+			     tree, tree);
 extern tree fix_string_type (tree);
 extern void constant_expression_warning (tree);
 extern void constant_expression_error (tree);
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index 9d03ff0..7964656 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -42,6 +42,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-format.h"
 #include "alloc-pool.h"
 #include "c-target.h"
+#include "rich-location.h"
 
 /* Handle attributes associated with format checking.  */
 
@@ -955,7 +956,7 @@ static void check_format_info (function_format_info *, tree);
 static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
 static void check_format_info_main (format_check_results *,
 				    function_format_info *,
-				    const char *, int, tree,
+				    tree, const char *, int, tree,
                                     unsigned HOST_WIDE_INT, alloc_pool);
 
 static void init_dollar_format_checking (int, tree);
@@ -967,8 +968,9 @@ static void finish_dollar_format_checking (format_check_results *, int);
 static const format_flag_spec *get_flag_spec (const format_flag_spec *,
 					      int, const char *);
 
-static void check_format_types (location_t, format_wanted_type *);
-static void format_type_warning (location_t, format_wanted_type *, tree, tree);
+static void check_format_types (rich_location *, format_wanted_type *);
+static void format_type_warning (rich_location *, format_wanted_type *, tree,
+				 tree);
 
 /* Decode a format type from a string, returning the type, or
    format_type_error if not valid, in which case the caller should print an
@@ -1619,13 +1621,15 @@ check_format_arg (void *ctx, tree format_tree,
   res->number_other++;
   fwt_pool = create_alloc_pool ("format_wanted_type pool",
                                 sizeof (format_wanted_type), 10);
-  check_format_info_main (res, info, format_chars, format_length,
+  check_format_info_main (res, info, format_tree, format_chars, format_length,
                           params, arg_num, fwt_pool);
   free_alloc_pool (fwt_pool);
 }
 
 
-/* Do the main part of checking a call to a format function.  FORMAT_CHARS
+/* Do the main part of checking a call to a format function.
+   FORMAT_STRING_CST is the STRING_CST format string.
+   FORMAT_CHARS
    is the NUL-terminated format string (which at this point may contain
    internal NUL characters); FORMAT_LENGTH is its length (excluding the
    terminating NUL character).  ARG_NUM is one less than the number of
@@ -1634,7 +1638,9 @@ check_format_arg (void *ctx, tree format_tree,
 
 static void
 check_format_info_main (format_check_results *res,
-			function_format_info *info, const char *format_chars,
+			function_format_info *info,
+			tree format_string_cst,
+			const char *format_chars,
 			int format_length, tree params,
                         unsigned HOST_WIDE_INT arg_num, alloc_pool fwt_pool)
 {
@@ -1652,6 +1658,8 @@ check_format_info_main (format_check_results *res,
 
   init_dollar_format_checking (info->first_arg_num, first_fillin_param);
 
+  const char *start_of_format_string = format_chars;
+
   while (*format_chars != 0)
     {
       int i;
@@ -1682,8 +1690,14 @@ check_format_info_main (format_check_results *res,
 	continue;
       if (*format_chars == 0)
 	{
-          warning_at (format_string_loc, OPT_Wformat_,
-		      "spurious trailing %<%%%> in format");
+	  ptrdiff_t offset = (format_chars - 1) - start_of_format_string;
+	  rich_location richloc (format_string_cst,
+				 offset,
+				 offset,
+				 offset,
+				 format_string_loc);
+	  warning_at_rich_loc (&richloc, OPT_Wformat_,
+			       "spurious trailing %<%%%> in format");
 	  continue;
 	}
       if (*format_chars == '%')
@@ -1691,6 +1705,7 @@ check_format_info_main (format_check_results *res,
 	  ++format_chars;
 	  continue;
 	}
+      const char *start_of_this_format = format_chars;
       flag_chars[0] = 0;
 
       if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
@@ -2323,7 +2338,16 @@ check_format_info_main (format_check_results *res,
 	}
 
       if (first_wanted_type != 0)
-        check_format_types (format_string_loc, first_wanted_type);
+	{
+	  ptrdiff_t offset_to_format_start = (start_of_this_format - 1) - orig_format_chars;
+	  ptrdiff_t offset_to_format_end = (format_chars - 1) - orig_format_chars;
+	  rich_location richloc (format_string_cst,
+				 offset_to_format_start,
+				 offset_to_format_start,
+				 offset_to_format_end,
+				 format_string_loc);
+	  check_format_types (&richloc, first_wanted_type);
+	}
     }
 
   if (format_chars - orig_format_chars != format_length)
@@ -2344,7 +2368,7 @@ check_format_info_main (format_check_results *res,
    including width and precision arguments).  LOC is the location of
    the format string.  */
 static void
-check_format_types (location_t loc, format_wanted_type *types)
+check_format_types (rich_location *richloc, format_wanted_type *types)
 {
   for (; types != 0; types = types->next)
     {
@@ -2371,7 +2395,7 @@ check_format_types (location_t loc, format_wanted_type *types)
       cur_param = types->param;
       if (!cur_param)
         {
-          format_type_warning (loc, types, wanted_type, NULL);
+          format_type_warning (richloc, types, wanted_type, NULL);
           continue;
         }
 
@@ -2445,7 +2469,7 @@ check_format_types (location_t loc, format_wanted_type *types)
 	    }
 	  else
 	    {
-              format_type_warning (loc, types, wanted_type, orig_cur_type);
+              format_type_warning (richloc, types, wanted_type, orig_cur_type);
 	      break;
 	    }
 	}
@@ -2513,7 +2537,7 @@ check_format_types (location_t loc, format_wanted_type *types)
 	  && TYPE_PRECISION (cur_type) == TYPE_PRECISION (wanted_type))
 	continue;
       /* Now we have a type mismatch.  */
-      format_type_warning (loc, types, wanted_type, orig_cur_type);
+      format_type_warning (richloc, types, wanted_type, orig_cur_type);
     }
 }
 
@@ -2526,7 +2550,7 @@ check_format_types (location_t loc, format_wanted_type *types)
    are taken from TYPE.  ARG_TYPE is the type of the actual argument,
    or NULL if it is missing.  */
 static void
-format_type_warning (location_t loc, format_wanted_type *type,
+format_type_warning (rich_location *richloc, format_wanted_type *type,
 		     tree wanted_type, tree arg_type)
 {
   int kind = type->kind;
@@ -2571,36 +2595,36 @@ format_type_warning (location_t loc, format_wanted_type *type,
   if (wanted_type_name)
     {
       if (arg_type)
-        warning_at (loc, OPT_Wformat_,
-		    "%s %<%s%.*s%> expects argument of type %<%s%s%>, "
-		    "but argument %d has type %qT",
-		    gettext (kind_descriptions[kind]),
-		    (kind == CF_KIND_FORMAT ? "%" : ""),
-		    format_length, format_start, 
-		    wanted_type_name, p, arg_num, arg_type);
+	warning_at_rich_loc (richloc, OPT_Wformat_,
+			     "%s %<%s%.*s%> expects argument of type %<%s%s%>, "
+			     "but argument %d has type %qT",
+			     gettext (kind_descriptions[kind]),
+			     (kind == CF_KIND_FORMAT ? "%" : ""),
+			     format_length, format_start,
+			     wanted_type_name, p, arg_num, arg_type);
       else
-        warning_at (loc, OPT_Wformat_,
-		    "%s %<%s%.*s%> expects a matching %<%s%s%> argument",
-		    gettext (kind_descriptions[kind]),
-		    (kind == CF_KIND_FORMAT ? "%" : ""),
-		    format_length, format_start, wanted_type_name, p);
+	warning_at_rich_loc (richloc, OPT_Wformat_,
+			     "%s %<%s%.*s%> expects a matching %<%s%s%> argument",
+			     gettext (kind_descriptions[kind]),
+			     (kind == CF_KIND_FORMAT ? "%" : ""),
+			     format_length, format_start, wanted_type_name, p);
     }
   else
     {
       if (arg_type)
-        warning_at (loc, OPT_Wformat_,
-		    "%s %<%s%.*s%> expects argument of type %<%T%s%>, "
-		    "but argument %d has type %qT",
-		    gettext (kind_descriptions[kind]),
-		    (kind == CF_KIND_FORMAT ? "%" : ""),
-		    format_length, format_start, 
-		    wanted_type, p, arg_num, arg_type);
+	warning_at_rich_loc (richloc, OPT_Wformat_,
+			     "%s %<%s%.*s%> expects argument of type %<%T%s%>, "
+			     "but argument %d has type %qT",
+			     gettext (kind_descriptions[kind]),
+			     (kind == CF_KIND_FORMAT ? "%" : ""),
+			     format_length, format_start,
+			     wanted_type, p, arg_num, arg_type);
       else
-        warning_at (loc, OPT_Wformat_,
-		    "%s %<%s%.*s%> expects a matching %<%T%s%> argument",
-		    gettext (kind_descriptions[kind]),
-		    (kind == CF_KIND_FORMAT ? "%" : ""),
-		    format_length, format_start, wanted_type, p);
+	warning_at_rich_loc (richloc, OPT_Wformat_,
+			     "%s %<%s%.*s%> expects a matching %<%T%s%> argument",
+			     gettext (kind_descriptions[kind]),
+			     (kind == CF_KIND_FORMAT ? "%" : ""),
+			     format_length, format_start, wanted_type, p);
     }
 }
 
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index c140837..2ce55f8 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -5293,9 +5293,10 @@ warn_defaults_to (location_t location, int opt, const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
                        flag_isoc99 ? DK_PEDWARN : DK_WARNING);
   diagnostic.option_index = opt;
   report_diagnostic (&diagnostic);
diff --git a/gcc/c/c-errors.c b/gcc/c/c-errors.c
index 78c3dfd..1873506 100644
--- a/gcc/c/c-errors.c
+++ b/gcc/c/c-errors.c
@@ -49,13 +49,14 @@ pedwarn_c99 (location_t location, int opt, const char *gmsgid, ...)
   diagnostic_info diagnostic;
   va_list ap;
   bool warned = false;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
   /* If desired, issue the C99/C11 compat warning, which is more specific
      than -pedantic.  */
   if (warn_c99_c11_compat > 0)
     {
-      diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
 			   (pedantic && !flag_isoc11)
 			   ? DK_PEDWARN : DK_WARNING);
       diagnostic.option_index = OPT_Wc99_c11_compat;
@@ -67,7 +68,7 @@ pedwarn_c99 (location_t location, int opt, const char *gmsgid, ...)
   /* For -pedantic outside C11, issue a pedwarn.  */
   else if (pedantic && !flag_isoc11)
     {
-      diagnostic_set_info (&diagnostic, gmsgid, &ap, location, DK_PEDWARN);
+      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_PEDWARN);
       diagnostic.option_index = opt;
       warned = report_diagnostic (&diagnostic);
     }
@@ -87,6 +88,7 @@ pedwarn_c90 (location_t location, int opt, const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
   /* Warnings such as -Wvla are the most specific ones.  */
@@ -97,7 +99,7 @@ pedwarn_c90 (location_t location, int opt, const char *gmsgid, ...)
         goto out;
       else if (opt_var > 0)
 	{
-	  diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+	  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
 			       (pedantic && !flag_isoc99)
 			       ? DK_PEDWARN : DK_WARNING);
 	  diagnostic.option_index = opt;
@@ -109,7 +111,7 @@ pedwarn_c90 (location_t location, int opt, const char *gmsgid, ...)
      specific than -pedantic.  */
   if (warn_c90_c99_compat > 0)
     {
-      diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
 			   (pedantic && !flag_isoc99)
 			   ? DK_PEDWARN : DK_WARNING);
       diagnostic.option_index = OPT_Wc90_c99_compat;
@@ -121,7 +123,7 @@ pedwarn_c90 (location_t location, int opt, const char *gmsgid, ...)
   /* For -pedantic outside C99, issue a pedwarn.  */
   else if (pedantic && !flag_isoc99)
     {
-      diagnostic_set_info (&diagnostic, gmsgid, &ap, location, DK_PEDWARN);
+      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_PEDWARN);
       diagnostic.option_index = opt;
       report_diagnostic (&diagnostic);
     }
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index ebe4c73..de09d3d 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -2869,7 +2869,11 @@ build_function_call (location_t loc, tree function, tree params)
 static void inform_declaration (tree decl)
 {
   if (decl && (TREE_CODE (decl) != FUNCTION_DECL || !DECL_BUILT_IN (decl)))
-    inform (DECL_SOURCE_LOCATION (decl), "declared here");
+    {
+      rich_location richloc (DECL_SOURCE_LOCATION (decl));
+      richloc.add_expr (decl);
+      inform_at_rich_loc (&richloc, "declared here");
+    }
 }
 
 /* Build a function call to function FUNCTION with parameters PARAMS.
@@ -2936,14 +2940,22 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
 		  function);
       else if (DECL_P (function))
 	{
-	  error_at (loc,
-		    "called object %qD is not a function or function pointer",
-		    function);
+	  rich_location richloc (loc);
+	  richloc.add_expr (function);
+	  error_at_rich_loc
+	    (&richloc,
+	     "called object %qD is not a function or function pointer",
+	     function);
 	  inform_declaration (function);
 	}
       else
-	error_at (loc,
-		  "called object is not a function or function pointer");
+	{
+	  rich_location richloc (loc);
+	  richloc.add_expr (function);
+	  error_at_rich_loc
+	    (&richloc,
+	     "called object is not a function or function pointer");
+	}
       return error_mark_node;
     }
 
@@ -10987,7 +10999,7 @@ build_binary_op (location_t location, enum tree_code code,
       && (!tree_int_cst_equal (TYPE_SIZE (type0), TYPE_SIZE (type1))
 	  || !vector_types_compatible_elements_p (type0, type1)))
     {
-      binary_op_error (location, code, type0, type1);
+      binary_op_error (location, code, orig_op0, orig_op1, type0, type1);
       return error_mark_node;
     }
 
@@ -11226,7 +11238,8 @@ build_binary_op (location_t location, enum tree_code code,
 
   if (!result_type)
     {
-      binary_op_error (location, code, TREE_TYPE (op0), TREE_TYPE (op1));
+      binary_op_error (location, code, orig_op0, orig_op1,
+		       TREE_TYPE (op0), TREE_TYPE (op1));
       return error_mark_node;
     }
 
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index fdd8436..d7cc46b 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -59,6 +59,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "cgraph.h"
 #include "wide-int.h"
 #include "internal-fn.h"
+#include "rich-location.h"
 
 /* The various kinds of conversion.  */
 
@@ -4023,13 +4024,17 @@ print_error_for_call_failure (tree fn, vec<tree, va_gc> *args,
 {
   tree name = DECL_NAME (OVL_CURRENT (fn));
   location_t loc = location_of (name);
+  rich_location richloc (loc);
+  richloc.add_expr (fn);
 
   if (!any_strictly_viable (candidates))
-    error_at (loc, "no matching function for call to %<%D(%A)%>",
-	      name, build_tree_list_vec (args));
+    error_at_rich_loc (&richloc,
+		       "no matching function for call to %<%D(%A)%>",
+		       name, build_tree_list_vec (args));
   else
-    error_at (loc, "call of overloaded %<%D(%A)%> is ambiguous",
-	      name, build_tree_list_vec (args));
+    error_at_rich_loc (&richloc,
+		       "call of overloaded %<%D(%A)%> is ambiguous",
+		       name, build_tree_list_vec (args));
   if (candidates)
     print_z_candidates (loc, candidates);
 }
@@ -4441,8 +4446,16 @@ op_error (location_t loc, enum tree_code code, enum tree_code code2,
     default:
       if (arg2)
 	if (flag_diagnostics_show_caret)
-	  error_at (loc, op_error_string (G_("%<operator%s%>"), 2, match),
-		    opname, TREE_TYPE (arg1), TREE_TYPE (arg2));
+	  {
+	    rich_location richloc (loc);
+	    richloc.add_expr_with_caption (arg1, global_dc->printer,
+					   G_("type %qT"), TREE_TYPE (arg1));
+	    richloc.add_expr_with_caption (arg2, global_dc->printer,
+					   G_("type %qT"), TREE_TYPE (arg2));
+	    error_at_rich_loc (&richloc,
+			       op_error_string (G_("%<operator%s%>"), 2, match),
+			       opname, TREE_TYPE (arg1), TREE_TYPE (arg2));
+	  }
 	else
 	  error_at (loc, op_error_string (G_("%<operator%s%> in %<%E %s %E%>"),
 					  2, match),
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index ce43f86..852a87d 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -3104,7 +3104,7 @@ static void
 cp_diagnostic_starter (diagnostic_context *context,
 		       diagnostic_info *diagnostic)
 {
-  diagnostic_report_current_module (context, diagnostic->location);
+  diagnostic_report_current_module (context, diagnostic->richloc->get_loc ());
   cp_print_error_function (context, diagnostic);
   maybe_print_instantiation_context (context);
   maybe_print_constexpr_context (context);
@@ -3125,7 +3125,7 @@ cp_print_error_function (diagnostic_context *context,
   if (diagnostic_last_function_changed (context, diagnostic))
     {
       const char *old_prefix = context->printer->prefix;
-      const char *file = LOCATION_FILE (diagnostic->location);
+      const char *file = LOCATION_FILE (diagnostic->richloc->get_loc ());
       tree abstract_origin = diagnostic_abstract_origin (diagnostic);
       char *new_prefix = (file && abstract_origin == NULL)
 			 ? file_name_as_prefix (context, file) : NULL;
@@ -3629,9 +3629,10 @@ pedwarn_cxx98 (location_t location, int opt, const char *gmsgid, ...)
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
 		       (cxx_dialect == cxx98) ? DK_PEDWARN : DK_WARNING);
   diagnostic.option_index = opt;
   ret = report_diagnostic (&diagnostic);
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index a209ee6..af17110 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -25157,12 +25157,20 @@ cp_parser_check_class_key (enum tag_types class_key, tree type)
     return;
   if ((TREE_CODE (type) == UNION_TYPE) != (class_key == union_type))
     {
-      if (permerror (input_location, "%qs tag used in naming %q#T",
-		     class_key == union_type ? "union"
-		     : class_key == record_type ? "struct" : "class",
-		     type))
-	inform (DECL_SOURCE_LOCATION (TYPE_NAME (type)),
-		"%q#T was previously declared here", type);
+      rich_location rl_tag_reuse (input_location);
+      rl_tag_reuse.add_expr (type); // FIXME: new tree it doesn't exist yet, probably must use token
+      if (permerror_at_rich_loc (&rl_tag_reuse,
+                                 "%qs tag used in naming %q#T",
+                                 class_key == union_type ? "union"
+                                 : class_key == record_type ? "struct" : "class",
+                                 type))
+        {
+          rich_location rl_prev_decl (DECL_SOURCE_LOCATION (TYPE_NAME (type)));
+          rl_prev_decl.add_expr (TYPE_NAME (type));
+          inform_at_rich_loc (&rl_prev_decl,
+                              "%q#T was previously declared here",
+                              type);
+        }
     }
 }
 
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index 4c128b7..80e3dda 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -4774,7 +4774,8 @@ cp_build_binary_op (location_t location,
 	      || !vector_types_compatible_elements_p (type0, type1))
 	    {
 	      if (complain & tf_error)
-		binary_op_error (location, code, type0, type1);
+		binary_op_error (location, code, orig_op0, orig_op1,
+				 type0, type1);
 	      return error_mark_node;
 	    }
 	  arithmetic_types_p = 1;
@@ -4797,8 +4798,16 @@ cp_build_binary_op (location_t location,
   if (!result_type)
     {
       if (complain & tf_error)
-	error ("invalid operands of types %qT and %qT to binary %qO",
-	       TREE_TYPE (orig_op0), TREE_TYPE (orig_op1), code);
+	{
+	  rich_location richloc (location);
+	  richloc.add_expr_with_caption (orig_op0, global_dc->printer,
+					 G_("type %qT"), TREE_TYPE (orig_op0));
+	  richloc.add_expr_with_caption (orig_op1, global_dc->printer,
+					 G_("type %qT"), TREE_TYPE (orig_op1));
+	  error_at_rich_loc (&richloc,
+			     "invalid operands of types %qT and %qT to binary %qO",
+			     TREE_TYPE (orig_op0), TREE_TYPE (orig_op1), code);
+	}
       return error_mark_node;
     }
 
@@ -8553,8 +8562,13 @@ check_return_expr (tree retval, bool *no_warning)
 	   its side-effects.  */
 	  finish_expr_stmt (retval);
       else
-	permerror (input_location, "return-statement with a value, in function "
-		   "returning 'void'");
+	{
+	  rich_location richloc (input_location);
+	  richloc.add_expr (retval);
+	  permerror_at_rich_loc (&richloc,
+				 "return-statement with a value, in function "
+				 "returning 'void'");
+	}
       current_function_returns_null = 1;
 
       /* There's really no value to return, after all.  */
diff --git a/gcc/diagnostic-color.c b/gcc/diagnostic-color.c
index 3fe49b2..21da066 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -165,6 +165,9 @@ static struct color_cap color_dict[] =
 	       7, false },
   { "note", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 4, false },
   { "caret", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN), 5, false },
+  { "range0", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_BLUE), 6, false },
+  { "range1", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA), 6, false },
+  { "rangeN", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_CYAN), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
   { NULL, NULL, 0, false }
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index 09a6867..c0d9454 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -41,6 +41,8 @@ extern const char *progname;
 
 extern const char *trim_filename (const char *);
 
+class rich_location;
+
 /* If we haven't already defined a front-end-specific diagnostics
    style, use the generic one.  */
 #ifndef GCC_DIAG_STYLE
@@ -64,18 +66,26 @@ extern bool warning_n (location_t, int, int, const char *, const char *, ...)
     ATTRIBUTE_GCC_DIAG(4,6) ATTRIBUTE_GCC_DIAG(5,6);
 extern bool warning_at (location_t, int, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,4);
+extern bool warning_at_rich_loc (rich_location *, int, const char *, ...)
+    ATTRIBUTE_GCC_DIAG(3,4);
 extern void error (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void error_n (location_t, int, const char *, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,5) ATTRIBUTE_GCC_DIAG(4,5);
 extern void error_at (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
+extern void error_at_rich_loc (rich_location *, const char *, ...)
+  ATTRIBUTE_GCC_DIAG(2,3);
 extern void fatal_error (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3)
      ATTRIBUTE_NORETURN;
 /* Pass one of the OPT_W* from options.h as the second parameter.  */
 extern bool pedwarn (location_t, int, const char *, ...)
      ATTRIBUTE_GCC_DIAG(3,4);
 extern bool permerror (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
+extern bool permerror_at_rich_loc (rich_location *, const char *,
+				   ...) ATTRIBUTE_GCC_DIAG(2,3);
 extern void sorry (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
 extern void inform (location_t, const char *, ...) ATTRIBUTE_GCC_DIAG(2,3);
+extern void inform_at_rich_loc (rich_location *, const char *,
+				...) ATTRIBUTE_GCC_DIAG(2,3);
 extern void inform_n (location_t, int, const char *, const char *, ...)
     ATTRIBUTE_GCC_DIAG(3,5) ATTRIBUTE_GCC_DIAG(4,5);
 extern void verbatim (const char *, ...) ATTRIBUTE_GCC_DIAG(1,2);
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 2196406..035ac72 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
+#include "tm.h"
 #include "version.h"
 #include "demangle.h"
 #include "input.h"
@@ -32,6 +33,23 @@ along with GCC; see the file COPYING3.  If not see
 #include "backtrace.h"
 #include "diagnostic.h"
 #include "diagnostic-color.h"
+#include "vec.h"
+
+#include "hash-set.h"
+#include "machmode.h"
+#include "vec.h"
+#include "double-int.h"
+#include "input.h"
+#include "alias.h"
+#include "symtab.h"
+#include "wide-int.h"
+#include "inchash.h"
+
+#include "tree-core.h"
+#include "tree.h"
+
+#include "rich-location.h"
+#include "box-drawing.h"
 
 #ifdef HAVE_TERMIOS_H
 # include <termios.h>
@@ -146,7 +164,8 @@ diagnostic_initialize (diagnostic_context *context, int n_opts)
     context->classify_diagnostic[i] = DK_UNSPECIFIED;
   context->show_caret = false;
   diagnostic_set_caret_max_width (context, pp_line_cutoff (context->printer));
-  context->caret_char = '^';
+  gcc_assert (g_line_art.caret);
+  context->caret_char = g_line_art.caret;
   context->show_option_requested = false;
   context->abort_on_error = false;
   context->show_column = false;
@@ -235,13 +254,13 @@ diagnostic_finish (diagnostic_context *context)
    translated.  */
 void
 diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
-				va_list *args, location_t location,
+				va_list *args, rich_location *richloc,
 				diagnostic_t kind)
 {
   diagnostic->message.err_no = errno;
   diagnostic->message.args_ptr = args;
   diagnostic->message.format_spec = msg;
-  diagnostic->location = location;
+  diagnostic->richloc = richloc;
   diagnostic->override_column = 0;
   diagnostic->kind = kind;
   diagnostic->option_index = 0;
@@ -251,12 +270,19 @@ diagnostic_set_info_translated (diagnostic_info *diagnostic, const char *msg,
    translated.  */
 void
 diagnostic_set_info (diagnostic_info *diagnostic, const char *gmsgid,
-		     va_list *args, location_t location,
+		     va_list *args, rich_location *richloc,
 		     diagnostic_t kind)
 {
-  diagnostic_set_info_translated (diagnostic, _(gmsgid), args, location, kind);
+  diagnostic_set_info_translated (diagnostic, _(gmsgid), args, richloc, kind);
 }
 
+static const char *const diagnostic_kind_color[] = {
+#define DEFINE_DIAGNOSTIC_KIND(K, T, C) (C),
+#include "diagnostic.def"
+#undef DEFINE_DIAGNOSTIC_KIND
+  NULL
+};
+
 /* Return a malloc'd string describing a location.  The caller is
    responsible for freeing the memory.  */
 char *
@@ -269,12 +295,6 @@ diagnostic_build_prefix (diagnostic_context *context,
 #undef DEFINE_DIAGNOSTIC_KIND
     "must-not-happen"
   };
-  static const char *const diagnostic_kind_color[] = {
-#define DEFINE_DIAGNOSTIC_KIND(K, T, C) (C),
-#include "diagnostic.def"
-#undef DEFINE_DIAGNOSTIC_KIND
-    NULL
-  };
   gcc_assert (diagnostic->kind < DK_LAST_DIAGNOSTIC_KIND);
 
   const char *text = _(diagnostic_kind_text[diagnostic->kind]);
@@ -291,7 +311,8 @@ diagnostic_build_prefix (diagnostic_context *context,
   locus_cs = colorize_start (pp_show_color (pp), "locus");
   locus_ce = colorize_stop (pp_show_color (pp));
 
-  expanded_location s = diagnostic_expand_location (diagnostic);
+  diagnostic->richloc->lazily_expand_location ();
+  expanded_location s = diagnostic->richloc->m_expanded_location;
   return
     (s.file == NULL
      ? build_message_string ("%s%s:%s %s%s%s", locus_cs, progname, locus_ce,
@@ -330,6 +351,576 @@ adjust_line (const char *line, int line_width,
   return line;
 }
 
+/* Is (column, row) within the given range?
+
+   Ranges are half-open.
+
+   Example A: a single-line range:
+     start:  (col=22, line=2)
+     finish: (col=38, line=2)
+
+  |00000011111111112222222222333333333344444444444
+  |34567890123456789012345678901234567890123456789
+--+-----------------------------------------------
+01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+02|bbbbbbbbbbbbbbbbbbbSwwwwwwwwwwwwwwwFaaaaaaaaaaa
+03|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+   Example B: a multiline range with
+     start:  (col=14, line=3)
+     finish: (col=08, line=5)
+
+  |00000011111111112222222222333333333344444444444
+  |34567890123456789012345678901234567890123456789
+--+-----------------------------------------------
+01|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+02|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+03|bbbbbbbbbbbSwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+04|wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+05|wwwwwFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+06|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--+-----------------------------------------------
+
+   Legend:
+   - 'b' indicates a point *before* the range
+   - 'S' indicates the start of the range
+   - 'w' indicates a point within the range
+   - 'F' indicates the finish of the range (which is
+	 *outside* of it).
+   - 'a' indicates a subsequent point *after* the range.  */
+
+bool
+location_range::contains_point (int row, int column) const
+{
+  gcc_assert (m_start.line <= m_finish.line);
+  /* ...but the equivalent isn't true for the columns;
+     consider example B in the comment above.  */
+
+  if (row < m_start.line)
+    /* Points before the first line of the range are
+       outside it (corresponding to line 01 in example A
+       and lines 01 and 02 in example B above).  */
+    return false;
+
+  if (row == m_start.line)
+    /* On same line as start of range (corresponding
+       to line 02 in example A and line 03 in example B).  */
+    {
+      if (column < m_start.column)
+	/* Points on the starting line of the range, but
+	   before the column in which it begins.  */
+	return false;
+
+      if (row < m_finish.line)
+	/* This is a multiline range; the point
+	   is within it (corresponds to line 03 in example B
+	   from column 14 onwards) */
+	return true;
+      else
+	{
+	  /* This is a single-line range.  */
+	  gcc_assert (row == m_finish.line);
+	  return column < m_finish.column;
+	}
+    }
+
+  /* The point is in a line beyond that containing the
+     start of the range: lines 03 onwards in example A,
+     and lines 04 onwards in example B.  */
+  gcc_assert (row > m_start.line);
+
+  if (row > m_finish.line)
+    /* The point is beyond the final line of the range
+       (lines 03 onwards in example A, and lines 06 onwards
+       in example B).  */
+    return false;
+
+  if (row < m_finish.line)
+    {
+      /* The point is in a line that's fully within a multiline
+	 range (e.g. line 04 in example B).  */
+      gcc_assert (m_start.line < m_finish.line);
+      return true;
+    }
+
+  gcc_assert (row ==  m_finish.line);
+
+  return column < m_finish.column;
+}
+
+/* state:
+   -2 : normal text
+   -1 : at caret
+   [0..n]: within range n. */
+#define STATE_NORMAL_TEXT (-2)
+#define STATE_AT_CARET    (-1)
+
+static int
+get_state_at_point (int row, int column, const rich_location *richloc,
+		    expanded_location *caret,
+		    int first_non_ws, int last_non_ws)
+{
+  if (row == caret->line && column == caret->column)
+    return STATE_AT_CARET;
+
+  /* Within a multiline range, don't display the caret in any leading
+     or trailing whitespace on a line.  */
+  if (column < first_non_ws || column > last_non_ws)
+    return STATE_NORMAL_TEXT;
+
+  int i;
+  location_range *range;
+  FOR_EACH_VEC_ELT (richloc->m_ranges, i,  range)
+    {
+#if 0
+      fprintf (stderr,
+	       "range ( (%i, %i), (%i, %i))->contains_point (%i, %i): %s\n",
+	       range->m_start.line,
+	       range->m_start.column,
+	       range->m_finish.line,
+	       range->m_finish.column,
+	       row,
+	       column,
+	       range->contains_point (row, column) ? "true" : "false");
+#endif
+
+      if (range->contains_point (row, column))
+	return i;
+    }
+
+  return STATE_NORMAL_TEXT;
+}
+
+/* Get the rightmost column that could contain a caret or range marker,
+   given that we stop rendering at trailing whitespace.  */
+
+static int
+get_final_column_for_row (int row, int caret_column,
+			  const rich_location *richloc,
+			  int last_non_ws)
+{
+  int i;
+  location_range *range;
+
+  int result = caret_column;
+
+  FOR_EACH_VEC_ELT (richloc->m_ranges, i,  range)
+    if (row >= range->m_start.line)
+      {
+	if (range->m_finish.line == row)
+	  {
+	    /* On the final line within a range; ensure that
+	       we render up to the end of the range.  */
+	    if (result < range->m_finish.column)
+	      result = range->m_finish.column;
+	  }
+	else if (row < range->m_finish.line)
+	  {
+	    /* Within a multiline range; ensure that we render up to the
+	       last non-whitespace column.  */
+	    if (result <= last_non_ws)
+	      result = last_non_ws + 1;
+	  }
+      }
+
+  return result;
+}
+
+/* A class to track state when printing the diagnostic locus.
+   Specifically, we track colorization.  */
+
+class locus_printer
+{
+ public:
+  locus_printer (diagnostic_context *context,
+		 const diagnostic_info *diagnostic);
+  ~locus_printer ();
+
+  void set_state (int state);
+
+ private:
+  void begin_state (int state);
+  void finish_state (int state);
+
+ private:
+  diagnostic_context *m_context;
+  const diagnostic_info *m_diagnostic;
+  int m_current_state;
+  const char *m_caret_cs;
+  const char *m_caret_ce;
+  const char *m_range0_cs;
+  const char *m_range1_cs;
+  const char *m_rangeN_cs;
+  const char *m_range_ce;
+};
+
+locus_printer::locus_printer (diagnostic_context *context,
+			      const diagnostic_info *diagnostic) :
+  m_context (context),
+  m_diagnostic (diagnostic),
+  m_current_state (STATE_NORMAL_TEXT)
+{
+  m_caret_ce = colorize_stop (pp_show_color (context->printer));
+  m_range0_cs = colorize_start (pp_show_color (context->printer), "range0");
+  m_range1_cs = colorize_start (pp_show_color (context->printer), "range1");
+  m_rangeN_cs = colorize_start (pp_show_color (context->printer), "rangeN");
+  m_range_ce = colorize_stop (pp_show_color (context->printer));
+}
+
+locus_printer::~locus_printer ()
+{
+  finish_state (m_current_state);
+}
+
+/* Update state.  */
+void
+locus_printer::set_state (int new_state)
+{
+  if (m_current_state != new_state)
+    {
+      finish_state (m_current_state);
+      m_current_state = new_state;
+      begin_state (new_state);
+    }
+}
+
+/* Turn on any colorization for STATE.  */
+
+void
+locus_printer::begin_state (int state)
+{
+  switch (state)
+    {
+    case STATE_NORMAL_TEXT:
+      break;
+    case STATE_AT_CARET:
+      /* Make the caret be the same color as the "kind" text (error vs
+	 warning vs note).  */
+      pp_string (m_context->printer,
+		 colorize_start (pp_show_color (m_context->printer),
+				 diagnostic_kind_color[m_diagnostic->kind]));
+      break;
+    default:
+      {
+	/* Within a range.  */
+	gcc_assert (state >= 0);
+
+	const char *range_cs;
+	switch (state)
+	  {
+	  case 0:
+	    range_cs = m_range0_cs;
+	    break;
+	  case 1:
+	    range_cs = m_range1_cs;
+	    break;
+	  default:
+	    range_cs = m_rangeN_cs;
+	    break;
+	  }
+	pp_string (m_context->printer, range_cs);
+      }
+      break;
+    }
+}
+
+/* Turn off any colorization for STATE.  */
+
+void
+locus_printer::finish_state (int state)
+{
+  switch (state)
+    {
+    case STATE_NORMAL_TEXT:
+      break;
+    case STATE_AT_CARET:
+      pp_string (m_context->printer, m_caret_ce);
+      break;
+    default:
+      /* Within a range.  */
+      gcc_assert (state >= 0);
+      pp_string (m_context->printer, m_range_ce);
+      break;
+    }
+}
+
+/* Information on how to display a specific range within a
+   rich_location.  */
+class range_layout
+{
+ public:
+  range_layout (int range_idx);
+
+  int get_range_index () const { return m_range_idx; }
+
+  void add_uniquely_captioned_row (int row);
+
+  int get_first_unique_row () const;
+  int get_last_unique_row () const;
+
+  bool contains_line (int line) const;
+
+  void determine_location_for_caption (const char *filename);
+
+  int get_caption_row () const { return m_caption_row; }
+  int get_caption_column () const { return m_caption_column; }
+
+ private:
+  int m_range_idx;
+  auto_vec<int> m_unique_rows;
+  int m_caption_row;
+  int m_caption_column;
+};
+
+range_layout::range_layout (int range_idx)
+: m_range_idx (range_idx),
+  m_unique_rows ()
+{
+}
+
+void
+range_layout::add_uniquely_captioned_row (int row)
+{
+  m_unique_rows.safe_push (row);
+}
+
+int
+range_layout::get_first_unique_row () const
+{
+  gcc_assert (m_unique_rows.length () > 0);
+  return m_unique_rows[0];
+}
+
+int
+range_layout::get_last_unique_row () const
+{
+  gcc_assert (m_unique_rows.length () > 0);
+
+  return m_unique_rows[m_unique_rows.length () - 1];
+}
+
+bool
+range_layout::contains_line (int line) const
+{
+  if (0 == m_unique_rows.length ())
+    return false;
+
+  return (line >= get_first_unique_row ()
+	  && line <= get_last_unique_row ());
+}
+
+static int
+get_line_width_without_trailing_whitespace (const char *line, int line_width)
+{
+  int result = line_width;
+  while (result > 0)
+    {
+      char ch = line[result - 1];
+      if (ch == ' ' || ch == '\t')
+	result--;
+      else
+	break;
+    }
+  gcc_assert (result >= 0);
+  gcc_assert (result <= line_width);
+  gcc_assert (result == 0 ||
+	      (line[result - 1] != ' '
+	       && line[result -1] != '\t'));
+  return result;
+}
+
+void
+range_layout::determine_location_for_caption (const char *filename)
+{
+  if (0 == m_unique_rows.length ())
+    return;
+
+  /* Determine the widest line, using it as the column in which to render
+     the caption (m_caption_column).  */
+  int result = 0;
+  for (int row = get_first_unique_row ();
+       row <= get_last_unique_row ();
+       row++)
+    {
+      int line_width;
+      const char *line = location_get_source_line (filename, row, &line_width);
+      line_width = get_line_width_without_trailing_whitespace (line,
+							       line_width);
+      if (result < line_width)
+	result = line_width + 1;
+    }
+  m_caption_column = result;
+
+  /* Determine which row to write the caption text in: the middle row
+     within the range, rounding down mathematically (and thus up
+     visually).  */
+  m_caption_row = (get_first_unique_row () + get_last_unique_row ()) / 2;
+}
+
+/* For now, just support the case of a single margin at once,
+   showing any captioned ranges that exclusively occupy their lines.  */
+
+class layout
+{
+ public:
+  layout (rich_location *richloc);
+
+  range_layout *
+  get_any_range (int line);
+
+  void
+  print_any_margin (diagnostic_context *context,
+		    locus_printer *locpr,
+		    int line, int column, bool src);
+
+ private:
+  rich_location *m_richloc;
+  auto_vec <range_layout> m_range_layouts;
+};
+
+layout::layout (rich_location *richloc)
+  : m_richloc (richloc),
+    m_range_layouts ()
+{
+  int i;
+  location_range *range;
+  FOR_EACH_VEC_ELT (richloc->m_ranges, i,  range)
+    {
+      range_layout rl (i);
+      m_range_layouts.safe_push (rl);
+    }
+
+  /* For each row, determine if it has a unique captioned range.  */
+  int last_line = m_richloc->get_last_line ();
+  for (int row = m_richloc->get_first_line ();
+       row <= last_line;
+       row++)
+    {
+      int i;
+      location_range *range;
+      int num_captions_in_row = 0;
+      location_range *first_caption = NULL;
+      int first_caption_idx;
+      FOR_EACH_VEC_ELT (richloc->m_ranges, i,  range)
+	if (range->m_caption)
+	  {
+	    if (row >= range->m_start.line
+		&& row <= range->m_finish.line)
+	      {
+		num_captions_in_row++;
+		if (!first_caption)
+		  {
+		    first_caption = range;
+		    first_caption_idx = i;
+		  }
+	      }
+	  }
+      if (first_caption && num_captions_in_row == 1)
+	m_range_layouts[first_caption_idx].add_uniquely_captioned_row (row);
+    }
+
+  /* At this stage, each range_layout now has a list of lines for which its
+     location_range uniquely captions that source line.  */
+
+  /* Next, calculate the best position in which to draw each caption.  */
+  range_layout *rl;
+  FOR_EACH_VEC_ELT (m_range_layouts, i, rl)
+    rl->determine_location_for_caption (richloc->m_ranges[i].m_start.file);
+}
+
+range_layout *
+layout::get_any_range (int line)
+{
+  int i;
+  range_layout *rl;
+  FOR_EACH_VEC_ELT (m_range_layouts, i, rl)
+    if (rl->contains_line (line))
+      return rl;
+  return NULL;
+}
+
+void
+layout::print_any_margin (diagnostic_context *context,
+			  locus_printer *locpr,
+			  int line, int column, bool src)
+{
+  range_layout *rl = get_any_range (line);
+  if (rl)
+    {
+      /* Fill with whitespace to get to the appropriate y-coordinate.  */
+      while (column++ < rl->get_caption_column ())
+	pp_space (context->printer);
+
+      /* Colorize based on the range.  */
+      int new_state = rl->get_range_index ();
+      locpr->set_state (new_state);
+
+      /* Draw a vertical line, with corners pointing to the left,
+	 (falling back to '|' if unicode box-drawing is not available).  */
+      if (line == rl->get_caption_row () && !src)
+	pp_string (context->printer,
+		   line == rl->get_last_unique_row ()
+		   ? g_line_art.rmargin_caption_row_at_end
+		   : g_line_art.rmargin_caption_row);
+      else if (line == rl->get_first_unique_row () && src)
+	/* Start of multiline range.  */
+	pp_string (context->printer, g_line_art.rmargin_start);
+      else if (line == rl->get_last_unique_row () && !src)
+	/* End of multiline range.  */
+	pp_string (context->printer, g_line_art.rmargin_end);
+      else
+	pp_string (context->printer, g_line_art.rmargin_vbar);
+
+      /* Render the caption on the appropriate line.
+	 This method is called twice per source line:
+	 once for the source line, and once for the "underlining" line.
+	 We render the caption for the second call.  */
+      if (line == rl->get_caption_row () && !src)
+	{
+	  location_range *range
+	    = m_richloc->get_range (rl->get_range_index ());
+	  gcc_assert (range);
+	  /* Display any caption.  */
+	  if (range->m_caption)
+	    pp_string (context->printer, range->m_caption);
+	}
+    }
+}
+
+/* For debugging layout issues in diagnostic_show_locus and friends,
+   render a ruler giving column numbers (after the 1-column indent).  */
+
+static void
+show_ruler (diagnostic_context *context, int max_width)
+{
+  /* Hundreds.  */
+  if (max_width > 99)
+    {
+      pp_space (context->printer);
+      for (int column = 1; column < max_width; column++)
+	if (0 == column % 10)
+	  pp_character (context->printer, '0' + (column / 100) % 10);
+	else
+	  pp_space (context->printer);
+      pp_newline (context->printer);
+    }
+
+  /* Tens.  */
+  pp_space (context->printer);
+  for (int column = 1; column < max_width; column++)
+    if (0 == column % 10)
+      pp_character (context->printer, '0' + (column / 10) % 10);
+    else
+      pp_space (context->printer);
+  pp_newline (context->printer);
+
+  /* Units.  */
+  pp_space (context->printer);
+  for (int column = 1; column < max_width; column++)
+    pp_character (context->printer, '0' + (column % 10));
+  pp_newline (context->printer);
+}
+
 /* Print the physical source line corresponding to the location of
    this diagnostic, and a caret indicating the precise column.  */
 void
@@ -338,20 +929,24 @@ diagnostic_show_locus (diagnostic_context * context,
 {
   const char *line;
   int line_width;
-  char *buffer;
   expanded_location s;
   int max_width;
   const char *saved_prefix;
-  const char *caret_cs, *caret_ce;
+  location_t location = diagnostic->richloc->get_loc ();
 
   if (!context->show_caret
-      || diagnostic->location <= BUILTINS_LOCATION
-      || diagnostic->location == context->last_location)
+      || location <= BUILTINS_LOCATION
+      || location == context->last_location)
     return;
 
-  context->last_location = diagnostic->location;
+  context->last_location = location;
+#if 0
   s = diagnostic_expand_location (diagnostic);
-  line = location_get_source_line (s, &line_width);
+#else
+  diagnostic->richloc->lazily_expand_location ();
+  s = diagnostic->richloc->m_expanded_location;
+#endif
+  line = location_get_source_line (s.file, s.line, &line_width);
   if (line == NULL || s.column > line_width)
     return;
 
@@ -361,6 +956,9 @@ diagnostic_show_locus (diagnostic_context * context,
   pp_newline (context->printer);
   saved_prefix = pp_get_prefix (context->printer);
   pp_set_prefix (context->printer, NULL);
+  /* FIXME: need to reimplement the adjust_line margin-adjustment logic
+     for the case of multiline, with ranges as well as carets.  */
+#if 0
   pp_space (context->printer);
   while (max_width > 0 && line_width > 0)
     {
@@ -373,19 +971,156 @@ diagnostic_show_locus (diagnostic_context * context,
       line++;
     }
   pp_newline (context->printer);
-  caret_cs = colorize_start (pp_show_color (context->printer), "caret");
-  caret_ce = colorize_stop (pp_show_color (context->printer));
+#endif
+
+  if (0)
+    show_ruler (context, max_width);
 
   /* pp_printf does not implement %*c.  */
-  size_t len = s.column + 3 + strlen (caret_cs) + strlen (caret_ce);
-  buffer = XALLOCAVEC (char, len);
-  snprintf (buffer, len, "%s %*c%s", caret_cs, s.column, context->caret_char,
-	    caret_ce);
-  pp_string (context->printer, buffer);
+  /* For now, assume we have disjoint ranges.  */
+  {
+    locus_printer locpr (context, diagnostic);
+    layout layout (diagnostic->richloc);
+    int last_line = diagnostic->richloc->get_last_line ();
+    for (int row = diagnostic->richloc->get_first_line ();
+	 row <= last_line;
+	 row++)
+      {
+	int first_non_ws = INT_MAX;
+	int last_non_ws = 0;
+	line = location_get_source_line (s.file, row, &line_width);
+	if (line)
+	  {
+	    /* Print the source code line.  */
+
+	    /* Stop printing at any trailing whitespace.  */
+	    line_width
+	      = get_line_width_without_trailing_whitespace (line,
+							    line_width);
+
+	    pp_space (context->printer);
+	    locpr.set_state (STATE_NORMAL_TEXT);
+	    int column;
+	    for (column = 1; column <= line_width; column++)
+	      {
+		int new_state = get_state_at_point (row, column,
+						    diagnostic->richloc, &s,
+						    0, INT_MAX);
+		locpr.set_state (new_state);
+		char c = *line == '\t' ? ' ' : *line;
+		if (c == '\0')
+		  c = ' ';
+		if (c != ' ')
+		  {
+		    last_non_ws = column;
+		    if (first_non_ws == INT_MAX)
+		      first_non_ws = column;
+		  }
+		pp_character (context->printer, c);
+		line++;
+	      }
+	    layout.print_any_margin (context, &locpr, row, column, true);
+	    pp_newline (context->printer);
+	  }
+
+	/* Print a line consisting of the caret/underlines for the
+	   given source line.  */
+	int final_column
+	  = get_final_column_for_row (row, s.column,
+				      diagnostic->richloc,
+				      last_non_ws);
+	pp_space (context->printer);
+	for (int column = 1; column < final_column; column++)
+	  {
+	    int new_state = get_state_at_point (row, column,
+						diagnostic->richloc, &s,
+						first_non_ws, last_non_ws);
+	    locpr.set_state (new_state);
+	    if (new_state == STATE_AT_CARET)
+	      pp_string (context->printer, context->caret_char);
+	    else if (new_state == STATE_NORMAL_TEXT)
+	      pp_character (context->printer, ' ');
+	    else {
+	      /* Underlining a range.  */
+	      const location_range *range
+		= &diagnostic->richloc->m_ranges[new_state];
+	      if (range->m_start.line != range->m_finish.line)
+		{
+		  /* Underline multiline ranges using southwest/southeast
+		     corners for the very first/last positions, and hbar
+		     everywhere else.  */
+		  if (row == range->m_start.line
+		      && column == range->m_start.column)
+		    /* Start of multiline range: use a southwest corner for
+		       the underline.  */
+		    pp_string (context->printer, g_line_art.underline_start);
+		  else if (row == range->m_finish.line
+			   && column == range->m_finish.column - 1)
+		    /* End of multiline range: use a southeast corner for
+		       the underline.  */
+		    pp_string (context->printer, g_line_art.underline_end);
+		  else
+		    pp_string (context->printer, g_line_art.underline_hbar);
+		}
+	      else
+		/* Render single-line ranges with the hbar character
+		   throughout.  */
+		pp_string (context->printer, g_line_art.underline_hbar);
+	    }
+	  }
+	layout.print_any_margin (context, &locpr, row, final_column, false);
+
+	if (row < last_line)
+	  pp_newline (context->printer);
+      }
+  }
+
   pp_set_prefix (context->printer, saved_prefix);
   pp_needs_newline (context->printer) = true;
 }
 
+int
+rich_location::get_first_line ()
+{
+  lazily_expand_location ();
+  int result = m_expanded_location.line;
+
+  int i;
+  location_range *range;
+
+  FOR_EACH_VEC_ELT (m_ranges, i, range)
+    if (result > range->m_start.line)
+      result = range->m_start.line;
+
+  return result;
+}
+
+int
+rich_location::get_last_line ()
+{
+  lazily_expand_location ();
+  int result = m_expanded_location.line;
+
+  int i;
+  location_range *range;
+
+  FOR_EACH_VEC_ELT (m_ranges, i, range)
+    if (result < range->m_finish.line)
+      result = range->m_finish.line;
+
+  return result;
+}
+
+void
+rich_location::lazily_expand_location ()
+{
+  if (m_have_expanded_location)
+    return;
+
+  m_expanded_location = expand_location_to_spelling_point (m_loc);
+  m_have_expanded_location = true;
+}
+
 /* Functions at which to stop the backtrace print.  It's not
    particularly helpful to print the callers of these functions.  */
 
@@ -604,7 +1339,7 @@ void
 default_diagnostic_starter (diagnostic_context *context,
 			    diagnostic_info *diagnostic)
 {
-  diagnostic_report_current_module (context, diagnostic->location);
+  diagnostic_report_current_module (context, diagnostic->richloc->get_loc ());
   pp_set_prefix (context->printer, diagnostic_build_prefix (context,
 							    diagnostic));
 }
@@ -716,7 +1451,7 @@ bool
 diagnostic_report_diagnostic (diagnostic_context *context,
 			      diagnostic_info *diagnostic)
 {
-  location_t location = diagnostic->location;
+  location_t location = diagnostic->richloc->get_loc();
   diagnostic_t orig_diag_kind = diagnostic->kind;
   const char *saved_format_spec;
 
@@ -859,7 +1594,7 @@ diagnostic_report_diagnostic (diagnostic_context *context,
 	  free (option_text);
 	}
     }
-  diagnostic->message.locus = &diagnostic->location;
+  diagnostic->message.locus = diagnostic->richloc->get_loc_addr ();
   diagnostic->message.x_data = &diagnostic->x_data;
   diagnostic->x_data = NULL;
   pp_format (context->printer, &diagnostic->message);
@@ -936,9 +1671,40 @@ diagnostic_append_note (diagnostic_context *context,
   diagnostic_info diagnostic;
   va_list ap;
   const char *saved_prefix;
+  rich_location richloc (location);
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_NOTE);
+  if (context->inhibit_notes_p)
+    {
+      va_end (ap);
+      return;
+    }
+  saved_prefix = pp_get_prefix (context->printer);
+  pp_set_prefix (context->printer,
+                 diagnostic_build_prefix (context, &diagnostic));
+  pp_newline (context->printer);
+  pp_format (context->printer, &diagnostic.message);
+  pp_output_formatted_text (context->printer);
+  pp_destroy_prefix (context->printer);
+  pp_set_prefix (context->printer, saved_prefix);
+  diagnostic_show_locus (context, &diagnostic);
+  va_end (ap);
+}
+
+/* Same as diagnostic_append_note, but at RICHLOC. */
+
+void
+diagnostic_append_note_at_rich_loc (diagnostic_context *context,
+				    rich_location *richloc,
+				    const char * gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
+  const char *saved_prefix;
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location, DK_NOTE);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, richloc, DK_NOTE);
   if (context->inhibit_notes_p)
     {
       va_end (ap);
@@ -963,16 +1729,17 @@ emit_diagnostic (diagnostic_t kind, location_t location, int opt,
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
   if (kind == DK_PERMERROR)
     {
-      diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
 			   permissive_error_kind (global_dc));
       diagnostic.option_index = permissive_error_option (global_dc);
     }
   else {
-      diagnostic_set_info (&diagnostic, gmsgid, &ap, location, kind);
+      diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, kind);
       if (kind == DK_WARNING || kind == DK_PEDWARN)
 	diagnostic.option_index = opt;
   }
@@ -989,9 +1756,23 @@ inform (location_t location, const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (location);
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_NOTE);
+  report_diagnostic (&diagnostic);
+  va_end (ap);
+}
+
+/* Same as "inform", but at RICHLOC.  */
+void
+inform_at_rich_loc (rich_location *richloc, const char *gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location, DK_NOTE);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, richloc, DK_NOTE);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1004,11 +1785,12 @@ inform_n (location_t location, int n, const char *singular_gmsgid,
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (location);
 
   va_start (ap, plural_gmsgid);
   diagnostic_set_info_translated (&diagnostic,
                                   ngettext (singular_gmsgid, plural_gmsgid, n),
-                                  &ap, location, DK_NOTE);
+                                  &ap, &richloc, DK_NOTE);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1022,9 +1804,10 @@ warning (int opt, const char *gmsgid, ...)
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (input_location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, input_location, DK_WARNING);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_WARNING);
   diagnostic.option_index = opt;
 
   ret = report_diagnostic (&diagnostic);
@@ -1042,9 +1825,27 @@ warning_at (location_t location, int opt, const char *gmsgid, ...)
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location, DK_WARNING);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_WARNING);
+  diagnostic.option_index = opt;
+  ret = report_diagnostic (&diagnostic);
+  va_end (ap);
+  return ret;
+}
+
+/* Same as warning at, but using RICHLOC.  */
+
+bool
+warning_at_rich_loc (rich_location *richloc, int opt, const char *gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
+  bool ret;
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, richloc, DK_WARNING);
   diagnostic.option_index = opt;
   ret = report_diagnostic (&diagnostic);
   va_end (ap);
@@ -1062,11 +1863,13 @@ warning_n (location_t location, int opt, int n, const char *singular_gmsgid,
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (location);
 
   va_start (ap, plural_gmsgid);
   diagnostic_set_info_translated (&diagnostic,
                                   ngettext (singular_gmsgid, plural_gmsgid, n),
-                                  &ap, location, DK_WARNING);
+                                  &ap, &richloc, DK_WARNING
+);
   diagnostic.option_index = opt;
   ret = report_diagnostic (&diagnostic);
   va_end (ap);
@@ -1092,9 +1895,10 @@ pedwarn (location_t location, int opt, const char *gmsgid, ...)
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location,  DK_PEDWARN);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,  DK_PEDWARN);
   diagnostic.option_index = opt;
   ret = report_diagnostic (&diagnostic);
   va_end (ap);
@@ -1114,9 +1918,28 @@ permerror (location_t location, const char *gmsgid, ...)
   diagnostic_info diagnostic;
   va_list ap;
   bool ret;
+  rich_location richloc (location);
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc,
+                       permissive_error_kind (global_dc));
+  diagnostic.option_index = permissive_error_option (global_dc);
+  ret = report_diagnostic (&diagnostic);
+  va_end (ap);
+  return ret;
+}
+
+/* Same as "permerror", but at RICHLOC.  */
+
+bool
+permerror_at_rich_loc (rich_location *richloc, const char *gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
+  bool ret;
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, location,
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, richloc,
                        permissive_error_kind (global_dc));
   diagnostic.option_index = permissive_error_option (global_dc);
   ret = report_diagnostic (&diagnostic);
@@ -1131,9 +1954,10 @@ error (const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (input_location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, input_location, DK_ERROR);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_ERROR);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1146,11 +1970,12 @@ error_n (location_t location, int n, const char *singular_gmsgid,
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (location);
 
   va_start (ap, plural_gmsgid);
   diagnostic_set_info_translated (&diagnostic,
                                   ngettext (singular_gmsgid, plural_gmsgid, n),
-                                  &ap, location, DK_ERROR);
+                                  &ap, &richloc, DK_ERROR);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1161,9 +1986,25 @@ error_at (location_t loc, const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (loc);
+
+  va_start (ap, gmsgid);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_ERROR);
+  report_diagnostic (&diagnostic);
+  va_end (ap);
+}
+
+/* Same as above, but use RICH_LOC.  */
+
+void
+error_at_rich_loc (rich_location *rich_loc, const char *gmsgid, ...)
+{
+  diagnostic_info diagnostic;
+  va_list ap;
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, loc, DK_ERROR);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, rich_loc,
+		       DK_ERROR);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1176,9 +2017,10 @@ sorry (const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (input_location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, input_location, DK_SORRY);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_SORRY);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1199,9 +2041,10 @@ fatal_error (location_t loc, const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (loc);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, loc, DK_FATAL);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_FATAL);
   report_diagnostic (&diagnostic);
   va_end (ap);
 
@@ -1217,9 +2060,10 @@ internal_error (const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (input_location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, input_location, DK_ICE);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_ICE);
   report_diagnostic (&diagnostic);
   va_end (ap);
 
@@ -1234,9 +2078,10 @@ internal_error_no_backtrace (const char *gmsgid, ...)
 {
   diagnostic_info diagnostic;
   va_list ap;
+  rich_location richloc (input_location);
 
   va_start (ap, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &ap, input_location, DK_ICE_NOBT);
+  diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_ICE_NOBT);
   report_diagnostic (&diagnostic);
   va_end (ap);
 
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 02434d8..e48f0bc 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -23,6 +23,7 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "pretty-print.h"
 #include "diagnostic-core.h"
+#include "rich-location.h"
 
 /* A diagnostic is described by the MESSAGE to send, the FILE and LINE of
    its context and its KIND (ice, error, warning, note, ...)  See complete
@@ -30,7 +31,7 @@ along with GCC; see the file COPYING3.  If not see
 struct diagnostic_info
 {
   text_info message;
-  location_t location;
+  rich_location *richloc;
   unsigned int override_column;
   /* Auxiliary data for client.  */
   void *x_data;
@@ -106,7 +107,7 @@ struct diagnostic_context
   int caret_max_width;
 
   /* Character used for caret diagnostics.  */
-  char caret_char;
+  const char *caret_char;
 
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
@@ -282,13 +283,17 @@ extern bool diagnostic_report_diagnostic (diagnostic_context *,
 					  diagnostic_info *);
 #ifdef ATTRIBUTE_GCC_DIAG
 extern void diagnostic_set_info (diagnostic_info *, const char *, va_list *,
-				 location_t, diagnostic_t) ATTRIBUTE_GCC_DIAG(2,0);
+				 rich_location *, diagnostic_t) ATTRIBUTE_GCC_DIAG(2,0);
 extern void diagnostic_set_info_translated (diagnostic_info *, const char *,
-					    va_list *, location_t,
+					    va_list *, rich_location *,
 					    diagnostic_t)
      ATTRIBUTE_GCC_DIAG(2,0);
 extern void diagnostic_append_note (diagnostic_context *, location_t,
                                     const char *, ...) ATTRIBUTE_GCC_DIAG(3,4);
+extern void diagnostic_append_note_at_rich_loc (diagnostic_context *,
+						rich_location *,
+						const char *, ...)
+  ATTRIBUTE_GCC_DIAG(3,4);
 #endif
 extern char *diagnostic_build_prefix (diagnostic_context *, const diagnostic_info *);
 void default_diagnostic_starter (diagnostic_context *, diagnostic_info *);
@@ -306,7 +311,7 @@ static inline expanded_location
 diagnostic_expand_location (const diagnostic_info * diagnostic)
 {
   expanded_location s
-    = expand_location_to_spelling_point (diagnostic->location);
+    = expand_location_to_spelling_point (diagnostic->richloc->get_loc ());
   if (diagnostic->override_column)
     s.column = diagnostic->override_column;
   return s;
diff --git a/gcc/fortran/cpp.c b/gcc/fortran/cpp.c
index e239f21..8943414 100644
--- a/gcc/fortran/cpp.c
+++ b/gcc/fortran/cpp.c
@@ -1045,6 +1045,7 @@ cb_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
   diagnostic_t dlevel;
   bool save_warn_system_headers = global_dc->dc_warn_system_headers;
   bool ret;
+  rich_location richloc (location);
 
   switch (level)
     {
@@ -1073,7 +1074,7 @@ cb_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
       gcc_unreachable ();
     }
   diagnostic_set_info_translated (&diagnostic, msg, ap,
-				  location, dlevel);
+				  &richloc, dlevel);
   if (column_override)
     diagnostic_override_column (&diagnostic, column_override);
   if (reason == CPP_W_WARNING_DIRECTIVE)
diff --git a/gcc/fortran/error.c b/gcc/fortran/error.c
index da0eb8f..1aba233 100644
--- a/gcc/fortran/error.c
+++ b/gcc/fortran/error.c
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "diagnostic-color.h"
 #include "tree-diagnostic.h" /* tree_diagnostics_defaults */
+#include "box-drawing.h"
 
 #include <new> /* For placement-new */
 
@@ -1125,7 +1126,7 @@ gfc_format_decoder (pretty_printer *pp,
 	  = linemap_position_for_loc_and_offset (line_table,
 						 loc->lb->location,
 						 offset);
-	global_dc->caret_char = '1';
+	global_dc->caret_char = "1";
 	pp_string (pp, result);
 	return true;
       }
@@ -1639,7 +1640,7 @@ gfc_diagnostics_init (void)
   diagnostic_starter (global_dc) = gfc_diagnostic_starter;
   diagnostic_finalizer (global_dc) = gfc_diagnostic_finalizer;
   diagnostic_format_decoder (global_dc) = gfc_format_decoder;
-  global_dc->caret_char = '^';
+  global_dc->caret_char = g_line_art.caret;
   pp_warning_buffer = new (XNEW (output_buffer)) output_buffer ();
   pp_warning_buffer->flush_p = false;
   pp_error_buffer = new (XNEW (output_buffer)) output_buffer ();
@@ -1654,5 +1655,5 @@ gfc_diagnostics_finish (void)
      defaults.  */
   diagnostic_starter (global_dc) = gfc_diagnostic_starter;
   diagnostic_finalizer (global_dc) = gfc_diagnostic_finalizer;
-  global_dc->caret_char = '^';
+  global_dc->caret_char = g_line_art.caret;
 }
diff --git a/gcc/input.c b/gcc/input.c
index 18c1e50..dd23237 100644
--- a/gcc/input.c
+++ b/gcc/input.c
@@ -685,27 +685,27 @@ read_line_num (fcache *c, size_t line_num,
   return read_next_line (c, line, line_len);
 }
 
-/* Return the physical source line that corresponds to xloc in a
+/* Return the physical source line that corresponds to FILE_PATH/LINE in a
    buffer that is statically allocated.  The newline is replaced by
    the null character.  Note that the line can contain several null
    characters, so LINE_LEN, if non-null, points to the actual length
    of the line.  */
 
 const char *
-location_get_source_line (expanded_location xloc,
+location_get_source_line (const char *file_path, int line,
 			  int *line_len)
 {
   static char *buffer;
   static ssize_t len;
 
-  if (xloc.line == 0)
+  if (line == 0)
     return NULL;
 
-  fcache *c = lookup_or_add_file_to_cache_tab (xloc.file);
+  fcache *c = lookup_or_add_file_to_cache_tab (file_path);
   if (c == NULL)
     return NULL;
 
-  bool read = read_line_num (c, xloc.line, &buffer, &len);
+  bool read = read_line_num (c, line, &buffer, &len);
 
   if (read && line_len)
     *line_len = len;
diff --git a/gcc/input.h b/gcc/input.h
index 7a0483f..6ac7eaf 100644
--- a/gcc/input.h
+++ b/gcc/input.h
@@ -38,7 +38,7 @@ extern char builtins_location_check[(BUILTINS_LOCATION
 
 extern bool is_location_from_builtin_token (source_location);
 extern expanded_location expand_location (source_location);
-extern const char *location_get_source_line (expanded_location xloc,
+extern const char *location_get_source_line (const char *file_path, int line,
 					     int *line_size);
 extern expanded_location expand_location_to_spelling_point (source_location);
 extern source_location expansion_point_location_if_in_system_header (source_location);
diff --git a/gcc/intl.c b/gcc/intl.c
index a902446..23c0539 100644
--- a/gcc/intl.c
+++ b/gcc/intl.c
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "intl.h"
+#include "box-drawing.h"
 
 #ifdef HAVE_LANGINFO_CODESET
 #include <langinfo.h>
@@ -86,6 +87,15 @@ gcc_init_libintl (void)
 	}
 #endif
     }
+
+  bool have_utf8 = false;
+#if defined HAVE_LANGINFO_CODESET
+  if (locale_utf8)
+    have_utf8 = true;
+#endif
+
+  //fprintf (stderr, "have_utf8: %s\n", have_utf8 ? "true" : "false");
+  g_line_art.init (have_utf8);
 }
 
 #if defined HAVE_WCHAR_H && defined HAVE_WORKING_MBSTOWCS && defined HAVE_WCSWIDTH
diff --git a/gcc/location-hacks.c b/gcc/location-hacks.c
new file mode 100644
index 0000000..858f7da
--- /dev/null
+++ b/gcc/location-hacks.c
@@ -0,0 +1,319 @@
+/* A file containing kludges and hardcoded data
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "rtl.h"
+#include "hash-set.h"
+#include "machmode.h"
+#include "vec.h"
+#include "double-int.h"
+#include "input.h"
+#include "alias.h"
+#include "symtab.h"
+#include "wide-int.h"
+#include "inchash.h"
+#include "tree-core.h"
+#include "tree.h"
+#include "diagnostic-core.h"
+#include "rich-location.h"
+#include "print-tree.h"
+#include "pretty-print.h"
+#include "intl.h"
+#include "location-hacks.h"
+
+/* We need to come up with an efficient way of storing information on
+   source code *ranges* (rather than mere points) for various constructs
+   such as expressions and macros.
+
+   Ideally the location data would preserve range information, and we'd
+   consult that.
+
+   In the meantime, here's a massive kludge: a magical location "oracle",
+   that simply hardcodes the data for each testcase.
+
+   Obviously this needs to be eliminated, but having it allows us to work on
+   other aspects of rich location reporting (e.g. to extend the diagnostics
+   API, and to write testcases) without needing to address the range
+   representation issue up-front.
+
+   Note that the column numbers here are 0-based convention, for ease of
+   working with Emacs, whereas the GCC location data structures and
+   reporting mechanisms are 1-based.  */
+
+struct hacked_range
+{
+  int start_line;
+  int start_column;
+  int finish_line;
+  int finish_column;
+};
+
+struct expr_locations_for_testcase
+{
+  const char *testcase_name;
+  int num_locs;
+  struct hacked_range loc_range[3];
+};
+
+struct expr_locations_for_testcase expr_location_oracle[] = {
+  {"testsuite/gcc.dg/20150303-01.c",
+   3,
+   {{6, 2, 6, 15},
+    {7, 2, 7, 6},
+    {4, 16, 4, 20}}
+  },
+
+  {"testsuite/gcc.dg/20150303-04.c",
+   2,
+   {{9, 2, 9, 10},
+    {9, 11, 9, 12}}
+  },
+
+  {"testsuite/gcc.dg/20150304-01.c",
+   3,
+   {{4, 89, 4, 92},
+    {4, 95, 4, 98},
+    {11, 6, 11, 17}}
+  },
+
+  {"testsuite/gcc.dg/divbyzero.c",
+   3,
+   {{13, 17, 13, 21},
+    {14, 17, 14, 18},
+    {15, 17, 15, 21}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/20150303-02.C",
+   2,
+   {{12, 9, 12, 17},
+    {12, 20, 12, 22}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/20150303-03.C",
+   1,
+   {{5, 9, 5, 16}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/20150304-02.C",
+   1,
+   {{10, 3, 10, 7}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/bitfld1.C",
+   2,
+   {{12, 10, 12, 13},
+    {12, 16, 12, 19}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/multiline-rich-location-01.C",
+   2,
+   {{12, 10, 12, 49},
+    {13, 12, 13, 52}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/multiline-rich-location-02.C",
+   2,
+   {{25, 10, 29, 70},
+    {30, 12, 35, 65}}
+  },
+
+  {"testsuite/g++.dg/diagnostic/wrong-tag-1.C",
+   2,
+   {{5, 14, 5, 21},
+    {4, 15, 4, 22}}
+  },
+
+  {"testsuite/g++.dg/other/error16.C",
+   2,
+   {{14, 2, 14, 10},
+    {14, 14, 14, 15}}
+  },
+
+  /* Sentinel.  */
+  {NULL, 0, {}}
+};
+
+static bool
+consult_the_range_oracle (location_range *r)
+{
+  const char *testcase = strstr (main_input_filename,
+				 "testsuite/");
+  if (!testcase)
+    return false;
+
+  /* Locate testcase by name within "expr_location_oracle" array.  */
+  static int num_calls = 0;
+  for (expr_locations_for_testcase *iter = expr_location_oracle;
+       iter->testcase_name;
+       iter++)
+    if (0 == strcmp (testcase, iter->testcase_name))
+      {
+	if (num_calls >= iter->num_locs)
+	  {
+	    fprintf (stderr,
+		     "ran out of locations within location oracle"
+		     " (testcase: %s)\n",
+		     testcase);
+	    return false;
+	  }
+
+	gcc_assert (num_calls < iter->num_locs);
+	struct hacked_range *hr = &iter->loc_range[num_calls++];
+	r->m_start.file = main_input_filename;
+	r->m_finish.file = main_input_filename;
+	r->m_start.line = hr->start_line;
+	r->m_finish.line = hr->finish_line;
+	/* Convert from 0-based column numberings to 1-based numbering.  */
+	r->m_start.column = hr->start_column + 1;
+	r->m_finish.column = hr->finish_column + 1;
+	return true;
+      }
+
+  /* Testcase not found.  */
+  fprintf (stderr,
+	   "not found within location oracle (testcase: %s)\n",
+	   testcase);
+  return false;
+}
+
+/* Get the location_range for the given tree expression.
+   For now, this simply looks thinks up in the hardcoded data.  */
+
+bool
+get_range_for_expr (tree /* expr */, location_range *r)
+{
+  /* For now, a total hack.  */
+  return consult_the_range_oracle (r);
+}
+
+/* Get the location_range for the given macro.
+   For now, this simply looks thinks up in the hardcoded data.  */
+
+bool
+get_range_for_macro (const line_map * /* macro */, location_range *r)
+{
+  /* Likewise, this is also currently a total hack.  */
+  return consult_the_range_oracle (r);
+}
+
+struct string_location
+{
+  int index;
+  int line;
+  int column; /* 0-based */
+};
+
+struct string_constant
+{
+  const char *chars;
+  int num_locs;
+  string_location locs[3];
+};
+
+struct string_locations_for_testcase
+{
+  const char *testcase_name;
+  int num_consts;
+  string_constant consts[1];
+};
+
+struct string_locations_for_testcase string_location_oracle[] = {
+  {"testsuite/gcc.dg/format/pr52952-multiline-format-string.c",
+   1,
+   {{"before the fmt specifier%dafter the fmt specifier",
+     2,
+     {{24, 6, 11},
+      {25, 7, 11}}}}
+  },
+
+  {NULL, 0, {}}
+};
+
+/* Determine the source-code location of character IDX within
+   string constant STRING_CST, writing the resuilt to LOC_OUT
+   and EXPLOC_OUT.  Ideally we'd be able to get this from the
+   STRING_CST.  Sadly, it's a hack, and HACKED_LOC_HINT is
+   needed to help for the case where we don't have hardcoded
+   data.  */
+
+void
+get_location_within_string_cst (location_t *loc_out,
+				expanded_location *exploc_out,
+				tree string_cst, int idx,
+				location_t hacked_loc_hint)
+{
+  gcc_assert (loc_out);
+  gcc_assert (exploc_out);
+  gcc_assert (string_cst);
+  gcc_assert (TREE_CODE (string_cst) == STRING_CST);
+  gcc_assert (idx >= 0);
+
+  /* FIXME: STRING_CSTs don't have a LOCATION_P, so for now we have
+     to fake it.  */
+  gcc_assert (!CAN_HAVE_LOCATION_P (string_cst));
+
+  const char *testcase = strstr (main_input_filename,
+				 "testsuite/");
+  if (testcase)
+    {
+      for (string_locations_for_testcase *iter = string_location_oracle;
+	   iter->testcase_name;
+	   iter++)
+	if (0 == strcmp (testcase, iter->testcase_name))
+	  {
+	    const char *str_chars = TREE_STRING_POINTER (string_cst);
+	    int str_length = TREE_STRING_LENGTH (string_cst);
+
+	    for (int i = 0; i < iter->num_consts; i++)
+	      {
+		string_constant *str_const = &iter->consts[i];
+		if (0 == strncmp (str_chars, str_const->chars, str_length - 1))
+		  for (int j = 0; j < str_const->num_locs; j++)
+		    {
+		      string_location *str_loc = &str_const->locs[j];
+		      if (idx == str_loc->index)
+			{
+			  *loc_out = hacked_loc_hint;
+			  *exploc_out
+			    = expand_location_to_spelling_point (*loc_out);
+			  exploc_out->line = str_loc->line;
+			  /* Convert from 0-based column numberings to
+			     1-based numbering.  */
+			  exploc_out->column = str_loc->column + 1;
+			  return;
+		      }
+		    }
+	      }
+	}
+    }
+
+  *loc_out = hacked_loc_hint;
+
+  *exploc_out = expand_location_to_spelling_point (*loc_out);
+
+  /* FIXME: for now, assume a simple contiguous string literal
+     with no escaped characters
+     The existing loc hint is for the opening quote of the string
+     literal, and so we need an extra + 1 so that idx 0 is the location
+     of the initial character.  */
+  exploc_out->column += idx + 1;
+}
diff --git a/gcc/location-hacks.h b/gcc/location-hacks.h
new file mode 100644
index 0000000..c07a624
--- /dev/null
+++ b/gcc/location-hacks.h
@@ -0,0 +1,24 @@
+/* Header file for location kludges and hardcoded data
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+extern bool
+get_range_for_expr (tree expr, location_range *r);
+
+extern bool
+get_range_for_macro (const line_map * macro, location_range *r);
diff --git a/gcc/rich-location.c b/gcc/rich-location.c
new file mode 100644
index 0000000..ebc40fd
--- /dev/null
+++ b/gcc/rich-location.c
@@ -0,0 +1,147 @@
+/* Implementation of rich_location class
+   Copyright (C) 2014-2015 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "rtl.h"
+#include "hash-set.h"
+#include "machmode.h"
+#include "vec.h"
+#include "double-int.h"
+#include "input.h"
+#include "alias.h"
+#include "symtab.h"
+#include "wide-int.h"
+#include "inchash.h"
+#include "tree-core.h"
+#include "tree.h"
+#include "diagnostic-core.h"
+#include "rich-location.h"
+#include "print-tree.h"
+#include "pretty-print.h"
+#include "intl.h"
+#include "location-hacks.h"
+
+/* A ctor for rich_location, constructing a rich_location for
+   a subset of string literal STRING_CST.
+   CARET_IDX, START_IDX and END_IDX are indices of characters within
+   the string, for the caret, and for the start/end of a range.  */
+
+rich_location::rich_location (tree string_cst,
+			      int caret_idx,
+			      int start_idx,
+			      int end_idx,
+			      location_t hacked_loc_hint)
+  : m_loc (UNKNOWN_LOCATION),
+    m_ranges (),
+    m_have_expanded_location (false)
+{
+  gcc_assert (string_cst);
+  gcc_assert (TREE_CODE (string_cst) == STRING_CST);
+
+  m_have_expanded_location = true;
+  get_location_within_string_cst (&m_loc,
+				  &m_expanded_location,
+				  string_cst, caret_idx,
+				  hacked_loc_hint);
+
+  location_t tmploc;
+  location_range r;
+  r.m_caption = NULL;
+  get_location_within_string_cst (&tmploc,
+				  &r.m_start,
+				  string_cst, start_idx,
+				  hacked_loc_hint);
+  /* For the end of the range, the end_idx is within the range,
+     so get its location, and offset by 1 to locate the first
+     column *outside* the range.  */
+  get_location_within_string_cst (&tmploc,
+				  &r.m_finish,
+				  string_cst, end_idx,
+				  hacked_loc_hint);
+  r.m_finish.column += 1;
+  m_ranges.safe_push (r);
+}
+
+/* Add a range to the rich_location, covering expression EXPR. */
+
+void
+rich_location::add_expr (tree expr)
+{
+  gcc_assert (expr);
+
+  location_range r;
+  r.m_caption = NULL;
+  //debug_tree (expr);
+  if (get_range_for_expr (expr, &r))
+    m_ranges.safe_push (r);
+}
+
+/* As per rich_location::add_expr, but adding a caption to the
+   resulting range.  */
+
+void
+rich_location::add_expr_with_caption (tree expr, pretty_printer *pp,
+				      const char *gmsgid, ...)
+{
+  gcc_assert (expr);
+  gcc_assert (gmsgid);
+
+  location_range r;
+  r.m_caption = NULL;
+  //debug_tree (expr);
+  if (get_range_for_expr (expr, &r))
+    {
+      /* We want to save the string information for later replay.
+	 The printer->format_decoder machinery assumes that we're working
+	 directly from va_args.  It's not possible to save a va_args
+	 for later reuse, since it makes use of a specific stack frame
+	 which needs to still be around.
+	 Hence we need to expand the formatting now, and save the result.  */
+
+      /* FIXME: Only bother if show-caret is enabled.  */
+      text_info text;
+      va_list ap;
+      va_start (ap, gmsgid);
+      text.err_no = errno;
+      text.args_ptr = &ap;
+      text.format_spec = G_(gmsgid); //_(gmsgid);
+      text.locus = NULL;
+      pp_format (pp, &text);
+      pp_output_formatted_text (pp);
+      va_end (ap);
+      r.m_caption = xstrdup (pp_formatted_text (pp));
+      pp_clear_output_area (pp);
+
+      m_ranges.safe_push (r);
+    }
+}
+
+/* Add a range to the rich_location, covering MACRO.  */
+
+void
+rich_location::add_range_for_macro (const line_map *macro)
+{
+  location_range r;
+  r.m_caption = NULL;
+  if (get_range_for_macro (macro, &r))
+    m_ranges.safe_push (r);
+}
diff --git a/gcc/rich-location.h b/gcc/rich-location.h
new file mode 100644
index 0000000..fb88952
--- /dev/null
+++ b/gcc/rich-location.h
@@ -0,0 +1,126 @@
+/* Declarations relating to class rich_location
+   Copyright (C) 2014-2015 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_RICH_LOCATION_H
+#define GCC_RICH_LOCATION_H
+
+#include "vec.h"
+
+/* Both gcc and emacs number source *lines* starting at 1, but
+   they have differing conventions for *columns*.
+
+   GCC uses a 1-based convention for source columns,
+   whereas Emacs's M-x column-number-mode uses a 0-based convention.
+
+   For example, an error in the initial, left-hand
+   column of source line 3 is reported by GCC as:
+
+      some-file.c:3:1: error: ...etc...
+
+   On navigating to the location of that error in Emacs
+   (e.g. via "next-error"),
+   the locus is reported in the Mode Line
+   (assuming M-x column-number-mode) as:
+
+     some-file.c   10%   (3, 0)
+
+   i.e. "3:1:" in GCC corresponds to "(3, 0)" in Emacs.  */
+
+/* Ranges are half-open:
+   m_start is the first location within the range, whereas
+   m_finish is the first location *after* the range.  */
+struct location_range
+{
+  expanded_location m_start;
+  expanded_location m_finish;
+
+  /* Caption, if any.  */
+  char *m_caption;
+
+  bool contains_point (int row, int column) const;
+};
+
+/* A "rich" source code location.  A position, along with
+   zero of more ranges.  Each range may optionally have a caption.  */
+
+class rich_location
+{
+ public:
+  /* Constructors.  */
+
+  /* Constructing from a location.  */
+  rich_location (location_t loc) :
+    m_loc (loc),
+    m_ranges (),
+    m_have_expanded_location (false)
+  {}
+
+  /* Constructing from a caret and a range of characters, all within
+     a string literal.
+     The range is open: both START_IDX and END_IDX are within the range.  */
+  rich_location (tree string_cst,
+		 int caret_idx,
+		 int start_idx,
+		 int end_idx,
+		 location_t hacked_loc_hint);
+
+  /* Accessors.  */
+  location_t get_loc () const { return m_loc; }
+
+  location_t *get_loc_addr () { return &m_loc; }
+
+  void
+  add_expr (tree expr);
+
+  void
+  add_expr_with_caption (tree expr, pretty_printer *pp,
+			 const char *gmsgid, ...)
+    ATTRIBUTE_GCC_DIAG(4,5);
+
+  void
+  add_range_for_macro (const line_map *macro);
+
+  int get_first_line ();
+  int get_last_line ();
+
+  location_range *get_range (unsigned int idx)
+  {
+    gcc_assert (idx < m_ranges.length ());
+    return &m_ranges[idx];
+  }
+
+  void lazily_expand_location ();
+
+//private:
+  location_t m_loc;
+  auto_vec <location_range> m_ranges;
+
+//private:
+  bool m_have_expanded_location;
+  expanded_location m_expanded_location;
+
+};
+
+extern void
+get_location_within_string_cst (location_t *loc_out,
+				expanded_location *exploc_out,
+				tree string_cst, int idx,
+				location_t hacked_loc_hint);
+
+#endif /* GCC_RICH_LOCATION_H */
diff --git a/gcc/rtl-error.c b/gcc/rtl-error.c
index c6745e4..7a7e036 100644
--- a/gcc/rtl-error.c
+++ b/gcc/rtl-error.c
@@ -70,9 +70,10 @@ diagnostic_for_asm (const rtx_insn *insn, const char *msg, va_list *args_ptr,
 		    diagnostic_t kind)
 {
   diagnostic_info diagnostic;
+  rich_location richloc (location_for_asm (insn));
 
   diagnostic_set_info (&diagnostic, msg, args_ptr,
-		       location_for_asm (insn), kind);
+		       &richloc, kind);
   report_diagnostic (&diagnostic);
 }
 
diff --git a/gcc/testsuite/g++.dg/diagnostic/20150303-02.C b/gcc/testsuite/g++.dg/diagnostic/20150303-02.C
new file mode 100644
index 0000000..9df5365
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/20150303-02.C
@@ -0,0 +1,18 @@
+// Adapted from https://gcc.gnu.org/wiki/ClangDiagnosticsComparison
+// { dg-options "-fdiagnostics-show-caret" }
+
+struct a {
+  virtual int bar();
+};
+
+struct foo : public virtual a {
+};
+
+int test(foo *P) {
+  return P->bar() + *P; // { dg-error "no match" }
+}
+
+/* { dg-begin-multiline-output "" }
+   return P->bar() + *P;
+          ~~~~~~~~ ^ ~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/g++.dg/diagnostic/20150303-03.C b/gcc/testsuite/g++.dg/diagnostic/20150303-03.C
new file mode 100644
index 0000000..472c674
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/20150303-03.C
@@ -0,0 +1,12 @@
+// Adapted from https://gcc.gnu.org/wiki/ClangDiagnosticsComparison
+// { dg-options "-fdiagnostics-show-caret" }
+
+void test(int *P) {
+  return *P + *P; // { dg-error "return-statement with a value" }
+}
+
+/*
+{ dg-begin-multiline-output "" }
+   return *P + *P;
+          ~~~~~~^
+{ dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/g++.dg/diagnostic/20150304-02.C b/gcc/testsuite/g++.dg/diagnostic/20150304-02.C
new file mode 100644
index 0000000..549de0b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/20150304-02.C
@@ -0,0 +1,23 @@
+// Adapted from https://gcc.gnu.org/wiki/ClangDiagnosticsComparison
+// { dg-options "-fdiagnostics-show-caret" }
+
+template<class T> void f(typename T::type) { } // { dg-message "candidate: template<class T> void f\\(typename T::type\\)|no type named 'type' in 'struct A'" }
+
+struct A { };
+void g()
+{
+   A a;
+   f<A>(a); // { dg-error "no matching function for call to 'f\\(A&\\)'" }
+}
+
+/*
+{ dg-begin-multiline-output "" }
+    f<A>(a);
+    ~~~~  ^
+{ dg-end-multiline-output "" }
+
+{ dg-begin-multiline-output "" }
+ template<class T> void f(typename T::type) { }
+                        ^
+{ dg-end-multiline-output "" }
+*/
diff --git a/gcc/testsuite/g++.dg/diagnostic/bitfld1.C b/gcc/testsuite/g++.dg/diagnostic/bitfld1.C
index f75df38..96ad7d7 100644
--- a/gcc/testsuite/g++.dg/diagnostic/bitfld1.C
+++ b/gcc/testsuite/g++.dg/diagnostic/bitfld1.C
@@ -1,5 +1,6 @@
 // PR c++/46001
 // { dg-do compile }
+// { dg-options "-fdiagnostics-show-caret" }
 
 struct S
 {
@@ -10,4 +11,9 @@ struct S
 struct S s;
 void *a = s.p | s.f;	// { dg-error "unsigned char:1" }
 
+/* { dg-begin-multiline-output "" }
+ void *a = s.p | s.f;
+           ~~~ ^ ~~~
+   { dg-end-multiline-output "" } */
+
 // { dg-bogus "__java_boolean" "" { target *-*-* } 11 }
diff --git a/gcc/testsuite/g++.dg/diagnostic/multiline-rich-location-01.C b/gcc/testsuite/g++.dg/diagnostic/multiline-rich-location-01.C
new file mode 100644
index 0000000..0a5a193
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/multiline-rich-location-01.C
@@ -0,0 +1,21 @@
+// Verify that we can print range information that spans
+// multiple source lines.
+
+// { dg-options "-fdiagnostics-show-caret" }
+
+struct s {};
+extern int first_function_with_a_very_long_name ();
+extern s   second_function_with_a_very_long_name ();
+
+int test()
+{
+  return (first_function_with_a_very_long_name ()
+          + second_function_with_a_very_long_name ()); // { dg-error "no match" }
+}
+
+/* { dg-begin-multiline-output "" }
+   return (first_function_with_a_very_long_name ()|
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+type 'int'
+           + second_function_with_a_very_long_name ());                           |
+           ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                             +type 's'
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/g++.dg/diagnostic/multiline-rich-location-02.C b/gcc/testsuite/g++.dg/diagnostic/multiline-rich-location-02.C
new file mode 100644
index 0000000..c1577d4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/multiline-rich-location-02.C
@@ -0,0 +1,64 @@
+// Verify that we can print range information that spans
+// multiple source lines, where the ranges themselves
+// are multiline
+
+// { dg-options "-fdiagnostics-show-caret" }
+
+struct s {};
+extern int first_function_with_a_very_long_name
+  (int lorem, int ipsum, int dolor, int sit, int amet, int consectetur,
+   int adipiscing, int elit, int sed, int eiusmod, int tempor,
+   int incididunt, int ut, int labore, int et, int dolore, int magna,
+   int aliqua);
+extern s second_function_with_a_very_long_name
+  (int lorem, int ipsum, int dolor, int sit, int amet, int consectetur,
+   int adipiscing, int elit, int sed, int eiusmod, int tempor,
+   int incididunt, int ut, int labore, int et, int dolore, int magna,
+   int aliqua);
+
+int test (int lorem, int ipsum, int dolor, int sit, int amet, int consectetur,
+          int adipiscing, int elit, int sed, int eiusmod, int tempor,
+          int incididunt, int ut, int labore, int et, int dolore, int magna,
+          int aliqua)
+{
+  /* The next line deliberately contains trailing whitespace.  */
+  return (first_function_with_a_very_long_name (lorem, ipsum, dolor, sit, amet,       
+                                                consectetur, adipiscing, elit,
+                                                sed, eiusmod, tempor,
+                                                incididunt, ut, labore, et,
+                                                dolore, magna, aliqua)
+          + second_function_with_a_very_long_name (lorem, ipsum, dolor, sit, // { dg-error "no match" }
+                                                   amet, consectetur,
+                                                   adipiscing, elit, sed,
+                                                   eiusmod, tempor, incididunt,
+                                                   ut, labore, et, dolore,
+                                                   magna, aliqua));
+}
+
+/* Note that the carets under the first line of the RHS of the expression
+   appear longer than they should: the quotes source line contains a dg
+   directive, which we can't quote in full.  The carets underline it.
+{ dg-begin-multiline-output "" }
+   return (first_function_with_a_very_long_name (lorem, ipsum, dolor, sit, amet,|
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
+                                                 consectetur, adipiscing, elit, |
+                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
+                                                 sed, eiusmod, tempor,          |
+                                                 ~~~~~~~~~~~~~~~~~~~~~          +type 'int'
+                                                 incididunt, ut, labore, et,    |
+                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~    |
+                                                 dolore, magna, aliqua)         |
+                                                 ~~~~~~~~~~~~~~~~~~~~~~         |
+           + second_function_with_a_very_long_name (lorem, ipsum, dolor, sit,                           |
+           ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
+                                                    amet, consectetur,                                  |
+                                                    ~~~~~~~~~~~~~~~~~~                                  |
+                                                    adipiscing, elit, sed,                              |
+                                                    ~~~~~~~~~~~~~~~~~~~~~~                              +type 's'
+                                                    eiusmod, tempor, incididunt,                        |
+                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~                        |
+                                                    ut, labore, et, dolore,                             |
+                                                    ~~~~~~~~~~~~~~~~~~~~~~~                             |
+                                                    magna, aliqua));                                    |
+                                                    ~~~~~~~~~~~~~~                                      |
+{ dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C b/gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C
index 2cf75f8..61890b2 100644
--- a/gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C
+++ b/gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C
@@ -1,4 +1,15 @@
 // Origin PR c++/51427
+// { dg-options "-fdiagnostics-show-caret" }
 
 typedef struct _GMutex GMutex; // { dg-message "previously declared here"}
 typedef union _GMutex GMutex; // { dg-error "tag used in naming" }
+
+/* { dg-begin-multiline-output "" }
+ typedef union _GMutex GMutex;
+               ^~~~~~~
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+ typedef struct _GMutex GMutex;
+                ^~~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/g++.dg/other/error16.C b/gcc/testsuite/g++.dg/other/error16.C
index 38c0fd6..d7d4e79 100644
--- a/gcc/testsuite/g++.dg/other/error16.C
+++ b/gcc/testsuite/g++.dg/other/error16.C
@@ -1,4 +1,5 @@
 // PR c++/17763
+// { dg-options "-fdiagnostics-show-caret" }
 
 template <typename U> struct Outer {
     struct Inner {};
@@ -12,3 +13,8 @@ int main() {
   Outer<int>  ab;
   ab.foo() == 1; // { dg-error "operand types are 'Outer<int>::Inner' and 'int'" }
 }
+
+/* { dg-begin-multiline-output "" }
+   ab.foo() == 1;
+   ~~~~~~~~ ^  ~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/20150303-01.c b/gcc/testsuite/gcc.dg/20150303-01.c
new file mode 100644
index 0000000..a286ebf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/20150303-01.c
@@ -0,0 +1,24 @@
+/* Adapted from https://gcc.gnu.org/wiki/ClangDiagnosticsComparison */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+void foo(char **argP, char **argQ)
+{
+  (argP - argQ)(); /* { dg-error "called object is not a function or function pointer" } */
+  argP();       /* { dg-error "called object 'argP' is not a function or function pointer" } */
+}
+
+/*
+{ dg-begin-multiline-output "" }
+   (argP - argQ)();
+   ^~~~~~~~~~~~~
+{ dg-end-multiline-output "" }
+
+{ dg-begin-multiline-output "" }
+   argP();
+   ^~~~
+{ dg-end-multiline-output "" }
+
+{ dg-begin-multiline-output "" }
+ void foo(char **argP, char **argQ)
+                 ^~~~
+{ dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/20150303-04.c b/gcc/testsuite/gcc.dg/20150303-04.c
new file mode 100644
index 0000000..75f3a35
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/20150303-04.c
@@ -0,0 +1,16 @@
+/* Adapted from https://gcc.gnu.org/wiki/ClangDiagnosticsComparison */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+typedef float __m128 __attribute__ ((vector_size (32)));
+void f()
+{
+  __m128 myvec[2];
+  int const *P;
+  myvec[1]/P; /* { dg-error "invalid operands to binary /" } */
+}
+
+/*
+{ dg-begin-multiline-output "" }
+   myvec[1]/P;
+   ~~~~~~~~^~
+{ dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/20150304-01.c b/gcc/testsuite/gcc.dg/20150304-01.c
new file mode 100644
index 0000000..755d284
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/20150304-01.c
@@ -0,0 +1,24 @@
+/* Adapted from https://gcc.gnu.org/wiki/ClangDiagnosticsComparison */
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+#define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) /* { dg-error "invalid operands to binary < \\(have 'struct mystruct' and 'float'\\)" } */
+
+struct mystruct { int i; };
+void f() {
+  int X;
+  float F;
+  struct mystruct P;
+  X = MYMAX(P, F);  /* { dg-message "in expansion of macro 'MYMAX'" } */
+}
+
+/*
+{ dg-begin-multiline-output "" }
+ #define MYMAX(A,B)    __extension__ ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
+                                                                                          ~~~ ^ ~~~
+{ dg-end-multiline-output "" }
+
+{ dg-begin-multiline-output "" }
+   X = MYMAX(P, F);
+       ^~~~~~~~~~~
+{ dg-end-multiline-output "" }
+*/
diff --git a/gcc/testsuite/gcc.dg/divbyzero.c b/gcc/testsuite/gcc.dg/divbyzero.c
index 607aa12..e436d55 100644
--- a/gcc/testsuite/gcc.dg/divbyzero.c
+++ b/gcc/testsuite/gcc.dg/divbyzero.c
@@ -1,6 +1,7 @@
 /* Copyright (C) 2001 Free Software Foundation, Inc.  */
 
 /* { dg-do compile } */
+/* { dg-options "-fdiagnostics-show-caret" } */
 
 /* Source: Neil Booth, Oct 22 2001.  PR 150 - warn about division by
    zero.  */
@@ -19,3 +20,18 @@ int main (int argc, char *argv[])
 
   return 0;
 }
+
+/* { dg-begin-multiline-output "" }
+   int w = argc % ZERO;
+                ^ ~~~~
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+   int x = argc / 0;
+                ^ ~
+   { dg-end-multiline-output "" } */
+
+/* { dg-begin-multiline-output "" }
+   int y = argc / ZERO;
+                ^ ~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/format/pr52952-mismatching-argument-type.c b/gcc/testsuite/gcc.dg/format/pr52952-mismatching-argument-type.c
new file mode 100644
index 0000000..5534be5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr52952-mismatching-argument-type.c
@@ -0,0 +1,15 @@
+/* { dg-options "-Wformat -fdiagnostics-show-caret" } */
+
+#include <stdio.h>
+
+void foo (const char *msg)
+{
+  printf("hello %i", msg);  /* { dg-warning "format '%i' expects argument of type 'int', but argument 2 has type 'const char \\*' " } */
+}
+
+/*
+{ dg-begin-multiline-output "" }
+   printf("hello %i", msg);
+                 ^~
+{ dg-end-multiline-output "" }
+*/
diff --git a/gcc/testsuite/gcc.dg/format/pr52952-multiline-format-string.c b/gcc/testsuite/gcc.dg/format/pr52952-multiline-format-string.c
new file mode 100644
index 0000000..db46764
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr52952-multiline-format-string.c
@@ -0,0 +1,18 @@
+/* { dg-options "-Wformat -fdiagnostics-show-caret" } */
+
+extern int printf (__const char *__restrict __format, ...);
+void f(void) {
+  printf ("before the fmt specifier"
+          "%"  /* { dg-warning "format '%d' expects a matching 'int' argument" } */
+          "d"
+          "after the fmt specifier");
+}
+
+/*
+{ dg-begin-multiline-output "" }
+           "%"
+            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+           "d"
+           ~~
+{ dg-end-multiline-output "" }
+*/
diff --git a/gcc/testsuite/gcc.dg/format/pr52952-spurious-trailing-percent.c b/gcc/testsuite/gcc.dg/format/pr52952-spurious-trailing-percent.c
new file mode 100644
index 0000000..157a509
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/pr52952-spurious-trailing-percent.c
@@ -0,0 +1,15 @@
+/* { dg-options "-Wformat -fdiagnostics-show-caret" } */
+
+#include <stdio.h>
+
+void foo(void)
+{
+  printf("hello world %"); /* { dg-warning "spurious trailing" } */
+}
+
+/*
+{ dg-begin-multiline-output "" }
+   printf("hello world %");
+                       ^
+{ dg-end-multiline-output "" }
+*/
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 4fa433d..784a7c5 100644
--- a/gcc/testsuite/lib/gcc-dg.exp
+++ b/gcc/testsuite/lib/gcc-dg.exp
@@ -29,6 +29,7 @@ load_lib libgloss.exp
 load_lib target-libpath.exp
 load_lib torture-options.exp
 load_lib fortran-modules.exp
+load_lib multiline.exp
 
 # We set LC_ALL and LANG to C so that we get the same error messages as expected.
 setenv LC_ALL C
diff --git a/gcc/testsuite/lib/multiline.exp b/gcc/testsuite/lib/multiline.exp
new file mode 100644
index 0000000..9c6b23c
--- /dev/null
+++ b/gcc/testsuite/lib/multiline.exp
@@ -0,0 +1,240 @@
+#   Copyright (C) 2015 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Testing of multiline output
+
+# We have pre-existing testcases like this:
+#   |typedef struct _GMutex GMutex; // { dg-message "previously declared here"}
+# (using "|" here to indicate the start of a line),
+# generating output like this:
+#   |gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C:4:16: note: 'struct _GMutex' was previously declared here
+# where the location of the dg-message determines the expected line at
+# which the error should be reported.
+#
+# To handle rich error-reporting, we want to be able to verify that we
+# get output like this:
+#   |gcc/testsuite/g++.dg/diagnostic/wrong-tag-1.C:4:16: note: 'struct _GMutex' was previously declared here
+#   | typedef struct _GMutex GMutex; // { dg-message "previously declared here"}
+#   |                ^~~~~~~
+# where the compiler's first line of output is as before, but in
+# which it then echoes the source lines, adding annotations.
+#
+# We want to be able to write testcases that verify that the
+# emitted source-and-annotations are sane.
+#
+# A complication here is that the source lines contain comments
+# containing DejaGnu directives (such as the "dg-message" above).
+#
+# We punt this somewhat by only matching the beginnings of lines.
+# so that we can write e.g.
+#   |/* { dg-begin-multiline-output "" }
+#   | typedef struct _GMutex GMutex;
+#   |                ^~~~~~~
+#   |   { dg-end-multiline-output "" } */
+# to have the testsuite verify the expected output.
+
+############################################################################
+# Global variables.  Although global, these are intended to only be used from
+# within multiline.exp.
+############################################################################
+
+# The line number of the last dg-begin-multiline-output directive.
+set _multiline_last_beginning_line -1
+
+# A list of lists of strings.
+set _multiline_expected_outputs []
+
+############################################################################
+# Exported functions.
+############################################################################
+
+# Mark the beginning of an expected multiline output
+# All lines between this and the next dg-end-multiline-output are
+# expected to be seen.
+
+proc dg-begin-multiline-output { args } {
+    global _multiline_last_beginning_line
+    verbose "dg-begin-multiline-output: args: $args" 3
+    set line [expr [lindex $args 0] + 1]
+    set _multiline_last_beginning_line $line
+}
+
+# Mark the end of an expected multiline output
+# All lines up to here since the last dg-begin-multiline-output are
+# expected to be seen.
+
+proc dg-end-multiline-output { args } {
+    global _multiline_last_beginning_line
+    verbose "dg-end-multiline-output: args: $args" 3
+    set line [expr [lindex $args 0] - 1]
+    verbose "multiline output lines: $_multiline_last_beginning_line-$line" 3
+
+    upvar 1 prog prog
+    verbose "prog: $prog" 3
+    # "prog" now contains the filename
+    # Load it and split it into lines
+
+    set lines [_get_lines $prog $_multiline_last_beginning_line $line]
+    set _multiline_last_beginning_line -1
+
+    verbose "lines: $lines" 3
+    global _multiline_expected_outputs
+    lappend _multiline_expected_outputs $lines
+    verbose "within dg-end-multiline-output: _multiline_expected_outputs: $_multiline_expected_outputs" 3
+}
+
+# Hook to be called by prune.exp's prune_gcc_output to
+# look for the expected multiline outputs, pruning them,
+# reporting PASS for those that are found, and FAIL for
+# those that weren't found.
+#
+# It returns a pruned version of its output.
+#
+# It also clears the list of expected multiline outputs.
+
+proc handle-multiline-outputs { text } {
+    global _multiline_expected_outputs
+    set index 0
+    foreach multiline $_multiline_expected_outputs {
+	verbose "  multiline: $multiline" 4
+	set rexp [_build_multiline_regex $multiline $index]
+	verbose "rexp: ${rexp}" 4
+	# Escape newlines in $rexp so that we can print them in
+	# pass/fail results.
+	set escaped_regex [string map {"\n" "\\n"} $rexp]
+	verbose "escaped_regex: ${escaped_regex}" 4
+
+	# Use "regsub" to attempt to prune the pattern from $text
+	if {[regsub -line $rexp $text "" text]} {
+	    # Success; the multiline pattern was pruned.
+	    pass "expected multiline pattern $index was found: \"$escaped_regex\""
+	} else {
+	    fail "expected multiline pattern $index not found: \"$escaped_regex\""
+	}
+
+	set index [expr $index + 1]
+    }
+
+    # Clear the list of expected multiline outputs
+    set _multiline_expected_outputs []
+
+    return $text
+}
+
+############################################################################
+# Internal functions
+############################################################################
+
+# Load FILENAME and extract the lines from FIRST_LINE
+# to LAST_LINE (inclusive) as a list of strings.
+
+proc _get_lines { filename first_line last_line } {
+    verbose "_get_lines" 3
+    verbose "  filename: $filename" 3
+    verbose "  first_line: $first_line" 3
+    verbose "  last_line: $last_line" 3
+
+    set fp [open $filename r]
+    set file_data [read $fp]
+    close $fp
+    set data [split $file_data "\n"]
+    set linenum 1
+    set lines []
+    foreach line $data {
+	verbose "line $linenum: $line" 4
+	if { $linenum >= $first_line && $linenum <= $last_line } {
+	    lappend lines $line
+	}
+	set linenum [expr $linenum + 1]
+    }
+
+    return $lines
+}
+
+# Convert $multiline from a list of strings to a multiline regex
+# We need to support matching arbitrary followup text on each line,
+# to deal with comments containing containing DejaGnu directives.
+
+proc _build_multiline_regex { multiline index } {
+    verbose "_build_multiline_regex: $multiline $index" 4
+
+    set rexp ""
+    foreach line $multiline {
+	verbose "  line: $line" 4
+
+	# We need to escape "^" and other regexp metacharacters.
+	set line [string map {"^" "\\^"
+	                      "(" "\\("
+	                      ")" "\\)"
+	                      "[" "\\["
+	                      "]" "\\]"
+	                      "." "\\."
+	                      "?" "\\?"
+	                      "+" "\\+"
+	                      "*" "\\*"
+	                      "|" "\\|"} $line]
+
+	append rexp $line
+	if {[string match "*^" $line] || [string match "*~" $line]} {
+	    # Assume a line containing a caret/range.  This must be
+	    # an exact match.
+	} elseif {[string match "*\\|" $line]} {
+	    # Assume a source line with a right-margin.  Support
+	    # arbitrary text in place of any whitespace before the
+	    # right-margin, to deal with comments containing containing
+	    # DejaGnu directives.
+
+	    # Remove final "\|":
+	    set rexp [string range $rexp 0 [expr [string length $rexp] - 3]]
+
+	    # Trim off trailing whitespace:
+	    set old_length [string length $rexp]
+	    set rexp [string trimright $rexp]
+	    set new_length [string length $rexp]
+
+	    # Replace the trimmed whitespace with "." chars to match anything:
+	    set ws [string repeat "." [expr $old_length - $new_length]]
+	    set rexp "${rexp}${ws}"
+
+	    # Add back the trailing '\|':
+	    set rexp "${rexp}\\|"
+	} else {
+	    # Assume that we have a quoted source line.
+	    # Support arbitrary followup text on each line,
+	    # to deal with comments containing containing DejaGnu
+	    # directives.
+	    append rexp ".*"
+	}
+	append rexp "\n"
+    }
+
+    # dg.exp's dg-test trims leading whitespace from the output
+    # in this line:
+    #   set comp_output [string trimleft $comp_output]
+    # so we can't rely on the exact leading whitespace for the
+    # first line in the *first* multiline regex.
+    #
+    # Trim leading whitespace from the regexp, replacing it with
+    # a "\s*", to match zero or more whitespace characters.
+    if { $index == 0 } {
+	set rexp [string trimleft $rexp]
+	set rexp "\\s*$rexp"
+    }
+
+    verbose "rexp: $rexp" 4
+
+    return $rexp
+}
diff --git a/gcc/testsuite/lib/prune.exp b/gcc/testsuite/lib/prune.exp
index 8e4c203..fa10043 100644
--- a/gcc/testsuite/lib/prune.exp
+++ b/gcc/testsuite/lib/prune.exp
@@ -16,6 +16,8 @@
 
 # Prune messages from gcc that aren't useful.
 
+load_lib multiline.exp
+
 if ![info exists TEST_ALWAYS_FLAGS] {
     set TEST_ALWAYS_FLAGS ""
 }
@@ -68,6 +70,9 @@ proc prune_gcc_output { text } {
     # Ignore harmless warnings from Xcode 4.0.
     regsub -all "(^|\n)\[^\n\]*ld: warning: could not create compact unwind for\[^\n\]*" $text "" text
 
+    # Call into multiline.exp to handle any multiline output directives.
+    set text [handle-multiline-outputs $text]
+
     #send_user "After:$text\n"
 
     return $text
diff --git a/gcc/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 99d47cb..cf06084 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -48,7 +48,7 @@ void
 diagnostic_report_current_function (diagnostic_context *context,
 				    diagnostic_info *diagnostic)
 {
-  diagnostic_report_current_module (context, diagnostic->location);
+  diagnostic_report_current_module (context, diagnostic->richloc->get_loc ());
   lang_hooks.print_error_function (context, LOCATION_FILE (input_location),
 				   diagnostic);
 }
@@ -153,7 +153,7 @@ maybe_unwind_expanded_macro_loc (diagnostic_context *context,
      first macro which expansion triggered this trace was expanded
      inside a system header.  */
   int saved_location_line =
-    expand_location_to_spelling_point (diagnostic->location).line;
+    expand_location_to_spelling_point (diagnostic->richloc->get_loc ()).line;
 
   if (!LINEMAP_SYSP (map))
     FOR_EACH_VEC_ELT (loc_vec, ix, iter)
@@ -226,9 +226,12 @@ maybe_unwind_expanded_macro_loc (diagnostic_context *context,
                                     MACRO_MAP_EXPANSION_POINT_LOCATION (iter->map),
                                     LRK_MACRO_DEFINITION_LOCATION, NULL);
 
-        diagnostic_append_note (context, resolved_exp_loc, 
-                                "in expansion of macro %qs",
-                                linemap_map_get_macro_name (iter->map));
+	rich_location richloc (resolved_exp_loc);
+	richloc.add_range_for_macro (iter->map);
+        diagnostic_append_note_at_rich_loc
+	  (context, &richloc,
+	   "in expansion of macro %qs",
+	   linemap_map_get_macro_name (iter->map));
       }
 
   loc_vec.release ();
@@ -252,7 +255,7 @@ virt_loc_aware_diagnostic_finalizer (diagnostic_context *context,
 				     diagnostic_info *diagnostic)
 {
   maybe_unwind_expanded_macro_loc (context, diagnostic,
-				   diagnostic->location);
+				   diagnostic->richloc->get_loc ());
 }
 
 /* Default tree printer.   Handles declarations only.  */
diff --git a/rich-errors.txt b/rich-errors.txt
new file mode 100644
index 0000000..471deef
--- /dev/null
+++ b/rich-errors.txt
@@ -0,0 +1,5 @@
+TODO:
+  * eliminate the "oracle"
+    * how to store efficiently?
+  * selftests
+  * eliminate this file, or move to official docs

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]