Auto merge of #143213 - dianne:lower-cond-tweaks, r=cjgillot
de-duplicate condition scoping logic between AST→HIR lowering and `ScopeTree` construction
There was some overlap between `rustc_ast_lowering::LoweringContext::lower_cond` and `rustc_hir_analysis::check::region::resolve_expr`, so I've removed the former and migrated its logic to the latter, with some simplifications.
Consequences:
- For `while` and `if` expressions' `let`-chains, this changes the `HirId`s for the `&&`s to properly correspond to their AST nodes. This is how guards were handled already.
- This makes match guards share previously-duplicated logic with `if`/`while` expressions. This will also be used by guard pattern[^1] guards.
- Aside from legacy syntax extensions (e.g. some builtin macros) that directly feed AST to the compiler, it's currently impossible to put attributes directly on `&&` operators in `let` chains[^2]. Nonetheless, attributes on `&&` operators in `let` chains in `if`/`while` expression conditions are no longer silently ignored and will be lowered.
- This no longer wraps conditions in `DropTemps`, so the HIR and THIR will be slightly smaller.
- `DesugaringKind::CondTemporary` is now gone. It's no longer applied to any spans, and all uses of it were dead since they were made to account for `if` and `while` being desugared to `match` on a boolean scrutinee.
- Should be a marginal perf improvement beyond that due to leveraging [`ScopeTree` construction](5e749eb66f/compiler/rustc_hir_analysis/src/check/region.rs (L312-L355))'s clever handling of `&&` and `||`:
- This removes some unnecessary terminating scopes that were placed around top-level `&&` and `||` operators in conditions. When lowered to MIR, logical operator chains don't create intermediate boolean temporaries, so there's no temporary to drop. The linked snippet handles wrapping the operands in terminating scopes as necessary, in case they create temporaries.
- The linked snippet takes care of letting `let` temporaries live and terminating other operands, so we don't need separate traversals of `&&` chains for that.
[^1]: rust-lang/rust#129967
[^2]: Case-by-case, here's my justification: `#[attr] e1 && e2` applies the attribute to `e1`. In `#[attr] (e1 && e2)` , the attribute is on the parentheses in the AST, plus it'd fail to parse if `e1` or `e2` contains a `let`. In `#[attr] expands_to_let_chain!()`, the attribute would already be ignored (rust-lang/rust#63221) and it'd fail to parse anyway; even if the expansion site is a condition, the expansion wouldn't be parsed with `Restrictions::ALLOW_LET`. If it *was* allowed, the notion of a "reparse context" from https://github.com/rust-lang/rust/issues/61733#issuecomment-509626449 would be necessary in order to make `let`-chains left-associative; multiple places in the compiler assume they are.
This commit is contained in:
commit
d2baa49a10
13 changed files with 63 additions and 128 deletions
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_config::Conf;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::higher::has_let_expr;
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::SpanRangeExt;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
|
|
@ -646,7 +647,9 @@ impl<'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'_, 'tcx> {
|
|||
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
|
||||
if !e.span.from_expansion() {
|
||||
match &e.kind {
|
||||
ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => {
|
||||
ExprKind::Binary(binop, _, _)
|
||||
if binop.node == BinOpKind::Or || binop.node == BinOpKind::And && !has_let_expr(e) =>
|
||||
{
|
||||
self.bool_expr(e);
|
||||
},
|
||||
ExprKind::Unary(UnOp::Not, inner) => {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]);
|
|||
impl LateLintPass<'_> for IfNotElse {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
|
||||
if let ExprKind::If(cond, cond_inner, Some(els)) = e.kind
|
||||
&& let ExprKind::DropTemps(cond) = cond.kind
|
||||
&& let ExprKind::Block(..) = els.kind
|
||||
{
|
||||
let (msg, help) = match cond.kind {
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
|
|||
impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if let ExprKind::If(cond, then, None) = expr.kind
|
||||
&& let ExprKind::DropTemps(expr1) = cond.kind
|
||||
&& let Some((c, op_node, l)) = get_const(cx, expr1)
|
||||
&& let Some((c, op_node, l)) = get_const(cx, cond)
|
||||
&& let BinOpKind::Ne | BinOpKind::Lt = op_node
|
||||
&& let ExprKind::Block(block, None) = then.kind
|
||||
&& let Block {
|
||||
|
|
@ -66,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
|
|||
&& Some(c) == get_int_max(ty)
|
||||
&& let ctxt = expr.span.ctxt()
|
||||
&& ex.span.ctxt() == ctxt
|
||||
&& expr1.span.ctxt() == ctxt
|
||||
&& cond.span.ctxt() == ctxt
|
||||
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
|
||||
&& AssignOpKind::AddAssign == op1.node
|
||||
&& let ExprKind::Lit(lit) = value.kind
|
||||
|
|
|
|||
|
|
@ -62,15 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
|
|||
if let hir::StmtKind::Let(local) = stmt.kind
|
||||
&& let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
|
||||
&& let hir::StmtKind::Expr(if_) = next.kind
|
||||
&& let hir::ExprKind::If(
|
||||
hir::Expr {
|
||||
kind: hir::ExprKind::DropTemps(cond),
|
||||
..
|
||||
},
|
||||
then,
|
||||
else_,
|
||||
) = if_.kind
|
||||
&& !is_local_used(cx, *cond, canonical_id)
|
||||
&& let hir::ExprKind::If(cond, then, else_) = if_.kind
|
||||
&& !is_local_used(cx, cond, canonical_id)
|
||||
&& let hir::ExprKind::Block(then, _) = then.kind
|
||||
&& let Some(value) = check_assign(cx, canonical_id, then)
|
||||
&& !is_local_used(cx, value, canonical_id)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ impl<'tcx> ForLoop<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// An `if` expression without `DropTemps`
|
||||
/// An `if` expression without `let`
|
||||
pub struct If<'hir> {
|
||||
/// `if` condition
|
||||
pub cond: &'hir Expr<'hir>,
|
||||
|
|
@ -66,16 +66,10 @@ pub struct If<'hir> {
|
|||
|
||||
impl<'hir> If<'hir> {
|
||||
#[inline]
|
||||
/// Parses an `if` expression
|
||||
/// Parses an `if` expression without `let`
|
||||
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
|
||||
if let ExprKind::If(
|
||||
Expr {
|
||||
kind: ExprKind::DropTemps(cond),
|
||||
..
|
||||
},
|
||||
then,
|
||||
r#else,
|
||||
) = expr.kind
|
||||
if let ExprKind::If(cond, then, r#else) = expr.kind
|
||||
&& !has_let_expr(cond)
|
||||
{
|
||||
Some(Self { cond, then, r#else })
|
||||
} else {
|
||||
|
|
@ -198,18 +192,10 @@ impl<'hir> IfOrIfLet<'hir> {
|
|||
/// Parses an `if` or `if let` expression
|
||||
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
|
||||
if let ExprKind::If(cond, then, r#else) = expr.kind {
|
||||
if let ExprKind::DropTemps(new_cond) = cond.kind {
|
||||
return Some(Self {
|
||||
cond: new_cond,
|
||||
then,
|
||||
r#else,
|
||||
});
|
||||
}
|
||||
if let ExprKind::Let(..) = cond.kind {
|
||||
return Some(Self { cond, then, r#else });
|
||||
}
|
||||
Some(Self { cond, then, r#else })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,15 +329,7 @@ impl<'hir> While<'hir> {
|
|||
Block {
|
||||
expr:
|
||||
Some(Expr {
|
||||
kind:
|
||||
ExprKind::If(
|
||||
Expr {
|
||||
kind: ExprKind::DropTemps(condition),
|
||||
..
|
||||
},
|
||||
body,
|
||||
_,
|
||||
),
|
||||
kind: ExprKind::If(condition, body, _),
|
||||
..
|
||||
}),
|
||||
..
|
||||
|
|
@ -360,6 +338,7 @@ impl<'hir> While<'hir> {
|
|||
LoopSource::While,
|
||||
span,
|
||||
) = expr.kind
|
||||
&& !has_let_expr(condition)
|
||||
{
|
||||
return Some(Self { condition, body, span });
|
||||
}
|
||||
|
|
@ -493,3 +472,13 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Checks that a condition doesn't have a `let` expression, to keep `If` and `While` from accepting
|
||||
/// `if let` and `while let`.
|
||||
pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
|
||||
match &cond.kind {
|
||||
ExprKind::Let(_) => true,
|
||||
ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
if let StmtKind::Let(local) = stmt.kind
|
||||
&& let Some(init) = local.init
|
||||
&& let ExprKind::If(cond, then, Some(else_expr)) = init.kind
|
||||
&& let ExprKind::DropTemps(expr) = cond.kind
|
||||
&& let ExprKind::Lit(ref lit) = expr.kind
|
||||
&& let ExprKind::Lit(ref lit) = cond.kind
|
||||
&& let LitKind::Bool(true) = lit.node
|
||||
&& let ExprKind::Block(block, None) = then.kind
|
||||
&& block.stmts.len() == 1
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ if let ExprKind::Struct(qpath, fields, None) = expr.kind
|
|||
&& fields.len() == 1
|
||||
&& fields[0].ident.as_str() == "field"
|
||||
&& let ExprKind::If(cond, then, Some(else_expr)) = fields[0].expr.kind
|
||||
&& let ExprKind::DropTemps(expr1) = cond.kind
|
||||
&& let ExprKind::Lit(ref lit) = expr1.kind
|
||||
&& let ExprKind::Lit(ref lit) = cond.kind
|
||||
&& let LitKind::Bool(true) = lit.node
|
||||
&& let ExprKind::Block(block, None) = then.kind
|
||||
&& block.stmts.is_empty()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue