@@ -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:: {
@@ -491,6 +492,58 @@ impl<'a> Parser<'a> {
491
492
}
492
493
}
493
494
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
+
494
547
/// Produces an error if comparison operators are chained (RFC #558).
495
548
/// We only need to check the LHS, not the RHS, because all comparison ops have same
496
549
/// precedence (see `fn precedence`) and are left-associative (see `fn fixity`).
@@ -506,27 +559,31 @@ impl<'a> Parser<'a> {
506
559
/// / \
507
560
/// inner_op r2
508
561
/// / \
509
- /// l1 r1
562
+ /// l1 r1
510
563
pub ( super ) fn check_no_chained_comparison (
511
564
& mut self ,
512
- lhs : & Expr ,
513
- outer_op : & AssocOp ,
565
+ inner_op : & Expr ,
566
+ outer_op : & Spanned < AssocOp > ,
514
567
) -> PResult < ' a , Option < P < Expr > > > {
515
568
debug_assert ! (
516
- outer_op. is_comparison( ) ,
569
+ outer_op. node . is_comparison( ) ,
517
570
"check_no_chained_comparison: {:?} is not comparison" ,
518
- outer_op,
571
+ outer_op. node ,
519
572
) ;
520
573
521
574
let mk_err_expr =
522
575
|this : & Self , span| Ok ( Some ( this. mk_expr ( span, ExprKind :: Err , AttrVec :: new ( ) ) ) ) ;
523
576
524
- match lhs . kind {
577
+ match inner_op . kind {
525
578
ExprKind :: Binary ( op, _, _) if op. node . is_comparison ( ) => {
526
579
// Respan to include both operators.
527
580
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) ;
530
587
531
588
let suggest = |err : & mut DiagnosticBuilder < ' _ > | {
532
589
err. span_suggestion_verbose (
@@ -538,12 +595,12 @@ impl<'a> Parser<'a> {
538
595
} ;
539
596
540
597
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
543
600
// even in a case like the following:
544
601
{
545
602
// Foo<Bar<Baz<Qux, ()>>>
546
- if * outer_op == AssocOp :: Less {
603
+ if outer_op. node == AssocOp :: Less {
547
604
let snapshot = self . clone ( ) ;
548
605
self . bump ( ) ;
549
606
// So far we have parsed `foo<bar<`, consume the rest of the type args.
@@ -575,7 +632,7 @@ impl<'a> Parser<'a> {
575
632
// FIXME: actually check that the two expressions in the binop are
576
633
// paths and resynthesize new fn call expression instead of using
577
634
// `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 ) )
579
636
}
580
637
Err ( mut expr_err) => {
581
638
expr_err. cancel ( ) ;
@@ -597,7 +654,7 @@ impl<'a> Parser<'a> {
597
654
// FIXME: actually check that the two expressions in the binop are
598
655
// paths and resynthesize new fn call expression instead of using
599
656
// `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 ) )
601
658
}
602
659
}
603
660
} else {
0 commit comments