Skip to main content

rustc_lint/
if_let_rescope.rs

1use std::iter::repeat_n;
2use std::ops::ControlFlow;
3
4use hir::intravisit::{self, Visitor};
5use rustc_ast::Recovered;
6use rustc_errors::{Applicability, Diag, EmissionGuarantee, Subdiagnostic, SuggestionStyle, msg};
7use rustc_hir::{self as hir, HirIdSet};
8use rustc_macros::{LintDiagnostic, Subdiagnostic};
9use rustc_middle::ty::adjustment::Adjust;
10use rustc_middle::ty::significant_drop_order::{
11    extract_component_with_significant_dtor, ty_dtor_span,
12};
13use rustc_middle::ty::{self, Ty, TyCtxt};
14use rustc_session::lint::{LintId, fcw};
15use rustc_session::{declare_lint, impl_lint_pass};
16use rustc_span::{DUMMY_SP, Span};
17use smallvec::SmallVec;
18
19use crate::{LateContext, LateLintPass};
20
21#[doc =
r" The `if_let_rescope` lint detects cases where a temporary value with"]
#[doc = r" significant drop is generated on the right hand side of `if let`"]
#[doc = r" and suggests a rewrite into `match` when possible."]
#[doc = r""]
#[doc = r" ### Example"]
#[doc = r""]
#[doc = r" ```rust,edition2021"]
#[doc = r" #![warn(if_let_rescope)]"]
#[doc = r" #![allow(unused_variables)]"]
#[doc = r""]
#[doc = r" struct Droppy;"]
#[doc = r" impl Drop for Droppy {"]
#[doc = r"     fn drop(&mut self) {"]
#[doc =
r"         // Custom destructor, including this `drop` implementation, is considered"]
#[doc = r"         // significant."]
#[doc =
r"         // Rust does not check whether this destructor emits side-effects that can"]
#[doc =
r"         // lead to observable change in program semantics, when the drop order changes."]
#[doc =
r"         // Rust biases to be on the safe side, so that you can apply discretion whether"]
#[doc =
r"         // this change indeed breaches any contract or specification that your code needs"]
#[doc = r"         // to honour."]
#[doc = r#"         println!("dropped");"#]
#[doc = r"     }"]
#[doc = r" }"]
#[doc = r" impl Droppy {"]
#[doc = r"     fn get(&self) -> Option<u8> {"]
#[doc = r"         None"]
#[doc = r"     }"]
#[doc = r" }"]
#[doc = r""]
#[doc = r" fn main() {"]
#[doc = r"     if let Some(value) = Droppy.get() {"]
#[doc = r"         // do something"]
#[doc = r"     } else {"]
#[doc = r"         // do something else"]
#[doc = r"     }"]
#[doc = r" }"]
#[doc = r" ```"]
#[doc = r""]
#[doc = r" {{produces}}"]
#[doc = r""]
#[doc = r" ### Explanation"]
#[doc = r""]
#[doc =
r" With Edition 2024, temporaries generated while evaluating `if let`s"]
#[doc = r" will be dropped before the `else` block."]
#[doc = r" This lint captures a possible change in runtime behaviour due to"]
#[doc =
r" a change in sequence of calls to significant `Drop::drop` destructors."]
#[doc = r""]
#[doc =
r" A significant [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html)"]
#[doc =
r" destructor here refers to an explicit, arbitrary implementation of the `Drop` trait on the type"]
#[doc =
r" with exceptions including `Vec`, `Box`, `Rc`, `BTreeMap` and `HashMap`"]
#[doc =
r" that are marked by the compiler otherwise so long that the generic types have"]
#[doc = r" no significant destructor recursively."]
#[doc =
r" In other words, a type has a significant drop destructor when it has a `Drop` implementation"]
#[doc = r" or its destructor invokes a significant destructor on a type."]
#[doc =
r" Since we cannot completely reason about the change by just inspecting the existence of"]
#[doc =
r" a significant destructor, this lint remains only a suggestion and is set to `allow` by default."]
#[doc = r""]
#[doc =
r" Whenever possible, a rewrite into an equivalent `match` expression that"]
#[doc =
r" observe the same order of calls to such destructors is proposed by this lint."]
#[doc =
r" Authors may take their own discretion whether the rewrite suggestion shall be"]
#[doc =
r" accepted, or rejected to continue the use of the `if let` expression."]
pub static IF_LET_RESCOPE: &::rustc_lint_defs::Lint =
    &::rustc_lint_defs::Lint {
            name: "IF_LET_RESCOPE",
            default_level: ::rustc_lint_defs::Allow,
            desc: "`if let` assigns a shorter lifetime to temporary values being pattern-matched against in Edition 2024 and \
    rewriting in `match` is an option to preserve the semantics up to Edition 2021",
            is_externally_loaded: false,
            future_incompatible: Some(::rustc_lint_defs::FutureIncompatibleInfo {
                    reason: ::rustc_lint_defs::FutureIncompatibilityReason::EditionSemanticsChange(::rustc_lint_defs::EditionFcw {
                            edition: rustc_span::edition::Edition::Edition2024,
                            page_slug: "temporary-if-let-scope",
                        }),
                    ..::rustc_lint_defs::FutureIncompatibleInfo::default_fields_for_macro()
                }),
            ..::rustc_lint_defs::Lint::default_fields_for_macro()
        };declare_lint! {
22    /// The `if_let_rescope` lint detects cases where a temporary value with
23    /// significant drop is generated on the right hand side of `if let`
24    /// and suggests a rewrite into `match` when possible.
25    ///
26    /// ### Example
27    ///
28    /// ```rust,edition2021
29    /// #![warn(if_let_rescope)]
30    /// #![allow(unused_variables)]
31    ///
32    /// struct Droppy;
33    /// impl Drop for Droppy {
34    ///     fn drop(&mut self) {
35    ///         // Custom destructor, including this `drop` implementation, is considered
36    ///         // significant.
37    ///         // Rust does not check whether this destructor emits side-effects that can
38    ///         // lead to observable change in program semantics, when the drop order changes.
39    ///         // Rust biases to be on the safe side, so that you can apply discretion whether
40    ///         // this change indeed breaches any contract or specification that your code needs
41    ///         // to honour.
42    ///         println!("dropped");
43    ///     }
44    /// }
45    /// impl Droppy {
46    ///     fn get(&self) -> Option<u8> {
47    ///         None
48    ///     }
49    /// }
50    ///
51    /// fn main() {
52    ///     if let Some(value) = Droppy.get() {
53    ///         // do something
54    ///     } else {
55    ///         // do something else
56    ///     }
57    /// }
58    /// ```
59    ///
60    /// {{produces}}
61    ///
62    /// ### Explanation
63    ///
64    /// With Edition 2024, temporaries generated while evaluating `if let`s
65    /// will be dropped before the `else` block.
66    /// This lint captures a possible change in runtime behaviour due to
67    /// a change in sequence of calls to significant `Drop::drop` destructors.
68    ///
69    /// A significant [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html)
70    /// destructor here refers to an explicit, arbitrary implementation of the `Drop` trait on the type
71    /// with exceptions including `Vec`, `Box`, `Rc`, `BTreeMap` and `HashMap`
72    /// that are marked by the compiler otherwise so long that the generic types have
73    /// no significant destructor recursively.
74    /// In other words, a type has a significant drop destructor when it has a `Drop` implementation
75    /// or its destructor invokes a significant destructor on a type.
76    /// Since we cannot completely reason about the change by just inspecting the existence of
77    /// a significant destructor, this lint remains only a suggestion and is set to `allow` by default.
78    ///
79    /// Whenever possible, a rewrite into an equivalent `match` expression that
80    /// observe the same order of calls to such destructors is proposed by this lint.
81    /// Authors may take their own discretion whether the rewrite suggestion shall be
82    /// accepted, or rejected to continue the use of the `if let` expression.
83    pub IF_LET_RESCOPE,
84    Allow,
85    "`if let` assigns a shorter lifetime to temporary values being pattern-matched against in Edition 2024 and \
86    rewriting in `match` is an option to preserve the semantics up to Edition 2021",
87    @future_incompatible = FutureIncompatibleInfo {
88        reason: fcw!(EditionSemanticsChange 2024 "temporary-if-let-scope"),
89    };
90}
91
92/// Lint for potential change in program semantics of `if let`s
93#[derive(#[automatically_derived]
impl ::core::default::Default for IfLetRescope {
    #[inline]
    fn default() -> IfLetRescope {
        IfLetRescope { skip: ::core::default::Default::default() }
    }
}Default)]
94pub(crate) struct IfLetRescope {
95    skip: HirIdSet,
96}
97
98fn expr_parent_is_else(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
99    let Some((_, hir::Node::Expr(expr))) = tcx.hir_parent_iter(hir_id).next() else {
100        return false;
101    };
102    let hir::ExprKind::If(_cond, _conseq, Some(alt)) = expr.kind else { return false };
103    alt.hir_id == hir_id
104}
105
106fn expr_parent_is_stmt(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
107    let mut parents = tcx.hir_parent_iter(hir_id);
108    let stmt = match parents.next() {
109        Some((_, hir::Node::Stmt(stmt))) => stmt,
110        Some((_, hir::Node::Block(_) | hir::Node::Arm(_))) => return true,
111        _ => return false,
112    };
113    let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = stmt.kind else { return false };
114    expr.hir_id == hir_id
115}
116
117fn match_head_needs_bracket(tcx: TyCtxt<'_>, expr: &hir::Expr<'_>) -> bool {
118    expr_parent_is_else(tcx, expr.hir_id) && #[allow(non_exhaustive_omitted_patterns)] match expr.kind {
    hir::ExprKind::If(..) => true,
    _ => false,
}matches!(expr.kind, hir::ExprKind::If(..))
119}
120
121impl IfLetRescope {
122    fn probe_if_cascade<'tcx>(&mut self, cx: &LateContext<'tcx>, mut expr: &'tcx hir::Expr<'tcx>) {
123        if self.skip.contains(&expr.hir_id) {
124            return;
125        }
126        let tcx = cx.tcx;
127        let source_map = tcx.sess.source_map();
128        let expr_end = match expr.kind {
129            hir::ExprKind::If(_cond, conseq, None) => conseq.span.shrink_to_hi(),
130            hir::ExprKind::If(_cond, _conseq, Some(alt)) => alt.span.shrink_to_hi(),
131            _ => return,
132        };
133        let mut seen_dyn = false;
134        let mut add_bracket_to_match_head = match_head_needs_bracket(tcx, expr);
135        let mut significant_droppers = ::alloc::vec::Vec::new()vec![];
136        let mut lifetime_ends = ::alloc::vec::Vec::new()vec![];
137        let mut closing_brackets = 0;
138        let mut alt_heads = ::alloc::vec::Vec::new()vec![];
139        let mut match_heads = ::alloc::vec::Vec::new()vec![];
140        let mut consequent_heads = ::alloc::vec::Vec::new()vec![];
141        let mut destructors = ::alloc::vec::Vec::new()vec![];
142        let mut first_if_to_lint = None;
143        let mut first_if_to_rewrite = false;
144        let mut empty_alt = false;
145        while let hir::ExprKind::If(cond, conseq, alt) = expr.kind {
146            self.skip.insert(expr.hir_id);
147            // We are interested in `let` fragment of the condition.
148            // Otherwise, we probe into the `else` fragment.
149            if let hir::ExprKind::Let(&hir::LetExpr {
150                span,
151                pat,
152                init,
153                ty: ty_ascription,
154                recovered: Recovered::No,
155            }) = cond.kind
156            {
157                // Peel off round braces
158                let if_let_pat = source_map
159                    .span_take_while(expr.span, |&ch| ch == '(' || ch.is_whitespace())
160                    .between(init.span);
161                // The consequent fragment is always a block.
162                let before_conseq = conseq.span.shrink_to_lo();
163                let lifetime_end = source_map.end_point(conseq.span);
164
165                if let ControlFlow::Break((drop_span, drop_tys)) =
166                    (FindSignificantDropper { cx }).check_if_let_scrutinee(init)
167                {
168                    destructors.extend(drop_tys.into_iter().filter_map(|ty| {
169                        if let Some(span) = ty_dtor_span(tcx, ty) {
170                            Some(DestructorLabel { span, dtor_kind: "concrete" })
171                        } else if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
    ty::Dynamic(..) => true,
    _ => false,
}matches!(ty.kind(), ty::Dynamic(..)) {
172                            if seen_dyn {
173                                None
174                            } else {
175                                seen_dyn = true;
176                                Some(DestructorLabel { span: DUMMY_SP, dtor_kind: "dyn" })
177                            }
178                        } else {
179                            None
180                        }
181                    }));
182                    first_if_to_lint = first_if_to_lint.or_else(|| Some((span, expr.hir_id)));
183                    significant_droppers.push(drop_span);
184                    lifetime_ends.push(lifetime_end);
185                    if ty_ascription.is_some()
186                        || !expr.span.can_be_used_for_suggestions()
187                        || !pat.span.can_be_used_for_suggestions()
188                        || !if_let_pat.can_be_used_for_suggestions()
189                        || !before_conseq.can_be_used_for_suggestions()
190                    {
191                        // Our `match` rewrites does not support type ascription,
192                        // so we just bail.
193                        // Alternatively when the span comes from proc macro expansion,
194                        // we will also bail.
195                        // FIXME(#101728): change this when type ascription syntax is stabilized again
196                    } else if let Ok(pat) = source_map.span_to_snippet(pat.span) {
197                        let emit_suggestion = |alt_span| {
198                            first_if_to_rewrite = true;
199                            if add_bracket_to_match_head {
200                                closing_brackets += 2;
201                                match_heads.push(SingleArmMatchBegin::WithOpenBracket(if_let_pat));
202                            } else {
203                                // Sometimes, wrapping `match` into a block is undesirable,
204                                // because the scrutinee temporary lifetime is shortened and
205                                // the proposed fix will not work.
206                                closing_brackets += 1;
207                                match_heads
208                                    .push(SingleArmMatchBegin::WithoutOpenBracket(if_let_pat));
209                            }
210                            consequent_heads.push(ConsequentRewrite { span: before_conseq, pat });
211                            if let Some(alt_span) = alt_span {
212                                alt_heads.push(AltHead(alt_span));
213                            }
214                        };
215                        if let Some(alt) = alt {
216                            let alt_head = conseq.span.between(alt.span);
217                            if alt_head.can_be_used_for_suggestions() {
218                                // We lint only when the `else` span is user code, too.
219                                emit_suggestion(Some(alt_head));
220                            }
221                        } else {
222                            // This is the end of the `if .. else ..` cascade.
223                            // We can stop here.
224                            emit_suggestion(None);
225                            empty_alt = true;
226                            break;
227                        }
228                    }
229                }
230            }
231            // At this point, any `if let` fragment in the cascade is definitely preceded by `else`,
232            // so a opening bracket is mandatory before each `match`.
233            add_bracket_to_match_head = true;
234            if let Some(alt) = alt {
235                expr = alt;
236            } else {
237                break;
238            }
239        }
240        if let Some((span, hir_id)) = first_if_to_lint {
241            tcx.emit_node_span_lint(
242                IF_LET_RESCOPE,
243                hir_id,
244                span,
245                IfLetRescopeLint {
246                    destructors,
247                    significant_droppers,
248                    lifetime_ends,
249                    rewrite: first_if_to_rewrite.then_some(IfLetRescopeRewrite {
250                        match_heads,
251                        consequent_heads,
252                        closing_brackets: ClosingBrackets {
253                            span: expr_end,
254                            count: closing_brackets,
255                            empty_alt,
256                        },
257                        alt_heads,
258                    }),
259                },
260            );
261        }
262    }
263}
264
265impl ::rustc_lint_defs::LintPass for IfLetRescope {
    fn name(&self) -> &'static str { "IfLetRescope" }
    fn get_lints(&self) -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [IF_LET_RESCOPE]))
    }
}
impl IfLetRescope {
    #[allow(unused)]
    pub fn lint_vec() -> ::rustc_lint_defs::LintVec {
        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                [IF_LET_RESCOPE]))
    }
}impl_lint_pass!(
266    IfLetRescope => [IF_LET_RESCOPE]
267);
268
269impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
270    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
271        if expr.span.edition().at_least_rust_2024()
272            || cx.tcx.lints_that_dont_need_to_run(()).contains(&LintId::of(IF_LET_RESCOPE))
273        {
274            return;
275        }
276
277        if let hir::ExprKind::Loop(block, _label, hir::LoopSource::While, _span) = expr.kind
278            && let Some(value) = block.expr
279            && let hir::ExprKind::If(cond, _conseq, _alt) = value.kind
280            && let hir::ExprKind::Let(..) = cond.kind
281        {
282            // Recall that `while let` is lowered into this:
283            // ```
284            // loop {
285            //     if let .. { body } else { break; }
286            // }
287            // ```
288            // There is no observable change in drop order on the overall `if let` expression
289            // given that the `{ break; }` block is trivial so the edition change
290            // means nothing substantial to this `while` statement.
291            self.skip.insert(value.hir_id);
292            return;
293        }
294        if expr_parent_is_stmt(cx.tcx, expr.hir_id)
295            && #[allow(non_exhaustive_omitted_patterns)] match expr.kind {
    hir::ExprKind::If(_cond, _conseq, None) => true,
    _ => false,
}matches!(expr.kind, hir::ExprKind::If(_cond, _conseq, None))
296        {
297            // `if let` statement without an `else` branch has no observable change
298            // so we can skip linting it
299            return;
300        }
301        self.probe_if_cascade(cx, expr);
302    }
303}
304
305#[derive(const _: () =
    {
        impl<'__a> rustc_errors::LintDiagnostic<'__a, ()> for IfLetRescopeLint
            {
            #[track_caller]
            fn decorate_lint<'__b>(self,
                diag: &'__b mut rustc_errors::Diag<'__a, ()>) {
                match self {
                    IfLetRescopeLint {
                        destructors: __binding_0,
                        significant_droppers: __binding_1,
                        lifetime_ends: __binding_2,
                        rewrite: __binding_3 } => {
                        diag.primary_message(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("`if let` assigns a shorter lifetime since Edition 2024")));
                        ;
                        for __binding_0 in __binding_0 {
                            diag.subdiagnostic(__binding_0);
                        }
                        for __binding_1 in __binding_1 {
                            diag.span_label(__binding_1,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("this value has a significant drop implementation which may observe a major change in drop order and requires your discretion")));
                        }
                        for __binding_2 in __binding_2 {
                            diag.span_help(__binding_2,
                                rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("the value is now dropped here in Edition 2024")));
                        }
                        if let Some(__binding_3) = __binding_3 {
                            diag.subdiagnostic(__binding_3);
                        }
                        diag
                    }
                };
            }
        }
    };LintDiagnostic)]
306#[diag("`if let` assigns a shorter lifetime since Edition 2024")]
307struct IfLetRescopeLint {
308    #[subdiagnostic]
309    destructors: Vec<DestructorLabel>,
310    #[label(
311        "this value has a significant drop implementation which may observe a major change in drop order and requires your discretion"
312    )]
313    significant_droppers: Vec<Span>,
314    #[help("the value is now dropped here in Edition 2024")]
315    lifetime_ends: Vec<Span>,
316    #[subdiagnostic]
317    rewrite: Option<IfLetRescopeRewrite>,
318}
319
320struct IfLetRescopeRewrite {
321    match_heads: Vec<SingleArmMatchBegin>,
322    consequent_heads: Vec<ConsequentRewrite>,
323    closing_brackets: ClosingBrackets,
324    alt_heads: Vec<AltHead>,
325}
326
327impl Subdiagnostic for IfLetRescopeRewrite {
328    fn add_to_diag<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
329        let mut suggestions = ::alloc::vec::Vec::new()vec![];
330        for match_head in self.match_heads {
331            match match_head {
332                SingleArmMatchBegin::WithOpenBracket(span) => {
333                    suggestions.push((span, "{ match ".into()))
334                }
335                SingleArmMatchBegin::WithoutOpenBracket(span) => {
336                    suggestions.push((span, "match ".into()))
337                }
338            }
339        }
340        for ConsequentRewrite { span, pat } in self.consequent_heads {
341            suggestions.push((span, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{{ {0} => ", pat))
    })format!("{{ {pat} => ")));
342        }
343        for AltHead(span) in self.alt_heads {
344            suggestions.push((span, " _ => ".into()));
345        }
346        let closing_brackets = self.closing_brackets;
347        suggestions.push((
348            closing_brackets.span,
349            closing_brackets
350                .empty_alt
351                .then_some(" _ => {}".chars())
352                .into_iter()
353                .flatten()
354                .chain(repeat_n('}', closing_brackets.count))
355                .collect(),
356        ));
357        let msg = diag.eagerly_translate(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("a `match` with a single arm can preserve the drop order up to Edition 2021"))msg!(
358            "a `match` with a single arm can preserve the drop order up to Edition 2021"
359        ));
360        diag.multipart_suggestion_with_style(
361            msg,
362            suggestions,
363            Applicability::MachineApplicable,
364            SuggestionStyle::ShowCode,
365        );
366    }
367}
368
369#[derive(const _: () =
    {
        impl rustc_errors::Subdiagnostic for DestructorLabel {
            fn add_to_diag<__G>(self, diag: &mut rustc_errors::Diag<'_, __G>)
                where __G: rustc_errors::EmissionGuarantee {
                match self {
                    DestructorLabel { span: __binding_0, dtor_kind: __binding_1
                        } => {
                        diag.store_args();
                        diag.arg("dtor_kind", __binding_1);
                        let __message =
                            diag.eagerly_translate(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$dtor_kind ->\n        [dyn] value may invoke a custom destructor because it contains a trait object\n        *[concrete] value invokes this custom destructor\n    }")));
                        diag.span_note(__binding_0, __message);
                        diag.restore_args();
                    }
                }
            }
        }
    };Subdiagnostic)]
370#[note(
371    "{$dtor_kind ->
372        [dyn] value may invoke a custom destructor because it contains a trait object
373        *[concrete] value invokes this custom destructor
374    }"
375)]
376struct DestructorLabel {
377    #[primary_span]
378    span: Span,
379    dtor_kind: &'static str,
380}
381
382struct AltHead(Span);
383
384struct ConsequentRewrite {
385    span: Span,
386    pat: String,
387}
388
389struct ClosingBrackets {
390    span: Span,
391    count: usize,
392    empty_alt: bool,
393}
394enum SingleArmMatchBegin {
395    WithOpenBracket(Span),
396    WithoutOpenBracket(Span),
397}
398
399struct FindSignificantDropper<'a, 'tcx> {
400    cx: &'a LateContext<'tcx>,
401}
402
403impl<'tcx> FindSignificantDropper<'_, 'tcx> {
404    /// Check the scrutinee of an `if let` to see if it promotes any temporary values
405    /// that would change drop order in edition 2024. Specifically, it checks the value
406    /// of the scrutinee itself, and also recurses into the expression to find any ref
407    /// exprs (or autoref) which would promote temporaries that would be scoped to the
408    /// end of this `if`.
409    fn check_if_let_scrutinee(
410        &mut self,
411        init: &'tcx hir::Expr<'tcx>,
412    ) -> ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)> {
413        self.check_promoted_temp_with_drop(init)?;
414        self.visit_expr(init)
415    }
416
417    /// Check that an expression is not a promoted temporary with a significant
418    /// drop impl.
419    ///
420    /// An expression is a promoted temporary if it has an addr taken (i.e. `&expr` or autoref)
421    /// or is the scrutinee of the `if let`, *and* the expression is not a place
422    /// expr, and it has a significant drop.
423    fn check_promoted_temp_with_drop(
424        &self,
425        expr: &'tcx hir::Expr<'tcx>,
426    ) -> ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)> {
427        if expr.is_place_expr(|base| {
428            self.cx
429                .typeck_results()
430                .adjustments()
431                .get(base.hir_id)
432                .is_some_and(|x| x.iter().any(|adj| #[allow(non_exhaustive_omitted_patterns)] match adj.kind {
    Adjust::Deref(_) => true,
    _ => false,
}matches!(adj.kind, Adjust::Deref(_))))
433        }) {
434            return ControlFlow::Continue(());
435        }
436
437        let drop_tys = extract_component_with_significant_dtor(
438            self.cx.tcx,
439            self.cx.typing_env(),
440            self.cx.typeck_results().expr_ty(expr),
441        );
442        if drop_tys.is_empty() {
443            return ControlFlow::Continue(());
444        }
445
446        ControlFlow::Break((expr.span, drop_tys))
447    }
448}
449
450impl<'tcx> Visitor<'tcx> for FindSignificantDropper<'_, 'tcx> {
451    type Result = ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)>;
452
453    fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) -> Self::Result {
454        // Blocks introduce temporary terminating scope for all of its
455        // statements, so just visit the tail expr, skipping over any
456        // statements. This prevents false positives like `{ let x = &Drop; }`.
457        if let Some(expr) = b.expr { self.visit_expr(expr) } else { ControlFlow::Continue(()) }
458    }
459
460    fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Self::Result {
461        // Check for promoted temporaries from autoref, e.g.
462        // `if let None = TypeWithDrop.as_ref() {} else {}`
463        // where `fn as_ref(&self) -> Option<...>`.
464        for adj in self.cx.typeck_results().expr_adjustments(expr) {
465            match adj.kind {
466                // Skip when we hit the first deref expr.
467                Adjust::Deref(_) => break,
468                Adjust::Borrow(_) => {
469                    self.check_promoted_temp_with_drop(expr)?;
470                }
471                _ => {}
472            }
473        }
474
475        match expr.kind {
476            // Account for cases like `if let None = Some(&Drop) {} else {}`.
477            hir::ExprKind::AddrOf(_, _, expr) => {
478                self.check_promoted_temp_with_drop(expr)?;
479                intravisit::walk_expr(self, expr)
480            }
481            // `(Drop, ()).1` introduces a temporary and then moves out of
482            // part of it, therefore we should check it for temporaries.
483            // FIXME: This may have false positives if we move the part
484            // that actually has drop, but oh well.
485            hir::ExprKind::Index(expr, _, _) | hir::ExprKind::Field(expr, _) => {
486                self.check_promoted_temp_with_drop(expr)?;
487                intravisit::walk_expr(self, expr)
488            }
489            // If always introduces a temporary terminating scope for its cond and arms,
490            // so don't visit them.
491            hir::ExprKind::If(..) => ControlFlow::Continue(()),
492            // Match introduces temporary terminating scopes for arms, so don't visit
493            // them, and only visit the scrutinee to account for cases like:
494            // `if let None = match &Drop { _ => Some(1) } {} else {}`.
495            hir::ExprKind::Match(scrut, _, _) => self.visit_expr(scrut),
496            // Self explanatory.
497            hir::ExprKind::DropTemps(_) => ControlFlow::Continue(()),
498            // Otherwise, walk into the expr's parts.
499            _ => intravisit::walk_expr(self, expr),
500        }
501    }
502}