@@ -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:: {
@@ -500,6 +501,58 @@ impl<'a> Parser<'a> {
500501 }
501502 }
502503
504+ /// Check to see if a pair of chained operators looks like an attempt at chained comparison,
505+ /// e.g. `1 < x <= 3`. If so, suggest either splitting the comparison into two, or
506+ /// parenthesising the leftmost comparison.
507+ fn attempt_chained_comparison_suggestion (
508+ & mut self ,
509+ err : & mut DiagnosticBuilder < ' _ > ,
510+ inner_op : & Expr ,
511+ outer_op : & Spanned < AssocOp > ,
512+ ) {
513+ if let ExprKind :: Binary ( op, ref l1, ref r1) = inner_op. kind {
514+ match ( op. node , & outer_op. node ) {
515+ // `x < y < z` and friends.
516+ ( BinOpKind :: Lt , AssocOp :: Less ) | ( BinOpKind :: Lt , AssocOp :: LessEqual ) |
517+ ( BinOpKind :: Le , AssocOp :: LessEqual ) | ( BinOpKind :: Le , AssocOp :: Less ) |
518+ // `x > y > z` and friends.
519+ ( BinOpKind :: Gt , AssocOp :: Greater ) | ( BinOpKind :: Gt , AssocOp :: GreaterEqual ) |
520+ ( BinOpKind :: Ge , AssocOp :: GreaterEqual ) | ( BinOpKind :: Ge , AssocOp :: Greater ) => {
521+ let expr_to_str = |e : & Expr | {
522+ self . span_to_snippet ( e. span )
523+ . unwrap_or_else ( |_| pprust:: expr_to_string ( & e) )
524+ } ;
525+ err. span_suggestion (
526+ inner_op. span . to ( outer_op. span ) ,
527+ "split the comparison into two..." ,
528+ format ! (
529+ "{} {} {} && {} {}" ,
530+ expr_to_str( & l1) ,
531+ op. node. to_string( ) ,
532+ expr_to_str( & r1) ,
533+ expr_to_str( & r1) ,
534+ outer_op. node. to_ast_binop( ) . unwrap( ) . to_string( ) ,
535+ ) ,
536+ Applicability :: MaybeIncorrect ,
537+ ) ;
538+ err. span_suggestion (
539+ inner_op. span . to ( outer_op. span ) ,
540+ "...or parenthesize one of the comparisons" ,
541+ format ! (
542+ "({} {} {}) {}" ,
543+ expr_to_str( & l1) ,
544+ op. node. to_string( ) ,
545+ expr_to_str( & r1) ,
546+ outer_op. node. to_ast_binop( ) . unwrap( ) . to_string( ) ,
547+ ) ,
548+ Applicability :: MaybeIncorrect ,
549+ ) ;
550+ }
551+ _ => { }
552+ }
553+ }
554+ }
555+
503556 /// Produces an error if comparison operators are chained (RFC #558).
504557 /// We only need to check the LHS, not the RHS, because all comparison ops have same
505558 /// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
@@ -515,27 +568,31 @@ impl<'a> Parser<'a> {
515568 /// / \
516569 /// inner_op r2
517570 /// / \
518- /// l1 r1
571+ /// l1 r1
519572 pub ( super ) fn check_no_chained_comparison (
520573 & mut self ,
521- lhs : & Expr ,
522- outer_op : & AssocOp ,
574+ inner_op : & Expr ,
575+ outer_op : & Spanned < AssocOp > ,
523576 ) -> PResult < ' a , Option < P < Expr > > > {
524577 debug_assert ! (
525- outer_op. is_comparison( ) ,
578+ outer_op. node . is_comparison( ) ,
526579 "check_no_chained_comparison: {:?} is not comparison" ,
527- outer_op,
580+ outer_op. node ,
528581 ) ;
529582
530583 let mk_err_expr =
531584 |this : & Self , span| Ok ( Some ( this. mk_expr ( span, ExprKind :: Err , AttrVec :: new ( ) ) ) ) ;
532585
533- match lhs . kind {
586+ match inner_op . kind {
534587 ExprKind :: Binary ( op, _, _) if op. node . is_comparison ( ) => {
535588 // Respan to include both operators.
536589 let op_span = op. span . to ( self . prev_span ) ;
537- let mut err = self
538- . struct_span_err ( op_span, "chained comparison operators require parentheses" ) ;
590+ let mut err =
591+ self . struct_span_err ( op_span, "comparison operators cannot be chained" ) ;
592+
593+ // If it looks like a genuine attempt to chain operators (as opposed to a
594+ // misformatted turbofish, for instance), suggest a correct form.
595+ self . attempt_chained_comparison_suggestion ( & mut err, inner_op, outer_op) ;
539596
540597 let suggest = |err : & mut DiagnosticBuilder < ' _ > | {
541598 err. span_suggestion_verbose (
@@ -547,12 +604,12 @@ impl<'a> Parser<'a> {
547604 } ;
548605
549606 if op. node == BinOpKind :: Lt &&
550- * outer_op == AssocOp :: Less || // Include `<` to provide this recommendation
551- * outer_op == AssocOp :: Greater
607+ outer_op. node == AssocOp :: Less || // Include `<` to provide this recommendation
608+ outer_op. node == AssocOp :: Greater
552609 // even in a case like the following:
553610 {
554611 // Foo<Bar<Baz<Qux, ()>>>
555- if * outer_op == AssocOp :: Less {
612+ if outer_op. node == AssocOp :: Less {
556613 let snapshot = self . clone ( ) ;
557614 self . bump ( ) ;
558615 // So far we have parsed `foo<bar<`, consume the rest of the type args.
@@ -584,7 +641,7 @@ impl<'a> Parser<'a> {
584641 // FIXME: actually check that the two expressions in the binop are
585642 // paths and resynthesize new fn call expression instead of using
586643 // `ExprKind::Err` placeholder.
587- mk_err_expr ( self , lhs . span . to ( self . prev_span ) )
644+ mk_err_expr ( self , inner_op . span . to ( self . prev_span ) )
588645 }
589646 Err ( mut expr_err) => {
590647 expr_err. cancel ( ) ;
@@ -606,7 +663,7 @@ impl<'a> Parser<'a> {
606663 // FIXME: actually check that the two expressions in the binop are
607664 // paths and resynthesize new fn call expression instead of using
608665 // `ExprKind::Err` placeholder.
609- mk_err_expr ( self , lhs . span . to ( self . prev_span ) )
666+ mk_err_expr ( self , inner_op . span . to ( self . prev_span ) )
610667 }
611668 }
612669 } else {
0 commit comments