Skip to content

Commit 9fa4953

Browse files
authored
Rollup merge of #69878 - estebank:chained-ops, r=Centril
Tweak chained operators diagnostic Use more selective spans Improve suggestion output Be more selective when displaying suggestions Silence some knock-down type errors r? @Centril
2 parents b105ac4 + 4832f3f commit 9fa4953

6 files changed

+202
-166
lines changed

src/librustc_parse/parser/diagnostics.rs

+103-52
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use rustc_span::symbol::kw;
1717
use rustc_span::{MultiSpan, Span, SpanSnippetError, DUMMY_SP};
1818

1919
use log::{debug, trace};
20-
use std::mem;
2120

2221
const TURBOFISH: &str = "use `::<...>` instead of `<...>` to specify type arguments";
2322

@@ -459,9 +458,28 @@ impl<'a> Parser<'a> {
459458
err: &mut DiagnosticBuilder<'_>,
460459
inner_op: &Expr,
461460
outer_op: &Spanned<AssocOp>,
462-
) {
461+
) -> bool /* advanced the cursor */ {
463462
if let ExprKind::Binary(op, ref l1, ref r1) = inner_op.kind {
464-
match (op.node, &outer_op.node) {
463+
if let ExprKind::Field(_, ident) = l1.kind {
464+
if ident.as_str().parse::<i32>().is_err() && !matches!(r1.kind, ExprKind::Lit(_)) {
465+
// The parser has encountered `foo.bar<baz`, the likelihood of the turbofish
466+
// suggestion being the only one to apply is high.
467+
return false;
468+
}
469+
}
470+
let mut enclose = |left: Span, right: Span| {
471+
err.multipart_suggestion(
472+
"parenthesize the comparison",
473+
vec![
474+
(left.shrink_to_lo(), "(".to_string()),
475+
(right.shrink_to_hi(), ")".to_string()),
476+
],
477+
Applicability::MaybeIncorrect,
478+
);
479+
};
480+
return match (op.node, &outer_op.node) {
481+
// `x == y == z`
482+
(BinOpKind::Eq, AssocOp::Equal) |
465483
// `x < y < z` and friends.
466484
(BinOpKind::Lt, AssocOp::Less) | (BinOpKind::Lt, AssocOp::LessEqual) |
467485
(BinOpKind::Le, AssocOp::LessEqual) | (BinOpKind::Le, AssocOp::Less) |
@@ -472,35 +490,55 @@ impl<'a> Parser<'a> {
472490
self.span_to_snippet(e.span)
473491
.unwrap_or_else(|_| pprust::expr_to_string(&e))
474492
};
475-
err.span_suggestion(
476-
inner_op.span.to(outer_op.span),
477-
"split the comparison into two...",
478-
format!(
479-
"{} {} {} && {} {}",
480-
expr_to_str(&l1),
481-
op.node.to_string(),
482-
expr_to_str(&r1),
483-
expr_to_str(&r1),
484-
outer_op.node.to_ast_binop().unwrap().to_string(),
485-
),
486-
Applicability::MaybeIncorrect,
487-
);
488-
err.span_suggestion(
489-
inner_op.span.to(outer_op.span),
490-
"...or parenthesize one of the comparisons",
491-
format!(
492-
"({} {} {}) {}",
493-
expr_to_str(&l1),
494-
op.node.to_string(),
495-
expr_to_str(&r1),
496-
outer_op.node.to_ast_binop().unwrap().to_string(),
497-
),
493+
err.span_suggestion_verbose(
494+
inner_op.span.shrink_to_hi(),
495+
"split the comparison into two",
496+
format!(" && {}", expr_to_str(&r1)),
498497
Applicability::MaybeIncorrect,
499498
);
499+
false // Keep the current parse behavior, where the AST is `(x < y) < z`.
500500
}
501-
_ => {}
502-
}
501+
// `x == y < z`
502+
(BinOpKind::Eq, AssocOp::Less) | (BinOpKind::Eq, AssocOp::LessEqual) |
503+
(BinOpKind::Eq, AssocOp::Greater) | (BinOpKind::Eq, AssocOp::GreaterEqual) => {
504+
// Consume `z`/outer-op-rhs.
505+
let snapshot = self.clone();
506+
match self.parse_expr() {
507+
Ok(r2) => {
508+
// We are sure that outer-op-rhs could be consumed, the suggestion is
509+
// likely correct.
510+
enclose(r1.span, r2.span);
511+
true
512+
}
513+
Err(mut expr_err) => {
514+
expr_err.cancel();
515+
*self = snapshot;
516+
false
517+
}
518+
}
519+
}
520+
// `x > y == z`
521+
(BinOpKind::Lt, AssocOp::Equal) | (BinOpKind::Le, AssocOp::Equal) |
522+
(BinOpKind::Gt, AssocOp::Equal) | (BinOpKind::Ge, AssocOp::Equal) => {
523+
let snapshot = self.clone();
524+
// At this point it is always valid to enclose the lhs in parentheses, no
525+
// further checks are necessary.
526+
match self.parse_expr() {
527+
Ok(_) => {
528+
enclose(l1.span, r1.span);
529+
true
530+
}
531+
Err(mut expr_err) => {
532+
expr_err.cancel();
533+
*self = snapshot;
534+
false
535+
}
536+
}
537+
}
538+
_ => false,
539+
};
503540
}
541+
false
504542
}
505543

506544
/// Produces an error if comparison operators are chained (RFC #558).
@@ -534,31 +572,26 @@ impl<'a> Parser<'a> {
534572
|this: &Self, span| Ok(Some(this.mk_expr(span, ExprKind::Err, AttrVec::new())));
535573

536574
match inner_op.kind {
537-
ExprKind::Binary(op, _, _) if op.node.is_comparison() => {
538-
// Respan to include both operators.
539-
let op_span = op.span.to(self.prev_token.span);
540-
let mut err =
541-
self.struct_span_err(op_span, "comparison operators cannot be chained");
542-
543-
// If it looks like a genuine attempt to chain operators (as opposed to a
544-
// misformatted turbofish, for instance), suggest a correct form.
545-
self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op);
575+
ExprKind::Binary(op, ref l1, ref r1) if op.node.is_comparison() => {
576+
let mut err = self.struct_span_err(
577+
vec![op.span, self.prev_token.span],
578+
"comparison operators cannot be chained",
579+
);
546580

547581
let suggest = |err: &mut DiagnosticBuilder<'_>| {
548582
err.span_suggestion_verbose(
549-
op_span.shrink_to_lo(),
583+
op.span.shrink_to_lo(),
550584
TURBOFISH,
551585
"::".to_string(),
552586
Applicability::MaybeIncorrect,
553587
);
554588
};
555589

556-
if op.node == BinOpKind::Lt &&
557-
outer_op.node == AssocOp::Less || // Include `<` to provide this recommendation
558-
outer_op.node == AssocOp::Greater
559-
// even in a case like the following:
590+
// Include `<` to provide this recommendation even in a case like
591+
// `Foo<Bar<Baz<Qux, ()>>>`
592+
if op.node == BinOpKind::Lt && outer_op.node == AssocOp::Less
593+
|| outer_op.node == AssocOp::Greater
560594
{
561-
// Foo<Bar<Baz<Qux, ()>>>
562595
if outer_op.node == AssocOp::Less {
563596
let snapshot = self.clone();
564597
self.bump();
@@ -572,7 +605,7 @@ impl<'a> Parser<'a> {
572605
{
573606
// We don't have `foo< bar >(` or `foo< bar >::`, so we rewind the
574607
// parser and bail out.
575-
mem::replace(self, snapshot.clone());
608+
*self = snapshot.clone();
576609
}
577610
}
578611
return if token::ModSep == self.token.kind {
@@ -597,7 +630,7 @@ impl<'a> Parser<'a> {
597630
expr_err.cancel();
598631
// Not entirely sure now, but we bubble the error up with the
599632
// suggestion.
600-
mem::replace(self, snapshot);
633+
*self = snapshot;
601634
Err(err)
602635
}
603636
}
@@ -617,15 +650,33 @@ impl<'a> Parser<'a> {
617650
}
618651
}
619652
} else {
620-
// All we know is that this is `foo < bar >` and *nothing* else. Try to
621-
// be helpful, but don't attempt to recover.
622-
err.help(TURBOFISH);
623-
err.help("or use `(...)` if you meant to specify fn arguments");
624-
// These cases cause too many knock-down errors, bail out (#61329).
625-
Err(err)
653+
if !matches!(l1.kind, ExprKind::Lit(_))
654+
&& !matches!(r1.kind, ExprKind::Lit(_))
655+
{
656+
// All we know is that this is `foo < bar >` and *nothing* else. Try to
657+
// be helpful, but don't attempt to recover.
658+
err.help(TURBOFISH);
659+
err.help("or use `(...)` if you meant to specify fn arguments");
660+
}
661+
662+
// If it looks like a genuine attempt to chain operators (as opposed to a
663+
// misformatted turbofish, for instance), suggest a correct form.
664+
if self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op)
665+
{
666+
err.emit();
667+
mk_err_expr(self, inner_op.span.to(self.prev_token.span))
668+
} else {
669+
// These cases cause too many knock-down errors, bail out (#61329).
670+
Err(err)
671+
}
626672
};
627673
}
674+
let recover =
675+
self.attempt_chained_comparison_suggestion(&mut err, inner_op, outer_op);
628676
err.emit();
677+
if recover {
678+
return mk_err_expr(self, inner_op.span.to(self.prev_token.span));
679+
}
629680
}
630681
_ => {}
631682
}
@@ -643,7 +694,7 @@ impl<'a> Parser<'a> {
643694

644695
if self.token.kind == token::Eof {
645696
// Not entirely sure that what we consumed were fn arguments, rollback.
646-
mem::replace(self, snapshot);
697+
*self = snapshot;
647698
Err(())
648699
} else {
649700
// 99% certain that the suggestion is correct, continue parsing.

src/test/ui/did_you_mean/issue-40396.stderr

+3-19
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,8 @@ error: comparison operators cannot be chained
22
--> $DIR/issue-40396.rs:2:20
33
|
44
LL | (0..13).collect<Vec<i32>>();
5-
| ^^^^^
5+
| ^ ^
66
|
7-
help: split the comparison into two...
8-
|
9-
LL | (0..13).collect < Vec && Vec <i32>>();
10-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11-
help: ...or parenthesize one of the comparisons
12-
|
13-
LL | ((0..13).collect < Vec) <i32>>();
14-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
157
help: use `::<...>` instead of `<...>` to specify type arguments
168
|
179
LL | (0..13).collect::<Vec<i32>>();
@@ -21,7 +13,7 @@ error: comparison operators cannot be chained
2113
--> $DIR/issue-40396.rs:4:8
2214
|
2315
LL | Vec<i32>::new();
24-
| ^^^^^
16+
| ^ ^
2517
|
2618
help: use `::<...>` instead of `<...>` to specify type arguments
2719
|
@@ -32,16 +24,8 @@ error: comparison operators cannot be chained
3224
--> $DIR/issue-40396.rs:6:20
3325
|
3426
LL | (0..13).collect<Vec<i32>();
35-
| ^^^^^
36-
|
37-
help: split the comparison into two...
38-
|
39-
LL | (0..13).collect < Vec && Vec <i32>();
40-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41-
help: ...or parenthesize one of the comparisons
27+
| ^ ^
4228
|
43-
LL | ((0..13).collect < Vec) <i32>();
44-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
4529
help: use `::<...>` instead of `<...>` to specify type arguments
4630
|
4731
LL | (0..13).collect::<Vec<i32>();

src/test/ui/parser/chained-comparison-suggestion.rs

+13
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,17 @@ fn comp8() {
3737
//~^ ERROR mismatched types
3838
}
3939

40+
fn comp9() {
41+
1 == 2 < 3; //~ ERROR comparison operators cannot be chained
42+
}
43+
44+
fn comp10() {
45+
1 > 2 == false; //~ ERROR comparison operators cannot be chained
46+
}
47+
48+
fn comp11() {
49+
1 == 2 == 3; //~ ERROR comparison operators cannot be chained
50+
//~^ ERROR mismatched types
51+
}
52+
4053
fn main() {}

0 commit comments

Comments
 (0)