This patch to the "experimental-html" diagnostic sink:
* adds use of the PatternFly 3 CSS library (via an optional link
  in the generated html to a copy in a CDN)
* uses PatternFly's "alert" pattern to show severities for diagnostics,
  properly nesting "note" diagnostics for diagnostic groups.
  Example:
    before: 
https://dmalcolm.fedorapeople.org/gcc/2025-06-10/before/diagnostic-ranges.c.html
     after: 
https://dmalcolm.fedorapeople.org/gcc/2025-06-10/after/diagnostic-ranges.c.html

* adds initial support for logical locations and physical locations
* adds initial support for multi-level nested diagnostics such as those
  for C++ concepts diagnostics.  Ideally this would show a clickable
  disclosure widget to expand/collapse a level, but for now it uses
  nested <ul> elements with <li> for the child diagnostics.
  Example:
    before: 
https://dmalcolm.fedorapeople.org/gcc/2025-06-10/before/nested-diagnostics-1.C.html
     after: 
https://dmalcolm.fedorapeople.org/gcc/2025-06-10/after/nested-diagnostics-1.C.html

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r16-1406-gcb1d203445c923.

gcc/ChangeLog:
        PR other/116792
        * diagnostic-format-html.cc: Include "diagnostic-path.h" and
        "diagnostic-client-data-hooks.h".
        (html_builder::m_logical_loc_mgr): New field.
        (html_builder::m_cur_nesting_levels): New field.
        (html_builder::m_last_logical_location): New field.
        (html_builder::m_last_location): New field.
        (html_builder::m_last_expanded_location): New field.
        (HTML_STYLE): Add "white-space: pre;" to .source and .annotation.
        Add "gcc-quoted-text" CSS class.
        (html_builder::html_builder): Initialize the new fields.  If CSS
        is enabled, add CDN links to PatternFly 3 stylesheets.
        (html_builder::add_stylesheet): New.
        (html_builder::on_report_diagnostic): Add "alert" param to
        make_element_for_diagnostic, setting it by default, but unsetting
        it for nested diagnostics below the top level.  Use
        add_at_nesting_level for nested diagnostics.
        (add_nesting_level_attr): New.
        (html_builder::add_at_nesting_level): New.
        (get_pf_class_for_alert_div): New.
        (get_pf_class_for_alert_icon): New.
        (get_label_for_logical_location_kind): New.
        (add_labelled_value): New.
        (html_builder::make_element_for_diagnostic): Add leading comment.
        Add "alert" param.  Drop class="gcc-diagnostic" from <div> tag,
        instead adding the class for a PatternFly 3 alert if "alert" is
        true, and adding a <span> with an alert icon, both according to
        the diagnostic severity.  Add a severity prefix to the message for
        alerts.  Add any metadata/option text as suffixes to the message.
        Show any logical location.  Show any physical location.  Don't
        show the locus if the last location is unchanged within the
        diagnostic_group.  Wrap any execution path element in a
        <div id="execution-path"> and add a label to it.  Wrap any
        generated patch in a <div id="suggested-fix"> and add a label
        to it.
        (selftest::test_simple_log): Update expected HTML.

gcc/testsuite/ChangeLog:
        PR other/116792
        * gcc.dg/html-output/missing-semicolon.py: Update for changes
        to diagnostic elements.
        * gcc.dg/format/diagnostic-ranges-html.py: Likewise.
        * gcc.dg/plugin/diagnostic-test-metadata-html.py: Likewise.  Drop
        out-of-date comment.
        * gcc.dg/plugin/diagnostic-test-paths-2.py: Likewise.
        * gcc.dg/plugin/diagnostic-test-paths-4.py: Likewise.  Drop
        out-of-date comment.
        * gcc.dg/plugin/diagnostic-test-show-locus.py: Likewise.
        * lib/htmltest.py (get_diag_by_index): Update to use search by id.
        (get_message_within_diag): Update to use search by class.

libcpp/ChangeLog:
        PR other/116792
        * include/line-map.h (typedef expanded_location): Convert to...
        (struct expanded_location): ...this.
        (operator==): New decl, for expanded_location.
        (operator!=): Likewise.
        * line-map.cc (operator==): New decl, for expanded_location.

Signed-off-by: David Malcolm <dmalc...@redhat.com>
---
 gcc/diagnostic-format-html.cc                 | 433 ++++++++++++++++--
 .../gcc.dg/format/diagnostic-ranges-html.py   |  15 +-
 .../gcc.dg/html-output/missing-semicolon.py   |  68 ++-
 .../plugin/diagnostic-test-metadata-html.py   |  38 +-
 .../gcc.dg/plugin/diagnostic-test-paths-2.py  |  11 +-
 .../gcc.dg/plugin/diagnostic-test-paths-4.py  | 155 +------
 .../plugin/diagnostic-test-show-locus.py      |  12 +-
 gcc/testsuite/lib/htmltest.py                 |   7 +-
 libcpp/include/line-map.h                     |  15 +-
 libcpp/line-map.cc                            |  22 +
 10 files changed, 549 insertions(+), 227 deletions(-)

diff --git a/gcc/diagnostic-format-html.cc b/gcc/diagnostic-format-html.cc
index 6010712b8a5b..22bf6b965df7 100644
--- a/gcc/diagnostic-format-html.cc
+++ b/gcc/diagnostic-format-html.cc
@@ -31,6 +31,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-format-text.h"
 #include "diagnostic-output-file.h"
 #include "diagnostic-buffer.h"
+#include "diagnostic-path.h"
+#include "diagnostic-client-data-hooks.h"
 #include "selftest.h"
 #include "selftest-diagnostic.h"
 #include "pretty-print-format-impl.h"
@@ -139,26 +141,45 @@ public:
   }
 
 private:
+  void
+  add_stylesheet (std::string url);
+
   std::unique_ptr<xml::element>
   make_element_for_diagnostic (const diagnostic_info &diagnostic,
-                              diagnostic_t orig_diag_kind);
+                              diagnostic_t orig_diag_kind,
+                              bool alert);
 
   std::unique_ptr<xml::element>
   make_metadata_element (label_text label,
                         label_text url);
 
+  void
+  add_at_nesting_level (size_t nesting_level,
+                       std::unique_ptr<xml::element> child_diag_element);
+
+  void
+  push_nesting_level ();
+
+  void
+  pop_nesting_level ();
+
   diagnostic_context &m_context;
   pretty_printer *m_printer;
   const line_maps *m_line_maps;
   html_generation_options m_html_gen_opts;
+  const logical_location_manager *m_logical_loc_mgr;
 
   std::unique_ptr<xml::document> m_document;
   xml::element *m_head_element;
   xml::element *m_title_element;
   xml::element *m_diagnostics_element;
   std::unique_ptr<xml::element> m_cur_diagnostic_element;
+  std::vector<xml::element *> m_cur_nesting_levels;
   int m_next_diag_id; // for handing out unique IDs
   json::array m_ui_focus_ids;
+  logical_location m_last_logical_location;
+  location_t m_last_location;
+  expanded_location m_last_expanded_location;
 };
 
 static std::unique_ptr<xml::element>
@@ -237,8 +258,10 @@ static const char * const HTML_STYLE
      "    .ruler { color: red;\n"
      "              white-space: pre; }\n"
      "    .source { color: blue;\n"
+     "              background-color: white;\n"
      "              white-space: pre; }\n"
      "    .annotation { color: green;\n"
+     "                  background-color: white;\n"
      "                  white-space: pre; }\n"
      "    .linenum-gap { text-align: center;\n"
      "                   border-top: 1px solid black;\n"
@@ -269,6 +292,8 @@ static const char * const HTML_STYLE
      "                   font-weight: bold; }\n"
      "    .highlight-b { color: #3f9c35;\n" // pf-green-400
      "                   font-weight: bold; }\n"
+     "    .gcc-quoted-text { font-weight: bold;\n"
+     "                       font-family: mono; }\n"
      "  </style>\n");
 
 /* A little JavaScript for ease of navigation.
@@ -351,13 +376,19 @@ html_builder::html_builder (diagnostic_context &context,
   m_printer (&pp),
   m_line_maps (line_maps),
   m_html_gen_opts (html_gen_opts),
+  m_logical_loc_mgr (nullptr),
   m_head_element (nullptr),
   m_title_element (nullptr),
   m_diagnostics_element (nullptr),
-  m_next_diag_id (0)
+  m_next_diag_id (0),
+  m_last_location (UNKNOWN_LOCATION),
+  m_last_expanded_location ({})
 {
   gcc_assert (m_line_maps);
 
+  if (auto client_data_hooks = context.get_client_data_hooks ())
+    m_logical_loc_mgr = client_data_hooks->get_logical_location_manager ();
+
   m_document = std::make_unique<xml::document> ();
   m_document->m_doctypedecl = std::make_unique<html_doctypedecl> ();
   {
@@ -374,8 +405,13 @@ html_builder::html_builder (diagnostic_context &context,
        xml::auto_print_element title (xp, "title", true);
        m_title_element = xp.get_insertion_point ();
       }
+
       if (m_html_gen_opts.m_css)
-       xp.add_raw (HTML_STYLE);
+       {
+         add_stylesheet 
("https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly.min.css";);
+         add_stylesheet 
("https://cdnjs.cloudflare.com/ajax/libs/patternfly/3.24.0/css/patternfly-additions.min.css";);
+         xp.add_raw (HTML_STYLE);
+       }
       if (m_html_gen_opts.m_javascript)
        {
          xp.push_tag ("script");
@@ -408,6 +444,18 @@ html_builder::set_main_input_filename (const char *name)
     }
 }
 
+void
+html_builder::add_stylesheet (std::string url)
+{
+  gcc_assert (m_head_element);
+
+  xml::printer xp (*m_head_element);
+  xp.push_tag ("link", false);
+  xp.set_attr ("rel", "stylesheet");
+  xp.set_attr ("type", "text/css");
+  xp.set_attr ("href", std::move (url));
+}
+
 /* Implementation of "on_report_diagnostic" for HTML output.  */
 
 void
@@ -425,8 +473,14 @@ html_builder::on_report_diagnostic (const diagnostic_info 
&diagnostic,
       fnotice (stderr, "Internal compiler error:\n");
     }
 
+  const int nesting_level = m_context.get_diagnostic_nesting_level ();
+  bool alert = true;
+  if (m_cur_diagnostic_element && nesting_level > 0)
+    alert = false;
+  if (!m_cur_diagnostic_element)
+    m_last_logical_location = logical_location ();
   auto diag_element
-    = make_element_for_diagnostic (diagnostic, orig_diag_kind);
+    = make_element_for_diagnostic (diagnostic, orig_diag_kind, alert);
   if (buffer)
     {
       gcc_assert (!m_cur_diagnostic_element);
@@ -435,12 +489,77 @@ html_builder::on_report_diagnostic (const diagnostic_info 
&diagnostic,
   else
     {
       if (m_cur_diagnostic_element)
-       /* Nested diagnostic.  */
-       m_cur_diagnostic_element->add_child (std::move (diag_element));
+       {
+         /* Nested diagnostic.  */
+         gcc_assert (nesting_level >= 0);
+         add_at_nesting_level (nesting_level, std::move (diag_element));
+       }
       else
        /* Top-level diagnostic.  */
-       m_cur_diagnostic_element = std::move (diag_element);
+       {
+         m_cur_diagnostic_element = std::move (diag_element);
+         m_cur_nesting_levels.clear ();
+       }
+    }
+}
+
+// For ease of comparison with experimental-nesting-show-levels=yes
+
+static void
+add_nesting_level_attr (xml::element &element,
+                       int nesting_level)
+{
+  element.set_attr ("nesting-level", std::to_string (nesting_level));
+}
+
+void
+html_builder::
+add_at_nesting_level (size_t nesting_level,
+                     std::unique_ptr<xml::element> child_diag_element)
+{
+  gcc_assert (m_cur_diagnostic_element);
+  while (nesting_level > m_cur_nesting_levels.size ())
+    push_nesting_level ();
+  while (nesting_level < m_cur_nesting_levels.size ())
+    pop_nesting_level ();
+
+  if (nesting_level > 0)
+    {
+      gcc_assert (!m_cur_nesting_levels.empty ());
+      auto current_nesting_level = m_cur_nesting_levels.back ();
+      xml::printer xp (*current_nesting_level);
+      xp.push_tag ("li");
+      add_nesting_level_attr (*xp.get_insertion_point (),
+                             m_cur_nesting_levels.size ());
+      xp.append (std::move (child_diag_element));
+      xp.pop_tag ("li");
     }
+  else
+    m_cur_diagnostic_element->add_child (std::move (child_diag_element));
+}
+
+void
+html_builder::push_nesting_level ()
+{
+  gcc_assert (m_cur_diagnostic_element);
+  auto new_nesting_level = std::make_unique<xml::element> ("ul", false);
+  add_nesting_level_attr (*new_nesting_level,
+                         m_cur_nesting_levels.size () + 1);
+  xml::element *current_nesting_level = nullptr;
+  if (!m_cur_nesting_levels.empty ())
+    current_nesting_level = m_cur_nesting_levels.back ();
+  m_cur_nesting_levels.push_back (new_nesting_level.get ());
+  if (current_nesting_level)
+    current_nesting_level->add_child (std::move (new_nesting_level));
+  else
+    m_cur_diagnostic_element->add_child (std::move (new_nesting_level));
+}
+
+void
+html_builder::pop_nesting_level ()
+{
+  gcc_assert (m_cur_diagnostic_element);
+  m_cur_nesting_levels.pop_back ();
 }
 
 /* Custom subclass of html_label_writer.
@@ -482,9 +601,148 @@ private:
   int m_next_event_idx;
 };
 
+/* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts */
+static const char *
+get_pf_class_for_alert_div (diagnostic_t diag_kind)
+{
+  switch (diag_kind)
+    {
+    case DK_DEBUG:
+    case DK_NOTE:
+      return "alert alert-info";
+
+    case DK_ANACHRONISM:
+    case DK_WARNING:
+      return "alert alert-warning";
+
+    case DK_ERROR:
+    case DK_SORRY:
+    case DK_ICE:
+    case DK_ICE_NOBT:
+    case DK_FATAL:
+      return "alert alert-danger";
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+static const char *
+get_pf_class_for_alert_icon (diagnostic_t diag_kind)
+{
+  switch (diag_kind)
+    {
+    case DK_DEBUG:
+    case DK_NOTE:
+      return "pficon pficon-info";
+
+    case DK_ANACHRONISM:
+    case DK_WARNING:
+      return "pficon pficon-warning-triangle-o";
+
+    case DK_ERROR:
+    case DK_SORRY:
+    case DK_ICE:
+    case DK_ICE_NOBT:
+    case DK_FATAL:
+      return "pficon pficon-error-circle-o";
+
+    default:
+      gcc_unreachable ();
+    }
+}
+
+static const char *
+get_label_for_logical_location_kind (enum logical_location_kind kind)
+{
+  switch (kind)
+    {
+    default:
+      gcc_unreachable ();
+    case logical_location_kind::unknown:
+      return nullptr;
+
+    /* Kinds within executable code.  */
+    case logical_location_kind::function:
+      return "Function";
+    case logical_location_kind::member:
+      return "Member";
+    case logical_location_kind::module_:
+      return "Module";
+    case logical_location_kind::namespace_:
+      return "Namespace";
+    case logical_location_kind::type:
+      return "Type";
+    case logical_location_kind::return_type:
+      return "Return type";
+    case logical_location_kind::parameter:
+      return "Parameter";
+    case logical_location_kind::variable:
+      return "Variable";
+
+    /* Kinds within XML or HTML documents.  */
+    case logical_location_kind::element:
+      return "Element";
+    case logical_location_kind::attribute:
+      return "Attribute";
+    case logical_location_kind::text:
+      return "Text";
+    case logical_location_kind::comment:
+      return "Comment";
+    case logical_location_kind::processing_instruction:
+      return "Processing Instruction";
+    case logical_location_kind::dtd:
+      return "DTD";
+    case logical_location_kind::declaration:
+      return "Declaration";
+
+  /* Kinds within JSON documents.  */
+    case logical_location_kind::object:
+      return "Object";
+    case logical_location_kind::array:
+      return "Array";
+    case logical_location_kind::property:
+      return "Property";
+    case logical_location_kind::value:
+      return "Value";
+    }
+}
+
+static void
+add_labelled_value (xml::printer &xp,
+                   std::string id,
+                   std::string label,
+                   std::string value,
+                   bool quote_value)
+{
+  xp.push_tag ("div", true);
+  xp.set_attr ("id", id);
+  xp.push_tag ("span");
+  xp.add_text (label);
+  xp.add_text (" ");
+  xp.pop_tag ("span");
+  xp.push_tag ("span");
+  if (quote_value)
+    xp.set_attr ("class", "gcc-quoted-text");
+  xp.add_text (std::move (value));
+  xp.pop_tag ("span");
+  xp.pop_tag ("div");
+}
+
+/* Make a <div class="gcc-diagnostic"> for DIAGNOSTIC.
+
+   If ALERT is true, make it be a PatternFly alert (see
+   https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts) and
+   show severity text (e.g. "error: ").
+
+   If ALERT is false, don't show the severity text and don't show
+   the filename if it's the same as the previous diagnostic within the
+   diagnostic group.  */
+
 std::unique_ptr<xml::element>
 html_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic,
-                                          diagnostic_t orig_diag_kind)
+                                          diagnostic_t orig_diag_kind,
+                                          bool alert)
 {
   class html_token_printer : public token_printer
   {
@@ -562,8 +820,6 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
     xml::printer m_xp;
   };
 
-  auto diag_element = make_div ("gcc-diagnostic");
-
   const int diag_idx = m_next_diag_id++;
   std::string diag_id;
   {
@@ -571,30 +827,75 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
     pp_printf (&pp, "gcc-diag-%i", diag_idx);
     diag_id = pp_formatted_text (&pp);
   }
-  diag_element->set_attr ("id", diag_id);
 
   // TODO: might be nice to emulate the text output format, but colorize it
 
-  auto message_span = make_span ("gcc-message");
-  std::string message_span_id (diag_id + "-message");
-  message_span->set_attr ("id", message_span_id);
-  add_focus_id (message_span_id);
+  /* See https://pf3.patternfly.org/v3/pattern-library/widgets/#alerts
+     which has this example:
+<div class="alert alert-danger">
+  <span class="pficon pficon-error-circle-o"></span>
+  <strong>Hey there is a problem!</strong> Yeah this is really messed up and 
you should <a href="#" class="alert-link">know about it</a>.
+</div>
+  */
+  auto diag_element = make_div ("gcc-diagnostic");
+  diag_element->set_attr ("id", diag_id);
+  if (alert)
+    diag_element->set_attr ("class",
+                           get_pf_class_for_alert_div (diagnostic.kind));
+
+  xml::printer xp (*diag_element.get ());
+  const size_t depth_within_alert_div = 1;
+
+  gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
+
+  if (alert)
+    {
+      xp.push_tag_with_class ("span",
+                             get_pf_class_for_alert_icon (diagnostic.kind),
+                             true);
+      xp.add_text (" ");
+      xp.pop_tag ("span");
+    }
+
+  // The rest goes in the <div>...
+  gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
+
+  xp.push_tag_with_class ("div", "gcc-message", true);
+  std::string message_alert_id (diag_id + "-message");
+  xp.set_attr ("id", message_alert_id);
+  add_focus_id (message_alert_id);
 
-  xml::printer xp (*message_span.get ());
+  const size_t depth_within_message_div = depth_within_alert_div + 1;
+  gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
+
+  // Severity e.g. "warning: "
+  bool show_severity = true;
+  if (!alert)
+    show_severity = false;
+  if (show_severity)
+  {
+    xp.push_tag ("strong");
+    xp.add_text (_(get_diagnostic_kind_text (diagnostic.kind)));
+    xp.pop_tag ("strong");
+    xp.add_text (" ");
+  }
+
+  // Add the message itself:
   html_token_printer tok_printer (*xp.get_insertion_point ());
   m_printer->set_token_printer (&tok_printer);
   pp_output_formatted_text (m_printer, m_context.get_urlifier ());
   m_printer->set_token_printer (nullptr);
   pp_clear_output_area (m_printer);
-  diag_element->add_child (std::move (message_span));
 
+  // Add any metadata as a suffix to the message
   if (diagnostic.metadata)
     {
-      diag_element->add_text (" ");
-      diag_element->add_child
-       (make_element_for_metadata (*diagnostic.metadata));
+      xp.add_text (" ");
+      xp.append (make_element_for_metadata (*diagnostic.metadata));
     }
 
+  // Add any option as a suffix to the message
+
   label_text option_text = label_text::take
     (m_context.make_option_name (diagnostic.option_id,
                                 orig_diag_kind, diagnostic.kind));
@@ -603,7 +904,7 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
       label_text option_url = label_text::take
        (m_context.make_option_url (diagnostic.option_id));
 
-      diag_element->add_text (" ");
+      xp.add_text (" ");
       auto option_span = make_span ("gcc-option");
       option_span->add_text ("[");
       {
@@ -618,35 +919,110 @@ html_builder::make_element_for_diagnostic (const 
diagnostic_info &diagnostic,
          option_span->add_text (option_text.get ());
        option_span->add_text ("]");
       }
-      diag_element->add_child (std::move (option_span));
+      xp.append (std::move (option_span));
+    }
+
+  gcc_assert (xp.get_num_open_tags () == depth_within_message_div);
+
+  xp.pop_tag ("div");
+
+  gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
+
+  /* Show any logical location.  */
+  if (m_logical_loc_mgr)
+    if (auto client_data_hooks = m_context.get_client_data_hooks ())
+      if (auto logical_loc = client_data_hooks->get_current_logical_location 
())
+       if (logical_loc != m_last_logical_location)
+         {
+           enum logical_location_kind kind
+             = m_logical_loc_mgr->get_kind (logical_loc);;
+           if (const char *label = get_label_for_logical_location_kind (kind))
+             if (const char *name_with_scope
+                 = m_logical_loc_mgr->get_name_with_scope (logical_loc))
+               add_labelled_value (xp, "logical-location",
+                                   label, name_with_scope, true);
+           m_last_logical_location = logical_loc;
+         }
+
+  /* Show any physical location.  */
+  const expanded_location s
+    = diagnostic_expand_location (&diagnostic);
+  if (s != m_last_expanded_location
+      || alert)
+    {
+      if (s.file
+         && (s.file != m_last_expanded_location.file
+             || alert))
+       add_labelled_value (xp, "file", "File", s.file, false);
+      if (s.line)
+       {
+         add_labelled_value (xp, "line", "Line", std::to_string (s.line), 
false);
+         diagnostic_column_policy column_policy (m_context);
+         int converted_column = column_policy.converted_column (s);
+         if (converted_column >= 0)
+           add_labelled_value (xp, "column", "Column",
+                               std::to_string (converted_column),
+                               false);
+       }
+      if (s.file)
+       m_last_expanded_location = s;
     }
 
   /* Source (and fix-it hints).  */
   {
-    xml::printer xp (*diag_element);
-    m_context.m_last_location = UNKNOWN_LOCATION;
+    // TODO: m_context.m_last_location should be moved into the sink
+    location_t saved = m_context.m_last_location;
+    m_context.m_last_location = m_last_location;
     m_context.maybe_show_locus_as_html (*diagnostic.richloc,
                                        m_context.m_source_printing,
                                        diagnostic.kind,
                                        xp,
                                        nullptr,
                                        nullptr);
+    m_context.m_last_location = saved;
+    m_last_location = m_context.m_last_location;
   }
 
+  gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
+
   /* Execution path.  */
   if (auto path = diagnostic.richloc->get_path ())
     {
-      xml::printer xp (*diag_element);
+      xp.push_tag ("div");
+      xp.set_attr ("id", "execution-path");
+
+      xp.push_tag ("label", true);
+      const int num_events = path->num_events ();
+      pretty_printer pp;
+      pp_printf_n (&pp, num_events,
+                  "Execution path with %i event",
+                  "Execution path with %i events",
+                  num_events);
+      xp.add_text_from_pp (pp);
+      xp.pop_tag ("label");
+
       std::string event_id_prefix (diag_id + "-event-");
       html_path_label_writer event_label_writer (xp, *this,
                                                 event_id_prefix);
       diagnostic_source_print_policy dspp (m_context);
       print_path_as_html (xp, *path, m_context, &event_label_writer,
                          dspp);
+
+      xp.pop_tag ("div");
     }
 
+  gcc_assert (xp.get_num_open_tags () == depth_within_alert_div);
+
   if (auto patch_element = make_element_for_patch (diagnostic))
-    diag_element->add_child (std::move (patch_element));
+    {
+      xp.push_tag ("div");
+      xp.set_attr ("id", "suggested-fix");
+      xp.push_tag ("label", true);
+      xp.add_text ("Suggested fix");
+      xp.pop_tag ("label");
+      xp.append (std::move (patch_element));
+      xp.pop_tag ("div");
+    }
 
   return diag_element;
 }
@@ -1032,8 +1408,9 @@ test_simple_log ()
       "  </head>\n"
       "  <body>\n"
       "    <div class=\"gcc-diagnostic-list\">\n"
-      "      <div class=\"gcc-diagnostic\" id=\"gcc-diag-0\">\n"
-      "        <span class=\"gcc-message\" id=\"gcc-diag-0-message\">this is a 
test: `<span class=\"gcc-quoted-text\">foo</span>&apos;</span>\n"
+      "      <div class=\"alert alert-danger\" id=\"gcc-diag-0\">\n"
+      "        <span class=\"pficon pficon-error-circle-o\"> </span>\n"
+      "        <div class=\"gcc-message\" 
id=\"gcc-diag-0-message\"><strong>error: </strong> this is a test: `<span 
class=\"gcc-quoted-text\">foo</span>&apos;</div>\n"
       "      </div>\n"
       "    </div>\n"
       "  </body>\n"
diff --git a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py 
b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py
index 1d798939e729..b0b59d951ca8 100644
--- a/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py
+++ b/gcc/testsuite/gcc.dg/format/diagnostic-ranges-html.py
@@ -19,17 +19,20 @@ def test_message(html_tree):
     diag = get_diag_by_index(html_tree, 0)
     msg = get_message_within_diag(diag)
 
-    assert_tag(msg[0], 'span')
-    assert_class(msg[0], 'gcc-quoted-text')
-    assert_highlighted_text(msg[0][0], 'highlight-a', '%i')
-
+    assert_tag(msg[0], 'strong')
+    assert msg[0].text == 'warning: '
+    
     assert_tag(msg[1], 'span')
     assert_class(msg[1], 'gcc-quoted-text')
-    assert_highlighted_text(msg[1][0], 'highlight-a', 'int')
+    assert_highlighted_text(msg[1][0], 'highlight-a', '%i')
 
     assert_tag(msg[2], 'span')
     assert_class(msg[2], 'gcc-quoted-text')
-    assert_highlighted_text(msg[2][0], 'highlight-b', 'const char *')
+    assert_highlighted_text(msg[2][0], 'highlight-a', 'int')
+
+    assert_tag(msg[3], 'span')
+    assert_class(msg[3], 'gcc-quoted-text')
+    assert_highlighted_text(msg[3][0], 'highlight-b', 'const char *')
 
 def test_annotations(html_tree):
     """
diff --git a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py 
b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py
index 880fcc0231bc..3adaa52ee64d 100644
--- a/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py
+++ b/gcc/testsuite/gcc.dg/html-output/missing-semicolon.py
@@ -37,22 +37,68 @@ def test_basics(html_tree):
 
     diag = diag_list.find('xhtml:div', ns)
     assert diag is not None
-    assert diag.attrib['class'] == 'gcc-diagnostic'
+    assert diag.attrib['class'] == 'alert alert-danger'
+    assert diag.attrib['id'] == 'gcc-diag-0'
 
-    message = diag.find('xhtml:span', ns)
+    icon = diag.find('xhtml:span', ns)
+    assert icon.attrib['class'] == 'pficon pficon-error-circle-o'
+
+    # The message line:
+    message = diag.find("./xhtml:div[@class='gcc-message']", ns)
     assert message is not None
-    assert message.attrib['class'] == 'gcc-message'
-    assert message.text == "expected '"
-    assert message[0].tag == make_tag('span')
-    assert message[0].attrib['class'] == 'gcc-quoted-text'
-    assert message[0].text == ';'
-    assert message[0].tail == "' before '"
+    # <html:div xmlns:html="http://www.w3.org/1999/xhtml"; class="gcc-message" 
id="gcc-diag-0-message"><html:strong>error: </html:strong> expected '<html:span 
class="gcc-quoted-text">;</html:span>' before '<html:span 
class="gcc-quoted-text">}</html:span>' token</html:div>
+    assert message[0].tag == make_tag('strong')
+    assert message[0].text == 'error: '
+    assert message[0].tail == " expected '"
     assert message[1].tag == make_tag('span')
     assert message[1].attrib['class'] == 'gcc-quoted-text'
-    assert message[1].text == '}'
-    assert message[1].tail == "' token"
+    assert message[1].text == ';'
+    assert message[1].tail == "' before '"
+    assert message[2].tag == make_tag('span')
+    assert message[2].attrib['class'] == 'gcc-quoted-text'
+    assert message[2].text == '}'
+    assert message[2].tail == "' token"
+
+    # Logical location
+    logical_loc = diag.find("./xhtml:div[@id='logical-location']", ns)
+    assert logical_loc is not None
+    assert len(logical_loc) == 2
+    assert logical_loc[0].tag == make_tag('span')
+    assert logical_loc[0].text == 'Function '
+    assert logical_loc[1].tag == make_tag('span')
+    assert logical_loc[1].text == 'missing_semicolon'
+    assert logical_loc[1].attrib['class'] == 'gcc-quoted-text'
+
+    # Physical location
+    file_ = diag.find("./xhtml:div[@id='file']", ns)
+    assert file_ is not None
+    assert len(file_) == 2
+    assert file_[0].tag == make_tag('span')
+    assert file_[0].text == 'File '
+    assert file_[1].tag == make_tag('span')
+    assert 
file_[1].text.endswith('gcc/testsuite/gcc.dg/html-output/missing-semicolon.c')
+
+    line = diag.find("./xhtml:div[@id='line']", ns)
+    assert line is not None
+    assert len(line) == 2
+    assert line[0].tag == make_tag('span')
+    assert line[0].text == 'Line '
+    assert line[1].tag == make_tag('span')
+    assert line[1].text == '8'
+
+    column = diag.find("./xhtml:div[@id='column']", ns)
+    assert column is not None
+    assert len(column) == 2
+    assert column[0].tag == make_tag('span')
+    assert column[0].text == 'Column '
+    assert column[1].tag == make_tag('span')
+    assert column[1].text == '12'
 
-    pre = diag.find('xhtml:pre', ns)
+    # Suggested fix
+    fix = diag.find("./xhtml:div[@id='suggested-fix']", ns)
+    label = fix.find('xhtml:label', ns)
+    assert label.text == "Suggested fix"
+    pre = fix.find('xhtml:pre', ns)
     assert pre is not None
     assert pre.attrib['class'] == 'gcc-generated-patch'
     assert pre.text.startswith('--- ')
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py
index b4c75b230ee2..67fb241d179c 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-metadata-html.py
@@ -21,10 +21,24 @@ def test_metadata(html_tree):
 
     diag = diag_list.find('xhtml:div', ns)
     assert diag is not None
-    assert diag.attrib['class'] == 'gcc-diagnostic'
+    assert diag.attrib['class'] == 'alert alert-warning'
 
-    spans = diag.findall('xhtml:span', ns)
-    metadata = spans[1]
+    icon = diag.find('xhtml:span', ns)
+    assert icon.attrib['class'] == 'pficon pficon-warning-triangle-o'
+
+    message = diag.find("./xhtml:div[@class='gcc-message']", ns)
+    assert message.attrib['id'] == 'gcc-diag-0-message'
+
+    assert message[0].tag == make_tag('strong')
+    assert message[0].text == 'warning: '
+    assert message[0].tail == " never use '"
+
+    assert message[1].tag == make_tag('span')
+    assert message[1].attrib['class'] == 'gcc-quoted-text'
+    assert message[1].text == 'gets'
+    assert message[1].tail == "' "
+    
+    metadata = message[2]
     assert metadata.attrib['class'] == 'gcc-metadata'
     assert metadata[0].tag == make_tag('span')
     assert metadata[0].attrib['class'] == 'gcc-metadata-item'
@@ -57,21 +71,3 @@ def test_metadata(html_tree):
     annotation_tr = rows[1]
     assert_annotation_line(annotation_tr,
                            '  ^~~~~~~~~~')
-
-# For reference, here's the generated HTML:
-"""
-  <body>
-    <div class="gcc-diagnostic-list">
-      <div class="gcc-diagnostic">
-        <span class="gcc-message">never use &apos;<span 
class="gcc-quoted-text">gets</span>&apos;</span> 
-        <span class="gcc-metadata"><span class="gcc-metadata-item">[<a 
href="https://cwe.mitre.org/data/definitions/242.html";>CWE-242</a>]</span><span 
class="gcc-metadata-item">[<a 
href="https://example.com/";>STR34-C</a>]</span></span><table class="locus">
-<tbody class="line-span">
-<tr><td class="linenum">   10</td> <td class="source">  gets (buf);</td></tr>
-<tr><td class="linenum"/><td class="annotation">  ^~~~~~~~~~</td></tr>
-</tbody>
-</table>
-
-      </div>
-    </div>
-  </body>
-"""
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py
index 59bee247103f..f0fed45626ef 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-2.py
@@ -21,9 +21,16 @@ def test_paths(html_tree):
 
     diag = diag_list.find('xhtml:div', ns)
     assert diag is not None
-    assert diag.attrib['class'] == 'gcc-diagnostic'
+    assert diag.attrib['class'] == 'alert alert-danger'
+    assert diag.attrib['id'] == 'gcc-diag-0'
 
-    event_ranges = diag.find('xhtml:div', ns)
+    exec_path = diag.find("./xhtml:div[@id='execution-path']", ns)
+    assert exec_path is not None
+
+    label = exec_path.find('xhtml:label', ns)
+    assert label.text == 'Execution path with 3 events'
+    
+    event_ranges = exec_path.find('xhtml:div', ns)
     assert_class(event_ranges, 'event-ranges')
 
     frame_margin = event_ranges.find('xhtml:table', ns)
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py
index e738729a3472..d2bc67c52a29 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-paths-4.py
@@ -25,7 +25,13 @@ def test_paths(html_tree):
     assert_annotation_line(annotation_tr,
                            '  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
 
-    event_ranges = diag.find('xhtml:div', ns)
+    exec_path = diag.find("./xhtml:div[@id='execution-path']", ns)
+    assert exec_path is not None
+
+    label = exec_path.find('xhtml:label', ns)
+    assert label.text == 'Execution path with 9 events'
+    
+    event_ranges = exec_path.find('xhtml:div', ns)
     assert_class(event_ranges, 'event-ranges')
 
     test_frame_margin = event_ranges.find('xhtml:table', ns)
@@ -41,150 +47,3 @@ def test_paths(html_tree):
     test_frame = tds[1]
     assert_frame(test_frame, 'test')
     assert_event_range_with_margin(test_frame[1])
-
-# For reference, generated HTML looks like this:
-"""
-<table class="stack-frame-with-margin"><tr>
-  <td class="interprocmargin" style="padding-left: 100px"/>
-  <td class="stack-frame">
-<div class="frame-funcname"><span>test</span></div><table 
class="event-range-with-margin"><tr>
-  <td class="event-range">
-    <div class="events-hdr"><span class="funcname">test</span>: <span 
class="event-ids">events 1-2</span></div>
-<table class="locus">
-<tbody class="line-span">
-<tr><td class="linenum">   27</td> <td class="source">{</td></tr>
-<tr><td class="linenum"/><td class="annotation">^</td></tr>
-<tr><td class="linenum"/><td class="annotation">|</td></tr>
-<tr><td class="linenum"/><td class="annotation">(1) entering 'test'</td></tr>
-<tr><td class="linenum">   28</td> <td class="source">  register_handler 
();</td></tr>
-<tr><td class="linenum"/><td class="annotation">  ~~~~~~~~~~~~~~~~~~~</td></tr>
-<tr><td class="linenum"/><td class="annotation">  |</td></tr>
-<tr><td class="linenum"/><td class="annotation">  (2) calling 
'register_handler'</td></tr>
-</tbody>
-</table>
-</td></tr></table>
-<div class="between-ranges-call">
-  <svg height="30" width="150">
-    <defs>
-      <marker id="arrowhead" markerWidth="10" markerHeight="7"
-              refX="0" refY="3.5" orient="auto" stroke="#0088ce" 
fill="#0088ce">
-      <polygon points="0 0, 10 3.5, 0 7"/>
-      </marker>
-    </defs>
-    <polyline points="20,0 20,10 120,10 120,20"
-              style="fill:none;stroke: #0088ce"
-              marker-end="url(#arrowhead)"/>
-  </svg>
-</div>
-
-<table class="stack-frame-with-margin"><tr>
-  <td class="interprocmargin" style="padding-left: 100px"/>
-  <td class="stack-frame">
-<div class="frame-funcname"><span>register_handler</span></div><table 
class="event-range-with-margin"><tr>
-  <td class="event-range">
-    <div class="events-hdr"><span class="funcname">register_handler</span>: 
<span class="event-ids">events 3-4</span></div>
-<table class="locus">
-<tbody class="line-span">
-<tr><td class="linenum">   22</td> <td class="source">{</td></tr>
-<tr><td class="linenum"/><td class="annotation">^</td></tr>
-<tr><td class="linenum"/><td class="annotation">|</td></tr>
-<tr><td class="linenum"/><td class="annotation">(3) entering 
'register_handler'</td></tr>
-<tr><td class="linenum">   23</td> <td class="source">  signal(SIGINT, 
int_handler);</td></tr>
-<tr><td class="linenum"/><td class="annotation">  
~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
-<tr><td class="linenum"/><td class="annotation">  |</td></tr>
-<tr><td class="linenum"/><td class="annotation">  (4) registering 
'int_handler' as signal handler</td></tr>
-</tbody>
-</table>
-</td></tr></table>
-</td></tr></table>
-</td></tr></table>
-<div class="between-ranges-return">
-  <svg height="30" width="250">
-    <defs>
-      <marker id="arrowhead" markerWidth="10" markerHeight="7"
-              refX="0" refY="3.5" orient="auto" stroke="#0088ce" 
fill="#0088ce">
-      <polygon points="0 0, 10 3.5, 0 7"/>
-      </marker>
-    </defs>
-    <polyline points="220,0 220,10 20,10 20,20"
-              style="fill:none;stroke: #0088ce"
-              marker-end="url(#arrowhead)"/>
-  </svg>
-</div>
-
-<table class="event-range-with-margin"><tr>
-  <td class="event-range">
-    <div class="events-hdr"><span class="event-ids">event 5</span></div>
- (5): later on, when the signal is delivered to the process
-</td></tr></table>
-<div class="between-ranges-call">
-  <svg height="30" width="150">
-    <defs>
-      <marker id="arrowhead" markerWidth="10" markerHeight="7"
-              refX="0" refY="3.5" orient="auto" stroke="#0088ce" 
fill="#0088ce">
-      <polygon points="0 0, 10 3.5, 0 7"/>
-      </marker>
-    </defs>
-    <polyline points="20,0 20,10 120,10 120,20"
-              style="fill:none;stroke: #0088ce"
-              marker-end="url(#arrowhead)"/>
-  </svg>
-</div>
-
-<table class="stack-frame-with-margin"><tr>
-  <td class="interprocmargin" style="padding-left: 100px"/>
-  <td class="stack-frame">
-<div class="frame-funcname"><span>int_handler</span></div><table 
class="event-range-with-margin"><tr>
-  <td class="event-range">
-    <div class="events-hdr"><span class="funcname">int_handler</span>: <span 
class="event-ids">events 6-7</span></div>
-<table class="locus">
-<tbody class="line-span">
-<tr><td class="linenum">   17</td> <td class="source">{</td></tr>
-<tr><td class="linenum"/><td class="annotation">^</td></tr>
-<tr><td class="linenum"/><td class="annotation">|</td></tr>
-<tr><td class="linenum"/><td class="annotation">(6) entering 
'int_handler'</td></tr>
-<tr><td class="linenum">   18</td> <td class="source">  custom_logger("got 
signal");</td></tr>
-<tr><td class="linenum"/><td class="annotation">  
~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
-<tr><td class="linenum"/><td class="annotation">  |</td></tr>
-<tr><td class="linenum"/><td class="annotation">  (7) calling 
'custom_logger'</td></tr>
-</tbody>
-</table>
-</td></tr></table>
-<div class="between-ranges-call">
-  <svg height="30" width="150">
-    <defs>
-      <marker id="arrowhead" markerWidth="10" markerHeight="7"
-              refX="0" refY="3.5" orient="auto" stroke="#0088ce" 
fill="#0088ce">
-      <polygon points="0 0, 10 3.5, 0 7"/>
-      </marker>
-    </defs>
-    <polyline points="20,0 20,10 120,10 120,20"
-              style="fill:none;stroke: #0088ce"
-              marker-end="url(#arrowhead)"/>
-  </svg>
-</div>
-
-<table class="stack-frame-with-margin"><tr>
-  <td class="interprocmargin" style="padding-left: 100px"/>
-  <td class="stack-frame">
-<div class="frame-funcname"><span>custom_logger</span></div><table 
class="event-range-with-margin"><tr>
-  <td class="event-range">
-    <div class="events-hdr"><span class="funcname">custom_logger</span>: <span 
class="event-ids">events 8-9</span></div>
-<table class="locus">
-<tbody class="line-span">
-<tr><td class="linenum">   12</td> <td class="source">{</td></tr>
-<tr><td class="linenum"/><td class="annotation">^</td></tr>
-<tr><td class="linenum"/><td class="annotation">|</td></tr>
-<tr><td class="linenum"/><td class="annotation">(8) entering 
'custom_logger'</td></tr>
-<tr><td class="linenum">   13</td> <td class="source">  fprintf(stderr, "LOG: 
%s", msg); /* { dg-warning "call to 'fprintf' from within signal handler" } 
*/</td></tr>
-<tr><td class="linenum"/><td class="annotation">  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~</td></tr>
-<tr><td class="linenum"/><td class="annotation">  |</td></tr>
-<tr><td class="linenum"/><td class="annotation">  (9) calling 
'fprintf'</td></tr>
-</tbody>
-</table>
-</td></tr></table>
-</td></tr></table>
-</td></tr></table>
-
-</div>
-    """
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py 
b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py
index d963b29830b5..aca1b6cdaccd 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic-test-show-locus.py
@@ -46,7 +46,8 @@ def test_very_wide_line(html_tree):
 def test_fixit_insert(html_tree):
     diag = get_diag_by_index(html_tree, 3)
     msg = get_message_within_diag(diag)
-    assert msg.text == 'example of insertion hints'
+    assert msg[0].text == 'warning: '
+    assert msg[0].tail == ' example of insertion hints'
 
     src = get_locus_within_diag(diag)
 
@@ -62,7 +63,8 @@ def test_fixit_insert(html_tree):
 def test_fixit_remove(html_tree):
     diag = get_diag_by_index(html_tree, 4)
     msg = get_message_within_diag(diag)
-    assert msg.text == 'example of a removal hint'
+    assert msg[0].text == 'warning: '
+    assert msg[0].tail == ' example of a removal hint'
 
     src = get_locus_within_diag(diag)
 
@@ -78,7 +80,8 @@ def test_fixit_remove(html_tree):
 def test_fixit_replace(html_tree):
     diag = get_diag_by_index(html_tree, 5)
     msg = get_message_within_diag(diag)
-    assert msg.text == 'example of a replacement hint'
+    assert msg[0].text == 'warning: '
+    assert msg[0].tail == ' example of a replacement hint'
 
     src = get_locus_within_diag(diag)
 
@@ -94,7 +97,8 @@ def test_fixit_replace(html_tree):
 def test_fixit_insert_newline(html_tree):
     diag = get_diag_by_index(html_tree, 6)
     msg = get_message_within_diag(diag)
-    assert msg.text == 'example of newline insertion hint'
+    assert msg[0].text == 'warning: '
+    assert msg[0].tail == ' example of newline insertion hint'
 
     src = get_locus_within_diag(diag)
 
diff --git a/gcc/testsuite/lib/htmltest.py b/gcc/testsuite/lib/htmltest.py
index 8e42a8c2a17e..49ed4b035820 100644
--- a/gcc/testsuite/lib/htmltest.py
+++ b/gcc/testsuite/lib/htmltest.py
@@ -83,14 +83,11 @@ def get_diag_by_index(html_tree, index):
     assert diag_list is not None
     assert_class(diag_list, 'gcc-diagnostic-list')
 
-    diags = diag_list.findall('xhtml:div', ns)
-    diag = diags[index]
-    assert_class(diag, 'gcc-diagnostic')
+    diag = diag_list.find(f"xhtml:div[@id='gcc-diag-{index}']", ns)
     return diag
 
 def get_message_within_diag(diag_element):
-    msg = diag_element.find('xhtml:span', ns)
-    assert_class(msg, 'gcc-message')
+    msg = diag_element.find("xhtml:div[@class='gcc-message']", ns)
     return msg
 
 def get_locus_within_diag(diag_element):
diff --git a/libcpp/include/line-map.h b/libcpp/include/line-map.h
index 75cc1ad2c275..83ebbde474df 100644
--- a/libcpp/include/line-map.h
+++ b/libcpp/include/line-map.h
@@ -1280,7 +1280,7 @@ linemap_location_before_p (const line_maps *set,
   return linemap_compare_locations (set, loc_a, loc_b) >= 0;
 }
 
-typedef struct
+struct expanded_location
 {
   /* The name of the source file involved.  */
   const char *file;
@@ -1294,7 +1294,18 @@ typedef struct
 
   /* In a system header?. */
   bool sysp;
-} expanded_location;
+};
+
+extern bool
+operator== (const expanded_location &a,
+           const expanded_location &b);
+inline bool
+operator!= (const expanded_location &a,
+           const expanded_location &b)
+{
+  return !(a == b);
+}
+
 
 /* This is enum is used by the function linemap_resolve_location
    below.  The meaning of the values is explained in the comment of
diff --git a/libcpp/line-map.cc b/libcpp/line-map.cc
index cf6557117c81..284af5781dcf 100644
--- a/libcpp/line-map.cc
+++ b/libcpp/line-map.cc
@@ -1949,6 +1949,28 @@ linemap_expand_location (const line_maps *set,
   return xloc;
 }
 
+bool
+operator== (const expanded_location &a,
+           const expanded_location &b)
+{
+  /* "file" can be null; for them to be equal they must both
+     have either null or nonnull values, and if non-null
+     they must compare as equal.  */
+  if ((a.file == nullptr) != (b.file == nullptr))
+    return false;
+  if (a.file && strcmp (a.file, b.file))
+    return false;
+
+  if (a.line != b.line)
+    return false;
+  if (a.column != b.column)
+    return false;
+  if (a.data != b.data)
+    return false;
+  if (a.sysp != b.sysp)
+    return false;
+  return true;
+}
 
 /* Dump line map at index IX in line table SET to STREAM.  If STREAM
    is NULL, use stderr.  IS_MACRO is true if the caller wants to
-- 
2.26.3

Reply via email to