1 use crate::ty::needs_ordered_drop;
2 use crate::{get_enclosing_block, path_to_local_id};
3 use core::ops::ControlFlow;
5 use rustc_hir::def::{CtorKind, DefKind, Res};
6 use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor};
8 Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp,
9 UnsafeSource, Unsafety,
11 use rustc_lint::LateContext;
12 use rustc_middle::hir::map::Map;
13 use rustc_middle::hir::nested_filter;
14 use rustc_middle::ty::adjustment::Adjust;
15 use rustc_middle::ty::{self, Ty, TypeckResults};
17 /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
18 /// bodies (i.e. closures) are visited.
19 /// If the callback returns `true`, the expr just provided to the callback is walked.
21 pub fn expr_visitor<'tcx>(cx: &LateContext<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
26 impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<'tcx, F> {
27 type NestedFilter = nested_filter::OnlyBodies;
28 fn nested_visit_map(&mut self) -> Self::Map {
32 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
34 walk_expr(self, expr);
38 V { hir: cx.tcx.hir(), f }
41 /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested
42 /// bodies (i.e. closures) are not visited.
43 /// If the callback returns `true`, the expr just provided to the callback is walked.
45 pub fn expr_visitor_no_bodies<'tcx>(f: impl FnMut(&'tcx Expr<'tcx>) -> bool) -> impl Visitor<'tcx> {
47 impl<'tcx, F: FnMut(&'tcx Expr<'tcx>) -> bool> Visitor<'tcx> for V<F> {
48 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
57 /// returns `true` if expr contains match expr desugared from try
58 fn contains_try(expr: &hir::Expr<'_>) -> bool {
59 let mut found = false;
60 expr_visitor_no_bodies(|e| {
62 found = matches!(e.kind, hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar));
70 pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
72 F: FnMut(&'hir hir::Expr<'hir>) -> bool,
80 struct WithStmtGuarg<'a, F> {
81 val: &'a mut RetFinder<F>,
85 impl<F> RetFinder<F> {
86 fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
87 let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
95 impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
96 type Target = RetFinder<F>;
98 fn deref(&self) -> &Self::Target {
103 impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
104 fn deref_mut(&mut self) -> &mut Self::Target {
109 impl<F> Drop for WithStmtGuarg<'_, F> {
111 self.val.in_stmt = self.prev_in_stmt;
115 impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
116 fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
117 intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt);
120 fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
126 hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
127 _ => intravisit::walk_expr(self, expr),
131 hir::ExprKind::If(cond, then, else_opt) => {
132 self.inside_stmt(true).visit_expr(cond);
133 self.visit_expr(then);
134 if let Some(el) = else_opt {
138 hir::ExprKind::Match(cond, arms, _) => {
139 self.inside_stmt(true).visit_expr(cond);
141 self.visit_expr(arm.body);
144 hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
145 hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
146 _ => self.failed |= !(self.cb)(expr),
152 !contains_try(expr) && {
153 let mut ret_finder = RetFinder {
158 ret_finder.visit_expr(expr);
163 /// A type which can be visited.
164 pub trait Visitable<'tcx> {
165 /// Calls the corresponding `visit_*` function on the visitor.
166 fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
168 macro_rules! visitable_ref {
169 ($t:ident, $f:ident) => {
170 impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
171 fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
177 visitable_ref!(Arm, visit_arm);
178 visitable_ref!(Block, visit_block);
179 visitable_ref!(Body, visit_body);
180 visitable_ref!(Expr, visit_expr);
181 visitable_ref!(Stmt, visit_stmt);
183 // impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
185 // I::Item: Visitable<'tcx>,
187 // fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
194 /// Checks if the given resolved path is used in the given body.
195 pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
196 let mut found = false;
197 expr_visitor(cx, |e| {
202 if let ExprKind::Path(p) = &e.kind {
203 if cx.qpath_res(p, e.hir_id) == res {
209 .visit_expr(&cx.tcx.hir().body(body).value);
213 /// Checks if the given local is used.
214 pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
215 let mut is_used = false;
216 let mut visitor = expr_visitor(cx, |expr| {
218 is_used = path_to_local_id(expr, id);
222 visitable.visit(&mut visitor);
227 /// Checks if the given expression is a constant.
228 pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
230 cx: &'a LateContext<'tcx>,
233 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
234 type NestedFilter = nested_filter::OnlyBodies;
235 fn nested_visit_map(&mut self) -> Self::Map {
239 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
244 ExprKind::ConstBlock(_) => return,
247 kind: ExprKind::Path(ref p),
254 .qpath_res(p, hir_id)
256 .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
257 ExprKind::MethodCall(..)
261 .type_dependent_def_id(e.hir_id)
262 .map_or(false, |id| self.cx.tcx.is_const_fn_raw(id)) => {},
263 ExprKind::Binary(_, lhs, rhs)
264 if self.cx.typeck_results().expr_ty(lhs).peel_refs().is_primitive_ty()
265 && self.cx.typeck_results().expr_ty(rhs).peel_refs().is_primitive_ty() => {},
266 ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_ref() => (),
267 ExprKind::Unary(_, e) if self.cx.typeck_results().expr_ty(e).peel_refs().is_primitive_ty() => (),
268 ExprKind::Index(base, _)
270 self.cx.typeck_results().expr_ty(base).peel_refs().kind(),
271 ty::Slice(_) | ty::Array(..)
273 ExprKind::Path(ref p)
275 self.cx.qpath_res(p, e.hir_id),
278 | DefKind::AssocConst
280 | DefKind::ConstParam
290 | ExprKind::Block(..)
292 | ExprKind::DropTemps(_)
293 | ExprKind::Field(..)
297 | ExprKind::Match(..)
298 | ExprKind::Repeat(..)
299 | ExprKind::Struct(..)
301 | ExprKind::Type(..) => (),
304 self.is_const = false;
312 let mut v = V { cx, is_const: true };
317 /// Checks if the given expression performs an unsafe operation outside of an unsafe block.
318 pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
320 cx: &'a LateContext<'tcx>,
323 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
324 type NestedFilter = nested_filter::OnlyBodies;
325 fn nested_visit_map(&mut self) -> Self::Map {
328 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
333 ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => {
334 self.is_unsafe = true;
336 ExprKind::MethodCall(..)
340 .type_dependent_def_id(e.hir_id)
341 .map_or(false, |id| self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe) =>
343 self.is_unsafe = true;
345 ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() {
346 ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
347 ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true,
348 _ => walk_expr(self, e),
350 ExprKind::Path(ref p)
353 .qpath_res(p, e.hir_id)
355 .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) =>
357 self.is_unsafe = true;
359 _ => walk_expr(self, e),
362 fn visit_block(&mut self, b: &'tcx Block<'_>) {
363 if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) {
367 fn visit_nested_item(&mut self, id: ItemId) {
368 if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind {
369 self.is_unsafe = i.unsafety == Unsafety::Unsafe;
373 let mut v = V { cx, is_unsafe: false };
378 /// Checks if the given expression contains an unsafe block
379 pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
380 struct V<'cx, 'tcx> {
381 cx: &'cx LateContext<'tcx>,
384 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
385 type NestedFilter = nested_filter::OnlyBodies;
386 fn nested_visit_map(&mut self) -> Self::Map {
390 fn visit_block(&mut self, b: &'tcx Block<'_>) {
391 if self.found_unsafe {
394 if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) {
395 self.found_unsafe = true;
409 /// Runs the given function for each sub-expression producing the final value consumed by the parent
410 /// of the give expression.
412 /// e.g. for the following expression
420 /// this will pass both `f(0)` and `1+1` to the given function.
421 pub fn for_each_value_source<'tcx, B>(
423 f: &mut impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
424 ) -> ControlFlow<B> {
426 ExprKind::Block(Block { expr: Some(e), .. }, _) => for_each_value_source(e, f),
427 ExprKind::Match(_, arms, _) => {
429 for_each_value_source(arm.body, f)?;
431 ControlFlow::Continue(())
433 ExprKind::If(_, if_expr, Some(else_expr)) => {
434 for_each_value_source(if_expr, f)?;
435 for_each_value_source(else_expr, f)
437 ExprKind::DropTemps(e) => for_each_value_source(e, f),
442 /// Runs the given function for each path expression referencing the given local which occur after
443 /// the given expression.
444 pub fn for_each_local_use_after_expr<'tcx, B>(
445 cx: &LateContext<'tcx>,
448 f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
449 ) -> ControlFlow<B> {
450 struct V<'cx, 'tcx, F, B> {
451 cx: &'cx LateContext<'tcx>,
458 impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> {
459 type NestedFilter = nested_filter::OnlyBodies;
460 fn nested_visit_map(&mut self) -> Self::Map {
464 fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
466 if e.hir_id == self.expr_id {
473 if self.res.is_break() {
476 if path_to_local_id(e, self.local_id) {
477 self.res = (self.f)(e);
484 if let Some(b) = get_enclosing_block(cx, local_id) {
490 res: ControlFlow::Continue(()),
496 ControlFlow::Continue(())
500 // Calls the given function for every unconsumed temporary created by the expression. Note the
501 // function is only guaranteed to be called for types which need to be dropped, but it may be called
503 pub fn for_each_unconsumed_temporary<'tcx, B>(
504 cx: &LateContext<'tcx>,
506 mut f: impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
507 ) -> ControlFlow<B> {
508 // Todo: Handle partially consumed values.
510 typeck: &'tcx TypeckResults<'tcx>,
513 f: &mut impl FnMut(Ty<'tcx>) -> ControlFlow<B>,
514 ) -> ControlFlow<B> {
517 typeck.expr_adjustments(e),
518 [adjust, ..] if matches!(adjust.kind, Adjust::Borrow(_) | Adjust::Deref(_))
522 ExprKind::Path(QPath::Resolved(None, p))
523 if matches!(p.res, Res::Def(DefKind::Ctor(_, CtorKind::Const), _)) =>
525 f(typeck.expr_ty(e))?;
528 | ExprKind::Unary(UnOp::Deref, _)
529 | ExprKind::Index(..)
530 | ExprKind::Field(..)
531 | ExprKind::AddrOf(..) => (),
532 _ => f(typeck.expr_ty(e))?,
536 ExprKind::AddrOf(_, _, e)
537 | ExprKind::Field(e, _)
538 | ExprKind::Unary(UnOp::Deref, e)
539 | ExprKind::Match(e, ..)
540 | ExprKind::Let(&Let { init: e, .. }) => {
541 helper(typeck, false, e, f)?;
543 ExprKind::Block(&Block { expr: Some(e), .. }, _)
545 | ExprKind::Cast(e, _)
546 | ExprKind::Unary(_, e) => {
547 helper(typeck, true, e, f)?;
549 ExprKind::Call(callee, args) => {
550 helper(typeck, true, callee, f)?;
552 helper(typeck, true, arg, f)?;
555 ExprKind::MethodCall(_, args, _) | ExprKind::Tup(args) | ExprKind::Array(args) => {
557 helper(typeck, true, arg, f)?;
560 ExprKind::Index(borrowed, consumed)
561 | ExprKind::Assign(borrowed, consumed, _)
562 | ExprKind::AssignOp(_, borrowed, consumed) => {
563 helper(typeck, false, borrowed, f)?;
564 helper(typeck, true, consumed, f)?;
566 ExprKind::Binary(_, lhs, rhs) => {
567 helper(typeck, true, lhs, f)?;
568 helper(typeck, true, rhs, f)?;
570 ExprKind::Struct(_, fields, default) => {
571 for field in fields {
572 helper(typeck, true, field.expr, f)?;
574 if let Some(default) = default {
575 helper(typeck, false, default, f)?;
578 ExprKind::If(cond, then, else_expr) => {
579 helper(typeck, true, cond, f)?;
580 helper(typeck, true, then, f)?;
581 if let Some(else_expr) = else_expr {
582 helper(typeck, true, else_expr, f)?;
585 ExprKind::Type(e, _) => {
586 helper(typeck, consume, e, f)?;
589 // Either drops temporaries, jumps out of the current expression, or has no sub expression.
590 ExprKind::DropTemps(_)
592 | ExprKind::Break(..)
593 | ExprKind::Yield(..)
594 | ExprKind::Block(..)
596 | ExprKind::Repeat(..)
598 | ExprKind::ConstBlock(_)
599 | ExprKind::Closure { .. }
601 | ExprKind::Continue(_)
602 | ExprKind::InlineAsm(_)
603 | ExprKind::Err => (),
605 ControlFlow::Continue(())
607 helper(cx.typeck_results(), true, e, &mut f)
610 pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
611 for_each_unconsumed_temporary(cx, e, |ty| {
612 if needs_ordered_drop(cx, ty) {
613 ControlFlow::Break(())
615 ControlFlow::Continue(())