diff mbox series

[2/2] C++: improvements to binary operator diagnostics (PR c++/87504)

Message ID 1541449869-59851-2-git-send-email-dmalcolm@redhat.com
State New
Headers show
Series [1/2] C++: more location wrapper nodes (PR c++/43064, PR c++/43486) | expand

Commit Message

David Malcolm Nov. 5, 2018, 8:31 p.m. UTC
The C frontend is able (where expression locations are available) to print
problems with binary operators in 3-location form, labelling the types of
the expressions:

  arg_0 op arg_1
  ~~~~~ ^~ ~~~~~
    |        |
    |        arg1 type
    arg0 type

The C++ frontend currently just shows the combined location:

  arg_0 op arg_1
  ~~~~~~^~~~~~~~

and fails to highlight where the subexpressions are, or their types.

This patch introduces a op_location_t struct for handling the above
operator-location vs combined-location split, and a new
class binary_op_rich_location for displaying the above, so that the
C++ frontend is able to use the more detailed 3-location form for
type mismatches in binary operators, and for -Wtautological-compare
(where types are not displayed).  Both forms can be seen in this
example:

bad-binary-ops.C:69:20: error: no match for 'operator&&' (operand types are
  's' and 't')
   69 |   return ns_4::foo && ns_4::inner::bar;
      |          ~~~~~~~~~ ^~ ~~~~~~~~~~~~~~~~
      |                |                   |
      |                s                   t
bad-binary-ops.C:69:20: note: candidate: 'operator&&(bool, bool)' <built-in>
   69 |   return ns_4::foo && ns_4::inner::bar;
      |          ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~

The patch also allows from some uses of macros in
-Wtautological-compare, where both sides of the comparison have
been spelled the same way, e.g.:

Wtautological-compare-ranges.c:23:11: warning: self-comparison always
   evaluates to true [-Wtautological-compare]
   23 |   if (FOO == FOO);
      |           ^~

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu, in
conjunction with the previous patch.

OK for trunk?
Dave

gcc/c-family/ChangeLog:
	PR c++/87504
	* c-common.h (warn_tautological_cmp): Convert 1st param from
	location_t to const op_location_t &.
	* c-warn.c (find_array_ref_with_const_idx_r): Strip location
	wrapper when testing for INTEGER_CST.
	(warn_tautological_bitwise_comparison): Convert 1st param from
	location_t to const op_location_t &; use it to build a
	binary_op_rich_location, and use this.
	(spelled_the_same_p): New function.
	(warn_tautological_cmp): Convert 1st param from location_t to
	const op_location_t &.  Warn for macro expansions if
	spelled_the_same_p.  Use binary_op_rich_location.

gcc/c/ChangeLog:
	PR c++/87504
	* c-typeck.c (class maybe_range_label_for_tree_type_mismatch):
	Move from here to gcc-rich-location.h and gcc-rich-location.c.
	(build_binary_op): Use struct op_location_t and
	class binary_op_rich_location.

gcc/cp/ChangeLog:
	PR c++/87504
	* call.c (op_error): Convert 1st param from location_t to
	const op_location_t &.  Use binary_op_rich_location for binary
	ops.
	(build_conditional_expr_1): Convert 1st param from location_t to
	const op_location_t &.
	(build_conditional_expr): Likewise.
	(build_new_op_1): Likewise.
	(build_new_op): Likewise.
	* cp-tree.h (build_conditional_expr): Likewise.
	(build_new_op): Likewise.
	(build_x_binary_op): Likewise.
	(cp_build_binary_op): Likewise.
	* parser.c (cp_parser_primary_expression): Build a location
	for id-expression nodes.
	(cp_parser_binary_expression): Use an op_location_t when
	calling build_x_binary_op.
	(cp_parser_operator): Build a location for user-defined literals.
	* typeck.c (build_x_binary_op): Convert 1st param from location_t
	to const op_location_t &.
	(cp_build_binary_op): Likewise.  Use binary_op_rich_location.

gcc/ChangeLog:
	PR c++/87504
	* gcc-rich-location.c
	(maybe_range_label_for_tree_type_mismatch::get_text): Move here from
	c/c-typeck.c.
	(binary_op_rich_location::binary_op_rich_location): New ctor.
	(binary_op_rich_location::use_operator_loc_p): New function.
	* gcc-rich-location.h
	(class maybe_range_label_for_tree_type_mismatch)): Move here from
	c/c-typeck.c.
	(struct op_location_t): New forward decl.
	(class binary_op_rich_location): New class.
	* tree.h (struct op_location_t): New struct.

gcc/testsuite/ChangeLog:
	* c-c++-common/Wtautological-compare-ranges.c: New test.
	* g++.dg/cpp0x/pr51420.C: Add -fdiagnostics-show-caret and update
	expected output.
	* g++.dg/cpp0x/udlit-declare-neg.C: Update expected columns in
	output.
	* g++.dg/cpp0x/udlit-member-neg.C: Likewise.
	* g++.dg/diagnostic/bad-binary-ops.C: Update expected output from
	1-location form to 3-location form, with labelling of ranges with
	types.  Add examples of id-expression nodes with namespaces.
	* g++.dg/diagnostic/param-type-mismatch-2.C: Likewise.
---
 gcc/c-family/c-common.h                            |  3 +-
 gcc/c-family/c-warn.c                              | 57 +++++++++++---
 gcc/c/c-typeck.c                                   | 41 +---------
 gcc/cp/call.c                                      | 28 ++++---
 gcc/cp/cp-tree.h                                   | 10 ++-
 gcc/cp/parser.c                                    | 31 +++++++-
 gcc/cp/typeck.c                                    | 14 ++--
 gcc/gcc-rich-location.c                            | 89 ++++++++++++++++++++++
 gcc/gcc-rich-location.h                            | 57 ++++++++++++++
 .../c-c++-common/Wtautological-compare-ranges.c    | 42 ++++++++++
 gcc/testsuite/g++.dg/cpp0x/pr51420.C               | 12 ++-
 gcc/testsuite/g++.dg/cpp0x/udlit-declare-neg.C     |  8 +-
 gcc/testsuite/g++.dg/cpp0x/udlit-member-neg.C      |  2 +-
 gcc/testsuite/g++.dg/diagnostic/bad-binary-ops.C   | 57 +++++++++++++-
 .../g++.dg/diagnostic/param-type-mismatch-2.C      |  4 +-
 gcc/tree.h                                         | 49 ++++++++++++
 16 files changed, 424 insertions(+), 80 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/Wtautological-compare-ranges.c
diff mbox series

Patch

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 641fe57..1e7ebb9 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1263,7 +1263,8 @@  extern void constant_expression_error (tree);
 extern void overflow_warning (location_t, tree, tree = NULL_TREE);
 extern void warn_logical_operator (location_t, enum tree_code, tree,
 				   enum tree_code, tree, enum tree_code, tree);
-extern void warn_tautological_cmp (location_t, enum tree_code, tree, tree);
+extern void warn_tautological_cmp (const op_location_t &, enum tree_code,
+				   tree, tree);
 extern void warn_logical_not_parentheses (location_t, enum tree_code, tree,
 					  tree);
 extern bool warn_if_unused_value (const_tree, location_t);
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index f2ed711..765b46d 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -322,7 +322,8 @@  find_array_ref_with_const_idx_r (tree *expr_p, int *, void *)
 
   if ((TREE_CODE (expr) == ARRAY_REF
        || TREE_CODE (expr) == ARRAY_RANGE_REF)
-      && TREE_CODE (TREE_OPERAND (expr, 1)) == INTEGER_CST)
+      && (TREE_CODE (tree_strip_any_location_wrapper (TREE_OPERAND (expr, 1)))
+	  == INTEGER_CST))
     return integer_type_node;
 
   return NULL_TREE;
@@ -334,7 +335,7 @@  find_array_ref_with_const_idx_r (tree *expr_p, int *, void *)
    of this comparison.  */
 
 static void
-warn_tautological_bitwise_comparison (location_t loc, tree_code code,
+warn_tautological_bitwise_comparison (const op_location_t &loc, tree_code code,
 				      tree lhs, tree rhs)
 {
   if (code != EQ_EXPR && code != NE_EXPR)
@@ -389,29 +390,64 @@  warn_tautological_bitwise_comparison (location_t loc, tree_code code,
   if (res == cstw)
     return;
 
+  binary_op_rich_location richloc (loc, lhs, rhs, false);
   if (code == EQ_EXPR)
-    warning_at (loc, OPT_Wtautological_compare,
+    warning_at (&richloc, OPT_Wtautological_compare,
 		"bitwise comparison always evaluates to false");
   else
-    warning_at (loc, OPT_Wtautological_compare,
+    warning_at (&richloc, OPT_Wtautological_compare,
 		"bitwise comparison always evaluates to true");
 }
 
+/* Given LOC_A and LOC_B from macro expansions, return true if
+   they are "spelled the same" i.e. if they are both directly from
+   expansion of the same non-function-like macro.  */
+
+static bool
+spelled_the_same_p (location_t loc_a, location_t loc_b)
+{
+  gcc_assert (from_macro_expansion_at (loc_a));
+  gcc_assert (from_macro_expansion_at (loc_b));
+
+  const line_map_macro *map_a
+    = linemap_check_macro (linemap_lookup (line_table, loc_a));
+
+  const line_map_macro *map_b
+    = linemap_check_macro (linemap_lookup (line_table, loc_b));
+
+  if (map_a->macro == map_b->macro)
+    if (!cpp_fun_like_macro_p (map_a->macro))
+      return true;
+
+  return false;
+}
+
 /* Warn if a self-comparison always evaluates to true or false.  LOC
    is the location of the comparison with code CODE, LHS and RHS are
    operands of the comparison.  */
 
 void
-warn_tautological_cmp (location_t loc, enum tree_code code, tree lhs, tree rhs)
+warn_tautological_cmp (const op_location_t &loc, enum tree_code code,
+		       tree lhs, tree rhs)
 {
   if (TREE_CODE_CLASS (code) != tcc_comparison)
     return;
 
   /* Don't warn for various macro expansions.  */
-  if (from_macro_expansion_at (loc)
-      || from_macro_expansion_at (EXPR_LOCATION (lhs))
-      || from_macro_expansion_at (EXPR_LOCATION (rhs)))
+  if (from_macro_expansion_at (loc))
     return;
+  bool lhs_in_macro = from_macro_expansion_at (EXPR_LOCATION (lhs));
+  bool rhs_in_macro = from_macro_expansion_at (EXPR_LOCATION (rhs));
+  if (lhs_in_macro || rhs_in_macro)
+    {
+      /* Don't warn if exactly one is from a macro.  */
+      if (!(lhs_in_macro && rhs_in_macro))
+	return;
+
+      /* If both are in a macro, only warn if they're spelled the same.  */
+      if (!spelled_the_same_p (EXPR_LOCATION (lhs), EXPR_LOCATION (rhs)))
+	return;
+    }
 
   warn_tautological_bitwise_comparison (loc, code, lhs, rhs);
 
@@ -446,11 +482,12 @@  warn_tautological_cmp (location_t loc, enum tree_code code, tree lhs, tree rhs)
       const bool always_true = (code == EQ_EXPR || code == LE_EXPR
 				|| code == GE_EXPR || code == UNLE_EXPR
 				|| code == UNGE_EXPR || code == UNEQ_EXPR);
+      binary_op_rich_location richloc (loc, lhs, rhs, false);
       if (always_true)
-	warning_at (loc, OPT_Wtautological_compare,
+	warning_at (&richloc, OPT_Wtautological_compare,
 		    "self-comparison always evaluates to true");
       else
-	warning_at (loc, OPT_Wtautological_compare,
+	warning_at (&richloc, OPT_Wtautological_compare,
 		    "self-comparison always evaluates to false");
     }
 }
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index 9d09b8d..76e533e 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -11044,38 +11044,6 @@  build_vec_cmp (tree_code code, tree type,
   return build3 (VEC_COND_EXPR, type, cmp, minus_one_vec, zero_vec);
 }
 
-/* Subclass of range_label for labelling the type of EXPR when reporting
-   a type mismatch between EXPR and OTHER_EXPR.
-   Either or both of EXPR and OTHER_EXPR could be NULL.  */
-
-class maybe_range_label_for_tree_type_mismatch : public range_label
-{
- public:
-  maybe_range_label_for_tree_type_mismatch (tree expr, tree other_expr)
-  : m_expr (expr), m_other_expr (other_expr)
-  {
-  }
-
-  label_text get_text (unsigned range_idx) const FINAL OVERRIDE
-  {
-    if (m_expr == NULL_TREE
-	|| !EXPR_P (m_expr))
-      return label_text (NULL, false);
-    tree expr_type = TREE_TYPE (m_expr);
-
-    tree other_type = NULL_TREE;
-    if (m_other_expr && EXPR_P (m_other_expr))
-      other_type = TREE_TYPE (m_other_expr);
-
-   range_label_for_type_mismatch inner (expr_type, other_type);
-   return inner.get_text (range_idx);
-  }
-
- private:
-  tree m_expr;
-  tree m_other_expr;
-};
-
 /* Build a binary-operation expression without default conversions.
    CODE is the kind of expression to build.
    LOCATION is the operator's location.
@@ -12206,12 +12174,9 @@  build_binary_op (location_t location, enum tree_code code,
 
   if (!result_type)
     {
-      gcc_rich_location richloc (location);
-      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);
+      /* Favor showing any expression locations that are available. */
+      op_location_t oploc (location, UNKNOWN_LOCATION);
+      binary_op_rich_location richloc (oploc, orig_op0, orig_op1, true);
       binary_op_error (&richloc, code, TREE_TYPE (op0), TREE_TYPE (op1));
       return error_mark_node;
     }
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index d366d35..145ba8f 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -166,8 +166,8 @@  static tree build_over_call (struct z_candidate *, int, tsubst_flags_t);
 		     /*c_cast_p=*/false, (COMPLAIN))
 static tree convert_like_real (conversion *, tree, tree, int, bool,
 			       bool, tsubst_flags_t);
-static void op_error (location_t, enum tree_code, enum tree_code, tree,
-		      tree, tree, bool);
+static void op_error (const op_location_t &, enum tree_code, enum tree_code,
+		      tree, tree, tree, bool);
 static struct z_candidate *build_user_type_conversion_1 (tree, tree, int,
 							 tsubst_flags_t);
 static void print_z_candidate (location_t, const char *, struct z_candidate *);
@@ -4707,7 +4707,8 @@  op_error_string (const char *errmsg, int ntypes, bool match)
 }
 
 static void
-op_error (location_t loc, enum tree_code code, enum tree_code code2,
+op_error (const op_location_t &loc,
+	  enum tree_code code, enum tree_code code2,
 	  tree arg1, tree arg2, tree arg3, bool match)
 {
   bool assop = code == MODIFY_EXPR;
@@ -4761,8 +4762,12 @@  op_error (location_t loc, enum tree_code code, enum tree_code code2,
     default:
       if (arg2)
 	if (flag_diagnostics_show_caret)
-	  error_at (loc, op_error_string (G_("%<operator%s%>"), 2, match),
-		    opname, TREE_TYPE (arg1), TREE_TYPE (arg2));
+	  {
+	    binary_op_rich_location richloc (loc, arg1, arg2, true);
+	    error_at (&richloc,
+		      op_error_string (G_("%<operator%s%>"), 2, match),
+		      opname, TREE_TYPE (arg1), TREE_TYPE (arg2));
+	  }
 	else
 	  error_at (loc, op_error_string (G_("%<operator%s%> in %<%E %s %E%>"),
 					  2, match),
@@ -4861,7 +4866,8 @@  conditional_conversion (tree e1, tree e2, tsubst_flags_t complain)
    arguments to the conditional expression.  */
 
 static tree
-build_conditional_expr_1 (location_t loc, tree arg1, tree arg2, tree arg3,
+build_conditional_expr_1 (const op_location_t &loc,
+			  tree arg1, tree arg2, tree arg3,
                           tsubst_flags_t complain)
 {
   tree arg2_type;
@@ -5455,7 +5461,8 @@  build_conditional_expr_1 (location_t loc, tree arg1, tree arg2, tree arg3,
 /* Wrapper for above.  */
 
 tree
-build_conditional_expr (location_t loc, tree arg1, tree arg2, tree arg3,
+build_conditional_expr (const op_location_t &loc,
+			tree arg1, tree arg2, tree arg3,
                         tsubst_flags_t complain)
 {
   tree ret;
@@ -5644,8 +5651,9 @@  op_is_ordered (tree_code code)
 }
 
 static tree
-build_new_op_1 (location_t loc, enum tree_code code, int flags, tree arg1,
-		tree arg2, tree arg3, tree *overload, tsubst_flags_t complain)
+build_new_op_1 (const op_location_t &loc, enum tree_code code, int flags,
+		tree arg1, tree arg2, tree arg3, tree *overload,
+		tsubst_flags_t complain)
 {
   struct z_candidate *candidates = 0, *cand;
   vec<tree, va_gc> *arglist;
@@ -6124,7 +6132,7 @@  build_new_op_1 (location_t loc, enum tree_code code, int flags, tree arg1,
 /* Wrapper for above.  */
 
 tree
-build_new_op (location_t loc, enum tree_code code, int flags,
+build_new_op (const op_location_t &loc, enum tree_code code, int flags,
 	      tree arg1, tree arg2, tree arg3,
 	      tree *overload, tsubst_flags_t complain)
 {
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 26ded3a..8bd8078 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6046,7 +6046,8 @@  extern int raw_dump_id;
 extern bool check_dtor_name			(tree, tree);
 int magic_varargs_p				(tree);
 
-extern tree build_conditional_expr		(location_t, tree, tree, tree, 
+extern tree build_conditional_expr		(const op_location_t &,
+						 tree, tree, tree,
                                                  tsubst_flags_t);
 extern tree build_addr_func			(tree, tsubst_flags_t);
 extern void set_flags_from_callee		(tree);
@@ -6071,7 +6072,8 @@  extern tree build_new_method_call		(tree, tree,
 extern tree build_special_member_call		(tree, tree,
 						 vec<tree, va_gc> **,
 						 tree, int, tsubst_flags_t);
-extern tree build_new_op			(location_t, enum tree_code,
+extern tree build_new_op			(const op_location_t &,
+						 enum tree_code,
 						 int, tree, tree, tree, tree *,
 						 tsubst_flags_t);
 extern tree build_op_call			(tree, vec<tree, va_gc> **,
@@ -7273,7 +7275,7 @@  extern tree cp_build_function_call_nary         (tree, tsubst_flags_t, ...)
 						ATTRIBUTE_SENTINEL;
 extern tree cp_build_function_call_vec		(tree, vec<tree, va_gc> **,
 						 tsubst_flags_t);
-extern tree build_x_binary_op			(location_t,
+extern tree build_x_binary_op			(const op_location_t &,
 						 enum tree_code, tree,
 						 enum tree_code, tree,
 						 enum tree_code, tree *,
@@ -7340,7 +7342,7 @@  extern tree composite_pointer_type		(tree, tree, tree, tree,
 extern tree merge_types				(tree, tree);
 extern tree strip_array_domain			(tree);
 extern tree check_return_expr			(tree, bool *);
-extern tree cp_build_binary_op                  (location_t,
+extern tree cp_build_binary_op                  (const op_location_t &,
 						 enum tree_code, tree, tree,
 						 tsubst_flags_t);
 extern tree build_x_vec_perm_expr               (location_t,
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index b3876e2..0103fae 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -5661,7 +5661,22 @@  cp_parser_primary_expression (cp_parser *parser,
 		 id_expression.get_location ()));
 	if (error_msg)
 	  cp_parser_error (parser, error_msg);
-	decl.set_location (id_expr_token->location);
+
+	/* Build a location for an id-expression of the form:
+	     ::ns::id
+             ~~~~~~^~
+	  or:
+	     id
+	     ^~
+	   i.e. from the start of the first token to the end of the final
+	   token, with the caret at the start of the unqualified-id.  */
+	location_t caret_loc = get_pure_location (id_expression.get_location ());
+	location_t start_loc = get_start (id_expr_token->location);
+	location_t finish_loc = get_finish (id_expression.get_location ());
+	location_t combined_loc
+	  = make_location (caret_loc, start_loc, finish_loc);
+
+	decl.set_location (combined_loc);
 	return decl;
       }
 
@@ -9371,7 +9386,8 @@  cp_parser_binary_expression (cp_parser* parser, bool cast_p,
 	}
       else
         {
-          current.lhs = build_x_binary_op (combined_loc, current.tree_type,
+	  op_location_t op_loc (current.loc, combined_loc);
+	  current.lhs = build_x_binary_op (op_loc, current.tree_type,
                                            current.lhs, current.lhs_type,
                                            rhs, rhs_type, &overload,
                                            complain_flags (decltype_p));
@@ -15087,7 +15103,16 @@  cp_parser_operator (cp_parser* parser)
 	    const char *name = IDENTIFIER_POINTER (id);
 	    id = cp_literal_operator_id (name);
 	  }
-	return id;
+	/* Generate a location of the form:
+	     "" _suffix_identifier
+	     ^~~~~~~~~~~~~~~~~~~~~
+	   with caret == start at the start token, finish at the end of the
+	   suffix identifier.  */
+	location_t finish_loc
+	  = get_finish (cp_lexer_previous_token (parser->lexer)->location);
+	location_t combined_loc
+	  = make_location (start_loc, start_loc, finish_loc);
+	return cp_expr (id, combined_loc);
       }
 
     default:
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index b97d3da..e62fee6 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -4109,7 +4109,7 @@  convert_arguments (tree typelist, vec<tree, va_gc> **values, tree fndecl,
    ARG2_CODE as ERROR_MARK.  */
 
 tree
-build_x_binary_op (location_t loc, enum tree_code code, tree arg1,
+build_x_binary_op (const op_location_t &loc, enum tree_code code, tree arg1,
 		   enum tree_code arg1_code, tree arg2,
 		   enum tree_code arg2_code, tree *overload_p,
 		   tsubst_flags_t complain)
@@ -4298,7 +4298,7 @@  warn_for_null_address (location_t location, tree op, tsubst_flags_t complain)
    multiple inheritance, and deal with pointer to member functions.  */
 
 tree
-cp_build_binary_op (location_t location,
+cp_build_binary_op (const op_location_t &location,
 		    enum tree_code code, tree orig_op0, tree orig_op1,
 		    tsubst_flags_t complain)
 {
@@ -5295,9 +5295,13 @@  cp_build_binary_op (location_t location,
   if (!result_type)
     {
       if (complain & tf_error)
-	error_at (location,
-		  "invalid operands of types %qT and %qT to binary %qO",
-		  TREE_TYPE (orig_op0), TREE_TYPE (orig_op1), code);
+	{
+	  binary_op_rich_location richloc (location,
+					   orig_op0, orig_op1, true);
+	  error_at (&richloc,
+		    "invalid operands of types %qT and %qT to binary %qO",
+		    TREE_TYPE (orig_op0), TREE_TYPE (orig_op1), code);
+	}
       return error_mark_node;
     }
 
diff --git a/gcc/gcc-rich-location.c b/gcc/gcc-rich-location.c
index 81beb61..25a604f 100644
--- a/gcc/gcc-rich-location.c
+++ b/gcc/gcc-rich-location.c
@@ -182,3 +182,92 @@  gcc_rich_location::add_fixit_insert_formatted (const char *content,
   else
     add_fixit_insert_before (insertion_point, content);
 }
+
+/* Implementation of range_label::get_text for
+   maybe_range_label_for_tree_type_mismatch.
+
+   If both expressions are non-NULL, then generate text describing
+   the first expression's type (using the other expression's type
+   for comparison, analogous to %H and %I in the C++ frontend, but
+   on expressions rather than types).  */
+
+label_text
+maybe_range_label_for_tree_type_mismatch::get_text (unsigned range_idx) const
+{
+  if (m_expr == NULL_TREE
+      || !EXPR_P (m_expr))
+    return label_text (NULL, false);
+  tree expr_type = TREE_TYPE (m_expr);
+
+  tree other_type = NULL_TREE;
+  if (m_other_expr && EXPR_P (m_other_expr))
+    other_type = TREE_TYPE (m_other_expr);
+
+  range_label_for_type_mismatch inner (expr_type, other_type);
+  return inner.get_text (range_idx);
+}
+
+/* binary_op_rich_location's ctor.
+
+   If use_operator_loc_p (LOC, ARG0, ARG1), then attempt to make a 3-location
+   rich_location of the form:
+
+     arg_0 op arg_1
+     ~~~~~ ^~ ~~~~~
+       |        |
+       |        arg1 type
+       arg0 type
+
+   labelling the types of the arguments if SHOW_TYPES is true.
+
+   Otherwise, make a 1-location rich_location using the compound
+   location within LOC:
+
+     arg_0 op arg_1
+     ~~~~~~^~~~~~~~
+
+   for which we can't label the types.  */
+
+binary_op_rich_location::binary_op_rich_location (const op_location_t &loc,
+						  tree arg0, tree arg1,
+						  bool show_types)
+: gcc_rich_location (loc.m_combined_loc),
+  m_label_for_arg0 (arg0, arg1),
+  m_label_for_arg1 (arg1, arg0)
+{
+  /* Default (above) to using the combined loc.
+     Potentially override it here: if we have location information for the
+     operator and for both arguments, then split them all out.
+     Alternatively, override it if we don't have the combined location.  */
+  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);
+    }
+}
+
+/* Determine if binary_op_rich_location's ctor should attempt to make
+   a 3-location rich_location (the location of the operator and of
+   the 2 arguments), or fall back to a 1-location rich_location showing
+   just the combined location of the operation as a whole.  */
+
+bool
+binary_op_rich_location::use_operator_loc_p (const op_location_t &loc,
+					     tree arg0, tree arg1)
+{
+  /* If we don't have a combined location, then use the operator location,
+     and try to add ranges for the operators.  */
+  if (loc.m_combined_loc == UNKNOWN_LOCATION)
+    return true;
+
+  /* If we don't have the operator location, then use the
+     combined location.  */
+  if (loc.m_operator_loc == UNKNOWN_LOCATION)
+    return false;
+
+  /* We have both operator location and combined location: only use the
+     operator location if we have locations for both arguments.  */
+  return (EXPR_HAS_LOCATION (arg0)
+	  && EXPR_HAS_LOCATION (arg1));
+}
diff --git a/gcc/gcc-rich-location.h b/gcc/gcc-rich-location.h
index d282fd4..db35400 100644
--- a/gcc/gcc-rich-location.h
+++ b/gcc/gcc-rich-location.h
@@ -162,4 +162,61 @@  class range_label_for_type_mismatch : public range_label
   tree m_other_type;
 };
 
+/* Subclass of range_label for labelling the type of EXPR when reporting
+   a type mismatch between EXPR and OTHER_EXPR.
+   Either or both of EXPR and OTHER_EXPR could be NULL.  */
+
+class maybe_range_label_for_tree_type_mismatch : public range_label
+{
+ public:
+  maybe_range_label_for_tree_type_mismatch (tree expr, tree other_expr)
+  : m_expr (expr), m_other_expr (other_expr)
+  {
+  }
+
+  label_text get_text (unsigned range_idx) const FINAL OVERRIDE;
+
+ private:
+  tree m_expr;
+  tree m_other_expr;
+};
+
+struct op_location_t;
+
+/* A subclass of rich_location for showing problems with binary operations.
+
+   If enough location information is available, the ctor will make a
+   3-location rich_location of the form:
+
+     arg_0 op arg_1
+     ~~~~~ ^~ ~~~~~
+       |        |
+       |        arg1 type
+       arg0 type
+
+   labelling the types of the arguments if SHOW_TYPES is true.
+
+   Otherwise, it will fall back to a 1-location rich_location using the
+   compound location within LOC:
+
+     arg_0 op arg_1
+     ~~~~~~^~~~~~~~
+
+   for which we can't label the types.  */
+
+class binary_op_rich_location : public gcc_rich_location
+{
+ public:
+  binary_op_rich_location (const op_location_t &loc,
+			   tree arg0, tree arg1,
+			   bool show_types);
+
+ private:
+  static bool use_operator_loc_p (const op_location_t &loc,
+				  tree arg0, tree arg1);
+
+  maybe_range_label_for_tree_type_mismatch m_label_for_arg0;
+  maybe_range_label_for_tree_type_mismatch m_label_for_arg1;
+};
+
 #endif /* GCC_RICH_LOCATION_H */
diff --git a/gcc/testsuite/c-c++-common/Wtautological-compare-ranges.c b/gcc/testsuite/c-c++-common/Wtautological-compare-ranges.c
new file mode 100644
index 0000000..2634d27
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wtautological-compare-ranges.c
@@ -0,0 +1,42 @@ 
+/* { dg-do compile } */
+/* { dg-options "-Wtautological-compare -fdiagnostics-show-caret" } */
+
+#define FOO foo
+
+void
+fn1 (int foo)
+{
+  if (foo == foo); /* { dg-warning "self-comparison always evaluates to true" } */
+  /* { dg-begin-multiline-output "" }
+   if (foo == foo);
+           ^~
+     { dg-end-multiline-output "" { target c } } */
+  /* { dg-begin-multiline-output "" }
+   if (foo == foo);
+       ~~~ ^~ ~~~
+     { dg-end-multiline-output "" { target c++ } } */
+}
+
+void
+fn2 (int foo)
+{
+  if (FOO == FOO); /* { dg-warning "self-comparison always evaluates to true" } */
+  /* { dg-begin-multiline-output "" }
+   if (FOO == FOO);
+           ^~
+     { dg-end-multiline-output "" } */
+}
+
+void
+fn3 (int foo)
+{
+  if ((foo & 16) == 10); /* { dg-warning "bitwise comparison always evaluates to false" } */
+  /* { dg-begin-multiline-output "" }
+   if ((foo & 16) == 10);
+                  ^~
+     { dg-end-multiline-output "" { target c } } */
+  /* { dg-begin-multiline-output "" }
+   if ((foo & 16) == 10);
+       ~~~~~~~~~~ ^~ ~~
+     { dg-end-multiline-output "" { target c++ } } */
+}
diff --git a/gcc/testsuite/g++.dg/cpp0x/pr51420.C b/gcc/testsuite/g++.dg/cpp0x/pr51420.C
index fc70d46..76cf77b 100644
--- a/gcc/testsuite/g++.dg/cpp0x/pr51420.C
+++ b/gcc/testsuite/g++.dg/cpp0x/pr51420.C
@@ -1,8 +1,18 @@ 
 // { dg-do compile { target c++11 } }
+// { dg-options "-fdiagnostics-show-caret" }
 
 void
 foo()
 {
-  float x = operator"" _F();  //  { dg-error  "13:'operator\"\"_F' was not declared in this scope" }
+  float x = operator"" _F();  //  { dg-error  "21:'operator\"\"_F' was not declared in this scope" }
+  /* { dg-begin-multiline-output "" }
+   float x = operator"" _F();
+             ~~~~~~~~^~~~~
+     { dg-end-multiline-output "" } */
+
   float y = 0_F;  //  { dg-error  "unable to find numeric literal operator" }
+  /* { dg-begin-multiline-output "" }
+   float y = 0_F;
+             ^~~
+     { dg-end-multiline-output "" } */
 }
diff --git a/gcc/testsuite/g++.dg/cpp0x/udlit-declare-neg.C b/gcc/testsuite/g++.dg/cpp0x/udlit-declare-neg.C
index 721f87d..8458c1c 100644
--- a/gcc/testsuite/g++.dg/cpp0x/udlit-declare-neg.C
+++ b/gcc/testsuite/g++.dg/cpp0x/udlit-declare-neg.C
@@ -2,14 +2,14 @@ 
 
 //  Check that undeclared literal operator calls and literals give appropriate errors.
 
-int i = operator"" _Bar('x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
+int i = operator"" _Bar('x');  // { dg-error "17:'operator\"\"_Bar' was not declared in this scope" }
 int j = 'x'_Bar;  // { dg-error "unable to find character literal operator|with|argument" }
 
-int ii = operator"" _BarCharStr("Howdy, Pardner!");  // { dg-error "10:'operator\"\"_BarCharStr' was not declared in this scope" }
+int ii = operator"" _BarCharStr("Howdy, Pardner!");  // { dg-error "18:'operator\"\"_BarCharStr' was not declared in this scope" }
 int jj = "Howdy, Pardner!"_BarCharStr;  // { dg-error "unable to find string literal operator|Possible missing length argument" }
 
-unsigned long long iULL = operator"" _BarULL(666ULL);  // { dg-error "27:'operator\"\"_BarULL' was not declared in this scope" }
+unsigned long long iULL = operator"" _BarULL(666ULL);  // { dg-error "35:'operator\"\"_BarULL' was not declared in this scope" }
 unsigned long long jULL = 666_BarULL;  // { dg-error "unable to find numeric literal operator" }
 
-long double iLD = operator"" _BarLD(666.0L);  // { dg-error "19:'operator\"\"_BarLD' was not declared in this scope" }
+long double iLD = operator"" _BarLD(666.0L);  // { dg-error "27:'operator\"\"_BarLD' was not declared in this scope" }
 long double jLD = 666.0_BarLD;  // { dg-error "unable to find numeric literal operator" }
diff --git a/gcc/testsuite/g++.dg/cpp0x/udlit-member-neg.C b/gcc/testsuite/g++.dg/cpp0x/udlit-member-neg.C
index f2eef0f..4d446f0 100644
--- a/gcc/testsuite/g++.dg/cpp0x/udlit-member-neg.C
+++ b/gcc/testsuite/g++.dg/cpp0x/udlit-member-neg.C
@@ -7,7 +7,7 @@  public:
   int operator"" _Bar(char32_t);  // { dg-error "7:.int Foo::operator\"\"_Bar\\(char32_t\\). must be a non-member function" }
 };
 
-int i = operator"" _Bar(U'x');  // { dg-error "9:'operator\"\"_Bar' was not declared in this scope" }
+int i = operator"" _Bar(U'x');  // { dg-error "17:'operator\"\"_Bar' was not declared in this scope" }
 int j = U'x'_Bar;  // { dg-error "unable to find character literal operator" }
 
 int
diff --git a/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops.C b/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops.C
index 4ab7656..fab5849 100644
--- a/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops.C
+++ b/gcc/testsuite/g++.dg/diagnostic/bad-binary-ops.C
@@ -11,7 +11,10 @@  void test_1 ()
 
 /* { dg-begin-multiline-output "" }
    myvec[1] / ptr;
-   ~~~~~~~~~^~~~~
+   ~~~~~~~~ ^ ~~~
+          |   |
+          |   const int*
+          __m128 {aka float}
    { dg-end-multiline-output "" } */
 }
 
@@ -28,8 +31,12 @@  int test_2 (void)
 /* { dg-begin-multiline-output "" }
    return (some_function ()
            ~~~~~~~~~~~~~~~~
+                         |
+                         s
     + some_other_function ());
-    ^~~~~~~~~~~~~~~~~~~~~~~~
+    ^ ~~~~~~~~~~~~~~~~~~~~~~
+                          |
+                          t
    { dg-end-multiline-output "" } */
 }
 
@@ -39,6 +46,52 @@  int test_3 (struct s param_s, struct t param_t)
 
 /* { dg-begin-multiline-output "" }
    return param_s && param_t;
+          ~~~~~~~ ^~ ~~~~~~~
+          |          |
+          s          t
+   { dg-end-multiline-output "" } */
+/* { dg-begin-multiline-output "" }
+   return param_s && param_t;
           ~~~~~~~~^~~~~~~~~~
    { dg-end-multiline-output "" } */
 }
+
+namespace ns_4
+{
+  struct s foo;
+  namespace inner {
+    struct t bar;
+  };
+};
+
+int test_4a (void)
+{
+  return ns_4::foo && ns_4::inner::bar; // { dg-error "no match for .operator" }
+  /* { dg-begin-multiline-output "" }
+   return ns_4::foo && ns_4::inner::bar;
+          ~~~~~~~~~ ^~ ~~~~~~~~~~~~~~~~
+                |                   |
+                s                   t
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   return ns_4::foo && ns_4::inner::bar;
+          ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+}
+
+int test_4b (void)
+{
+  return ::ns_4::foo && ns_4::inner::bar; // { dg-error "no match for .operator" }
+  /* { dg-begin-multiline-output "" }
+   return ::ns_4::foo && ns_4::inner::bar;
+          ~~~~~~~~~~~ ^~ ~~~~~~~~~~~~~~~~
+                  |                   |
+                  s                   t
+     { dg-end-multiline-output "" } */
+
+  /* { dg-begin-multiline-output "" }
+   return ::ns_4::foo && ns_4::inner::bar;
+          ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+}
diff --git a/gcc/testsuite/g++.dg/diagnostic/param-type-mismatch-2.C b/gcc/testsuite/g++.dg/diagnostic/param-type-mismatch-2.C
index f74f8d3..5f3c154 100644
--- a/gcc/testsuite/g++.dg/diagnostic/param-type-mismatch-2.C
+++ b/gcc/testsuite/g++.dg/diagnostic/param-type-mismatch-2.C
@@ -208,7 +208,9 @@  int test_10 ()
   return v10_a - v10_b; // { dg-error "no match for" }
   /* { dg-begin-multiline-output "" }
    return v10_a - v10_b;
-          ~~~~~~^~~~~~~
+          ~~~~~ ^ ~~~~~
+          |       |
+          s10     s10
      { dg-end-multiline-output "" } */
   // { dg-message "candidate" "" { target *-*-* } s10_operator }
   /* { dg-begin-multiline-output "" }
diff --git a/gcc/tree.h b/gcc/tree.h
index 311caa1..3b0772c 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -5915,4 +5915,53 @@  fndecl_built_in_p (const_tree node, built_in_function name)
 	  && DECL_FUNCTION_CODE (node) == name);
 }
 
+/* A struct for encapsulating location information about an operator
+   and the operation built from it.
+
+   m_operator_loc is the location of the operator
+   m_combined_loc is the location of the compound expression.
+
+   For example, given "a && b" the, operator location is:
+      a && b
+        ^~
+   and the combined location is:
+      a && b
+      ~~^~~~
+   Capturing this information allows for class binary_op_rich_location
+   to provide detailed information about e.g. type mismatches in binary
+   operations where enough location information is available:
+
+     arg_0 op arg_1
+     ~~~~~ ^~ ~~~~~
+       |        |
+       |        arg1 type
+       arg0 type
+
+   falling back to just showing the combined location:
+
+     arg_0 op arg_1
+     ~~~~~~^~~~~~~~
+
+   where it is not.  */
+
+struct op_location_t
+{
+  location_t m_operator_loc;
+  location_t m_combined_loc;
+
+  /* 1-argument ctor, for constructing from a combined location.  */
+  op_location_t (location_t combined_loc)
+  : m_operator_loc (UNKNOWN_LOCATION), m_combined_loc (combined_loc)
+  {}
+
+  /* 2-argument ctor, for distinguishing between the operator's location
+     and the combined location.  */
+  op_location_t (location_t operator_loc, location_t combined_loc)
+  : m_operator_loc (operator_loc), m_combined_loc (combined_loc)
+  {}
+
+  /* Implicitly convert back to a location_t, using the combined location.  */
+  operator location_t () const { return m_combined_loc; }
+};
+
 #endif  /* GCC_TREE_H  */