diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/clippy/clippy_utils/src/lib.rs | 355 |
1 files changed, 190 insertions, 165 deletions
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index 3ebfc5e00..90192f46c 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -66,7 +66,7 @@ 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, over, HirEqInterExpr, SpanlessEq, SpanlessHash, + both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over, HirEqInterExpr, SpanlessEq, SpanlessHash, }; use core::ops::ControlFlow; @@ -80,17 +80,16 @@ use rustc_ast::ast::{self, LitKind}; use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; -use rustc_hir as hir; -use rustc_hir::def::{DefKind, Namespace, Res}; -use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::{ - def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness, Destination, Expr, - ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, - Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, - TraitRef, TyKind, UnOp, + self as hir, def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Closure, Constness, Destination, + Expr, ExprKind, FnDecl, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, IsAsync, Item, ItemKind, LangItem, Local, + MatchSource, Mutability, Node, OwnerId, 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}; @@ -106,50 +105,28 @@ use rustc_middle::ty::{ layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture, }; use rustc_middle::ty::{FloatTy, IntTy, UintTy}; -use rustc_semver::RustcVersion; -use rustc_session::Session; use rustc_span::hygiene::{ExpnKind, MacroKind}; -use rustc_span::source_map::original_sp; use rustc_span::source_map::SourceMap; use rustc_span::sym; -use rustc_span::symbol::{kw, Symbol}; -use rustc_span::{Span, DUMMY_SP}; +use rustc_span::symbol::{kw, Ident, Symbol}; +use rustc_span::Span; use rustc_target::abi::Integer; use crate::consts::{constant, Constant}; use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param}; use crate::visitors::for_each_expr; -pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> { - if let Ok(version) = RustcVersion::parse(msrv) { - return Some(version); - } else if let Some(sess) = sess { - if let Some(span) = span { - sess.span_err(span, format!("`{msrv}` is not a valid Rust version")); - } - } - None -} - -pub fn meets_msrv(msrv: Option<RustcVersion>, lint_msrv: RustcVersion) -> bool { - msrv.map_or(true, |msrv| msrv.meets(lint_msrv)) -} - #[macro_export] macro_rules! extract_msrv_attr { ($context:ident) => { fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) { let sess = rustc_lint::LintContext::sess(cx); - match $crate::get_unique_inner_attr(sess, attrs, "msrv") { - Some(msrv_attr) => { - if let Some(msrv) = msrv_attr.value_str() { - self.msrv = $crate::parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span)); - } else { - sess.span_err(msrv_attr.span, "bad clippy attribute"); - } - }, - _ => (), - } + self.msrv.enter_lint_attrs(sess, attrs); + } + + fn exit_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) { + let sess = rustc_lint::LintContext::sess(cx); + self.msrv.exit_lint_attrs(sess, attrs); } }; } @@ -247,7 +224,7 @@ pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool { /// 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 Ok(lang_id) = cx.tcx.lang_items().require(lang_item) + && let Some(lang_id) = cx.tcx.lang_items().get(lang_item) && let Some(id) = cx.tcx.opt_parent(id) { id == lang_id @@ -303,7 +280,7 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> _ => did, }; - cx.tcx.lang_items().require(item).map_or(false, |id| id == did) + cx.tcx.lang_items().get(item) == Some(did) } pub fn is_unit_expr(expr: &Expr<'_>) -> bool { @@ -435,6 +412,12 @@ pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, 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<'_>, @@ -525,125 +508,177 @@ pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx> path_res(cx, maybe_path).opt_def_id() } -fn find_primitive<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx { - let single = |ty| tcx.incoherent_impls(ty).iter().copied(); - let empty = || [].iter().copied(); - match name { - "bool" => single(BoolSimplifiedType), - "char" => single(CharSimplifiedType), - "str" => single(StrSimplifiedType), - "array" => single(ArraySimplifiedType), - "slice" => single(SliceSimplifiedType), +fn find_primitive_impls<'tcx>(tcx: TyCtxt<'tcx>, name: &str) -> impl Iterator<Item = DefId> + 'tcx { + let ty = match name { + "bool" => BoolSimplifiedType, + "char" => CharSimplifiedType, + "str" => StrSimplifiedType, + "array" => ArraySimplifiedType, + "slice" => SliceSimplifiedType, // FIXME: rustdoc documents these two using just `pointer`. // // Maybe this is something we should do here too. - "const_ptr" => single(PtrSimplifiedType(Mutability::Not)), - "mut_ptr" => single(PtrSimplifiedType(Mutability::Mut)), - "isize" => single(IntSimplifiedType(IntTy::Isize)), - "i8" => single(IntSimplifiedType(IntTy::I8)), - "i16" => single(IntSimplifiedType(IntTy::I16)), - "i32" => single(IntSimplifiedType(IntTy::I32)), - "i64" => single(IntSimplifiedType(IntTy::I64)), - "i128" => single(IntSimplifiedType(IntTy::I128)), - "usize" => single(UintSimplifiedType(UintTy::Usize)), - "u8" => single(UintSimplifiedType(UintTy::U8)), - "u16" => single(UintSimplifiedType(UintTy::U16)), - "u32" => single(UintSimplifiedType(UintTy::U32)), - "u64" => single(UintSimplifiedType(UintTy::U64)), - "u128" => single(UintSimplifiedType(UintTy::U128)), - "f32" => single(FloatSimplifiedType(FloatTy::F32)), - "f64" => single(FloatSimplifiedType(FloatTy::F64)), - _ => empty(), - } -} - -/// Resolves a def path like `std::vec::Vec`. `namespace_hint` can be supplied to disambiguate -/// between `std::vec` the module and `std::vec` the macro -/// -/// This function is expensive and should be used sparingly. -pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option<Namespace>) -> Res { - fn item_child_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: &str, matches_ns: impl Fn(Res) -> bool) -> Option<Res> { - match tcx.def_kind(def_id) { - DefKind::Mod | DefKind::Enum | DefKind::Trait => tcx - .module_children(def_id) - .iter() - .find(|item| item.ident.name.as_str() == name && matches_ns(item.res.expect_non_local())) - .map(|child| child.res.expect_non_local()), - DefKind::Impl => tcx - .associated_item_def_ids(def_id) - .iter() - .copied() - .find(|assoc_def_id| tcx.item_name(*assoc_def_id).as_str() == name) - .map(|assoc_def_id| Res::Def(tcx.def_kind(assoc_def_id), assoc_def_id)), - DefKind::Struct | DefKind::Union => tcx - .adt_def(def_id) - .non_enum_variant() - .fields - .iter() - .find(|f| f.name.as_str() == name) - .map(|f| Res::Def(DefKind::Field, f.did)), - _ => None, + "const_ptr" => PtrSimplifiedType(Mutability::Not), + "mut_ptr" => PtrSimplifiedType(Mutability::Mut), + "isize" => IntSimplifiedType(IntTy::Isize), + "i8" => IntSimplifiedType(IntTy::I8), + "i16" => IntSimplifiedType(IntTy::I16), + "i32" => IntSimplifiedType(IntTy::I32), + "i64" => IntSimplifiedType(IntTy::I64), + "i128" => IntSimplifiedType(IntTy::I128), + "usize" => UintSimplifiedType(UintTy::Usize), + "u8" => UintSimplifiedType(UintTy::U8), + "u16" => UintSimplifiedType(UintTy::U16), + "u32" => UintSimplifiedType(UintTy::U32), + "u64" => UintSimplifiedType(UintTy::U64), + "u128" => UintSimplifiedType(UintTy::U128), + "f32" => FloatSimplifiedType(FloatTy::F32), + "f64" => FloatSimplifiedType(FloatTy::F64), + _ => return [].iter().copied(), + }; + + tcx.incoherent_impls(ty).iter().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 hir.find_by_def_id(local_id) { + Some(Node::Crate(r#mod)) => { + root_mod = ItemKind::Mod(r#mod); + &root_mod + }, + Some(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 find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> { +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 mulitple 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(cx: &LateContext<'_>, path: &[&str]) -> Vec<Res> { + fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator<Item = DefId> + '_ { tcx.crates(()) .iter() .copied() - .find(|&num| tcx.crate_name(num).as_str() == name) + .filter(move |&num| tcx.crate_name(num) == name) .map(CrateNum::as_def_id) } - let (base, path) = match *path { + let tcx = cx.tcx; + + let (base, mut path) = match *path { [primitive] => { - return PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy); + return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)]; }, [base, ref path @ ..] => (base, path), - _ => return Res::Err, + _ => return Vec::new(), }; - let tcx = cx.tcx; - let starts = find_primitive(tcx, base) - .chain(find_crate(tcx, base)) + + 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)); - for first in starts { - let last = path - .iter() - .copied() - .enumerate() - // for each segment, find the child item - .try_fold(first, |res, (idx, segment)| { - let matches_ns = |res: Res| { - // If at the last segment in the path, respect the namespace hint - if idx == path.len() - 1 { - match namespace_hint { - Some(ns) => res.matches_ns(ns), - None => true, - } - } else { - res.matches_ns(Namespace::TypeNS) - } - }; - - let def_id = res.def_id(); - if let Some(item) = item_child_by_name(tcx, def_id, segment, matches_ns) { - Some(item) - } else if matches!(res, Res::Def(DefKind::Enum | DefKind::Struct, _)) { - // it is not a child item so check inherent impl items - tcx.inherent_impls(def_id) - .iter() - .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment, matches_ns)) - } else { - None - } - }); + let mut resolutions: Vec<Res> = starts.collect(); - if let Some(last) = last { - return last; - } + 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) + .iter() + .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(); } - Res::Err + resolutions +} + +/// Resolves a def path like `std::vec::Vec` to its [`DefId`]s, see [`def_path_res`]. +pub fn def_path_def_ids(cx: &LateContext<'_>, path: &[&str]) -> impl Iterator<Item = DefId> { + def_path_res(cx, path).into_iter().filter_map(|res| res.opt_def_id()) } /// Convenience function to get the `DefId` of a trait by path. @@ -651,10 +686,10 @@ pub fn def_path_res(cx: &LateContext<'_>, path: &[&str], namespace_hint: Option< /// /// This function is expensive and should be used sparingly. pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> { - match def_path_res(cx, path, Some(Namespace::TypeNS)) { + def_path_res(cx, 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. @@ -760,7 +795,6 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) - /// constructor from the std library fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool { let std_types_symbols = &[ - sym::String, sym::Vec, sym::VecDeque, sym::LinkedList, @@ -775,9 +809,9 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath< 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).ty_adt_def() { - return std_types_symbols - .iter() - .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did())); + return std_types_symbols.iter().any(|&symbol| { + cx.tcx.is_diagnostic_item(symbol, adt.did()) || Some(adt.did()) == cx.tcx.lang_items().string() + }); } } } @@ -834,7 +868,7 @@ fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: & ExprKind::Lit(hir::Lit { node: LitKind::Str(ref sym, _), .. - }) => return sym.is_empty() && is_path_diagnostic_item(cx, ty, sym::String), + }) => 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 ExprKind::Lit(ref const_lit) = cx.tcx.hir().body(len.body).value.kind && @@ -954,7 +988,7 @@ impl std::ops::BitOrAssign for CaptureKind { /// 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<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind { +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 @@ -1251,23 +1285,6 @@ pub fn contains_return(expr: &hir::Expr<'_>) -> bool { .is_some() } -/// Extends the span to the beginning of the spans line, incl. whitespaces. -/// -/// ```rust -/// let x = (); -/// // ^^ -/// // will be converted to -/// let x = (); -/// // ^^^^^^^^^^^^^^ -/// ``` -fn line_span<T: LintContext>(cx: &T, span: Span) -> Span { - let span = original_sp(span, DUMMY_SP); - let source_map_and_line = cx.sess().source_map().lookup_line(span.lo()).unwrap(); - let line_no = source_map_and_line.line; - let line_start = source_map_and_line.sf.lines(|lines| lines[line_no]); - span.with_lo(line_start) -} - /// Gets the parent node, if any. pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> { tcx.hir().parent_iter(id).next().map(|(_, node)| node) @@ -1740,6 +1757,10 @@ 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; @@ -1812,7 +1833,7 @@ pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) } /// Checks if the given `DefId` matches the path. -pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { +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()) @@ -1861,7 +1882,11 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, /// Checks if the given function kind is an async function. pub fn is_async_fn(kind: FnKind<'_>) -> bool { - matches!(kind, FnKind::ItemFn(_, _, header) if header.asyncness == IsAsync::Async) + match kind { + FnKind::ItemFn(_, _, header) => header.asyncness == IsAsync::Async, + FnKind::Method(_, sig) => sig.header.asyncness == IsAsync::Async, + FnKind::Closure => false, + } } /// Peels away all the compiler generated code surrounding the body of an async function, |