Skip to content

Commit 9cd272d

Browse files
committed
Auto merge of #146044 - estebank:issue-88727, r=chenyukang
Suggest parentheses when `match` or `if` expression in binop is parsed as statement ``` error[E0308]: mismatched types --> $DIR/expr-as-stmt.rs:81:5 | LL | match () { _ => true } && match () { _ => true }; | ^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `bool` | help: parentheses are required to parse this as an expression | LL | (match () { _ => true }) && match () { _ => true }; | + + ``` Address the common case from #88727. The original parse error is still outstanding, but the cases brought up in the thread are resolved.
2 parents c559c4a + ff60e5c commit 9cd272d

File tree

8 files changed

+393
-49
lines changed

8 files changed

+393
-49
lines changed

compiler/rustc_hir_typeck/src/coercion.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1897,7 +1897,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
18971897
fcx.suggest_semicolon_at_end(cond_expr.span, &mut err);
18981898
}
18991899
}
1900-
};
1900+
}
19011901

19021902
// If this is due to an explicit `return`, suggest adding a return type.
19031903
if let Some((fn_id, fn_decl)) = fcx.get_fn_decl(block_or_return_id)

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1912,7 +1912,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19121912
hir::StmtKind::Expr(ref expr) => {
19131913
// Check with expected type of `()`.
19141914
self.check_expr_has_type_or_error(expr, self.tcx.types.unit, |err| {
1915-
if expr.can_have_side_effects() {
1915+
if self.is_next_stmt_expr_continuation(stmt.hir_id)
1916+
&& let hir::ExprKind::Match(..) | hir::ExprKind::If(..) = expr.kind
1917+
{
1918+
// We have something like `match () { _ => true } && true`. Suggest
1919+
// wrapping in parentheses. We find the statement or expression
1920+
// following the `match` (`&& true`) and see if it is something that
1921+
// can reasonably be interpreted as a binop following an expression.
1922+
err.multipart_suggestion(
1923+
"parentheses are required to parse this as an expression",
1924+
vec![
1925+
(expr.span.shrink_to_lo(), "(".to_string()),
1926+
(expr.span.shrink_to_hi(), ")".to_string()),
1927+
],
1928+
Applicability::MachineApplicable,
1929+
);
1930+
} else if expr.can_have_side_effects() {
19161931
self.suggest_semicolon_at_end(expr.span, err);
19171932
}
19181933
});

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

Lines changed: 106 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
use core::cmp::min;
23
use core::iter;
34

@@ -766,56 +767,121 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
766767
needs_block: bool,
767768
parent_is_closure: bool,
768769
) {
769-
if expected.is_unit() {
770-
// `BlockTailExpression` only relevant if the tail expr would be
771-
// useful on its own.
772-
match expression.kind {
773-
ExprKind::Call(..)
774-
| ExprKind::MethodCall(..)
775-
| ExprKind::Loop(..)
776-
| ExprKind::If(..)
777-
| ExprKind::Match(..)
778-
| ExprKind::Block(..)
779-
if expression.can_have_side_effects()
780-
// If the expression is from an external macro, then do not suggest
781-
// adding a semicolon, because there's nowhere to put it.
782-
// See issue #81943.
783-
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
770+
if !expected.is_unit() {
771+
return;
772+
}
773+
// `BlockTailExpression` only relevant if the tail expr would be
774+
// useful on its own.
775+
match expression.kind {
776+
ExprKind::Call(..)
777+
| ExprKind::MethodCall(..)
778+
| ExprKind::Loop(..)
779+
| ExprKind::If(..)
780+
| ExprKind::Match(..)
781+
| ExprKind::Block(..)
782+
if expression.can_have_side_effects()
783+
// If the expression is from an external macro, then do not suggest
784+
// adding a semicolon, because there's nowhere to put it.
785+
// See issue #81943.
786+
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
787+
{
788+
if needs_block {
789+
err.multipart_suggestion(
790+
"consider using a semicolon here",
791+
vec![
792+
(expression.span.shrink_to_lo(), "{ ".to_owned()),
793+
(expression.span.shrink_to_hi(), "; }".to_owned()),
794+
],
795+
Applicability::MachineApplicable,
796+
);
797+
} else if let hir::Node::Block(block) = self.tcx.parent_hir_node(expression.hir_id)
798+
&& let hir::Node::Expr(expr) = self.tcx.parent_hir_node(block.hir_id)
799+
&& let hir::Node::Expr(if_expr) = self.tcx.parent_hir_node(expr.hir_id)
800+
&& let hir::ExprKind::If(_cond, _then, Some(_else)) = if_expr.kind
801+
&& let hir::Node::Stmt(stmt) = self.tcx.parent_hir_node(if_expr.hir_id)
802+
&& let hir::StmtKind::Expr(_) = stmt.kind
803+
&& self.is_next_stmt_expr_continuation(stmt.hir_id)
784804
{
785-
if needs_block {
786-
err.multipart_suggestion(
787-
"consider using a semicolon here",
788-
vec![
789-
(expression.span.shrink_to_lo(), "{ ".to_owned()),
790-
(expression.span.shrink_to_hi(), "; }".to_owned()),
791-
],
792-
Applicability::MachineApplicable,
793-
);
794-
} else {
795-
err.span_suggestion(
796-
expression.span.shrink_to_hi(),
797-
"consider using a semicolon here",
798-
";",
799-
Applicability::MachineApplicable,
800-
);
801-
}
805+
err.multipart_suggestion(
806+
"parentheses are required to parse this as an expression",
807+
vec![
808+
(stmt.span.shrink_to_lo(), "(".to_string()),
809+
(stmt.span.shrink_to_hi(), ")".to_string()),
810+
],
811+
Applicability::MachineApplicable,
812+
);
813+
} else {
814+
err.span_suggestion(
815+
expression.span.shrink_to_hi(),
816+
"consider using a semicolon here",
817+
";",
818+
Applicability::MachineApplicable,
819+
);
802820
}
803-
ExprKind::Path(..) | ExprKind::Lit(_)
804-
if parent_is_closure
805-
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
821+
}
822+
ExprKind::Path(..) | ExprKind::Lit(_)
823+
if parent_is_closure
824+
&& !expression.span.in_external_macro(self.tcx.sess.source_map()) =>
825+
{
826+
err.span_suggestion_verbose(
827+
expression.span.shrink_to_lo(),
828+
"consider ignoring the value",
829+
"_ = ",
830+
Applicability::MachineApplicable,
831+
);
832+
}
833+
_ => {
834+
if let hir::Node::Block(block) = self.tcx.parent_hir_node(expression.hir_id)
835+
&& let hir::Node::Expr(expr) = self.tcx.parent_hir_node(block.hir_id)
836+
&& let hir::Node::Expr(if_expr) = self.tcx.parent_hir_node(expr.hir_id)
837+
&& let hir::ExprKind::If(_cond, _then, Some(_else)) = if_expr.kind
838+
&& let hir::Node::Stmt(stmt) = self.tcx.parent_hir_node(if_expr.hir_id)
839+
&& let hir::StmtKind::Expr(_) = stmt.kind
840+
&& self.is_next_stmt_expr_continuation(stmt.hir_id)
806841
{
807-
err.span_suggestion_verbose(
808-
expression.span.shrink_to_lo(),
809-
"consider ignoring the value",
810-
"_ = ",
842+
// The error is pointing at an arm of an if-expression, and we want to get the
843+
// `Span` of the whole if-expression for the suggestion. This only works for a
844+
// single level of nesting, which is fine.
845+
// We have something like `if true { false } else { true } && true`. Suggest
846+
// wrapping in parentheses. We find the statement or expression following the
847+
// `if` (`&& true`) and see if it is something that can reasonably be
848+
// interpreted as a binop following an expression.
849+
err.multipart_suggestion(
850+
"parentheses are required to parse this as an expression",
851+
vec![
852+
(stmt.span.shrink_to_lo(), "(".to_string()),
853+
(stmt.span.shrink_to_hi(), ")".to_string()),
854+
],
811855
Applicability::MachineApplicable,
812856
);
813857
}
814-
_ => (),
815858
}
816859
}
817860
}
818861

862+
pub(crate) fn is_next_stmt_expr_continuation(&self, hir_id: HirId) -> bool {
863+
if let hir::Node::Block(b) = self.tcx.parent_hir_node(hir_id)
864+
&& let mut stmts = b.stmts.iter().skip_while(|s| s.hir_id != hir_id)
865+
&& let Some(_) = stmts.next() // The statement the statement that was passed in
866+
&& let Some(next) = match (stmts.next(), b.expr) { // The following statement
867+
(Some(next), _) => match next.kind {
868+
hir::StmtKind::Expr(next) | hir::StmtKind::Semi(next) => Some(next),
869+
_ => None,
870+
},
871+
(None, Some(next)) => Some(next),
872+
_ => None,
873+
}
874+
&& let hir::ExprKind::AddrOf(..) // prev_stmt && next
875+
| hir::ExprKind::Unary(..) // prev_stmt * next
876+
| hir::ExprKind::Err(_) = next.kind
877+
// prev_stmt + next
878+
{
879+
true
880+
} else {
881+
false
882+
}
883+
}
884+
819885
/// A possible error is to forget to add a return type that is needed:
820886
///
821887
/// ```compile_fail,E0308

tests/ui/parser/expr-as-stmt-2.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,25 @@ fn foo(a: Option<u32>, b: Option<u32>) -> bool {
77
if let Some(y) = a { true } else { false }
88
}
99

10-
fn main() {}
10+
fn bar() -> bool {
11+
false
12+
}
13+
14+
fn main() {
15+
if true { true } else { false } && true;
16+
//~^ ERROR mismatched types
17+
//~| ERROR mismatched types
18+
if true { true } else { false } && if true { true } else { false };
19+
//~^ ERROR mismatched types
20+
//~| ERROR mismatched types
21+
if true { true } else { false } if true { true } else { false };
22+
//~^ ERROR mismatched types
23+
//~| ERROR mismatched types
24+
if true { bar() } else { bar() } && if true { bar() } else { bar() };
25+
//~^ ERROR mismatched types
26+
//~| ERROR mismatched types
27+
if true { bar() } else { bar() } if true { bar() } else { bar() };
28+
//~^ ERROR mismatched types
29+
//~| ERROR mismatched types
30+
let _ = if true { true } else { false } && true; // ok
31+
}

0 commit comments

Comments
 (0)