Skip to content

Commit 2a9cefe

Browse files
Improve univariant match case
1 parent 1cda77b commit 2a9cefe

File tree

3 files changed

+110
-13
lines changed

3 files changed

+110
-13
lines changed

compiler/rustc_hir/src/hir.rs

+8
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,14 @@ impl<'hir> Pat<'hir> {
14971497
});
14981498
is_never_pattern
14991499
}
1500+
1501+
pub fn peel_borrows(&self) -> &Self {
1502+
let mut pat = self;
1503+
while let PatKind::Ref(inner, _) = &pat.kind {
1504+
pat = inner;
1505+
}
1506+
pat
1507+
}
15001508
}
15011509

15021510
/// A single field in a struct pattern.

compiler/rustc_lint/src/if_let_rescope.rs

+92-13
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ use rustc_ast::Recovered;
66
use rustc_errors::{
77
Applicability, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
88
};
9+
use rustc_hir::def::Res;
10+
use rustc_hir::def_id::DefId;
911
use rustc_hir::{self as hir, HirIdSet};
1012
use rustc_macros::LintDiagnostic;
11-
use rustc_middle::ty::TyCtxt;
1213
use rustc_middle::ty::adjustment::Adjust;
14+
use rustc_middle::ty::{self, TyCtxt};
1315
use rustc_session::lint::{FutureIncompatibilityReason, LintId};
1416
use rustc_session::{declare_lint, impl_lint_pass};
1517
use rustc_span::Span;
@@ -161,7 +163,7 @@ impl IfLetRescope {
161163
let lifetime_end = source_map.end_point(conseq.span);
162164

163165
if let ControlFlow::Break(significant_dropper) =
164-
(FindSignificantDropper { cx }).check_if_let_scrutinee(init)
166+
(FindSignificantDropper { cx }).check_if_let_scrutinee(init, pat)
165167
{
166168
first_if_to_lint = first_if_to_lint.or_else(|| Some((span, expr.hir_id)));
167169
significant_droppers.push(significant_dropper);
@@ -374,7 +376,52 @@ impl<'tcx> FindSignificantDropper<'_, 'tcx> {
374376
/// of the scrutinee itself, and also recurses into the expression to find any ref
375377
/// exprs (or autoref) which would promote temporaries that would be scoped to the
376378
/// end of this `if`.
377-
fn check_if_let_scrutinee(&mut self, init: &'tcx hir::Expr<'tcx>) -> ControlFlow<Span> {
379+
fn check_if_let_scrutinee(
380+
&mut self,
381+
init: &'tcx hir::Expr<'tcx>,
382+
scrut_pat: &'tcx hir::Pat<'tcx>,
383+
) -> ControlFlow<Span> {
384+
// Try first to match a variant constructor. If that variant constructor's
385+
// subpatterns are infallible (which for now is just `x` and `_`), then
386+
// check the fields of the ADT for drop *excluding* that variant, and walk
387+
// that pattern excluding any of the peeled refs up to that point.
388+
match &scrut_pat.peel_borrows().kind {
389+
rustc_hir::PatKind::Struct(qpath, pat_fields, _) => {
390+
if let Res::Def(_, def_id) =
391+
self.cx.typeck_results().qpath_res(qpath, scrut_pat.hir_id)
392+
&& pat_fields.iter().all(|pat| is_infallible(pat.pat))
393+
{
394+
// Peel the borrows of the init expr because we want to drill down to
395+
// the (possibly) temporary value. For example, given something like:
396+
// `let Enum::Variant(_) = &&&temp()`, we want to drill down to the
397+
// type of `temp()` so we can check it excluding the variant.
398+
self.check_promoted_temp_with_drop_excluding_variant(
399+
init.peel_borrows(),
400+
def_id,
401+
)?;
402+
// Once we've already checked the adt, just visit the expression after
403+
// all the refs. Otherwise we'll re-encounter the expression and count
404+
// it as a bad temporary.
405+
return self.visit_expr(init.peel_borrows());
406+
}
407+
}
408+
rustc_hir::PatKind::TupleStruct(qpath, pats, _) => {
409+
if let Res::Def(_, def_id) =
410+
self.cx.typeck_results().qpath_res(qpath, scrut_pat.hir_id)
411+
&& let def_id = self.cx.tcx.parent(def_id)
412+
&& pats.iter().all(|pat| is_infallible(pat))
413+
{
414+
// See above for the justification here.
415+
self.check_promoted_temp_with_drop_excluding_variant(
416+
init.peel_borrows(),
417+
def_id,
418+
)?;
419+
return self.visit_expr(init.peel_borrows());
420+
}
421+
}
422+
_ => {}
423+
}
424+
378425
self.check_promoted_temp_with_drop(init)?;
379426
self.visit_expr(init)
380427
}
@@ -386,23 +433,55 @@ impl<'tcx> FindSignificantDropper<'_, 'tcx> {
386433
/// or is the scrutinee of the `if let`, *and* the expression is not a place
387434
/// expr, and it has a significant drop.
388435
fn check_promoted_temp_with_drop(&self, expr: &'tcx hir::Expr<'tcx>) -> ControlFlow<Span> {
389-
if !expr.is_place_expr(|base| {
390-
self.cx
436+
if !self.expr_is_place(expr)
437+
&& self
438+
.cx
391439
.typeck_results()
392-
.adjustments()
393-
.get(base.hir_id)
394-
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
395-
}) && self
396-
.cx
397-
.typeck_results()
398-
.expr_ty(expr)
399-
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
440+
.expr_ty(expr)
441+
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
442+
{
443+
ControlFlow::Break(expr.span)
444+
} else {
445+
ControlFlow::Continue(())
446+
}
447+
}
448+
449+
/// Same as above, but ignoring the excluded variant def-id.
450+
fn check_promoted_temp_with_drop_excluding_variant(
451+
&self,
452+
expr: &'tcx hir::Expr<'tcx>,
453+
excluded_variant: DefId,
454+
) -> ControlFlow<Span> {
455+
if !self.expr_is_place(expr)
456+
&& let ty::Adt(def, args) = self.cx.typeck_results().expr_ty(expr).kind()
457+
&& def.variants().iter().any(|variant| {
458+
variant.def_id != excluded_variant
459+
&& variant.fields.iter().any(|f| {
460+
f.ty(self.cx.tcx, args)
461+
.has_significant_drop(self.cx.tcx, self.cx.typing_env())
462+
})
463+
})
400464
{
401465
ControlFlow::Break(expr.span)
402466
} else {
403467
ControlFlow::Continue(())
404468
}
405469
}
470+
471+
fn expr_is_place(&self, expr: &rustc_hir::Expr<'tcx>) -> bool {
472+
expr.is_place_expr(|base| {
473+
self.cx
474+
.typeck_results()
475+
.adjustments()
476+
.get(base.hir_id)
477+
.is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
478+
})
479+
}
480+
}
481+
482+
// FIXME: This is pretty rudimentary.
483+
fn is_infallible<'tcx>(pat: &'tcx hir::Pat<'tcx>) -> bool {
484+
matches!(pat.kind, hir::PatKind::Wild | hir::PatKind::Binding(_, _, _, None))
406485
}
407486

408487
impl<'tcx> Visitor<'tcx> for FindSignificantDropper<'_, 'tcx> {

tests/ui/drop/lint-if-let-rescope-false-positives.rs

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ impl Drop {
1818

1919
fn consume(_: impl Sized) -> Option<i32> { Some(1) }
2020

21+
enum MyOption<T> {
22+
Some { f: T },
23+
None,
24+
}
25+
2126
fn main() {
2227
let drop = Drop;
2328

@@ -26,4 +31,9 @@ fn main() {
2631

2732
// Make sure we don't lint if we consume the droppy value.
2833
if let None = consume(Drop) {} else {}
34+
35+
// Make sure we don't lint if the droppy value is unconditionally matched.
36+
if let Some(_) = Some(Drop) {} else {}
37+
if let Some(_) = &Some(Drop) {} else {}
38+
if let MyOption::Some { f: _ } = (MyOption::Some{ f: Drop }) {} else {}
2939
}

0 commit comments

Comments
 (0)