@@ -4,6 +4,7 @@ use rustc_data_structures::fx::FxHashSet;
44use rustc_error_codes:: * ;
55use rustc_errors:: { pluralize, struct_span_err} ;
66use rustc_errors:: { Applicability , DiagnosticBuilder , Handler , PResult } ;
7+ use rustc_span:: source_map:: Spanned ;
78use rustc_span:: symbol:: kw;
89use rustc_span:: { MultiSpan , Span , SpanSnippetError , DUMMY_SP } ;
910use syntax:: ast:: {
@@ -491,6 +492,58 @@ impl<'a> Parser<'a> {
491492 }
492493 }
493494
495+ /// Check to see if a pair of chained operators looks like an attempt at chained comparison,
496+ /// e.g. `1 < x <= 3`. If so, suggest either splitting the comparison into two, or
497+ /// parenthesising the leftmost comparison.
498+ fn attempt_chained_comparison_suggestion (
499+ & mut self ,
500+ err : & mut DiagnosticBuilder < ' _ > ,
501+ inner_op : & Expr ,
502+ outer_op : & Spanned < AssocOp > ,
503+ ) {
504+ if let ExprKind :: Binary ( op, ref l1, ref r1) = inner_op. kind {
505+ match ( op. node , & outer_op. node ) {
506+ // `x < y < z` and friends.
507+ ( BinOpKind :: Lt , AssocOp :: Less ) | ( BinOpKind :: Lt , AssocOp :: LessEqual ) |
508+ ( BinOpKind :: Le , AssocOp :: LessEqual ) | ( BinOpKind :: Le , AssocOp :: Less ) |
509+ // `x > y > z` and friends.
510+ ( BinOpKind :: Gt , AssocOp :: Greater ) | ( BinOpKind :: Gt , AssocOp :: GreaterEqual ) |
511+ ( BinOpKind :: Ge , AssocOp :: GreaterEqual ) | ( BinOpKind :: Ge , AssocOp :: Greater ) => {
512+ let expr_to_str = |e : & Expr | {
513+ self . span_to_snippet ( e. span )
514+ . unwrap_or_else ( |_| pprust:: expr_to_string ( & e) )
515+ } ;
516+ err. span_suggestion (
517+ inner_op. span . to ( outer_op. span ) ,
518+ "split the comparison into two..." ,
519+ format ! (
520+ "{} {} {} && {} {}" ,
521+ expr_to_str( & l1) ,
522+ op. node. to_string( ) ,
523+ expr_to_str( & r1) ,
524+ expr_to_str( & r1) ,
525+ outer_op. node. to_ast_binop( ) . unwrap( ) . to_string( ) ,
526+ ) ,
527+ Applicability :: MaybeIncorrect ,
528+ ) ;
529+ err. span_suggestion (
530+ inner_op. span . to ( outer_op. span ) ,
531+ "...or parenthesize one of the comparisons" ,
532+ format ! (
533+ "({} {} {}) {}" ,
534+ expr_to_str( & l1) ,
535+ op. node. to_string( ) ,
536+ expr_to_str( & r1) ,
537+ outer_op. node. to_ast_binop( ) . unwrap( ) . to_string( ) ,
538+ ) ,
539+ Applicability :: MaybeIncorrect ,
540+ ) ;
541+ }
542+ _ => { }
543+ }
544+ }
545+ }
546+
494547 /// Produces an error if comparison operators are chained (RFC #558).
495548 /// We only need to check the LHS, not the RHS, because all comparison ops have same
496549 /// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
@@ -506,27 +559,31 @@ impl<'a> Parser<'a> {
506559 /// / \
507560 /// inner_op r2
508561 /// / \
509- /// l1 r1
562+ /// l1 r1
510563 pub ( super ) fn check_no_chained_comparison (
511564 & mut self ,
512- lhs : & Expr ,
513- outer_op : & AssocOp ,
565+ inner_op : & Expr ,
566+ outer_op : & Spanned < AssocOp > ,
514567 ) -> PResult < ' a , Option < P < Expr > > > {
515568 debug_assert ! (
516- outer_op. is_comparison( ) ,
569+ outer_op. node . is_comparison( ) ,
517570 "check_no_chained_comparison: {:?} is not comparison" ,
518- outer_op,
571+ outer_op. node ,
519572 ) ;
520573
521574 let mk_err_expr =
522575 |this : & Self , span| Ok ( Some ( this. mk_expr ( span, ExprKind :: Err , AttrVec :: new ( ) ) ) ) ;
523576
524- match lhs . kind {
577+ match inner_op . kind {
525578 ExprKind :: Binary ( op, _, _) if op. node . is_comparison ( ) => {
526579 // Respan to include both operators.
527580 let op_span = op. span . to ( self . prev_span ) ;
528- let mut err = self
529- . struct_span_err ( op_span, "chained comparison operators require parentheses" ) ;
581+ let mut err =
582+ self . struct_span_err ( op_span, "comparison operators cannot be chained" ) ;
583+
584+ // If it looks like a genuine attempt to chain operators (as opposed to a
585+ // misformatted turbofish, for instance), suggest a correct form.
586+ self . attempt_chained_comparison_suggestion ( & mut err, inner_op, outer_op) ;
530587
531588 let suggest = |err : & mut DiagnosticBuilder < ' _ > | {
532589 err. span_suggestion_verbose (
@@ -538,12 +595,12 @@ impl<'a> Parser<'a> {
538595 } ;
539596
540597 if op. node == BinOpKind :: Lt &&
541- * outer_op == AssocOp :: Less || // Include `<` to provide this recommendation
542- * outer_op == AssocOp :: Greater
598+ outer_op. node == AssocOp :: Less || // Include `<` to provide this recommendation
599+ outer_op. node == AssocOp :: Greater
543600 // even in a case like the following:
544601 {
545602 // Foo<Bar<Baz<Qux, ()>>>
546- if * outer_op == AssocOp :: Less {
603+ if outer_op. node == AssocOp :: Less {
547604 let snapshot = self . clone ( ) ;
548605 self . bump ( ) ;
549606 // So far we have parsed `foo<bar<`, consume the rest of the type args.
@@ -575,7 +632,7 @@ impl<'a> Parser<'a> {
575632 // FIXME: actually check that the two expressions in the binop are
576633 // paths and resynthesize new fn call expression instead of using
577634 // `ExprKind::Err` placeholder.
578- mk_err_expr ( self , lhs . span . to ( self . prev_span ) )
635+ mk_err_expr ( self , inner_op . span . to ( self . prev_span ) )
579636 }
580637 Err ( mut expr_err) => {
581638 expr_err. cancel ( ) ;
@@ -597,7 +654,7 @@ impl<'a> Parser<'a> {
597654 // FIXME: actually check that the two expressions in the binop are
598655 // paths and resynthesize new fn call expression instead of using
599656 // `ExprKind::Err` placeholder.
600- mk_err_expr ( self , lhs . span . to ( self . prev_span ) )
657+ mk_err_expr ( self , inner_op . span . to ( self . prev_span ) )
601658 }
602659 }
603660 } else {
0 commit comments