diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 7d752acd430c..e7e371fd26f6 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "debug.h"
 #include "tree-vector-builder.h"
 #include "vec-perm-indices.h"
+#include "tree-pretty-print-markup.h"
 
 cpp_reader *parse_in;		/* Declared in c-pragma.h.  */
 
@@ -2961,9 +2962,11 @@ binary_op_error (rich_location *richloc, enum tree_code code,
     default:
       gcc_unreachable ();
     }
+  pp_markup::element_quoted_type element_0 (type0, highlight_colors::lhs);
+  pp_markup::element_quoted_type element_1 (type1, highlight_colors::rhs);
   error_at (richloc,
-	    "invalid operands to binary %s (have %qT and %qT)",
-	    opname, type0, type1);
+	    "invalid operands to binary %s (have %e and %e)",
+	    opname, &element_0, &element_1);
 }
 
 /* Given an expression as a tree, return its original type.  Do this
@@ -6158,11 +6161,15 @@ attribute_fallthrough_p (tree attr)
 
    The arguments in ARGARRAY may not have been folded yet (e.g. for C++,
    to preserve location wrappers); checks that require folded arguments
-   should call fold_for_warn on them.  */
+   should call fold_for_warn on them.
+
+   Use the frontend-supplied COMP_TYPES when determining if
+   one type is a subclass of another.  */
 
 bool
 check_function_arguments (location_t loc, const_tree fndecl, const_tree fntype,
-			  int nargs, tree *argarray, vec<location_t> *arglocs)
+			  int nargs, tree *argarray, vec<location_t> *arglocs,
+			  bool (*comp_types) (tree, tree))
 {
   bool warned_p = false;
 
@@ -6179,7 +6186,7 @@ check_function_arguments (location_t loc, const_tree fndecl, const_tree fntype,
 
   if (warn_format || warn_suggest_attribute_format)
     check_function_format (fndecl ? fndecl : fntype, TYPE_ATTRIBUTES (fntype), nargs,
-			   argarray, arglocs);
+			   argarray, arglocs, comp_types);
 
   if (warn_format)
     check_function_sentinel (fntype, nargs, argarray);
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 48c89b603bcd..ccaea27c2b9f 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -848,7 +848,8 @@ extern tree fname_decl (location_t, unsigned, tree);
 
 extern int check_user_alignment (const_tree, bool, bool);
 extern bool check_function_arguments (location_t loc, const_tree, const_tree,
-				      int, tree *, vec<location_t> *);
+				      int, tree *, vec<location_t> *,
+				      bool (*comp_types) (tree, tree));
 extern void check_function_arguments_recurse (void (*)
 					      (void *, tree,
 					       unsigned HOST_WIDE_INT),
@@ -858,7 +859,8 @@ extern void check_function_arguments_recurse (void (*)
 extern bool check_builtin_function_arguments (location_t, vec<location_t>,
 					      tree, tree, int, tree *);
 extern void check_function_format (const_tree, tree, int, tree *,
-				   vec<location_t> *);
+				   vec<location_t> *,
+				   bool (*comp_types) (tree, tree));
 extern bool attribute_fallthrough_p (tree);
 extern tree handle_format_attribute (tree *, tree, tree, int, bool *);
 extern tree handle_format_arg_attribute (tree *, tree, tree, int, bool *);
diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
index 5bfd2fc4469e..07b91a1c7a1d 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -36,6 +36,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "builtins.h"
 #include "attribs.h"
 #include "c-family/c-type-mismatch.h"
+#include "tree-pretty-print-markup.h"
 
 /* Handle attributes associated with format checking.  */
 
@@ -66,6 +67,7 @@ struct function_format_info
 /* Initialized in init_dynamic_diag_info.  */
 static GTY(()) tree local_tree_type_node;
 static GTY(()) tree local_event_ptr_node;
+static GTY(()) tree local_pp_element_ptr_node;
 static GTY(()) tree local_gimple_ptr_node;
 static GTY(()) tree local_cgraph_node_ptr_node;
 static GTY(()) tree locus;
@@ -768,6 +770,7 @@ static const format_char_info asm_fprintf_char_table[] =
   { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "q",  "c",  NULL }, \
   { "r",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "",    "//cR",   NULL }, \
   { "@",   1, STD_C89, { T_EVENT_PTR,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"",   NULL }, \
+  { "e",   1, STD_C89, { T_PP_ELEMENT_PTR,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN  }, "", "\"", NULL }, \
   { "<",   0, STD_C89, NOARGUMENTS, "",      "<",   NULL }, \
   { ">",   0, STD_C89, NOARGUMENTS, "",      ">",   NULL }, \
   { "'" ,  0, STD_C89, NOARGUMENTS, "",      "",    NULL }, \
@@ -1067,11 +1070,13 @@ struct format_check_context
   format_check_context (format_check_results *res,
 			function_format_info *info,
 			tree params,
-			vec<location_t> *arglocs)
+			vec<location_t> *arglocs,
+			bool (*comp_types) (tree, tree))
   : m_res (res),
     m_info (info),
     m_params (params),
-    m_arglocs (arglocs)
+    m_arglocs (arglocs),
+    m_comp_types (comp_types)
   {
   }
 
@@ -1079,6 +1084,7 @@ struct format_check_context
   function_format_info *m_info;
   tree m_params;
   vec<location_t> *m_arglocs;
+  bool (*m_comp_types) (tree, tree);
 };
 
 /* Return the format name (as specified in the original table) for the format
@@ -1102,7 +1108,8 @@ format_flags (int format_num)
 }
 
 static void check_format_info (function_format_info *, tree,
-			       vec<location_t> *);
+			       vec<location_t> *,
+			       bool (*comp_types) (tree, tree));
 static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
 static void check_format_info_main (format_check_results *,
 				    function_format_info *, const char *,
@@ -1110,7 +1117,8 @@ static void check_format_info_main (format_check_results *,
 				    int, tree,
 				    unsigned HOST_WIDE_INT,
 				    object_allocator<format_wanted_type> &,
-				    vec<location_t> *);
+				    vec<location_t> *,
+				    bool (*comp_types) (tree, tree));
 
 static void init_dollar_format_checking (int, tree);
 static int maybe_read_dollar_number (const char **, int,
@@ -1126,7 +1134,8 @@ static void check_format_types (const substring_loc &fmt_loc,
 				const format_kind_info *fki,
 				int offset_to_type_start,
 				char conversion_char,
-				vec<location_t> *arglocs);
+				vec<location_t> *arglocs,
+				bool (*comp_types) (tree, tree));
 static void format_type_warning (const substring_loc &fmt_loc,
 				 location_t param_loc,
 				 format_wanted_type *, tree,
@@ -1197,7 +1206,8 @@ decode_format_type (const char *s, bool *is_raw /* = NULL */)
 
 void
 check_function_format (const_tree fn, tree attrs, int nargs,
-		       tree *argarray, vec<location_t> *arglocs)
+		       tree *argarray, vec<location_t> *arglocs,
+		       bool (*comp_types) (tree, tree))
 {
   tree a;
 
@@ -1254,7 +1264,7 @@ check_function_format (const_tree fn, tree attrs, int nargs,
 	      int i;
 	      for (i = nargs - 1; i >= 0; i--)
 		params = tree_cons (NULL_TREE, argarray[i], params);
-	      check_format_info (&info, params, arglocs);
+	      check_format_info (&info, params, arglocs, comp_types);
 	    }
 
 	  /* Attempt to detect whether the current function might benefit
@@ -1562,7 +1572,8 @@ get_flag_spec (const format_flag_spec *spec, int flag, const char *predicates)
 
 static void
 check_format_info (function_format_info *info, tree params,
-		   vec<location_t> *arglocs)
+		   vec<location_t> *arglocs,
+		   bool (*comp_types) (tree, tree))
 {
   unsigned HOST_WIDE_INT arg_num;
   tree format_tree;
@@ -1583,7 +1594,7 @@ check_format_info (function_format_info *info, tree params,
 
   format_check_results res (input_location);
 
-  format_check_context format_ctx (&res, info, params, arglocs);
+  format_check_context format_ctx (&res, info, params, arglocs, comp_types);
 
   check_function_arguments_recurse (check_format_arg, &format_ctx,
 				    format_tree, arg_num, OPT_Wformat_);
@@ -1674,6 +1685,7 @@ check_format_arg (void *ctx, tree format_tree,
   function_format_info *info = format_ctx->m_info;
   tree params = format_ctx->m_params;
   vec<location_t> *arglocs = format_ctx->m_arglocs;
+  bool (*comp_types) (tree, tree) = format_ctx->m_comp_types;
 
   int format_length;
   HOST_WIDE_INT offset;
@@ -1865,7 +1877,8 @@ check_format_arg (void *ctx, tree format_tree,
   res->number_other++;
   object_allocator <format_wanted_type> fwt_pool ("format_wanted_type pool");
   check_format_info_main (res, info, format_chars, fmt_param_loc, format_tree,
-			  format_length, params, arg_num, fwt_pool, arglocs);
+			  format_length, params, arg_num, fwt_pool, arglocs,
+			  comp_types);
 }
 
 /* Support class for argument_parser and check_format_info_main.
@@ -1932,7 +1945,8 @@ class argument_parser
 		   location_t format_string_loc, flag_chars_t &flag_chars,
 		   int &has_operand_number, tree first_fillin_param,
 		   object_allocator <format_wanted_type> &fwt_pool_,
-		   vec<location_t> *arglocs);
+		   vec<location_t> *arglocs,
+		   bool (*comp_types) (tree, tree));
 
   bool read_any_dollar ();
 
@@ -2012,6 +2026,7 @@ class argument_parser
   format_wanted_type *first_wanted_type;
   format_wanted_type *last_wanted_type;
   vec<location_t> *arglocs;
+  bool (*m_comp_types) (tree, tree);
 };
 
 /* flag_chars_t's constructor.  */
@@ -2163,7 +2178,8 @@ argument_parser (function_format_info *info_, const char *&format_chars_,
 		 int &has_operand_number_,
 		 tree first_fillin_param_,
 		 object_allocator <format_wanted_type> &fwt_pool_,
-		 vec<location_t> *arglocs_)
+		 vec<location_t> *arglocs_,
+		 bool (*comp_types) (tree, tree))
 : info (info_),
   fki (&format_types[info->format_type]),
   flag_specs (fki->flag_specs),
@@ -2180,7 +2196,8 @@ argument_parser (function_format_info *info_, const char *&format_chars_,
   first_fillin_param (first_fillin_param_),
   first_wanted_type (NULL),
   last_wanted_type (NULL),
-  arglocs (arglocs_)
+  arglocs (arglocs_),
+  m_comp_types (comp_types)
 {
 }
 
@@ -2903,7 +2920,7 @@ check_argument_type (const format_char_info *fci,
       ptrdiff_t offset_to_type_start = type_start - orig_format_chars;
       check_format_types (fmt_loc, first_wanted_type, fki,
 			  offset_to_type_start,
-			  conversion_char, arglocs);
+			  conversion_char, arglocs, m_comp_types);
     }
 
   return true;
@@ -3831,7 +3848,8 @@ check_format_info_main (format_check_results *res,
 			int format_length, tree params,
 			unsigned HOST_WIDE_INT arg_num,
 			object_allocator <format_wanted_type> &fwt_pool,
-			vec<location_t> *arglocs)
+			vec<location_t> *arglocs,
+			bool (*comp_types) (tree, tree))
 {
   const char * const orig_format_chars = format_chars;
   const tree first_fillin_param = params;
@@ -3898,7 +3916,8 @@ check_format_info_main (format_check_results *res,
       argument_parser arg_parser (info, format_chars, format_string_cst,
 				  orig_format_chars, format_string_loc,
 				  flag_chars, has_operand_number,
-				  first_fillin_param, fwt_pool, arglocs);
+				  first_fillin_param, fwt_pool, arglocs,
+				  comp_types);
 
       if (!arg_parser.read_any_dollar ())
 	return;
@@ -4075,6 +4094,28 @@ check_format_info_main (format_check_results *res,
 			    OPT_Wformat_, "unterminated color directive");
 }
 
+/* Special-case to support inheritance for %e.
+   Return true for the case where we have %e with a valid
+   pointer to a pp_element or pp_element subclass; false
+   otherwise.  */
+
+static bool
+handle_subclass_of_pp_element_p (format_wanted_type *types,
+				 bool (*comp_types) (tree, tree))
+{
+  if (types->wanted_type != local_pp_element_ptr_node)
+    return false;
+
+  tree param_type = TREE_TYPE (types->param);
+  if (param_type == error_mark_node)
+    return false;
+
+  if (comp_types (types->wanted_type, param_type))
+    return true;
+
+  return false;
+}
+
 /* Check the argument types from a single format conversion (possibly
    including width and precision arguments).
 
@@ -4128,7 +4169,8 @@ check_format_types (const substring_loc &fmt_loc,
 		    format_wanted_type *types, const format_kind_info *fki,
 		    int offset_to_type_start,
 		    char conversion_char,
-		    vec<location_t> *arglocs)
+		    vec<location_t> *arglocs,
+		    bool (*comp_types) (tree, tree))
 {
   for (; types != 0; types = types->next)
     {
@@ -4150,6 +4192,9 @@ check_format_types (const substring_loc &fmt_loc,
       if (types->pointer_count == 0)
 	wanted_type = lang_hooks.types.type_promotes_to (wanted_type);
 
+      if (handle_subclass_of_pp_element_p (types, comp_types))
+	continue;
+
       wanted_type = TYPE_MAIN_VARIANT (wanted_type);
 
       cur_param = types->param;
@@ -4693,6 +4738,85 @@ class range_label_for_format_type_mismatch
   int m_pointer_count;
 };
 
+/* Subclass of pp_element for text describing part of a format string.  */
+
+class element_format_substring : public pp_element
+{
+public:
+  element_format_substring (const char *highlight_color,
+			    const char *prefix,
+			    int format_length,
+			    const char *format_start)
+  : m_highlight_color (highlight_color),
+    m_prefix (prefix),
+    m_format_length (format_length),
+    m_format_start (format_start)
+  {
+  }
+
+  void add_to_phase_2 (pp_markup::context &ctxt) final override
+  {
+    ctxt.begin_quote ();
+    ctxt.begin_highlight_color (m_highlight_color);
+    pp_string (&ctxt.m_pp, m_prefix);
+    pp_string_n (&ctxt.m_pp, m_format_start, m_format_length);
+    ctxt.end_highlight_color ();
+    ctxt.end_quote ();
+  }
+
+private:
+  const char *m_highlight_color;
+  const char *m_prefix;
+  int m_format_length;
+  const char *m_format_start;
+};
+
+/* Subclass of pp_element for text describing an optional WANTED_TYPE_NAME
+   with a fallback TYPE, part of an expected type with some number of
+   indirections.  */
+
+class element_expected_type_with_indirection
+  : public pp_markup::element_expected_type
+{
+public:
+  element_expected_type_with_indirection (tree wanted_type,
+					  const char *wanted_type_name,
+					  int pointer_count)
+  : pp_markup::element_expected_type (wanted_type),
+    m_wanted_type_name (wanted_type_name),
+    m_pointer_count (pointer_count)
+  {
+    // m_wanted_type_name can be nullptr
+  }
+
+  void add_to_phase_2 (pp_markup::context &ctxt) final override
+  {
+    indirection_suffix suffix (m_pointer_count);
+    char *indirection_buf = (char *) alloca (suffix.get_buffer_size ());
+    suffix.fill_buffer (indirection_buf);
+
+    ctxt.begin_quote ();
+    ctxt.begin_highlight_color (highlight_colors::expected);
+    if (m_wanted_type_name)
+      pp_string (&ctxt.m_pp, m_wanted_type_name);
+    else
+      {
+	/* Any trailing quotes should be printed within the quoted.  */
+	ctxt.m_quoted = false;
+	print_type (ctxt);
+	ctxt.m_quoted = true;
+      }
+    pp_string (&ctxt.m_pp, indirection_buf);
+    ctxt.end_highlight_color ();
+    ctxt.end_quote ();
+  }
+
+private:
+  const char *m_wanted_type_name;
+  tree m_wanted_type;
+  int m_pointer_count;
+};
+
 /* Give a warning about a format argument of different type from that expected.
    The range of the diagnostic is taken from WHOLE_FMT_LOC; the caret location
    is based on the location of the char at TYPE->offset_loc.
@@ -4753,10 +4877,6 @@ format_type_warning (const substring_loc &whole_fmt_loc,
 		  lang_hooks.decl_printable_name (TYPE_NAME (arg_type), 2)))
     arg_type = TYPE_MAIN_VARIANT (arg_type);
 
-  indirection_suffix suffix (pointer_count);
-  char *p = (char *) alloca (suffix.get_buffer_size ());
-  suffix.fill_buffer (p);
-
   /* WHOLE_FMT_LOC has the caret at the end of the range.
      Set the caret to be at the offset from TYPE.  Subtract one
      from the offset for the same reason as in format_warning_at_char.  */
@@ -4774,45 +4894,26 @@ format_type_warning (const substring_loc &whole_fmt_loc,
 			       offset_to_type_start, conversion_char);
   format_string_diagnostic_t diag (fmt_loc, &fmt_label, param_loc, &param_label,
 				   corrected_substring);
-  if (wanted_type_name)
-    {
-      if (arg_type)
-	diag.emit_warning
-	  (OPT_Wformat_,
-	   "%s %<%s%.*s%> expects argument of type %<%s%s%>, "
-	   "but argument %d has type %qT",
-	   gettext (kind_descriptions[kind]),
-	   (kind == CF_KIND_FORMAT ? "%" : ""),
-	   format_length, format_start,
-	   wanted_type_name, p, arg_num, arg_type);
-      else
-	diag.emit_warning
-	  (OPT_Wformat_,
-	   "%s %<%s%.*s%> expects a matching %<%s%s%> argument",
-	   gettext (kind_descriptions[kind]),
-	   (kind == CF_KIND_FORMAT ? "%" : ""),
-	   format_length, format_start, wanted_type_name, p);
-    }
+  element_format_substring elem_format_substring
+    (format_string_diagnostic_t::highlight_color_format_string,
+     (kind == CF_KIND_FORMAT ? "%" : ""),
+     format_length, format_start);
+  pp_markup::element_actual_type elem_actual_param_type (arg_type);
+  element_expected_type_with_indirection
+    elem_expected_type (wanted_type, wanted_type_name, pointer_count);
+  if (arg_type)
+    diag.emit_warning (OPT_Wformat_,
+		       "%s %e expects argument of type %e, "
+		       "but argument %d has type %e",
+		       gettext (kind_descriptions[kind]),
+		       &elem_format_substring, &elem_expected_type,
+		       arg_num, &elem_actual_param_type);
   else
-    {
-      if (arg_type)
-	diag.emit_warning
-	  (OPT_Wformat_,
-	   "%s %<%s%.*s%> expects argument of type %<%T%s%>, "
-	   "but argument %d has type %qT",
-	   gettext (kind_descriptions[kind]),
-	   (kind == CF_KIND_FORMAT ? "%" : ""),
-	   format_length, format_start,
-	   wanted_type, p, arg_num, arg_type);
-      else
-	diag.emit_warning
-	  (OPT_Wformat_,
-	   "%s %<%s%.*s%> expects a matching %<%T%s%> argument",
-	   gettext (kind_descriptions[kind]),
-	   (kind == CF_KIND_FORMAT ? "%" : ""),
-	   format_length, format_start, wanted_type, p);
-    }
-
+    diag.emit_warning (OPT_Wformat_,
+		       "%s %e expects a matching %e argument",
+		       gettext (kind_descriptions[kind]),
+		       &elem_format_substring,
+		       &elem_expected_type);
   free (corrected_substring);
 }
 
@@ -5111,6 +5212,14 @@ init_dynamic_diag_info (void)
       || local_event_ptr_node == void_type_node)
     local_event_ptr_node = get_named_type ("diagnostic_event_id_t");
 
+  /* Similar to the above but for pp_element*.  */
+  if (!local_pp_element_ptr_node
+      || local_pp_element_ptr_node == void_type_node)
+    {
+      if (tree pp_element_node = get_named_type ("pp_element"))
+	local_pp_element_ptr_node = build_pointer_type (pp_element_node);
+    }
+
   /* All the GCC diag formats use the same length specs.  */
   dynamic_format_types[gcc_diag_format_type].length_char_specs =
     dynamic_format_types[gcc_tdiag_format_type].length_char_specs =
@@ -5461,7 +5570,7 @@ test_type_mismatch_range_labels ()
 						  integer_type_node, 1);
   range_label_for_type_mismatch param_label (integer_type_node,
 					     char_type_node);
-  gcc_rich_location richloc (fmt, &fmt_label);
+  gcc_rich_location richloc (fmt, &fmt_label, nullptr);
   richloc.add_range (param, SHOW_RANGE_WITHOUT_CARET, &param_label);
 
   test_diagnostic_context dc;
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index 21242d1b0367..7b435f8244e0 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -317,6 +317,7 @@ struct format_kind_info
 #define T89_G   { STD_C89, NULL, &local_gimple_ptr_node }
 #define T_CGRAPH_NODE   { STD_C89, NULL, &local_cgraph_node_ptr_node }
 #define T_EVENT_PTR    { STD_C89, NULL, &local_event_ptr_node }
+#define T_PP_ELEMENT_PTR    { STD_C89, NULL, &local_pp_element_ptr_node }
 #define T89_T   { STD_C89, NULL, &local_tree_type_node }
 #define T89_V	{ STD_C89, NULL, T_V }
 #define T_W	&wchar_type_node
diff --git a/gcc/c-family/c-type-mismatch.cc b/gcc/c-family/c-type-mismatch.cc
index fae31261d544..89646854f3c7 100644
--- a/gcc/c-family/c-type-mismatch.cc
+++ b/gcc/c-family/c-type-mismatch.cc
@@ -36,6 +36,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "cpplib.h"
 #include "diagnostic.h"
+#include "diagnostic-highlight-colors.h"
 
 /* Implementation of range_label::get_text for
    maybe_range_label_for_tree_type_mismatch.
@@ -72,7 +73,8 @@ maybe_range_label_for_tree_type_mismatch::get_text (unsigned range_idx) const
        |        arg1 type
        arg0 type
 
-   labelling the types of the arguments if SHOW_TYPES is true.
+   labelling the types of the arguments if SHOW_TYPES is true,
+   and using highlight_colors::lhs and highlight_colors::rhs for the ranges.
 
    Otherwise, make a 1-location rich_location using the compound
    location within LOC:
@@ -96,8 +98,10 @@ binary_op_rich_location::binary_op_rich_location (const op_location_t &loc,
   if (use_operator_loc_p (loc, arg0, arg1))
     {
       set_range (0, loc.m_operator_loc, SHOW_RANGE_WITH_CARET);
-      maybe_add_expr (arg0, show_types ? &m_label_for_arg0 : NULL);
-      maybe_add_expr (arg1, show_types ? &m_label_for_arg1 : NULL);
+      maybe_add_expr (arg0, show_types ? &m_label_for_arg0 : NULL,
+		      highlight_colors::lhs);
+      maybe_add_expr (arg1, show_types ? &m_label_for_arg1 : NULL,
+		      highlight_colors::rhs);
     }
 }
 
diff --git a/gcc/c-family/c-type-mismatch.h b/gcc/c-family/c-type-mismatch.h
index 58fba5d4d6ee..7f0924ca5970 100644
--- a/gcc/c-family/c-type-mismatch.h
+++ b/gcc/c-family/c-type-mismatch.h
@@ -98,7 +98,8 @@ class op_location_t;
        |        arg1 type
        arg0 type
 
-   labelling the types of the arguments if SHOW_TYPES is true.
+   labelling the types of the arguments if SHOW_TYPES is true,
+   and using highlight_colors::lhs and highlight_colors::rhs for the ranges.
 
    Otherwise, it will fall back to a 1-location rich_location using the
    compound location within LOC:
diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc
index 1025e0e1c7b9..fde9ae6ad667 100644
--- a/gcc/c/c-objc-common.cc
+++ b/gcc/c/c-objc-common.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "c-family/c-pretty-print.h"
 #include "tree-pretty-print.h"
+#include "tree-pretty-print-markup.h"
 #include "gimple-pretty-print.h"
 #include "langhooks.h"
 #include "c-objc-common.h"
@@ -222,7 +223,8 @@ get_aka_type (tree type)
 /* Print T to CPP.  */
 
 static void
-print_type (c_pretty_printer *cpp, tree t, bool *quoted)
+print_type (c_pretty_printer *cpp, tree t, bool *quoted,
+	    const char *highlight_color = nullptr)
 {
   if (t == error_mark_node)
     {
@@ -230,6 +232,9 @@ print_type (c_pretty_printer *cpp, tree t, bool *quoted)
       return;
     }
 
+  if (!pp_show_highlight_colors (cpp))
+    highlight_color = nullptr;
+
   gcc_assert (TYPE_P (t));
   struct obstack *ob = pp_buffer (cpp)->obstack;
   char *p = (char *) obstack_base (ob);
@@ -249,6 +254,7 @@ print_type (c_pretty_printer *cpp, tree t, bool *quoted)
   tree aka_type = get_aka_type (t);
   if (aka_type != t)
     {
+      const bool show_color = pp_show_color (cpp);
       c_pretty_printer cpp2;
       /* Print the stripped version into a temporary printer.  */
       cpp2.type_id (aka_type);
@@ -264,22 +270,38 @@ print_type (c_pretty_printer *cpp, tree t, bool *quoted)
 
       /* They're not, print the stripped version now.  */
       if (*quoted)
-	pp_end_quote (cpp, pp_show_color (cpp));
+	pp_end_quote (cpp, show_color);
       pp_c_whitespace (cpp);
       pp_left_brace (cpp);
       pp_c_ws_string (cpp, _("aka"));
       pp_c_whitespace (cpp);
+      pp_string (cpp, colorize_stop (show_color));
       if (*quoted)
-	pp_begin_quote (cpp, pp_show_color (cpp));
+	pp_begin_quote (cpp, show_color);
+      if (highlight_color)
+	pp_string (cpp, colorize_start (show_color, highlight_color));
       cpp->type_id (aka_type);
       if (*quoted)
-	pp_end_quote (cpp, pp_show_color (cpp));
+	pp_end_quote (cpp, show_color);
       pp_right_brace (cpp);
       /* No further closing quotes are needed.  */
       *quoted = false;
     }
 }
 
+/* Implementation of pp_markup::element_quoted_type::print_type
+   for C/ObjC.  */
+
+void
+pp_markup::element_quoted_type::print_type (pp_markup::context &ctxt)
+{
+  c_pretty_printer *cpp = (c_pretty_printer *) ctxt.m_pp.clone ();
+  cpp->set_padding (pp_none);
+  ::print_type (cpp, m_type, &ctxt.m_quoted, m_highlight_color);
+  pp_string (&ctxt.m_pp, pp_formatted_text (cpp));
+  delete cpp;
+}
+
 /* Called during diagnostic message formatting process to print a
    source-level entity onto BUFFER.  The meaning of the format specifiers
    is as follows:
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index ffcab7df4d3b..5b20eda70205 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -53,6 +53,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "attribs.h"
 #include "asan.h"
 #include "realmpfr.h"
+#include "tree-pretty-print-markup.h"
 
 /* Possible cases of implicit conversions.  Used to select diagnostic messages
    and control folding initializers in convert_for_assignment.  */
@@ -3475,6 +3476,14 @@ inform_declaration (tree decl)
     inform (DECL_SOURCE_LOCATION (decl), "declared here");
 }
 
+/* C implementation of callback for use when checking param types.  */
+
+static bool
+comp_parm_types (tree wanted_type, tree actual_type)
+{
+  return comptypes (wanted_type, actual_type);
+}
+
 /* Build a function call to function FUNCTION with parameters PARAMS.
    If FUNCTION is the result of resolving an overloaded target built-in,
    ORIG_FUNDECL is the original function decl, otherwise it is null.
@@ -3594,7 +3603,8 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
 
   /* Check that the arguments to the function are valid.  */
   bool warned_p = check_function_arguments (loc, fundecl, fntype,
-					    nargs, argarray, &arg_loc);
+					    nargs, argarray, &arg_loc,
+					    comp_parm_types);
 
   if (TYPE_QUALS (return_type) != TYPE_UNQUALIFIED
       && !VOID_TYPE_P (return_type))
@@ -7194,7 +7204,10 @@ get_fndecl_argument_location (tree fndecl, int argnum)
    to FUNDECL, for types EXPECTED_TYPE and ACTUAL_TYPE.
    Attempt to issue the note at the pertinent parameter of the decl;
    failing that issue it at the location of FUNDECL; failing that
-   issue it at PLOC.  */
+   issue it at PLOC.
+   Use highlight_colors::actual for the ACTUAL_TYPE
+   and highlight_colors::expected for EXPECTED_TYPE and the
+   parameter of FUNDECL*/
 
 static void
 inform_for_arg (tree fundecl, location_t ploc, int parmnum,
@@ -7206,9 +7219,13 @@ inform_for_arg (tree fundecl, location_t ploc, int parmnum,
   else
     loc = ploc;
 
-  inform (loc,
-	  "expected %qT but argument is of type %qT",
-	  expected_type, actual_type);
+  gcc_rich_location richloc (loc, nullptr, highlight_colors::expected);
+
+  pp_markup::element_expected_type elem_expected_type (expected_type);
+  pp_markup::element_actual_type elem_actual_type (actual_type);
+  inform (&richloc,
+	  "expected %e but argument is of type %e",
+	  &elem_expected_type, &elem_actual_type);
 }
 
 /* Issue a warning when an argument of ARGTYPE is passed to a built-in
@@ -8017,7 +8034,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 		    {
 		      auto_diagnostic_group d;
 		      range_label_for_type_mismatch rhs_label (rhstype, type);
-		      gcc_rich_location richloc (expr_loc, &rhs_label);
+		      gcc_rich_location richloc (expr_loc, &rhs_label,
+						 highlight_colors::actual);
 		      if (pedwarn (&richloc, OPT_Wpointer_sign,
 				   "pointer targets in passing argument %d of "
 				   "%qE differ in signedness", parmnum, rname))
@@ -8078,7 +8096,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	      {
 		auto_diagnostic_group d;
 		range_label_for_type_mismatch rhs_label (rhstype, type);
-		gcc_rich_location richloc (expr_loc, &rhs_label);
+		gcc_rich_location richloc (expr_loc, &rhs_label,
+					   highlight_colors::actual);
 		if (permerror_opt (&richloc, OPT_Wincompatible_pointer_types,
 				   "passing argument %d of %qE from "
 				   "incompatible pointer type",
@@ -8158,7 +8177,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	    {
 	      auto_diagnostic_group d;
 	      range_label_for_type_mismatch rhs_label (rhstype, type);
-	      gcc_rich_location richloc (expr_loc, &rhs_label);
+	      gcc_rich_location richloc (expr_loc, &rhs_label,
+					 highlight_colors::actual);
 	      if (permerror_opt (&richloc, OPT_Wint_conversion,
 				 "passing argument %d of %qE makes pointer "
 				 "from integer without a cast", parmnum, rname))
@@ -8197,7 +8217,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	  {
 	    auto_diagnostic_group d;
 	    range_label_for_type_mismatch rhs_label (rhstype, type);
-	    gcc_rich_location richloc (expr_loc, &rhs_label);
+	    gcc_rich_location richloc (expr_loc, &rhs_label,
+				       highlight_colors::actual);
 	    if (permerror_opt (&richloc, OPT_Wint_conversion,
 			       "passing argument %d of %qE makes integer from "
 			       "pointer without a cast", parmnum, rname))
@@ -8247,7 +8268,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
       {
 	auto_diagnostic_group d;
 	range_label_for_type_mismatch rhs_label (rhstype, type);
-	gcc_rich_location richloc (expr_loc, &rhs_label);
+	gcc_rich_location richloc (expr_loc, &rhs_label,
+				   highlight_colors::actual);
 	const char msg[] = G_("incompatible type for argument %d of %qE");
 	if (warnopt)
 	  warning_at (expr_loc, warnopt, msg, parmnum, rname);
@@ -13452,8 +13474,8 @@ build_binary_op (location_t location, enum tree_code code,
       maybe_range_label_for_tree_type_mismatch
 	label_for_op0 (orig_op0, orig_op1),
 	label_for_op1 (orig_op1, orig_op0);
-      richloc.maybe_add_expr (orig_op0, &label_for_op0);
-      richloc.maybe_add_expr (orig_op1, &label_for_op1);
+      richloc.maybe_add_expr (orig_op0, &label_for_op0, highlight_colors::lhs);
+      richloc.maybe_add_expr (orig_op1, &label_for_op1, highlight_colors::rhs);
       binary_op_error (&richloc, code, type0, type1);
       return error_mark_node;
     }
diff --git a/gcc/common.opt b/gcc/common.opt
index c4bda3b7da82..ea39f87ae716 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -1507,6 +1507,10 @@ fdiagnostics-show-rules
 Common Var(flag_diagnostics_show_rules) Init(1)
 Print any rules associated with diagnostic messages.
 
+fdiagnostics-show-highlight-colors
+Common Var(flag_diagnostics_show_highlight_colors) Init(1)
+Use color within diagnostic messages to highlight pertinent information.
+
 fdiagnostics-path-format=
 Common Joined RejectNegative Var(flag_diagnostics_path_format) Enum(diagnostic_path_format) Init(DPF_INLINE_EVENTS)
 Specify how to print any control-flow path associated with a diagnostic.
diff --git a/gcc/common.opt.urls b/gcc/common.opt.urls
index 0e71bce27c49..b917f90b0fff 100644
--- a/gcc/common.opt.urls
+++ b/gcc/common.opt.urls
@@ -582,6 +582,9 @@ UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-show
 fdiagnostics-show-rules
 UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-show-rules)
 
+fdiagnostics-show-highlight-colors
+UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-show-highlight-colors)
+
 fdiagnostics-path-format=
 UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-path-format)
 
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index 00c1c58bd8c1..0544bb8fd976 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -162,6 +162,8 @@ class diagnostic_context;
 class pretty_printer;
 class diagnostic_event_id_t;
 typedef const char * (*diagnostic_input_charset_callback)(const char *);
+namespace pp_markup { class element; }
+typedef pp_markup::element pp_element;
 
 template<typename T> struct array_traits;
 
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 83070b2f633e..a5d3426b70c4 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -44,6 +44,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "decl.h"
 #include "c-family/c-type-mismatch.h"
 #include "tristate.h"
+#include "tree-pretty-print-markup.h"
 
 /* The various kinds of conversion.  */
 
@@ -4760,7 +4761,8 @@ implicit_conversion_error (location_t loc, tree type, tree expr)
   else
     {
       range_label_for_type_mismatch label (TREE_TYPE (expr), type);
-      gcc_rich_location rich_loc (loc, &label);
+      gcc_rich_location rich_loc (loc, &label,
+				  highlight_colors::percent_h);
       error_at (&rich_loc, "could not convert %qE from %qH to %qI",
 		expr, TREE_TYPE (expr), type);
     }
@@ -5446,6 +5448,19 @@ build_op_call (tree obj, vec<tree, va_gc> **args, tsubst_flags_t complain)
   return result;
 }
 
+/* Subroutine for preparing format strings suitable for the error
+   function.  It concatenates a prefix (controlled by MATCH), ERRMSG,
+   and SUFFIX.  */
+
+static const char *
+concat_op_error_string (bool match, const char *errmsg, const char *suffix)
+{
+  return concat (match
+		 ? G_("ambiguous overload for ")
+		 : G_("no match for "),
+		 errmsg, suffix, nullptr);
+}
+
 /* Called by op_error to prepare format strings suitable for the error
    function.  It concatenates a prefix (controlled by MATCH), ERRMSG,
    and a suffix (controlled by NTYPES).  */
@@ -5453,19 +5468,24 @@ build_op_call (tree obj, vec<tree, va_gc> **args, tsubst_flags_t complain)
 static const char *
 op_error_string (const char *errmsg, int ntypes, bool match)
 {
-  const char *msg;
-
-  const char *msgp = concat (match ? G_("ambiguous overload for ")
-			           : G_("no match for "), errmsg, NULL);
-
+  const char *suffix;
   if (ntypes == 3)
-    msg = concat (msgp, G_(" (operand types are %qT, %qT, and %qT)"), NULL);
+    suffix = G_(" (operand types are %qT, %qT, and %qT)");
   else if (ntypes == 2)
-    msg = concat (msgp, G_(" (operand types are %qT and %qT)"), NULL);
+    suffix = G_(" (operand types are %qT and %qT)");
   else
-    msg = concat (msgp, G_(" (operand type is %qT)"), NULL);
+    suffix = G_(" (operand type is %qT)");
+  return concat_op_error_string (match, errmsg, suffix);
+}
 
-  return msg;
+/* Similar to op_error_string, but a special-case for binary ops that
+   use %e for the args, rather than %qT.  */
+
+static const char *
+binop_error_string (const char *errmsg, bool match)
+{
+  return concat_op_error_string (match, errmsg,
+				 G_(" (operand types are %e and %e)"));
 }
 
 static void
@@ -5536,9 +5556,13 @@ op_error (const op_location_t &loc,
 	if (flag_diagnostics_show_caret)
 	  {
 	    binary_op_rich_location richloc (loc, arg1, arg2, true);
+	    pp_markup::element_quoted_type element_0
+	      (TREE_TYPE (arg1), highlight_colors::lhs);
+	    pp_markup::element_quoted_type element_1
+	      (TREE_TYPE (arg2), highlight_colors::rhs);
 	    error_at (&richloc,
-		      op_error_string (G_("%<operator%s%>"), 2, match),
-		      opname, TREE_TYPE (arg1), TREE_TYPE (arg2));
+		      binop_error_string (G_("%<operator%s%>"), match),
+		      opname, &element_0, &element_1);
 	  }
 	else
 	  error_at (loc, op_error_string (G_("%<operator%s%> in %<%E %s %E%>"),
@@ -8370,11 +8394,16 @@ get_fndecl_argument_location (tree fndecl, int argnum)
    wrong).  */
 
 void
-maybe_inform_about_fndecl_for_bogus_argument_init (tree fn, int argnum)
+maybe_inform_about_fndecl_for_bogus_argument_init (tree fn, int argnum,
+						   const char *highlight_color)
 {
   if (fn)
-    inform (get_fndecl_argument_location (fn, argnum),
-	    "  initializing argument %P of %qD", argnum, fn);
+    {
+      gcc_rich_location richloc (get_fndecl_argument_location (fn, argnum));
+      richloc.set_highlight_color (highlight_color);
+      inform (&richloc,
+	      "  initializing argument %P of %qD", argnum, fn);
+    }
 }
 
 /* Maybe warn about C++20 Conversions to arrays of unknown bound.  C is
@@ -8523,7 +8552,7 @@ convert_like_internal (conversion *convs, tree expr, tree fn, int argnum,
       if (!complained && expr != error_mark_node)
 	{
 	  range_label_for_type_mismatch label (TREE_TYPE (expr), totype);
-	  gcc_rich_location richloc (loc, &label);
+	  gcc_rich_location richloc (loc, &label, highlight_colors::percent_h);
 	  complained = permerror (&richloc,
 				  "invalid conversion from %qH to %qI",
 				  TREE_TYPE (expr), totype);
@@ -8535,7 +8564,8 @@ convert_like_internal (conversion *convs, tree expr, tree fn, int argnum,
       else
 	expr = cp_convert (totype, expr, complain);
       if (complained == 1)
-	maybe_inform_about_fndecl_for_bogus_argument_init (fn, argnum);
+	maybe_inform_about_fndecl_for_bogus_argument_init
+	  (fn, argnum, highlight_colors::percent_i);
       return expr;
     }
 
@@ -10380,7 +10410,8 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 	}
 
       warned_p = check_function_arguments (input_location, fn, TREE_TYPE (fn),
-					   nargs, fargs, NULL);
+					   nargs, fargs, NULL,
+					   cp_comp_parm_types);
     }
 
   if (DECL_INHERITED_CTOR (fn))
@@ -11460,12 +11491,14 @@ complain_about_bad_argument (location_t arg_loc,
       arg_loc = input_location;
       label = NULL;
     }
-  gcc_rich_location richloc (arg_loc, label);
+  gcc_rich_location richloc (arg_loc, label, highlight_colors::percent_h);
   error_at (&richloc,
 	    "cannot convert %qH to %qI",
 	    from_type, to_type);
-  maybe_inform_about_fndecl_for_bogus_argument_init (fndecl,
-						     parmnum);
+  maybe_inform_about_fndecl_for_bogus_argument_init
+    (fndecl,
+     parmnum,
+     highlight_colors::percent_i);
 }
 
 /* Subroutine of build_new_method_call_1, for where there are no viable
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 4bb3e9c4989b..0c9bccd78816 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6782,7 +6782,8 @@ extern location_t get_fndecl_argument_location  (tree, int);
 extern void complain_about_bad_argument	(location_t arg_loc,
 						 tree from_type, tree to_type,
 						 tree fndecl, int parmnum);
-extern void maybe_inform_about_fndecl_for_bogus_argument_init (tree, int);
+extern void maybe_inform_about_fndecl_for_bogus_argument_init (tree, int,
+							       const char * = nullptr);
 extern tree perform_dguide_overload_resolution	(tree, const vec<tree, va_gc> *,
 						 tsubst_flags_t);
 
@@ -8193,6 +8194,7 @@ extern bool comp_except_specs			(const_tree, const_tree, int);
 extern bool comptypes				(tree, tree, int);
 extern bool same_type_ignoring_top_level_qualifiers_p (tree, tree);
 extern bool similar_type_p			(tree, tree);
+extern bool cp_comp_parm_types			(tree, tree);
 extern bool next_common_initial_sequence	(tree &, tree &);
 extern bool layout_compatible_type_p		(tree, tree);
 extern bool compparms				(const_tree, const_tree);
@@ -9068,6 +9070,15 @@ name_independent_decl_p (tree decl)
 	  && !DECL_EXTERNAL (decl));
 }
 
+namespace highlight_colors {
+
+/* Color names for highlighting "%qH" vs "%qI" values,
+   and ranges corresponding to them.  */
+extern const char *const percent_h;
+extern const char *const percent_i;
+
+} // namespace highlight_colors
+
 #if CHECKING_P
 namespace selftest {
   extern void run_cp_tests (void);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 1f36563ae2cc..e35448f54346 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -30,6 +30,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "cxx-pretty-print.h"
 #include "tree-pretty-print.h"
+#include "tree-pretty-print-markup.h"
 #include "gimple-pretty-print.h"
 #include "c-family/c-objc.h"
 #include "ubsan.h"
@@ -62,7 +63,8 @@ static const char *decl_to_string (tree, int, bool);
 static const char *fndecl_to_string (tree, int);
 static const char *op_to_string	(bool, enum tree_code);
 static const char *parm_to_string (int);
-static const char *type_to_string (tree, int, bool, bool *, bool);
+static const char *type_to_string (tree, int, bool, bool *, bool,
+				   const char * = nullptr);
 
 static void dump_alias_template_specialization (cxx_pretty_printer *, tree, int);
 static void dump_type (cxx_pretty_printer *, tree, int);
@@ -109,6 +111,11 @@ static void cp_print_error_function (diagnostic_context *,
 static bool cp_printer (pretty_printer *, text_info *, const char *,
 			int, bool, bool, bool, bool *, const char **);
 
+/* Color names for highlighting "%qH" vs "%qI" values,
+   and ranges corresponding to them.  */
+const char *const highlight_colors::percent_h = "highlight-a";
+const char *const highlight_colors::percent_i = "highlight-b";
+
 /* Struct for handling %H or %I, which require delaying printing the
    type until a postprocessing stage.  */
 
@@ -3405,12 +3412,12 @@ op_to_string (bool assop, enum tree_code p)
    pp_format, or is not needed due to QUOTE being NULL (for template arguments
    within %H and %I).
 
-   SHOW_COLOR is used to determine the colorization of any quotes that
-   are added.  */
+   SHOW_COLOR and HIGHLIGHT_COLOR are used to determine the colorization of
+   any quotes that are added.  */
 
 static const char *
 type_to_string (tree typ, int verbose, bool postprocessed, bool *quote,
-		bool show_color)
+		bool show_color, const char *highlight_color)
 {
   int flags = 0;
   if (verbose)
@@ -3421,7 +3428,11 @@ type_to_string (tree typ, int verbose, bool postprocessed, bool *quote,
   pp_show_color (cxx_pp) = show_color;
 
   if (postprocessed && quote && *quote)
-    pp_begin_quote (cxx_pp, show_color);
+    {
+      pp_begin_quote (cxx_pp, show_color);
+      if (show_color && highlight_color)
+	pp_string (cxx_pp, colorize_start (show_color, highlight_color));
+    }
 
   struct obstack *ob = pp_buffer (cxx_pp)->obstack;
   int type_start, type_len;
@@ -3447,6 +3458,8 @@ type_to_string (tree typ, int verbose, bool postprocessed, bool *quote,
       pp_cxx_whitespace (cxx_pp);
       if (quote && *quote)
 	pp_begin_quote (cxx_pp, show_color);
+      if (highlight_color)
+	pp_string (cxx_pp, colorize_start (show_color, highlight_color));
       /* And remember the start of the aka dump.  */
       aka_start = obstack_object_size (ob);
       dump_type (cxx_pp, aka, flags);
@@ -3476,6 +3489,8 @@ type_to_string (tree typ, int verbose, bool postprocessed, bool *quote,
 
   if (quote && *quote)
     {
+      if (show_color && highlight_color)
+	pp_string (cxx_pp, colorize_stop (show_color));
       pp_end_quote (cxx_pp, show_color);
       *quote = false;
     }
@@ -4100,13 +4115,17 @@ arg_to_string (tree arg, bool verbose)
    print_template_tree_comparison.
 
    Print a representation of ARG (an expression or type) to PP,
-   colorizing it as "type-diff" if PP->show_color.  */
+   colorizing it if PP->show_color, using HIGHLIGHT_COLOR,
+   or "type-diff" if the latter is NULL.  */
 
 static void
-print_nonequal_arg (pretty_printer *pp, tree arg, bool verbose)
+print_nonequal_arg (pretty_printer *pp, tree arg, bool verbose,
+		    const char *highlight_color)
 {
+  if (!highlight_color)
+    highlight_color = "type-diff";
   pp_printf (pp, "%r%s%R",
-	     "type-diff",
+	     highlight_color,
 	     (arg
 	      ? arg_to_string (arg, verbose)
 	      : G_("(no argument)")));
@@ -4158,7 +4177,9 @@ print_nonequal_arg (pretty_printer *pp, tree arg, bool verbose)
 
 static void
 print_template_differences (pretty_printer *pp, tree type_a, tree type_b,
-			    bool verbose, int indent)
+			    bool verbose, int indent,
+			    const char *highlight_color_a,
+			    const char *highlight_color_b)
 {
   if (indent)
     newline_and_indent (pp, indent);
@@ -4209,19 +4230,20 @@ print_template_differences (pretty_printer *pp, tree type_a, tree type_b,
 	{
 	  int new_indent = indent ? indent + 2 : 0;
 	  if (comparable_template_types_p (arg_a, arg_b))
-	    print_template_differences (pp, arg_a, arg_b, verbose, new_indent);
+	    print_template_differences (pp, arg_a, arg_b, verbose, new_indent,
+					highlight_color_a, highlight_color_b);
 	  else
 	    if (indent)
 	      {
 		newline_and_indent (pp, indent + 2);
 		pp_character (pp, '[');
-		print_nonequal_arg (pp, arg_a, verbose);
+		print_nonequal_arg (pp, arg_a, verbose, highlight_color_a);
 		pp_string (pp, " != ");
-		print_nonequal_arg (pp, arg_b, verbose);
+		print_nonequal_arg (pp, arg_b, verbose, highlight_color_b);
 		pp_character (pp, ']');
 	      }
 	    else
-	      print_nonequal_arg (pp, arg_a, verbose);
+	      print_nonequal_arg (pp, arg_a, verbose, highlight_color_a);
 	}
     }
   pp_printf (pp, ">");
@@ -4247,13 +4269,16 @@ print_template_differences (pretty_printer *pp, tree type_a, tree type_b,
 
 static const char *
 type_to_string_with_compare (tree type, tree peer, bool verbose,
-			     bool show_color)
+			     bool show_color,
+			     const char *this_highlight_color,
+			     const char *peer_highlight_color)
 {
   pretty_printer inner_pp;
   pretty_printer *pp = &inner_pp;
   pp_show_color (pp) = show_color;
 
-  print_template_differences (pp, type, peer, verbose, 0);
+  print_template_differences (pp, type, peer, verbose, 0,
+			      this_highlight_color, peer_highlight_color);
   return pp_ggc_formatted_text (pp);
 }
 
@@ -4291,9 +4316,13 @@ type_to_string_with_compare (tree type, tree peer, bool verbose,
 
 static void
 print_template_tree_comparison (pretty_printer *pp, tree type_a, tree type_b,
-				bool verbose, int indent)
+				bool verbose, int indent,
+				const char *highlight_color_a,
+				const char *highlight_color_b)
 {
-  print_template_differences (pp, type_a, type_b, verbose, indent);
+  print_template_differences (pp, type_a, type_b, verbose, indent,
+			      highlight_color_a,
+			      highlight_color_b);
 }
 
 /* Subroutine for use in a format_postprocessor::handle
@@ -4345,6 +4374,11 @@ cxx_format_postprocessor::handle (pretty_printer *pp)
      been present.  */
   if (m_type_a.m_tree || m_type_b.m_tree)
     {
+      const bool show_highlight_colors = pp_show_highlight_colors (pp);
+      const char *percent_h
+	= show_highlight_colors ? highlight_colors::percent_h : nullptr;
+      const char *percent_i
+	= show_highlight_colors ? highlight_colors::percent_i : nullptr;
       /* Avoid reentrancy issues by working with a copy of
 	 m_type_a and m_type_b, resetting them now.  */
       deferred_printed_type type_a = m_type_a;
@@ -4362,19 +4396,22 @@ cxx_format_postprocessor::handle (pretty_printer *pp)
 
       if (comparable_template_types_p (type_a.m_tree, type_b.m_tree))
 	{
-	  type_a_text
-	    = type_to_string_with_compare (type_a.m_tree, type_b.m_tree,
-					   type_a.m_verbose, show_color);
-	  type_b_text
-	    = type_to_string_with_compare (type_b.m_tree, type_a.m_tree,
-					   type_b.m_verbose, show_color);
+	  type_a_text = type_to_string_with_compare
+	    (type_a.m_tree, type_b.m_tree,
+	     type_a.m_verbose, show_color,
+	     percent_h, percent_i);
+	  type_b_text = type_to_string_with_compare
+	    (type_b.m_tree, type_a.m_tree,
+	     type_b.m_verbose, show_color,
+	     percent_i, percent_h);
 
 	  if (flag_diagnostics_show_template_tree)
 	    {
 	      pretty_printer inner_pp;
 	      pp_show_color (&inner_pp) = pp_show_color (pp);
 	      print_template_tree_comparison
-		(&inner_pp, type_a.m_tree, type_b.m_tree, type_a.m_verbose, 2);
+		(&inner_pp, type_a.m_tree, type_b.m_tree, type_a.m_verbose, 2,
+		 percent_h, percent_i);
 	      append_formatted_chunk (pp, pp_ggc_formatted_text (&inner_pp));
 	    }
 	}
@@ -4384,9 +4421,11 @@ cxx_format_postprocessor::handle (pretty_printer *pp)
 	     provided), they are printed normally, and no difference tree
 	     is printed.  */
 	  type_a_text = type_to_string (type_a.m_tree, type_a.m_verbose,
-					true, &type_a.m_quote, show_color);
+					true, &type_a.m_quote, show_color,
+					percent_h);
 	  type_b_text = type_to_string (type_b.m_tree, type_b.m_verbose,
-					true, &type_b.m_quote, show_color);
+					true, &type_b.m_quote, show_color,
+					percent_i);
 	}
 
       if (type_a.m_quote)
@@ -4438,6 +4477,19 @@ defer_phase_2_of_type_diff (deferred_printed_type *deferred,
   *deferred = deferred_printed_type (type, buffer_ptr, verbose, quote);
 }
 
+/* Implementation of pp_markup::element_quoted_type::print_type
+   for C++/ObjC++.  */
+
+void
+pp_markup::element_quoted_type::print_type (pp_markup::context &ctxt)
+{
+  const char *highlight_color
+    = pp_show_highlight_colors (&ctxt.m_pp) ? m_highlight_color : nullptr;
+  const char *result
+    = type_to_string (m_type, false, false, &ctxt.m_quoted,
+		      pp_show_color (&ctxt.m_pp), highlight_color);
+  pp_string (&ctxt.m_pp, result);
+}
 
 /* Called from output_format -- during diagnostic message processing --
    to handle C++ specific format specifier with the following meanings:
@@ -4748,7 +4800,8 @@ range_label_for_type_mismatch::get_text (unsigned /*range_idx*/) const
   if (m_other_type
       && comparable_template_types_p (m_labelled_type, m_other_type))
     result = type_to_string_with_compare (m_labelled_type, m_other_type,
-					  verbose, show_color);
+					  verbose, show_color,
+					  nullptr, nullptr);
   else
     result = type_to_string (m_labelled_type, verbose, true, NULL, show_color);
 
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 5519d7705e9c..5041a70d0891 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -4346,6 +4346,18 @@ cp_build_function_call_nary (tree function, tsubst_flags_t complain, ...)
   return ret;
 }
 
+/* C++ implementation of callback for use when checking param types.  */
+
+bool
+cp_comp_parm_types (tree wanted_type, tree actual_type)
+{
+  if (TREE_CODE (wanted_type) == POINTER_TYPE
+      && TREE_CODE (actual_type) == POINTER_TYPE)
+    return same_or_base_type_p (TREE_TYPE (wanted_type),
+				TREE_TYPE (actual_type));
+  return false;
+}
+
 /* Build a function call using a vector of arguments.
    If FUNCTION is the result of resolving an overloaded target built-in,
    ORIG_FNDECL is the original function decl, otherwise it is null.
@@ -4457,7 +4469,8 @@ cp_build_function_call_vec (tree function, vec<tree, va_gc> **params,
   /* Check for errors in format strings and inappropriately
      null parameters.  */
   bool warned_p = check_function_arguments (input_location, fndecl, fntype,
-					    nargs, argarray, NULL);
+					    nargs, argarray, NULL,
+					    cp_comp_parm_types);
 
   ret = build_cxx_call (function, nargs, argarray, complain, orig_fndecl);
 
@@ -10380,7 +10393,10 @@ convert_for_assignment (tree type, tree rhs,
 	      else
 		{
 		  range_label_for_type_mismatch label (rhstype, type);
-		  gcc_rich_location richloc (rhs_loc, has_loc ? &label : NULL);
+		  gcc_rich_location richloc
+		    (rhs_loc,
+		     has_loc ? &label : NULL,
+		     has_loc ? highlight_colors::percent_h : NULL);
 		  auto_diagnostic_group d;
 
 		  switch (errtype)
diff --git a/gcc/diagnostic-color.cc b/gcc/diagnostic-color.cc
index 184419cea367..8b195d023eb7 100644
--- a/gcc/diagnostic-color.cc
+++ b/gcc/diagnostic-color.cc
@@ -102,7 +102,9 @@ static const color_default gcc_color_defaults[] =
   { "diff-insert", SGR_SEQ (COLOR_FG_GREEN) },
   { "type-diff", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
   { "valid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
-  { "invalid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED) }
+  { "invalid", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_RED) },
+  { "highlight-a", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_GREEN) },
+  { "highlight-b", SGR_SEQ (COLOR_BOLD COLOR_SEPARATOR COLOR_FG_BLUE) }
 };
 
 class diagnostic_color_dict
diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc
index 8f2ff6cfde20..4dc0f264fc70 100644
--- a/gcc/diagnostic-format-json.cc
+++ b/gcc/diagnostic-format-json.cc
@@ -401,6 +401,7 @@ diagnostic_output_format_init_json (diagnostic_context *context)
 
   /* Don't colorize the text.  */
   pp_show_color (context->printer) = false;
+  context->set_show_highlight_colors (false);
 }
 
 /* Populate CONTEXT in preparation for JSON output to stderr.  */
diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 2745c72ea3e5..225476995d13 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -2033,6 +2033,7 @@ diagnostic_output_format_init_sarif (diagnostic_context *context)
 
   /* Don't colorize the text.  */
   pp_show_color (context->printer) = false;
+  context->set_show_highlight_colors (false);
 }
 
 /* Populate CONTEXT in preparation for SARIF output to stderr.  */
diff --git a/gcc/diagnostic-highlight-colors.h b/gcc/diagnostic-highlight-colors.h
new file mode 100644
index 000000000000..66acfc8e8145
--- /dev/null
+++ b/gcc/diagnostic-highlight-colors.h
@@ -0,0 +1,56 @@
+/* Symbolic names for highlighting colors in diagnostics.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_DIAGNOSTIC_HIGHLIGHT_COLORS_H
+#define GCC_DIAGNOSTIC_HIGHLIGHT_COLORS_H
+
+/* Symbolic names for highlight colors in diagnostics, so that e.g.
+   in
+
+warning: format `%i' expects argument of type `int',
+ but argument 2 has type `const char *' [-Wformat=]
+  279 |   printf("hello " INT_FMT " world", msg);
+      |          ^~~~~~~~                   ~~~
+      |                                     |
+      |                                     const char *
+note: format string is defined here
+  278 | #define INT_FMT "%i"
+      |                  ~^
+      |                   |
+      |                   int
+      |                  %s
+
+    we can refer to the color of "int" as highlight_colors::expected
+    and the color of "const char *" as highlight_colors::actual
+    to help get consistent contrasting colorization, both for the types
+    within the diagnostic messages, and the underlined ranges.  */
+
+namespace highlight_colors {
+
+/* Color names for expressing "expected" vs "actual" values.  */
+extern const char *const expected;
+extern const char *const actual;
+
+/* Color names for expressing "LHS" vs "RHS" values in a binary operation.  */
+extern const char *const lhs;
+extern const char *const rhs;
+
+} // namespace highlight_colors
+
+#endif /* ! GCC_DIAGNOSTIC_HIGHLIGHT_COLORS_H */
diff --git a/gcc/diagnostic-path.cc b/gcc/diagnostic-path.cc
index f279aead6c59..b497d89d0594 100644
--- a/gcc/diagnostic-path.cc
+++ b/gcc/diagnostic-path.cc
@@ -518,7 +518,7 @@ struct event_range
     m_stack_depth (initial_event.get_stack_depth ()),
     m_start_idx (start_idx), m_end_idx (start_idx),
     m_path_label (ctxt, path, start_idx),
-    m_richloc (initial_event.get_location (), &m_path_label),
+    m_richloc (initial_event.get_location (), &m_path_label, nullptr),
     m_thread_id (initial_event.get_thread_id ()),
     m_per_thread_summary (t),
     m_show_event_links (show_event_links)
diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index abd01827f479..d0fc2ff1b6d2 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -88,12 +88,24 @@ class colorizer
 {
  public:
   colorizer (pretty_printer *pp,
+	     const rich_location &richloc,
 	     diagnostic_t diagnostic_kind);
   ~colorizer ();
 
   void set_range (int range_idx)
   {
-    /* Normally we emphasize the primary location, then alternate between
+    /* If we have a specific highlight color for the range, use it.  */
+    if (pp_show_highlight_colors (m_pp))
+      {
+	const location_range *const loc_range = m_richloc.get_range (range_idx);
+	if (loc_range->m_highlight_color)
+	  {
+	    set_named_color (loc_range->m_highlight_color);
+	    return;
+	  }
+      }
+
+    /* Otherwise, we emphasize the primary location, then alternate between
        two colors for the secondary locations.
        But if we're printing a run of events in a diagnostic path, that
        makes no sense, so print all of them with the same colorization.  */
@@ -106,6 +118,7 @@ class colorizer
   void set_normal_text () { set_state (STATE_NORMAL_TEXT); }
   void set_fixit_insert () { set_state (STATE_FIXIT_INSERT); }
   void set_fixit_delete () { set_state (STATE_FIXIT_DELETE); }
+  void set_named_color (const char *color);
 
  private:
   void set_state (int state);
@@ -117,8 +130,10 @@ class colorizer
   static const int STATE_NORMAL_TEXT = -1;
   static const int STATE_FIXIT_INSERT  = -2;
   static const int STATE_FIXIT_DELETE  = -3;
+  static const int STATE_NAMED_COLOR  = -4;
 
   pretty_printer *m_pp;
+  const rich_location &m_richloc;
   diagnostic_t m_diagnostic_kind;
   int m_current_state;
   const char *m_range1;
@@ -520,8 +535,10 @@ class layout
    different kinds of things we might need to print.  */
 
 colorizer::colorizer (pretty_printer *pp,
+		      const rich_location &richloc,
 		      diagnostic_t diagnostic_kind) :
   m_pp (pp),
+  m_richloc (richloc),
   m_diagnostic_kind (diagnostic_kind),
   m_current_state (STATE_NORMAL_TEXT)
 {
@@ -540,6 +557,17 @@ colorizer::~colorizer ()
   finish_state (m_current_state);
 }
 
+/* Update state, changing to the specific named color and printing its
+   color codes.  */
+
+void
+colorizer::set_named_color (const char *color)
+{
+  finish_state (m_current_state);
+  m_current_state = STATE_NAMED_COLOR;
+  pp_string (m_pp, colorize_start (pp_show_color (m_pp), color));
+}
+
 /* Update state, printing color codes if necessary if there's a state
    change.  */
 
@@ -572,6 +600,10 @@ colorizer::begin_state (int state)
       pp_string (m_pp, m_fixit_delete);
       break;
 
+    case STATE_NAMED_COLOR:
+      /* Should be handled by colorizer::set_named_color.  */
+      gcc_unreachable ();
+
     case 0:
       /* Make range 0 be the same color as the "kind" text
 	 (error vs warning vs note).  */
@@ -1304,7 +1336,7 @@ layout::layout (const diagnostic_context &context,
   m_exploc (m_file_cache,
 	    richloc.get_expanded_location (0), m_policy,
 	    LOCATION_ASPECT_CARET),
-  m_colorizer (m_pp, diagnostic_kind),
+  m_colorizer (m_pp, richloc, diagnostic_kind),
   m_diagnostic_path_p (diagnostic_kind == DK_DIAGNOSTIC_PATH),
   m_layout_ranges (richloc.get_num_locations ()),
   m_fixit_hints (richloc.get_num_fixit_hints ()),
@@ -4012,7 +4044,7 @@ test_one_liner_labels ()
     text_range_label label0 ("0");
     text_range_label label1 ("1");
     text_range_label label2 ("2");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4042,7 +4074,7 @@ test_one_liner_labels ()
     text_range_label label0 ("label 0");
     text_range_label label1 ("label 1");
     text_range_label label2 ("label 2");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4063,7 +4095,7 @@ test_one_liner_labels ()
     text_range_label label0 ("aaaaa");
     text_range_label label1 ("bbbb");
     text_range_label label2 ("c");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4082,7 +4114,7 @@ test_one_liner_labels ()
     text_range_label label0 ("0");
     text_range_label label1 ("1");
     text_range_label label2 ("2");
-    gcc_rich_location richloc (field, &label0);
+    gcc_rich_location richloc (field, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4101,7 +4133,7 @@ test_one_liner_labels ()
     text_range_label label0 ("label 0");
     text_range_label label1 ("label 1");
     text_range_label label2 ("label 2");
-    gcc_rich_location richloc (bar, &label0);
+    gcc_rich_location richloc (bar, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4128,7 +4160,7 @@ test_one_liner_labels ()
     text_range_label label_0c ("label 0c");
     text_range_label label_1c ("label 1c");
     text_range_label label_2c ("label 2c");
-    gcc_rich_location richloc (field, &label_0a);
+    gcc_rich_location richloc (field, &label_0a, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label_1a);
     richloc.add_range (foo, SHOW_RANGE_WITHOUT_CARET, &label_2a);
 
@@ -4161,7 +4193,7 @@ test_one_liner_labels ()
      handled gracefully.  */
   {
     text_range_label label (NULL);
-    gcc_rich_location richloc (bar, &label);
+    gcc_rich_location richloc (bar, &label, nullptr);
 
     test_diagnostic_context dc;
     diagnostic_show_locus (&dc, &richloc, DK_ERROR);
@@ -4620,7 +4652,7 @@ test_one_liner_labels_utf8 ()
     text_range_label label2
       ("\xf0\x9f\x98\x82\xcf\x80\xf0\x9f\x98\x82\xf0\x9f\x98\x82\xcf\x80"
        "\xcf\x80");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4648,7 +4680,7 @@ test_one_liner_labels_utf8 ()
     text_range_label label0 ("label 0\xf0\x9f\x98\x82");
     text_range_label label1 ("label 1\xcf\x80");
     text_range_label label2 ("label 2\xcf\x80");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4674,7 +4706,7 @@ test_one_liner_labels_utf8 ()
     text_range_label label0 ("aaaaa\xf0\x9f\x98\x82\xcf\x80");
     text_range_label label1 ("bb\xf0\x9f\x98\x82\xf0\x9f\x98\x82");
     text_range_label label2 ("c");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
 
@@ -4698,7 +4730,7 @@ test_one_liner_labels_utf8 ()
     text_range_label label0 ("label 0\xf0\x9f\x98\x82");
     text_range_label label1 ("label 1\xcf\x80");
     text_range_label label2 ("label 2\xcf\x80");
-    gcc_rich_location richloc (foo, &label0);
+    gcc_rich_location richloc (foo, &label0, nullptr);
     richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1);
     richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
     richloc.set_escape_on_output (true);
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 4dc448928d59..cb58bc63c2c3 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -466,6 +466,10 @@ public:
   }
   void set_show_cwe (bool val) { m_show_cwe = val;  }
   void set_show_rules (bool val) { m_show_rules = val; }
+  void set_show_highlight_colors (bool val)
+  {
+    pp_show_highlight_colors (printer) = val;
+  }
   void set_path_format (enum diagnostic_path_format val)
   {
     m_path_format = val;
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 4d671c4f6d89..241c2752e89b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -310,7 +310,8 @@ Objective-C and Objective-C++ Dialects}.
 -fno-diagnostics-show-event-links
 -fno-diagnostics-show-labels  -fno-diagnostics-show-line-numbers
 -fno-diagnostics-show-cwe
--fno-diagnostics-show-rule
+-fno-diagnostics-show-rules
+-fno-diagnostics-show-highlight-colors
 -fdiagnostics-minimum-margin-width=@var{width}
 -fdiagnostics-parseable-fixits  -fdiagnostics-generate-patch
 -fdiagnostics-show-template-tree  -fno-elide-type
@@ -5290,7 +5291,8 @@ The default @env{GCC_COLORS} is
 error=01;31:warning=01;35:note=01;36:range1=32:range2=34:locus=01:\
 quote=01:path=01;36:fixit-insert=32:fixit-delete=31:\
 diff-filename=01:diff-hunk=32:diff-delete=31:diff-insert=32:\
-type-diff=01;32:fnname=01;32:targs=35:valid=01;31:invalid=01;32
+type-diff=01;32:fnname=01;32:targs=35:valid=01;31:invalid=01;32\
+highlight-a=01;32:highlight-b=01;34
 @end smallexample
 @noindent
 where @samp{01;31} is bold red, @samp{01;35} is bold magenta,
@@ -5381,6 +5383,14 @@ SGR substring for highlighting valid elements within text art diagrams.
 @vindex invalid GCC_COLORS @r{capability}
 @item invalid=
 SGR substring for highlighting invalid elements within text art diagrams.
+
+@vindex highlight-a GCC_COLORS @r{capability}
+@vindex highlight-b GCC_COLORS @r{capability}
+@item highlight-a=
+@item highlight-b=
+SGR substrings for contrasting two different things within diagnostics,
+such as a pair of mismatching types.
+See @option{-fdiagnostics-show-highlight-colors}.
 @end table
 
 @opindex fdiagnostics-urls
@@ -5506,6 +5516,43 @@ GCC itself does not do this for any of its diagnostics, but plugins may do so.
 By default, if this information is present, it will be printed with
 the diagnostic.  This option suppresses the printing of this metadata.
 
+@opindex fno-diagnostics-show-highlight-colors
+@opindex fdiagnostics-show-highlight-colors
+@item -fno-diagnostics-show-highlight-colors
+
+GCC can use color for emphasis and contrast when printing diagnostic
+messages and quoting the user's source.
+
+For example, in
+
+@smallexample
+demo.c: In function `test_bad_format_string_args':
+../../src/demo.c:25:18: warning: format `%i' expects argument of type `int', but argument 2 has type `const char *' [-Wformat=]
+   25 |   printf("hello %i", msg);
+      |                 ~^   ~~~
+      |                  |   |
+      |                  int const char *
+      |                 %s
+@end smallexample
+
+@itemize @bullet
+@item
+the @code{%i} and @code{int} in the message and the @code{int} in the
+quoted source are colored using @code{highlight-a} (bold green by default),
+and
+@item
+the @code{const char *} in the message and in the quoted source are both
+colored using @code{highlight-b} (bold blue by default).
+@end itemize
+
+The intent is to draw the reader's eyes to the relationships between the
+various aspects of the diagnostic message and the source, using color
+to group related elements and distinguish between mismatching ones.
+
+This additional colorization is enabled by default if color printing
+is enabled (as per @option{-fdiagnostics-color=}), but it can be separately
+disabled via @option{-fno-diagnostics-show-highlight-colors}.
+
 @opindex fno-diagnostics-show-line-numbers
 @opindex fdiagnostics-show-line-numbers
 @item -fno-diagnostics-show-line-numbers
diff --git a/gcc/doc/ux.texi b/gcc/doc/ux.texi
index 84568699f5d3..125ce8e14d53 100644
--- a/gcc/doc/ux.texi
+++ b/gcc/doc/ux.texi
@@ -443,6 +443,93 @@ argument %d of %qE must be a pointer type
 since the argument number does not refer to a numerical constant in the
 source code it should not be quoted.
 
+@subsection Use color consistently when highlighting mismatches
+
+As of GCC 15, the diagnostics subsystem has a concept of ``highlight
+colors''.  These should be used to consistently colorize both the text
+within diagnostic messages and underlined ranges of quoted source when
+highlighting mismatches, for all messages with a logically-related group
+of diagnostics.
+
+See @file{diagnostic-highlight-colors.h} for symbolic names for color
+codes, covering e.g.
+
+@itemize @bullet
+
+@item
+@code{highlight_colors::expected} versus @code{highlight_colors::actual}
+
+@item
+@code{highlight_colors::lhs} versus @code{highlight_colors::rhs}
+
+@end itemize
+
+For example, given:
+
+@smallexample
+error: invalid operands to binary + (have 'S' @{aka 'struct s'@} and 'T' @{aka 'struct t'@})
+   return callee_4a () + callee_4b ();
+          ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~
+          |              |
+          |              T @{aka struct t@}
+          S @{aka struct s@}
+@end smallexample
+
+@itemize @bullet
+
+@item
+the text ``S @{aka struct s@}'' in the message and the left-hand label
+in the quoted source should be colorized as @code{highlight_colors::lhs}
+(which equates to the color name @code{highlight-a})
+
+@item
+the text ``T @{aka struct t@}'' in the message and the right-hand label
+in the quoted source should be colorized as @code{highlight_colors::rhs}
+(which equates to the color name @code{highlight-b})
+
+@end itemize
+
+Doing so ought to make it easier for the user to understand what the
+diagnostic is telling them.
+
+When issuing followup @code{note} diagnostics, all diagnostics within the
+group should use a consistent scheme to highlight the mismatching elements,
+so that color contrasts the differences.  For example, given:
+
+@smallexample
+warning: format ‘%i’ expects argument of type ‘int’, but argument 2 has type ‘const char *’ [-Wformat=]
+  279 |   printf("hello " INT_FMT " world", msg);
+      |          ^~~~~~~~                   ~~~
+      |                                     |
+      |                                     const char *
+note: format string is defined here
+  278 | #define INT_FMT "%i"
+      |                  ~^
+      |                   |
+      |                   int
+      |                  %s
+@end smallexample
+
+@itemize @bullet
+
+@item
+the text @code{%i} and @code{int} referring to the format string and the
+expected type due to it should be colorized as @code{highlight-a} both
+in the diagnostics message and in the range quoted in the @code{range}.
+
+@item
+the text @code{const char *} in the diagnostic message and in the quoted
+range should be colorized as @code{highlight-b}.
+
+@end itemize
+
+This can be implemented by using e.g.
+@code{highlight_colors::actual} and
+@code{highlight_colors::expected} when adding ranges to
+@code{rich_location} instances, and e.g. by using the @code{%e} format
+code for @code{pretty_printer} to use a @code{pp_element *}, and using
+appropriate member functions of pp_element to add colorization.
+
 @subsection Spelling and Terminology
 
 See the @uref{https://gcc.gnu.org/codingconventions.html#Spelling
diff --git a/gcc/gcc-rich-location.cc b/gcc/gcc-rich-location.cc
index e9d753badfe0..568744b39a31 100644
--- a/gcc/gcc-rich-location.cc
+++ b/gcc/gcc-rich-location.cc
@@ -42,22 +42,26 @@ along with GCC; see the file COPYING3.  If not see
    using LABEL if non-NULL. */
 
 void
-gcc_rich_location::add_expr (tree expr, range_label *label)
+gcc_rich_location::add_expr (tree expr,
+			     range_label *label,
+			     const char *highlight_color)
 {
   gcc_assert (expr);
 
   if (CAN_HAVE_RANGE_P (expr))
-    add_range (EXPR_LOCATION (expr), SHOW_RANGE_WITHOUT_CARET, label);
+    add_range (EXPR_LOCATION (expr), SHOW_RANGE_WITHOUT_CARET, label,
+	       highlight_color);
 }
 
 /* If T is an expression, add a range for it to the rich_location,
    using LABEL if non-NULL. */
 
 void
-gcc_rich_location::maybe_add_expr (tree t, range_label *label)
+gcc_rich_location::maybe_add_expr (tree t, range_label *label,
+				   const char *highlight_color)
 {
   if (EXPR_P (t))
-    add_expr (t, label);
+    add_expr (t, label, highlight_color);
 }
 
 /* Add a fixit hint suggesting replacing the range at MISSPELLED_TOKEN_LOC
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index d5009f3ff59b..0cd19aab1a7e 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -32,17 +32,29 @@ class gcc_rich_location : public rich_location
   /* Constructors.  */
 
   /* Constructing from a location.  */
-  explicit gcc_rich_location (location_t loc, const range_label *label = NULL)
-  : rich_location (line_table, loc, label)
+  explicit gcc_rich_location (location_t loc)
+    : rich_location (line_table, loc, nullptr, nullptr)
+  {
+  }
+
+  /* Constructing from a location with a label and a highlight color.  */
+  explicit gcc_rich_location (location_t loc,
+			      const range_label *label,
+			      const char *highlight_color)
+    : rich_location (line_table, loc, label, highlight_color)
   {
   }
 
   /* Methods for adding ranges via gcc entities.  */
   void
-  add_expr (tree expr, range_label *label);
+  add_expr (tree expr,
+	    range_label *label,
+	    const char *highlight_color);
 
   void
-  maybe_add_expr (tree t, range_label *label);
+  maybe_add_expr (tree t,
+		  range_label *label,
+		  const char *highlight_color);
 
   void add_fixit_misspelled_id (location_t misspelled_token_loc,
 				tree hint_id);
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index d80b604a48df..00f93d00f969 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -4350,6 +4350,10 @@ driver_handle_option (struct gcc_options *opts,
       diagnostic_urls_init (dc, value);
       break;
 
+    case OPT_fdiagnostics_show_highlight_colors:
+      dc->set_show_highlight_colors (value);
+      break;
+
     case OPT_fdiagnostics_format_:
 	{
 	  const char *basename = (opts->x_dump_base_name ? opts->x_dump_base_name
diff --git a/gcc/lto-wrapper.cc b/gcc/lto-wrapper.cc
index 84835a888ef1..6bfc96590a5a 100644
--- a/gcc/lto-wrapper.cc
+++ b/gcc/lto-wrapper.cc
@@ -311,6 +311,7 @@ merge_and_complain (vec<cl_decoded_option> &decoded_options,
 	  /* Fallthru.  */
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_event_links:
+	case OPT_fdiagnostics_show_highlight_colors:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
 	case OPT_fdiagnostics_show_option:
@@ -728,6 +729,7 @@ append_compiler_options (obstack *argv_obstack, vec<cl_decoded_option> opts)
 	{
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_event_links:
+	case OPT_fdiagnostics_show_highlight_colors:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
 	case OPT_fdiagnostics_show_option:
@@ -788,6 +790,7 @@ append_diag_options (obstack *argv_obstack, vec<cl_decoded_option> opts)
 	case OPT_fdiagnostics_format_:
 	case OPT_fdiagnostics_show_caret:
 	case OPT_fdiagnostics_show_event_links:
+	case OPT_fdiagnostics_show_highlight_colors:
 	case OPT_fdiagnostics_show_labels:
 	case OPT_fdiagnostics_show_line_numbers:
 	case OPT_fdiagnostics_show_option:
@@ -1593,6 +1596,10 @@ run_gcc (unsigned argc, char *argv[])
 	  diagnostic_color_init (global_dc, option->value);
 	  break;
 
+	case OPT_fdiagnostics_show_highlight_colors:
+	  global_dc->set_show_highlight_colors (option->value);
+	  break;
+
 	default:
 	  break;
 	}
diff --git a/gcc/opts-common.cc b/gcc/opts-common.cc
index fcabc27dd542..3b37de80ca6a 100644
--- a/gcc/opts-common.cc
+++ b/gcc/opts-common.cc
@@ -1094,6 +1094,8 @@ decode_cmdline_options_to_array (unsigned int argc, const char **argv,
 	    "-fdiagnostics-path-format=separate-events",
 	    "-fdiagnostics-text-art-charset=none",
 	    "-fno-diagnostics-show-event-links"
+	    /* We don't put "-fno-diagnostics-show-highlight-colors" here
+	       as -fdiagnostics-color=never makes it redundant.  */
 	  };
 	  const int num_expanded = ARRAY_SIZE (expanded_args);
 	  opt_array_len += num_expanded - 1;
diff --git a/gcc/opts-global.cc b/gcc/opts-global.cc
index 648f8fbf212f..e2b83d0f6f3a 100644
--- a/gcc/opts-global.cc
+++ b/gcc/opts-global.cc
@@ -256,11 +256,16 @@ init_options_once (void)
   /* Perform language-specific options initialization.  */
   initial_lang_mask = lang_hooks.option_lang_mask ();
 
+  const bool show_highlight_colors
+    = pp_show_highlight_colors (global_dc->printer);
+
   lang_hooks.initialize_diagnostics (global_dc);
   /* ??? Ideally, we should do this earlier and the FEs will override
      it if desired (none do it so far).  However, the way the FEs
      construct their pretty-printers means that all previous settings
      are overriden.  */
+  global_dc->set_show_highlight_colors (show_highlight_colors);
+
   diagnostic_color_init (global_dc);
   diagnostic_urls_init (global_dc);
 }
diff --git a/gcc/opts.cc b/gcc/opts.cc
index d7e0126e11f8..be90a632338f 100644
--- a/gcc/opts.cc
+++ b/gcc/opts.cc
@@ -2992,6 +2992,10 @@ common_handle_option (struct gcc_options *opts,
       dc->set_escape_format ((enum diagnostics_escape_format)value);
       break;
 
+    case OPT_fdiagnostics_show_highlight_colors:
+      dc->set_show_highlight_colors (value);
+      break;
+
     case OPT_fdiagnostics_show_cwe:
       dc->set_show_cwe (value);
       break;
@@ -3822,6 +3826,7 @@ gen_command_line_string (cl_decoded_option *options,
       case OPT_grecord_gcc_switches:
       case OPT_frecord_gcc_switches:
       case OPT__output_pch:
+      case OPT_fdiagnostics_show_highlight_colors:
       case OPT_fdiagnostics_show_location_:
       case OPT_fdiagnostics_show_option:
       case OPT_fdiagnostics_show_caret:
diff --git a/gcc/pretty-print-markup.h b/gcc/pretty-print-markup.h
new file mode 100644
index 000000000000..b35632a79da9
--- /dev/null
+++ b/gcc/pretty-print-markup.h
@@ -0,0 +1,75 @@
+/* Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_PRETTY_PRINT_MARKUP_H
+#define GCC_PRETTY_PRINT_MARKUP_H
+
+#include "diagnostic-color.h"
+
+namespace pp_markup {
+
+class context
+{
+public:
+  context (pretty_printer &pp,
+	   output_buffer &buf,
+	   unsigned chunk_idx,
+	   bool &quoted,
+	   const urlifier *urlifier)
+  : m_pp (pp),
+    m_buf (buf),
+    m_chunk_idx (chunk_idx),
+    m_quoted (quoted),
+    m_urlifier (urlifier)
+  {
+  }
+
+  void begin_quote ();
+  void end_quote ();
+
+  void begin_highlight_color (const char *color_name);
+  void end_highlight_color ();
+
+  pretty_printer &m_pp;
+  output_buffer &m_buf;
+  unsigned m_chunk_idx;
+  bool &m_quoted;
+  const urlifier *m_urlifier;
+};
+
+/* Abstract base class for use in pp_format for handling "%e".
+   This can add arbitrary content to the buffer being constructed, and
+   isolates the non-typesafe part of the formatting call in one place.  */
+
+class element
+{
+public:
+  virtual ~element () {}
+  virtual void add_to_phase_2 (context &ctxt) = 0;
+
+protected:
+  element () {}
+
+private:
+  DISABLE_COPY_AND_ASSIGN (element);
+};
+
+} // namespace pp_markup
+
+#endif /* GCC_PRETTY_PRINT_MARKUP_H */
diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc
index ec44172f24df..64713803dbe7 100644
--- a/gcc/pretty-print.cc
+++ b/gcc/pretty-print.cc
@@ -24,9 +24,11 @@ along with GCC; see the file COPYING3.  If not see
 #include "coretypes.h"
 #include "intl.h"
 #include "pretty-print.h"
+#include "pretty-print-markup.h"
 #include "pretty-print-urlifier.h"
 #include "diagnostic-color.h"
 #include "diagnostic-event-id.h"
+#include "diagnostic-highlight-colors.h"
 #include "selftest.h"
 
 #if HAVE_ICONV
@@ -1321,6 +1323,8 @@ chunk_info::on_end_quote (pretty_printer *pp,
    %Z: Requires two arguments - array of int, and len. Prints elements
    of the array.
 
+   %e: Consumes a pp_element * argument.
+
    Arguments can be used sequentially, or through %N$ resp. *N$
    notation Nth argument after the format string.  If %N$ / *N$
    notation is used, it must be used for all arguments, except %m, %%,
@@ -1761,6 +1765,17 @@ pretty_printer::format (text_info *text,
 	  begin_url (va_arg (*text->m_args_ptr, const char *));
 	  break;
 
+	case 'e':
+	  {
+	    pp_element *element
+	      = va_arg (*text->m_args_ptr, pp_element *);
+	    pp_markup::context ctxt (*this, *buffer, chunk,
+				     quote, /* by reference */
+				     urlifier);
+	    element->add_to_phase_2 (ctxt);
+	  }
+	  break;
+
 	default:
 	  {
 	    bool ok;
@@ -2101,6 +2116,7 @@ pretty_printer::pretty_printer (int maximum_length)
     m_need_newline (false),
     m_translate_identifiers (true),
     m_show_color (false),
+    m_show_highlight_colors (false),
     m_url_format (URL_FORMAT_NONE),
     m_skipping_null_url (false)
 {
@@ -2125,6 +2141,7 @@ pretty_printer::pretty_printer (const pretty_printer &other)
   m_need_newline (other.m_need_newline),
   m_translate_identifiers (other.m_translate_identifiers),
   m_show_color (other.m_show_color),
+  m_show_highlight_colors (other.m_show_highlight_colors),
   m_url_format (other.m_url_format),
   m_skipping_null_url (false)
 {
@@ -2260,6 +2277,15 @@ pp_string (pretty_printer *pp, const char *str)
   pp_maybe_wrap_text (pp, str, str + strlen (str));
 }
 
+/* As per pp_string, but only append the first LEN of STR.  */
+
+void
+pp_string_n (pretty_printer *pp, const char *str, size_t len)
+{
+  gcc_checking_assert (str);
+  pp_maybe_wrap_text (pp, str, str + len);
+}
+
 /* Append code point C to the output area of PRETTY-PRINTER, encoding it
    as UTF-8.  */
 
@@ -2710,6 +2736,55 @@ pretty_printer::end_url ()
     pp_string (this, get_end_url_string (this));
 }
 
+/* class pp_markup::context.  */
+
+void
+pp_markup::context::begin_quote ()
+{
+  gcc_assert (!m_quoted);
+  pp_begin_quote (&m_pp, pp_show_color (&m_pp));
+  m_buf.cur_chunk_array->on_begin_quote (m_buf, m_chunk_idx, m_urlifier);
+  m_quoted = true;
+}
+
+void
+pp_markup::context::end_quote ()
+{
+  /* Bail out if the quotes have already been ended, such as by
+     printing a type emitting "TYPEDEF' {aka `TYPE'}".  */
+  if (!m_quoted)
+    return;
+  m_buf.cur_chunk_array->on_end_quote (&m_pp, m_buf, m_chunk_idx, m_urlifier);
+  pp_end_quote (&m_pp, pp_show_color (&m_pp));
+  m_quoted = false;
+}
+
+void
+pp_markup::context::begin_highlight_color (const char *color_name)
+{
+  if (!pp_show_highlight_colors (&m_pp))
+    return;
+  pp_string (&m_pp, colorize_start (pp_show_color (&m_pp), color_name));
+}
+
+void
+pp_markup::context::end_highlight_color ()
+{
+  if (!pp_show_highlight_colors (&m_pp))
+    return;
+  const char *colorstr = colorize_stop (pp_show_color (&m_pp));
+  obstack_grow (&m_buf.chunk_obstack, colorstr, strlen (colorstr));
+}
+
+
+/* Color names for expressing "expected" vs "actual" values.  */
+const char *const highlight_colors::expected = "highlight-a";
+const char *const highlight_colors::actual   = "highlight-b";
+
+/* Color names for expressing "LHS" vs "RHS" values in a binary operation.  */
+const char *const highlight_colors::lhs = "highlight-a";
+const char *const highlight_colors::rhs = "highlight-b";
+
 #if CHECKING_P
 
 namespace selftest {
@@ -2802,6 +2877,22 @@ assert_pp_format_colored (const location &loc, const char *expected,
                       (ARG1), (ARG2), (ARG3));		      \
   SELFTEST_END_STMT
 
+class test_element : public pp_element
+{
+public:
+  test_element (const char *text) : m_text (text) {}
+
+  void add_to_phase_2 (pp_markup::context &ctxt) final override
+  {
+    ctxt.begin_quote ();
+    pp_string (&ctxt.m_pp, m_text);
+    ctxt.end_quote ();
+  }
+
+private:
+  const char *m_text;
+};
+
 /* Verify that pp_format works, for various format codes.  */
 
 static void
@@ -2914,6 +3005,15 @@ test_pp_format ()
   int v2[] = { 0 }; 
   ASSERT_PP_FORMAT_3 ("0 12345678", "%Z %x", v2, 1, 0x12345678);
 
+  /* Verify %e.  */
+  {
+    test_element foo ("foo");
+    test_element bar ("bar");
+    ASSERT_PP_FORMAT_2 ("before `foo' `bar' after",
+			"before %e %e after",
+			&foo, &bar);
+  }
+
   /* Verify that combinations work, along with unformatted text.  */
   assert_pp_format (SELFTEST_LOCATION,
 		    "the quick brown fox jumps over the lazy dog",
@@ -3260,6 +3360,19 @@ test_urlification ()
       ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar",
        pp_formatted_text (&pp));
   }
+
+  /* Example of %e.  */
+  {
+    pretty_printer pp;
+    pp.set_url_format (URL_FORMAT_ST);
+    test_element elem ("-foption");
+    pp_printf_with_urlifier (&pp, &urlifier,
+			     "foo %e bar",
+			     &elem);
+    ASSERT_STREQ
+      ("foo `\33]8;;http://example.com\33\\-foption\33]8;;\33\\' bar",
+       pp_formatted_text (&pp));
+  }
 }
 
 /* Test multibyte awareness.  */
diff --git a/gcc/pretty-print.h b/gcc/pretty-print.h
index b41d3ce31d22..d28814a84b89 100644
--- a/gcc/pretty-print.h
+++ b/gcc/pretty-print.h
@@ -73,6 +73,10 @@ class quoting_info;
 class output_buffer;
 class urlifier;
 
+namespace pp_markup {
+  class context;
+} // namespace pp_markup
+
 /* The chunk_info data structure forms a stack of the results from the
    first phase of formatting (pp_format) which have not yet been
    output (pp_output_formatted_text).  A stack is necessary because
@@ -81,6 +85,7 @@ class urlifier;
 class chunk_info
 {
   friend class pretty_printer;
+  friend class pp_markup::context;
 
 public:
   const char * const *get_args () const { return m_args; }
@@ -245,6 +250,7 @@ inline bool & pp_translate_identifiers (pretty_printer *pp);
 inline bool & pp_show_color (pretty_printer *pp);
 inline printer_fn &pp_format_decoder (pretty_printer *pp);
 inline format_postprocessor *& pp_format_postprocessor (pretty_printer *pp);
+inline bool & pp_show_highlight_colors (pretty_printer *pp);
 
 class urlifier;
 
@@ -271,6 +277,7 @@ public:
   friend bool & pp_show_color (pretty_printer *pp);
   friend printer_fn &pp_format_decoder (pretty_printer *pp);
   friend format_postprocessor *& pp_format_postprocessor (pretty_printer *pp);
+  friend bool & pp_show_highlight_colors (pretty_printer *pp);
 
   /* Default construct a pretty printer with specified
      maximum line length cut off limit.  */
@@ -373,6 +380,10 @@ private:
   /* Nonzero means that text should be colorized.  */
   bool m_show_color;
 
+  /* True means that pertinent sections within the text should be
+     highlighted with color.  */
+  bool m_show_highlight_colors;
+
   /* Whether URLs should be emitted, and which terminator to use.  */
   diagnostic_url_format m_url_format;
 
@@ -441,6 +452,12 @@ pp_format_postprocessor (pretty_printer *pp)
   return pp->m_format_postprocessor;
 }
 
+inline bool &
+pp_show_highlight_colors (pretty_printer *pp)
+{
+  return pp->m_show_highlight_colors;
+}
+
 /* Maximum characters per line in automatic line wrapping mode.
    Zero means don't wrap lines.  */
 inline int &
@@ -582,6 +599,7 @@ extern void pp_indent (pretty_printer *);
 extern void pp_newline (pretty_printer *);
 extern void pp_character (pretty_printer *, int);
 extern void pp_string (pretty_printer *, const char *);
+extern void pp_string_n (pretty_printer *, const char *, size_t);
 extern void pp_unicode_character (pretty_printer *, unsigned);
 
 extern void pp_write_text_to_stream (pretty_printer *);
diff --git a/gcc/substring-locations.cc b/gcc/substring-locations.cc
index 96473c02c6f3..00ad8edd5869 100644
--- a/gcc/substring-locations.cc
+++ b/gcc/substring-locations.cc
@@ -27,6 +27,15 @@ along with GCC; see the file COPYING3.  If not see
 #include "langhooks.h"
 #include "substring-locations.h"
 #include "gcc-rich-location.h"
+#include "diagnostic-highlight-colors.h"
+
+const char *const
+format_string_diagnostic_t::highlight_color_format_string
+  = highlight_colors::expected;
+
+const char *const
+format_string_diagnostic_t::highlight_color_param
+  = highlight_colors::actual;
 
 /* format_string_diagnostic_t's ctor, giving information for use by
    the emit_warning* member functions, as follows:
@@ -184,10 +193,12 @@ format_string_diagnostic_t::emit_warning_n_va (int opt,
     primary_label = m_fmt_label;
 
   auto_diagnostic_group d;
-  gcc_rich_location richloc (primary_loc, primary_label);
+  gcc_rich_location richloc (primary_loc, primary_label,
+			     highlight_color_format_string);
 
   if (m_param_loc != UNKNOWN_LOCATION)
-    richloc.add_range (m_param_loc, SHOW_RANGE_WITHOUT_CARET, m_param_label);
+    richloc.add_range (m_param_loc, SHOW_RANGE_WITHOUT_CARET, m_param_label,
+		       highlight_color_param);
 
   if (!err && m_corrected_substring && substring_within_range)
     richloc.add_fixit_replace (fmt_substring_range, m_corrected_substring);
@@ -220,8 +231,10 @@ format_string_diagnostic_t::emit_warning_n_va (int opt,
     if (warned)
       {
 	/* Use fmt_label in the note for case 2.  */
-	rich_location substring_richloc (line_table, fmt_substring_loc,
-					 m_fmt_label);
+	rich_location substring_richloc
+	  (line_table, fmt_substring_loc,
+	   m_fmt_label,
+	   highlight_color_format_string);
 	if (m_corrected_substring)
 	  substring_richloc.add_fixit_replace (fmt_substring_range,
 					       m_corrected_substring);
diff --git a/gcc/substring-locations.h b/gcc/substring-locations.h
index 68a3b80a45f3..b286ae8e3b5f 100644
--- a/gcc/substring-locations.h
+++ b/gcc/substring-locations.h
@@ -79,6 +79,9 @@ class substring_loc
 class format_string_diagnostic_t
 {
  public:
+  static const char * const highlight_color_format_string;
+  static const char * const highlight_color_param;
+
   format_string_diagnostic_t (const substring_loc &fmt_loc,
 			      const range_label *fmt_label,
 			      location_t param_loc,
diff --git a/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops-highlight-colors.C b/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops-highlight-colors.C
new file mode 100644
index 000000000000..12603456b484
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops-highlight-colors.C
@@ -0,0 +1,34 @@
+/* Verify that colorization affects both text within diagnostic messages
+   and underlined ranges of quoted source, and that the types we use
+   match up between them.
+   Also implicitly verify that -fdiagnostics-show-highlight-colors is
+   on by default.  */
+
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-color=always" } */
+
+struct s {};
+struct t {};
+typedef struct s S;
+typedef struct t T;
+
+extern S callee_4a (void);
+extern T callee_4b (void);
+
+int test_4 (void)
+{
+  return callee_4a () + callee_4b ();
+
+  /* { dg-begin-multiline-output "" }
+  [m[K [01;31m[Kerror: [m[Kno match for '[01m[Koperator+[m[K' (operand types are '[01m[K[01;32m[KS[m[K' {aka '[01m[K[01;32m[Ks[m[K'}[m[K and '[01m[K[01;34m[KT[m[K' {aka '[01m[K[01;34m[Kt[m[K'}[m[K)
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   return [01;32m[Kc[m[K[01;32m[Ka[m[K[01;32m[Kl[m[K[01;32m[Kl[m[K[01;32m[Ke[m[K[01;32m[Ke[m[K[01;32m[K_[m[K[01;32m[K4[m[K[01;32m[Ka[m[K[01;32m[K [m[K[01;32m[K([m[K[01;32m[K)[m[K [01;31m[K+[m[K [01;34m[Kc[m[K[01;34m[Ka[m[K[01;34m[Kl[m[K[01;34m[Kl[m[K[01;34m[Ke[m[K[01;34m[Ke[m[K[01;34m[K_[m[K[01;34m[K4[m[K[01;34m[Kb[m[K[01;34m[K [m[K[01;34m[K([m[K[01;34m[K)[m[K;
+          [01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K [01;31m[K^[m[K [01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K
+                    [01;32m[K|[m[K              [01;34m[K|[m[K
+                    [01;32m[KS {aka s}[m[K      [01;34m[KT {aka t}[m[K
+     { dg-end-multiline-output "" } */
+
+  /* { dg-prune-output "In function" } */
+  /* { dg-prune-output "bad-binary-ops-highlight-colors.C" } */
+}
diff --git a/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops-no-highlight-colors.C b/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops-no-highlight-colors.C
new file mode 100644
index 000000000000..5b1b65a72939
--- /dev/null
+++ b/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops-no-highlight-colors.C
@@ -0,0 +1,29 @@
+/* Verify that -fno-diagnostics-show-highlight-colors works.  */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-color=always -fno-diagnostics-show-highlight-colors" } */
+
+struct s {};
+struct t {};
+typedef struct s S;
+typedef struct t T;
+
+extern S callee_4a (void);
+extern T callee_4b (void);
+
+int test_4 (void)
+{
+  return callee_4a () + callee_4b ();
+
+  /* { dg-begin-multiline-output "" }
+  [m[K [01;31m[Kerror: [m[Kno match for '[01m[Koperator+[m[K' (operand types are '[01m[KS[m[K' {aka '[01m[Ks[m[K'} and '[01m[KT[m[K' {aka '[01m[Kt[m[K'})
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   return [32m[Kcallee_4a ()[m[K [01;31m[K+[m[K [34m[Kcallee_4b ()[m[K;
+          [32m[K~~~~~~~~~~~~[m[K [01;31m[K^[m[K [34m[K~~~~~~~~~~~~[m[K
+                    [32m[K|[m[K              [34m[K|[m[K
+                    [32m[KS {aka s}[m[K      [34m[KT {aka t}[m[K
+     { dg-end-multiline-output "" } */
+
+  /* { dg-prune-output "In function" } */
+  /* { dg-prune-output "bad-binary-ops-no-highlight-colors.C" } */
+}
diff --git a/gcc/testsuite/g++.dg/plugin/plugin.exp b/gcc/testsuite/g++.dg/plugin/plugin.exp
index 88f9b49ba880..b5f8743872ca 100644
--- a/gcc/testsuite/g++.dg/plugin/plugin.exp
+++ b/gcc/testsuite/g++.dg/plugin/plugin.exp
@@ -77,6 +77,7 @@ set plugin_test_list [list \
           location-overflow-test-pr100796.c } \
     { show_template_tree_color_plugin.c \
     	  show-template-tree-color.C \
+    	  show-template-tree-color-no-highlight-colors.C \
     	  show-template-tree-color-labels.C \
     	  show-template-tree-color-no-elide-type.C } \
     { comment_plugin.c comments-1.C } \
diff --git a/gcc/testsuite/g++.dg/plugin/show-template-tree-color-labels.C b/gcc/testsuite/g++.dg/plugin/show-template-tree-color-labels.C
index 462e1bd33ee5..75488cb6e0f1 100644
--- a/gcc/testsuite/g++.dg/plugin/show-template-tree-color-labels.C
+++ b/gcc/testsuite/g++.dg/plugin/show-template-tree-color-labels.C
@@ -15,11 +15,11 @@ void test_1 (vector<double> vec)
 {
   fn_1 (vec);
   /* { dg-begin-multiline-output "" }
-could not convert '[01m[Kvec[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;32m[Kint[m[K>[m[K'
-   fn_1 ([01;31m[Kvec[m[K);
-         [01;31m[K^~~[m[K
-         [01;31m[K|[m[K
-         [01;31m[Kvector<double>[m[K
+could not convert '[01m[Kvec[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;34m[Kint[m[K>[m[K'
+   fn_1 ([01;32m[Kv[m[K[01;32m[Ke[m[K[01;32m[Kc[m[K);
+         [01;32m[K^[m[K[01;32m[K~[m[K[01;32m[K~[m[K
+         [01;32m[K|[m[K
+         [01;32m[Kvector<double>[m[K
      { dg-end-multiline-output "" } */
   // TODO: we don't yet highlight the mismatching part with color
 }
@@ -28,11 +28,11 @@ void test_2 (const map<int, double> &m)
 {
   fn_2 (m);
   /* { dg-begin-multiline-output "" }
-could not convert '[01m[Km[m[K' from '[01m[Kmap<[...],[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<[...],[01;32m[Kint[m[K>[m[K'
-   fn_2 ([01;31m[Km[m[K);
-         [01;31m[K^[m[K
-         [01;31m[K|[m[K
-         [01;31m[Kmap<[...],double>[m[K
+could not convert '[01m[Km[m[K' from '[01m[Kmap<[...],[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<[...],[01;34m[Kint[m[K>[m[K'
+   fn_2 ([01;32m[Km[m[K);
+         [01;32m[K^[m[K
+         [01;32m[K|[m[K
+         [01;32m[Kmap<[...],double>[m[K
      { dg-end-multiline-output "" } */
   // TODO: we don't yet highlight the mismatching part with color
 }
diff --git a/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-elide-type.C b/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-elide-type.C
index cab0359f240d..758a77fdff2b 100644
--- a/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-elide-type.C
+++ b/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-elide-type.C
@@ -15,16 +15,16 @@ void test ()
 {
   fn_1 (vector<double> ());
   /* { dg-begin-multiline-output "" }
-could not convert '[01m[Kvector<double>()[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;32m[Kint[m[K>[m[K'
+could not convert '[01m[Kvector<double>()[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;34m[Kint[m[K>[m[K'
   vector<
-    [[01;32m[Kdouble[m[K != [01;32m[Kint[m[K]>
+    [[01;32m[Kdouble[m[K != [01;34m[Kint[m[K]>
      { dg-end-multiline-output "" } */
 
   fn_2 (map<int, double>());
   /* { dg-begin-multiline-output "" }
-could not convert '[01m[Kmap<int, double>()[m[K' from '[01m[Kmap<int,[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<int,[01;32m[Kint[m[K>[m[K'
+could not convert '[01m[Kmap<int, double>()[m[K' from '[01m[Kmap<int,[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<int,[01;34m[Kint[m[K>[m[K'
   map<
     int,
-    [[01;32m[Kdouble[m[K != [01;32m[Kint[m[K]>
+    [[01;32m[Kdouble[m[K != [01;34m[Kint[m[K]>
      { dg-end-multiline-output "" } */
 }
diff --git a/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-highlight-colors.C b/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-highlight-colors.C
new file mode 100644
index 000000000000..5329f90fdda0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/plugin/show-template-tree-color-no-highlight-colors.C
@@ -0,0 +1,32 @@
+/* Verify colorization of the output of -fdiagnostics-show-template-tree,
+   and within the %H and %I format codes, but with
+   -fno-diagnostics-show-highlight-colors.
+
+   Doing so requires a plugin; see the comments in the plugin for the
+   rationale.  */
+
+// { dg-options "-fdiagnostics-show-template-tree -fdiagnostics-color=always -fno-diagnostics-show-highlight-colors" }
+
+template<typename> struct vector {};
+template<typename, typename> struct map {};
+
+void fn_1(vector<int>);
+void fn_2(map<int, int>);
+
+void test ()
+{
+  fn_1 (vector<double> ());
+  /* { dg-begin-multiline-output "" }
+could not convert '[01m[Kvector<double>()[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;32m[Kint[m[K>[m[K'
+  vector<
+    [[01;32m[Kdouble[m[K != [01;32m[Kint[m[K]>
+     { dg-end-multiline-output "" } */
+
+  fn_2 (map<int, double>());
+  /* { dg-begin-multiline-output "" }
+could not convert '[01m[Kmap<int, double>()[m[K' from '[01m[Kmap<[...],[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<[...],[01;32m[Kint[m[K>[m[K'
+  map<
+    [...],
+    [[01;32m[Kdouble[m[K != [01;32m[Kint[m[K]>
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/plugin/show-template-tree-color.C b/gcc/testsuite/g++.dg/plugin/show-template-tree-color.C
index eb99a3ed8dac..cc60ba6f732b 100644
--- a/gcc/testsuite/g++.dg/plugin/show-template-tree-color.C
+++ b/gcc/testsuite/g++.dg/plugin/show-template-tree-color.C
@@ -15,16 +15,16 @@ void test ()
 {
   fn_1 (vector<double> ());
   /* { dg-begin-multiline-output "" }
-could not convert '[01m[Kvector<double>()[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;32m[Kint[m[K>[m[K'
+could not convert '[01m[Kvector<double>()[m[K' from '[01m[Kvector<[01;32m[Kdouble[m[K>[m[K' to '[01m[Kvector<[01;34m[Kint[m[K>[m[K'
   vector<
-    [[01;32m[Kdouble[m[K != [01;32m[Kint[m[K]>
+    [[01;32m[Kdouble[m[K != [01;34m[Kint[m[K]>
      { dg-end-multiline-output "" } */
 
   fn_2 (map<int, double>());
   /* { dg-begin-multiline-output "" }
-could not convert '[01m[Kmap<int, double>()[m[K' from '[01m[Kmap<[...],[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<[...],[01;32m[Kint[m[K>[m[K'
+could not convert '[01m[Kmap<int, double>()[m[K' from '[01m[Kmap<[...],[01;32m[Kdouble[m[K>[m[K' to '[01m[Kmap<[...],[01;34m[Kint[m[K>[m[K'
   map<
     [...],
-    [[01;32m[Kdouble[m[K != [01;32m[Kint[m[K]>
+    [[01;32m[Kdouble[m[K != [01;34m[Kint[m[K]>
      { dg-end-multiline-output "" } */
 }
diff --git a/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-1.C b/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-1.C
new file mode 100644
index 000000000000..dd41b08d621e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-1.C
@@ -0,0 +1,200 @@
+// Test of -Wformat for GCC-specific formats
+// Copy of gcc.dg/format/gcc_diag-10.c, with class pp_element added.
+// { dg-do compile }
+// { dg-options "-Wformat" }
+
+/* Magic identifiers must be set before the attribute is used.  */
+
+typedef long long __gcc_host_wide_int__;
+
+typedef struct location_s
+{
+  const char *file;
+  int line;
+} location_t;
+
+union tree_node;
+typedef union tree_node *tree;
+
+/* Define gimple as a dummy type.  The typedef must be provided for
+   the C test to find the symbol.  */
+typedef struct gimple gimple;
+
+/* Likewise for gimple.  */
+typedef struct cgraph_node cgraph_node;
+
+/* Likewise for diagnostic_event_id_t.  */
+typedef struct diagnostic_event_id_t diagnostic_event_id_t;
+
+namespace pp_markup { class element; }
+typedef pp_markup::element pp_element;
+
+#define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
+
+void diag (const char*, ...) FORMAT (diag);
+void cdiag (const char*, ...) FORMAT (cdiag);
+void tdiag (const char*, ...) FORMAT (tdiag);
+void cxxdiag (const char*, ...) FORMAT (cxxdiag);
+void dump (const char*, ...) FORMAT (dump_printf);
+
+void test_diag (tree t, gimple *gc, diagnostic_event_id_t *event_id_ptr,
+		pp_element *elem)
+{
+  diag ("%<");   /* { dg-warning "unterminated quoting directive" } */
+  diag ("%>");   /* { dg-warning "unmatched quoting directive " } */
+  diag ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  diag ("%G", gc); /* { dg-warning "format" } */
+  diag ("%K", t); /* { dg-warning "format" } */
+  diag ("%@", event_id_ptr);
+
+  diag ("%R");       /* { dg-warning "unmatched color reset directive" } */
+  diag ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  diag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  diag ("%r%R", "");
+  diag ("%r%r%R", "", "");
+  diag ("%r%R%r%R", "", "");
+
+  diag ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  diag ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  diag ("%<%r%R%>", "");
+
+  diag ("%e", elem);
+  diag ("%e", 42); /* { dg-warning "format" } */
+}
+
+void test_cdiag (tree t, gimple *gc)
+{
+  cdiag ("%<");   /* { dg-warning "unterminated quoting directive" } */
+  cdiag ("%>");   /* { dg-warning "unmatched quoting directive " } */
+  cdiag ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  cdiag ("%D", t);       /* { dg-warning ".D. conversion used unquoted" } */
+  cdiag ("%E", t);
+  cdiag ("%F", t);       /* { dg-warning ".F. conversion used unquoted" } */
+  cdiag ("%G", gc);      /* { dg-warning "format" } */
+  cdiag ("%K", t);       /* { dg-warning "format" } */
+
+  cdiag ("%R");       /* { dg-warning "unmatched color reset directive" } */
+  cdiag ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  cdiag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  cdiag ("%r%R", "");
+  cdiag ("%r%r%R", "", "");
+  cdiag ("%r%R%r%R", "", "");
+
+  cdiag ("%T", t);       /* { dg-warning ".T. conversion used unquoted" } */
+  cdiag ("%V", t);       /* { dg-warning ".V. conversion used unquoted" } */
+
+  cdiag ("%<%D%>", t);
+  cdiag ("%<%E%>", t);
+  cdiag ("%<%F%>", t);
+  cdiag ("%<%G%>", gc);  /* { dg-warning "format" } */
+  cdiag ("%<%K%>", t);   /* { dg-warning "format" } */
+
+  cdiag ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  cdiag ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  cdiag ("%<%r%R%>", "");
+
+  cdiag ("%<%T%>", t);
+  cdiag ("%<%V%>", t);
+
+  cdiag ("%<%qD%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  cdiag ("%<%qE%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  cdiag ("%<%qT%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+}
+
+void test_tdiag (tree t, gimple *gc)
+{
+  tdiag ("%<");       /* { dg-warning "unterminated quoting directive" } */
+  tdiag ("%>");       /* { dg-warning "unmatched quoting directive " } */
+  tdiag ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  tdiag ("%D", t);       /* { dg-warning ".D. conversion used unquoted" } */
+  tdiag ("%E", t);
+  tdiag ("%G", gc);     /* { dg-warning "format" } */
+  tdiag ("%K", t);      /* { dg-warning "format" } */
+
+  tdiag ("%R");          /* { dg-warning "unmatched color reset directive" } */
+  tdiag ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  tdiag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  tdiag ("%r%R", "");
+  tdiag ("%r%R", "");
+  tdiag ("%r%r%R", "", "");
+  tdiag ("%r%R%r%R", "", "");
+
+  tdiag ("%T", t);       /* { dg-warning ".T. conversion used unquoted" } */
+
+  tdiag ("%<%D%>", t);
+  tdiag ("%<%E%>", t);
+  tdiag ("%<%G%>", gc);  /* { dg-warning "format" } */
+  tdiag ("%<%K%>", t);   /* { dg-warning "format" } */
+
+  tdiag ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  tdiag ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  tdiag ("%<%r%R%>", "");
+
+  tdiag ("%<%T%>", t);
+
+  tdiag ("%<%qD%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  tdiag ("%<%qE%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+  tdiag ("%<%qT%>", t);  /* { dg-warning ".q. flag used within a quoted sequence" } */
+}
+
+void test_cxxdiag (tree t, gimple *gc)
+{
+  cxxdiag ("%A", t);     /* { dg-warning ".A. conversion used unquoted" } */
+  cxxdiag ("%D", t);     /* { dg-warning ".D. conversion used unquoted" } */
+  cxxdiag ("%E", t);
+  cxxdiag ("%F", t);     /* { dg-warning ".F. conversion used unquoted" } */
+  cxxdiag ("%G", gc);    /* { dg-warning "format" } */
+  cxxdiag ("%K", t);     /* { dg-warning "format" } */
+
+  cxxdiag ("%R");        /* { dg-warning "unmatched color reset directive" } */
+  cxxdiag ("%r", "");    /* { dg-warning "unterminated color directive" } */
+  cxxdiag ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  cxxdiag ("%r%R", "");
+  cxxdiag ("%r%R", "");
+  cxxdiag ("%r%r%R", "", "");
+  cxxdiag ("%r%R%r%R", "", "");
+
+  cxxdiag ("%S", t);     /* { dg-warning ".S. conversion used unquoted" } */
+  cxxdiag ("%T", t);     /* { dg-warning ".T. conversion used unquoted" } */
+  cxxdiag ("%V", t);     /* { dg-warning ".V. conversion used unquoted" } */
+  cxxdiag ("%X", t);     /* { dg-warning ".X. conversion used unquoted" } */
+
+  cxxdiag ("%<%A%>", t);
+  cxxdiag ("%<%D%>", t);
+  cxxdiag ("%<%E%>", t);
+  cxxdiag ("%<%F%>", t);
+  cxxdiag ("%<%R%>");    /* { dg-warning "unmatched color reset" } */
+  cxxdiag ("%<%r%R%>", "");
+  cxxdiag ("%<%S%>", t);
+  cxxdiag ("%<%T%>", t);
+  cxxdiag ("%<%V%>", t);
+  cxxdiag ("%<%X%>", t);
+}
+
+void test_dump (tree t, gimple *stmt, cgraph_node *node)
+{
+  dump ("%<");   /* { dg-warning "unterminated quoting directive" } */
+  dump ("%>");   /* { dg-warning "unmatched quoting directive " } */
+  dump ("%<foo%<bar%>%>");   /* { dg-warning "nested quoting directive" } */
+
+  dump ("%R");       /* { dg-warning "unmatched color reset directive" } */
+  dump ("%r", "");   /* { dg-warning "unterminated color directive" } */
+  dump ("%r%r", "", "");   /* { dg-warning "unterminated color directive" } */
+  dump ("%r%R", "");
+  dump ("%r%r%R", "", "");
+  dump ("%r%R%r%R", "", "");
+
+  dump ("%<%R%>");      /* { dg-warning "unmatched color reset directive" } */
+  dump ("%<%r%>", "");  /* { dg-warning "unterminated color directive" } */
+  dump ("%<%r%R%>", "");
+
+  dump ("%E", stmt);
+  dump ("%T", t);
+  dump ("%G", stmt);
+  dump ("%C", node);
+  dump ("%f", 1.0);
+  dump ("%4.2f", 1.0); /* { dg-warning "format" } */
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-2.C b/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-2.C
new file mode 100644
index 000000000000..0cce53c9c36d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-2.C
@@ -0,0 +1,52 @@
+// Test of -Wformat for subclasses of pp_element
+// { dg-do compile }
+// { dg-options "-Wformat" }
+
+/* Magic identifiers must be set before the attribute is used.  */
+
+typedef long long __gcc_host_wide_int__;
+
+typedef struct location_s
+{
+  const char *file;
+  int line;
+} location_t;
+
+union tree_node;
+typedef union tree_node *tree;
+
+/* Define gimple as a dummy type.  The typedef must be provided for
+   the C test to find the symbol.  */
+typedef struct gimple gimple;
+
+/* Likewise for gimple.  */
+typedef struct cgraph_node cgraph_node;
+
+/* Likewise for diagnostic_event_id_t.  */
+typedef struct diagnostic_event_id_t diagnostic_event_id_t;
+
+namespace pp_markup {
+class element
+{
+};
+} // namespace pp_markup
+typedef pp_markup::element pp_element;
+
+#define FORMAT(kind) __attribute__ ((format (__gcc_## kind ##__, 1, 2)))
+
+void diag (const char*, ...) FORMAT (diag);
+
+class sub_element : public pp_element
+{
+};
+
+class sub_sub_element : public sub_element
+{
+};
+
+void test_diag ()
+{
+  sub_element e1;
+  sub_sub_element e2;
+  diag ("%e %e", &e1, &e2);
+}
diff --git a/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-3.C b/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-3.C
new file mode 100644
index 000000000000..f474778398ef
--- /dev/null
+++ b/gcc/testsuite/g++.dg/warn/Wformat-gcc_diag-3.C
@@ -0,0 +1,54 @@
+// Test of -Wformat for subclasses of pp_element
+// { dg-do compile }
+// { dg-options "-Wformat" }
+
+union tree_node;
+typedef union tree_node *tree;
+class rich_location;
+class pretty_printer;
+
+namespace pp_markup {
+class element {};
+
+} // namespace pp_markup
+
+typedef pp_markup::element pp_element;
+
+extern void error_at (rich_location *, const char *, ...)
+  __attribute__ ((__format__ (__gcc_cdiag__, 2, 3),
+		  __nonnull__ (2)));
+
+namespace highlight_colors {
+
+extern const char *const lhs;
+extern const char *const rhs;
+
+} // namespace highlight_colors
+
+namespace pp_markup {
+
+class element_quoted_type : public element
+{
+public:
+  element_quoted_type (tree type, const char *highlight_color)
+  : m_type (type),
+    m_highlight_color (highlight_color)
+  {
+  }
+private:
+  tree m_type;
+  const char *m_highlight_color;
+};
+
+}
+
+void
+binary_op_error (rich_location *richloc, const char *opname,
+		 tree type0, tree type1)
+{
+  pp_markup::element_quoted_type element_0 (type0, highlight_colors::lhs);
+  pp_markup::element_quoted_type element_1 (type1, highlight_colors::rhs);
+  error_at (richloc,
+	    "invalid operands to binary %s (have %e and %e)",
+	    opname, &element_0, &element_1);
+}
diff --git a/gcc/testsuite/gcc.dg/bad-binary-ops-highlight-colors.c b/gcc/testsuite/gcc.dg/bad-binary-ops-highlight-colors.c
new file mode 100644
index 000000000000..efbcdf39c502
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/bad-binary-ops-highlight-colors.c
@@ -0,0 +1,35 @@
+/* Verify that colorization affects both text within diagnostic messages
+   and underlined ranges of quoted source, and that the types we use
+   match up between them.
+   Also implicitly verify that -fdiagnostics-show-highlight-colors is
+   on by default.  */
+
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-color=always" } */
+
+struct s {};
+struct t {};
+typedef struct s S;
+typedef struct t T;
+
+extern S callee_4a (void);
+extern T callee_4b (void);
+
+int test_4 (void)
+{
+  return callee_4a () + callee_4b ();
+
+  /* { dg-begin-multiline-output "" }
+  [m[K [01;31m[Kerror: [m[Kinvalid operands to binary + (have '[01m[K[01;32m[KS[m[K' {aka [m[K'[01m[K[01;32m[Kstruct s[m[K'}[m[K and '[01m[K[01;34m[KT[m[K' {aka [m[K'[01m[K[01;34m[Kstruct t[m[K'}[m[K)
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   return [01;32m[Kc[m[K[01;32m[Ka[m[K[01;32m[Kl[m[K[01;32m[Kl[m[K[01;32m[Ke[m[K[01;32m[Ke[m[K[01;32m[K_[m[K[01;32m[K4[m[K[01;32m[Ka[m[K[01;32m[K [m[K[01;32m[K([m[K[01;32m[K)[m[K [01;31m[K+[m[K [01;34m[Kc[m[K[01;34m[Ka[m[K[01;34m[Kl[m[K[01;34m[Kl[m[K[01;34m[Ke[m[K[01;34m[Ke[m[K[01;34m[K_[m[K[01;34m[K4[m[K[01;34m[Kb[m[K[01;34m[K [m[K[01;34m[K([m[K[01;34m[K)[m[K;
+          [01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K[01;32m[K~[m[K [01;31m[K^[m[K [01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K
+          [01;32m[K|[m[K              [01;34m[K|[m[K
+          [01;32m[K|[m[K              [01;34m[KT {aka struct t}[m[K
+          [01;32m[KS {aka struct s}[m[K
+     { dg-end-multiline-output "" } */
+
+  /* { dg-prune-output "In function" } */
+  /* { dg-prune-output "bad-binary-ops-highlight-colors.c" } */
+}
diff --git a/gcc/testsuite/gcc.dg/bad-binary-ops-no-highlight-colors.c b/gcc/testsuite/gcc.dg/bad-binary-ops-no-highlight-colors.c
new file mode 100644
index 000000000000..9eb22cb81a53
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/bad-binary-ops-no-highlight-colors.c
@@ -0,0 +1,30 @@
+/* Verify that -fno-diagnostics-show-highlight-colors works.  */
+/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-color=always -fno-diagnostics-show-highlight-colors" } */
+
+struct s {};
+struct t {};
+typedef struct s S;
+typedef struct t T;
+
+extern S callee_4a (void);
+extern T callee_4b (void);
+
+int test_4 (void)
+{
+  return callee_4a () + callee_4b ();
+
+  /* { dg-begin-multiline-output "" }
+  [m[K [01;31m[Kerror: [m[Kinvalid operands to binary + (have '[01m[KS[m[K' {aka [m[K'[01m[Kstruct s[m[K'} and '[01m[KT[m[K' {aka [m[K'[01m[Kstruct t[m[K'})
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   return [32m[Kcallee_4a ()[m[K [01;31m[K+[m[K [34m[Kcallee_4b ()[m[K;
+          [32m[K~~~~~~~~~~~~[m[K [01;31m[K^[m[K [34m[K~~~~~~~~~~~~[m[K
+          [32m[K|[m[K              [34m[K|[m[K
+          [32m[K|[m[K              [34m[KT {aka struct t}[m[K
+          [32m[KS {aka struct s}[m[K
+     { dg-end-multiline-output "" } */
+
+  /* { dg-prune-output "In function" } */
+  /* { dg-prune-output "bad-binary-ops-no-highlight-colors.c" } */
+}
diff --git a/gcc/testsuite/gcc.dg/format/colors.c b/gcc/testsuite/gcc.dg/format/colors.c
new file mode 100644
index 000000000000..43484b70a2a1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/colors.c
@@ -0,0 +1,27 @@
+/* Verify that colorization affects both text within diagnostic messages
+   and underlined ranges of quoted source, and that the types we use
+   match up between them.  */
+
+/* { dg-do compile } */
+/* { dg-options "-Wformat -fdiagnostics-show-caret -fdiagnostics-color=always" } */
+
+#include "format.h"
+
+void test_mismatching_types (const char *msg)
+{
+  printf("hello %i", msg);
+
+/* { dg-begin-multiline-output "" }
+warning: [m[Kformat '[01m[K[01;32m[K%i[m[K[m[K' expects argument of type '[01m[K[01;32m[Kint[m[K[m[K', but argument 2 has type '[01m[K[01;34m[Kconst char *[m[K[m[K' [[01;35m[K-Wformat=[m[K]
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+   printf("hello [01;32m[K%[m[K[01;32m[Ki[m[K", [01;34m[Km[m[K[01;34m[Ks[m[K[01;34m[Kg[m[K);
+                 [01;32m[K~[m[K[01;32m[K^[m[K   [01;34m[K~[m[K[01;34m[K~[m[K[01;34m[K~[m[K
+                  [01;32m[K|[m[K   [01;34m[K|[m[K
+                  [01;32m[Kint[m[K [01;34m[Kconst char *[m[K
+                 [32m[K%s[m[K
+   { dg-end-multiline-output "" } */
+}
+
+/* { dg-prune-output "In function" } */
+/* { dg-prune-output "colors.c" } */
diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_show_trees.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_show_trees.c
index f5c6fc5202f9..44b94dafad39 100644
--- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_show_trees.c
+++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_show_trees.c
@@ -41,7 +41,7 @@ show_tree (tree node)
     return;
 
   gcc_rich_location richloc (EXPR_LOCATION (node));
-  richloc.add_expr (node, NULL);
+  richloc.add_expr (node, nullptr, nullptr);
 
   if (richloc.get_num_locations () < 2)
     {
diff --git a/gcc/toplev.cc b/gcc/toplev.cc
index f715f977b727..8933a89bd281 100644
--- a/gcc/toplev.cc
+++ b/gcc/toplev.cc
@@ -1048,6 +1048,8 @@ general_init (const char *argv0, bool init_signals)
     = global_options_init.x_diagnostics_minimum_margin_width;
   global_dc->m_show_column
     = global_options_init.x_flag_show_column;
+  global_dc->set_show_highlight_colors
+    (global_options_init.x_flag_diagnostics_show_highlight_colors);
   global_dc->m_internal_error = internal_error_function;
   const unsigned lang_mask = lang_hooks.option_lang_mask ();
   global_dc->set_option_hooks (option_enabled,
diff --git a/gcc/tree-pretty-print-markup.h b/gcc/tree-pretty-print-markup.h
new file mode 100644
index 000000000000..05400d980b66
--- /dev/null
+++ b/gcc/tree-pretty-print-markup.h
@@ -0,0 +1,84 @@
+/* Copyright (C) 2024 Free Software Foundation, Inc.
+   Contributed by David Malcolm <dmalcolm@redhat.com>
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_TREE_PRETTY_PRINT_MARKUP_H
+#define GCC_TREE_PRETTY_PRINT_MARKUP_H
+
+#include "pretty-print-markup.h"
+#include "diagnostic-highlight-colors.h"
+
+namespace pp_markup {
+
+/* Concrete subclass of pp_markup::element.
+   Print a type in quotes with the given highlighting color.  */
+
+class element_quoted_type : public element
+{
+public:
+  element_quoted_type (tree type, const char *highlight_color)
+  : m_type (type),
+    m_highlight_color (highlight_color)
+  {
+  }
+
+  void add_to_phase_2 (context &ctxt) override
+  {
+    ctxt.begin_quote ();
+    ctxt.begin_highlight_color (m_highlight_color);
+
+    print_type (ctxt);
+
+    ctxt.end_highlight_color ();
+    ctxt.end_quote ();
+  }
+
+  void print_type (context &ctxt);
+
+private:
+  tree m_type;
+  const char *m_highlight_color;
+};
+
+/* Concrete subclass of pp_markup::element.
+   Print a type in quotes highlighted as the "expected" type.  */
+
+class element_expected_type : public element_quoted_type
+{
+public:
+  element_expected_type (tree type)
+  : element_quoted_type (type, highlight_colors::expected)
+  {
+  }
+};
+
+/* Concrete subclass of pp_markup::element.
+   Print a type in quotes highlighted as the "actual" type.  */
+
+class element_actual_type : public element_quoted_type
+{
+public:
+  element_actual_type (tree type)
+  : element_quoted_type (type, highlight_colors::actual)
+  {
+  }
+};
+
+} // namespace pp_markup
+
+#endif /* GCC_TREE_PRETTY_PRINT_MARKUP_H */
diff --git a/libcpp/include/rich-location.h b/libcpp/include/rich-location.h
index cc4f888f458b..ae4886f13bac 100644
--- a/libcpp/include/rich-location.h
+++ b/libcpp/include/rich-location.h
@@ -69,6 +69,9 @@ struct location_range
 
   /* If non-NULL, the label for this range.  */
   const range_label *m_label;
+
+  /* If non-null, the name of the color to use for this range.  */
+  const char *m_highlight_color;
 };
 
 /* A partially-embedded vec for use within rich_location for storing
@@ -378,7 +381,8 @@ class rich_location
 
   /* Constructing from a location.  */
   rich_location (line_maps *set, location_t loc,
-		 const range_label *label = NULL);
+		 const range_label *label = nullptr,
+		 const char *label_highlight_color = nullptr);
 
   /* Destructor.  */
   ~rich_location ();
@@ -393,15 +397,19 @@ class rich_location
   location_t get_loc () const { return get_loc (0); }
   location_t get_loc (unsigned int idx) const;
 
+  void set_highlight_color (const char *highlight_color);
+
   void
   add_range (location_t loc,
 	     enum range_display_kind range_display_kind
 	       = SHOW_RANGE_WITHOUT_CARET,
-	     const range_label *label = NULL);
+	     const range_label *label = nullptr,
+	     const char *highlight_color = nullptr);
 
   void
   set_range (unsigned int idx, location_t loc,
-	     enum range_display_kind range_display_kind);
+	     enum range_display_kind range_display_kind,
+	     const char *highlight_color = nullptr);
 
   unsigned int get_num_locations () const { return m_ranges.count (); }
 
diff --git a/libcpp/line-map.cc b/libcpp/line-map.cc
index d5200b317eee..41aee987b292 100644
--- a/libcpp/line-map.cc
+++ b/libcpp/line-map.cc
@@ -2160,8 +2160,9 @@ line_table_dump (FILE *stream, const line_maps *set, unsigned int num_ordinary,
 /* Construct a rich_location with location LOC as its initial range.  */
 
 rich_location::rich_location (line_maps *set, location_t loc,
-			      const range_label *label) :
-  m_line_table (set),
+			      const range_label *label,
+			      const char *label_highlight_color)
+: m_line_table (set),
   m_ranges (),
   m_column_override (0),
   m_have_expanded_location (false),
@@ -2171,7 +2172,7 @@ rich_location::rich_location (line_maps *set, location_t loc,
   m_fixit_hints (),
   m_path (NULL)
 {
-  add_range (loc, SHOW_RANGE_WITH_CARET, label);
+  add_range (loc, SHOW_RANGE_WITH_CARET, label, label_highlight_color);
 }
 
 /* The destructor for class rich_location.  */
@@ -2244,22 +2245,34 @@ rich_location::override_column (int column)
   m_have_expanded_location = false;
 }
 
+/* Set (or clear) the highlight color of the primary location.  */
+
+void
+rich_location::set_highlight_color (const char *highlight_color)
+{
+  location_range *locrange = get_range (0);
+  locrange->m_highlight_color = highlight_color;
+}
+
 /* Add the given range.  */
 
 void
 rich_location::add_range (location_t loc,
 			  enum range_display_kind range_display_kind,
-			  const range_label *label)
+			  const range_label *label,
+			  const char *label_highlight_color)
 {
   location_range range;
   range.m_loc = loc;
   range.m_range_display_kind = range_display_kind;
   range.m_label = label;
+  range.m_highlight_color = label_highlight_color;
   m_ranges.push (range);
 }
 
 /* Add or overwrite the location given by IDX, setting its location to LOC,
-   and setting its m_range_display_kind to RANGE_DISPLAY_KIND.
+   setting its m_range_display_kind to RANGE_DISPLAY_KIND, and setting
+   its m_highlight_color to HIGHLIGHT_COLOR (which may be nullptr).
 
    It must either overwrite an existing location, or add one *exactly* on
    the end of the array.
@@ -2273,19 +2286,21 @@ rich_location::add_range (location_t loc,
 
 void
 rich_location::set_range (unsigned int idx, location_t loc,
-			  enum range_display_kind range_display_kind)
+			  enum range_display_kind range_display_kind,
+			  const char *highlight_color)
 {
   /* We can either overwrite an existing range, or add one exactly
      on the end of the array.  */
   linemap_assert (idx <= m_ranges.count ());
 
   if (idx == m_ranges.count ())
-    add_range (loc, range_display_kind);
+    add_range (loc, range_display_kind, nullptr, highlight_color);
   else
     {
       location_range *locrange = get_range (idx);
       locrange->m_loc = loc;
       locrange->m_range_display_kind = range_display_kind;
+      locrange->m_highlight_color = highlight_color;
     }
 
   if (idx == 0)
