diff mbox

v3 of diagnostic_show_locus and rich_location (was Re: [PATCH 2/5] Reimplement diagnostic_show_locus, introducing rich_location classes (v2))

Message ID 1443211881.30732.121.camel@surprise
State New
Headers show

Commit Message

David Malcolm Sept. 25, 2015, 8:11 p.m. UTC
On Fri, 2015-09-25 at 10:51 +0200, Dodji Seketeli wrote:
> Hello David,
> 
> I like this!  Thank you very much for working on this.

Thanks for the review.

> I think this patch is in great shape, and once we agree on some of the
> nits I have commented on below, I think it should go in. Oh, it also
> needs the first patch (1/5, dejagnu first) to go in first, as this one
> depends on it.  I defer to the dejagnu maintainers for that one.

Indeed.  Jeff just approved it, fwiw.

> The line-map parts are OK to me too, but I have no power on those.  So I
> defer to the FE maintainers for that.  The diagnostics parts of the
> Fortran, C++ and C FE look good to me too; these are just well contained
> mechanical adjustments, but I defer to FE maintainers for the final
> word.
> 
> Please find my comments below.

Updated patch attached.

> [...]
> 
> diff --git a/gcc/diagnostic-show-locus.c b/gcc/diagnostic-show-locus.c
> 
> [...]
> 
> +/* A class to inject colorization codes when printing the diagnostic locus,
> +   tracking state as it goes.  */
> +
> +class colorizer
> +{
> 
> [...]
> 
>     +  void set_state (int state);
>     +  void begin_state (int state);
>     +  void finish_state (int state);
> 
> I think the concept of state could use a little bit of explanation, at
> least to say that there are the same number of states, as the number
> of ranges.  And that the 'state' argument to these functions really is
> the range index.

Here's the revised comment I put in the attached patch:

+/* A class to inject colorization codes when printing the diagnostic locus.
+
+   It has one kind of colorization for each of:
+     - normal text
+     - range 0 (the "primary location")
+     - range 1
+     - range 2
+
+   The class caches the lookup of the color codes for the above.
+
+   The class also has responsibility for tracking which of the above is
+   active, filtering out unnecessary changes.  This allows layout::print_line
+   to simply request a colorization code for *every* character it prints
+   thorough this class, and have the filtering be done for it here.  */

Hopefully that comment explains the possible states the colorizer can
have.

FWIW I have a follow-up patch to add support for fix-it hints, so they
might be another kind of colorization state.
(see https://gcc.gnu.org/ml/gcc-patches/2015-09/msg00732.html for the
earlier version of said patch, in v1 of the kit).

> Also, I am thinking that there should maybe be a layout::state type,
> which would have two notional properties (for now): range_index and
> draw_caret_p. So that this function:
> 
> +bool
> +layout::get_state_at_point (/* Inputs.  */
> +			    int row, int column,
> +			    int first_non_ws, int last_non_ws,
> +			    /* Outputs.  */
> +			    int *out_range_idx,
> +			    bool *out_draw_caret_p)
> 
> Would take just one output parameter, e.g, a reference to
> layout::state.

Fixed, though I called it "struct point_state", given that it's coming
from get_state_at_point.  I passed it by pointer, since AFAIK our coding
standards don't yet approve of the use of references in the codebase
(outside of places where we need them e.g. container classes).

I also added a unit test for a rich_location with two caret locations
(mimicking one of the Fortran examples), to give us coverage for this
case:

+void test_multiple_carets (void)
+{
+#if 0
+   x = x + y /* { dg-warning "8: test" } */
+/* { dg-begin-multiline-output "" }
+    x = x + y
+        A   B
+   { dg-end-multiline-output "" } */
+#endif
+}

where the "A" and "B" as caret chars are coming from new code in the
show_locus unittest plugin.

> +layout::layout (diagnostic_context * context,
> +		const diagnostic_info *diagnostic)
> 
> [...]
> 
> +      if (loc_range->m_finish.file != m_exploc.file)
> +	continue;
> +      if (loc_range->m_show_caret_p)
> +	if (loc_range->m_finish.file != m_exploc.file)
> +	  continue;
> 
> I think the second if clause is redundant.

Good catch; thanks.  The second if clause was meant to be testing
m_caret.file.  Fixed in the updated patch.

> 
> +  if (0)
> +    show_ruler (context, line_width, m_x_offset);
> 
> This should probably be removed from the final code to be committed.

FWIW, the ruler is very helpful to me when debugging the locus-printing
(e.g. when adding fix-it-hints), and if we remove that if (0) call, we
get:

warning: ‘void show_ruler(diagnostic_context*, int, int)’ defined but
not used [-Wunused-function]

which will break bootstrap, so perhaps it instead should be an option?
"-fdiagnostics-show-ruler" or somesuch?

I don't know that it would be helpful to end-users though.

I'd prefer to just keep it in the code with the
  if (0)
as-is, since it's useful "scaffolding" for hacking on the code.

> [...]
> 
> +/* Get the column beyond the rightmost one that could contain a caret or
> +   range marker, given that we stop rendering at trailing whitespace.  */
> +
> +int
> +layout::get_x_bound_for_row (int row, int caret_column,
> +			     int last_non_ws)
> 
> Please describe what the parameters mean here, especially last_non_ws.
> I had to read its code to know that last_non_ws was the *column* of
> the last non white space character.

I renamed it to "last_non_ws_column", and fleshed out the comment:

-/* Get the column beyond the rightmost one that could contain a caret or
-   range marker, given that we stop rendering at trailing whitespace.  */
+/* Helper function for use by layout::print_line when printing the
+   annotation line under the source line.
+   Get the column beyond the rightmost one that could contain a caret or
+   range marker, given that we stop rendering at trailing whitespace.
+   ROW is the source line within the given file.
+   CARET_COLUMN is the column of range 0's caret.
+   LAST_NON_WS_COLUMN is the last column containing a non-whitespace
+   character of source (as determined when printing the source line).  */

> [...]
> 
> +void
> +layout::print_line (int row)
> 
> This function is neat.  I like it! :-)

:)

> [...]
> 
>  void
>  diagnostic_show_locus (diagnostic_context * context,
>  		       const diagnostic_info *diagnostic)
> @@ -75,16 +710,25 @@ diagnostic_show_locus (diagnostic_context * context,
>      return;
> 
> +      /* The GCC 5 routine. */
> 
> I'd say the GCC <= 5 routine ;-)

> +  else
> +    /* The GCC 6 routine.  */
> 
> And here, the GCC > 5 routine.

Changed to "GCC < 6" and "GCC >= 6", on the pedantic grounds that e.g.
5.1 > 5

> I would be surprised to see this patch in particular incur any
> noticeable increase in time and space consumption, but, have you noticed
> anythying related to that during bootstrap?

I hadn't noticed it, but I wasn't timing.  I'll have a look.

One possible nit here is that the patch expands locations when
constructing rich_location instances, and it does that for warnings
before the logic to ignore them.  So there may be some extra calls there
that aren't present in trunk, for discarded warnings.  I don't expect
that to affect the speed of the compiler though (I expect it to be lost
in the noise).

Updated patch attached.  It compiles; a bootstrap/regrtest is in
progress, but may not be done before I disappear on vacation.  I believe
it addresses all of the points you raised apart from the show_ruler one.

OK for trunk if it passes bootstrap/regrtest?
(see https://gcc.gnu.org/ml/gcc-patches/2015-09/msg01700.html for the
supporting blurb for v2).

Dave

Comments

Manuel López-Ibáñez Sept. 25, 2015, 8:18 p.m. UTC | #1
On 25 September 2015 at 22:11, David Malcolm <dmalcolm@redhat.com> wrote:
>>
>> +  if (0)
>> +    show_ruler (context, line_width, m_x_offset);
>>
>> This should probably be removed from the final code to be committed.
>
> FWIW, the ruler is very helpful to me when debugging the locus-printing
> (e.g. when adding fix-it-hints), and if we remove that if (0) call, we
> get:
>
> warning: ‘void show_ruler(diagnostic_context*, int, int)’ defined but
> not used [-Wunused-function]
>
> which will break bootstrap, so perhaps it instead should be an option?
> "-fdiagnostics-show-ruler" or somesuch?
>
> I don't know that it would be helpful to end-users though.

Functions that are useful only for debugging GCC usually start with
debug_* and have special attribute annotation (grep ^debug_) which
prevents those kinds of warnings (or the optimizers being too smart
and removing them).

Cheers,

Manuel.
Manuel López-Ibáñez Sept. 25, 2015, 8:39 p.m. UTC | #2
On 25 September 2015 at 22:18, Manuel López-Ibáñez
<lopezibanez@gmail.com> wrote:
> On 25 September 2015 at 22:11, David Malcolm <dmalcolm@redhat.com> wrote:


   context->last_location = diagnostic_location (diagnostic, 0);
-  expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
-  expanded_location s1 = { };
-  /* Zero-initialized. This is checked later by
diagnostic_print_caret_line.  */

-  if (diagnostic_location (diagnostic, 1) > BUILTINS_LOCATION)
-    s1 = diagnostic_expand_location (diagnostic, 1);
+  if (context->frontend_calls_diagnostic_print_caret_line_p)
+    {
+      /* The GCC < 6 routine. */
+      expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
+      expanded_location s1 = { };
+      /* Zero-initialized. This is checked later by
+     diagnostic_print_caret_line.  */
+
+      if (diagnostic_num_locations (diagnostic) >= 2)
+    s1 = diagnostic->message.m_richloc->get_range (1)->m_start;

-  diagnostic_print_caret_line (context, s0, s1,
-                   context->caret_chars[0],
-                   context->caret_chars[1]);
+      diagnostic_print_caret_line (context, s0, s1,
+                   context->caret_chars[0],
+                   context->caret_chars[1]);
+    }
+  else
+    /* The GCC >= 6 routine.  */
+    diagnostic_print_ranges (context, diagnostic);
 }


I haven't had time to look at the patch in detail, so please excuse me
if this is answered elsewhere.

Why do you need this hack? The whole point of moving Fortran to the
common machinery is to not have this duplication.

Can't the new code print one caret without ranges ever? Something like:

error: expected ';'
  }
   ^

If it can, then the function responsible for doing that can be called
by Fortran and it should replace diagnostic_print_caret_line.

Or is it that the new diagnostic_print_ranges cannot print multiple
carets in the same line? Like this

error: error at (1) and (2)
  adfadfafd asdfdaffa
   1            2

If this is the case, this is a missing functionality that
diagnostic_print_caret_line already has and that was ready to be used
in C/C++. See example at  (O) here:
https://gcc.gnu.org/wiki/Better_Diagnostics

In my mind, it should be possible for Fortran to pass to the
diagnostics machinery two locations with range width 1 (or 0,
depending how you want to represent a range that covers exactly one
char) and get a caret line like the example above. Why is this not
possible?

Cheers,

Manuel.
Manuel López-Ibáñez Sept. 25, 2015, 9:13 p.m. UTC | #3
+   If SHOW_CARET_P is true, then the range should be rendered with
+   a caret at its starting location.  This
+   is for use by the Fortran frontend, for implementing the
+   "%C" and "%L" format codes.  */
+
+void
+rich_location::set_range (unsigned int idx, source_range src_range,
+              bool show_caret_p, bool overwrite_loc_p)

I do not understand when is this show_caret_p used by Fortran given
the diagnostic_show_locus code mentioned earlier.

Related to this:

inline void set_location (unsigned int idx, location_t loc, bool caret_p)

is always called with the last parameter 'true' (boolean parameters
are always almost bad API). Do you really need this parameter?

+/* Overwrite the range within this text_info's rich_location.
+   For use e.g. when implementing "+" in client format decoders.  */

If we got rid of '+' we would not need this extra work. Also '+'
breaks #pragma diagnostics. Not the fault of your patch, but it just
shows that technical debt keeps accumulating.
https://gcc.gnu.org/wiki/Partial_Transitions

Cheers,

Manuel.
David Malcolm Sept. 25, 2015, 9:15 p.m. UTC | #4
On Fri, 2015-09-25 at 22:39 +0200, Manuel López-Ibáñez wrote:
> On 25 September 2015 at 22:18, Manuel López-Ibáñez
> <lopezibanez@gmail.com> wrote:
> > On 25 September 2015 at 22:11, David Malcolm <dmalcolm@redhat.com> wrote:
> 
> 
>    context->last_location = diagnostic_location (diagnostic, 0);
> -  expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
> -  expanded_location s1 = { };
> -  /* Zero-initialized. This is checked later by
> diagnostic_print_caret_line.  */
> 
> -  if (diagnostic_location (diagnostic, 1) > BUILTINS_LOCATION)
> -    s1 = diagnostic_expand_location (diagnostic, 1);
> +  if (context->frontend_calls_diagnostic_print_caret_line_p)
> +    {
> +      /* The GCC < 6 routine. */
> +      expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
> +      expanded_location s1 = { };
> +      /* Zero-initialized. This is checked later by
> +     diagnostic_print_caret_line.  */
> +
> +      if (diagnostic_num_locations (diagnostic) >= 2)
> +    s1 = diagnostic->message.m_richloc->get_range (1)->m_start;
> 
> -  diagnostic_print_caret_line (context, s0, s1,
> -                   context->caret_chars[0],
> -                   context->caret_chars[1]);
> +      diagnostic_print_caret_line (context, s0, s1,
> +                   context->caret_chars[0],
> +                   context->caret_chars[1]);
> +    }
> +  else
> +    /* The GCC >= 6 routine.  */
> +    diagnostic_print_ranges (context, diagnostic);
>  }
> 
> 
> I haven't had time to look at the patch in detail, so please excuse me
> if this is answered elsewhere.

(nods; the discussion has gotten large).

> Why do you need this hack? The whole point of moving Fortran to the
> common machinery is to not have this duplication.

I attempted to address this in:
  https://gcc.gnu.org/ml/gcc-patches/2015-09/msg01700.html
where I said:

  * The Fortran frontend has its own logic for printing multiple
  locations, repeatedly calling in to diagnostic_print_caret_line.
  I hope the new printing logic is suitable for use by Fortran, but I
  wanted to keep the job of "introducing range-capable printing logic"
  separate from that of "updating Fortran diagnostics to use it",
  since I'm not very familiar with Fortran, and what is desirable
  there.  Hence to faithfully preserve the existing behavior, I
  introduced a flag into the diagnostic_context:
    "frontend_calls_diagnostic_print_caret_line_p"
  which is set by the Fortran frontend, and makes diagnostic_show_locus
  use the existing printing logic.  Hopefully that's acceptable,
  say, as a migration path.

My recollection is that I saw that the Fortran frontend has logic for
calling into diagnostic_print_caret_line, noticed that the fortran
testsuite has dg- assertions about finding specific messages, and I got
worried that they embed assumptions about how the old printer worked.
Hence I wanted to avoid touching that for the first version, and so in
this patch it's a hybrid of the old Fortran printing code with the new
representation for multiple locations.

Maybe that's a cop-out.  Would you prefer that the patch goes all the
way, and that I attempt to eliminate all calls to
diagnostic_print_caret_line from the Fortran FE, and eliminate the old
implementation?  (either now, or as a followup patch?)  I may need
assistance with that; I suspect that some of the dg- assertions in the
Fortran test suite may need updating.

> Can't the new code print one caret without ranges ever? Something like:
> 
> error: expected ';'
>   }
>    ^

It can handle that just fine.  See the examples in line-map.h in the
patch for the kinds of things that a rich_location can represent.


> If it can, then the function responsible for doing that can be called
> by Fortran and it should replace diagnostic_print_caret_line.
> 
> Or is it that the new diagnostic_print_ranges cannot print multiple
> carets in the same line? Like this
> 
> error: error at (1) and (2)
>   adfadfafd asdfdaffa
>    1            2

It can do that too; again see the big comment in line-map.h

> If this is the case, this is a missing functionality that
> diagnostic_print_caret_line already has and that was ready to be used
> in C/C++.

We're good, I believe.

> See example at  (O) here:
> https://gcc.gnu.org/wiki/Better_Diagnostics

Pasting it here:

foo.cc: 3:17,3:22: warning: missing braces around initializer for ‘int
[2]’ [-Wmissing-braces]
int a[2][2] = { 0, 1 , 2, 3 }; // { dg-warning "" }
                ^    ^ 
                {    }


It can support printing carets at both locations.  For the braces line,
I'd prefer to do those by explicitly adding a fixit-hints API.  I have a
followup patch to do that; see
  https://gcc.gnu.org/ml/gcc-patches/2015-09/msg00732.html
for an earlier version of said patch, which in fact uses that example in
the unit tests "test_fixit_insert".  Although to be fair, I did that
with a single range rather than a pair of carets:

    int a[2][2] = { 0, 1 , 2, 3 };
                    ^~~~
                    {   }

The code supports both approaches (I feel the latter is slightly more
user-friendly as it's more clearly identifying the initializer for
int[2]  ...but this is veering off into bike-shed territory).

> In my mind, it should be possible for Fortran to pass to the
> diagnostics machinery two locations with range width 1 (or 0,
> depending how you want to represent a range that covers exactly one
> char) and get a caret line like the example above. Why is this not
> possible?

It is possible.

> Cheers,
> 
> Manuel.

Thanks for the comments

Dave

[as noted before, I'm about to disappear on vacation for 10 days, so
replies to followups may be delayed]
David Malcolm Sept. 25, 2015, 9:24 p.m. UTC | #5
On Fri, 2015-09-25 at 23:13 +0200, Manuel López-Ibáñez wrote:
> +   If SHOW_CARET_P is true, then the range should be rendered with
> +   a caret at its starting location.  This
> +   is for use by the Fortran frontend, for implementing the
> +   "%C" and "%L" format codes.  */
> +
> +void
> +rich_location::set_range (unsigned int idx, source_range src_range,
> +              bool show_caret_p, bool overwrite_loc_p)
> 
> I do not understand when is this show_caret_p used by Fortran given
> the diagnostic_show_locus code mentioned earlier.

The patch is something of a hybrid: on the one hand it's using the new
rich_location class for storing multiple locations for a diagnostic (and
this replaces the existing way we did this in struct text_info), but on
the other hand, for Fortran, it's using the old printing code.

rich_location::set_range exists to ensure that the %C and %L codes used
by Fortran (and "+" in the C family of FEs) can write back into the
rich_location instance, faithfully emulating the old code that wrote
back to
struct text_info's:
  location_t locations[MAX_LOCATIONS_PER_MESSAGE];

(that array is replaced in the patch by a rich_location *, pointing back
at the rich_location in the diagnostic_info).

> Related to this:
> 
> inline void set_location (unsigned int idx, location_t loc, bool caret_p)
> 
> is always called with the last parameter 'true' (boolean parameters
> are always almost bad API). Do you really need this parameter?

Ah, OK.  Maybe not there.

> +/* Overwrite the range within this text_info's rich_location.
> +   For use e.g. when implementing "+" in client format decoders.  */
> 
> If we got rid of '+' we would not need this extra work. Also '+'
> breaks #pragma diagnostics. Not the fault of your patch, but it just
> shows that technical debt keeps accumulating.
> https://gcc.gnu.org/wiki/Partial_Transitions

(nods)   That "+" thing was one of the surprises I ran into when working
on this, and is the reason that it isn't a :
  const rich_location *
but just a:
  rich_location *
given that formatting the diagnostic text can lead to the location being
modified.  I'm just emulating/supporting the existing behavior.

Dave
Manuel López-Ibáñez Sept. 25, 2015, 10:09 p.m. UTC | #6
On 25 September 2015 at 23:24, David Malcolm <dmalcolm@redhat.com> wrote:
> On Fri, 2015-09-25 at 23:13 +0200, Manuel López-Ibáñez wrote:
>> +   If SHOW_CARET_P is true, then the range should be rendered with
>> +   a caret at its starting location.  This
>> +   is for use by the Fortran frontend, for implementing the
>> +   "%C" and "%L" format codes.  */
>> +
>> +void
>> +rich_location::set_range (unsigned int idx, source_range src_range,
>> +              bool show_caret_p, bool overwrite_loc_p)
>>
>> I do not understand when is this show_caret_p used by Fortran given
>> the diagnostic_show_locus code mentioned earlier.

[...]
> rich_location::set_range exists to ensure that the %C and %L codes used
> by Fortran (and "+" in the C family of FEs) can write back into the
> rich_location instance, faithfully emulating the old code that wrote
> back to
> struct text_info's:
>   location_t locations[MAX_LOCATIONS_PER_MESSAGE];

Why Fortran cannot use text->set_location like the other FEs? This way
you do not need set_range at all. In fact, you do:

+    source_range range
+      = source_range::from_location (
+          linemap_position_for_loc_and_offset (line_table,
+                           loc->lb->location,
+                           offset));
+    text->set_range (loc_num, range, true);

But I guess this doesn't actually create a range like ^~~~ but as single ^.

The other issue that confuses me is that show_caret_p is always true
when reaching this function via the pretty-printer. Thus, show_caret_p
is also used by C/C++. In fact, I'm not sure when it can be false.

Cheers,

Manuel.
Manuel López-Ibáñez Sept. 25, 2015, 10:39 p.m. UTC | #7
On 25 September 2015 at 23:15, David Malcolm <dmalcolm@redhat.com> wrote:
> My recollection is that I saw that the Fortran frontend has logic for
> calling into diagnostic_print_caret_line, noticed that the fortran
> testsuite has dg- assertions about finding specific messages, and I got
> worried that they embed assumptions about how the old printer worked.
> Hence I wanted to avoid touching that for the first version, and so in
> this patch it's a hybrid of the old Fortran printing code with the new
> representation for multiple locations.

It is quite simple, one you understand the logic. Fortran has three
types of output:

(a) #     [name]:[locus]:
    #
    #        some code
    #              1
    #     Error: Some error at (1)

which can call the same function used by other FEs to print the caret
line (I call the caret line, the line that contains the caret
character/ranges, 1 in this case).

(b) #     [name]:[locus]:
    #
    #       some code and some more code
    #              1       2
    #     Error: Some error at (1) and (2)


which according to what you explained should also be possible by
calling diagnostic_show_locus with the appropriate location info and

(c) #     [name]:[locus]:
    #
    #       some code
    #              1
    #     [name]:[locus2]:
    #
    #       some other code
    #         2
    #     Error: Some error at (1) and (2)
    # or

which was implemented by calling diagnostic_show_locus with just the
location of 1, then calling diagnostic_print_caret_line with just the
expanded_location of 2. I could have just called diagnostic_show_locus
also to print 2 by overriding diagnostic->location[0] =
diagnostic->location[1] and caret_char[0] = caret_char[1], but that
seemed a bit hackish and more expensive (but perhaps less confusing?).

If you have a function that you can call with one or more
location_t/expanded_location  (or something that can be converted from
a location_t) and pass explicitly the caret_char, then you just need
to call that function with the right parameters to get the second part
of (c). Otherwise, you may simply temporarily do caret_char[0] =
caret_char[1], before calling the same function that prints the
caret-line for (a).

> Maybe that's a cop-out.  Would you prefer that the patch goes all the
> way, and that I attempt to eliminate all calls to
> diagnostic_print_caret_line from the Fortran FE, and eliminate the old
> implementation?  (either now, or as a followup patch?)  I may need
> assistance with that; I suspect that some of the dg- assertions in the
> Fortran test suite may need updating.

There is only one call! I just think this hack is really not necessary
(in fact, it seems more complicated than the alternatives outlined
above). And I'm afraid that once it goes in, it will stay there
forever. You are in a far better position than the Fortran devs to
understand how to call your new interfaces to get the output you
desire.

Cheers,

Manuel.
Dodji Seketeli Sept. 27, 2015, 12:55 a.m. UTC | #8
[Note to libcpp, C, and Fortran maintainers: we still need your input :-)]

Hello,

David Malcolm <dmalcolm@redhat.com> writes:

[...]

> Here's the revised comment I put in the attached patch:

[...]

> +   The class caches the lookup of the color codes for the above.
> +
> +   The class also has responsibility for tracking which of the above is
> +   active, filtering out unnecessary changes.  This allows layout::print_line
> +   to simply request a colorization code for *every* character it prints
> +   thorough this class, and have the filtering be done for it here.

You probably meant "*through* this class" ?

> */

> Hopefully that comment explains the possible states the colorizer can
> have.

Yes it does, great comment, thank you.


> FWIW I have a follow-up patch to add support for fix-it hints, so they
> might be another kind of colorization state.
> (see https://gcc.gnu.org/ml/gcc-patches/2015-09/msg00732.html for the
> earlier version of said patch, in v1 of the kit).

Yeah, I'll comment on that one separatly.

>> Also, I am thinking that there should maybe be a layout::state type,
>> which would have two notional properties (for now): range_index and
>> draw_caret_p. So that this function:
>> 
>> +bool
>> +layout::get_state_at_point (/* Inputs.  */
>> +			    int row, int column,
>> +			    int first_non_ws, int last_non_ws,
>> +			    /* Outputs.  */
>> +			    int *out_range_idx,
>> +			    bool *out_draw_caret_p)
>> 
>> Would take just one output parameter, e.g, a reference to
>> layout::state.
>
> Fixed, though I called it "struct point_state", given that it's coming
> from get_state_at_point.  I passed it by pointer, since AFAIK our coding
> standards don't yet approve of the use of references in the codebase
> (outside of places where we need them e.g. container classes).

Great.  Thanks.

>
> I also added a unit test for a rich_location with two caret locations
> (mimicking one of the Fortran examples), to give us coverage for this
> case:
>
> +void test_multiple_carets (void)
> +{
> +#if 0
> +   x = x + y /* { dg-warning "8: test" } */
> +/* { dg-begin-multiline-output "" }
> +    x = x + y
> +        A   B
> +   { dg-end-multiline-output "" } */
> +#endif
> +}
>
> where the "A" and "B" as caret chars are coming from new code in the
> show_locus unittest plugin.

Yeah, saw that.  Excellent, thanks.

[...]

>> +  if (0)
>> +    show_ruler (context, line_width, m_x_offset);
>> 
>> This should probably be removed from the final code to be committed.
>
> FWIW, the ruler is very helpful to me when debugging the locus-printing
> (e.g. when adding fix-it-hints), and if we remove that if (0) call, we
> get:
>
> warning: ‘void show_ruler(diagnostic_context*, int, int)’ defined but
> not used [-Wunused-function]
>
> which will break bootstrap, so perhaps it instead should be an option?
> "-fdiagnostics-show-ruler" or somesuch?
>
> I don't know that it would be helpful to end-users though.
>
> I'd prefer to just keep it in the code with the
>   if (0)
> as-is, since it's useful "scaffolding" for hacking on the code.
>

OK, I understand; though, as Manuel noted elsewhere, you might rename
that function debug_show_ruler and declare it as:

    DEBUG_FUNCTION static void
    debug_show_ruler (diagnostic_context *context, int max_width, int x_offset)
    {
      /* ...  */
    }
to comply with that is generally done in the compiler.

[...]

>> +/* Get the column beyond the rightmost one that could contain a caret or
>> +   range marker, given that we stop rendering at trailing whitespace.  */
>> +
>> +int
>> +layout::get_x_bound_for_row (int row, int caret_column,
>> +			     int last_non_ws)
>> 
>> Please describe what the parameters mean here, especially last_non_ws.
>> I had to read its code to know that last_non_ws was the *column* of
>> the last non white space character.
>
> I renamed it to "last_non_ws_column", and fleshed out the comment

OK.

[...]

>>  void
>>  diagnostic_show_locus (diagnostic_context * context,
>>  		       const diagnostic_info *diagnostic)
>> @@ -75,16 +710,25 @@ diagnostic_show_locus (diagnostic_context * context,
>>      return;
>> 
>> +      /* The GCC 5 routine. */
>> 
>> I'd say the GCC <= 5 routine ;-)
>
>> +  else
>> +    /* The GCC 6 routine.  */
>> 
>> And here, the GCC > 5 routine.
>
> Changed to "GCC < 6" and "GCC >= 6", on the pedantic grounds that e.g.
> 5.1 > 5

OK.

>
>> I would be surprised to see this patch in particular incur any
>> noticeable increase in time and space consumption, but, have you noticed
>> anythying related to that during bootstrap?
>
> I hadn't noticed it, but I wasn't timing.  I'll have a look.

Ok, thanks.

> One possible nit here is that the patch expands locations when
> constructing rich_location instances, and it does that for warnings
> before the logic to ignore them.  So there may be some extra calls there
> that aren't present in trunk, for discarded warnings.  I don't expect
> that to affect the speed of the compiler though (I expect it to be lost
> in the noise).

Fair enough.

> OK for trunk if it passes bootstrap/regrtest?

The core diagnostics bits are IMHO in good shape.  I'd like to see the
discussion with Manuel be resolved before it goes in, though.  And we
still need the "Go" from the FE maintainers.

So I am continuing the discussion with Manuel below.

Manuel López-Ibáñez <lopezibanez@gmail.com> writes:

> On 25 September 2015 at 23:15, David Malcolm <dmalcolm@redhat.com> wrote:
>> My recollection is that I saw that the Fortran frontend has logic for
>> calling into diagnostic_print_caret_line, noticed that the fortran
>> testsuite has dg- assertions about finding specific messages, and I got
>> worried that they embed assumptions about how the old printer worked.
>> Hence I wanted to avoid touching that for the first version, and so in
>> this patch it's a hybrid of the old Fortran printing code with the new
>> representation for multiple locations.
>
> It is quite simple, one you understand the logic. Fortran has three
> types of output:
>
> (a) #     [name]:[locus]:
>     #
>     #        some code
>     #              1
>     #     Error: Some error at (1)
>
> which can call the same function used by other FEs to print the caret
> line (I call the caret line, the line that contains the caret
> character/ranges, 1 in this case).
>
> (b) #     [name]:[locus]:
>     #
>     #       some code and some more code
>     #              1       2
>     #     Error: Some error at (1) and (2)
>
>
> which according to what you explained should also be possible by
> calling diagnostic_show_locus with the appropriate location info and
>
> (c) #     [name]:[locus]:
>     #
>     #       some code
>     #              1
>     #     [name]:[locus2]:
>     #
>     #       some other code
>     #         2
>     #     Error: Some error at (1) and (2)
>     # or
>
> which was implemented by calling diagnostic_show_locus with just the
> location of 1, then calling diagnostic_print_caret_line with just the
> expanded_location of 2. I could have just called diagnostic_show_locus
> also to print 2 by overriding diagnostic->location[0] =
> diagnostic->location[1] and caret_char[0] = caret_char[1], but that
> seemed a bit hackish and more expensive (but perhaps less confusing?).
>
> If you have a function that you can call with one or more
> location_t/expanded_location  (or something that can be converted from
> a location_t) and pass explicitly the caret_char, then you just need
> to call that function with the right parameters to get the second part
> of (c). Otherwise, you may simply temporarily do caret_char[0] =
> caret_char[1], before calling the same function that prints the
> caret-line for (a).
>
>> Maybe that's a cop-out.  Would you prefer that the patch goes all the
>> way, and that I attempt to eliminate all calls to
>> diagnostic_print_caret_line from the Fortran FE, and eliminate the old
>> implementation?  (either now, or as a followup patch?)  I may need
>> assistance with that; I suspect that some of the dg- assertions in the
>> Fortran test suite may need updating.
>
> There is only one call! I just think this hack is really not necessary
> (in fact, it seems more complicated than the alternatives outlined
> above).

I agree that David's approach of adding an impedance adaptation layer in
gcc/diagnostic-show-locus.c to keep the Fortran FE unchanged *for now*
complicates diagnostic-show-locus.c.  But then the benefit is to
break-up the tasks at hand in smaller (less coupled) tasks, making the
whole process more manageable.

IOW, I think the Fortran FE can then be updated later in a follow-up
patch, and then the adaptation layer code can be removed from
diagnostic-show-locus.c *if* we all agree that the Fortran FE can be
adapted to use the foundation being put in place in this current patch.

And I think all the functionalities the Fortran FE needs for that
matter, are supported by this patch.  Please correct me if I am wrong.

> And I'm afraid that once it goes in, it will stay there forever.

:-)

Or maybe not :-)

It looks we are in motion (with some good energy) to change these things
at the moment, thanks to David and you, amongst others.  So I would
agree to bet on a positive outcome of this, and to let the patch go in.
I guess if the Fortran FE doesn't get updated afterwards, I'll be the
one on the hook; I'd take the bet nevertheless.

> You are in a far better position than the Fortran devs to understand
> how to call your new interfaces to get the output you desire.

That is correct.  And hopefully, letting the current patch go in won't
prevent the further needed changes of the FEs to happen, including the
Fortran one.

[...]

Manuel López-Ibáñez <lopezibanez@gmail.com> writes:

> On 25 September 2015 at 23:24, David Malcolm <dmalcolm@redhat.com> wrote:
>> On Fri, 2015-09-25 at 23:13 +0200, Manuel López-Ibáñez wrote:
>>> +   If SHOW_CARET_P is true, then the range should be rendered with
>>> +   a caret at its starting location.  This
>>> +   is for use by the Fortran frontend, for implementing the
>>> +   "%C" and "%L" format codes.  */
>>> +
>>> +void
>>> +rich_location::set_range (unsigned int idx, source_range src_range,
>>> +              bool show_caret_p, bool overwrite_loc_p)
>>>
>>> I do not understand when is this show_caret_p used by Fortran given
>>> the diagnostic_show_locus code mentioned earlier.
>
> [...]
>> rich_location::set_range exists to ensure that the %C and %L codes used
>> by Fortran (and "+" in the C family of FEs) can write back into the
>> rich_location instance, faithfully emulating the old code that wrote
>> back to
>> struct text_info's:
>>   location_t locations[MAX_LOCATIONS_PER_MESSAGE];
>
> Why Fortran cannot use text->set_location like the other FEs? This way
> you do not need set_range at all.

Yes, it does look to me that this change:

    @@ -938,10 +939,12 @@ gfc_format_decoder (pretty_printer *pp,
            /* If location[0] != UNKNOWN_LOCATION means that we already
               processed one of %C/%L.  */
            int loc_num = text->get_location (0) == UNKNOWN_LOCATION ? 0 : 1;
    -	text->set_location (loc_num,
    -			    linemap_position_for_loc_and_offset (line_table,
    -								 loc->lb->location,
    -								 offset));
    +	source_range range
    +	  = source_range::from_location (
    +	      linemap_position_for_loc_and_offset (line_table,
    +						   loc->lb->location,
    +						   offset));
    +	text->set_range (loc_num, range, true);

is unnecessary because text_info::set_location() got updated as:

    @@ -40,21 +35,17 @@ struct text_info
       va_list *args_ptr;
       int err_no;  /* for %m */
       void **x_data;
    +  rich_location *m_richloc;

    -  inline void set_location (unsigned int index_of_location, location_t loc)
    +  inline void set_location (unsigned int idx, location_t loc, bool caret_p)
       {
    -    gcc_checking_assert (index_of_location < MAX_LOCATIONS_PER_MESSAGE);
    -    this->locations[index_of_location] = loc;
    +    source_range src_range;
    +    src_range.m_start = loc;
    +    src_range.m_finish = loc;
    +    set_range (idx, src_range, caret_p);
       }

So what is needed AIUI is to just add a 'caret_p' argument to the
initial call to:

        text->set_location (loc_num,
    			    linemap_position_for_loc_and_offset (line_table,
    								 loc->lb->location,
    								 offset));

> The other issue that confuses me is that show_caret_p is always true
> when reaching this function via the pretty-printer. Thus, show_caret_p
> is also used by C/C++. In fact, I'm not sure when it can be false.

I think the reason why show_caret_p is always true here is that the
patch updates the existing FE code so that it keeps emitting the *same*
diagnostics as before.  And today, FEs emits diagnostics with ranges
that always starts with a caret.

But I think it's easy to imagine hypothetic (and reasonably probable)
examples where we'd have only one range with a caret, associated to other
ranges with no caret.  The ranges with no carets would thus require the
show_caret_p argument to be non-null.  In fact, patch 2/5 has one of
these examples in a comment in line-map.h:

    +   Example C
    +   *********
    +      a = (foo && bar)
    +           ~~~ ^~ ~~~
    +   This rich location has three ranges:
    +   - Range 0 has its caret and start location at the first "&" and
    +     end at the second "&.
    +   - Range 1 has its start and finish at the "f" and "o" of "foo";
    +     the caret is not flagged for display, but is perhaps at the "f"
    +     of "foo".
    +   - Similarly, range 2 has its start and finish at the "b" and "r" of
    +     "bar"; the caret is not flagged for display, but is perhaps at the
    +     "b" of "bar".

We'll then start seeing cases with show_caret_p being false when *new*
diagnostics are added to FEs to leverage on this new feature.

So I don't think this "show_caret_p" argument's value is an issue, quite
the contrary; it's an interesting feature.

Cheers,
diff mbox

Patch

commit 980725625dc77a478ef725d9e28d907352c9cbd4
Author: David Malcolm <dmalcolm@redhat.com>
Date:   Mon Aug 31 21:32:20 2015 -0400

    Reimplement diagnostic_show_locus, introducing rich_location classes (v3)
    
    gcc/ChangeLog:
    	* diagnostic-color.c (color_dict): Eliminate "caret"; add "range1"
    	and "range2".
    	(parse_gcc_colors): Update comment to describe default GCC_COLORS.
    	* diagnostic-core.h (warning_at_rich_loc): New declaration.
    	(error_at_rich_loc): New declaration.
    	(permerror_at_rich_loc): New declaration.
    	(inform_at_rich_loc): New declaration.
    	* diagnostic-show-locus.c (struct point_state): New struct.
    	(class colorizer): New class.
    	(class layout_point): New class.
    	(class layout_range): New class.
    	(class layout): New class.
    	(colorizer::colorizer): New ctor.
    	(colorizer::~colorizer): New dtor.
    	(colorizer::set_state): New method.
    	(colorizer::begin_state): New method.
    	(colorizer::finish_state): New method.
    	(layout_range::layout_range): New ctor.
    	(layout_range::contains_point): New method.
    	(get_line_width_without_trailing_whitespace): New function.
    	(layout::layout): New ctor.
    	(layout::print_line): New method.
    	(layout::get_state_at_point): New method.
    	(layout::get_x_bound_for_row): New method.
    	(show_ruler): New function.
    	(diagnostic_show_locus): Call new function diagnostic_print_ranges,
    	falling back to diagnostic_print_caret_line if the frontend has
    	set frontend_calls_diagnostic_print_caret_line_p on the
    	diagnostic_context.
    	(diagnostic_print_ranges): New function.
    	* diagnostic.c (diagnostic_initialize): Replace
    	MAX_LOCATIONS_PER_MESSAGE with rich_location::MAX_RANGES.
    	(diagnostic_set_info_translated): Convert param from location_t
    	to rich_location *.  Eliminate calls to set_location on the
    	message in favor of storing the rich_location ptr there.
    	(diagnostic_set_info): Convert param from location_t to
    	rich_location *.
    	(diagnostic_build_prefix): Break out array into...
    	(diagnostic_kind_color): New variable.
    	(diagnostic_get_color_for_kind): New function.
    	(diagnostic_report_diagnostic): Colorize the option_text
    	using the color for the severity.
    	(diagnostic_append_note): Update for change in signature of
    	diagnostic_set_info.
    	(diagnostic_append_note_at_rich_loc): New function.
    	(emit_diagnostic): Update for change in signature of
    	diagnostic_set_info.
    	(inform): Likewise.
    	(inform_at_rich_loc): New function.
    	(inform_n): Update for change in signature of diagnostic_set_info.
    	(warning): Likewise.
    	(warning_at): Likewise.
    	(warning_at_rich_loc): New function.
    	(warning_n): Update for change in signature of diagnostic_set_info.
    	(pedwarn): Likewise.
    	(permerror): Likewise.
    	(permerror_at_rich_loc): New function.
    	(error): Update for change in signature of diagnostic_set_info.
    	(error_n): Likewise.
    	(error_at): Likewise.
    	(error_at_rich_loc): New function.
    	(sorry): Update for change in signature of diagnostic_set_info.
    	(fatal_error): Likewise.
    	(internal_error): Likewise.
    	(internal_error_no_backtrace): Likewise.
    	(source_range::debug): New function.
    	* diagnostic.h (struct diagnostic_info): Eliminate field
    	"override_column".  Add field "richloc".
    	(struct diagnostic_context): Convert MAX_LOCATIONS_PER_MESSAGE to
    	rich_location::MAX_RANGES.  Add field
    	"frontend_calls_diagnostic_print_caret_line_p".
    	(diagnostic_override_column): Eliminate this macro.
    	(diagnostic_set_info): Convert param from location_t to
    	rich_location *.
    	(diagnostic_set_info_translated): Likewise.
    	(diagnostic_append_note_at_rich_loc): New function.
    	(diagnostic_num_locations): New function.
    	(diagnostic_expand_location): Get the location from the
    	rich_location.
    	(diagnostic_get_color_for_kind): New declaration.
    	* genmatch.c (linemap_client_expand_location_to_spelling_point): New.
    	(error_cb): Update for change in signature of "error" callback.
    	(fatal_at): Likewise.
    	(warning_at): Likewise.
    	* input.c (linemap_client_expand_location_to_spelling_point): New.
    	* pretty-print.c (text_info::set_range): New method.
    	(text_info::get_location): New method.
    	* pretty-print.h (MAX_LOCATIONS_PER_MESSAGE): Eliminate this macro.
    	(struct text_info): Eliminate "locations" array in favor of
    	"m_richloc", a rich_location *.
    	(textinfo::set_location): Add a "caret_p" param, and reimplement
    	in terms of a call to set_range.
    	(textinfo::get_location): Eliminate inline implementation in favor of
    	an out-of-line reimplementation.
    	(textinfo::set_range): New method.
    	* rtl-error.c (diagnostic_for_asm): Update for change in signature
    	of diagnostic_set_info.
    	* tree-diagnostic.c (default_tree_printer): Update for new
    	"caret_p" param for textinfo::set_location.
    	* tree-pretty-print.c (percent_K_format): Likewise.
    
    gcc/c-family/ChangeLog:
    	* c-common.c (c_cpp_error): Convert parameter from location_t to
    	rich_location *.  Eliminate the "column_override" parameter and
    	the call to diagnostic_override_column.
    	Update the "done_lexing" clause to set range 0
    	on the rich_location, rather than overwriting a location_t.
    	* c-common.h (c_cpp_error): Convert parameter from location_t to
    	rich_location *.  Eliminate the "column_override" parameter.
    
    gcc/c/ChangeLog:
    	* c-decl.c (warn_defaults_to): Update for change in signature
    	of diagnostic_set_info.
    	* c-errors.c (pedwarn_c99): Likewise.
    	(pedwarn_c90): Likewise.
    	* c-objc-common.c (c_tree_printer): Update for new "caret_p" param
    	for textinfo::set_location.
    
    gcc/cp/ChangeLog:
    	* error.c (cp_printer): Update for new "caret_p" param for
    	textinfo::set_location.
    	(pedwarn_cxx98): Update for change in signature of
    	diagnostic_set_info.
    
    gcc/fortran/ChangeLog:
    	* cpp.c (cb_cpp_error): Convert parameter from location_t to
    	rich_location *.  Eliminate the "column_override" parameter.
    	* error.c (gfc_warning): Update for change in signature of
    	diagnostic_set_info.
    	(gfc_format_decoder): Update handling of %C/%L for changes
    	to struct text_info.
    	(gfc_diagnostic_starter): Use richloc when determining whether to
    	print one locus or two.
    	(gfc_warning_now_at): Update for change in signature of
    	diagnostic_set_info.
    	(gfc_warning_now): Likewise.
    	(gfc_error_now): Likewise.
    	(gfc_fatal_error): Likewise.
    	(gfc_error): Likewise.
    	(gfc_internal_error): Likewise.
    	(gfc_diagnostics_init): Set
    	frontend_calls_diagnostic_print_caret_line_p.
    
    gcc/testsuite/ChangeLog:
    	* gcc.dg/plugin/diagnostic-test-show-locus-bw.c: New file.
    	* gcc.dg/plugin/diagnostic-test-show-locus-color.c: New file.
    	* gcc.dg/plugin/diagnostic_plugin_test_show_locus.c: New file.
    	* gcc.dg/plugin/plugin.exp (plugin_test_list): Add the above.
    	* lib/gcc-dg.exp: Load multiline.exp.
    
    libcpp/ChangeLog:
    	* errors.c (cpp_diagnostic): Update for change in signature
    	of "error" callback.
    	(cpp_diagnostic_with_line): Likewise, calling override_column
    	on the rich_location.
    	* include/cpplib.h (struct cpp_callbacks): Within "error"
    	callback, convert param from source_location to rich_location *,
    	and drop column_override param.
    	* include/line-map.h (struct source_range): New struct.
    	(struct location_range): New struct.
    	(class rich_location): New class.
    	(linemap_client_expand_location_to_spelling_point): New declaration.
    	* line-map.c (rich_location::rich_location): New ctors.
    	(rich_location::lazily_expand_location): New method.
    	(rich_location::override_column): New method.
    	(rich_location::add_range): New methods.
    	(rich_location::set_range): New method.

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 4b922bf..ded23d3 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -10451,15 +10451,14 @@  c_option_controlling_cpp_error (int reason)
 /* Callback from cpp_error for PFILE to print diagnostics from the
    preprocessor.  The diagnostic is of type LEVEL, with REASON set
    to the reason code if LEVEL is represents a warning, at location
-   LOCATION unless this is after lexing and the compiler's location
-   should be used instead, with column number possibly overridden by
-   COLUMN_OVERRIDE if not zero; MSG is the translated message and AP
+   RICHLOC unless this is after lexing and the compiler's location
+   should be used instead; MSG is the translated message and AP
    the arguments.  Returns true if a diagnostic was emitted, false
    otherwise.  */
 
 bool
 c_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
-	     location_t location, unsigned int column_override,
+	     rich_location *richloc,
 	     const char *msg, va_list *ap)
 {
   diagnostic_info diagnostic;
@@ -10500,11 +10499,11 @@  c_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
       gcc_unreachable ();
     }
   if (done_lexing)
-    location = input_location;
+    richloc->set_range (0,
+			source_range::from_location (input_location),
+			true, true);
   diagnostic_set_info_translated (&diagnostic, msg, ap,
-				  location, dlevel);
-  if (column_override)
-    diagnostic_override_column (&diagnostic, column_override);
+				  richloc, dlevel);
   diagnostic_override_option_index (&diagnostic,
                                     c_option_controlling_cpp_error (reason));
   ret = report_diagnostic (&diagnostic);
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 74d1bc1..bb17fcc 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -981,9 +981,9 @@  extern void init_c_lex (void);
 
 extern void c_cpp_builtins (cpp_reader *);
 extern void c_cpp_builtins_optimize_pragma (cpp_reader *, tree, tree);
-extern bool c_cpp_error (cpp_reader *, int, int, location_t, unsigned int,
+extern bool c_cpp_error (cpp_reader *, int, int, rich_location *,
 			 const char *, va_list *)
-     ATTRIBUTE_GCC_DIAG(6,0);
+     ATTRIBUTE_GCC_DIAG(5,0);
 extern int c_common_has_attribute (cpp_reader *);
 
 extern bool parse_optimize_options (tree, bool);
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index a110226..9af447c 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -5285,9 +5285,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 e5fbf05..0f8b933 100644
--- a/gcc/c/c-errors.c
+++ b/gcc/c/c-errors.c
@@ -42,13 +42,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;
@@ -60,7 +61,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);
     }
@@ -80,6 +81,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.  */
@@ -90,7 +92,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;
@@ -102,7 +104,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;
@@ -114,7 +116,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-objc-common.c b/gcc/c/c-objc-common.c
index 47fd7de..1e601f9 100644
--- a/gcc/c/c-objc-common.c
+++ b/gcc/c/c-objc-common.c
@@ -101,7 +101,7 @@  c_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
     {
       t = va_arg (*text->args_ptr, tree);
       if (set_locus)
-	text->set_location (0, DECL_SOURCE_LOCATION (t));
+	text->set_location (0, DECL_SOURCE_LOCATION (t), true);
     }
 
   switch (*spec)
diff --git a/gcc/cp/error.c b/gcc/cp/error.c
index faf8744..19ca8c3 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -3554,7 +3554,7 @@  cp_printer (pretty_printer *pp, text_info *text, const char *spec,
 
   pp_string (pp, result);
   if (set_locus && t != NULL)
-    text->set_location (0, location_of (t));
+    text->set_location (0, location_of (t), true);
   return true;
 #undef next_tree
 #undef next_tcode
@@ -3668,9 +3668,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/diagnostic-color.c b/gcc/diagnostic-color.c
index 3fe49b2..d848dfc 100644
--- a/gcc/diagnostic-color.c
+++ b/gcc/diagnostic-color.c
@@ -164,7 +164,8 @@  static struct color_cap color_dict[] =
   { "warning", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_MAGENTA),
 	       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 },
+  { "range1", SGR_SEQ (COLOR_FG_GREEN), 6, false },
+  { "range2", SGR_SEQ (COLOR_FG_BLUE), 6, false },
   { "locus", SGR_SEQ (COLOR_BOLD), 5, false },
   { "quote", SGR_SEQ (COLOR_BOLD), 5, false },
   { NULL, NULL, 0, false }
@@ -195,7 +196,7 @@  colorize_stop (bool show_color)
 }
 
 /* Parse GCC_COLORS.  The default would look like:
-   GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
+   GCC_COLORS='error=01;31:warning=01;35:note=01;36:range1=32:range2=34;locus=01:quote=01'
    No character escaping is needed or supported.  */
 static bool
 parse_gcc_colors (void)
diff --git a/gcc/diagnostic-core.h b/gcc/diagnostic-core.h
index 66d2e42..a8a7c37 100644
--- a/gcc/diagnostic-core.h
+++ b/gcc/diagnostic-core.h
@@ -63,18 +63,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-show-locus.c b/gcc/diagnostic-show-locus.c
index 147a2b8..c3a941d 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -36,6 +36,13 @@  along with GCC; see the file COPYING3.  If not see
 # include <sys/ioctl.h>
 #endif
 
+static void
+show_ruler (diagnostic_context *context, int max_width, int x_offset);
+
+static void
+diagnostic_print_ranges (diagnostic_context * context,
+			 const diagnostic_info *diagnostic);
+
 /* If LINE is longer than MAX_WIDTH, and COLUMN is not smaller than
    MAX_WIDTH by some margin, then adjust the start of the line such
    that the COLUMN is smaller than MAX_WIDTH minus the margin.  The
@@ -60,11 +67,663 @@  adjust_line (const char *line, int line_width,
   return line;
 }
 
-/* Print the physical source line corresponding to the location of
-   this diagnostic, and a caret indicating the precise column.  This
-   function only prints two caret characters if the two locations
-   given by DIAGNOSTIC are on the same line according to
-   diagnostic_same_line().  */
+/* Classes for rendering source code and diagnostics, within an
+   anonymous namespace.
+   The work is done by "class layout", which embeds and uses
+   "class colorizer" and "class layout_range" to get things done.  */
+
+namespace {
+
+/* The state at a given point of the source code, assuming that we're
+   in a range: which range are we in, and whether we should draw a caret at
+   this point.  */
+
+struct point_state
+{
+  int range_idx;
+  bool draw_caret_p;
+};
+
+/* A class to inject colorization codes when printing the diagnostic locus.
+
+   It has one kind of colorization for each of:
+     - normal text
+     - range 0 (the "primary location")
+     - range 1
+     - range 2
+
+   The class caches the lookup of the color codes for the above.
+
+   The class also has responsibility for tracking which of the above is
+   active, filtering out unnecessary changes.  This allows layout::print_line
+   to simply request a colorization code for *every* character it prints
+   thorough this class, and have the filtering be done for it here.  */
+
+class colorizer
+{
+ public:
+  colorizer (diagnostic_context *context,
+	     const diagnostic_info *diagnostic);
+  ~colorizer ();
+
+  void set_range (int range_idx) { set_state (range_idx); }
+  void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
+
+ private:
+  void set_state (int state);
+  void begin_state (int state);
+  void finish_state (int state);
+
+ private:
+  static const int STATE_NORMAL_TEXT = -1;
+
+  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_range1_cs;
+  const char *m_range2_cs;
+  const char *m_range_ce;
+};
+
+/* A point within a layout_range; similar to an expanded_location,
+   but after filtering on file.  */
+
+class layout_point
+{
+ public:
+  layout_point (const expanded_location &exploc)
+  : m_line (exploc.line),
+    m_column (exploc.column) {}
+
+  int m_line;
+  int m_column;
+};
+
+/* A class for use by "class layout" below: a filtered location_range.  */
+
+class layout_range
+{
+ public:
+  layout_range (const location_range *loc_range);
+
+  bool contains_point (int row, int column) const;
+
+  layout_point m_start;
+  layout_point m_finish;
+  bool m_show_caret_p;
+  layout_point m_caret;
+};
+
+/* A class to control the overall layout when printing a diagnostic.
+
+   The layout is determined within the constructor.
+   It is then printed by repeatedly calling the "print_line" method.
+   Each such call can print two lines: one for the source line itself,
+   and potentially an "annotation" line, containing carets/underlines.
+
+   We assume we have disjoint ranges.  */
+
+class layout
+{
+ public:
+  layout (diagnostic_context *context,
+	  const diagnostic_info *diagnostic);
+
+  int get_first_line () const { return m_first_line; }
+  int get_last_line () const { return m_last_line; }
+
+  void print_line (int row);
+
+ private:
+  bool
+  get_state_at_point (/* Inputs.  */
+		      int row, int column,
+		      int first_non_ws, int last_non_ws,
+		      /* Outputs.  */
+		      point_state *out_state);
+
+  int
+  get_x_bound_for_row (int row, int caret_column,
+		       int last_non_ws);
+
+ private:
+  diagnostic_context *m_context;
+  pretty_printer *m_pp;
+  diagnostic_t m_diagnostic_kind;
+  expanded_location m_exploc;
+  colorizer m_colorizer;
+  auto_vec <layout_range> m_layout_ranges;
+  int m_first_line;
+  int m_last_line;
+  int m_x_offset;
+};
+
+/* Implementation of "class colorizer".  */
+
+/* The constructor for "colorizer".  Lookup and store color codes for the
+   different kinds of things we might need to print.  */
+
+colorizer::colorizer (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_range1_cs = colorize_start (pp_show_color (context->printer), "range1");
+  m_range2_cs = colorize_start (pp_show_color (context->printer), "range2");
+  m_range_ce = colorize_stop (pp_show_color (context->printer));
+}
+
+/* The destructor for "colorize".  If colorization is on, print a code to
+   turn it off.  */
+
+colorizer::~colorizer ()
+{
+  finish_state (m_current_state);
+}
+
+/* Update state, printing color codes if necessary if there's a state
+   change.  */
+
+void
+colorizer::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
+colorizer::begin_state (int state)
+{
+  switch (state)
+    {
+    case STATE_NORMAL_TEXT:
+      break;
+
+    case 0:
+      /* Make range 0 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_get_color_for_kind (m_diagnostic->kind)));
+      break;
+
+    case 1:
+      pp_string (m_context->printer, m_range1_cs);
+      break;
+
+    case 2:
+      pp_string (m_context->printer, m_range2_cs);
+      break;
+
+    default:
+      /* We don't expect more than 3 ranges per diagnostic.  */
+      gcc_unreachable ();
+      break;
+    }
+}
+
+/* Turn off any colorization for STATE.  */
+
+void
+colorizer::finish_state (int state)
+{
+  switch (state)
+    {
+    case STATE_NORMAL_TEXT:
+      break;
+
+    case 0:
+      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;
+    }
+}
+
+/* Implementation of class layout_range.  */
+
+/* The constructor for class layout_range.
+   Initialize various layout_point fields from expanded_location
+   equivalents; we've already filtered on file.  */
+
+layout_range::layout_range (const location_range *loc_range)
+: m_start (loc_range->m_start),
+  m_finish (loc_range->m_finish),
+  m_show_caret_p (loc_range->m_show_caret_p),
+  m_caret (loc_range->m_caret)
+{
+}
+
+/* Is (column, row) within the given range?
+   We've already filtered on the file.
+
+   Ranges are closed (both limits are within the range).
+
+   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
+	 within it).
+   - 'a' indicates a subsequent point *after* the range.  */
+
+bool
+layout_range::contains_point (int row, int column) const
+{
+  gcc_assert (m_start.m_line <= m_finish.m_line);
+  /* ...but the equivalent isn't true for the columns;
+     consider example B in the comment above.  */
+
+  if (row < m_start.m_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.m_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.m_column)
+	/* Points on the starting line of the range, but
+	   before the column in which it begins.  */
+	return false;
+
+      if (row < m_finish.m_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.m_line);
+	  return column <= m_finish.m_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.m_line);
+
+  if (row > m_finish.m_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.m_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.m_line < m_finish.m_line);
+      return true;
+    }
+
+  gcc_assert (row ==  m_finish.m_line);
+
+  return column <= m_finish.m_column;
+}
+
+/* Given a source line LINE of length LINE_WIDTH, determine the width
+   without any trailing whitespace.  */
+
+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;
+}
+
+/* Implementation of class layout.  */
+
+/* Constructor for class layout.
+
+   Filter the ranges from the rich_location to those that we can
+   sanely print, populating m_layout_ranges.
+   Determine the range of lines that we will print.
+   Determine m_x_offset, to ensure that the primary caret
+   will fit within the max_width provided by the diagnostic_context.  */
+
+layout::layout (diagnostic_context * context,
+		const diagnostic_info *diagnostic)
+: m_context (context),
+  m_pp (context->printer),
+  m_diagnostic_kind (diagnostic->kind),
+  m_exploc (diagnostic->richloc->lazily_expand_location ()),
+  m_colorizer (context, diagnostic),
+  m_layout_ranges (rich_location::MAX_RANGES),
+  m_first_line (m_exploc.line),
+  m_last_line  (m_exploc.line),
+  m_x_offset (0)
+{
+  rich_location *richloc = diagnostic->richloc;
+  for (unsigned int idx = 0; idx < richloc->get_num_locations (); idx++)
+    {
+      /* This diagnostic printer can only cope with "sufficiently sane" ranges.
+	 Ignore any ranges that are awkward to handle.  */
+      location_range *loc_range = richloc->get_range (idx);
+
+      /* If any part of the range isn't in the same file as the primary
+	 location of this diagnostic, ignore the range.  */
+      if (loc_range->m_start.file != m_exploc.file)
+	continue;
+      if (loc_range->m_finish.file != m_exploc.file)
+	continue;
+      if (loc_range->m_show_caret_p)
+	if (loc_range->m_caret.file != m_exploc.file)
+	  continue;
+
+      /* Passed all the tests; add the range to m_layout_ranges so that
+	 it will be printed.  */
+      layout_range ri (loc_range);
+      m_layout_ranges.safe_push (ri);
+
+      /* Update m_first_line/m_last_line if necessary.  */
+      if (loc_range->m_start.line < m_first_line)
+	m_first_line = loc_range->m_start.line;
+      if (loc_range->m_finish.line > m_last_line)
+	m_last_line = loc_range->m_finish.line;
+    }
+
+  /* Adjust m_x_offset.
+     Center the primary caret to fit in max_width; all columns
+     will be adjusted accordingly.  */
+  int max_width = m_context->caret_max_width;
+  int line_width;
+  const char *line = location_get_source_line (m_exploc.file, m_exploc.line,
+					       &line_width);
+  if (line && m_exploc.column <= line_width)
+    {
+      int right_margin = CARET_LINE_MARGIN;
+      int column = m_exploc.column;
+      right_margin = MIN (line_width - column, right_margin);
+      right_margin = max_width - right_margin;
+      if (line_width >= max_width && column > right_margin)
+	m_x_offset = column - right_margin;
+      gcc_assert (m_x_offset >= 0);
+    }
+
+  if (0)
+    show_ruler (context, line_width, m_x_offset);
+}
+
+/* Print text describing a line of source code.
+   This typically prints two lines:
+
+   (1) the source code itself, colorized at any ranges, and
+   (2) an annotation line containing any carets/underlines
+   describing the ranges.  */
+
+void
+layout::print_line (int row)
+{
+  int line_width;
+  const char *line = location_get_source_line (m_exploc.file, row,
+					       &line_width);
+  if (!line)
+    return;
+
+  line += m_x_offset;
+
+  m_colorizer.set_normal_text ();
+
+  /* Step 1: print the source code line.  */
+
+  /* We will stop printing at any trailing whitespace.  */
+  line_width
+    = get_line_width_without_trailing_whitespace (line,
+						  line_width);
+  pp_space (m_pp);
+  int first_non_ws = INT_MAX;
+  int last_non_ws = 0;
+  int column;
+  for (column = 1 + m_x_offset; column <= line_width; column++)
+    {
+      bool in_range_p;
+      point_state state;
+      in_range_p = get_state_at_point (row, column,
+				       0, INT_MAX,
+				       &state);
+      if (in_range_p)
+	m_colorizer.set_range (state.range_idx);
+      else
+	m_colorizer.set_normal_text ();
+      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 (m_pp, c);
+      line++;
+    }
+  pp_newline (m_pp);
+
+  /* Step 2: print a line consisting of the caret/underlines for the
+     given source line.  */
+  int x_bound = get_x_bound_for_row (row, m_exploc.column,
+				     last_non_ws);
+
+  pp_space (m_pp);
+  for (int column = 1 + m_x_offset; column < x_bound; column++)
+    {
+      bool in_range_p;
+      point_state state;
+      in_range_p = get_state_at_point (row, column,
+				       first_non_ws, last_non_ws,
+				       &state);
+      if (in_range_p)
+	{
+	  /* Within a range.  Draw either the caret or an underline.  */
+	  m_colorizer.set_range (state.range_idx);
+	  if (state.draw_caret_p)
+	    /* Draw the caret.  */
+	    pp_character (m_pp, m_context->caret_chars[state.range_idx]);
+	  else
+	    pp_character (m_pp, '~');
+	}
+      else
+	{
+	  /* Not in a range.  */
+	  m_colorizer.set_normal_text ();
+	  pp_character (m_pp, ' ');
+	}
+    }
+  pp_newline (m_pp);
+}
+
+/* Return true if (ROW/COLUMN) is within a range of the layout.
+   If it returns true, OUT_STATE is written to, with the
+   range index, and whether we should draw the caret at
+   (ROW/COLUMN) (as opposed to an underline).  */
+
+bool
+layout::get_state_at_point (/* Inputs.  */
+			    int row, int column,
+			    int first_non_ws, int last_non_ws,
+			    /* Outputs.  */
+			    point_state *out_state)
+{
+  /* Within a multiline range, don't display any underline or caret
+     in any leading or trailing whitespace on a line.  */
+  if (column < first_non_ws || column > last_non_ws)
+    return false;
+
+  layout_range *range;
+  int i;
+  FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+    {
+      if (0)
+	fprintf (stderr,
+		 "range ( (%i, %i), (%i, %i))->contains_point (%i, %i): %s\n",
+		 range->m_start.m_line,
+		 range->m_start.m_column,
+		 range->m_finish.m_line,
+		 range->m_finish.m_column,
+		 row,
+		 column,
+		 range->contains_point (row, column) ? "true" : "false");
+
+      if (range->contains_point (row, column))
+	{
+	  out_state->range_idx = i;
+
+	  /* Are we at the range's caret?  is it visible? */
+	  out_state->draw_caret_p = false;
+	  if (row == range->m_caret.m_line
+	      && column == range->m_caret.m_column)
+	    out_state->draw_caret_p = range->m_show_caret_p;
+
+	  /* We are within a range.  */
+	  return true;
+	}
+    }
+
+  return false;
+}
+
+/* Helper function for use by layout::print_line when printing the
+   annotation line under the source line.
+   Get the column beyond the rightmost one that could contain a caret or
+   range marker, given that we stop rendering at trailing whitespace.
+   ROW is the source line within the given file.
+   CARET_COLUMN is the column of range 0's caret.
+   LAST_NON_WS_COLUMN is the last column containing a non-whitespace
+   character of source (as determined when printing the source line).  */
+
+int
+layout::get_x_bound_for_row (int row, int caret_column,
+			     int last_non_ws_column)
+{
+  int result = caret_column + 1;
+
+  layout_range *range;
+  int i;
+  FOR_EACH_VEC_ELT (m_layout_ranges, i, range)
+    {
+      if (row >= range->m_start.m_line)
+	{
+	  if (range->m_finish.m_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.m_column)
+		result = range->m_finish.m_column + 1;
+	    }
+	  else if (row < range->m_finish.m_line)
+	    {
+	      /* Within a multiline range; ensure that we render up to the
+		 last non-whitespace column.  */
+	      if (result <= last_non_ws_column)
+		result = last_non_ws_column + 1;
+	    }
+	}
+    }
+
+  return result;
+}
+
+} /* End of anonymous namespace.  */
+
+/* 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, int x_offset)
+{
+  /* Hundreds.  */
+  if (max_width > 99)
+    {
+      pp_space (context->printer);
+      for (int column = 1 + x_offset; 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 + x_offset; 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 + x_offset; column < max_width; column++)
+    pp_character (context->printer, '0' + (column % 10));
+  pp_newline (context->printer);
+}
+
+/* Print the physical source code corresponding to the location of
+   this diagnostic, with additional annotations.
+   If CONTEXT has set frontend_calls_diagnostic_print_caret_line_p,
+   the code is printed using diagnostic_print_caret_line; otherwise
+   it is printed using diagnostic_print_ranges.  */
+
 void
 diagnostic_show_locus (diagnostic_context * context,
 		       const diagnostic_info *diagnostic)
@@ -75,16 +734,25 @@  diagnostic_show_locus (diagnostic_context * context,
     return;
 
   context->last_location = diagnostic_location (diagnostic, 0);
-  expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
-  expanded_location s1 = { };
-  /* Zero-initialized. This is checked later by diagnostic_print_caret_line.  */
 
-  if (diagnostic_location (diagnostic, 1) > BUILTINS_LOCATION)
-    s1 = diagnostic_expand_location (diagnostic, 1);
+  if (context->frontend_calls_diagnostic_print_caret_line_p)
+    {
+      /* The GCC < 6 routine. */
+      expanded_location s0 = diagnostic_expand_location (diagnostic, 0);
+      expanded_location s1 = { };
+      /* Zero-initialized. This is checked later by
+	 diagnostic_print_caret_line.  */
+
+      if (diagnostic_num_locations (diagnostic) >= 2)
+	s1 = diagnostic->message.m_richloc->get_range (1)->m_start;
 
-  diagnostic_print_caret_line (context, s0, s1,
-			       context->caret_chars[0],
-			       context->caret_chars[1]);
+      diagnostic_print_caret_line (context, s0, s1,
+				   context->caret_chars[0],
+				   context->caret_chars[1]);
+    }
+  else
+    /* The GCC >= 6 routine.  */
+    diagnostic_print_ranges (context, diagnostic);
 }
 
 /* Print (part) of the source line given by xloc1 with caret1 pointing
@@ -164,3 +832,33 @@  diagnostic_print_caret_line (diagnostic_context * context,
   pp_set_prefix (context->printer, saved_prefix);
   pp_needs_newline (context->printer) = true;
 }
+
+/* Print all source lines covered by the locations and any ranges
+   within DIAGNOSTIC, displaying one or more carets and zero or more
+   underlines as appropriate.  */
+
+static void
+diagnostic_print_ranges (diagnostic_context * context,
+			 const diagnostic_info *diagnostic)
+{
+  pp_newline (context->printer);
+
+  const char *saved_prefix = pp_get_prefix (context->printer);
+  pp_set_prefix (context->printer, NULL);
+
+  {
+    layout layout (context, diagnostic);
+    int last_line = layout.get_last_line ();
+    for (int row = layout.get_first_line ();
+	 row <= last_line;
+	 row++)
+      layout.print_line (row);
+
+    /* The closing scope here leads to the dtor for layout and thus
+       colorizer being called here, which affects the precise
+       place where colorization is turned off in the unittest
+       for colorized output.  */
+  }
+
+  pp_set_prefix (context->printer, saved_prefix);
+}
diff --git a/gcc/diagnostic.c b/gcc/diagnostic.c
index 831859a..5fe6627 100644
--- a/gcc/diagnostic.c
+++ b/gcc/diagnostic.c
@@ -144,7 +144,7 @@  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));
-  for (i = 0; i < MAX_LOCATIONS_PER_MESSAGE; i++)
+  for (i = 0; i < rich_location::MAX_RANGES; i++)
     context->caret_chars[i] = '^';
   context->show_option_requested = false;
   context->abort_on_error = false;
@@ -234,16 +234,15 @@  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)
 {
+  gcc_assert (richloc);
   diagnostic->message.err_no = errno;
   diagnostic->message.args_ptr = args;
   diagnostic->message.format_spec = msg;
-  diagnostic->message.set_location (0, location);
-  for (int i = 1; i < MAX_LOCATIONS_PER_MESSAGE; i++)
-    diagnostic->message.set_location (i, UNKNOWN_LOCATION);
-  diagnostic->override_column = 0;
+  diagnostic->message.m_richloc = richloc;
+  diagnostic->richloc = richloc;
   diagnostic->kind = kind;
   diagnostic->option_index = 0;
 }
@@ -252,10 +251,27 @@  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);
+  gcc_assert (richloc);
+  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
+};
+
+/* Get a color name for diagnostics of type KIND
+   Result could be NULL.  */
+
+const char *
+diagnostic_get_color_for_kind (diagnostic_t kind)
+{
+  return diagnostic_kind_color[kind];
 }
 
 /* Return a malloc'd string describing a location.  The caller is
@@ -270,12 +286,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]);
@@ -771,10 +781,14 @@  diagnostic_report_diagnostic (diagnostic_context *context,
 
       if (option_text)
 	{
+	  const char *cs
+	    = colorize_start (pp_show_color (context->printer),
+			      diagnostic_kind_color[diagnostic->kind]);
+	  const char *ce = colorize_stop (pp_show_color (context->printer));
 	  diagnostic->message.format_spec
 	    = ACONCAT ((diagnostic->message.format_spec,
 			" ", 
-			"[", option_text, "]",
+			"[", cs, option_text, ce, "]",
 			NULL));
 	  free (option_text);
 	}
@@ -854,9 +868,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, location, DK_NOTE);
+  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, richloc, DK_NOTE);
   if (context->inhibit_notes_p)
     {
       va_end (ap);
@@ -881,16 +926,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;
   }
@@ -907,9 +953,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, location, DK_NOTE);
+  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, richloc, DK_NOTE);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -922,11 +982,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);
 }
@@ -940,9 +1001,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);
@@ -960,9 +1022,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);
@@ -980,11 +1060,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);
@@ -1010,9 +1092,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);
@@ -1032,9 +1115,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);
@@ -1049,9 +1151,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);
 }
@@ -1064,11 +1167,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);
 }
@@ -1079,9 +1183,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, loc, DK_ERROR);
+  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, rich_loc,
+		       DK_ERROR);
   report_diagnostic (&diagnostic);
   va_end (ap);
 }
@@ -1094,9 +1214,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);
 }
@@ -1117,9 +1238,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);
 
@@ -1135,9 +1257,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);
 
@@ -1152,9 +1275,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);
 
@@ -1218,3 +1342,11 @@  real_abort (void)
 {
   abort ();
 }
+
+void
+source_range::debug (const char *msg) const
+{
+  rich_location richloc (m_start);
+  richloc.add_range (m_start, m_finish);
+  inform_at_rich_loc (&richloc, "%s", msg);
+}
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 7fcb6a8..66a867c 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -29,10 +29,12 @@  along with GCC; see the file COPYING3.  If not see
    list in diagnostic.def.  */
 struct diagnostic_info
 {
-  /* Text to be formatted. It also contains the location(s) for this
-     diagnostic.  */
+  /* Text to be formatted.  */
   text_info message;
-  unsigned int override_column;
+
+  /* The location at which the diagnostic is to be reported.  */
+  rich_location *richloc;
+
   /* Auxiliary data for client.  */
   void *x_data;
   /* The kind of diagnostic it is about.  */
@@ -102,8 +104,8 @@  struct diagnostic_context
   /* Maximum width of the source line printed.  */
   int caret_max_width;
 
-  /* Characters used for caret diagnostics.  */
-  char caret_chars[MAX_LOCATIONS_PER_MESSAGE];
+  /* Character used for caret diagnostics.  */
+  char caret_chars[rich_location::MAX_RANGES];
 
   /* True if we should print the command line option which controls
      each diagnostic, if known.  */
@@ -181,6 +183,11 @@  struct diagnostic_context
   int lock;
 
   bool inhibit_notes_p;
+
+  /* Does the frontend make calls to diagnostic_print_caret_line?
+     If so, we fall back to the old implementation of
+     diagnostic_show_locus.  */
+  bool frontend_calls_diagnostic_print_caret_line_p;
 };
 
 static inline void
@@ -252,10 +259,6 @@  extern diagnostic_context *global_dc;
 
 #define report_diagnostic(D) diagnostic_report_diagnostic (global_dc, D)
 
-/* Override the column number to be used for reporting a
-   diagnostic.  */
-#define diagnostic_override_column(DI, COL) (DI)->override_column = (COL)
-
 /* Override the option index to be used for reporting a
    diagnostic.  */
 #define diagnostic_override_option_index(DI, OPTIDX) \
@@ -279,13 +282,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,6 +313,14 @@  diagnostic_location (const diagnostic_info * diagnostic, int which = 0)
   return diagnostic->message.get_location (which);
 }
 
+/* Return the number of locations to be printed in DIAGNOSTIC.  */
+
+static inline unsigned int
+diagnostic_num_locations (const diagnostic_info * diagnostic)
+{
+  return diagnostic->message.m_richloc->get_num_locations ();
+}
+
 /* Expand the location of this diagnostic. Use this function for
    consistency.  Parameter WHICH specifies which location. By default,
    expand the first one.  */
@@ -313,12 +328,7 @@  diagnostic_location (const diagnostic_info * diagnostic, int which = 0)
 static inline expanded_location
 diagnostic_expand_location (const diagnostic_info * diagnostic, int which = 0)
 {
-  expanded_location s
-    = expand_location_to_spelling_point (diagnostic_location (diagnostic,
-							      which));
-  if (which == 0 && diagnostic->override_column)
-    s.column = diagnostic->override_column;
-  return s;
+  return diagnostic->richloc->get_range (which)->m_caret;
 }
 
 /* This is somehow the right-side margin of a caret line, that is, we
@@ -344,6 +354,10 @@  diagnostic_print_caret_line (diagnostic_context * context,
 			     expanded_location xloc2,
 			     char caret1, char caret2);
 
+
+extern const char *
+diagnostic_get_color_for_kind (diagnostic_t kind);
+
 /* Pure text formatting support functions.  */
 extern char *file_name_as_prefix (diagnostic_context *, const char *);
 
diff --git a/gcc/fortran/cpp.c b/gcc/fortran/cpp.c
index daffc20..92dc584 100644
--- a/gcc/fortran/cpp.c
+++ b/gcc/fortran/cpp.c
@@ -149,9 +149,9 @@  static void cb_include (cpp_reader *, source_location, const unsigned char *,
 static void cb_ident (cpp_reader *, source_location, const cpp_string *);
 static void cb_used_define (cpp_reader *, source_location, cpp_hashnode *);
 static void cb_used_undef (cpp_reader *, source_location, cpp_hashnode *);
-static bool cb_cpp_error (cpp_reader *, int, int, location_t, unsigned int,
+static bool cb_cpp_error (cpp_reader *, int, int, rich_location *,
 			  const char *, va_list *)
-     ATTRIBUTE_GCC_DIAG(6,0);
+     ATTRIBUTE_GCC_DIAG(5,0);
 void pp_dir_change (cpp_reader *, const char *);
 
 static int dump_macro (cpp_reader *, cpp_hashnode *, void *);
@@ -1026,13 +1026,12 @@  cb_used_define (cpp_reader *pfile, source_location line ATTRIBUTE_UNUSED,
 /* Callback from cpp_error for PFILE to print diagnostics from the
    preprocessor.  The diagnostic is of type LEVEL, with REASON set
    to the reason code if LEVEL is represents a warning, at location
-   LOCATION, with column number possibly overridden by COLUMN_OVERRIDE
-   if not zero; MSG is the translated message and AP the arguments.
+   RICHLOC; MSG is the translated message and AP the arguments.
    Returns true if a diagnostic was emitted, false otherwise.  */
 
 static bool
 cb_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
-	      location_t location, unsigned int column_override,
+	      rich_location *richloc,
 	      const char *msg, va_list *ap)
 {
   diagnostic_info diagnostic;
@@ -1067,9 +1066,7 @@  cb_cpp_error (cpp_reader *pfile ATTRIBUTE_UNUSED, int level, int reason,
       gcc_unreachable ();
     }
   diagnostic_set_info_translated (&diagnostic, msg, ap,
-				  location, dlevel);
-  if (column_override)
-    diagnostic_override_column (&diagnostic, column_override);
+				  richloc, dlevel);
   if (reason == CPP_W_WARNING_DIRECTIVE)
     diagnostic_override_option_index (&diagnostic, OPT_Wcpp);
   ret = report_diagnostic (&diagnostic);
diff --git a/gcc/fortran/error.c b/gcc/fortran/error.c
index 3825751..3d9deb0 100644
--- a/gcc/fortran/error.c
+++ b/gcc/fortran/error.c
@@ -773,6 +773,7 @@  gfc_warning (int opt, const char *gmsgid, va_list ap)
   va_copy (argp, ap);
 
   diagnostic_info diagnostic;
+  rich_location rich_loc (UNKNOWN_LOCATION);
   bool fatal_errors = global_dc->fatal_errors;
   pretty_printer *pp = global_dc->printer;
   output_buffer *tmp_buffer = pp->buffer;
@@ -787,7 +788,7 @@  gfc_warning (int opt, const char *gmsgid, va_list ap)
       --werrorcount;
     }
 
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, UNKNOWN_LOCATION,
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &rich_loc,
 		       DK_WARNING);
   diagnostic.option_index = opt;
   bool ret = report_diagnostic (&diagnostic);
@@ -938,10 +939,12 @@  gfc_format_decoder (pretty_printer *pp,
 	/* If location[0] != UNKNOWN_LOCATION means that we already
 	   processed one of %C/%L.  */
 	int loc_num = text->get_location (0) == UNKNOWN_LOCATION ? 0 : 1;
-	text->set_location (loc_num,
-			    linemap_position_for_loc_and_offset (line_table,
-								 loc->lb->location,
-								 offset));
+	source_range range
+	  = source_range::from_location (
+	      linemap_position_for_loc_and_offset (line_table,
+						   loc->lb->location,
+						   offset));
+	text->set_range (loc_num, range, true);
 	pp_string (pp, result[loc_num]);
 	return true;
       }
@@ -1075,7 +1078,7 @@  gfc_diagnostic_starter (diagnostic_context *context,
 
   expanded_location s1 = diagnostic_expand_location (diagnostic);
   expanded_location s2;
-  bool one_locus = diagnostic_location (diagnostic, 1) == UNKNOWN_LOCATION;
+  bool one_locus = diagnostic->richloc->get_num_locations () < 2;
   bool same_locus = false;
 
   if (!one_locus) 
@@ -1173,10 +1176,11 @@  gfc_warning_now_at (location_t loc, int opt, const char *gmsgid, ...)
 {
   va_list argp;
   diagnostic_info diagnostic;
+  rich_location rich_loc (loc);
   bool ret;
 
   va_start (argp, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, loc, DK_WARNING);
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &rich_loc, DK_WARNING);
   diagnostic.option_index = opt;
   ret = report_diagnostic (&diagnostic);
   va_end (argp);
@@ -1190,10 +1194,11 @@  gfc_warning_now (int opt, const char *gmsgid, ...)
 {
   va_list argp;
   diagnostic_info diagnostic;
+  rich_location rich_loc (UNKNOWN_LOCATION);
   bool ret;
 
   va_start (argp, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, UNKNOWN_LOCATION,
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &rich_loc,
 		       DK_WARNING);
   diagnostic.option_index = opt;
   ret = report_diagnostic (&diagnostic);
@@ -1209,11 +1214,12 @@  gfc_error_now (const char *gmsgid, ...)
 {
   va_list argp;
   diagnostic_info diagnostic;
+  rich_location rich_loc (UNKNOWN_LOCATION);
 
   error_buffer.flag = true;
 
   va_start (argp, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, UNKNOWN_LOCATION, DK_ERROR);
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &rich_loc, DK_ERROR);
   report_diagnostic (&diagnostic);
   va_end (argp);
 }
@@ -1226,9 +1232,10 @@  gfc_fatal_error (const char *gmsgid, ...)
 {
   va_list argp;
   diagnostic_info diagnostic;
+  rich_location rich_loc (UNKNOWN_LOCATION);
 
   va_start (argp, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, UNKNOWN_LOCATION, DK_FATAL);
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &rich_loc, DK_FATAL);
   report_diagnostic (&diagnostic);
   va_end (argp);
 
@@ -1291,6 +1298,7 @@  gfc_error (const char *gmsgid, va_list ap)
     }
 
   diagnostic_info diagnostic;
+  rich_location richloc (UNKNOWN_LOCATION);
   bool fatal_errors = global_dc->fatal_errors;
   pretty_printer *pp = global_dc->printer;
   output_buffer *tmp_buffer = pp->buffer;
@@ -1306,7 +1314,7 @@  gfc_error (const char *gmsgid, va_list ap)
       --errorcount;
     }
 
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, UNKNOWN_LOCATION, DK_ERROR);
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &richloc, DK_ERROR);
   report_diagnostic (&diagnostic);
 
   if (buffered_p)
@@ -1336,9 +1344,10 @@  gfc_internal_error (const char *gmsgid, ...)
 {
   va_list argp;
   diagnostic_info diagnostic;
+  rich_location rich_loc (UNKNOWN_LOCATION);
 
   va_start (argp, gmsgid);
-  diagnostic_set_info (&diagnostic, gmsgid, &argp, UNKNOWN_LOCATION, DK_ICE);
+  diagnostic_set_info (&diagnostic, gmsgid, &argp, &rich_loc, DK_ICE);
   report_diagnostic (&diagnostic);
   va_end (argp);
 
@@ -1472,6 +1481,7 @@  gfc_diagnostics_init (void)
   diagnostic_format_decoder (global_dc) = gfc_format_decoder;
   global_dc->caret_chars[0] = '1';
   global_dc->caret_chars[1] = '2';
+  global_dc->frontend_calls_diagnostic_print_caret_line_p = true;
   pp_warning_buffer = new (XNEW (output_buffer)) output_buffer ();
   pp_warning_buffer->flush_p = false;
   /* pp_error_buffer is statically allocated.  This simplifies memory
diff --git a/gcc/genmatch.c b/gcc/genmatch.c
index 102a635..6bfde06 100644
--- a/gcc/genmatch.c
+++ b/gcc/genmatch.c
@@ -53,14 +53,23 @@  unsigned verbose;
 
 static struct line_maps *line_table;
 
+expanded_location
+linemap_client_expand_location_to_spelling_point (source_location loc)
+{
+  const struct line_map_ordinary *map;
+  loc = linemap_resolve_location (line_table, loc, LRK_SPELLING_LOCATION, &map);
+  return linemap_expand_location (line_table, map, loc);
+}
+
 static bool
 #if GCC_VERSION >= 4001
-__attribute__((format (printf, 6, 0)))
+__attribute__((format (printf, 5, 0)))
 #endif
-error_cb (cpp_reader *, int errtype, int, source_location location,
-	  unsigned int, const char *msg, va_list *ap)
+error_cb (cpp_reader *, int errtype, int, rich_location *richloc,
+	  const char *msg, va_list *ap)
 {
   const line_map_ordinary *map;
+  source_location location = richloc->get_loc ();
   linemap_resolve_location (line_table, location, LRK_SPELLING_LOCATION, &map);
   expanded_location loc = linemap_expand_location (line_table, map, location);
   fprintf (stderr, "%s:%d:%d %s: ", loc.file, loc.line, loc.column,
@@ -102,9 +111,10 @@  __attribute__((format (printf, 2, 3)))
 #endif
 fatal_at (const cpp_token *tk, const char *msg, ...)
 {
+  rich_location richloc (tk->src_loc);
   va_list ap;
   va_start (ap, msg);
-  error_cb (NULL, CPP_DL_FATAL, 0, tk->src_loc, 0, msg, &ap);
+  error_cb (NULL, CPP_DL_FATAL, 0, &richloc, msg, &ap);
   va_end (ap);
 }
 
@@ -114,9 +124,10 @@  __attribute__((format (printf, 2, 3)))
 #endif
 fatal_at (source_location loc, const char *msg, ...)
 {
+  rich_location richloc (loc);
   va_list ap;
   va_start (ap, msg);
-  error_cb (NULL, CPP_DL_FATAL, 0, loc, 0, msg, &ap);
+  error_cb (NULL, CPP_DL_FATAL, 0, &richloc, msg, &ap);
   va_end (ap);
 }
 
@@ -126,9 +137,10 @@  __attribute__((format (printf, 2, 3)))
 #endif
 warning_at (const cpp_token *tk, const char *msg, ...)
 {
+  rich_location richloc (tk->src_loc);
   va_list ap;
   va_start (ap, msg);
-  error_cb (NULL, CPP_DL_WARNING, 0, tk->src_loc, 0, msg, &ap);
+  error_cb (NULL, CPP_DL_WARNING, 0, &richloc, msg, &ap);
   va_end (ap);
 }
 
@@ -138,9 +150,10 @@  __attribute__((format (printf, 2, 3)))
 #endif
 warning_at (source_location loc, const char *msg, ...)
 {
+  rich_location richloc (loc);
   va_list ap;
   va_start (ap, msg);
-  error_cb (NULL, CPP_DL_WARNING, 0, loc, 0, msg, &ap);
+  error_cb (NULL, CPP_DL_WARNING, 0, &richloc, msg, &ap);
   va_end (ap);
 }
 
diff --git a/gcc/input.c b/gcc/input.c
index e7302a4..bdba20f 100644
--- a/gcc/input.c
+++ b/gcc/input.c
@@ -751,6 +751,13 @@  expand_location_to_spelling_point (source_location loc)
   return expand_location_1 (loc, /*expansion_point_p=*/false);
 }
 
+expanded_location
+linemap_client_expand_location_to_spelling_point (source_location loc)
+{
+  return expand_location_to_spelling_point (loc);
+}
+
+
 /* If LOCATION is in a system header and if it is a virtual location for
    a token coming from the expansion of a macro, unwind it to the
    location of the expansion point of the macro.  Otherwise, just return
diff --git a/gcc/pretty-print.c b/gcc/pretty-print.c
index fdc7b4d..fe50df8 100644
--- a/gcc/pretty-print.c
+++ b/gcc/pretty-print.c
@@ -31,6 +31,27 @@  along with GCC; see the file COPYING3.  If not see
 #include <iconv.h>
 #endif
 
+/* Overwrite the range within this text_info's rich_location.
+   For use e.g. when implementing "+" in client format decoders.  */
+
+void
+text_info::set_range (unsigned int idx, source_range range, bool caret_p)
+{
+  gcc_checking_assert (m_richloc);
+  m_richloc->set_range (idx, range, caret_p, true);
+}
+
+location_t
+text_info::get_location (unsigned int index_of_location) const
+{
+  gcc_checking_assert (m_richloc);
+
+  if (index_of_location == 0)
+    return m_richloc->get_loc ();
+  else
+    return UNKNOWN_LOCATION;
+}
+
 // Default construct an output buffer.
 
 output_buffer::output_buffer ()
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index 36d4e37..d10272c 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -27,11 +27,6 @@  along with GCC; see the file COPYING3.  If not see
 /* Maximum number of format string arguments.  */
 #define PP_NL_ARGMAX   30
 
-/* Maximum number of locations associated to each message.  If
-   location 'i' is UNKNOWN_LOCATION, then location 'i+1' is not
-   valid.  */
-#define MAX_LOCATIONS_PER_MESSAGE 2
-
 /* The type of a text to be formatted according a format specification
    along with a list of things.  */
 struct text_info
@@ -40,21 +35,17 @@  struct text_info
   va_list *args_ptr;
   int err_no;  /* for %m */
   void **x_data;
+  rich_location *m_richloc;
 
-  inline void set_location (unsigned int index_of_location, location_t loc)
+  inline void set_location (unsigned int idx, location_t loc, bool caret_p)
   {
-    gcc_checking_assert (index_of_location < MAX_LOCATIONS_PER_MESSAGE);
-    this->locations[index_of_location] = loc;
+    source_range src_range;
+    src_range.m_start = loc;
+    src_range.m_finish = loc;
+    set_range (idx, src_range, caret_p);
   }
-
-  inline location_t get_location (unsigned int index_of_location) const
-  {
-    gcc_checking_assert (index_of_location < MAX_LOCATIONS_PER_MESSAGE);
-    return this->locations[index_of_location];
-  }
-
-private:
-  location_t locations[MAX_LOCATIONS_PER_MESSAGE];
+  void set_range (unsigned int idx, source_range range, bool caret_p);
+  location_t get_location (unsigned int index_of_location) const;
 };
 
 /* How often diagnostics are prefixed by their locations:
diff --git a/gcc/rtl-error.c b/gcc/rtl-error.c
index 8b9b391..d28be1d 100644
--- a/gcc/rtl-error.c
+++ b/gcc/rtl-error.c
@@ -69,9 +69,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/gcc.dg/plugin/diagnostic-test-show-locus-bw.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw.c
new file mode 100644
index 0000000..ab74a11
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw.c
@@ -0,0 +1,135 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O -fdiagnostics-show-caret" } */
+
+/* This is a collection of unittests for diagnostic_show_locus;
+   see the overview in diagnostic_plugin_test_show_locus.c.
+
+   In particular, note the discussion of why we need a very long line here:
+01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+   and that we can't use macros in this file.  */
+
+void test_simple (void)
+{
+#if 0
+  myvar = myvar.x; /* { dg-warning "test" } */
+
+/* { dg-begin-multiline-output "" }
+   myvar = myvar.x;
+           ~~~~~^~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_simple_2 (void)
+{
+#if 0
+  x = first_function () + second_function ();  /* { dg-warning "test" } */
+
+/* { dg-begin-multiline-output "" }
+   x = first_function () + second_function ();
+       ~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+
+void test_multiline (void)
+{
+#if 0
+  x = (first_function ()
+       + second_function ()); /* { dg-warning "test" } */
+
+/* { dg-begin-multiline-output "" }
+   x = (first_function ()
+        ~~~~~~~~~~~~~~~~~
+        + second_function ());
+        ^ ~~~~~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_many_lines (void)
+{
+#if 0
+  x = (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-warning "test" } */
+                                                amet, consectetur,
+                                                adipiscing, elit, sed,
+                                                eiusmod, tempor, incididunt,
+                                                ut, labore, et, dolore,
+                                                magna, aliqua));
+
+/* { dg-begin-multiline-output "" }
+   x = (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,
+        ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                                                 amet, consectetur,
+                                                 ~~~~~~~~~~~~~~~~~~
+                                                 adipiscing, elit, sed,
+                                                 ~~~~~~~~~~~~~~~~~~~~~~
+                                                 eiusmod, tempor, incididunt,
+                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                                                 ut, labore, et, dolore,
+                                                 ~~~~~~~~~~~~~~~~~~~~~~~
+                                                 magna, aliqua));
+                                                 ~~~~~~~~~~~~~~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_richloc_from_proper_range (void)
+{
+#if 0
+  float f = 98.6f; /* { dg-warning "test" } */
+/* { dg-begin-multiline-output "" }
+   float f = 98.6f;
+             ^~~~~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_caret_within_proper_range (void)
+{
+#if 0
+  float f = foo * bar; /* { dg-warning "17: test" } */
+/* { dg-begin-multiline-output "" }
+   float f = foo * bar;
+             ~~~~^~~~~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_very_wide_line (void)
+{
+#if 0
+                                                                                float f = foo * bar; /* { dg-warning "95: test" } */
+/* { dg-begin-multiline-output "" }
+                                              float f = foo * bar;
+                                                        ~~~~^~~~~
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_multiple_carets (void)
+{
+#if 0
+   x = x + y /* { dg-warning "8: test" } */
+/* { dg-begin-multiline-output "" }
+    x = x + y
+        A   B
+   { dg-end-multiline-output "" } */
+#endif
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-color.c b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-color.c
new file mode 100644
index 0000000..6789a47
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-color.c
@@ -0,0 +1,143 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O -fdiagnostics-show-caret -fplugin-arg-diagnostic_plugin_test_show_locus-color" } */
+
+/* This is a collection of unittests for diagnostic_show_locus;
+   see the overview in diagnostic_plugin_test_show_locus.c.
+
+   In particular, note the discussion of why we need a very long line here:
+01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+   and that we can't use macros in this file.  */
+
+void test_simple (void)
+{
+#if 0
+  myvar = myvar.x; /* { dg-warning "test" } */
+
+/* { dg-begin-multiline-output "" }
+   myvar = myvar.x;
+           ~~~~~^~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_simple_2 (void)
+{
+#if 0
+  x = first_function () + second_function ();  /* { dg-warning "test" } */
+
+/* { dg-begin-multiline-output "" }
+   x = first_function () + second_function ();
+       ~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+
+void test_multiline (void)
+{
+#if 0
+  x = (first_function ()
+       + second_function ()); /* { dg-warning "test" } */
+
+/* { dg-begin-multiline-output "" }
+   x = (first_function ()
+        ~~~~~~~~~~~~~~~~~
+        + second_function ());
+        ^ ~~~~~~~~~~~~~~~~~~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_many_lines (void)
+{
+#if 0
+  x = (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-warning "test" } */
+                                                amet, consectetur,
+                                                adipiscing, elit, sed,
+                                                eiusmod, tempor, incididunt,
+                                                ut, labore, et, dolore,
+                                                magna, aliqua));
+
+/* { dg-begin-multiline-output "" }
+   x = (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,
+        ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                                                 amet, consectetur,
+                                                 ~~~~~~~~~~~~~~~~~~
+                                                 adipiscing, elit, sed,
+                                                 ~~~~~~~~~~~~~~~~~~~~~~
+                                                 eiusmod, tempor, incididunt,
+                                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                                                 ut, labore, et, dolore,
+                                                 ~~~~~~~~~~~~~~~~~~~~~~~
+                                                 magna, aliqua));
+                                                 ~~~~~~~~~~~~~~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_richloc_from_proper_range (void)
+{
+#if 0
+  float f = 98.6f; /* { dg-warning "test" } */
+/* { dg-begin-multiline-output "" }
+   float f = 98.6f;
+             ^~~~~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_caret_within_proper_range (void)
+{
+#if 0
+  float f = foo * bar; /* { dg-warning "17: test" } */
+/* { dg-begin-multiline-output "" }
+   float f = foo * bar;
+             ~~~~^~~~~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_very_wide_line (void)
+{
+#if 0
+                                                                                float f = foo * bar; /* { dg-warning "95: test" } */
+/* { dg-begin-multiline-output "" }
+                                              float f = foo * bar;
+                                                        ~~~~^~~~~
+
+   { dg-end-multiline-output "" } */
+#endif
+}
+
+void test_multiple_carets (void)
+{
+#if 0
+   x = x + y /* { dg-warning "8: test" } */
+/* { dg-begin-multiline-output "" }
+    x = x + y
+        A   B
+
+   { dg-end-multiline-output "" } */
+#endif
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
new file mode 100644
index 0000000..4917fad
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
@@ -0,0 +1,300 @@ 
+/* { dg-options "-O" } */
+
+/* This plugin exercises the diagnostics-printing code.
+
+   The goal is to unit-test the range-printing code without needing any
+   correct range data within the compiler's IR.  We can't use any real
+   diagnostics for this, so we have to fake it, hence this plugin.
+
+   There are two test files used with this code:
+
+     diagnostic-test-show-locus-ascii-bw.c
+     ..........................-ascii-color.c
+
+   to exercise uncolored vs colored output by supplying plugin arguments
+   to hack in the desired behavior:
+
+     -fplugin-arg-diagnostic_plugin_test_show_locus-color
+
+   The test files contain functions, but the body of each
+   function is disabled using the preprocessor.  The plugin detects
+   the functions by name, and inject diagnostics within them, using
+   hard-coded locations relative to the top of each function.
+
+   The plugin uses a function "get_loc" below to map from line/column
+   numbers to source_location, and this relies on input_location being in
+   the same ordinary line_map as the locations in question.  The plugin
+   runs after parsing, so input_location will be at the end of the file.
+
+   This need for all of the test code to be in a single ordinary line map
+   means that each test file needs to have a very long line near the top
+   (potentially to cover the extra byte-count of colorized data),
+   to ensure that further very long lines don't start a new linemap.
+   This also means that we can't use macros in the test files.  */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "diagnostic.h"
+#include "context.h"
+#include "print-tree.h"
+
+int plugin_is_GPL_compatible;
+
+const pass_data pass_data_test_show_locus =
+{
+  GIMPLE_PASS, /* type */
+  "test_show_locus", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_test_show_locus : public gimple_opt_pass
+{
+public:
+  pass_test_show_locus(gcc::context *ctxt)
+    : gimple_opt_pass(pass_data_test_show_locus, ctxt)
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) { return true; }
+  virtual unsigned int execute (function *);
+
+}; // class pass_test_show_locus
+
+/* Given LINE_NUM and COL_NUM, generate a source_location in the
+   current file, relative to input_location.  This relies on the
+   location being expressible in the same ordinary line_map as
+   input_location (which is typically at the end of the source file
+   when this is called).  Hence the test files we compile with this
+   plugin must have an initial very long line (to avoid long lines
+   starting a new line map), and must not use macros.
+
+   COL_NUM uses the Emacs convention of 0-based column numbers.  */
+
+static source_location
+get_loc (unsigned int line_num, unsigned int col_num)
+{
+  /* Use input_location to get the relevant line_map */
+  const struct line_map_ordinary *line_map
+    = (const line_map_ordinary *)(linemap_lookup (line_table,
+						  input_location));
+
+  /* Convert from 0-based column numbers to 1-based column numbers.  */
+  source_location loc
+    = linemap_position_for_line_and_column (line_map,
+					    line_num, col_num + 1);
+
+  return loc;
+}
+
+/* Was "color" passed in as a plugin argument?  */
+static bool force_show_locus_color = false;
+
+/* We want to verify the colorized output of diagnostic_show_locus,
+   but turning on colorization for everything confuses "dg-warning" etc.
+   Hence we special-case it within this plugin by using this modified
+   version of default_diagnostic_finalizer, which, if "color" is
+   passed in as a plugin argument turns on colorization, but just
+   for diagnostic_show_locus.  */
+
+static void
+custom_diagnostic_finalizer (diagnostic_context *context,
+			     diagnostic_info *diagnostic)
+{
+  bool old_show_color = pp_show_color (context->printer);
+  if (force_show_locus_color)
+    pp_show_color (context->printer) = true;
+  diagnostic_show_locus (context, diagnostic);
+  pp_show_color (context->printer) = old_show_color;
+
+  pp_destroy_prefix (context->printer);
+  pp_newline_and_flush (context->printer);
+}
+
+/* Exercise the diagnostic machinery to emit various warnings,
+   for use by diagnostic-test-show-locus-*.c.
+
+   We inject each warning relative to the start of a function,
+   which avoids lots of hardcoded absolute locations.  */
+
+static void
+test_show_locus (function *fun)
+{
+  tree fndecl = fun->decl;
+  tree identifier = DECL_NAME (fndecl);
+  const char *fnname = IDENTIFIER_POINTER (identifier);
+  location_t fnstart = fun->function_start_locus;
+  int fnstart_line = LOCATION_LINE (fnstart);
+
+  diagnostic_finalizer (global_dc) = custom_diagnostic_finalizer;
+
+  /* Hardcode the "terminal width", to verify the behavior of
+     very wide lines.  */
+  global_dc->caret_max_width = 70;
+
+  if (0 == strcmp (fnname, "test_simple"))
+    {
+      const int line = fnstart_line + 2;
+      rich_location richloc (get_loc (line, 15));
+      richloc.add_range (get_loc (line, 10), get_loc (line, 14));
+      richloc.add_range (get_loc (line, 16), get_loc (line, 16));
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  if (0 == strcmp (fnname, "test_simple_2"))
+    {
+      const int line = fnstart_line + 2;
+      rich_location richloc (get_loc (line, 24));
+      richloc.add_range (get_loc (line, 6),
+			 get_loc (line, 22));
+      richloc.add_range (get_loc (line, 26),
+			 get_loc (line, 43));
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  if (0 == strcmp (fnname, "test_multiline"))
+    {
+      const int line = fnstart_line + 2;
+      rich_location richloc (get_loc (line + 1, 7));
+      richloc.add_range (get_loc (line, 7),
+			 get_loc (line, 23));
+      richloc.add_range (get_loc (line + 1, 9),
+			 get_loc (line + 1, 26));
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  if (0 == strcmp (fnname, "test_many_lines"))
+    {
+      const int line = fnstart_line + 2;
+      rich_location richloc (get_loc (line + 5, 7));
+      richloc.add_range (get_loc (line, 7),
+			 get_loc (line + 4, 65));
+      richloc.add_range (get_loc (line + 5, 9),
+			 get_loc (line + 10, 61));
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  /* Example of a rich_location constructed directly from a
+     source_range where the range is larger than one character.  */
+  if (0 == strcmp (fnname, "test_richloc_from_proper_range"))
+    {
+      const int line = fnstart_line + 2;
+      source_range src_range;
+      src_range.m_start = get_loc (line, 12);
+      src_range.m_finish = get_loc (line, 16);
+      rich_location richloc (src_range);
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  /* Example of a single-range location where the range starts
+     before the caret.  */
+  if (0 == strcmp (fnname, "test_caret_within_proper_range"))
+    {
+      const int line = fnstart_line + 2;
+      location_t caret = get_loc (line, 16);
+      source_range src_range;
+      src_range.m_start = get_loc (line, 12);
+      src_range.m_finish = get_loc (line, 20);
+      rich_location richloc (caret);
+      richloc.set_range (0, src_range, true, false);
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  /* Example of a very wide line, where the information of interest
+     is beyond the width of the terminal (hardcoded above).  */
+  if (0 == strcmp (fnname, "test_very_wide_line"))
+    {
+      const int line = fnstart_line + 2;
+      location_t caret = get_loc (line, 94);
+      source_range src_range;
+      src_range.m_start = get_loc (line, 90);
+      src_range.m_finish = get_loc (line, 98);
+      rich_location richloc (caret);
+      richloc.set_range (0, src_range, true, false);
+      warning_at_rich_loc (&richloc, 0, "test");
+    }
+
+  /* Example of multiple carets.  */
+  if (0 == strcmp (fnname, "test_multiple_carets"))
+    {
+      const int line = fnstart_line + 2;
+      location_t caret_a = get_loc (line, 7);
+      location_t caret_b = get_loc (line, 11);
+      rich_location richloc (caret_a);
+      richloc.add_range (caret_b, caret_b, true);
+      global_dc->caret_chars[0] = 'A';
+      global_dc->caret_chars[1] = 'B';
+      warning_at_rich_loc (&richloc, 0, "test");
+      global_dc->caret_chars[0] = '^';
+      global_dc->caret_chars[1] = '^';
+    }
+}
+
+unsigned int
+pass_test_show_locus::execute (function *fun)
+{
+  test_show_locus (fun);
+  return 0;
+}
+
+static gimple_opt_pass *
+make_pass_test_show_locus (gcc::context *ctxt)
+{
+  return new pass_test_show_locus (ctxt);
+}
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  for (int i = 0; i < argc; i++)
+    {
+      if (0 == strcmp (argv[i].key, "color"))
+	force_show_locus_color = true;
+    }
+
+  pass_info.pass = make_pass_test_show_locus (g);
+  pass_info.reference_pass_name = "ssa";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_AFTER;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 39fab6e..941bccc 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -63,6 +63,9 @@  set plugin_test_list [list \
     { start_unit_plugin.c start_unit-test-1.c } \
     { finish_unit_plugin.c finish_unit-test-1.c } \
     { wide-int_plugin.c wide-int-test-1.c } \
+    { diagnostic_plugin_test_show_locus.c \
+	  diagnostic-test-show-locus-bw.c \
+	  diagnostic-test-show-locus-color.c } \
 ]
 
 foreach plugin_test $plugin_test_list {
diff --git a/gcc/testsuite/lib/gcc-dg.exp b/gcc/testsuite/lib/gcc-dg.exp
index 7c1ab85..8cc1d87 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/tree-diagnostic.c b/gcc/tree-diagnostic.c
index 135f142..02009d8 100644
--- a/gcc/tree-diagnostic.c
+++ b/gcc/tree-diagnostic.c
@@ -289,7 +289,7 @@  default_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
     }
 
   if (set_locus)
-    text->set_location (0, DECL_SOURCE_LOCATION (t));
+    text->set_location (0, DECL_SOURCE_LOCATION (t), true);
 
   if (DECL_P (t))
     {
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index 7cd1fe7..3c34d51 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -3602,7 +3602,7 @@  void
 percent_K_format (text_info *text)
 {
   tree t = va_arg (*text->args_ptr, tree), block;
-  text->set_location (0, EXPR_LOCATION (t));
+  text->set_location (0, EXPR_LOCATION (t), true);
   gcc_assert (pp_ti_abstract_origin (text) != NULL);
   block = TREE_BLOCK (t);
   *pp_ti_abstract_origin (text) = NULL;
diff --git a/libcpp/errors.c b/libcpp/errors.c
index a33196e..c351c11 100644
--- a/libcpp/errors.c
+++ b/libcpp/errors.c
@@ -57,7 +57,8 @@  cpp_diagnostic (cpp_reader * pfile, int level, int reason,
 
   if (!pfile->cb.error)
     abort ();
-  ret = pfile->cb.error (pfile, level, reason, src_loc, 0, _(msgid), ap);
+  rich_location richloc (src_loc);
+  ret = pfile->cb.error (pfile, level, reason, &richloc, _(msgid), ap);
 
   return ret;
 }
@@ -139,7 +140,9 @@  cpp_diagnostic_with_line (cpp_reader * pfile, int level, int reason,
   
   if (!pfile->cb.error)
     abort ();
-  ret = pfile->cb.error (pfile, level, reason, src_loc, column, _(msgid), ap);
+  rich_location richloc (src_loc);
+  richloc.override_column (column);
+  ret = pfile->cb.error (pfile, level, reason, &richloc, _(msgid), ap);
 
   return ret;
 }
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 5eaea6b..a2bdfa0 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -573,9 +573,9 @@  struct cpp_callbacks
 
   /* Called to emit a diagnostic.  This callback receives the
      translated message.  */
-  bool (*error) (cpp_reader *, int, int, source_location, unsigned int,
+  bool (*error) (cpp_reader *, int, int, rich_location *,
 		 const char *, va_list *)
-       ATTRIBUTE_FPTR_PRINTF(6,0);
+       ATTRIBUTE_FPTR_PRINTF(5,0);
 
   /* Callbacks for when a macro is expanded, or tested (whether
      defined or not at the time) in #ifdef, #ifndef or "defined".  */
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index bc747c1..bd73780 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -118,6 +118,35 @@  typedef unsigned int linenum_type;
   libcpp/location-example.txt.  */
 typedef unsigned int source_location;
 
+/* A range of source locations.
+
+   Ranges are closed:
+   m_start is the first location within the range,
+   m_finish is the last location within the range.
+
+   We may need a more compact way to store these, but for now,
+   let's do it the simple way, as a pair.  */
+struct GTY(()) source_range
+{
+  source_location m_start;
+  source_location m_finish;
+
+  void debug (const char *msg) const;
+
+  /* We avoid using constructors, since various structs that
+     don't yet have constructors will embed instances of
+     source_range.  */
+
+  /* Make a source_range from a source_location.  */
+  static source_range from_location (source_location loc)
+  {
+    source_range result;
+    result.m_start = loc;
+    result.m_finish = loc;
+    return result;
+  }
+};
+
 /* Memory allocation function typedef.  Works like xrealloc.  */
 typedef void *(*line_map_realloc) (void *, size_t);
 
@@ -1015,6 +1044,175 @@  typedef struct
   bool sysp;
 } expanded_location;
 
+/* 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 closed
+   m_start is the first location within the range, and
+   m_finish is the last location within the range.  */
+struct location_range
+{
+  expanded_location m_start;
+  expanded_location m_finish;
+
+  /* Should a caret be drawn for this range?  Typically this is
+     true for the 0th range, and false for subsequent ranges,
+     but the Fortran frontend overrides this for rendering things like:
+
+       x = x + y
+           1   2
+       Error: Shapes for operands at (1) and (2) are not conformable
+
+     where "1" and "2" are notionally carets.  */
+  bool m_show_caret_p;
+  expanded_location m_caret;
+};
+
+/* A "rich" source code location, for use when printing diagnostics.
+   A rich_location has one or more ranges, each optionally with
+   a caret.   Typically the zeroth range has a caret; other ranges
+   sometimes have carets.
+
+   The "primary" location of a rich_location is the caret of range 0,
+   used for determining the line/column when printing diagnostic
+   text, such as:
+
+      some-file.c:3:1: error: ...etc...
+
+   Additional ranges may be added to help the user identify other
+   pertinent clauses in a diagnostic.
+
+   rich_location instances are intended to be allocated on the stack
+   when generating diagnostics, and to be short-lived.
+
+   Examples of rich locations
+   --------------------------
+
+   Example A
+   *********
+      int i = "foo";
+              ^
+   This "rich" location is simply a single range (range 0), with
+   caret = start = finish at the given point.
+
+   Example B
+   *********
+      a = (foo && bar)
+          ~~~~~^~~~~~~
+   This rich location has a single range (range 0), with the caret
+   at the first "&", and the start/finish at the parentheses.
+   Compare with example C below.
+
+   Example C
+   *********
+      a = (foo && bar)
+           ~~~ ^~ ~~~
+   This rich location has three ranges:
+   - Range 0 has its caret and start location at the first "&" and
+     end at the second "&.
+   - Range 1 has its start and finish at the "f" and "o" of "foo";
+     the caret is not flagged for display, but is perhaps at the "f"
+     of "foo".
+   - Similarly, range 2 has its start and finish at the "b" and "r" of
+     "bar"; the caret is not flagged for display, but is perhaps at the
+     "b" of "bar".
+   Compare with example B above.
+
+   Example D (Fortran frontend)
+   ****************************
+       x = x + y
+           1   2
+   This rich location has range 0 at "1", and range 1 at "2".
+   Both are flagged for caret display.  Both ranges have start/finish
+   equal to their caret point.  The frontend overrides the diagnostic
+   context's default caret character for these ranges.
+
+   Example E
+   *********
+      printf ("arg0: %i  arg1: %s arg2: %i",
+                               ^~
+              100, 101, 102);
+                   ~~~
+   This rich location has two ranges:
+   - range 0 is at the "%s" with start = caret = "%" and finish at
+     the "s".
+   - range 1 has start/finish covering the "101" and is not flagged for
+     caret printing; it is perhaps at the start of "101".  */
+
+class rich_location
+{
+ public:
+  /* Constructors.  */
+
+  /* Constructing from a location.  */
+  rich_location (source_location loc);
+
+  /* Constructing from a source_range.  */
+  rich_location (source_range src_range);
+
+  /* Accessors.  */
+  source_location get_loc () const { return m_loc; }
+
+  source_location *get_loc_addr () { return &m_loc; }
+
+  void
+  add_range (source_location start, source_location finish,
+	     bool show_caret_p = false);
+
+  void
+  add_range (source_range src_range,
+	     bool show_caret_p = false);
+
+  void
+  add_range (location_range *src_range);
+
+  void
+  set_range (unsigned int idx, source_range src_range,
+	     bool show_caret_p, bool overwrite_loc_p);
+
+  unsigned int get_num_locations () const { return m_num_ranges; }
+
+  location_range *get_range (unsigned int idx)
+  {
+    linemap_assert (idx < m_num_ranges);
+    return &m_ranges[idx];
+  }
+
+  expanded_location lazily_expand_location ();
+
+  void
+  override_column (int column);
+
+public:
+  static const int MAX_RANGES = 3;
+
+protected:
+  source_location m_loc;
+
+  unsigned int m_num_ranges;
+  location_range m_ranges[MAX_RANGES];
+
+  bool m_have_expanded_location;
+  expanded_location m_expanded_location;
+};
+
 /* This is enum is used by the function linemap_resolve_location
    below.  The meaning of the values is explained in the comment of
    that function.  */
@@ -1158,4 +1356,13 @@  void linemap_dump (FILE *, struct line_maps *, unsigned, bool);
    specifies how many macro maps to dump.  */
 void line_table_dump (FILE *, struct line_maps *, unsigned int, unsigned int);
 
+/* The rich_location class requires a way to expand source_location instances.
+   We would directly use expand_location_to_spelling_point, which is
+   implemented in gcc/input.c, but we also need to use it for rich_location
+   within genmatch.c.
+   Hence we require client code of libcpp to implement the following
+   symbol.  */
+extern expanded_location
+linemap_client_expand_location_to_spelling_point (source_location );
+
 #endif /* !LIBCPP_LINE_MAP_H  */
diff --git a/libcpp/line-map.c b/libcpp/line-map.c
index 3d82e9b..a6fa782 100644
--- a/libcpp/line-map.c
+++ b/libcpp/line-map.c
@@ -1752,3 +1752,133 @@  line_table_dump (FILE *stream, struct line_maps *set, unsigned int num_ordinary,
       fprintf (stream, "\n");
     }
 }
+
+/* class rich_location.  */
+
+/* Construct a rich_location with location LOC as its initial range.  */
+
+rich_location::rich_location (source_location loc) :
+  m_loc (loc),
+  m_num_ranges (0),
+  m_have_expanded_location (false)
+{
+  /* Set up the 0th range: */
+  add_range (loc, loc, true);
+  m_ranges[0].m_caret = lazily_expand_location ();
+}
+
+/* Construct a rich_location with source_range SRC_RANGE as its
+   initial range.  */
+
+rich_location::rich_location (source_range src_range)
+: m_loc (src_range.m_start),
+  m_num_ranges (0),
+  m_have_expanded_location (false)
+{
+  /* Set up the 0th range: */
+  add_range (src_range, true);
+}
+
+/* Get an expanded_location for this rich_location's primary
+   location.  */
+
+expanded_location
+rich_location::lazily_expand_location ()
+{
+  if (!m_have_expanded_location)
+    {
+      m_expanded_location
+	= linemap_client_expand_location_to_spelling_point (m_loc);
+      m_have_expanded_location = true;
+    }
+
+  return m_expanded_location;
+}
+
+/* Set the column of the primary location.  */
+
+void
+rich_location::override_column (int column)
+{
+  lazily_expand_location ();
+  m_expanded_location.column = column;
+}
+
+/* Add the given range.  */
+
+void
+rich_location::add_range (source_location start, source_location finish,
+			  bool show_caret_p)
+{
+  linemap_assert (m_num_ranges < MAX_RANGES);
+
+  location_range *range = &m_ranges[m_num_ranges++];
+  range->m_start = linemap_client_expand_location_to_spelling_point (start);
+  range->m_finish = linemap_client_expand_location_to_spelling_point (finish);
+  range->m_caret = range->m_start;
+  range->m_show_caret_p = show_caret_p;
+}
+
+/* Add the given range.  */
+
+void
+rich_location::add_range (source_range src_range, bool show_caret_p)
+{
+  linemap_assert (m_num_ranges < MAX_RANGES);
+
+  add_range (src_range.m_start, src_range.m_finish, show_caret_p);
+}
+
+void
+rich_location::add_range (location_range *src_range)
+{
+  linemap_assert (m_num_ranges < MAX_RANGES);
+
+  m_ranges[m_num_ranges++] = *src_range;
+}
+
+/* Add or overwrite the range given by IDX.  It must either
+   overwrite an existing range, or add one *exactly* on the end of
+   the array.
+
+   This is primarily for use by gcc when implementing diagnostic
+   format decoders e.g. the "+" in the C/C++ frontends, for handling
+   format codes like "%q+D" (which writes the source location of a
+   tree back into range 0 of the rich_location).
+
+   If SHOW_CARET_P is true, then the range should be rendered with
+   a caret at its starting location.  This
+   is for use by the Fortran frontend, for implementing the
+   "%C" and "%L" format codes.  */
+
+void
+rich_location::set_range (unsigned int idx, source_range src_range,
+			  bool show_caret_p, bool overwrite_loc_p)
+{
+  linemap_assert (idx < MAX_RANGES);
+
+  /* We can either overwrite an existing range, or add one exactly
+     on the end of the array.  */
+  linemap_assert (idx <= m_num_ranges);
+
+  location_range *locrange = &m_ranges[idx];
+  locrange->m_start
+    = linemap_client_expand_location_to_spelling_point (src_range.m_start);
+  locrange->m_finish
+    = linemap_client_expand_location_to_spelling_point (src_range.m_finish);
+
+  locrange->m_show_caret_p = show_caret_p;
+  if (overwrite_loc_p)
+    locrange->m_caret = locrange->m_start;
+
+  /* Are we adding a range onto the end?  */
+  if (idx == m_num_ranges)
+    m_num_ranges = idx + 1;
+
+  if (idx == 0 && overwrite_loc_p)
+    {
+      m_loc = src_range.m_start;
+      /* Mark any cached value here as dirty.  */
+      m_have_expanded_location = false;
+    }
+}