diff mbox

v4 of diagnostic_show_locus and rich_location

Message ID 1444664698.17932.24.camel@surprise
State New
Headers show

Commit Message

David Malcolm Oct. 12, 2015, 3:44 p.m. UTC
On Sun, 2015-09-27 at 02:55 +0200, Dodji Seketeli wrote:
> [Note to libcpp, C, and Fortran maintainers: we still need your input :-)]

Updated version of patch attached (v4); a diff relative to v3 can be
seen at:
https://dmalcolm.fedorapeople.org/gcc/2015-10-12/0003-Eliminate-special-casing-for-Fortran.patch

v4 eliminates the lingering parts of the old implementation of
diagnostic_show_locus, porting the Fortran frontend to use the new
implementation.

In the process I discovered an issue with the Fortran frontend: some of
the caret locations appear to have an off-by-one error.
For example, in gcc/testsuite/gfortran.dg/associate_5.f03, the old
implementation would issue this diagnostic:

associate_5.f03:33:6:

       y = 5 ! { dg-error "variable definition context" }
      1
associate_5.f03:32:20:

     ASSOCIATE (y => x) ! { dg-error "variable definition context" }
                    2
Error: Associate-name ‘y’ can not appear in a variable definition
context (assignment) at (1) because its target at (2) can not, either

Note how the carets 1 and 2 appear one column before the "y" and the "x"
that they refer to.

This seems to be a pre-existing bug in the Fortran FE, which I've now
filed as PR fortran/67936.

On porting the Fortran FE to fully use the new implementation of
diagnostic_show_locus, I found that the "1" caret in the above
disappeared, due to v3 of the layout printer suppressed carets and
underlines appearing within the leading whitespace before the text in
its line.  So I updated that to only suppress underlines in such a
location, and not carets, to ensure that we at least faithfully print
both carets, at the given (erroneous) locations.   I added test coverage
for this (test_caret_on_leading_whitespace).

The existing Fortran testcase for diagnostics with multiple locations
don't seem to verify the -fdiagnostics-show-caret case; I visually
inspected the results, but perhaps we could add some automated test
coverage there using the dg-{begin|end}-multiline directives from
earlier in this kit (which is now in trunk).  I don't know if adding
such test coverage is necessary for acceptance of this patch though.

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu.  OK for
trunk?

Some other comments inline.

> 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" ?

Yes, thanks.  Fixed.

> > */
> 
> > 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.

FWIW. I didn't do this.  The function is probably only meaningful to
call from where I was calling it, not necessarily from gdb.

> [...]
> 
> >> +/* 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).

That's what I've ended up going with.


> 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.
> 
> [...]

v4 of the patch does the conversion of Fortran, and eliminates the
adaptation layer.  No partial transitions here!

As noted above, a diff relative to v3 can be seen at:
https://dmalcolm.fedorapeople.org/gcc/2015-10-12/0003-Eliminate-special-casing-for-Fortran.patch


Manu: I hope this addresses your concerns.

> 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,

Thanks for the review and other comments.
Dave

Comments

Manuel López-Ibáñez Oct. 12, 2015, 4:36 p.m. UTC | #1
On 12 October 2015 at 16:44, David Malcolm <dmalcolm@redhat.com> wrote:
> v4 of the patch does the conversion of Fortran, and eliminates the
> adaptation layer.  No partial transitions here!
>
> Manu: I hope this addresses your concerns.

Yes, it looks great. I don't understand how this

-   and for two locations that do not fit in the same locus line:
-
-       [name]:[locus]: Error: (1)
-       [name]:[locus2]: Error: Some error at (1) and (2)
+       [locus of primary range]: Error: Some error at (1) and (2)


passes the Fortran regression testsuite since the testcases normally
try to match the two locus separately, but I guess you figured out a
way to make it work and I must admit I did not have the time to read
the patch in deep detail. But it is a bit strange that you also
deleted this part:

-   With -fdiagnostic-show-caret (the default) and for valid locations,
-   it prints for one location:
+   With -fdiagnostic-show-caret (the default) it prints:

-       [locus]:
+       [locus of primary range]:

           some code
                  1
        Error: Some error at (1)

-   for two locations that fit in the same locus line:
+  With -fno-diagnostic-show-caret or if the primary range is not
+  valid, it prints:

-       [locus]:
-
-         some code and some more code
-                1       2
-       Error: Some error at (1) and (2)
-
-   and for two locations that do not fit in the same locus line:
-
-       [locus]:
-
-         some code
-                1
-       [locus2]:
-
-         some other code
-           2
-       Error: Some error at (1) and (2)
-

which should work the same before and after your patch. Independently
of whether the actual logic moved into some new mechanism in the new
rich locations world, this seems like useful info to keep in
fortran/error.c.

Cheers,

Manuel.
David Malcolm Oct. 13, 2015, 6:09 p.m. UTC | #2
On Mon, 2015-10-12 at 17:36 +0100, Manuel López-Ibáñez wrote:
> On 12 October 2015 at 16:44, David Malcolm <dmalcolm@redhat.com> wrote:
> > v4 of the patch does the conversion of Fortran, and eliminates the
> > adaptation layer.  No partial transitions here!
> >
> > Manu: I hope this addresses your concerns.
> 
> Yes, it looks great. I don't understand how this
> 
> -   and for two locations that do not fit in the same locus line:
> -
> -       [name]:[locus]: Error: (1)
> -       [name]:[locus2]: Error: Some error at (1) and (2)
> +       [locus of primary range]: Error: Some error at (1) and (2)
> 
> 
> passes the Fortran regression testsuite since the testcases normally
> try to match the two locus separately, but I guess you figured out a
> way to make it work and I must admit I did not have the time to read
> the patch in deep detail.

The way it works is that the patch kit emulates the behavior of the old
printer for the -fno-diagnostics-show-caret case.

Consider this two-locus error, where the loci are on different lines.
With the patch it prints:

associate_5.f03:33:6:

     ASSOCIATE (y => x) ! { dg-error "variable definition context" }
                    2
       y = 5 ! { dg-error "variable definition context" }
      1

Error: Associate-name ‘y’ can not appear in a variable definition context (assignment) at (1) because its target at (2) can not, either

...using the new implementation of diagnostic-show-locus.


With -fno-diagnostics-show-caret, it prints:

associate_5.f03:33:6: Error: (1)
associate_5.f03:32:20: Error: Associate-name ‘y’ can not appear in a variable definition context (assignment) at (1) because its target at (2) can not, either

where the latter is the same behavior as before the patch.

The testsuite passes since it's faithfully emulating the old
-fno-diagnostics-show-caret behavior.

> But it is a bit strange that you also
> deleted this part:
> 
> -   With -fdiagnostic-show-caret (the default) and for valid locations,
> -   it prints for one location:
> +   With -fdiagnostic-show-caret (the default) it prints:
> 
> -       [locus]:
> +       [locus of primary range]:
> 
>            some code
>                   1
>         Error: Some error at (1)
> 
> -   for two locations that fit in the same locus line:
> +  With -fno-diagnostic-show-caret or if the primary range is not
> +  valid, it prints:
> 
> -       [locus]:
> -
> -         some code and some more code
> -                1       2
> -       Error: Some error at (1) and (2)
> -
> -   and for two locations that do not fit in the same locus line:
> -
> -       [locus]:
> -
> -         some code
> -                1
> -       [locus2]:
> -
> -         some other code
> -           2
> -       Error: Some error at (1) and (2)
> -
> 
> which should work the same before and after your patch. 

But this isn't what the new printer prints, for the
-fdiagnostic-show-caret case.  It doesn't print multiple "[locusN]:"
lines; these are only printed for the no-d-show-caret case.

> Independently
> of whether the actual logic moved into some new mechanism in the new
> rich locations world, this seems like useful info to keep in
> fortran/error.c.

Perhaps it's easiest to approach this from the POV of what the comment
*should* say.  For reference, the comment for gfc_diagnostic_starter
reads like this after the patch:

/* This function prints the locus (file:line:column), the diagnostic kind
   (Error, Warning) and (optionally) the relevant lines of code with
   annotation lines with '1' and/or '2' below them.

   With -fdiagnostic-show-caret (the default) it prints:

       [locus of primary range]:
       
          some code
                 1
       Error: Some error at (1)
        
  With -fno-diagnostic-show-caret or if the primary range is not
  valid, it prints:

       [locus of primary range]: Error: Some error at (1) and (2)
*/

Does this look OK?

Thanks
Dave
diff mbox

Patch

From 9ab85c1602bf91b5e24308f02796d963b3f32fc4 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Mon, 31 Aug 2015 21:32:20 -0400
Subject: [PATCH] Reimplement diagnostic_show_locus, introducing rich_location
 classes (v4)

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 (adjust_line): Delete.
	(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.
	(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): Reimplement in terms of class layout.
	* 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".
	(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_print_caret_line): Delete.
	(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.  When handling a location that will
	involve a call to diagnostic_show_locus, only attempt to print the
	locus for the primary location, and don't call into
	diagnostic_print_caret_line.
	(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.

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.
---
 gcc/c-family/c-common.c                            |  15 +-
 gcc/c-family/c-common.h                            |   4 +-
 gcc/c/c-decl.c                                     |   3 +-
 gcc/c/c-errors.c                                   |  12 +-
 gcc/c/c-objc-common.c                              |   2 +-
 gcc/cp/error.c                                     |   5 +-
 gcc/diagnostic-color.c                             |   5 +-
 gcc/diagnostic-core.h                              |   8 +
 gcc/diagnostic-show-locus.c                        | 761 ++++++++++++++++++---
 gcc/diagnostic.c                                   | 196 +++++-
 gcc/diagnostic.h                                   |  46 +-
 gcc/fortran/cpp.c                                  |  13 +-
 gcc/fortran/error.c                                | 103 +--
 gcc/genmatch.c                                     |  27 +-
 gcc/input.c                                        |   7 +
 gcc/pretty-print.c                                 |  21 +
 gcc/pretty-print.h                                 |  25 +-
 gcc/rtl-error.c                                    |   3 +-
 .../gcc.dg/plugin/diagnostic-test-show-locus-bw.c  | 149 ++++
 .../plugin/diagnostic-test-show-locus-color.c      | 158 +++++
 .../plugin/diagnostic_plugin_test_show_locus.c     | 321 +++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp             |   3 +
 gcc/testsuite/lib/gcc-dg.exp                       |   1 +
 gcc/tree-diagnostic.c                              |   2 +-
 gcc/tree-pretty-print.c                            |   2 +-
 libcpp/errors.c                                    |   7 +-
 libcpp/include/cpplib.h                            |   4 +-
 libcpp/include/line-map.h                          | 207 ++++++
 libcpp/line-map.c                                  | 130 ++++
 29 files changed, 1952 insertions(+), 288 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-color.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 4b64a44..4a5ccb7 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -10477,15 +10477,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;
@@ -10526,11 +10525,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 d5fb499..b0a7661 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -995,9 +995,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 ce8406a..732080a 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -5297,9 +5297,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 17870b5..2e2ff10 100644
--- a/gcc/cp/error.c
+++ b/gcc/cp/error.c
@@ -3562,7 +3562,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
@@ -3676,9 +3676,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..b6c9040 100644
--- a/gcc/diagnostic-show-locus.c
+++ b/gcc/diagnostic-show-locus.c
@@ -36,131 +36,694 @@  along with GCC; see the file COPYING3.  If not see
 # include <sys/ioctl.h>
 #endif
 
-/* 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
-   margin is either CARET_LINE_MARGIN characters or the difference
-   between the column and the length of the line, whatever is smaller.
-   The length of LINE is given by LINE_WIDTH.  */
-static const char *
-adjust_line (const char *line, int line_width,
-	     int max_width, int *column_p)
-{
-  int right_margin = CARET_LINE_MARGIN;
-  int column = *column_p;
-
-  gcc_checking_assert (line_width >= column);
-  right_margin = MIN (line_width - column, right_margin);
-  right_margin = max_width - right_margin;
-  if (line_width >= max_width && column > right_margin)
+static void
+show_ruler (diagnostic_context *context, int max_width, int x_offset);
+
+/* 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
+   through 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)
     {
-      line += column - right_margin;
-      *column_p = right_margin;
+      finish_state (m_current_state);
+      m_current_state = new_state;
+      begin_state (new_state);
     }
-  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().  */
+/* Turn on any colorization for STATE.  */
+
 void
-diagnostic_show_locus (diagnostic_context * context,
-		       const diagnostic_info *diagnostic)
+colorizer::begin_state (int state)
 {
-  if (!context->show_caret
-      || diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION
-      || diagnostic_location (diagnostic, 0) == context->last_location)
-    return;
+  switch (state)
+    {
+    case STATE_NORMAL_TEXT:
+      break;
 
-  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.  */
+    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 (diagnostic_location (diagnostic, 1) > BUILTINS_LOCATION)
-    s1 = diagnostic_expand_location (diagnostic, 1);
+  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);
+    }
 
-  diagnostic_print_caret_line (context, s0, s1,
-			       context->caret_chars[0],
-			       context->caret_chars[1]);
+  if (0)
+    show_ruler (context, line_width, m_x_offset);
 }
 
-/* Print (part) of the source line given by xloc1 with caret1 pointing
-   at the column.  If xloc2.column != 0 and it fits within the same
-   line as xloc1 according to diagnostic_same_line (), then caret2 is
-   printed at xloc2.colum.  Otherwise, the caller has to set up things
-   to print a second caret line for xloc2.  */
+/* 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
-diagnostic_print_caret_line (diagnostic_context * context,
-			     expanded_location xloc1,
-			     expanded_location xloc2,
-			     char caret1, char caret2)
-{
-  if (!diagnostic_same_line (context, xloc1, xloc2))
-    /* This will mean ignore xloc2.  */
-    xloc2.column = 0;
-  else if (xloc1.column == xloc2.column)
-    xloc2.column++;
-
-  int cmax = MAX (xloc1.column, xloc2.column);
+layout::print_line (int row)
+{
   int line_width;
-  const char *line = location_get_source_line (xloc1.file, xloc1.line,
+  const char *line = location_get_source_line (m_exploc.file, row,
 					       &line_width);
-  if (line == NULL || cmax > line_width)
+  if (!line)
     return;
 
-  /* Center the interesting part of the source line to fit in
-     max_width, and adjust all columns accordingly.  */
-  int max_width = context->caret_max_width;
-  int offset = (int) cmax;
-  line = adjust_line (line, line_width, max_width, &offset);
-  offset -= cmax;
-  cmax += offset;
-  xloc1.column += offset;
-  if (xloc2.column)
-    xloc2.column += offset;
-
-  /* Print the source line.  */
-  pp_newline (context->printer);
-  const char *saved_prefix = pp_get_prefix (context->printer);
-  pp_set_prefix (context->printer, NULL);
-  pp_space (context->printer);
-  while (max_width > 0 && line_width > 0)
+  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 = ' ';
-      pp_character (context->printer, c);
-      max_width--;
-      line_width--;
+      if (c != ' ')
+	{
+	  last_non_ws = column;
+	  if (first_non_ws == INT_MAX)
+	    first_non_ws = column;
+	}
+      pp_character (m_pp, c);
       line++;
     }
-  pp_newline (context->printer);
+  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);
 
-  /* Print the caret under the line.  */
-  const char *caret_cs, *caret_ce;
-  caret_cs = colorize_start (pp_show_color (context->printer), "caret");
-  caret_ce = colorize_stop (pp_show_color (context->printer));
-  int cmin = xloc2.column
-    ? MIN (xloc1.column, xloc2.column) : xloc1.column;
-  int caret_min = cmin == xloc1.column ? caret1 : caret2;
-  int caret_max = cmin == xloc1.column ? caret2 : caret1;
-
-  /* cmin is >= 1, but we indent with an extra space at the start like
-     we did above.  */
+  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)
+{
+  layout_range *range;
   int i;
-  for (i = 0; i < cmin; i++)
-    pp_space (context->printer);
-  pp_printf (context->printer, "%s%c%s", caret_cs, caret_min, caret_ce);
+  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;
 
-  if (xloc2.column)
+	  /* 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;
+
+	  /* Within a multiline range, don't display any underline
+	     in any leading or trailing whitespace on a line.
+	     We do display carets, however.  */
+	  if (!out_state->draw_caret_p)
+	    if (column < first_non_ws || column > last_non_ws)
+	      return false;
+
+	  /* 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)
     {
-      for (i++; i < cmax; i++)
-	pp_space (context->printer);
-      pp_printf (context->printer, "%s%c%s", caret_cs, caret_max, caret_ce);
+      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.  */
+
+void
+diagnostic_show_locus (diagnostic_context * context,
+		       const diagnostic_info *diagnostic)
+{
+  if (!context->show_caret
+      || diagnostic_location (diagnostic, 0) <= BUILTINS_LOCATION
+      || diagnostic_location (diagnostic, 0) == context->last_location)
+    return;
+
+  context->last_location = diagnostic_location (diagnostic, 0);
+
+  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);
-  pp_needs_newline (context->printer) = true;
 }
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..d4ebf86 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.  */
@@ -252,10 +254,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 +277,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 +308,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 +323,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
@@ -338,11 +343,8 @@  diagnostic_same_line (const diagnostic_context *context,
     && context->caret_max_width - CARET_LINE_MARGIN > abs (s1.column - s2.column);
 }
 
-void
-diagnostic_print_caret_line (diagnostic_context * context,
-			     expanded_location xloc1,
-			     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..4b3d31c 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;
       }
@@ -1024,48 +1027,21 @@  gfc_diagnostic_build_locus_prefix (diagnostic_context *context,
 }
 
 /* This function prints the locus (file:line:column), the diagnostic kind
-   (Error, Warning) and (optionally) the caret line (a source line
-   with '1' and/or '2' below it).
+   (Error, Warning) and (optionally) the relevant lines of code with
+   annotation lines with '1' and/or '2' below them.
 
-   With -fdiagnostic-show-caret (the default) and for valid locations,
-   it prints for one location:
+   With -fdiagnostic-show-caret (the default) it prints:
 
-       [locus]:
+       [locus of primary range]:
        
           some code
                  1
        Error: Some error at (1)
         
-   for two locations that fit in the same locus line:
+  With -fno-diagnostic-show-caret or if the primary range is not
+  valid, it prints:
 
-       [locus]:
-       
-         some code and some more code
-                1       2
-       Error: Some error at (1) and (2)
-
-   and for two locations that do not fit in the same locus line:
-
-       [locus]:
-       
-         some code
-                1
-       [locus2]:
-       
-         some other code
-           2
-       Error: Some error at (1) and (2)
-       
-  With -fno-diagnostic-show-caret or if one of the locations is not
-  valid, it prints for one location (or for two locations that fit in
-  the same locus line):
-
-       [locus]: Error: Some error at (1) and (2)
-
-   and for two locations that do not fit in the same locus line:
-
-       [name]:[locus]: Error: (1)
-       [name]:[locus2]: Error: Some error at (1) and (2)
+       [locus of primary range]: Error: Some error at (1) and (2)
 */
 static void 
 gfc_diagnostic_starter (diagnostic_context *context,
@@ -1075,7 +1051,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) 
@@ -1125,35 +1101,6 @@  gfc_diagnostic_starter (diagnostic_context *context,
       /* If the caret line was shown, the prefix does not contain the
 	 locus.  */
       pp_set_prefix (context->printer, kind_prefix);
-
-      if (one_locus || same_locus)
-	  return;
-
-      locus_prefix = gfc_diagnostic_build_locus_prefix (context, s2);
-      if (diagnostic_location (diagnostic, 1) <= BUILTINS_LOCATION)
-	{
-	  /* No caret line for the second location. Override the previous
-	     prefix with [locus2]:[prefix].  */
-	  pp_set_prefix (context->printer,
-			 concat (locus_prefix, " ", kind_prefix, NULL));
-	  free (kind_prefix);
-	  free (locus_prefix);
-	}
-      else
-	{
-	  /* We print the caret for the second location.  */
-	  pp_verbatim (context->printer, locus_prefix);
-	  free (locus_prefix);
-	  /* Fortran uses an empty line between locus and caret line.  */
-	  pp_newline (context->printer);
-	  s1.column = 0; /* Print only a caret line for s2.  */
-	  diagnostic_print_caret_line (context, s2, s1,
-				       context->caret_chars[1], '\0');
-	  pp_newline (context->printer);
-	  /* If the caret line was shown, the prefix does not contain the
-	     locus.  */
-	  pp_set_prefix (context->printer, kind_prefix);
-	}
     }
 }
 
@@ -1173,10 +1120,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 +1138,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 +1158,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 +1176,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 +1242,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 +1258,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 +1288,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);
 
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 5889015..aee4172 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 2654b0f..cdee253 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..a4b16da
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-bw.c
@@ -0,0 +1,149 @@ 
+/* { 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
+}
+
+void test_caret_on_leading_whitespace (void)
+{
+#if 0
+    ASSOCIATE (y => x)
+      y = 5 /* { dg-warning "6: test" } */
+/* { dg-begin-multiline-output "" }
+     ASSOCIATE (y => x)
+                    2
+       y = 5
+      1
+   { 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..47639b2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus-color.c
@@ -0,0 +1,158 @@ 
+/* { 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
+}
+
+void test_caret_on_leading_whitespace (void)
+{
+#if 0
+    ASSOCIATE (y => x)
+      y = 5 /* { dg-warning "6: test" } */
+/* { dg-begin-multiline-output "" }
+     ASSOCIATE (y => x)
+                    2
+       y = 5
+      1
+
+   { 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..e49cf46
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_show_locus.c
@@ -0,0 +1,321 @@ 
+/* { 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] = '^';
+    }
+
+  /* Example of two carets where both carets appear to have an off-by-one
+     error appearing one column early.
+     Seen with gfortran.dg/associate_5.f03.
+     In an earlier version of the printer, the printing of caret 0 aka
+     "1" was suppressed due to it appearing within the leading whitespace
+     before the text in its line.  Ensure that we at least faithfully
+     print both carets, at the given (erroneous) locations.  */
+  if (0 == strcmp (fnname, "test_caret_on_leading_whitespace"))
+    {
+      const int line = fnstart_line + 3;
+      location_t caret_a = get_loc (line, 5);
+      location_t caret_b = get_loc (line - 1, 19);
+      rich_location richloc (caret_a);
+      richloc.add_range (caret_b, caret_b, true);
+      global_dc->caret_chars[0] = '1';
+      global_dc->caret_chars[1] = '2';
+      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 ce3f6a8..29bc48a 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -3592,7 +3592,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;
+    }
+}
-- 
1.8.5.3