@@ -4,6 +4,7 @@ use rustc_data_structures::fx::FxHashSet;
4
4
use rustc_error_codes:: * ;
5
5
use rustc_errors:: { pluralize, struct_span_err} ;
6
6
use rustc_errors:: { Applicability , DiagnosticBuilder , Handler , PResult } ;
7
+ use rustc_span:: source_map:: Spanned ;
7
8
use rustc_span:: symbol:: kw;
8
9
use rustc_span:: { MultiSpan , Span , SpanSnippetError , DUMMY_SP } ;
9
10
use syntax:: ast:: {
@@ -500,6 +501,58 @@ impl<'a> Parser<'a> {
500
501
}
501
502
}
502
503
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
+
503
556
/// Produces an error if comparison operators are chained (RFC #558).
504
557
/// We only need to check the LHS, not the RHS, because all comparison ops have same
505
558
/// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
@@ -515,27 +568,31 @@ impl<'a> Parser<'a> {
515
568
/// / \
516
569
/// inner_op r2
517
570
/// / \
518
- /// l1 r1
571
+ /// l1 r1
519
572
pub ( super ) fn check_no_chained_comparison (
520
573
& mut self ,
521
- lhs : & Expr ,
522
- outer_op : & AssocOp ,
574
+ inner_op : & Expr ,
575
+ outer_op : & Spanned < AssocOp > ,
523
576
) -> PResult < ' a , Option < P < Expr > > > {
524
577
debug_assert ! (
525
- outer_op. is_comparison( ) ,
578
+ outer_op. node . is_comparison( ) ,
526
579
"check_no_chained_comparison: {:?} is not comparison" ,
527
- outer_op,
580
+ outer_op. node ,
528
581
) ;
529
582
530
583
let mk_err_expr =
531
584
|this : & Self , span| Ok ( Some ( this. mk_expr ( span, ExprKind :: Err , AttrVec :: new ( ) ) ) ) ;
532
585
533
- match lhs . kind {
586
+ match inner_op . kind {
534
587
ExprKind :: Binary ( op, _, _) if op. node . is_comparison ( ) => {
535
588
// Respan to include both operators.
536
589
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) ;
539
596
540
597
let suggest = |err : & mut DiagnosticBuilder < ' _ > | {
541
598
err. span_suggestion_verbose (
@@ -547,12 +604,12 @@ impl<'a> Parser<'a> {
547
604
} ;
548
605
549
606
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
552
609
// even in a case like the following:
553
610
{
554
611
// Foo<Bar<Baz<Qux, ()>>>
555
- if * outer_op == AssocOp :: Less {
612
+ if outer_op. node == AssocOp :: Less {
556
613
let snapshot = self . clone ( ) ;
557
614
self . bump ( ) ;
558
615
// So far we have parsed `foo<bar<`, consume the rest of the type args.
@@ -584,7 +641,7 @@ impl<'a> Parser<'a> {
584
641
// FIXME: actually check that the two expressions in the binop are
585
642
// paths and resynthesize new fn call expression instead of using
586
643
// `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 ) )
588
645
}
589
646
Err ( mut expr_err) => {
590
647
expr_err. cancel ( ) ;
@@ -606,7 +663,7 @@ impl<'a> Parser<'a> {
606
663
// FIXME: actually check that the two expressions in the binop are
607
664
// paths and resynthesize new fn call expression instead of using
608
665
// `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 ) )
610
667
}
611
668
}
612
669
} else {
0 commit comments