| #![feature(array_chunks)] |
| #![feature(box_patterns)] |
| #![feature(control_flow_enum)] |
| #![feature(f128)] |
| #![feature(f16)] |
| #![feature(if_let_guard)] |
| #![feature(let_chains)] |
| #![cfg_attr(bootstrap, feature(lint_reasons))] |
| #![feature(never_type)] |
| #![feature(rustc_private)] |
| #![feature(assert_matches)] |
| #![feature(unwrap_infallible)] |
| #![recursion_limit = "512"] |
| #![cfg_attr(feature = "deny-warnings", deny(warnings))] |
| #![allow( |
| clippy::missing_errors_doc, |
| clippy::missing_panics_doc, |
| clippy::must_use_candidate, |
| rustc::diagnostic_outside_of_impl, |
| rustc::untranslatable_diagnostic |
| )] |
| #![warn( |
| trivial_casts, |
| trivial_numeric_casts, |
| rust_2018_idioms, |
| unused_lifetimes, |
| unused_qualifications, |
| rustc::internal |
| )] |
| |
| // FIXME: switch to something more ergonomic here, once available. |
| // (Currently there is no way to opt into sysroot crates without `extern crate`.) |
| extern crate rustc_ast; |
| extern crate rustc_ast_pretty; |
| extern crate rustc_attr; |
| extern crate rustc_const_eval; |
| extern crate rustc_data_structures; |
| // The `rustc_driver` crate seems to be required in order to use the `rust_ast` crate. |
| #[allow(unused_extern_crates)] |
| extern crate rustc_driver; |
| extern crate rustc_errors; |
| extern crate rustc_hir; |
| extern crate rustc_hir_typeck; |
| extern crate rustc_index; |
| extern crate rustc_infer; |
| extern crate rustc_lexer; |
| extern crate rustc_lint; |
| extern crate rustc_middle; |
| extern crate rustc_mir_dataflow; |
| extern crate rustc_session; |
| extern crate rustc_span; |
| extern crate rustc_target; |
| extern crate rustc_trait_selection; |
| |
| #[macro_use] |
| pub mod sym_helper; |
| |
| pub mod ast_utils; |
| pub mod attrs; |
| mod check_proc_macro; |
| pub mod comparisons; |
| pub mod consts; |
| pub mod diagnostics; |
| pub mod eager_or_lazy; |
| pub mod higher; |
| mod hir_utils; |
| pub mod macros; |
| pub mod mir; |
| pub mod numeric_literal; |
| pub mod paths; |
| pub mod ptr; |
| pub mod qualify_min_const_fn; |
| pub mod source; |
| pub mod str_utils; |
| pub mod sugg; |
| pub mod ty; |
| pub mod usage; |
| pub mod visitors; |
| |
| pub use self::attrs::*; |
| pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match}; |
| pub use self::hir_utils::{ |
| both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, HirEqInterExpr, SpanlessEq, SpanlessHash, |
| }; |
| |
| use core::mem; |
| use core::ops::ControlFlow; |
| use std::collections::hash_map::Entry; |
| use std::hash::BuildHasherDefault; |
| use std::iter::{once, repeat}; |
| use std::sync::{Mutex, MutexGuard, OnceLock}; |
| |
| use clippy_config::types::DisallowedPath; |
| use itertools::Itertools; |
| use rustc_ast::ast::{self, LitKind, RangeLimits}; |
| use rustc_data_structures::fx::FxHashMap; |
| use rustc_data_structures::packed::Pu128; |
| use rustc_data_structures::unhash::UnhashMap; |
| use rustc_hir::def::{DefKind, Res}; |
| use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LocalModDefId, LOCAL_CRATE}; |
| use rustc_hir::definitions::{DefPath, DefPathData}; |
| use rustc_hir::hir_id::{HirIdMap, HirIdSet}; |
| use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; |
| use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; |
| use rustc_hir::{ |
| self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, |
| ConstContext, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, |
| ImplItemKind, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, |
| Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, |
| TraitRef, TyKind, UnOp, |
| }; |
| use rustc_lexer::{tokenize, TokenKind}; |
| use rustc_lint::{LateContext, Level, Lint, LintContext}; |
| use rustc_middle::hir::place::PlaceBase; |
| use rustc_middle::mir::Const; |
| use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; |
| use rustc_middle::ty::fast_reject::SimplifiedType; |
| use rustc_middle::ty::layout::IntegerExt; |
| use rustc_middle::ty::{ |
| self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, ParamEnv, |
| ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt, UintTy, UpvarCapture, |
| }; |
| use rustc_span::hygiene::{ExpnKind, MacroKind}; |
| use rustc_span::source_map::SourceMap; |
| use rustc_span::symbol::{kw, Ident, Symbol}; |
| use rustc_span::{sym, Span}; |
| use rustc_target::abi::Integer; |
| use visitors::Visitable; |
| |
| use crate::consts::{constant, mir_to_const, Constant}; |
| use crate::higher::Range; |
| use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; |
| use crate::visitors::for_each_expr_without_closures; |
| |
| use rustc_middle::hir::nested_filter; |
| |
| #[macro_export] |
| macro_rules! extract_msrv_attr { |
| ($context:ident) => { |
| fn check_attributes(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) { |
| let sess = rustc_lint::LintContext::sess(cx); |
| self.msrv.check_attributes(sess, attrs); |
| } |
| |
| fn check_attributes_post(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) { |
| let sess = rustc_lint::LintContext::sess(cx); |
| self.msrv.check_attributes_post(sess, attrs); |
| } |
| }; |
| } |
| |
| /// If the given expression is a local binding, find the initializer expression. |
| /// If that initializer expression is another local binding, find its initializer again. |
| /// This process repeats as long as possible (but usually no more than once). Initializer |
| /// expressions with adjustments are ignored. If this is not desired, use [`find_binding_init`] |
| /// instead. |
| /// |
| /// Examples: |
| /// ```no_run |
| /// let abc = 1; |
| /// // ^ output |
| /// let def = abc; |
| /// dbg!(def); |
| /// // ^^^ input |
| /// |
| /// // or... |
| /// let abc = 1; |
| /// let def = abc + 2; |
| /// // ^^^^^^^ output |
| /// dbg!(def); |
| /// // ^^^ input |
| /// ``` |
| pub fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> { |
| while let Some(init) = path_to_local(expr) |
| .and_then(|id| find_binding_init(cx, id)) |
| .filter(|init| cx.typeck_results().expr_adjustments(init).is_empty()) |
| { |
| expr = init; |
| } |
| expr |
| } |
| |
| /// Finds the initializer expression for a local binding. Returns `None` if the binding is mutable. |
| /// By only considering immutable bindings, we guarantee that the returned expression represents the |
| /// value of the binding wherever it is referenced. |
| /// |
| /// Example: For `let x = 1`, if the `HirId` of `x` is provided, the `Expr` `1` is returned. |
| /// Note: If you have an expression that references a binding `x`, use `path_to_local` to get the |
| /// canonical binding `HirId`. |
| pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> { |
| if let Node::Pat(pat) = cx.tcx.hir_node(hir_id) |
| && matches!(pat.kind, PatKind::Binding(BindingMode::NONE, ..)) |
| && let Node::LetStmt(local) = cx.tcx.parent_hir_node(hir_id) |
| { |
| return local.init; |
| } |
| None |
| } |
| |
| /// Checks if the given local has an initializer or is from something other than a `let` statement |
| /// |
| /// e.g. returns true for `x` in `fn f(x: usize) { .. }` and `let x = 1;` but false for `let x;` |
| pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool { |
| for (_, node) in cx.tcx.hir().parent_iter(local) { |
| match node { |
| Node::Pat(..) | Node::PatField(..) => {}, |
| Node::LetStmt(let_stmt) => return let_stmt.init.is_some(), |
| _ => return true, |
| } |
| } |
| |
| false |
| } |
| |
| /// Returns `true` if the given `HirId` is inside a constant context. |
| /// |
| /// This is the same as `is_inside_always_const_context`, but also includes |
| /// `const fn`. |
| /// |
| /// # Example |
| /// |
| /// ```rust,ignore |
| /// if in_constant(cx, expr.hir_id) { |
| /// // Do something |
| /// } |
| /// ``` |
| pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool { |
| cx.tcx.hir().is_inside_const_context(id) |
| } |
| |
| /// Returns `true` if the given `HirId` is inside an always constant context. |
| /// |
| /// This context includes: |
| /// * const/static items |
| /// * const blocks (or inline consts) |
| /// * associated constants |
| pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { |
| use ConstContext::{Const, ConstFn, Static}; |
| let hir = tcx.hir(); |
| let Some(ctx) = hir.body_const_context(hir.enclosing_body_owner(hir_id)) else { |
| return false; |
| }; |
| match ctx { |
| ConstFn => false, |
| Static(_) | Const { inline: _ } => true, |
| } |
| } |
| |
| /// Checks if a `Res` refers to a constructor of a `LangItem` |
| /// For example, use this to check whether a function call or a pattern is `Some(..)`. |
| pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool { |
| if let Res::Def(DefKind::Ctor(..), id) = res |
| && let Some(lang_id) = cx.tcx.lang_items().get(lang_item) |
| && let Some(id) = cx.tcx.opt_parent(id) |
| { |
| id == lang_id |
| } else { |
| false |
| } |
| } |
| |
| pub fn is_res_diagnostic_ctor(cx: &LateContext<'_>, res: Res, diag_item: Symbol) -> bool { |
| if let Res::Def(DefKind::Ctor(..), id) = res |
| && let Some(id) = cx.tcx.opt_parent(id) |
| { |
| cx.tcx.is_diagnostic_item(diag_item, id) |
| } else { |
| false |
| } |
| } |
| |
| /// Checks if a `QPath` resolves to a constructor of a diagnostic item. |
| pub fn is_diagnostic_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, diagnostic_item: Symbol) -> bool { |
| if let QPath::Resolved(_, path) = qpath { |
| if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res { |
| return cx.tcx.is_diagnostic_item(diagnostic_item, cx.tcx.parent(ctor_id)); |
| } |
| } |
| false |
| } |
| |
| /// Checks if the `DefId` matches the given diagnostic item or it's constructor. |
| pub fn is_diagnostic_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: Symbol) -> bool { |
| let did = match cx.tcx.def_kind(did) { |
| DefKind::Ctor(..) => cx.tcx.parent(did), |
| // Constructors for types in external crates seem to have `DefKind::Variant` |
| DefKind::Variant => match cx.tcx.opt_parent(did) { |
| Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did, |
| _ => did, |
| }, |
| _ => did, |
| }; |
| |
| cx.tcx.is_diagnostic_item(item, did) |
| } |
| |
| /// Checks if the `DefId` matches the given `LangItem` or it's constructor. |
| pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> bool { |
| let did = match cx.tcx.def_kind(did) { |
| DefKind::Ctor(..) => cx.tcx.parent(did), |
| // Constructors for types in external crates seem to have `DefKind::Variant` |
| DefKind::Variant => match cx.tcx.opt_parent(did) { |
| Some(did) if matches!(cx.tcx.def_kind(did), DefKind::Variant) => did, |
| _ => did, |
| }, |
| _ => did, |
| }; |
| |
| cx.tcx.lang_items().get(item) == Some(did) |
| } |
| |
| pub fn is_unit_expr(expr: &Expr<'_>) -> bool { |
| matches!( |
| expr.kind, |
| ExprKind::Block( |
| Block { |
| stmts: [], |
| expr: None, |
| .. |
| }, |
| _ |
| ) | ExprKind::Tup([]) |
| ) |
| } |
| |
| /// Checks if given pattern is a wildcard (`_`) |
| pub fn is_wild(pat: &Pat<'_>) -> bool { |
| matches!(pat.kind, PatKind::Wild) |
| } |
| |
| /// Checks if the given `QPath` belongs to a type alias. |
| pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { |
| match *qpath { |
| QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias | DefKind::AssocTy, ..)), |
| QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => is_ty_alias(&qpath), |
| _ => false, |
| } |
| } |
| |
| /// Checks if the method call given in `expr` belongs to the given trait. |
| /// This is a deprecated function, consider using [`is_trait_method`]. |
| pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool { |
| cx.typeck_results() |
| .type_dependent_def_id(expr.hir_id) |
| .and_then(|defid| cx.tcx.trait_of_item(defid)) |
| .map_or(false, |trt_id| match_def_path(cx, trt_id, path)) |
| } |
| |
| /// Checks if the given method call expression calls an inherent method. |
| pub fn is_inherent_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { |
| cx.tcx.trait_of_item(method_id).is_none() |
| } else { |
| false |
| } |
| } |
| |
| /// Checks if a method is defined in an impl of a diagnostic item |
| pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { |
| if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { |
| if let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { |
| return cx.tcx.is_diagnostic_item(diag_item, adt.did()); |
| } |
| } |
| false |
| } |
| |
| /// Checks if a method is in a diagnostic item trait |
| pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool { |
| if let Some(trait_did) = cx.tcx.trait_of_item(def_id) { |
| return cx.tcx.is_diagnostic_item(diag_item, trait_did); |
| } |
| false |
| } |
| |
| /// Checks if the method call given in `expr` belongs to the given trait. |
| pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { |
| cx.typeck_results() |
| .type_dependent_def_id(expr.hir_id) |
| .map_or(false, |did| is_diag_trait_item(cx, did, diag_item)) |
| } |
| |
| /// Checks if the `def_id` belongs to a function that is part of a trait impl. |
| pub fn is_def_id_trait_method(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { |
| if let Node::Item(item) = cx.tcx.parent_hir_node(cx.tcx.local_def_id_to_hir_id(def_id)) |
| && let ItemKind::Impl(imp) = item.kind |
| { |
| imp.of_trait.is_some() |
| } else { |
| false |
| } |
| } |
| |
| /// Checks if the given expression is a path referring an item on the trait |
| /// that is marked with the given diagnostic item. |
| /// |
| /// For checking method call expressions instead of path expressions, use |
| /// [`is_trait_method`]. |
| /// |
| /// For example, this can be used to find if an expression like `u64::default` |
| /// refers to an item of the trait `Default`, which is associated with the |
| /// `diag_item` of `sym::Default`. |
| pub fn is_trait_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { |
| if let ExprKind::Path(ref qpath) = expr.kind { |
| cx.qpath_res(qpath, expr.hir_id) |
| .opt_def_id() |
| .map_or(false, |def_id| is_diag_trait_item(cx, def_id, diag_item)) |
| } else { |
| false |
| } |
| } |
| |
| pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { |
| match *path { |
| QPath::Resolved(_, path) => path.segments.last().expect("A path must have at least one segment"), |
| QPath::TypeRelative(_, seg) => seg, |
| QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"), |
| } |
| } |
| |
| pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> { |
| last_path_segment(qpath) |
| .args |
| .map_or(&[][..], |a| a.args) |
| .iter() |
| .filter_map(|a| match a { |
| hir::GenericArg::Type(ty) => Some(*ty), |
| _ => None, |
| }) |
| } |
| |
| /// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the |
| /// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from |
| /// `QPath::Resolved.1.res.opt_def_id()`. |
| /// |
| /// Matches a `QPath` against a slice of segment string literals. |
| /// |
| /// There is also `match_path` if you are dealing with a `rustc_hir::Path` instead of a |
| /// `rustc_hir::QPath`. |
| /// |
| /// # Examples |
| /// ```rust,ignore |
| /// match_qpath(path, &["std", "rt", "begin_unwind"]) |
| /// ``` |
| pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { |
| match *path { |
| QPath::Resolved(_, path) => match_path(path, segments), |
| QPath::TypeRelative(ty, segment) => match ty.kind { |
| TyKind::Path(ref inner_path) => { |
| if let [prefix @ .., end] = segments { |
| if match_qpath(inner_path, prefix) { |
| return segment.ident.name.as_str() == *end; |
| } |
| } |
| false |
| }, |
| _ => false, |
| }, |
| QPath::LangItem(..) => false, |
| } |
| } |
| |
| /// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path. |
| /// |
| /// Please use `is_path_diagnostic_item` if the target is a diagnostic item. |
| pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool { |
| path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments)) |
| } |
| |
| /// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if |
| /// it matches the given lang item. |
| pub fn is_path_lang_item<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>, lang_item: LangItem) -> bool { |
| path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.lang_items().get(lang_item) == Some(id)) |
| } |
| |
| /// If `maybe_path` is a path node which resolves to an item, resolves it to a `DefId` and checks if |
| /// it matches the given diagnostic item. |
| pub fn is_path_diagnostic_item<'tcx>( |
| cx: &LateContext<'_>, |
| maybe_path: &impl MaybePath<'tcx>, |
| diag_item: Symbol, |
| ) -> bool { |
| path_def_id(cx, maybe_path).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) |
| } |
| |
| /// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the |
| /// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from |
| /// `QPath::Resolved.1.res.opt_def_id()`. |
| /// |
| /// Matches a `Path` against a slice of segment string literals. |
| /// |
| /// There is also `match_qpath` if you are dealing with a `rustc_hir::QPath` instead of a |
| /// `rustc_hir::Path`. |
| /// |
| /// # Examples |
| /// |
| /// ```rust,ignore |
| /// if match_path(&trait_ref.path, &paths::HASH) { |
| /// // This is the `std::hash::Hash` trait. |
| /// } |
| /// |
| /// if match_path(ty_path, &["rustc", "lint", "Lint"]) { |
| /// // This is a `rustc_middle::lint::Lint`. |
| /// } |
| /// ``` |
| pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool { |
| path.segments |
| .iter() |
| .rev() |
| .zip(segments.iter().rev()) |
| .all(|(a, b)| a.ident.name.as_str() == *b) |
| } |
| |
| /// If the expression is a path to a local, returns the canonical `HirId` of the local. |
| pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> { |
| if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind { |
| if let Res::Local(id) = path.res { |
| return Some(id); |
| } |
| } |
| None |
| } |
| |
| /// Returns true if the expression is a path to a local with the specified `HirId`. |
| /// Use this function to see if an expression matches a function argument or a match binding. |
| pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool { |
| path_to_local(expr) == Some(id) |
| } |
| |
| pub trait MaybePath<'hir> { |
| fn hir_id(&self) -> HirId; |
| fn qpath_opt(&self) -> Option<&QPath<'hir>>; |
| } |
| |
| macro_rules! maybe_path { |
| ($ty:ident, $kind:ident) => { |
| impl<'hir> MaybePath<'hir> for hir::$ty<'hir> { |
| fn hir_id(&self) -> HirId { |
| self.hir_id |
| } |
| fn qpath_opt(&self) -> Option<&QPath<'hir>> { |
| match &self.kind { |
| hir::$kind::Path(qpath) => Some(qpath), |
| _ => None, |
| } |
| } |
| } |
| }; |
| } |
| maybe_path!(Expr, ExprKind); |
| maybe_path!(Pat, PatKind); |
| maybe_path!(Ty, TyKind); |
| |
| /// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err` |
| pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res { |
| match maybe_path.qpath_opt() { |
| None => Res::Err, |
| Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()), |
| } |
| } |
| |
| /// If `maybe_path` is a path node which resolves to an item, retrieves the item ID |
| pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option<DefId> { |
| path_res(cx, maybe_path).opt_def_id() |
| } |
| |
| fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx { |
| let ty = match name { |
| "bool" => SimplifiedType::Bool, |
| "char" => SimplifiedType::Char, |
| "str" => SimplifiedType::Str, |
| "array" => SimplifiedType::Array, |
| "slice" => SimplifiedType::Slice, |
| // FIXME: rustdoc documents these two using just `pointer`. |
| // |
| // Maybe this is something we should do here too. |
| "const_ptr" => SimplifiedType::Ptr(Mutability::Not), |
| "mut_ptr" => SimplifiedType::Ptr(Mutability::Mut), |
| "isize" => SimplifiedType::Int(IntTy::Isize), |
| "i8" => SimplifiedType::Int(IntTy::I8), |
| "i16" => SimplifiedType::Int(IntTy::I16), |
| "i32" => SimplifiedType::Int(IntTy::I32), |
| "i64" => SimplifiedType::Int(IntTy::I64), |
| "i128" => SimplifiedType::Int(IntTy::I128), |
| "usize" => SimplifiedType::Uint(UintTy::Usize), |
| "u8" => SimplifiedType::Uint(UintTy::U8), |
| "u16" => SimplifiedType::Uint(UintTy::U16), |
| "u32" => SimplifiedType::Uint(UintTy::U32), |
| "u64" => SimplifiedType::Uint(UintTy::U64), |
| "u128" => SimplifiedType::Uint(UintTy::U128), |
| "f32" => SimplifiedType::Float(FloatTy::F32), |
| "f64" => SimplifiedType::Float(FloatTy::F64), |
| #[allow(trivial_casts)] |
| _ => { |
| return Result::<_, rustc_errors::ErrorGuaranteed>::Ok(&[] as &[_]) |
| .into_iter() |
| .flatten() |
| .copied(); |
| }, |
| }; |
| |
| tcx.incoherent_impls(ty).into_iter().flatten().copied() |
| } |
| |
| fn non_local_item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> { |
| match tcx.def_kind(def_id) { |
| DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx |
| .module_children(def_id) |
| .iter() |
| .filter(|item| item.ident.name == name) |
| .map(|child| child.res.expect_non_local()) |
| .collect(), |
| DefKind::Impl { .. } => tcx |
| .associated_item_def_ids(def_id) |
| .iter() |
| .copied() |
| .filter(|assoc_def_id| tcx.item_name(*assoc_def_id) == name) |
| .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)) |
| .collect(), |
| _ => Vec::new(), |
| } |
| } |
| |
| fn local_item_children_by_name(tcx: TyCtxt<'_>, local_id: LocalDefId, name: Symbol) -> Vec<Res> { |
| let hir = tcx.hir(); |
| |
| let root_mod; |
| let item_kind = match tcx.hir_node_by_def_id(local_id) { |
| Node::Crate(r#mod) => { |
| root_mod = ItemKind::Mod(r#mod); |
| &root_mod |
| }, |
| Node::Item(item) => &item.kind, |
| _ => return Vec::new(), |
| }; |
| |
| let res = |ident: Ident, owner_id: OwnerId| { |
| if ident.name == name { |
| let def_id = owner_id.to_def_id(); |
| Some(Res::Def(tcx.def_kind(def_id), def_id)) |
| } else { |
| None |
| } |
| }; |
| |
| match item_kind { |
| ItemKind::Mod(r#mod) => r#mod |
| .item_ids |
| .iter() |
| .filter_map(|&item_id| res(hir.item(item_id).ident, item_id.owner_id)) |
| .collect(), |
| ItemKind::Impl(r#impl) => r#impl |
| .items |
| .iter() |
| .filter_map(|&ImplItemRef { ident, id, .. }| res(ident, id.owner_id)) |
| .collect(), |
| ItemKind::Trait(.., trait_item_refs) => trait_item_refs |
| .iter() |
| .filter_map(|&TraitItemRef { ident, id, .. }| res(ident, id.owner_id)) |
| .collect(), |
| _ => Vec::new(), |
| } |
| } |
| |
| fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec<Res> { |
| if let Some(local_id) = def_id.as_local() { |
| local_item_children_by_name(tcx, local_id, name) |
| } else { |
| non_local_item_children_by_name(tcx, def_id, name) |
| } |
| } |
| |
| /// Resolves a def path like `std::vec::Vec`. |
| /// |
| /// Can return multiple resolutions when there are multiple versions of the same crate, e.g. |
| /// `memchr::memchr` could return the functions from both memchr 1.0 and memchr 2.0. |
| /// |
| /// Also returns multiple results when there are multiple paths under the same name e.g. `std::vec` |
| /// would have both a [`DefKind::Mod`] and [`DefKind::Macro`]. |
| /// |
| /// This function is expensive and should be used sparingly. |
| pub fn def_path_res(tcx: TyCtxt<'_>, path: &[&str]) -> Vec<Res> { |
| fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator<Item = DefId> + '_ { |
| tcx.crates(()) |
| .iter() |
| .copied() |
| .filter(move |&num| tcx.crate_name(num) == name) |
| .map(CrateNum::as_def_id) |
| } |
| |
| let (base, mut path) = match *path { |
| [primitive] => { |
| return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)]; |
| }, |
| [base, ref path @ ..] => (base, path), |
| _ => return Vec::new(), |
| }; |
| |
| let base_sym = Symbol::intern(base); |
| |
| let local_crate = if tcx.crate_name(LOCAL_CRATE) == base_sym { |
| Some(LOCAL_CRATE.as_def_id()) |
| } else { |
| None |
| }; |
| |
| let starts = find_primitive_impls(tcx, base) |
| .chain(find_crates(tcx, base_sym)) |
| .chain(local_crate) |
| .map(|id| Res::Def(tcx.def_kind(id), id)); |
| |
| let mut resolutions: Vec<Res> = starts.collect(); |
| |
| while let [segment, rest @ ..] = path { |
| path = rest; |
| let segment = Symbol::intern(segment); |
| |
| resolutions = resolutions |
| .into_iter() |
| .filter_map(|res| res.opt_def_id()) |
| .flat_map(|def_id| { |
| // When the current def_id is e.g. `struct S`, check the impl items in |
| // `impl S { ... }` |
| let inherent_impl_children = tcx |
| .inherent_impls(def_id) |
| .into_iter() |
| .flatten() |
| .flat_map(|&impl_def_id| item_children_by_name(tcx, impl_def_id, segment)); |
| |
| let direct_children = item_children_by_name(tcx, def_id, segment); |
| |
| inherent_impl_children.chain(direct_children) |
| }) |
| .collect(); |
| } |
| |
| resolutions |
| } |
| |
| /// Resolves a def path like `std::vec::Vec` to its [`DefId`]s, see [`def_path_res`]. |
| pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator<Item = DefId> { |
| def_path_res(tcx, path).into_iter().filter_map(|res| res.opt_def_id()) |
| } |
| |
| /// Creates a map of disallowed items to the reason they were disallowed. |
| pub fn create_disallowed_map( |
| tcx: TyCtxt<'_>, |
| disallowed: &'static [DisallowedPath], |
| ) -> DefIdMap<(&'static str, Option<&'static str>)> { |
| disallowed |
| .iter() |
| .map(|x| (x.path(), x.path().split("::").collect::<Vec<_>>(), x.reason())) |
| .flat_map(|(name, path, reason)| def_path_def_ids(tcx, &path).map(move |id| (id, (name, reason)))) |
| .collect() |
| } |
| |
| /// Convenience function to get the `DefId` of a trait by path. |
| /// It could be a trait or trait alias. |
| /// |
| /// This function is expensive and should be used sparingly. |
| pub fn get_trait_def_id(tcx: TyCtxt<'_>, path: &[&str]) -> Option<DefId> { |
| def_path_res(tcx, path).into_iter().find_map(|res| match res { |
| Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), |
| _ => None, |
| }) |
| } |
| |
| /// Gets the `hir::TraitRef` of the trait the given method is implemented for. |
| /// |
| /// Use this if you want to find the `TraitRef` of the `Add` trait in this example: |
| /// |
| /// ```no_run |
| /// struct Point(isize, isize); |
| /// |
| /// impl std::ops::Add for Point { |
| /// type Output = Self; |
| /// |
| /// fn add(self, other: Self) -> Self { |
| /// Point(0, 0) |
| /// } |
| /// } |
| /// ``` |
| pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, def_id: LocalDefId) -> Option<&'tcx TraitRef<'tcx>> { |
| // Get the implemented trait for the current function |
| let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); |
| let parent_impl = cx.tcx.hir().get_parent_item(hir_id); |
| if parent_impl != hir::CRATE_OWNER_ID |
| && let Node::Item(item) = cx.tcx.hir_node_by_def_id(parent_impl.def_id) |
| && let ItemKind::Impl(impl_) = &item.kind |
| { |
| return impl_.of_trait.as_ref(); |
| } |
| None |
| } |
| |
| /// This method will return tuple of projection stack and root of the expression, |
| /// used in `can_mut_borrow_both`. |
| /// |
| /// For example, if `e` represents the `v[0].a.b[x]` |
| /// this method will return a tuple, composed of a `Vec` |
| /// containing the `Expr`s for `v[0], v[0].a, v[0].a.b, v[0].a.b[x]` |
| /// and an `Expr` for root of them, `v` |
| fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &'a Expr<'hir>) { |
| let mut result = vec![]; |
| let root = loop { |
| match e.kind { |
| ExprKind::Index(ep, _, _) | ExprKind::Field(ep, _) => { |
| result.push(e); |
| e = ep; |
| }, |
| _ => break e, |
| }; |
| }; |
| result.reverse(); |
| (result, root) |
| } |
| |
| /// Gets the mutability of the custom deref adjustment, if any. |
| pub fn expr_custom_deref_adjustment(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<Mutability> { |
| cx.typeck_results() |
| .expr_adjustments(e) |
| .iter() |
| .find_map(|a| match a.kind { |
| Adjust::Deref(Some(d)) => Some(Some(d.mutbl)), |
| Adjust::Deref(None) => None, |
| _ => Some(None), |
| }) |
| .and_then(|x| x) |
| } |
| |
| /// Checks if two expressions can be mutably borrowed simultaneously |
| /// and they aren't dependent on borrowing same thing twice |
| pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -> bool { |
| let (s1, r1) = projection_stack(e1); |
| let (s2, r2) = projection_stack(e2); |
| if !eq_expr_value(cx, r1, r2) { |
| return true; |
| } |
| if expr_custom_deref_adjustment(cx, r1).is_some() || expr_custom_deref_adjustment(cx, r2).is_some() { |
| return false; |
| } |
| |
| for (x1, x2) in s1.iter().zip(s2.iter()) { |
| if expr_custom_deref_adjustment(cx, x1).is_some() || expr_custom_deref_adjustment(cx, x2).is_some() { |
| return false; |
| } |
| |
| match (&x1.kind, &x2.kind) { |
| (ExprKind::Field(_, i1), ExprKind::Field(_, i2)) => { |
| if i1 != i2 { |
| return true; |
| } |
| }, |
| (ExprKind::Index(_, i1, _), ExprKind::Index(_, i2, _)) => { |
| if !eq_expr_value(cx, i1, i2) { |
| return false; |
| } |
| }, |
| _ => return false, |
| } |
| } |
| false |
| } |
| |
| /// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent" |
| /// constructor from the std library |
| fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool { |
| let std_types_symbols = &[ |
| sym::Vec, |
| sym::VecDeque, |
| sym::LinkedList, |
| sym::HashMap, |
| sym::BTreeMap, |
| sym::HashSet, |
| sym::BTreeSet, |
| sym::BinaryHeap, |
| ]; |
| |
| if let QPath::TypeRelative(_, method) = path { |
| if method.ident.name == sym::new { |
| if let Some(impl_did) = cx.tcx.impl_of_method(def_id) { |
| if let Some(adt) = cx.tcx.type_of(impl_did).instantiate_identity().ty_adt_def() { |
| return std_types_symbols.iter().any(|&symbol| { |
| cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string() |
| }); |
| } |
| } |
| } |
| } |
| false |
| } |
| |
| /// Returns true if the expr is equal to `Default::default` when evaluated. |
| pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> bool { |
| if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind |
| && let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() |
| && (is_diag_trait_item(cx, repl_def_id, sym::Default) |
| || is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath)) |
| { |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Returns true if the expr is equal to `Default::default()` of it's type when evaluated. |
| /// It doesn't cover all cases, for example indirect function calls (some of std |
| /// functions are supported) but it is the best we have. |
| pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { |
| match &e.kind { |
| ExprKind::Lit(lit) => match lit.node { |
| LitKind::Bool(false) | LitKind::Int(Pu128(0), _) => true, |
| LitKind::Str(s, _) => s.is_empty(), |
| _ => false, |
| }, |
| ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)), |
| ExprKind::Repeat(x, ArrayLen::Body(len)) => { |
| if let ConstArgKind::Anon(anon_const) = len.kind |
| && let ExprKind::Lit(const_lit) = cx.tcx.hir().body(anon_const.body).value.kind |
| && let LitKind::Int(v, _) = const_lit.node |
| && v <= 32 |
| && is_default_equivalent(cx, x) |
| { |
| true |
| } else { |
| false |
| } |
| }, |
| ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func), |
| ExprKind::Call(from_func, [ref arg]) => is_default_equivalent_from(cx, from_func, arg), |
| ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone), |
| ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])), |
| _ => false, |
| } |
| } |
| |
| fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: &Expr<'_>) -> bool { |
| if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = from_func.kind |
| && seg.ident.name == sym::from |
| { |
| match arg.kind { |
| ExprKind::Lit(hir::Lit { |
| node: LitKind::Str(ref sym, _), |
| .. |
| }) => return sym.is_empty() && is_path_lang_item(cx, ty, LangItem::String), |
| ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec), |
| ExprKind::Repeat(_, ArrayLen::Body(len)) => { |
| if let ConstArgKind::Anon(anon_const) = len.kind |
| && let ExprKind::Lit(const_lit) = cx.tcx.hir().body(anon_const.body).value.kind |
| && let LitKind::Int(v, _) = const_lit.node |
| { |
| return v == 0 && is_path_diagnostic_item(cx, ty, sym::Vec); |
| } |
| }, |
| _ => (), |
| } |
| } |
| false |
| } |
| |
| /// Checks if the top level expression can be moved into a closure as is. |
| /// Currently checks for: |
| /// * Break/Continue outside the given loop HIR ids. |
| /// * Yield/Return statements. |
| /// * Inline assembly. |
| /// * Usages of a field of a local where the type of the local can be partially moved. |
| /// |
| /// For example, given the following function: |
| /// |
| /// ```no_run |
| /// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) { |
| /// for item in iter { |
| /// let s = item.1; |
| /// if item.0 > 10 { |
| /// continue; |
| /// } else { |
| /// s.clear(); |
| /// } |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// When called on the expression `item.0` this will return false unless the local `item` is in the |
| /// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it |
| /// isn't always safe to move into a closure when only a single field is needed. |
| /// |
| /// When called on the `continue` expression this will return false unless the outer loop expression |
| /// is in the `loop_ids` set. |
| /// |
| /// Note that this check is not recursive, so passing the `if` expression will always return true |
| /// even though sub-expressions might return false. |
| pub fn can_move_expr_to_closure_no_visit<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| loop_ids: &[HirId], |
| ignore_locals: &HirIdSet, |
| ) -> bool { |
| match expr.kind { |
| ExprKind::Break(Destination { target_id: Ok(id), .. }, _) |
| | ExprKind::Continue(Destination { target_id: Ok(id), .. }) |
| if loop_ids.contains(&id) => |
| { |
| true |
| }, |
| ExprKind::Break(..) |
| | ExprKind::Continue(_) |
| | ExprKind::Ret(_) |
| | ExprKind::Yield(..) |
| | ExprKind::InlineAsm(_) => false, |
| // Accessing a field of a local value can only be done if the type isn't |
| // partially moved. |
| ExprKind::Field( |
| &Expr { |
| hir_id, |
| kind: |
| ExprKind::Path(QPath::Resolved( |
| _, |
| Path { |
| res: Res::Local(local_id), |
| .. |
| }, |
| )), |
| .. |
| }, |
| _, |
| ) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => { |
| // TODO: check if the local has been partially moved. Assume it has for now. |
| false |
| }, |
| _ => true, |
| } |
| } |
| |
| /// How a local is captured by a closure |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| pub enum CaptureKind { |
| Value, |
| Ref(Mutability), |
| } |
| impl CaptureKind { |
| pub fn is_imm_ref(self) -> bool { |
| self == Self::Ref(Mutability::Not) |
| } |
| } |
| impl std::ops::BitOr for CaptureKind { |
| type Output = Self; |
| fn bitor(self, rhs: Self) -> Self::Output { |
| match (self, rhs) { |
| (CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value, |
| (CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_)) |
| | (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut), |
| (CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not), |
| } |
| } |
| } |
| impl std::ops::BitOrAssign for CaptureKind { |
| fn bitor_assign(&mut self, rhs: Self) { |
| *self = *self | rhs; |
| } |
| } |
| |
| /// Given an expression referencing a local, determines how it would be captured in a closure. |
| /// Note as this will walk up to parent expressions until the capture can be determined it should |
| /// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or |
| /// function argument (other than a receiver). |
| pub fn capture_local_usage(cx: &LateContext<'_>, e: &Expr<'_>) -> CaptureKind { |
| fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind { |
| let mut capture = CaptureKind::Ref(Mutability::Not); |
| pat.each_binding_or_first(&mut |_, id, span, _| match cx |
| .typeck_results() |
| .extract_binding_mode(cx.sess(), id, span) |
| .unwrap() |
| .0 |
| { |
| ByRef::No if !is_copy(cx, cx.typeck_results().node_type(id)) => { |
| capture = CaptureKind::Value; |
| }, |
| ByRef::Yes(Mutability::Mut) if capture != CaptureKind::Value => { |
| capture = CaptureKind::Ref(Mutability::Mut); |
| }, |
| _ => (), |
| }); |
| capture |
| } |
| |
| debug_assert!(matches!( |
| e.kind, |
| ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. })) |
| )); |
| |
| let mut child_id = e.hir_id; |
| let mut capture = CaptureKind::Value; |
| let mut capture_expr_ty = e; |
| |
| for (parent_id, parent) in cx.tcx.hir().parent_iter(e.hir_id) { |
| if let [ |
| Adjustment { |
| kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)), |
| target, |
| }, |
| ref adjust @ .., |
| ] = *cx |
| .typeck_results() |
| .adjustments() |
| .get(child_id) |
| .map_or(&[][..], |x| &**x) |
| { |
| if let rustc_ty::RawPtr(_, mutability) | rustc_ty::Ref(_, _, mutability) = |
| *adjust.last().map_or(target, |a| a.target).kind() |
| { |
| return CaptureKind::Ref(mutability); |
| } |
| } |
| |
| match parent { |
| Node::Expr(e) => match e.kind { |
| ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability), |
| ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not), |
| ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => { |
| return CaptureKind::Ref(Mutability::Mut); |
| }, |
| ExprKind::Field(..) => { |
| if capture == CaptureKind::Value { |
| capture_expr_ty = e; |
| } |
| }, |
| ExprKind::Let(let_expr) => { |
| let mutability = match pat_capture_kind(cx, let_expr.pat) { |
| CaptureKind::Value => Mutability::Not, |
| CaptureKind::Ref(m) => m, |
| }; |
| return CaptureKind::Ref(mutability); |
| }, |
| ExprKind::Match(_, arms, _) => { |
| let mut mutability = Mutability::Not; |
| for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) { |
| match capture { |
| CaptureKind::Value => break, |
| CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut, |
| CaptureKind::Ref(Mutability::Not) => (), |
| } |
| } |
| return CaptureKind::Ref(mutability); |
| }, |
| _ => break, |
| }, |
| Node::LetStmt(l) => match pat_capture_kind(cx, l.pat) { |
| CaptureKind::Value => break, |
| capture @ CaptureKind::Ref(_) => return capture, |
| }, |
| _ => break, |
| } |
| |
| child_id = parent_id; |
| } |
| |
| if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) { |
| // Copy types are never automatically captured by value. |
| CaptureKind::Ref(Mutability::Not) |
| } else { |
| capture |
| } |
| } |
| |
| /// Checks if the expression can be moved into a closure as is. This will return a list of captures |
| /// if so, otherwise, `None`. |
| pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> { |
| struct V<'cx, 'tcx> { |
| cx: &'cx LateContext<'tcx>, |
| // Stack of potential break targets contained in the expression. |
| loops: Vec<HirId>, |
| /// Local variables created in the expression. These don't need to be captured. |
| locals: HirIdSet, |
| /// Whether this expression can be turned into a closure. |
| allow_closure: bool, |
| /// Locals which need to be captured, and whether they need to be by value, reference, or |
| /// mutable reference. |
| captures: HirIdMap<CaptureKind>, |
| } |
| impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { |
| fn visit_expr(&mut self, e: &'tcx Expr<'_>) { |
| if !self.allow_closure { |
| return; |
| } |
| |
| match e.kind { |
| ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => { |
| if !self.locals.contains(&l) { |
| let cap = capture_local_usage(self.cx, e); |
| self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap); |
| } |
| }, |
| ExprKind::Closure(closure) => { |
| for capture in self.cx.typeck_results().closure_min_captures_flattened(closure.def_id) { |
| let local_id = match capture.place.base { |
| PlaceBase::Local(id) => id, |
| PlaceBase::Upvar(var) => var.var_path.hir_id, |
| _ => continue, |
| }; |
| if !self.locals.contains(&local_id) { |
| let capture = match capture.info.capture_kind { |
| UpvarCapture::ByValue => CaptureKind::Value, |
| UpvarCapture::ByRef(kind) => match kind { |
| BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not), |
| BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => { |
| CaptureKind::Ref(Mutability::Mut) |
| }, |
| }, |
| }; |
| self.captures |
| .entry(local_id) |
| .and_modify(|e| *e |= capture) |
| .or_insert(capture); |
| } |
| } |
| }, |
| ExprKind::Loop(b, ..) => { |
| self.loops.push(e.hir_id); |
| self.visit_block(b); |
| self.loops.pop(); |
| }, |
| _ => { |
| self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals); |
| walk_expr(self, e); |
| }, |
| } |
| } |
| |
| fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) { |
| p.each_binding_or_first(&mut |_, id, _, _| { |
| self.locals.insert(id); |
| }); |
| } |
| } |
| |
| let mut v = V { |
| cx, |
| allow_closure: true, |
| loops: Vec::new(), |
| locals: HirIdSet::default(), |
| captures: HirIdMap::default(), |
| }; |
| v.visit_expr(expr); |
| v.allow_closure.then_some(v.captures) |
| } |
| |
| /// Arguments of a method: the receiver and all the additional arguments. |
| pub type MethodArguments<'tcx> = Vec<(&'tcx Expr<'tcx>, &'tcx [Expr<'tcx>])>; |
| |
| /// Returns the method names and argument list of nested method call expressions that make up |
| /// `expr`. method/span lists are sorted with the most recent call first. |
| pub fn method_calls<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> (Vec<Symbol>, MethodArguments<'tcx>, Vec<Span>) { |
| let mut method_names = Vec::with_capacity(max_depth); |
| let mut arg_lists = Vec::with_capacity(max_depth); |
| let mut spans = Vec::with_capacity(max_depth); |
| |
| let mut current = expr; |
| for _ in 0..max_depth { |
| if let ExprKind::MethodCall(path, receiver, args, _) = ¤t.kind { |
| if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) { |
| break; |
| } |
| method_names.push(path.ident.name); |
| arg_lists.push((*receiver, &**args)); |
| spans.push(path.ident.span); |
| current = receiver; |
| } else { |
| break; |
| } |
| } |
| |
| (method_names, arg_lists, spans) |
| } |
| |
| /// Matches an `Expr` against a chain of methods, and return the matched `Expr`s. |
| /// |
| /// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`, |
| /// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec` |
| /// containing the `Expr`s for |
| /// `.bar()` and `.baz()` |
| pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<(&'a Expr<'a>, &'a [Expr<'a>])>> { |
| let mut current = expr; |
| let mut matched = Vec::with_capacity(methods.len()); |
| for method_name in methods.iter().rev() { |
| // method chains are stored last -> first |
| if let ExprKind::MethodCall(path, receiver, args, _) = current.kind { |
| if path.ident.name.as_str() == *method_name { |
| if receiver.span.from_expansion() || args.iter().any(|e| e.span.from_expansion()) { |
| return None; |
| } |
| matched.push((receiver, args)); // build up `matched` backwards |
| current = receiver; // go to parent expression |
| } else { |
| return None; |
| } |
| } else { |
| return None; |
| } |
| } |
| // Reverse `matched` so that it is in the same order as `methods`. |
| matched.reverse(); |
| Some(matched) |
| } |
| |
| /// Returns `true` if the provided `def_id` is an entrypoint to a program. |
| pub fn is_entrypoint_fn(cx: &LateContext<'_>, def_id: DefId) -> bool { |
| cx.tcx |
| .entry_fn(()) |
| .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id) |
| } |
| |
| /// Returns `true` if the expression is in the program's `#[panic_handler]`. |
| pub fn is_in_panic_handler(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { |
| let parent = cx.tcx.hir().get_parent_item(e.hir_id); |
| Some(parent.to_def_id()) == cx.tcx.lang_items().panic_impl() |
| } |
| |
| /// Gets the name of the item the expression is in, if available. |
| pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> { |
| let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id).def_id; |
| match cx.tcx.hir_node_by_def_id(parent_id) { |
| Node::Item(Item { ident, .. }) |
| | Node::TraitItem(TraitItem { ident, .. }) |
| | Node::ImplItem(ImplItem { ident, .. }) => Some(ident.name), |
| _ => None, |
| } |
| } |
| |
| pub struct ContainsName<'a, 'tcx> { |
| pub cx: &'a LateContext<'tcx>, |
| pub name: Symbol, |
| pub result: bool, |
| } |
| |
| impl<'a, 'tcx> Visitor<'tcx> for ContainsName<'a, 'tcx> { |
| type NestedFilter = nested_filter::OnlyBodies; |
| |
| fn visit_name(&mut self, name: Symbol) { |
| if self.name == name { |
| self.result = true; |
| } |
| } |
| |
| fn nested_visit_map(&mut self) -> Self::Map { |
| self.cx.tcx.hir() |
| } |
| } |
| |
| /// Checks if an `Expr` contains a certain name. |
| pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { |
| let mut cn = ContainsName { |
| name, |
| result: false, |
| cx, |
| }; |
| cn.visit_expr(expr); |
| cn.result |
| } |
| |
| /// Returns `true` if `expr` contains a return expression |
| pub fn contains_return<'tcx>(expr: impl Visitable<'tcx>) -> bool { |
| for_each_expr_without_closures(expr, |e| { |
| if matches!(e.kind, ExprKind::Ret(..)) { |
| ControlFlow::Break(()) |
| } else { |
| ControlFlow::Continue(()) |
| } |
| }) |
| .is_some() |
| } |
| |
| /// Gets the parent expression, if any –- this is useful to constrain a lint. |
| pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { |
| get_parent_expr_for_hir(cx, e.hir_id) |
| } |
| |
| /// This retrieves the parent for the given `HirId` if it's an expression. This is useful for |
| /// constraint lints |
| pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Expr<'tcx>> { |
| match cx.tcx.parent_hir_node(hir_id) { |
| Node::Expr(parent) => Some(parent), |
| _ => None, |
| } |
| } |
| |
| /// Gets the enclosing block, if any. |
| pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<&'tcx Block<'tcx>> { |
| let map = &cx.tcx.hir(); |
| let enclosing_node = map |
| .get_enclosing_scope(hir_id) |
| .map(|enclosing_id| cx.tcx.hir_node(enclosing_id)); |
| enclosing_node.and_then(|node| match node { |
| Node::Block(block) => Some(block), |
| Node::Item(&Item { |
| kind: ItemKind::Fn(_, _, eid), |
| .. |
| }) |
| | Node::ImplItem(&ImplItem { |
| kind: ImplItemKind::Fn(_, eid), |
| .. |
| }) => match cx.tcx.hir().body(eid).value.kind { |
| ExprKind::Block(block, _) => Some(block), |
| _ => None, |
| }, |
| _ => None, |
| }) |
| } |
| |
| /// Gets the loop or closure enclosing the given expression, if any. |
| pub fn get_enclosing_loop_or_multi_call_closure<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &Expr<'_>, |
| ) -> Option<&'tcx Expr<'tcx>> { |
| for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) { |
| match node { |
| Node::Expr(e) => match e.kind { |
| ExprKind::Closure { .. } |
| if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind() |
| && subs.as_closure().kind() == ClosureKind::FnOnce => {}, |
| |
| // Note: A closure's kind is determined by how it's used, not it's captures. |
| ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e), |
| _ => (), |
| }, |
| Node::Stmt(_) | Node::Block(_) | Node::LetStmt(_) | Node::Arm(_) | Node::ExprField(_) => (), |
| _ => break, |
| } |
| } |
| None |
| } |
| |
| /// Gets the parent node if it's an impl block. |
| pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> { |
| match tcx.hir().parent_iter(id).next() { |
| Some(( |
| _, |
| Node::Item(Item { |
| kind: ItemKind::Impl(imp), |
| .. |
| }), |
| )) => Some(imp), |
| _ => None, |
| } |
| } |
| |
| /// Removes blocks around an expression, only if the block contains just one expression |
| /// and no statements. Unsafe blocks are not removed. |
| /// |
| /// Examples: |
| /// * `{}` -> `{}` |
| /// * `{ x }` -> `x` |
| /// * `{{ x }}` -> `x` |
| /// * `{ x; }` -> `{ x; }` |
| /// * `{ x; y }` -> `{ x; y }` |
| /// * `{ unsafe { x } }` -> `unsafe { x }` |
| pub fn peel_blocks<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { |
| while let ExprKind::Block( |
| Block { |
| stmts: [], |
| expr: Some(inner), |
| rules: BlockCheckMode::DefaultBlock, |
| .. |
| }, |
| _, |
| ) = expr.kind |
| { |
| expr = inner; |
| } |
| expr |
| } |
| |
| /// Removes blocks around an expression, only if the block contains just one expression |
| /// or just one expression statement with a semicolon. Unsafe blocks are not removed. |
| /// |
| /// Examples: |
| /// * `{}` -> `{}` |
| /// * `{ x }` -> `x` |
| /// * `{ x; }` -> `x` |
| /// * `{{ x; }}` -> `x` |
| /// * `{ x; y }` -> `{ x; y }` |
| /// * `{ unsafe { x } }` -> `unsafe { x }` |
| pub fn peel_blocks_with_stmt<'a>(mut expr: &'a Expr<'a>) -> &'a Expr<'a> { |
| while let ExprKind::Block( |
| Block { |
| stmts: [], |
| expr: Some(inner), |
| rules: BlockCheckMode::DefaultBlock, |
| .. |
| } |
| | Block { |
| stmts: |
| [ |
| Stmt { |
| kind: StmtKind::Expr(inner) | StmtKind::Semi(inner), |
| .. |
| }, |
| ], |
| expr: None, |
| rules: BlockCheckMode::DefaultBlock, |
| .. |
| }, |
| _, |
| ) = expr.kind |
| { |
| expr = inner; |
| } |
| expr |
| } |
| |
| /// Checks if the given expression is the else clause of either an `if` or `if let` expression. |
| pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { |
| let mut iter = tcx.hir().parent_iter(expr.hir_id); |
| match iter.next() { |
| Some(( |
| _, |
| Node::Expr(Expr { |
| kind: ExprKind::If(_, _, Some(else_expr)), |
| .. |
| }), |
| )) => else_expr.hir_id == expr.hir_id, |
| _ => false, |
| } |
| } |
| |
| /// Checks if the given expression is a part of `let else` |
| /// returns `true` for both the `init` and the `else` part |
| pub fn is_inside_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { |
| let mut child_id = expr.hir_id; |
| for (parent_id, node) in tcx.hir().parent_iter(child_id) { |
| if let Node::LetStmt(LetStmt { |
| init: Some(init), |
| els: Some(els), |
| .. |
| }) = node |
| && (init.hir_id == child_id || els.hir_id == child_id) |
| { |
| return true; |
| } |
| |
| child_id = parent_id; |
| } |
| |
| false |
| } |
| |
| /// Checks if the given expression is the else clause of a `let else` expression |
| pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { |
| let mut child_id = expr.hir_id; |
| for (parent_id, node) in tcx.hir().parent_iter(child_id) { |
| if let Node::LetStmt(LetStmt { els: Some(els), .. }) = node |
| && els.hir_id == child_id |
| { |
| return true; |
| } |
| |
| child_id = parent_id; |
| } |
| |
| false |
| } |
| |
| /// Checks whether the given `Expr` is a range equivalent to a `RangeFull`. |
| /// |
| /// For the lower bound, this means that: |
| /// - either there is none |
| /// - or it is the smallest value that can be represented by the range's integer type |
| /// |
| /// For the upper bound, this means that: |
| /// - either there is none |
| /// - or it is the largest value that can be represented by the range's integer type and is |
| /// inclusive |
| /// - or it is a call to some container's `len` method and is exclusive, and the range is passed to |
| /// a method call on that same container (e.g. `v.drain(..v.len())`) |
| /// |
| /// If the given `Expr` is not some kind of range, the function returns `false`. |
| pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool { |
| let ty = cx.typeck_results().expr_ty(expr); |
| if let Some(Range { start, end, limits }) = Range::hir(expr) { |
| let start_is_none_or_min = start.map_or(true, |start| { |
| if let rustc_ty::Adt(_, subst) = ty.kind() |
| && let bnd_ty = subst.type_at(0) |
| && let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx) |
| && let Some(min_const) = mir_to_const(cx, Const::from_ty_const(min_val, bnd_ty, cx.tcx)) |
| && let Some(start_const) = constant(cx, cx.typeck_results(), start) |
| { |
| start_const == min_const |
| } else { |
| false |
| } |
| }); |
| let end_is_none_or_max = end.map_or(true, |end| match limits { |
| RangeLimits::Closed => { |
| if let rustc_ty::Adt(_, subst) = ty.kind() |
| && let bnd_ty = subst.type_at(0) |
| && let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx) |
| && let Some(max_const) = mir_to_const(cx, Const::from_ty_const(max_val, bnd_ty, cx.tcx)) |
| && let Some(end_const) = constant(cx, cx.typeck_results(), end) |
| { |
| end_const == max_const |
| } else { |
| false |
| } |
| }, |
| RangeLimits::HalfOpen => { |
| if let Some(container_path) = container_path |
| && let ExprKind::MethodCall(name, self_arg, [], _) = end.kind |
| && name.ident.name == sym::len |
| && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind |
| { |
| container_path.res == path.res |
| } else { |
| false |
| } |
| }, |
| }); |
| return start_is_none_or_min && end_is_none_or_max; |
| } |
| false |
| } |
| |
| /// Checks whether the given expression is a constant integer of the given value. |
| /// unlike `is_integer_literal`, this version does const folding |
| pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool { |
| if is_integer_literal(e, value) { |
| return true; |
| } |
| let enclosing_body = cx.tcx.hir().enclosing_body_owner(e.hir_id); |
| if let Some(Constant::Int(v)) = constant(cx, cx.tcx.typeck(enclosing_body), e) { |
| return value == v; |
| } |
| false |
| } |
| |
| /// Checks whether the given expression is a constant literal of the given value. |
| pub fn is_integer_literal(expr: &Expr<'_>, value: u128) -> bool { |
| // FIXME: use constant folding |
| if let ExprKind::Lit(spanned) = expr.kind { |
| if let LitKind::Int(v, _) = spanned.node { |
| return v == value; |
| } |
| } |
| false |
| } |
| |
| /// Returns `true` if the given `Expr` has been coerced before. |
| /// |
| /// Examples of coercions can be found in the Nomicon at |
| /// <https://doc.rust-lang.org/nomicon/coercions.html>. |
| /// |
| /// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_hir_analysis::check::coercion` for |
| /// more information on adjustments and coercions. |
| pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { |
| cx.typeck_results().adjustments().get(e.hir_id).is_some() |
| } |
| |
| /// Returns the pre-expansion span if this comes from an expansion of the |
| /// macro `name`. |
| /// See also [`is_direct_expn_of`]. |
| #[must_use] |
| pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> { |
| loop { |
| if span.from_expansion() { |
| let data = span.ctxt().outer_expn_data(); |
| let new_span = data.call_site; |
| |
| if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { |
| if mac_name.as_str() == name { |
| return Some(new_span); |
| } |
| } |
| |
| span = new_span; |
| } else { |
| return None; |
| } |
| } |
| } |
| |
| /// Returns the pre-expansion span if the span directly comes from an expansion |
| /// of the macro `name`. |
| /// The difference with [`is_expn_of`] is that in |
| /// ```no_run |
| /// # macro_rules! foo { ($name:tt!$args:tt) => { $name!$args } } |
| /// # macro_rules! bar { ($e:expr) => { $e } } |
| /// foo!(bar!(42)); |
| /// ``` |
| /// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only |
| /// from `bar!` by `is_direct_expn_of`. |
| #[must_use] |
| pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> { |
| if span.from_expansion() { |
| let data = span.ctxt().outer_expn_data(); |
| let new_span = data.call_site; |
| |
| if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { |
| if mac_name.as_str() == name { |
| return Some(new_span); |
| } |
| } |
| } |
| |
| None |
| } |
| |
| /// Convenience function to get the return type of a function. |
| pub fn return_ty<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId) -> Ty<'tcx> { |
| let ret_ty = cx.tcx.fn_sig(fn_def_id).instantiate_identity().output(); |
| cx.tcx.instantiate_bound_regions_with_erased(ret_ty) |
| } |
| |
| /// Convenience function to get the nth argument type of a function. |
| pub fn nth_arg<'tcx>(cx: &LateContext<'tcx>, fn_def_id: OwnerId, nth: usize) -> Ty<'tcx> { |
| let arg = cx.tcx.fn_sig(fn_def_id).instantiate_identity().input(nth); |
| cx.tcx.instantiate_bound_regions_with_erased(arg) |
| } |
| |
| /// Checks if an expression is constructing a tuple-like enum variant or struct |
| pub fn is_ctor_or_promotable_const_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| if let ExprKind::Call(fun, _) = expr.kind { |
| if let ExprKind::Path(ref qp) = fun.kind { |
| let res = cx.qpath_res(qp, fun.hir_id); |
| return match res { |
| Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true, |
| Res::Def(_, def_id) => cx.tcx.is_promotable_const_fn(def_id), |
| _ => false, |
| }; |
| } |
| } |
| false |
| } |
| |
| /// Returns `true` if a pattern is refutable. |
| // TODO: should be implemented using rustc/mir_build/thir machinery |
| pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { |
| fn is_enum_variant(cx: &LateContext<'_>, qpath: &QPath<'_>, id: HirId) -> bool { |
| matches!( |
| cx.qpath_res(qpath, id), |
| Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _) |
| ) |
| } |
| |
| fn are_refutable<'a, I: IntoIterator<Item = &'a Pat<'a>>>(cx: &LateContext<'_>, i: I) -> bool { |
| i.into_iter().any(|pat| is_refutable(cx, pat)) |
| } |
| |
| match pat.kind { |
| PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable. |
| PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)), |
| PatKind::Box(pat) | PatKind::Deref(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat), |
| PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), |
| PatKind::Or(pats) => { |
| // TODO: should be the honest check, that pats is exhaustive set |
| are_refutable(cx, pats) |
| }, |
| PatKind::Tuple(pats, _) => are_refutable(cx, pats), |
| PatKind::Struct(ref qpath, fields, _) => { |
| is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| field.pat)) |
| }, |
| PatKind::TupleStruct(ref qpath, pats, _) => is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats), |
| PatKind::Slice(head, middle, tail) => { |
| match &cx.typeck_results().node_type(pat.hir_id).kind() { |
| rustc_ty::Slice(..) => { |
| // [..] is the only irrefutable slice pattern. |
| !head.is_empty() || middle.is_none() || !tail.is_empty() |
| }, |
| rustc_ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter())), |
| _ => { |
| // unreachable!() |
| true |
| }, |
| } |
| }, |
| PatKind::Lit(..) | PatKind::Range(..) | PatKind::Err(_) => true, |
| } |
| } |
| |
| /// If the pattern is an `or` pattern, call the function once for each sub pattern. Otherwise, call |
| /// the function once on the given pattern. |
| pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) { |
| if let PatKind::Or(pats) = pat.kind { |
| pats.iter().for_each(f); |
| } else { |
| f(pat); |
| } |
| } |
| |
| pub fn is_self(slf: &Param<'_>) -> bool { |
| if let PatKind::Binding(.., name, _) = slf.pat.kind { |
| name.name == kw::SelfLower |
| } else { |
| false |
| } |
| } |
| |
| pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { |
| if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind { |
| if let Res::SelfTyParam { .. } | Res::SelfTyAlias { .. } = path.res { |
| return true; |
| } |
| } |
| false |
| } |
| |
| pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator<Item = &'tcx Param<'tcx>> { |
| (0..decl.inputs.len()).map(move |i| &body.params[i]) |
| } |
| |
| /// Checks if a given expression is a match expression expanded from the `?` |
| /// operator or the `try` macro. |
| pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { |
| fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { |
| if let PatKind::TupleStruct(ref path, pat, ddpos) = arm.pat.kind |
| && ddpos.as_opt_usize().is_none() |
| && is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultOk) |
| && let PatKind::Binding(_, hir_id, _, None) = pat[0].kind |
| && path_to_local_id(arm.body, hir_id) |
| { |
| return true; |
| } |
| false |
| } |
| |
| fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { |
| if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind { |
| is_res_lang_ctor(cx, cx.qpath_res(path, arm.pat.hir_id), ResultErr) |
| } else { |
| false |
| } |
| } |
| |
| if let ExprKind::Match(_, arms, ref source) = expr.kind { |
| // desugared from a `?` operator |
| if let MatchSource::TryDesugar(_) = *source { |
| return Some(expr); |
| } |
| |
| if arms.len() == 2 |
| && arms[0].guard.is_none() |
| && arms[1].guard.is_none() |
| && ((is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0]))) |
| { |
| return Some(expr); |
| } |
| } |
| |
| None |
| } |
| |
| /// Returns `true` if the lint is `#[allow]`ed or `#[expect]`ed at any of the `ids`, fulfilling all |
| /// of the expectations in `ids` |
| /// |
| /// This should only be used when the lint would otherwise be emitted, for a way to check if a lint |
| /// is allowed early to skip work see [`is_lint_allowed`] |
| /// |
| /// To emit at a lint at a different context than the one current see |
| /// [`span_lint_hir`](diagnostics::span_lint_hir) or |
| /// [`span_lint_hir_and_then`](diagnostics::span_lint_hir_and_then) |
| pub fn fulfill_or_allowed(cx: &LateContext<'_>, lint: &'static Lint, ids: impl IntoIterator<Item = HirId>) -> bool { |
| let mut suppress_lint = false; |
| |
| for id in ids { |
| let (level, _) = cx.tcx.lint_level_at_node(lint, id); |
| if let Some(expectation) = level.get_expectation_id() { |
| cx.fulfill_expectation(expectation); |
| } |
| |
| match level { |
| Level::Allow | Level::Expect(_) => suppress_lint = true, |
| Level::Warn | Level::ForceWarn(_) | Level::Deny | Level::Forbid => {}, |
| } |
| } |
| |
| suppress_lint |
| } |
| |
| /// Returns `true` if the lint is allowed in the current context. This is useful for |
| /// skipping long running code when it's unnecessary |
| /// |
| /// This function should check the lint level for the same node, that the lint will |
| /// be emitted at. If the information is buffered to be emitted at a later point, please |
| /// make sure to use `span_lint_hir` functions to emit the lint. This ensures that |
| /// expectations at the checked nodes will be fulfilled. |
| pub fn is_lint_allowed(cx: &LateContext<'_>, lint: &'static Lint, id: HirId) -> bool { |
| cx.tcx.lint_level_at_node(lint, id).0 == Level::Allow |
| } |
| |
| pub fn strip_pat_refs<'hir>(mut pat: &'hir Pat<'hir>) -> &'hir Pat<'hir> { |
| while let PatKind::Ref(subpat, _) = pat.kind { |
| pat = subpat; |
| } |
| pat |
| } |
| |
| pub fn int_bits(tcx: TyCtxt<'_>, ity: IntTy) -> u64 { |
| Integer::from_int_ty(&tcx, ity).size().bits() |
| } |
| |
| #[expect(clippy::cast_possible_wrap)] |
| /// Turn a constant int byte representation into an i128 |
| pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: IntTy) -> i128 { |
| let amt = 128 - int_bits(tcx, ity); |
| ((u as i128) << amt) >> amt |
| } |
| |
| #[expect(clippy::cast_sign_loss)] |
| /// clip unused bytes |
| pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: IntTy) -> u128 { |
| let amt = 128 - int_bits(tcx, ity); |
| ((u as u128) << amt) >> amt |
| } |
| |
| /// clip unused bytes |
| pub fn clip(tcx: TyCtxt<'_>, u: u128, ity: UintTy) -> u128 { |
| let bits = Integer::from_uint_ty(&tcx, ity).size().bits(); |
| let amt = 128 - bits; |
| (u << amt) >> amt |
| } |
| |
| pub fn has_attr(attrs: &[ast::Attribute], symbol: Symbol) -> bool { |
| attrs.iter().any(|attr| attr.has_name(symbol)) |
| } |
| |
| pub fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { |
| has_attr(cx.tcx.hir().attrs(hir_id), sym::repr) |
| } |
| |
| pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool { |
| let map = &tcx.hir(); |
| let mut prev_enclosing_node = None; |
| let mut enclosing_node = node; |
| while Some(enclosing_node) != prev_enclosing_node { |
| if has_attr(map.attrs(enclosing_node), symbol) { |
| return true; |
| } |
| prev_enclosing_node = Some(enclosing_node); |
| enclosing_node = map.get_parent_item(enclosing_node).into(); |
| } |
| |
| false |
| } |
| |
| /// Checks if the given HIR node is inside an `impl` block with the `automatically_derived` |
| /// attribute. |
| pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool { |
| tcx.hir() |
| .parent_owner_iter(id) |
| .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_)))) |
| .any(|(id, _)| { |
| has_attr( |
| tcx.hir().attrs(tcx.local_def_id_to_hir_id(id.def_id)), |
| sym::automatically_derived, |
| ) |
| }) |
| } |
| |
| /// Matches a function call with the given path and returns the arguments. |
| /// |
| /// Usage: |
| /// |
| /// ```rust,ignore |
| /// if let Some(args) = match_function_call(cx, cmp_max_call, &paths::CMP_MAX); |
| /// ``` |
| /// This function is deprecated. Use [`match_function_call_with_def_id`]. |
| pub fn match_function_call<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| path: &[&str], |
| ) -> Option<&'tcx [Expr<'tcx>]> { |
| if let ExprKind::Call(fun, args) = expr.kind |
| && let ExprKind::Path(ref qpath) = fun.kind |
| && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() |
| && match_def_path(cx, fun_def_id, path) |
| { |
| return Some(args); |
| }; |
| None |
| } |
| |
| pub fn match_function_call_with_def_id<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &'tcx Expr<'_>, |
| fun_def_id: DefId, |
| ) -> Option<&'tcx [Expr<'tcx>]> { |
| if let ExprKind::Call(fun, args) = expr.kind |
| && let ExprKind::Path(ref qpath) = fun.kind |
| && cx.qpath_res(qpath, fun.hir_id).opt_def_id() == Some(fun_def_id) |
| { |
| return Some(args); |
| }; |
| None |
| } |
| |
| /// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if |
| /// any. |
| /// |
| /// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items. |
| pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> { |
| let search_path = cx.get_def_path(did); |
| paths |
| .iter() |
| .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied())) |
| } |
| |
| /// Checks if the given `DefId` matches the path. |
| pub fn match_def_path(cx: &LateContext<'_>, did: DefId, syms: &[&str]) -> bool { |
| // We should probably move to Symbols in Clippy as well rather than interning every time. |
| let path = cx.get_def_path(did); |
| syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied()) |
| } |
| |
| /// Checks if the given `DefId` matches the `libc` item. |
| pub fn match_libc_symbol(cx: &LateContext<'_>, did: DefId, name: &str) -> bool { |
| let path = cx.get_def_path(did); |
| // libc is meant to be used as a flat list of names, but they're all actually defined in different |
| // modules based on the target platform. Ignore everything but crate name and the item name. |
| path.first().map_or(false, |s| s.as_str() == "libc") && path.last().map_or(false, |s| s.as_str() == name) |
| } |
| |
| /// Returns the list of condition expressions and the list of blocks in a |
| /// sequence of `if/else`. |
| /// E.g., this returns `([a, b], [c, d, e])` for the expression |
| /// `if a { c } else if b { d } else { e }`. |
| pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, Vec<&'tcx Block<'tcx>>) { |
| let mut conds = Vec::new(); |
| let mut blocks: Vec<&Block<'_>> = Vec::new(); |
| |
| while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) { |
| conds.push(cond); |
| if let ExprKind::Block(block, _) = then.kind { |
| blocks.push(block); |
| } else { |
| panic!("ExprKind::If node is not an ExprKind::Block"); |
| } |
| |
| if let Some(else_expr) = r#else { |
| expr = else_expr; |
| } else { |
| break; |
| } |
| } |
| |
| // final `else {..}` |
| if !blocks.is_empty() { |
| if let ExprKind::Block(block, _) = expr.kind { |
| blocks.push(block); |
| } |
| } |
| |
| (conds, blocks) |
| } |
| |
| /// Checks if the given function kind is an async function. |
| pub fn is_async_fn(kind: FnKind<'_>) -> bool { |
| match kind { |
| FnKind::ItemFn(_, _, header) => header.asyncness.is_async(), |
| FnKind::Method(_, sig) => sig.header.asyncness.is_async(), |
| FnKind::Closure => false, |
| } |
| } |
| |
| /// Peels away all the compiler generated code surrounding the body of an async function, |
| pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> { |
| if let ExprKind::Closure(&Closure { body, .. }) = body.value.kind { |
| if let ExprKind::Block( |
| Block { |
| stmts: [], |
| expr: |
| Some(Expr { |
| kind: ExprKind::DropTemps(expr), |
| .. |
| }), |
| .. |
| }, |
| _, |
| ) = tcx.hir().body(body).value.kind |
| { |
| return Some(expr); |
| } |
| }; |
| None |
| } |
| |
| // check if expr is calling method or function with #[must_use] attribute |
| pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| let did = match expr.kind { |
| ExprKind::Call(path, _) => { |
| if let ExprKind::Path(ref qpath) = path.kind |
| && let Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id) |
| { |
| Some(did) |
| } else { |
| None |
| } |
| }, |
| ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), |
| _ => None, |
| }; |
| |
| did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use)) |
| } |
| |
| /// Checks if a function's body represents the identity function. Looks for bodies of the form: |
| /// * `|x| x` |
| /// * `|x| return x` |
| /// * `|x| { return x }` |
| /// * `|x| { return x; }` |
| /// * `|(x, y)| (x, y)` |
| /// |
| /// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead. |
| fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { |
| fn check_pat(cx: &LateContext<'_>, pat: &Pat<'_>, expr: &Expr<'_>) -> bool { |
| if cx |
| .typeck_results() |
| .pat_binding_modes() |
| .get(pat.hir_id) |
| .is_some_and(|mode| matches!(mode.0, ByRef::Yes(_))) |
| { |
| // If a tuple `(x, y)` is of type `&(i32, i32)`, then due to match ergonomics, |
| // the inner patterns become references. Don't consider this the identity function |
| // as that changes types. |
| return false; |
| } |
| |
| match (pat.kind, expr.kind) { |
| (PatKind::Binding(_, id, _, _), _) => { |
| path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty() |
| }, |
| (PatKind::Tuple(pats, dotdot), ExprKind::Tup(tup)) |
| if dotdot.as_opt_usize().is_none() && pats.len() == tup.len() => |
| { |
| pats.iter().zip(tup).all(|(pat, expr)| check_pat(cx, pat, expr)) |
| }, |
| _ => false, |
| } |
| } |
| |
| let [param] = func.params else { |
| return false; |
| }; |
| |
| let mut expr = func.value; |
| loop { |
| match expr.kind { |
| ExprKind::Block( |
| &Block { |
| stmts: [], |
| expr: Some(e), |
| .. |
| }, |
| _, |
| ) |
| | ExprKind::Ret(Some(e)) => expr = e, |
| ExprKind::Block( |
| &Block { |
| stmts: [stmt], |
| expr: None, |
| .. |
| }, |
| _, |
| ) => { |
| if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind |
| && let ExprKind::Ret(Some(ret_val)) = e.kind |
| { |
| expr = ret_val; |
| } else { |
| return false; |
| } |
| }, |
| _ => return check_pat(cx, param.pat, expr), |
| } |
| } |
| } |
| |
| /// This is the same as [`is_expr_identity_function`], but does not consider closures |
| /// with type annotations for its bindings (or similar) as identity functions: |
| /// * `|x: u8| x` |
| /// * `std::convert::identity::<u8>` |
| pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| match expr.kind { |
| ExprKind::Closure(&Closure { body, fn_decl, .. }) |
| if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) => |
| { |
| is_body_identity_function(cx, cx.tcx.hir().body(body)) |
| }, |
| ExprKind::Path(QPath::Resolved(_, path)) |
| if path.segments.iter().all(|seg| seg.infer_args) |
| && let Some(did) = path.res.opt_def_id() => |
| { |
| cx.tcx.is_diagnostic_item(sym::convert_identity, did) |
| }, |
| _ => false, |
| } |
| } |
| |
| /// Checks if an expression represents the identity function |
| /// Only examines closures and `std::convert::identity` |
| /// |
| /// NOTE: If you want to use this function to find out if a closure is unnecessary, you likely want |
| /// to call [`is_expr_untyped_identity_function`] instead, which makes sure that the closure doesn't |
| /// have type annotations. This is important because removing a closure with bindings can |
| /// remove type information that helped type inference before, which can then lead to compile |
| /// errors. |
| pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| match expr.kind { |
| ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)), |
| _ => path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(sym::convert_identity, id)), |
| } |
| } |
| |
| /// Gets the node where an expression is either used, or it's type is unified with another branch. |
| /// Returns both the node and the `HirId` of the closest child node. |
| pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> { |
| let mut child_id = expr.hir_id; |
| let mut iter = tcx.hir().parent_iter(child_id); |
| loop { |
| match iter.next() { |
| None => break None, |
| Some((id, Node::Block(_))) => child_id = id, |
| Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id, |
| Some((_, Node::Expr(expr))) => match expr.kind { |
| ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id, |
| ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id, |
| ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None, |
| _ => break Some((Node::Expr(expr), child_id)), |
| }, |
| Some((_, node)) => break Some((node, child_id)), |
| } |
| } |
| } |
| |
| /// Checks if the result of an expression is used, or it's type is unified with another branch. |
| pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { |
| !matches!( |
| get_expr_use_or_unification_node(tcx, expr), |
| None | Some(( |
| Node::Stmt(Stmt { |
| kind: StmtKind::Expr(_) |
| | StmtKind::Semi(_) |
| | StmtKind::Let(LetStmt { |
| pat: Pat { |
| kind: PatKind::Wild, |
| .. |
| }, |
| .. |
| }), |
| .. |
| }), |
| _ |
| )) |
| ) |
| } |
| |
| /// Checks if the expression is the final expression returned from a block. |
| pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { |
| matches!(tcx.parent_hir_node(expr.hir_id), Node::Block(..)) |
| } |
| |
| pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> { |
| if !is_no_std_crate(cx) { |
| Some("std") |
| } else if !is_no_core_crate(cx) { |
| Some("core") |
| } else { |
| None |
| } |
| } |
| |
| pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool { |
| cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { |
| if let ast::AttrKind::Normal(ref normal) = attr.kind { |
| normal.item.path == sym::no_std |
| } else { |
| false |
| } |
| }) |
| } |
| |
| pub fn is_no_core_crate(cx: &LateContext<'_>) -> bool { |
| cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| { |
| if let ast::AttrKind::Normal(ref normal) = attr.kind { |
| normal.item.path == sym::no_core |
| } else { |
| false |
| } |
| }) |
| } |
| |
| /// Check if parent of a hir node is a trait implementation block. |
| /// For example, `f` in |
| /// ```no_run |
| /// # struct S; |
| /// # trait Trait { fn f(); } |
| /// impl Trait for S { |
| /// fn f() {} |
| /// } |
| /// ``` |
| pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool { |
| if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) { |
| matches!(item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. })) |
| } else { |
| false |
| } |
| } |
| |
| /// Check if it's even possible to satisfy the `where` clause for the item. |
| /// |
| /// `trivial_bounds` feature allows functions with unsatisfiable bounds, for example: |
| /// |
| /// ```ignore |
| /// fn foo() where i32: Iterator { |
| /// for _ in 2i32 {} |
| /// } |
| /// ``` |
| pub fn fn_has_unsatisfiable_preds(cx: &LateContext<'_>, did: DefId) -> bool { |
| use rustc_trait_selection::traits; |
| let predicates = cx |
| .tcx |
| .predicates_of(did) |
| .predicates |
| .iter() |
| .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); |
| traits::impossible_predicates(cx.tcx, traits::elaborate(cx.tcx, predicates).collect::<Vec<_>>()) |
| } |
| |
| /// Returns the `DefId` of the callee if the given expression is a function or method call. |
| pub fn fn_def_id(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<DefId> { |
| fn_def_id_with_node_args(cx, expr).map(|(did, _)| did) |
| } |
| |
| /// Returns the `DefId` of the callee if the given expression is a function or method call, |
| /// as well as its node args. |
| pub fn fn_def_id_with_node_args<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &Expr<'_>, |
| ) -> Option<(DefId, GenericArgsRef<'tcx>)> { |
| let typeck = cx.typeck_results(); |
| match &expr.kind { |
| ExprKind::MethodCall(..) => Some(( |
| typeck.type_dependent_def_id(expr.hir_id)?, |
| typeck.node_args(expr.hir_id), |
| )), |
| ExprKind::Call( |
| Expr { |
| kind: ExprKind::Path(qpath), |
| hir_id: path_hir_id, |
| .. |
| }, |
| .., |
| ) => { |
| // Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or |
| // deref to fn pointers, dyn Fn, impl Fn - #8850 |
| if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) = |
| typeck.qpath_res(qpath, *path_hir_id) |
| { |
| Some((id, typeck.node_args(*path_hir_id))) |
| } else { |
| None |
| } |
| }, |
| _ => None, |
| } |
| } |
| |
| /// Returns `Option<String>` where String is a textual representation of the type encapsulated in |
| /// the slice iff the given expression is a slice of primitives (as defined in the |
| /// `is_recursively_primitive_type` function) and `None` otherwise. |
| pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> { |
| let expr_type = cx.typeck_results().expr_ty_adjusted(expr); |
| let expr_kind = expr_type.kind(); |
| let is_primitive = match expr_kind { |
| rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type), |
| rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => { |
| if let rustc_ty::Slice(element_type) = inner_ty.kind() { |
| is_recursively_primitive_type(*element_type) |
| } else { |
| unreachable!() |
| } |
| }, |
| _ => false, |
| }; |
| |
| if is_primitive { |
| // if we have wrappers like Array, Slice or Tuple, print these |
| // and get the type enclosed in the slice ref |
| match expr_type.peel_refs().walk().nth(1).unwrap().expect_ty().kind() { |
| rustc_ty::Slice(..) => return Some("slice".into()), |
| rustc_ty::Array(..) => return Some("array".into()), |
| rustc_ty::Tuple(..) => return Some("tuple".into()), |
| _ => { |
| // is_recursively_primitive_type() should have taken care |
| // of the rest and we can rely on the type that is found |
| let refs_peeled = expr_type.peel_refs(); |
| return Some(refs_peeled.walk().last().unwrap().to_string()); |
| }, |
| } |
| } |
| None |
| } |
| |
| /// Returns list of all pairs `(a, b)` where `eq(a, b) == true` |
| /// and `a` is before `b` in `exprs` for all `a` and `b` in |
| /// `exprs` |
| /// |
| /// Given functions `eq` and `hash` such that `eq(a, b) == true` |
| /// implies `hash(a) == hash(b)` |
| pub fn search_same<T, Hash, Eq>(exprs: &[T], mut hash: Hash, mut eq: Eq) -> Vec<(&T, &T)> |
| where |
| Hash: FnMut(&T) -> u64, |
| Eq: FnMut(&T, &T) -> bool, |
| { |
| match exprs { |
| [a, b] if eq(a, b) => return vec![(a, b)], |
| _ if exprs.len() <= 2 => return vec![], |
| _ => {}, |
| } |
| |
| let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); |
| |
| let mut map: UnhashMap<u64, Vec<&_>> = |
| UnhashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); |
| |
| for expr in exprs { |
| match map.entry(hash(expr)) { |
| Entry::Occupied(mut o) => { |
| for o in o.get() { |
| if eq(o, expr) { |
| match_expr_list.push((o, expr)); |
| } |
| } |
| o.get_mut().push(expr); |
| }, |
| Entry::Vacant(v) => { |
| v.insert(vec![expr]); |
| }, |
| } |
| } |
| |
| match_expr_list |
| } |
| |
| /// Peels off all references on the pattern. Returns the underlying pattern and the number of |
| /// references removed. |
| pub fn peel_hir_pat_refs<'a>(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { |
| fn peel<'a>(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) { |
| if let PatKind::Ref(pat, _) = pat.kind { |
| peel(pat, count + 1) |
| } else { |
| (pat, count) |
| } |
| } |
| peel(pat, 0) |
| } |
| |
| /// Peels of expressions while the given closure returns `Some`. |
| pub fn peel_hir_expr_while<'tcx>( |
| mut expr: &'tcx Expr<'tcx>, |
| mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>, |
| ) -> &'tcx Expr<'tcx> { |
| while let Some(e) = f(expr) { |
| expr = e; |
| } |
| expr |
| } |
| |
| /// Peels off up to the given number of references on the expression. Returns the underlying |
| /// expression and the number of references removed. |
| pub fn peel_n_hir_expr_refs<'a>(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { |
| let mut remaining = count; |
| let e = peel_hir_expr_while(expr, |e| match e.kind { |
| ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => { |
| remaining -= 1; |
| Some(e) |
| }, |
| _ => None, |
| }); |
| (e, count - remaining) |
| } |
| |
| /// Peels off all unary operators of an expression. Returns the underlying expression and the number |
| /// of operators removed. |
| pub fn peel_hir_expr_unary<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { |
| let mut count: usize = 0; |
| let mut curr_expr = expr; |
| while let ExprKind::Unary(_, local_expr) = curr_expr.kind { |
| count = count.wrapping_add(1); |
| curr_expr = local_expr; |
| } |
| (curr_expr, count) |
| } |
| |
| /// Peels off all references on the expression. Returns the underlying expression and the number of |
| /// references removed. |
| pub fn peel_hir_expr_refs<'a>(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { |
| let mut count = 0; |
| let e = peel_hir_expr_while(expr, |e| match e.kind { |
| ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => { |
| count += 1; |
| Some(e) |
| }, |
| _ => None, |
| }); |
| (e, count) |
| } |
| |
| /// Peels off all references on the type. Returns the underlying type and the number of references |
| /// removed. |
| pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) { |
| let mut count = 0; |
| loop { |
| match &ty.kind { |
| TyKind::Ref(_, ref_ty) => { |
| ty = ref_ty.ty; |
| count += 1; |
| }, |
| _ => break (ty, count), |
| } |
| } |
| } |
| |
| /// Peels off all references on the type. Returns the underlying type and the number of references |
| /// removed. |
| pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { |
| let mut count = 0; |
| while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() { |
| ty = *dest_ty; |
| count += 1; |
| } |
| (ty, count) |
| } |
| |
| /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is |
| /// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. |
| pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { |
| loop { |
| match expr.kind { |
| ExprKind::AddrOf(_, _, e) => expr = e, |
| ExprKind::Unary(UnOp::Deref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e, |
| _ => break, |
| } |
| } |
| expr |
| } |
| |
| pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { |
| if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { |
| if let Res::Def(_, def_id) = path.res { |
| return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr); |
| } |
| } |
| false |
| } |
| |
| static TEST_ITEM_NAMES_CACHE: OnceLock<Mutex<FxHashMap<LocalModDefId, Vec<Symbol>>>> = OnceLock::new(); |
| |
| fn with_test_item_names(tcx: TyCtxt<'_>, module: LocalModDefId, f: impl Fn(&[Symbol]) -> bool) -> bool { |
| let cache = TEST_ITEM_NAMES_CACHE.get_or_init(|| Mutex::new(FxHashMap::default())); |
| let mut map: MutexGuard<'_, FxHashMap<LocalModDefId, Vec<Symbol>>> = cache.lock().unwrap(); |
| let value = map.entry(module); |
| match value { |
| Entry::Occupied(entry) => f(entry.get()), |
| Entry::Vacant(entry) => { |
| let mut names = Vec::new(); |
| for id in tcx.hir().module_items(module) { |
| if matches!(tcx.def_kind(id.owner_id), DefKind::Const) |
| && let item = tcx.hir().item(id) |
| && let ItemKind::Const(ty, _generics, _body) = item.kind |
| { |
| if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { |
| // We could also check for the type name `test::TestDescAndFn` |
| if let Res::Def(DefKind::Struct, _) = path.res { |
| let has_test_marker = tcx |
| .hir() |
| .attrs(item.hir_id()) |
| .iter() |
| .any(|a| a.has_name(sym::rustc_test_marker)); |
| if has_test_marker { |
| names.push(item.ident.name); |
| } |
| } |
| } |
| } |
| } |
| names.sort_unstable(); |
| f(entry.insert(names)) |
| }, |
| } |
| } |
| |
| /// Checks if the function containing the given `HirId` is a `#[test]` function |
| /// |
| /// Note: Add `//@compile-flags: --test` to UI tests with a `#[test]` function |
| pub fn is_in_test_function(tcx: TyCtxt<'_>, id: HirId) -> bool { |
| with_test_item_names(tcx, tcx.parent_module(id), |names| { |
| let node = tcx.hir_node(id); |
| once((id, node)) |
| .chain(tcx.hir().parent_iter(id)) |
| // Since you can nest functions we need to collect all until we leave |
| // function scope |
| .any(|(_id, node)| { |
| if let Node::Item(item) = node { |
| if let ItemKind::Fn(_, _, _) = item.kind { |
| // Note that we have sorted the item names in the visitor, |
| // so the binary_search gets the same as `contains`, but faster. |
| return names.binary_search(&item.ident.name).is_ok(); |
| } |
| } |
| false |
| }) |
| }) |
| } |
| |
| /// Checks if `id` has a `#[cfg(test)]` attribute applied |
| /// |
| /// This only checks directly applied attributes, to see if a node is inside a `#[cfg(test)]` parent |
| /// use [`is_in_cfg_test`] |
| pub fn is_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool { |
| tcx.hir().attrs(id).iter().any(|attr| { |
| if attr.has_name(sym::cfg) |
| && let Some(items) = attr.meta_item_list() |
| && let [item] = &*items |
| && item.has_name(sym::test) |
| { |
| true |
| } else { |
| false |
| } |
| }) |
| } |
| |
| /// Checks if any parent node of `HirId` has `#[cfg(test)]` attribute applied |
| pub fn is_in_cfg_test(tcx: TyCtxt<'_>, id: HirId) -> bool { |
| tcx.hir() |
| .parent_id_iter(id) |
| .any(|parent_id| is_cfg_test(tcx, parent_id)) |
| } |
| |
| /// Checks if the node is in a `#[test]` function or has any parent node marked `#[cfg(test)]` |
| pub fn is_in_test(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { |
| is_in_test_function(tcx, hir_id) || is_in_cfg_test(tcx, hir_id) |
| } |
| |
| /// Checks if the item of any of its parents has `#[cfg(...)]` attribute applied. |
| pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { |
| let hir = tcx.hir(); |
| |
| tcx.has_attr(def_id, sym::cfg) |
| || hir |
| .parent_iter(tcx.local_def_id_to_hir_id(def_id)) |
| .flat_map(|(parent_id, _)| hir.attrs(parent_id)) |
| .any(|attr| attr.has_name(sym::cfg)) |
| } |
| |
| /// Walks up the HIR tree from the given expression in an attempt to find where the value is |
| /// consumed. |
| /// |
| /// Termination has three conditions: |
| /// - The given function returns `Break`. This function will return the value. |
| /// - The consuming node is found. This function will return `Continue(use_node, child_id)`. |
| /// - No further parent nodes are found. This will trigger a debug assert or return `None`. |
| /// |
| /// This allows walking through `if`, `match`, `break`, and block expressions to find where the |
| /// value produced by the expression is consumed. |
| pub fn walk_to_expr_usage<'tcx, T>( |
| cx: &LateContext<'tcx>, |
| e: &Expr<'tcx>, |
| mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>, |
| ) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> { |
| let map = cx.tcx.hir(); |
| let mut iter = map.parent_iter(e.hir_id); |
| let mut child_id = e.hir_id; |
| |
| while let Some((parent_id, parent)) = iter.next() { |
| if let ControlFlow::Break(x) = f(parent_id, parent, child_id) { |
| return Some(ControlFlow::Break(x)); |
| } |
| let parent_expr = match parent { |
| Node::Expr(e) => e, |
| Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { |
| child_id = parent_id; |
| continue; |
| }, |
| Node::Arm(a) if a.body.hir_id == child_id => { |
| child_id = parent_id; |
| continue; |
| }, |
| _ => return Some(ControlFlow::Continue((parent, child_id))), |
| }; |
| match parent_expr.kind { |
| ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, |
| ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { |
| child_id = id; |
| iter = map.parent_iter(id); |
| }, |
| ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id, |
| _ => return Some(ControlFlow::Continue((parent, child_id))), |
| } |
| } |
| debug_assert!(false, "no parent node found for `{child_id:?}`"); |
| None |
| } |
| |
| /// A type definition as it would be viewed from within a function. |
| #[derive(Clone, Copy)] |
| pub enum DefinedTy<'tcx> { |
| // Used for locals and closures defined within the function. |
| Hir(&'tcx hir::Ty<'tcx>), |
| /// Used for function signatures, and constant and static values. This includes the `ParamEnv` |
| /// from the definition site. |
| Mir(ParamEnvAnd<'tcx, Binder<'tcx, Ty<'tcx>>>), |
| } |
| |
| /// The context an expressions value is used in. |
| pub struct ExprUseCtxt<'tcx> { |
| /// The parent node which consumes the value. |
| pub node: Node<'tcx>, |
| /// The child id of the node the value came from. |
| pub child_id: HirId, |
| /// Any adjustments applied to the type. |
| pub adjustments: &'tcx [Adjustment<'tcx>], |
| /// Whether the type must unify with another code path. |
| pub is_ty_unified: bool, |
| /// Whether the value will be moved before it's used. |
| pub moved_before_use: bool, |
| /// Whether the use site has the same `SyntaxContext` as the value. |
| pub same_ctxt: bool, |
| } |
| impl<'tcx> ExprUseCtxt<'tcx> { |
| pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> { |
| match self.node { |
| Node::LetStmt(l) => ExprUseNode::LetStmt(l), |
| Node::ExprField(field) => ExprUseNode::Field(field), |
| |
| Node::Item(&Item { |
| kind: ItemKind::Static(..) | ItemKind::Const(..), |
| owner_id, |
| .. |
| }) |
| | Node::TraitItem(&TraitItem { |
| kind: TraitItemKind::Const(..), |
| owner_id, |
| .. |
| }) |
| | Node::ImplItem(&ImplItem { |
| kind: ImplItemKind::Const(..), |
| owner_id, |
| .. |
| }) => ExprUseNode::ConstStatic(owner_id), |
| |
| Node::Item(&Item { |
| kind: ItemKind::Fn(..), |
| owner_id, |
| .. |
| }) |
| | Node::TraitItem(&TraitItem { |
| kind: TraitItemKind::Fn(..), |
| owner_id, |
| .. |
| }) |
| | Node::ImplItem(&ImplItem { |
| kind: ImplItemKind::Fn(..), |
| owner_id, |
| .. |
| }) => ExprUseNode::Return(owner_id), |
| |
| Node::Expr(use_expr) => match use_expr.kind { |
| ExprKind::Ret(_) => ExprUseNode::Return(OwnerId { |
| def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), |
| }), |
| |
| ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }), |
| ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) { |
| Some(i) => ExprUseNode::FnArg(func, i), |
| None => ExprUseNode::Callee, |
| }, |
| ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg( |
| use_expr.hir_id, |
| name.args, |
| args.iter() |
| .position(|arg| arg.hir_id == self.child_id) |
| .map_or(0, |i| i + 1), |
| ), |
| ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name), |
| ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl), |
| _ => ExprUseNode::Other, |
| }, |
| _ => ExprUseNode::Other, |
| } |
| } |
| } |
| |
| /// The node which consumes a value. |
| pub enum ExprUseNode<'tcx> { |
| /// Assignment to, or initializer for, a local |
| LetStmt(&'tcx LetStmt<'tcx>), |
| /// Initializer for a const or static item. |
| ConstStatic(OwnerId), |
| /// Implicit or explicit return from a function. |
| Return(OwnerId), |
| /// Initialization of a struct field. |
| Field(&'tcx ExprField<'tcx>), |
| /// An argument to a function. |
| FnArg(&'tcx Expr<'tcx>, usize), |
| /// An argument to a method. |
| MethodArg(HirId, Option<&'tcx GenericArgs<'tcx>>, usize), |
| /// The callee of a function call. |
| Callee, |
| /// Access of a field. |
| FieldAccess(Ident), |
| /// Borrow expression. |
| AddrOf(ast::BorrowKind, Mutability), |
| Other, |
| } |
| impl<'tcx> ExprUseNode<'tcx> { |
| /// Checks if the value is returned from the function. |
| pub fn is_return(&self) -> bool { |
| matches!(self, Self::Return(_)) |
| } |
| |
| /// Checks if the value is used as a method call receiver. |
| pub fn is_recv(&self) -> bool { |
| matches!(self, Self::MethodArg(_, _, 0)) |
| } |
| |
| /// Gets the needed type as it's defined without any type inference. |
| pub fn defined_ty(&self, cx: &LateContext<'tcx>) -> Option<DefinedTy<'tcx>> { |
| match *self { |
| Self::LetStmt(LetStmt { ty: Some(ty), .. }) => Some(DefinedTy::Hir(ty)), |
| Self::ConstStatic(id) => Some(DefinedTy::Mir( |
| cx.param_env |
| .and(Binder::dummy(cx.tcx.type_of(id).instantiate_identity())), |
| )), |
| Self::Return(id) => { |
| if let Node::Expr(Expr { |
| kind: ExprKind::Closure(c), |
| .. |
| }) = cx.tcx.hir_node_by_def_id(id.def_id) |
| { |
| match c.fn_decl.output { |
| FnRetTy::DefaultReturn(_) => None, |
| FnRetTy::Return(ty) => Some(DefinedTy::Hir(ty)), |
| } |
| } else { |
| Some(DefinedTy::Mir( |
| cx.param_env.and(cx.tcx.fn_sig(id).instantiate_identity().output()), |
| )) |
| } |
| }, |
| Self::Field(field) => match get_parent_expr_for_hir(cx, field.hir_id) { |
| Some(Expr { |
| hir_id, |
| kind: ExprKind::Struct(path, ..), |
| .. |
| }) => adt_and_variant_of_res(cx, cx.qpath_res(path, *hir_id)) |
| .and_then(|(adt, variant)| { |
| variant |
| .fields |
| .iter() |
| .find(|f| f.name == field.ident.name) |
| .map(|f| (adt, f)) |
| }) |
| .map(|(adt, field_def)| { |
| DefinedTy::Mir( |
| cx.tcx |
| .param_env(adt.did()) |
| .and(Binder::dummy(cx.tcx.type_of(field_def.did).instantiate_identity())), |
| ) |
| }), |
| _ => None, |
| }, |
| Self::FnArg(callee, i) => { |
| let sig = expr_sig(cx, callee)?; |
| let (hir_ty, ty) = sig.input_with_hir(i)?; |
| Some(match hir_ty { |
| Some(hir_ty) => DefinedTy::Hir(hir_ty), |
| None => DefinedTy::Mir( |
| sig.predicates_id() |
| .map_or(ParamEnv::empty(), |id| cx.tcx.param_env(id)) |
| .and(ty), |
| ), |
| }) |
| }, |
| Self::MethodArg(id, _, i) => { |
| let id = cx.typeck_results().type_dependent_def_id(id)?; |
| let sig = cx.tcx.fn_sig(id).skip_binder(); |
| Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) |
| }, |
| Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None, |
| } |
| } |
| } |
| |
| /// Gets the context an expression's value is used in. |
| pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprUseCtxt<'tcx> { |
| let mut adjustments = [].as_slice(); |
| let mut is_ty_unified = false; |
| let mut moved_before_use = false; |
| let mut same_ctxt = true; |
| let ctxt = e.span.ctxt(); |
| let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow<!> { |
| if adjustments.is_empty() |
| && let Node::Expr(e) = cx.tcx.hir_node(child_id) |
| { |
| adjustments = cx.typeck_results().expr_adjustments(e); |
| } |
| same_ctxt &= cx.tcx.hir().span(parent_id).ctxt() == ctxt; |
| if let Node::Expr(e) = parent { |
| match e.kind { |
| ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { |
| is_ty_unified = true; |
| moved_before_use = true; |
| }, |
| ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { |
| is_ty_unified = true; |
| moved_before_use = true; |
| }, |
| ExprKind::Block(..) => moved_before_use = true, |
| _ => {}, |
| } |
| } |
| ControlFlow::Continue(()) |
| }); |
| match node { |
| Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt { |
| node, |
| child_id, |
| adjustments, |
| is_ty_unified, |
| moved_before_use, |
| same_ctxt, |
| }, |
| Some(ControlFlow::Break(_)) => unreachable!("type of node is ControlFlow<!>"), |
| None => ExprUseCtxt { |
| node: Node::Crate(cx.tcx.hir().root_module()), |
| child_id: HirId::INVALID, |
| adjustments: &[], |
| is_ty_unified: true, |
| moved_before_use: true, |
| same_ctxt: false, |
| }, |
| } |
| } |
| |
| /// Tokenizes the input while keeping the text associated with each token. |
| pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> { |
| let mut pos = 0; |
| tokenize(s).map(move |t| { |
| let end = pos + t.len; |
| let range = pos as usize..end as usize; |
| pos = end; |
| (t.kind, s.get(range).unwrap_or_default()) |
| }) |
| } |
| |
| /// Checks whether a given span has any comment token |
| /// This checks for all types of comment: line "//", block "/**", doc "///" "//!" |
| pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool { |
| let Ok(snippet) = sm.span_to_snippet(span) else { |
| return false; |
| }; |
| return tokenize(&snippet).any(|token| { |
| matches!( |
| token.kind, |
| TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } |
| ) |
| }); |
| } |
| |
| /// Returns all the comments a given span contains |
| /// |
| /// Comments are returned wrapped with their relevant delimiters |
| pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String { |
| let snippet = sm.span_to_snippet(span).unwrap_or_default(); |
| let res = tokenize_with_text(&snippet) |
| .filter(|(t, _)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. })) |
| .map(|(_, s)| s) |
| .join("\n"); |
| res |
| } |
| |
| pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span { |
| sm.span_take_while(span, |&ch| ch == ' ' || ch == ';') |
| } |
| |
| /// Returns whether the given let pattern and else body can be turned into a question mark |
| /// |
| /// For this example: |
| /// ```ignore |
| /// let FooBar { a, b } = if let Some(a) = ex { a } else { return None }; |
| /// ``` |
| /// We get as parameters: |
| /// ```ignore |
| /// pat: Some(a) |
| /// else_body: return None |
| /// ``` |
| |
| /// And for this example: |
| /// ```ignore |
| /// let Some(FooBar { a, b }) = ex else { return None }; |
| /// ``` |
| /// We get as parameters: |
| /// ```ignore |
| /// pat: Some(FooBar { a, b }) |
| /// else_body: return None |
| /// ``` |
| |
| /// We output `Some(a)` in the first instance, and `Some(FooBar { a, b })` in the second, because |
| /// the question mark operator is applicable here. Callers have to check whether we are in a |
| /// constant or not. |
| pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( |
| cx: &LateContext<'_>, |
| pat: &'a Pat<'hir>, |
| else_body: &Expr<'_>, |
| ) -> Option<&'a Pat<'hir>> { |
| if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind |
| && is_res_lang_ctor(cx, cx.qpath_res(&pat_path, pat.hir_id), OptionSome) |
| && !is_refutable(cx, inner_pat) |
| && let else_body = peel_blocks(else_body) |
| && let ExprKind::Ret(Some(ret_val)) = else_body.kind |
| && let ExprKind::Path(ret_path) = ret_val.kind |
| && is_res_lang_ctor(cx, cx.qpath_res(&ret_path, ret_val.hir_id), OptionNone) |
| { |
| Some(inner_pat) |
| } else { |
| None |
| } |
| } |
| |
| macro_rules! op_utils { |
| ($($name:ident $assign:ident)*) => { |
| /// Binary operation traits like `LangItem::Add` |
| pub static BINOP_TRAITS: &[LangItem] = &[$(LangItem::$name,)*]; |
| |
| /// Operator-Assign traits like `LangItem::AddAssign` |
| pub static OP_ASSIGN_TRAITS: &[LangItem] = &[$(LangItem::$assign,)*]; |
| |
| /// Converts `BinOpKind::Add` to `(LangItem::Add, LangItem::AddAssign)`, for example |
| pub fn binop_traits(kind: hir::BinOpKind) -> Option<(LangItem, LangItem)> { |
| match kind { |
| $(hir::BinOpKind::$name => Some((LangItem::$name, LangItem::$assign)),)* |
| _ => None, |
| } |
| } |
| }; |
| } |
| |
| op_utils! { |
| Add AddAssign |
| Sub SubAssign |
| Mul MulAssign |
| Div DivAssign |
| Rem RemAssign |
| BitXor BitXorAssign |
| BitAnd BitAndAssign |
| BitOr BitOrAssign |
| Shl ShlAssign |
| Shr ShrAssign |
| } |
| |
| /// Returns `true` if the pattern is a `PatWild`, or is an ident prefixed with `_` |
| /// that is not locally used. |
| pub fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: impl Visitable<'tcx>) -> bool { |
| match *pat { |
| PatKind::Wild => true, |
| PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { |
| !visitors::is_local_used(cx, body, id) |
| }, |
| _ => false, |
| } |
| } |
| |
| #[derive(Clone, Copy)] |
| pub enum RequiresSemi { |
| Yes, |
| No, |
| } |
| impl RequiresSemi { |
| pub fn requires_semi(self) -> bool { |
| matches!(self, Self::Yes) |
| } |
| } |
| |
| /// Check if the expression return `!`, a type coerced from `!`, or could return `!` if the final |
| /// expression were turned into a statement. |
| #[expect(clippy::too_many_lines)] |
| pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<RequiresSemi> { |
| struct BreakTarget { |
| id: HirId, |
| unused: bool, |
| } |
| |
| struct V<'cx, 'tcx> { |
| cx: &'cx LateContext<'tcx>, |
| break_targets: Vec<BreakTarget>, |
| break_targets_for_result_ty: u32, |
| in_final_expr: bool, |
| requires_semi: bool, |
| is_never: bool, |
| } |
| |
| impl<'tcx> V<'_, 'tcx> { |
| fn push_break_target(&mut self, id: HirId) { |
| self.break_targets.push(BreakTarget { id, unused: true }); |
| self.break_targets_for_result_ty += u32::from(self.in_final_expr); |
| } |
| } |
| |
| impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { |
| fn visit_expr(&mut self, e: &'tcx Expr<'_>) { |
| // Note: Part of the complexity here comes from the fact that |
| // coercions are applied to the innermost expression. |
| // e.g. In `let x: u32 = { break () };` the never-to-any coercion |
| // is applied to the break expression. This means we can't just |
| // check the block's type as it will be `u32` despite the fact |
| // that the block always diverges. |
| |
| // The rest of the complexity comes from checking blocks which |
| // syntactically return a value, but will always diverge before |
| // reaching that point. |
| // e.g. In `let x = { foo(panic!()) };` the block's type will be the |
| // return type of `foo` even though it will never actually run. This |
| // can be trivially fixed by adding a semicolon after the call, but |
| // we must first detect that a semicolon is needed to make that |
| // suggestion. |
| |
| if self.is_never && self.break_targets.is_empty() { |
| if self.in_final_expr && !self.requires_semi { |
| // This expression won't ever run, but we still need to check |
| // if it can affect the type of the final expression. |
| match e.kind { |
| ExprKind::DropTemps(e) => self.visit_expr(e), |
| ExprKind::If(_, then, Some(else_)) => { |
| self.visit_expr(then); |
| self.visit_expr(else_); |
| }, |
| ExprKind::Match(_, arms, _) => { |
| for arm in arms { |
| self.visit_expr(arm.body); |
| } |
| }, |
| ExprKind::Loop(b, ..) => { |
| self.push_break_target(e.hir_id); |
| self.in_final_expr = false; |
| self.visit_block(b); |
| self.break_targets.pop(); |
| }, |
| ExprKind::Block(b, _) => { |
| if b.targeted_by_break { |
| self.push_break_target(b.hir_id); |
| self.visit_block(b); |
| self.break_targets.pop(); |
| } else { |
| self.visit_block(b); |
| } |
| }, |
| _ => { |
| self.requires_semi = !self.cx.typeck_results().expr_ty(e).is_never(); |
| }, |
| } |
| } |
| return; |
| } |
| match e.kind { |
| ExprKind::DropTemps(e) => self.visit_expr(e), |
| ExprKind::Ret(None) | ExprKind::Continue(_) => self.is_never = true, |
| ExprKind::Ret(Some(e)) | ExprKind::Become(e) => { |
| self.in_final_expr = false; |
| self.visit_expr(e); |
| self.is_never = true; |
| }, |
| ExprKind::Break(dest, e) => { |
| if let Some(e) = e { |
| self.in_final_expr = false; |
| self.visit_expr(e); |
| } |
| if let Ok(id) = dest.target_id |
| && let Some((i, target)) = self |
| .break_targets |
| .iter_mut() |
| .enumerate() |
| .find(|(_, target)| target.id == id) |
| { |
| target.unused &= self.is_never; |
| if i < self.break_targets_for_result_ty as usize { |
| self.requires_semi = true; |
| } |
| } |
| self.is_never = true; |
| }, |
| ExprKind::If(cond, then, else_) => { |
| let in_final_expr = mem::replace(&mut self.in_final_expr, false); |
| self.visit_expr(cond); |
| self.in_final_expr = in_final_expr; |
| |
| if self.is_never { |
| self.visit_expr(then); |
| if let Some(else_) = else_ { |
| self.visit_expr(else_); |
| } |
| } else { |
| self.visit_expr(then); |
| let is_never = mem::replace(&mut self.is_never, false); |
| if let Some(else_) = else_ { |
| self.visit_expr(else_); |
| self.is_never &= is_never; |
| } |
| } |
| }, |
| ExprKind::Match(scrutinee, arms, _) => { |
| let in_final_expr = mem::replace(&mut self.in_final_expr, false); |
| self.visit_expr(scrutinee); |
| self.in_final_expr = in_final_expr; |
| |
| if self.is_never { |
| for arm in arms { |
| self.visit_arm(arm); |
| } |
| } else { |
| let mut is_never = true; |
| for arm in arms { |
| self.is_never = false; |
| if let Some(guard) = arm.guard { |
| let in_final_expr = mem::replace(&mut self.in_final_expr, false); |
| self.visit_expr(guard); |
| self.in_final_expr = in_final_expr; |
| // The compiler doesn't consider diverging guards as causing the arm to diverge. |
| self.is_never = false; |
| } |
| self.visit_expr(arm.body); |
| is_never &= self.is_never; |
| } |
| self.is_never = is_never; |
| } |
| }, |
| ExprKind::Loop(b, _, _, _) => { |
| self.push_break_target(e.hir_id); |
| self.in_final_expr = false; |
| self.visit_block(b); |
| self.is_never = self.break_targets.pop().unwrap().unused; |
| }, |
| ExprKind::Block(b, _) => { |
| if b.targeted_by_break { |
| self.push_break_target(b.hir_id); |
| self.visit_block(b); |
| self.is_never &= self.break_targets.pop().unwrap().unused; |
| } else { |
| self.visit_block(b); |
| } |
| }, |
| _ => { |
| self.in_final_expr = false; |
| walk_expr(self, e); |
| self.is_never |= self.cx.typeck_results().expr_ty(e).is_never(); |
| }, |
| } |
| } |
| |
| fn visit_block(&mut self, b: &'tcx Block<'_>) { |
| let in_final_expr = mem::replace(&mut self.in_final_expr, false); |
| for s in b.stmts { |
| self.visit_stmt(s); |
| } |
| self.in_final_expr = in_final_expr; |
| if let Some(e) = b.expr { |
| self.visit_expr(e); |
| } |
| } |
| |
| fn visit_local(&mut self, l: &'tcx LetStmt<'_>) { |
| if let Some(e) = l.init { |
| self.visit_expr(e); |
| } |
| if let Some(else_) = l.els { |
| let is_never = self.is_never; |
| self.visit_block(else_); |
| self.is_never = is_never; |
| } |
| } |
| |
| fn visit_arm(&mut self, arm: &Arm<'tcx>) { |
| if let Some(guard) = arm.guard { |
| let in_final_expr = mem::replace(&mut self.in_final_expr, false); |
| self.visit_expr(guard); |
| self.in_final_expr = in_final_expr; |
| } |
| self.visit_expr(arm.body); |
| } |
| } |
| |
| if cx.typeck_results().expr_ty(e).is_never() { |
| Some(RequiresSemi::No) |
| } else if let ExprKind::Block(b, _) = e.kind |
| && !b.targeted_by_break |
| && b.expr.is_none() |
| { |
| // If a block diverges without a final expression then it's type is `!`. |
| None |
| } else { |
| let mut v = V { |
| cx, |
| break_targets: Vec::new(), |
| break_targets_for_result_ty: 0, |
| in_final_expr: true, |
| requires_semi: false, |
| is_never: false, |
| }; |
| v.visit_expr(e); |
| v.is_never |
| .then_some(if v.requires_semi && matches!(e.kind, ExprKind::Block(..)) { |
| RequiresSemi::Yes |
| } else { |
| RequiresSemi::No |
| }) |
| } |
| } |
| |
| /// Produces a path from a local caller to the type of the called method. Suitable for user |
| /// output/suggestions. |
| /// |
| /// Returned path can be either absolute (for methods defined non-locally), or relative (for local |
| /// methods). |
| pub fn get_path_from_caller_to_method_type<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| from: LocalDefId, |
| method: DefId, |
| args: GenericArgsRef<'tcx>, |
| ) -> String { |
| let assoc_item = tcx.associated_item(method); |
| let def_id = assoc_item.container_id(tcx); |
| match assoc_item.container { |
| rustc_ty::TraitContainer => get_path_to_callee(tcx, from, def_id), |
| rustc_ty::ImplContainer => { |
| let ty = tcx.type_of(def_id).instantiate_identity(); |
| get_path_to_ty(tcx, from, ty, args) |
| }, |
| } |
| } |
| |
| fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String { |
| match ty.kind() { |
| rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()), |
| // TODO these types need to be recursively resolved as well |
| rustc_ty::Array(..) |
| | rustc_ty::Dynamic(..) |
| | rustc_ty::Never |
| | rustc_ty::RawPtr(_, _) |
| | rustc_ty::Ref(..) |
| | rustc_ty::Slice(_) |
| | rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)), |
| _ => ty.to_string(), |
| } |
| } |
| |
| /// Produce a path from some local caller to the callee. Suitable for user output/suggestions. |
| fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String { |
| // only search for a relative path if the call is fully local |
| if callee.is_local() { |
| let callee_path = tcx.def_path(callee); |
| let caller_path = tcx.def_path(from.to_def_id()); |
| maybe_get_relative_path(&caller_path, &callee_path, 2) |
| } else { |
| tcx.def_path_str(callee) |
| } |
| } |
| |
| /// Tries to produce a relative path from `from` to `to`; if such a path would contain more than |
| /// `max_super` `super` items, produces an absolute path instead. Both `from` and `to` should be in |
| /// the local crate. |
| /// |
| /// Suitable for user output/suggestions. |
| /// |
| /// This ignores use items, and assumes that the target path is visible from the source |
| /// path (which _should_ be a reasonable assumption since we in order to be able to use an object of |
| /// certain type T, T is required to be visible). |
| /// |
| /// TODO make use of `use` items. Maybe we should have something more sophisticated like |
| /// rust-analyzer does? <https://docs.rs/ra_ap_hir_def/0.0.169/src/ra_ap_hir_def/find_path.rs.html#19-27> |
| fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String { |
| use itertools::EitherOrBoth::{Both, Left, Right}; |
| |
| // 1. skip the segments common for both paths (regardless of their type) |
| let unique_parts = to |
| .data |
| .iter() |
| .zip_longest(from.data.iter()) |
| .skip_while(|el| matches!(el, Both(l, r) if l == r)) |
| .map(|el| match el { |
| Both(l, r) => Both(l.data, r.data), |
| Left(l) => Left(l.data), |
| Right(r) => Right(r.data), |
| }); |
| |
| // 2. for the remaining segments, construct relative path using only mod names and `super` |
| let mut go_up_by = 0; |
| let mut path = Vec::new(); |
| for el in unique_parts { |
| match el { |
| Both(l, r) => { |
| // consider: |
| // a::b::sym:: :: refers to |
| // c::d::e ::f::sym |
| // result should be super::super::c::d::e::f |
| // |
| // alternatively: |
| // a::b::c ::d::sym refers to |
| // e::f::sym:: :: |
| // result should be super::super::super::super::e::f |
| if let DefPathData::TypeNs(s) = l { |
| path.push(s.to_string()); |
| } |
| if let DefPathData::TypeNs(_) = r { |
| go_up_by += 1; |
| } |
| }, |
| // consider: |
| // a::b::sym:: :: refers to |
| // c::d::e ::f::sym |
| // when looking at `f` |
| Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()), |
| // consider: |
| // a::b::c ::d::sym refers to |
| // e::f::sym:: :: |
| // when looking at `d` |
| Right(DefPathData::TypeNs(_)) => go_up_by += 1, |
| _ => {}, |
| } |
| } |
| |
| if go_up_by > max_super { |
| // `super` chain would be too long, just use the absolute path instead |
| once(String::from("crate")) |
| .chain(to.data.iter().filter_map(|el| { |
| if let DefPathData::TypeNs(sym) = el.data { |
| Some(sym.to_string()) |
| } else { |
| None |
| } |
| })) |
| .join("::") |
| } else { |
| repeat(String::from("super")).take(go_up_by).chain(path).join("::") |
| } |
| } |
| |
| /// Returns true if the specified `HirId` is the top-level expression of a statement or the only |
| /// expression in a block. |
| pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { |
| matches!( |
| cx.tcx.parent_hir_node(id), |
| Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) |
| ) |
| } |
| |
| /// Returns true if the given `expr` is a block or resembled as a block, |
| /// such as `if`, `loop`, `match` expressions etc. |
| pub fn is_block_like(expr: &Expr<'_>) -> bool { |
| matches!( |
| expr.kind, |
| ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) |
| ) |
| } |
| |
| /// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses. |
| pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool { |
| fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool { |
| match expr.kind { |
| ExprKind::Binary(_, lhs, _) => contains_block(lhs, true), |
| _ if is_block_like(expr) => is_operand, |
| _ => false, |
| } |
| } |
| |
| contains_block(expr, false) |
| } |
| |
| /// Returns true if the specified expression is in a receiver position. |
| pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { |
| if let Some(parent_expr) = get_parent_expr(cx, expr) |
| && let ExprKind::MethodCall(_, receiver, ..) = parent_expr.kind |
| && receiver.hir_id == expr.hir_id |
| { |
| return true; |
| } |
| false |
| } |