Skip to content

Commit d2baa49

Browse files
committed
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](https://github.com/rust-lang/rust/blob/5e749eb66f93ee998145399fbdde337e57cd72ef/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]: #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 (#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 #61733 (comment) would be necessary in order to make `let`-chains left-associative; multiple places in the compiler assume they are.
2 parents b1d2f2c + 68d860f commit d2baa49

File tree

13 files changed

+63
-128
lines changed

13 files changed

+63
-128
lines changed

compiler/rustc_ast_lowering/src/expr.rs

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
528528
then: &Block,
529529
else_opt: Option<&Expr>,
530530
) -> hir::ExprKind<'hir> {
531-
let lowered_cond = self.lower_cond(cond);
531+
let lowered_cond = self.lower_expr(cond);
532532
let then_expr = self.lower_block_expr(then);
533533
if let Some(rslt) = else_opt {
534534
hir::ExprKind::If(
@@ -541,44 +541,6 @@ impl<'hir> LoweringContext<'_, 'hir> {
541541
}
542542
}
543543

544-
// Lowers a condition (i.e. `cond` in `if cond` or `while cond`), wrapping it in a terminating scope
545-
// so that temporaries created in the condition don't live beyond it.
546-
fn lower_cond(&mut self, cond: &Expr) -> &'hir hir::Expr<'hir> {
547-
fn has_let_expr(expr: &Expr) -> bool {
548-
match &expr.kind {
549-
ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
550-
ExprKind::Let(..) => true,
551-
_ => false,
552-
}
553-
}
554-
555-
// We have to take special care for `let` exprs in the condition, e.g. in
556-
// `if let pat = val` or `if foo && let pat = val`, as we _do_ want `val` to live beyond the
557-
// condition in this case.
558-
//
559-
// In order to maintain the drop behavior for the non `let` parts of the condition,
560-
// we still wrap them in terminating scopes, e.g. `if foo && let pat = val` essentially
561-
// gets transformed into `if { let _t = foo; _t } && let pat = val`
562-
match &cond.kind {
563-
ExprKind::Binary(op @ Spanned { node: ast::BinOpKind::And, .. }, lhs, rhs)
564-
if has_let_expr(cond) =>
565-
{
566-
let op = self.lower_binop(*op);
567-
let lhs = self.lower_cond(lhs);
568-
let rhs = self.lower_cond(rhs);
569-
570-
self.arena.alloc(self.expr(cond.span, hir::ExprKind::Binary(op, lhs, rhs)))
571-
}
572-
ExprKind::Let(..) => self.lower_expr(cond),
573-
_ => {
574-
let cond = self.lower_expr(cond);
575-
let reason = DesugaringKind::CondTemporary;
576-
let span_block = self.mark_span_with_reason(reason, cond.span, None);
577-
self.expr_drop_temps(span_block, cond)
578-
}
579-
}
580-
}
581-
582544
// We desugar: `'label: while $cond $body` into:
583545
//
584546
// ```
@@ -602,7 +564,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
602564
body: &Block,
603565
opt_label: Option<Label>,
604566
) -> hir::ExprKind<'hir> {
605-
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_cond(cond));
567+
let lowered_cond = self.with_loop_condition_scope(|t| t.lower_expr(cond));
606568
let then = self.lower_block_expr(body);
607569
let expr_break = self.expr_break(span);
608570
let stmt_break = self.stmt_expr(span, expr_break);
@@ -2091,7 +2053,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
20912053
/// In terms of drop order, it has the same effect as wrapping `expr` in
20922054
/// `{ let _t = $expr; _t }` but should provide better compile-time performance.
20932055
///
2094-
/// The drop order can be important in e.g. `if expr { .. }`.
2056+
/// The drop order can be important, e.g. to drop temporaries from an `async fn`
2057+
/// body before its parameters.
20952058
pub(super) fn expr_drop_temps(
20962059
&mut self,
20972060
span: Span,

compiler/rustc_hir_analysis/src/check/region.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -167,23 +167,39 @@ fn resolve_block<'tcx>(
167167
visitor.cx = prev_cx;
168168
}
169169

170-
fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir::Arm<'tcx>) {
171-
fn has_let_expr(expr: &Expr<'_>) -> bool {
172-
match &expr.kind {
173-
hir::ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
174-
hir::ExprKind::Let(..) => true,
175-
_ => false,
176-
}
177-
}
170+
/// Resolve a condition from an `if` expression or match guard so that it is a terminating scope
171+
/// if it doesn't contain `let` expressions.
172+
fn resolve_cond<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, cond: &'tcx hir::Expr<'tcx>) {
173+
let terminate = match cond.kind {
174+
// Temporaries for `let` expressions must live into the success branch.
175+
hir::ExprKind::Let(_) => false,
176+
// Logical operator chains are handled in `resolve_expr`. Since logical operator chains in
177+
// conditions are lowered to control-flow rather than boolean temporaries, there's no
178+
// temporary to drop for logical operators themselves. `resolve_expr` will also recursively
179+
// wrap any operands in terminating scopes, other than `let` expressions (which we shouldn't
180+
// terminate) and other logical operators (which don't need a terminating scope, since their
181+
// operands will be terminated). Any temporaries that would need to be dropped will be
182+
// dropped before we leave this operator's scope; terminating them here would be redundant.
183+
hir::ExprKind::Binary(
184+
source_map::Spanned { node: hir::BinOpKind::And | hir::BinOpKind::Or, .. },
185+
_,
186+
_,
187+
) => false,
188+
// Otherwise, conditions should always drop their temporaries.
189+
_ => true,
190+
};
191+
resolve_expr(visitor, cond, terminate);
192+
}
178193

194+
fn resolve_arm<'tcx>(visitor: &mut ScopeResolutionVisitor<'tcx>, arm: &'tcx hir::Arm<'tcx>) {
179195
let prev_cx = visitor.cx;
180196

181197
visitor.enter_node_scope_with_dtor(arm.hir_id.local_id, true);
182198
visitor.cx.var_parent = visitor.cx.parent;
183199

184200
resolve_pat(visitor, arm.pat);
185201
if let Some(guard) = arm.guard {
186-
resolve_expr(visitor, guard, !has_let_expr(guard));
202+
resolve_cond(visitor, guard);
187203
}
188204
resolve_expr(visitor, arm.body, false);
189205

@@ -340,7 +356,7 @@ fn resolve_expr<'tcx>(
340356
};
341357
visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data });
342358
visitor.cx.var_parent = visitor.cx.parent;
343-
visitor.visit_expr(cond);
359+
resolve_cond(visitor, cond);
344360
resolve_expr(visitor, then, true);
345361
visitor.cx = expr_cx;
346362
resolve_expr(visitor, otherwise, true);
@@ -355,7 +371,7 @@ fn resolve_expr<'tcx>(
355371
};
356372
visitor.enter_scope(Scope { local_id: then.hir_id.local_id, data });
357373
visitor.cx.var_parent = visitor.cx.parent;
358-
visitor.visit_expr(cond);
374+
resolve_cond(visitor, cond);
359375
resolve_expr(visitor, then, true);
360376
visitor.cx = expr_cx;
361377
}

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5151
};
5252

5353
match span.desugaring_kind() {
54-
// If span arose from a desugaring of `if` or `while`, then it is the condition
55-
// itself, which diverges, that we are about to lint on. This gives suboptimal
56-
// diagnostics. Instead, stop here so that the `if`- or `while`-expression's
57-
// block is linted instead.
58-
Some(DesugaringKind::CondTemporary) => return,
59-
6054
// Don't lint if the result of an async block or async function is `!`.
6155
// This does not affect the unreachable lints *within* the body.
6256
Some(DesugaringKind::Async) => return,

compiler/rustc_hir_typeck/src/lib.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -428,11 +428,9 @@ fn report_unexpected_variant_res(
428428
}
429429
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Binary(..), hir_id, .. }) => {
430430
suggestion.push((expr.span.shrink_to_lo(), "(".to_string()));
431-
if let hir::Node::Expr(drop_temps) = tcx.parent_hir_node(*hir_id)
432-
&& let hir::ExprKind::DropTemps(_) = drop_temps.kind
433-
&& let hir::Node::Expr(parent) = tcx.parent_hir_node(drop_temps.hir_id)
431+
if let hir::Node::Expr(parent) = tcx.parent_hir_node(*hir_id)
434432
&& let hir::ExprKind::If(condition, block, None) = parent.kind
435-
&& condition.hir_id == drop_temps.hir_id
433+
&& condition.hir_id == *hir_id
436434
&& let hir::ExprKind::Block(block, _) = block.kind
437435
&& block.stmts.is_empty()
438436
&& let Some(expr) = block.expr

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
2424
use rustc_session::parse::feature_err;
2525
use rustc_span::edit_distance::find_best_match_for_name;
2626
use rustc_span::edition::Edition;
27-
use rustc_span::hygiene::DesugaringKind;
2827
use rustc_span::source_map::Spanned;
2928
use rustc_span::{BytePos, DUMMY_SP, Ident, Span, kw, sym};
3029
use rustc_trait_selection::infer::InferCtxtExt;
@@ -902,16 +901,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
902901
// then that's equivalent to there existing a LUB.
903902
let cause = self.pattern_cause(ti, span);
904903
if let Err(err) = self.demand_suptype_with_origin(&cause, expected, pat_ty) {
905-
err.emit_unless(
906-
ti.span
907-
.filter(|&s| {
908-
// In the case of `if`- and `while`-expressions we've already checked
909-
// that `scrutinee: bool`. We know that the pattern is `true`,
910-
// so an error here would be a duplicate and from the wrong POV.
911-
s.is_desugaring(DesugaringKind::CondTemporary)
912-
})
913-
.is_some(),
914-
);
904+
err.emit();
915905
}
916906

917907
pat_ty

compiler/rustc_span/src/hygiene.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,11 +1191,6 @@ impl AstPass {
11911191
/// The kind of compiler desugaring.
11921192
#[derive(Clone, Copy, PartialEq, Debug, Encodable, Decodable, HashStable_Generic)]
11931193
pub enum DesugaringKind {
1194-
/// We desugar `if c { i } else { e }` to `match $ExprKind::Use(c) { true => i, _ => e }`.
1195-
/// However, we do not want to blame `c` for unreachability but rather say that `i`
1196-
/// is unreachable. This desugaring kind allows us to avoid blaming `c`.
1197-
/// This also applies to `while` loops.
1198-
CondTemporary,
11991194
QuestionMark,
12001195
TryBlock,
12011196
YeetExpr,
@@ -1230,7 +1225,6 @@ impl DesugaringKind {
12301225
/// The description wording should combine well with "desugaring of {}".
12311226
pub fn descr(self) -> &'static str {
12321227
match self {
1233-
DesugaringKind::CondTemporary => "`if` or `while` condition",
12341228
DesugaringKind::Async => "`async` block or function",
12351229
DesugaringKind::Await => "`await` expression",
12361230
DesugaringKind::QuestionMark => "operator `?`",
@@ -1253,7 +1247,6 @@ impl DesugaringKind {
12531247
/// like `from_desugaring = "QuestionMark"`
12541248
pub fn matches(&self, value: &str) -> bool {
12551249
match self {
1256-
DesugaringKind::CondTemporary => value == "CondTemporary",
12571250
DesugaringKind::Async => value == "Async",
12581251
DesugaringKind::Await => value == "Await",
12591252
DesugaringKind::QuestionMark => value == "QuestionMark",

src/tools/clippy/clippy_lints/src/booleans.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
3+
use clippy_utils::higher::has_let_expr;
34
use clippy_utils::msrvs::{self, Msrv};
45
use clippy_utils::source::SpanRangeExt;
56
use clippy_utils::sugg::Sugg;
@@ -646,7 +647,9 @@ impl<'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'_, 'tcx> {
646647
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
647648
if !e.span.from_expansion() {
648649
match &e.kind {
649-
ExprKind::Binary(binop, _, _) if binop.node == BinOpKind::Or || binop.node == BinOpKind::And => {
650+
ExprKind::Binary(binop, _, _)
651+
if binop.node == BinOpKind::Or || binop.node == BinOpKind::And && !has_let_expr(e) =>
652+
{
650653
self.bool_expr(e);
651654
},
652655
ExprKind::Unary(UnOp::Not, inner) => {

src/tools/clippy/clippy_lints/src/if_not_else.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ declare_lint_pass!(IfNotElse => [IF_NOT_ELSE]);
5151
impl LateLintPass<'_> for IfNotElse {
5252
fn check_expr(&mut self, cx: &LateContext<'_>, e: &Expr<'_>) {
5353
if let ExprKind::If(cond, cond_inner, Some(els)) = e.kind
54-
&& let ExprKind::DropTemps(cond) = cond.kind
5554
&& let ExprKind::Block(..) = els.kind
5655
{
5756
let (msg, help) = match cond.kind {

src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ declare_lint_pass!(ImplicitSaturatingAdd => [IMPLICIT_SATURATING_ADD]);
4141
impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
4242
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
4343
if let ExprKind::If(cond, then, None) = expr.kind
44-
&& let ExprKind::DropTemps(expr1) = cond.kind
45-
&& let Some((c, op_node, l)) = get_const(cx, expr1)
44+
&& let Some((c, op_node, l)) = get_const(cx, cond)
4645
&& let BinOpKind::Ne | BinOpKind::Lt = op_node
4746
&& let ExprKind::Block(block, None) = then.kind
4847
&& let Block {
@@ -66,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
6665
&& Some(c) == get_int_max(ty)
6766
&& let ctxt = expr.span.ctxt()
6867
&& ex.span.ctxt() == ctxt
69-
&& expr1.span.ctxt() == ctxt
68+
&& cond.span.ctxt() == ctxt
7069
&& clippy_utils::SpanlessEq::new(cx).eq_expr(l, target)
7170
&& AssignOpKind::AddAssign == op1.node
7271
&& let ExprKind::Lit(lit) = value.kind

src/tools/clippy/clippy_lints/src/let_if_seq.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
6262
if let hir::StmtKind::Let(local) = stmt.kind
6363
&& let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind
6464
&& let hir::StmtKind::Expr(if_) = next.kind
65-
&& let hir::ExprKind::If(
66-
hir::Expr {
67-
kind: hir::ExprKind::DropTemps(cond),
68-
..
69-
},
70-
then,
71-
else_,
72-
) = if_.kind
73-
&& !is_local_used(cx, *cond, canonical_id)
65+
&& let hir::ExprKind::If(cond, then, else_) = if_.kind
66+
&& !is_local_used(cx, cond, canonical_id)
7467
&& let hir::ExprKind::Block(then, _) = then.kind
7568
&& let Some(value) = check_assign(cx, canonical_id, then)
7669
&& !is_local_used(cx, value, canonical_id)

0 commit comments

Comments
 (0)