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