Skip to content

Commit 29bc94f

Browse files
committed
Handle let-else initializer edge case errors
1 parent 2f4e86b commit 29bc94f

File tree

3 files changed

+101
-15
lines changed

3 files changed

+101
-15
lines changed

compiler/rustc_ast/src/util/classify.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,30 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
2323
| ast::ExprKind::TryBlock(..)
2424
)
2525
}
26+
27+
/// If an expression ends with `}`, returns the innermost expression ending in the `}`
28+
pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
29+
use ast::ExprKind::*;
30+
31+
loop {
32+
match &expr.kind {
33+
AddrOf(_, _, e)
34+
| Assign(_, e, _)
35+
| AssignOp(_, _, e)
36+
| Binary(_, _, e)
37+
| Box(e)
38+
| Break(_, Some(e))
39+
| Closure(.., e, _)
40+
| Let(_, e, _)
41+
| Range(_, Some(e), _)
42+
| Ret(Some(e))
43+
| Unary(_, e)
44+
| Yield(Some(e)) => {
45+
expr = e;
46+
}
47+
Async(..) | Block(..) | ForLoop(..) | If(..) | Loop(..) | Match(..) | Struct(..)
48+
| TryBlock(..) | While(..) => break Some(expr),
49+
_ => break None,
50+
}
51+
}
52+
}

compiler/rustc_lint/src/unused.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::Lint;
22
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
33
use rustc_ast as ast;
4-
use rustc_ast::util::parser;
4+
use rustc_ast::util::{classify, parser};
55
use rustc_ast::{ExprKind, StmtKind};
66
use rustc_ast_pretty::pprust;
77
use rustc_errors::{pluralize, Applicability};
@@ -382,6 +382,7 @@ enum UnusedDelimsCtx {
382382
FunctionArg,
383383
MethodArg,
384384
AssignedValue,
385+
AssignedValueLetElse,
385386
IfCond,
386387
WhileCond,
387388
ForIterExpr,
@@ -398,7 +399,9 @@ impl From<UnusedDelimsCtx> for &'static str {
398399
match ctx {
399400
UnusedDelimsCtx::FunctionArg => "function argument",
400401
UnusedDelimsCtx::MethodArg => "method argument",
401-
UnusedDelimsCtx::AssignedValue => "assigned value",
402+
UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => {
403+
"assigned value"
404+
}
402405
UnusedDelimsCtx::IfCond => "`if` condition",
403406
UnusedDelimsCtx::WhileCond => "`while` condition",
404407
UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
@@ -441,14 +444,26 @@ trait UnusedDelimLint {
441444
right_pos: Option<BytePos>,
442445
);
443446

444-
fn is_expr_delims_necessary(inner: &ast::Expr, followed_by_block: bool) -> bool {
447+
fn is_expr_delims_necessary(
448+
inner: &ast::Expr,
449+
followed_by_block: bool,
450+
followed_by_else: bool,
451+
) -> bool {
452+
if followed_by_else {
453+
match inner.kind {
454+
ast::ExprKind::Binary(op, ..) if op.node.lazy() => return true,
455+
_ if classify::expr_trailing_brace(inner).is_some() => return true,
456+
_ => {}
457+
}
458+
}
459+
445460
// Prevent false-positives in cases like `fn x() -> u8 { ({ 0 } + 1) }`
446461
let lhs_needs_parens = {
447462
let mut innermost = inner;
448463
loop {
449464
if let ExprKind::Binary(_, lhs, _rhs) = &innermost.kind {
450465
innermost = lhs;
451-
if !rustc_ast::util::classify::expr_requires_semi_to_be_stmt(innermost) {
466+
if !classify::expr_requires_semi_to_be_stmt(innermost) {
452467
break true;
453468
}
454469
} else {
@@ -618,15 +633,12 @@ trait UnusedDelimLint {
618633
fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
619634
match s.kind {
620635
StmtKind::Local(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
621-
if let Some(value) = local.kind.init() {
622-
self.check_unused_delims_expr(
623-
cx,
624-
&value,
625-
UnusedDelimsCtx::AssignedValue,
626-
false,
627-
None,
628-
None,
629-
);
636+
if let Some((init, els)) = local.kind.init_else_opt() {
637+
let ctx = match els {
638+
None => UnusedDelimsCtx::AssignedValue,
639+
Some(_) => UnusedDelimsCtx::AssignedValueLetElse,
640+
};
641+
self.check_unused_delims_expr(cx, init, ctx, false, None, None);
630642
}
631643
}
632644
StmtKind::Expr(ref expr) => {
@@ -702,7 +714,8 @@ impl UnusedDelimLint for UnusedParens {
702714
) {
703715
match value.kind {
704716
ast::ExprKind::Paren(ref inner) => {
705-
if !Self::is_expr_delims_necessary(inner, followed_by_block)
717+
let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
718+
if !Self::is_expr_delims_necessary(inner, followed_by_block, followed_by_else)
706719
&& value.attrs.is_empty()
707720
&& !value.span.from_expansion()
708721
&& (ctx != UnusedDelimsCtx::LetScrutineeExpr
@@ -941,7 +954,7 @@ impl UnusedDelimLint for UnusedBraces {
941954
// FIXME(const_generics): handle paths when #67075 is fixed.
942955
if let [stmt] = inner.stmts.as_slice() {
943956
if let ast::StmtKind::Expr(ref expr) = stmt.kind {
944-
if !Self::is_expr_delims_necessary(expr, followed_by_block)
957+
if !Self::is_expr_delims_necessary(expr, followed_by_block, false)
945958
&& (ctx != UnusedDelimsCtx::AnonConst
946959
|| matches!(expr.kind, ast::ExprKind::Lit(_)))
947960
&& !cx.sess().source_map().is_multiline(value.span)

compiler/rustc_parse/src/parser/stmt.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ impl<'a> Parser<'a> {
298298
Some(init) => {
299299
if self.eat_keyword(kw::Else) {
300300
let els = self.parse_block()?;
301+
self.check_let_else_init_bool_expr(&init);
302+
self.check_let_else_init_trailing_brace(&init);
301303
LocalKind::InitElse(init, els)
302304
} else {
303305
LocalKind::Init(init)
@@ -308,6 +310,50 @@ impl<'a> Parser<'a> {
308310
Ok(P(ast::Local { ty, pat, kind, id: DUMMY_NODE_ID, span: lo.to(hi), attrs, tokens: None }))
309311
}
310312

313+
fn check_let_else_init_bool_expr(&self, init: &ast::Expr) {
314+
if let ast::ExprKind::Binary(op, ..) = init.kind {
315+
if op.node.lazy() {
316+
let suggs = vec![
317+
(init.span.shrink_to_lo(), "(".to_string()),
318+
(init.span.shrink_to_hi(), ")".to_string()),
319+
];
320+
self.struct_span_err(
321+
init.span,
322+
&format!(
323+
"a `{}` expression cannot be directly assigned in `let...else`",
324+
op.node.to_string()
325+
),
326+
)
327+
.multipart_suggestion(
328+
"wrap the expression in parenthesis",
329+
suggs,
330+
Applicability::MachineApplicable,
331+
)
332+
.emit();
333+
}
334+
}
335+
}
336+
337+
fn check_let_else_init_trailing_brace(&self, init: &ast::Expr) {
338+
if let Some(trailing) = classify::expr_trailing_brace(init) {
339+
let err_span = trailing.span.with_lo(trailing.span.hi() - BytePos(1));
340+
let suggs = vec![
341+
(trailing.span.shrink_to_lo(), "(".to_string()),
342+
(trailing.span.shrink_to_hi(), ")".to_string()),
343+
];
344+
self.struct_span_err(
345+
err_span,
346+
"right curly brace `}` before `else` in a `let...else` statement not allowed",
347+
)
348+
.multipart_suggestion(
349+
"try wrapping the expression in parenthesis",
350+
suggs,
351+
Applicability::MachineApplicable,
352+
)
353+
.emit();
354+
}
355+
}
356+
311357
/// Parses the RHS of a local variable declaration (e.g., `= 14;`).
312358
fn parse_initializer(&mut self, eq_optional: bool) -> PResult<'a, Option<P<Expr>>> {
313359
let eq_consumed = match self.token.kind {

0 commit comments

Comments
 (0)