diff --git a/.gitignore b/.gitignore index adf5e8feddf..139129d55e3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ out *Cargo.lock /target /clippy_lints/target +/clippy_utils/target /clippy_workspace_tests/target /clippy_dev/target /rustc_tools_util/target diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 840341fefc6..d5ec8597044 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -18,6 +18,7 @@ edition = "2018" [dependencies] cargo_metadata = "0.12" +clippy_utils = { path = "../clippy_utils" } if_chain = "1.0.0" itertools = "0.9" pulldown-cmark = { version = "0.8", default-features = false } @@ -38,4 +39,4 @@ syn = { version = "1", features = ["full"] } [features] deny-warnings = [] # build clippy with internal lints enabled, off by default -internal-lints = [] +internal-lints = ["clippy_utils/internal-lints"] diff --git a/clippy_lints/src/consts.rs b/clippy_lints/src/consts.rs index 1b89d0bbe38..7e87f53e3fb 100644 --- a/clippy_lints/src/consts.rs +++ b/clippy_lints/src/consts.rs @@ -1,574 +1 @@ -#![allow(clippy::float_cmp)] - -use crate::utils::{clip, sext, unsext}; -use if_chain::if_chain; -use rustc_ast::ast::{self, LitFloatType, LitKind}; -use rustc_data_structures::sync::Lrc; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp}; -use rustc_lint::LateContext; -use rustc_middle::mir::interpret::Scalar; -use rustc_middle::ty::subst::{Subst, SubstsRef}; -use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt}; -use rustc_middle::{bug, span_bug}; -use rustc_span::symbol::Symbol; -use std::cmp::Ordering::{self, Equal}; -use std::convert::TryInto; -use std::hash::{Hash, Hasher}; - -/// A `LitKind`-like enum to fold constant `Expr`s into. -#[derive(Debug, Clone)] -pub enum Constant { - /// A `String` (e.g., "abc"). - Str(String), - /// A binary string (e.g., `b"abc"`). - Binary(Lrc<[u8]>), - /// A single `char` (e.g., `'a'`). - Char(char), - /// An integer's bit representation. - Int(u128), - /// An `f32`. - F32(f32), - /// An `f64`. - F64(f64), - /// `true` or `false`. - Bool(bool), - /// An array of constants. - Vec(Vec), - /// Also an array, but with only one constant, repeated N times. - Repeat(Box, u64), - /// A tuple of constants. - Tuple(Vec), - /// A raw pointer. - RawPtr(u128), - /// A reference - Ref(Box), - /// A literal with syntax error. - Err(Symbol), -} - -impl PartialEq for Constant { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, - (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, - (&Self::Char(l), &Self::Char(r)) => l == r, - (&Self::Int(l), &Self::Int(r)) => l == r, - (&Self::F64(l), &Self::F64(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - l.to_bits() == r.to_bits() - }, - (&Self::F32(l), &Self::F32(r)) => { - // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have - // `Fw32 == Fw64`, so don’t compare them. - // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. - f64::from(l).to_bits() == f64::from(r).to_bits() - }, - (&Self::Bool(l), &Self::Bool(r)) => l == r, - (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, - (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv, - (&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb, - // TODO: are there inter-type equalities? - _ => false, - } - } -} - -impl Hash for Constant { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - std::mem::discriminant(self).hash(state); - match *self { - Self::Str(ref s) => { - s.hash(state); - }, - Self::Binary(ref b) => { - b.hash(state); - }, - Self::Char(c) => { - c.hash(state); - }, - Self::Int(i) => { - i.hash(state); - }, - Self::F32(f) => { - f64::from(f).to_bits().hash(state); - }, - Self::F64(f) => { - f.to_bits().hash(state); - }, - Self::Bool(b) => { - b.hash(state); - }, - Self::Vec(ref v) | Self::Tuple(ref v) => { - v.hash(state); - }, - Self::Repeat(ref c, l) => { - c.hash(state); - l.hash(state); - }, - Self::RawPtr(u) => { - u.hash(state); - }, - Self::Ref(ref r) => { - r.hash(state); - }, - Self::Err(ref s) => { - s.hash(state); - }, - } - } -} - -impl Constant { - pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { - match (left, right) { - (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), - (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), - (&Self::Int(l), &Self::Int(r)) => { - if let ty::Int(int_ty) = *cmp_type.kind() { - Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))) - } else { - Some(l.cmp(&r)) - } - }, - (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), - (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), - (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)), - (&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l - .iter() - .zip(r.iter()) - .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri)) - .find(|r| r.map_or(true, |o| o != Ordering::Equal)) - .unwrap_or_else(|| Some(l.len().cmp(&r.len()))), - (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => { - match Self::partial_cmp(tcx, cmp_type, lv, rv) { - Some(Equal) => Some(ls.cmp(rs)), - x => x, - } - }, - (&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb), - // TODO: are there any useful inter-type orderings? - _ => None, - } - } -} - -/// Parses a `LitKind` to a `Constant`. -pub fn lit_to_constant(lit: &LitKind, ty: Option>) -> Constant { - match *lit { - LitKind::Str(ref is, _) => Constant::Str(is.to_string()), - LitKind::Byte(b) => Constant::Int(u128::from(b)), - LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)), - LitKind::Char(c) => Constant::Char(c), - LitKind::Int(n, _) => Constant::Int(n), - LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { - ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), - ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), - }, - LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() { - ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()), - ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()), - _ => bug!(), - }, - LitKind::Bool(b) => Constant::Bool(b), - LitKind::Err(s) => Constant::Err(s), - } -} - -pub fn constant<'tcx>( - lcx: &LateContext<'tcx>, - typeck_results: &ty::TypeckResults<'tcx>, - e: &Expr<'_>, -) -> Option<(Constant, bool)> { - let mut cx = ConstEvalLateContext { - lcx, - typeck_results, - param_env: lcx.param_env, - needed_resolution: false, - substs: lcx.tcx.intern_substs(&[]), - }; - cx.expr(e).map(|cst| (cst, cx.needed_resolution)) -} - -pub fn constant_simple<'tcx>( - lcx: &LateContext<'tcx>, - typeck_results: &ty::TypeckResults<'tcx>, - e: &Expr<'_>, -) -> Option { - constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) }) -} - -/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`. -pub fn constant_context<'a, 'tcx>( - lcx: &'a LateContext<'tcx>, - typeck_results: &'a ty::TypeckResults<'tcx>, -) -> ConstEvalLateContext<'a, 'tcx> { - ConstEvalLateContext { - lcx, - typeck_results, - param_env: lcx.param_env, - needed_resolution: false, - substs: lcx.tcx.intern_substs(&[]), - } -} - -pub struct ConstEvalLateContext<'a, 'tcx> { - lcx: &'a LateContext<'tcx>, - typeck_results: &'a ty::TypeckResults<'tcx>, - param_env: ty::ParamEnv<'tcx>, - needed_resolution: bool, - substs: SubstsRef<'tcx>, -} - -impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { - /// Simple constant folding: Insert an expression, get a constant or none. - pub fn expr(&mut self, e: &Expr<'_>) -> Option { - match e.kind { - ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), - ExprKind::Block(ref block, _) => self.block(block), - ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.typeck_results.expr_ty_opt(e))), - ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec), - ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple), - ExprKind::Repeat(ref value, _) => { - let n = match self.typeck_results.expr_ty(e).kind() { - ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, - _ => span_bug!(e.span, "typeck error"), - }; - self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) - }, - ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op { - UnOp::Not => self.constant_not(&o, self.typeck_results.expr_ty(e)), - UnOp::Neg => self.constant_negate(&o, self.typeck_results.expr_ty(e)), - UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }), - }), - ExprKind::If(ref cond, ref then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), - ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right), - ExprKind::Call(ref callee, ref args) => { - // We only handle a few const functions for now. - if_chain! { - if args.is_empty(); - if let ExprKind::Path(qpath) = &callee.kind; - let res = self.typeck_results.qpath_res(qpath, callee.hir_id); - if let Some(def_id) = res.opt_def_id(); - let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect(); - let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect(); - if let ["core", "num", int_impl, "max_value"] = *def_path; - then { - let value = match int_impl { - "" => i8::MAX as u128, - "" => i16::MAX as u128, - "" => i32::MAX as u128, - "" => i64::MAX as u128, - "" => i128::MAX as u128, - _ => return None, - }; - Some(Constant::Int(value)) - } - else { - None - } - } - }, - ExprKind::Index(ref arr, ref index) => self.index(arr, index), - ExprKind::AddrOf(_, _, ref inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), - // TODO: add other expressions. - _ => None, - } - } - - #[allow(clippy::cast_possible_wrap)] - fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { - use self::Constant::{Bool, Int}; - match *o { - Bool(b) => Some(Bool(!b)), - Int(value) => { - let value = !value; - match *ty.kind() { - ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))), - ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))), - _ => None, - } - }, - _ => None, - } - } - - fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { - use self::Constant::{Int, F32, F64}; - match *o { - Int(value) => { - let ity = match *ty.kind() { - ty::Int(ity) => ity, - _ => return None, - }; - // sign extend - let value = sext(self.lcx.tcx, value, ity); - let value = value.checked_neg()?; - // clear unused bits - Some(Int(unsext(self.lcx.tcx, value, ity))) - }, - F32(f) => Some(F32(-f)), - F64(f) => Some(F64(-f)), - _ => None, - } - } - - /// Create `Some(Vec![..])` of all constants, unless there is any - /// non-constant part. - fn multi(&mut self, vec: &[Expr<'_>]) -> Option> { - vec.iter().map(|elem| self.expr(elem)).collect::>() - } - - /// Lookup a possibly constant expression from a `ExprKind::Path`. - fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option { - let res = self.typeck_results.qpath_res(qpath, id); - match res { - Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { - let substs = self.typeck_results.node_substs(id); - let substs = if self.substs.is_empty() { - substs - } else { - substs.subst(self.lcx.tcx, self.substs) - }; - - let result = self - .lcx - .tcx - .const_eval_resolve( - self.param_env, - ty::WithOptConstParam::unknown(def_id), - substs, - None, - None, - ) - .ok() - .map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?; - let result = miri_to_const(&result); - if result.is_some() { - self.needed_resolution = true; - } - result - }, - // FIXME: cover all usable cases. - _ => None, - } - } - - fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { - let lhs = self.expr(lhs); - let index = self.expr(index); - - match (lhs, index) { - (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) { - Some(Constant::F32(x)) => Some(Constant::F32(*x)), - Some(Constant::F64(x)) => Some(Constant::F64(*x)), - _ => None, - }, - (Some(Constant::Vec(vec)), _) => { - if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { - match vec.get(0) { - Some(Constant::F32(x)) => Some(Constant::F32(*x)), - Some(Constant::F64(x)) => Some(Constant::F64(*x)), - _ => None, - } - } else { - None - } - }, - _ => None, - } - } - - /// A block can only yield a constant if it only has one constant expression. - fn block(&mut self, block: &Block<'_>) -> Option { - if block.stmts.is_empty() { - block.expr.as_ref().and_then(|b| self.expr(b)) - } else { - None - } - } - - fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { - if let Some(Constant::Bool(b)) = self.expr(cond) { - if b { - self.expr(&*then) - } else { - otherwise.as_ref().and_then(|expr| self.expr(expr)) - } - } else { - None - } - } - - fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option { - let l = self.expr(left)?; - let r = self.expr(right); - match (l, r) { - (Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() { - ty::Int(ity) => { - let l = sext(self.lcx.tcx, l, ity); - let r = sext(self.lcx.tcx, r, ity); - let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity)); - match op.node { - BinOpKind::Add => l.checked_add(r).map(zext), - BinOpKind::Sub => l.checked_sub(r).map(zext), - BinOpKind::Mul => l.checked_mul(r).map(zext), - BinOpKind::Div if r != 0 => l.checked_div(r).map(zext), - BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext), - BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext), - BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext), - BinOpKind::BitXor => Some(zext(l ^ r)), - BinOpKind::BitOr => Some(zext(l | r)), - BinOpKind::BitAnd => Some(zext(l & r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - } - }, - ty::Uint(_) => match op.node { - BinOpKind::Add => l.checked_add(r).map(Constant::Int), - BinOpKind::Sub => l.checked_sub(r).map(Constant::Int), - BinOpKind::Mul => l.checked_mul(r).map(Constant::Int), - BinOpKind::Div => l.checked_div(r).map(Constant::Int), - BinOpKind::Rem => l.checked_rem(r).map(Constant::Int), - BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int), - BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int), - BinOpKind::BitXor => Some(Constant::Int(l ^ r)), - BinOpKind::BitOr => Some(Constant::Int(l | r)), - BinOpKind::BitAnd => Some(Constant::Int(l & r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - }, - _ => None, - }, - (Constant::F32(l), Some(Constant::F32(r))) => match op.node { - BinOpKind::Add => Some(Constant::F32(l + r)), - BinOpKind::Sub => Some(Constant::F32(l - r)), - BinOpKind::Mul => Some(Constant::F32(l * r)), - BinOpKind::Div => Some(Constant::F32(l / r)), - BinOpKind::Rem => Some(Constant::F32(l % r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - }, - (Constant::F64(l), Some(Constant::F64(r))) => match op.node { - BinOpKind::Add => Some(Constant::F64(l + r)), - BinOpKind::Sub => Some(Constant::F64(l - r)), - BinOpKind::Mul => Some(Constant::F64(l * r)), - BinOpKind::Div => Some(Constant::F64(l / r)), - BinOpKind::Rem => Some(Constant::F64(l % r)), - BinOpKind::Eq => Some(Constant::Bool(l == r)), - BinOpKind::Ne => Some(Constant::Bool(l != r)), - BinOpKind::Lt => Some(Constant::Bool(l < r)), - BinOpKind::Le => Some(Constant::Bool(l <= r)), - BinOpKind::Ge => Some(Constant::Bool(l >= r)), - BinOpKind::Gt => Some(Constant::Bool(l > r)), - _ => None, - }, - (l, r) => match (op.node, l, r) { - (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)), - (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)), - (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => { - Some(r) - }, - (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)), - (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)), - (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)), - _ => None, - }, - } - } -} - -pub fn miri_to_const(result: &ty::Const<'_>) -> Option { - use rustc_middle::mir::interpret::ConstValue; - match result.val { - ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => { - match result.ty.kind() { - ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), - ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), - ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( - int.try_into().expect("invalid f32 bit representation"), - ))), - ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( - int.try_into().expect("invalid f64 bit representation"), - ))), - ty::RawPtr(type_and_mut) => { - if let ty::Uint(_) = type_and_mut.ty.kind() { - return Some(Constant::RawPtr(int.assert_bits(int.size()))); - } - None - }, - // FIXME: implement other conversions. - _ => None, - } - }, - ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() { - ty::Ref(_, tam, _) => match tam.kind() { - ty::Str => String::from_utf8( - data.inspect_with_uninit_and_ptr_outside_interpreter(start..end) - .to_owned(), - ) - .ok() - .map(Constant::Str), - _ => None, - }, - _ => None, - }, - ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() { - ty::Array(sub_type, len) => match sub_type.kind() { - ty::Float(FloatTy::F32) => match miri_to_const(len) { - Some(Constant::Int(len)) => alloc - .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) - .to_owned() - .chunks(4) - .map(|chunk| { - Some(Constant::F32(f32::from_le_bytes( - chunk.try_into().expect("this shouldn't happen"), - ))) - }) - .collect::>>() - .map(Constant::Vec), - _ => None, - }, - ty::Float(FloatTy::F64) => match miri_to_const(len) { - Some(Constant::Int(len)) => alloc - .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) - .to_owned() - .chunks(8) - .map(|chunk| { - Some(Constant::F64(f64::from_le_bytes( - chunk.try_into().expect("this shouldn't happen"), - ))) - }) - .collect::>>() - .map(Constant::Vec), - _ => None, - }, - // FIXME: implement other array type conversions. - _ => None, - }, - _ => None, - }, - // FIXME: implement other conversions. - _ => None, - } -} +pub use clippy_utils::consts::*; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index d66e3720be0..c8502e9cc74 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,13 +1,9 @@ // error-pattern:cargo-clippy -#![feature(bindings_after_at)] #![feature(box_patterns)] #![feature(box_syntax)] -#![feature(concat_idents)] -#![feature(crate_visibility_modifier)] #![feature(drain_filter)] #![feature(in_band_lifetimes)] -#![feature(once_cell)] #![feature(or_patterns)] #![feature(rustc_private)] #![feature(stmt_expr_attributes)] @@ -149,6 +145,20 @@ macro_rules! declare_clippy_lint { }; } +#[macro_export] +macro_rules! sym { + ( $($x:tt)* ) => { clippy_utils::sym!($($x)*) } +} + +#[macro_export] +macro_rules! unwrap_cargo_metadata { + ( $($x:tt)* ) => { clippy_utils::unwrap_cargo_metadata!($($x)*) } +} + +macro_rules! extract_msrv_attr { + ( $($x:tt)* ) => { clippy_utils::extract_msrv_attr!($($x)*); } +} + mod consts; #[macro_use] mod utils; diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 40771449264..68ab8161e20 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1,1862 +1,6 @@ -#[macro_use] -pub mod sym_helper; - -#[allow(clippy::module_name_repetitions)] -pub mod ast_utils; -pub mod attrs; pub mod author; -pub mod camel_case; -pub mod comparisons; -pub mod conf; -mod diagnostics; -pub mod eager_or_lazy; -pub mod higher; -mod hir_utils; pub mod inspector; #[cfg(feature = "internal-lints")] pub mod internal_lints; -pub mod numeric_literal; -pub mod paths; -pub mod ptr; -pub mod qualify_min_const_fn; -pub mod sugg; -pub mod usage; -pub mod visitors; -pub use self::attrs::*; -pub use self::diagnostics::*; -pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash}; - -use std::borrow::Cow; -use std::collections::hash_map::Entry; -use std::hash::BuildHasherDefault; - -use if_chain::if_chain; -use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind, Mutability}; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; -use rustc_hir::Node; -use rustc_hir::{ - def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind, - MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind, Unsafety, -}; -use rustc_infer::infer::TyCtxtInferExt; -use rustc_lint::{LateContext, Level, Lint, LintContext}; -use rustc_middle::hir::exports::Export; -use rustc_middle::hir::map::Map; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; -use rustc_middle::ty::{self, layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable}; -use rustc_semver::RustcVersion; -use rustc_session::Session; -use rustc_span::hygiene::{ExpnKind, MacroKind}; -use rustc_span::source_map::original_sp; -use rustc_span::sym; -use rustc_span::symbol::{kw, Symbol}; -use rustc_span::{BytePos, Pos, Span, DUMMY_SP}; -use rustc_target::abi::Integer; -use rustc_trait_selection::traits::query::normalize::AtExt; -use smallvec::SmallVec; - -use crate::consts::{constant, Constant}; - -pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { - 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!("`{}` is not a valid Rust version", msrv)); - } - } - None -} - -pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool { - msrv.map_or(true, |msrv| msrv.meets(*lint_msrv)) -} - -macro_rules! extract_msrv_attr { - (LateContext) => { - extract_msrv_attr!(@LateContext, ()); - }; - (EarlyContext) => { - extract_msrv_attr!(@EarlyContext); - }; - (@$context:ident$(, $call:tt)?) => { - fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) { - use $crate::utils::get_unique_inner_attr; - match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") { - Some(msrv_attr) => { - if let Some(msrv) = msrv_attr.value_str() { - self.msrv = $crate::utils::parse_msrv( - &msrv.to_string(), - Some(cx.sess$($call)?), - Some(msrv_attr.span), - ); - } else { - cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute"); - } - }, - _ => (), - } - } - }; -} - -/// Returns `true` if the two spans come from differing expansions (i.e., one is -/// from a macro and one isn't). -#[must_use] -pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool { - rhs.ctxt() != lhs.ctxt() -} - -/// Returns `true` if the given `NodeId` is inside a constant context -/// -/// # Example -/// -/// ```rust,ignore -/// if in_constant(cx, expr.hir_id) { -/// // Do something -/// } -/// ``` -pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool { - let parent_id = cx.tcx.hir().get_parent_item(id); - match cx.tcx.hir().get(parent_id) { - Node::Item(&Item { - kind: ItemKind::Const(..) | ItemKind::Static(..), - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Const(..), - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Const(..), - .. - }) - | Node::AnonConst(_) => true, - Node::Item(&Item { - kind: ItemKind::Fn(ref sig, ..), - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Fn(ref sig, _), - .. - }) => sig.header.constness == Constness::Const, - _ => false, - } -} - -/// Returns `true` if this `span` was expanded by any macro. -#[must_use] -pub fn in_macro(span: Span) -> bool { - if span.from_expansion() { - !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..)) - } else { - false - } -} - -// If the snippet is empty, it's an attribute that was inserted during macro -// expansion and we want to ignore those, because they could come from external -// sources that the user has no control over. -// For some reason these attributes don't have any expansion info on them, so -// we have to check it this way until there is a better way. -pub fn is_present_in_source(cx: &T, span: Span) -> bool { - if let Some(snippet) = snippet_opt(cx, span) { - if snippet.is_empty() { - return false; - } - } - true -} - -/// Checks if given pattern is a wildcard (`_`) -pub fn is_wild<'tcx>(pat: &impl std::ops::Deref>) -> bool { - matches!(pat.kind, PatKind::Wild) -} - -/// Checks if type is struct, enum or union type with the given def path. -/// -/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead. -/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` -pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool { - match ty.kind() { - ty::Adt(adt, _) => match_def_path(cx, adt.did, path), - _ => false, - } -} - -/// Checks if the type is equal to a diagnostic item -/// -/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` -pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { - match ty.kind() { - ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did), - _ => false, - } -} - -/// Checks if the type is equal to a lang item -pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool { - match ty.kind() { - ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).unwrap() == adt.did, - _ => false, - } -} - -/// Checks if the method call given in `expr` belongs to the given trait. -pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool { - let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); - let trt_id = cx.tcx.trait_of_item(def_id); - trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path)) -} - -/// Checks if an expression references a variable of the given name. -pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool { - if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind { - if let [p] = path.segments { - return p.ident.name == var; - } - } - false -} - -pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { - match *path { - QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"), - QPath::TypeRelative(_, ref seg) => seg, - QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"), - } -} - -pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { - match *path { - QPath::Resolved(_, ref path) => path.segments.get(0), - QPath::TypeRelative(_, ref seg) => Some(seg), - QPath::LangItem(..) => None, - } -} - -/// 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(_, ref path) => match_path(path, segments), - QPath::TypeRelative(ref ty, ref 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, - } -} - -/// 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) -} - -/// Matches a `Path` against a slice of segment string literals, e.g. -/// -/// # Examples -/// ```rust,ignore -/// match_path_ast(path, &["std", "rt", "begin_unwind"]) -/// ``` -pub fn match_path_ast(path: &ast::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 { - if let ExprKind::Path(QPath::Resolved(None, ref 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) -} - -/// Gets the definition associated to a path. -#[allow(clippy::shadow_unrelated)] // false positive #6563 -pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res { - macro_rules! try_res { - ($e:expr) => { - match $e { - Some(e) => e, - None => return Res::Err, - } - }; - } - fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export> { - tcx.item_children(def_id) - .iter() - .find(|item| item.ident.name.as_str() == name) - } - - let (krate, first, path) = match *path { - [krate, first, ref path @ ..] => (krate, first, path), - _ => return Res::Err, - }; - let tcx = cx.tcx; - let crates = tcx.crates(); - let krate = try_res!(crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate)); - let first = try_res!(item_child_by_name(tcx, krate.as_def_id(), first)); - let last = path - .iter() - .copied() - // `get_def_path` seems to generate these empty segments for extern blocks. - // We can just ignore them. - .filter(|segment| !segment.is_empty()) - // for each segment, find the child item - .try_fold(first, |item, segment| { - let def_id = item.res.def_id(); - if let Some(item) = item_child_by_name(tcx, def_id, segment) { - Some(item) - } else if matches!(item.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)) - } else { - None - } - }); - try_res!(last).res -} - -/// Convenience function to get the `DefId` of a trait by path. -/// It could be a trait or trait alias. -pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option { - match path_to_res(cx, path) { - Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), - _ => None, - } -} - -/// Checks whether a type implements a trait. -/// See also `get_trait_def_id`. -pub fn implements_trait<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - trait_id: DefId, - ty_params: &[GenericArg<'tcx>], -) -> bool { - // Do not check on infer_types to avoid panic in evaluate_obligation. - if ty.has_infer_types() { - return false; - } - let ty = cx.tcx.erase_regions(ty); - if ty.has_escaping_bound_vars() { - return false; - } - let ty_params = cx.tcx.mk_substs(ty_params.iter()); - cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env)) -} - -/// 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: -/// -/// ```rust -/// 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>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> { - // Get the implemented trait for the current function - let parent_impl = cx.tcx.hir().get_parent_item(hir_id); - if_chain! { - if parent_impl != hir::CRATE_HIR_ID; - if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl); - if let hir::ItemKind::Impl(impl_) = &item.kind; - then { return impl_.of_trait.as_ref(); } - } - None -} - -/// Checks whether this type implements `Drop`. -pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - match ty.ty_adt_def() { - Some(def) => def.has_dtor(cx.tcx), - None => false, - } -} - -/// 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, Vec<&'tcx [Expr<'tcx>]>, Vec) { - 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, span, args, _) = ¤t.kind { - if args.iter().any(|e| e.span.from_expansion()) { - break; - } - method_names.push(path.ident.name); - arg_lists.push(&**args); - spans.push(*span); - current = &args[0]; - } 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]>> { - 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(ref path, _, ref args, _) = current.kind { - if path.ident.name.as_str() == *method_name { - if args.iter().any(|e| e.span.from_expansion()) { - return None; - } - matched.push(&**args); // build up `matched` backwards - current = &args[0] // 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(LOCAL_CRATE) - .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_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); - let def_id = cx.tcx.hir().local_def_id(parent).to_def_id(); - Some(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 { - let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); - match cx.tcx.hir().find(parent_id) { - Some( - Node::Item(Item { ident, .. }) - | Node::TraitItem(TraitItem { ident, .. }) - | Node::ImplItem(ImplItem { ident, .. }), - ) => Some(ident.name), - _ => None, - } -} - -/// Gets the name of a `Pat`, if any. -pub fn get_pat_name(pat: &Pat<'_>) -> Option { - match pat.kind { - PatKind::Binding(.., ref spname, _) => Some(spname.name), - PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name), - PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p), - _ => None, - } -} - -struct ContainsName { - name: Symbol, - result: bool, -} - -impl<'tcx> Visitor<'tcx> for ContainsName { - type Map = Map<'tcx>; - - fn visit_name(&mut self, _: Span, name: Symbol) { - if self.name == name { - self.result = true; - } - } - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -/// Checks if an `Expr` contains a certain name. -pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool { - let mut cn = ContainsName { name, result: false }; - cn.visit_expr(expr); - cn.result -} - -/// Returns `true` if `expr` contains a return expression -pub fn contains_return(expr: &hir::Expr<'_>) -> bool { - struct RetCallFinder { - found: bool, - } - - impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { - if self.found { - return; - } - if let hir::ExprKind::Ret(..) = &expr.kind { - self.found = true; - } else { - hir::intravisit::walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap { - hir::intravisit::NestedVisitorMap::None - } - } - - let mut visitor = RetCallFinder { found: false }; - visitor.visit_expr(expr); - visitor.found -} - -struct FindMacroCalls<'a, 'b> { - names: &'a [&'b str], - result: Vec, -} - -impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) { - self.result.push(expr.span); - } - // and check sub-expressions - intravisit::walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -/// Finds calls of the specified macros in a function body. -pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec { - let mut fmc = FindMacroCalls { - names, - result: Vec::new(), - }; - fmc.visit_expr(&body.value); - fmc.result -} - -/// Converts a span to a code snippet if available, otherwise use default. -/// -/// This is useful if you want to provide suggestions for your lint or more generally, if you want -/// to convert a given `Span` to a `str`. -/// -/// # Example -/// ```rust,ignore -/// snippet(cx, expr.span, "..") -/// ``` -pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { - snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from) -} - -/// Same as `snippet`, but it adapts the applicability level by following rules: -/// -/// - Applicability level `Unspecified` will never be changed. -/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. -/// - If the default value is used and the applicability level is `MachineApplicable`, change it to -/// `HasPlaceholders` -pub fn snippet_with_applicability<'a, T: LintContext>( - cx: &T, - span: Span, - default: &'a str, - applicability: &mut Applicability, -) -> Cow<'a, str> { - if *applicability != Applicability::Unspecified && span.from_expansion() { - *applicability = Applicability::MaybeIncorrect; - } - snippet_opt(cx, span).map_or_else( - || { - if *applicability == Applicability::MachineApplicable { - *applicability = Applicability::HasPlaceholders; - } - Cow::Borrowed(default) - }, - From::from, - ) -} - -/// Same as `snippet`, but should only be used when it's clear that the input span is -/// not a macro argument. -pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { - snippet(cx, span.source_callsite(), default) -} - -/// Converts a span to a code snippet. Returns `None` if not available. -pub fn snippet_opt(cx: &T, span: Span) -> Option { - cx.sess().source_map().span_to_snippet(span).ok() -} - -/// Converts a span (from a block) to a code snippet if available, otherwise use default. -/// -/// This trims the code of indentation, except for the first line. Use it for blocks or block-like -/// things which need to be printed as such. -/// -/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the -/// resulting snippet of the given span. -/// -/// # Example -/// -/// ```rust,ignore -/// snippet_block(cx, block.span, "..", None) -/// // where, `block` is the block of the if expr -/// if x { -/// y; -/// } -/// // will return the snippet -/// { -/// y; -/// } -/// ``` -/// -/// ```rust,ignore -/// snippet_block(cx, block.span, "..", Some(if_expr.span)) -/// // where, `block` is the block of the if expr -/// if x { -/// y; -/// } -/// // will return the snippet -/// { -/// y; -/// } // aligned with `if` -/// ``` -/// Note that the first line of the snippet always has 0 indentation. -pub fn snippet_block<'a, T: LintContext>( - cx: &T, - span: Span, - default: &'a str, - indent_relative_to: Option, -) -> Cow<'a, str> { - let snip = snippet(cx, span, default); - let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); - reindent_multiline(snip, true, indent) -} - -/// Same as `snippet_block`, but adapts the applicability level by the rules of -/// `snippet_with_applicability`. -pub fn snippet_block_with_applicability<'a, T: LintContext>( - cx: &T, - span: Span, - default: &'a str, - indent_relative_to: Option, - applicability: &mut Applicability, -) -> Cow<'a, str> { - let snip = snippet_with_applicability(cx, span, default, applicability); - let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); - reindent_multiline(snip, true, indent) -} - -/// Returns a new Span that extends the original Span to the first non-whitespace char of the first -/// line. -/// -/// ```rust,ignore -/// let x = (); -/// // ^^ -/// // will be converted to -/// let x = (); -/// // ^^^^^^^^^^ -/// ``` -pub fn first_line_of_span(cx: &T, span: Span) -> Span { - first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos)) -} - -fn first_char_in_first_line(cx: &T, span: Span) -> Option { - let line_span = line_span(cx, span); - snippet_opt(cx, line_span).and_then(|snip| { - snip.find(|c: char| !c.is_whitespace()) - .map(|pos| line_span.lo() + BytePos::from_usize(pos)) - }) -} - -/// Returns the indentation of the line of a span -/// -/// ```rust,ignore -/// let x = (); -/// // ^^ -- will return 0 -/// let x = (); -/// // ^^ -- will return 4 -/// ``` -pub fn indent_of(cx: &T, span: Span) -> Option { - snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace())) -} - -/// Returns the positon just before rarrow -/// -/// ```rust,ignore -/// fn into(self) -> () {} -/// ^ -/// // in case of unformatted code -/// fn into2(self)-> () {} -/// ^ -/// fn into3(self) -> () {} -/// ^ -/// ``` -pub fn position_before_rarrow(s: &str) -> Option { - s.rfind("->").map(|rpos| { - let mut rpos = rpos; - let chars: Vec = s.chars().collect(); - while rpos > 1 { - if let Some(c) = chars.get(rpos - 1) { - if c.is_whitespace() { - rpos -= 1; - continue; - } - } - break; - } - rpos - }) -} - -/// Extends the span to the beginning of the spans line, incl. whitespaces. -/// -/// ```rust,ignore -/// let x = (); -/// // ^^ -/// // will be converted to -/// let x = (); -/// // ^^^^^^^^^^^^^^ -/// ``` -fn line_span(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[line_no]; - Span::new(line_start, span.hi(), span.ctxt()) -} - -/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. -/// Also takes an `Option` which can be put inside the braces. -pub fn expr_block<'a, T: LintContext>( - cx: &T, - expr: &Expr<'_>, - option: Option, - default: &'a str, - indent_relative_to: Option, -) -> Cow<'a, str> { - let code = snippet_block(cx, expr.span, default, indent_relative_to); - let string = option.unwrap_or_default(); - if expr.span.from_expansion() { - Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default))) - } else if let ExprKind::Block(_, _) = expr.kind { - Cow::Owned(format!("{}{}", code, string)) - } else if string.is_empty() { - Cow::Owned(format!("{{ {} }}", code)) - } else { - Cow::Owned(format!("{{\n{};\n{}\n}}", code, string)) - } -} - -/// Reindent a multiline string with possibility of ignoring the first line. -#[allow(clippy::needless_pass_by_value)] -pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option) -> Cow<'_, str> { - let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' '); - let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t'); - reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into() -} - -fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option, ch: char) -> String { - let x = s - .lines() - .skip(ignore_first as usize) - .filter_map(|l| { - if l.is_empty() { - None - } else { - // ignore empty lines - Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0) - } - }) - .min() - .unwrap_or(0); - let indent = indent.unwrap_or(0); - s.lines() - .enumerate() - .map(|(i, l)| { - if (ignore_first && i == 0) || l.is_empty() { - l.to_owned() - } else if x > indent { - l.split_at(x - indent).1.to_owned() - } else { - " ".repeat(indent - x) + l - } - }) - .collect::>() - .join("\n") -} - -/// 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>> { - let map = &cx.tcx.hir(); - let hir_id = e.hir_id; - let parent_id = map.get_parent_node(hir_id); - if hir_id == parent_id { - return None; - } - map.find(parent_id).and_then(|node| { - if let Node::Expr(parent) = node { - Some(parent) - } else { - None - } - }) -} - -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) - .and_then(|enclosing_id| map.find(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(ref block, _) => Some(block), - _ => None, - }, - _ => None, - }) -} - -/// Returns the base type for HIR references and pointers. -pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { - match ty.kind { - TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty), - _ => ty, - } -} - -/// Returns the base type for references and raw pointers, and count reference -/// depth. -pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { - fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { - match ty.kind() { - ty::Ref(_, ty, _) => inner(ty, depth + 1), - _ => (ty, depth), - } - } - inner(ty, 0) -} - -/// 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 map = cx.tcx.hir(); - let parent_item = map.get_parent_item(e.hir_id); - if let Some((Constant::Int(v), _)) = map - .maybe_body_owned_by(parent_item) - .and_then(|body_id| constant(cx, cx.tcx.typeck_body(body_id), e)) - { - value == v - } else { - 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(ref 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 -/// . -/// -/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::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 is 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 { - 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 -/// ```rust,ignore -/// foo!(bar!(42)); -/// ``` -/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only -/// `bar!` by -/// `is_direct_expn_of`. -#[must_use] -pub fn is_direct_expn_of(span: Span, name: &str) -> Option { - 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_item: hir::HirId) -> Ty<'tcx> { - let fn_def_id = cx.tcx.hir().local_def_id(fn_item); - let ret_ty = cx.tcx.fn_sig(fn_def_id).output(); - cx.tcx.erase_late_bound_regions(ret_ty) -} - -/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty` -pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool { - ty.walk().any(|inner| match inner.unpack() { - GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty), - GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, - }) -} - -/// Returns `true` if the given type is an `unsafe` function. -pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - match ty.kind() { - ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe, - _ => false, - } -} - -pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env) -} - -/// 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(ref fun, _) = expr.kind { - if let ExprKind::Path(ref qp) = fun.kind { - let res = cx.qpath_res(qp, fun.hir_id); - return match res { - def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true, - def::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), - def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _) - ) - } - - fn are_refutable<'a, I: Iterator>>(cx: &LateContext<'_>, mut i: I) -> bool { - i.any(|pat| is_refutable(cx, pat)) - } - - match pat.kind { - PatKind::Wild => false, - PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)), - PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat), - PatKind::Lit(..) | PatKind::Range(..) => true, - PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), - PatKind::Or(ref pats) => { - // TODO: should be the honest check, that pats is exhaustive set - are_refutable(cx, pats.iter().map(|pat| &**pat)) - }, - PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)), - PatKind::Struct(ref qpath, ref fields, _) => { - is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat)) - }, - PatKind::TupleStruct(ref qpath, ref pats, _) => { - is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat)) - }, - PatKind::Slice(ref head, ref middle, ref tail) => { - match &cx.typeck_results().node_type(pat.hir_id).kind() { - ty::Slice(..) => { - // [..] is the only irrefutable slice pattern. - !head.is_empty() || middle.is_none() || !tail.is_empty() - }, - ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)), - _ => { - // unreachable!() - true - }, - } - }, - } -} - -/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d -/// implementations have. -pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| attr.has_name(sym::automatically_derived)) -} - -/// Remove blocks around an expression. -/// -/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return -/// themselves. -pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { - while let ExprKind::Block(ref block, ..) = expr.kind { - match (block.stmts.is_empty(), block.expr.as_ref()) { - (true, Some(e)) => expr = e, - _ => break, - } - } - expr -} - -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_chain! { - if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind; - if let Res::SelfTy(..) = path.res; - then { - return true - } - } - false -} - -pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator> { - (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>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - fn is_ok(arm: &Arm<'_>) -> bool { - if_chain! { - if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind; - if match_qpath(path, &paths::RESULT_OK[1..]); - if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind; - if path_to_local_id(arm.body, hir_id); - then { - return true; - } - } - false - } - - fn is_err(arm: &Arm<'_>) -> bool { - if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind { - match_qpath(path, &paths::RESULT_ERR[1..]) - } else { - false - } - } - - if let ExprKind::Match(_, ref arms, ref source) = expr.kind { - // desugared from a `?` operator - if let MatchSource::TryDesugar = *source { - return Some(expr); - } - - if_chain! { - if arms.len() == 2; - if arms[0].guard.is_none(); - if arms[1].guard.is_none(); - if (is_ok(&arms[0]) && is_err(&arms[1])) || - (is_ok(&arms[1]) && is_err(&arms[0])); - then { - return Some(expr); - } - } - } - - None -} - -/// Returns `true` if the lint is allowed in the current context -/// -/// Useful for skipping long running code when it's unnecessary -pub fn is_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: ty::IntTy) -> u64 { - Integer::from_int_ty(&tcx, ity).size().bits() -} - -#[allow(clippy::cast_possible_wrap)] -/// Turn a constant int byte representation into an i128 -pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: ty::IntTy) -> i128 { - let amt = 128 - int_bits(tcx, ity); - ((u as i128) << amt) >> amt -} - -#[allow(clippy::cast_sign_loss)] -/// clip unused bytes -pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: ty::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: ty::UintTy) -> u128 { - let bits = Integer::from_uint_ty(&tcx, ity).size().bits(); - let amt = 128 - bits; - (u << amt) >> amt -} - -/// Removes block comments from the given `Vec` of lines. -/// -/// # Examples -/// -/// ```rust,ignore -/// without_block_comments(vec!["/*", "foo", "*/"]); -/// // => vec![] -/// -/// without_block_comments(vec!["bar", "/*", "foo", "*/"]); -/// // => vec!["bar"] -/// ``` -pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> { - let mut without = vec![]; - - let mut nest_level = 0; - - for line in lines { - if line.contains("/*") { - nest_level += 1; - continue; - } else if line.contains("*/") { - nest_level -= 1; - continue; - } - - if nest_level == 0 { - without.push(line); - } - } - - without -} - -pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { - let map = &tcx.hir(); - let mut prev_enclosing_node = None; - let mut enclosing_node = node; - while Some(enclosing_node) != prev_enclosing_node { - if is_automatically_derived(map.attrs(enclosing_node)) { - return true; - } - prev_enclosing_node = Some(enclosing_node); - enclosing_node = map.get_parent_item(enclosing_node); - } - false -} - -/// Returns true if ty has `iter` or `iter_mut` methods -pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<&'static str> { - // FIXME: instead of this hard-coded list, we should check if `::iter` - // exists and has the desired signature. Unfortunately FnCtxt is not exported - // so we can't use its `lookup_method` method. - let into_iter_collections: [&[&str]; 13] = [ - &paths::VEC, - &paths::OPTION, - &paths::RESULT, - &paths::BTREESET, - &paths::BTREEMAP, - &paths::VEC_DEQUE, - &paths::LINKED_LIST, - &paths::BINARY_HEAP, - &paths::HASHSET, - &paths::HASHMAP, - &paths::PATH_BUF, - &paths::PATH, - &paths::RECEIVER, - ]; - - let ty_to_check = match probably_ref_ty.kind() { - ty::Ref(_, ty_to_check, _) => ty_to_check, - _ => probably_ref_ty, - }; - - let def_id = match ty_to_check.kind() { - ty::Array(..) => return Some("array"), - ty::Slice(..) => return Some("slice"), - ty::Adt(adt, _) => adt.did, - _ => return None, - }; - - for path in &into_iter_collections { - if match_def_path(cx, def_id, path) { - return Some(*path.last().unwrap()); - } - } - None -} - -/// 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); -/// ``` -pub fn match_function_call<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - path: &[&str], -) -> Option<&'tcx [Expr<'tcx>]> { - if_chain! { - if let ExprKind::Call(ref fun, ref args) = expr.kind; - if let ExprKind::Path(ref qpath) = fun.kind; - if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); - if match_def_path(cx, fun_def_id, path); - then { - return Some(&args) - } - }; - None -} - -/// Checks if `Ty` is normalizable. This function is useful -/// to avoid crashes on `layout_of`. -pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool { - cx.tcx.infer_ctxt().enter(|infcx| { - let cause = rustc_middle::traits::ObligationCause::dummy(); - infcx.at(&cause, param_env).normalize(ty).is_ok() - }) -} - -pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { - // We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path` - // accepts only that. We should probably move to Symbols in Clippy as well. - let syms = syms.iter().map(|p| Symbol::intern(p)).collect::>(); - cx.match_def_path(did, &syms) -} - -pub fn match_panic_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx [Expr<'tcx>]> { - match_function_call(cx, expr, &paths::BEGIN_PANIC) - .or_else(|| match_function_call(cx, expr, &paths::BEGIN_PANIC_FMT)) - .or_else(|| match_function_call(cx, expr, &paths::PANIC_ANY)) - .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC)) - .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_FMT)) - .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_STR)) -} - -pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool { - match_def_path(cx, did, &paths::BEGIN_PANIC) - || match_def_path(cx, did, &paths::BEGIN_PANIC_FMT) - || match_def_path(cx, did, &paths::PANIC_ANY) - || match_def_path(cx, did, &paths::PANICKING_PANIC) - || match_def_path(cx, did, &paths::PANICKING_PANIC_FMT) - || match_def_path(cx, did, &paths::PANICKING_PANIC_STR) -} - -/// 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>, -) -> (SmallVec<[&'tcx Expr<'tcx>; 1]>, SmallVec<[&'tcx Block<'tcx>; 1]>) { - let mut conds = SmallVec::new(); - let mut blocks: SmallVec<[&Block<'_>; 1]> = SmallVec::new(); - - while let ExprKind::If(ref cond, ref then_expr, ref else_expr) = expr.kind { - conds.push(&**cond); - if let ExprKind::Block(ref block, _) = then_expr.kind { - blocks.push(block); - } else { - panic!("ExprKind::If node is not an ExprKind::Block"); - } - - if let Some(ref else_expr) = *else_expr { - expr = else_expr; - } else { - break; - } - } - - // final `else {..}` - if !blocks.is_empty() { - if let ExprKind::Block(ref block, _) = expr.kind { - blocks.push(&**block); - } - } - - (conds, blocks) -} - -pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { - let map = cx.tcx.hir(); - let parent_id = map.get_parent_node(expr.hir_id); - let parent_node = map.get(parent_id); - matches!( - parent_node, - Node::Expr(Expr { - kind: ExprKind::If(_, _, _), - .. - }) - ) -} - -// Finds the attribute with the given name, if any -pub fn attr_by_name<'a>(attrs: &'a [Attribute], name: &'_ str) -> Option<&'a Attribute> { - attrs - .iter() - .find(|attr| attr.ident().map_or(false, |ident| ident.as_str() == name)) -} - -// Finds the `#[must_use]` attribute, if any -pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { - attr_by_name(attrs, "must_use") -} - -// Returns whether the type has #[must_use] attribute -pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - match ty.kind() { - ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(), - ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(), - ty::Slice(ref ty) - | ty::Array(ref ty, _) - | ty::RawPtr(ty::TypeAndMut { ref ty, .. }) - | ty::Ref(_, ref ty, _) => { - // for the Array case we don't need to care for the len == 0 case - // because we don't want to lint functions returning empty arrays - is_must_use_ty(cx, *ty) - }, - ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)), - ty::Opaque(ref def_id, _) => { - for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) { - if let ty::PredicateKind::Trait(trait_predicate, _) = predicate.kind().skip_binder() { - if must_use_attr(&cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() { - return true; - } - } - } - false - }, - ty::Dynamic(binder, _) => { - for predicate in binder.iter() { - if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { - if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() { - return true; - } - } - } - false - }, - _ => false, - } -} - -// 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(ref path, _) => if_chain! { - if let ExprKind::Path(ref qpath) = path.kind; - if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id); - then { - Some(did) - } else { - None - } - }, - ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id), - _ => None, - }; - - did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some()) -} - -pub fn is_no_std_crate(krate: &Crate<'_>) -> bool { - krate.item.attrs.iter().any(|attr| { - if let ast::AttrKind::Normal(ref attr, _) = attr.kind { - attr.path == sym::no_std - } else { - false - } - }) -} - -/// Check if parent of a hir node is a trait implementation block. -/// For example, `f` in -/// ```rust,ignore -/// impl Trait for S { -/// fn f() {} -/// } -/// ``` -pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool { - if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { - matches!(item.kind, ItemKind::Impl(hir::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_predicates(cx.tcx, predicates) - .map(|o| o.predicate) - .collect::>(), - ) -} - -/// 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 { - match &expr.kind { - ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), - ExprKind::Call( - Expr { - kind: ExprKind::Path(qpath), - hir_id: path_hir_id, - .. - }, - .., - ) => cx.typeck_results().qpath_res(qpath, *path_hir_id).opt_def_id(), - _ => None, - } -} - -pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bool { - lints.iter().any(|lint| { - matches!( - cx.tcx.lint_level_at_node(lint, id), - (Level::Forbid | Level::Deny | Level::Warn, _) - ) - }) -} - -/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point -/// number type, a str, or an array, slice, or tuple of those types). -pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { - match ty.kind() { - ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, - ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true, - ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type), - ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type), - _ => false, - } -} - -/// Returns Option 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 { - let expr_type = cx.typeck_results().expr_ty_adjusted(expr); - let expr_kind = expr_type.kind(); - let is_primitive = match expr_kind { - ty::Slice(element_type) => is_recursively_primitive_type(element_type), - ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &ty::Slice(_)) => { - if let 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() { - ty::Slice(..) => return Some("slice".into()), - ty::Array(..) => return Some("array".into()), - 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) from `exprs` such that `eq(a, b)` -/// `hash` must be comformed with `eq` -pub fn search_same(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)> -where - Hash: Fn(&T) -> u64, - Eq: Fn(&T, &T) -> bool, -{ - if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) { - return vec![(&exprs[0], &exprs[1])]; - } - - let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); - - let mut map: FxHashMap<_, Vec<&_>> = - FxHashMap::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(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { - fn peel(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 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(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { - fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) { - match expr.kind { - ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target), - _ => (expr, count), - } - } - f(expr, 0, count) -} - -/// Peels off all references on the expression. Returns the underlying expression and the number of -/// references removed. -pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { - fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { - match expr.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1), - _ => (expr, count), - } - } - f(expr, 0) -} - -/// Peels off all references on the type. Returns the underlying type and the number of references -/// removed. -pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { - fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) { - if let ty::Ref(_, ty, _) = ty.kind() { - peel(ty, count + 1) - } else { - (ty, count) - } - } - peel(ty, 0) -} - -/// Peels off all references on the type.Returns the underlying type, the number of references -/// removed, and whether the pointer is ultimately mutable or not. -pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { - fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { - match ty.kind() { - ty::Ref(_, ty, Mutability::Mut) => f(ty, count + 1, mutability), - ty::Ref(_, ty, Mutability::Not) => f(ty, count + 1, Mutability::Not), - _ => (ty, count, mutability), - } - } - f(ty, 0, Mutability::Mut) -} - -#[macro_export] -macro_rules! unwrap_cargo_metadata { - ($cx: ident, $lint: ident, $deps: expr) => {{ - let mut command = cargo_metadata::MetadataCommand::new(); - if !$deps { - command.no_deps(); - } - - match command.exec() { - Ok(metadata) => metadata, - Err(err) => { - span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err)); - return; - }, - } - }}; -} - -pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { - if_chain! { - if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind; - if let Res::Def(_, def_id) = path.res; - then { - cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr) - } else { - false - } - } -} - -/// Check if the resolution of a given path is an `Ok` variant of `Result`. -pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool { - if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() { - if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { - if let Some(variant_id) = cx.tcx.parent(id) { - return variant_id == ok_id; - } - } - } - false -} - -/// Check if the resolution of a given path is a `Some` variant of `Option`. -pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool { - if let Some(some_id) = cx.tcx.lang_items().option_some_variant() { - if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { - if let Some(variant_id) = cx.tcx.parent(id) { - return variant_id == some_id; - } - } - } - false -} - -#[cfg(test)] -mod test { - use super::{reindent_multiline, without_block_comments}; - - #[test] - fn test_reindent_multiline_single_line() { - assert_eq!("", reindent_multiline("".into(), false, None)); - assert_eq!("...", reindent_multiline("...".into(), false, None)); - assert_eq!("...", reindent_multiline(" ...".into(), false, None)); - assert_eq!("...", reindent_multiline("\t...".into(), false, None)); - assert_eq!("...", reindent_multiline("\t\t...".into(), false, None)); - } - - #[test] - #[rustfmt::skip] - fn test_reindent_multiline_block() { - assert_eq!("\ - if x { - y - } else { - z - }", reindent_multiline(" if x { - y - } else { - z - }".into(), false, None)); - assert_eq!("\ - if x { - \ty - } else { - \tz - }", reindent_multiline(" if x { - \ty - } else { - \tz - }".into(), false, None)); - } - - #[test] - #[rustfmt::skip] - fn test_reindent_multiline_empty_line() { - assert_eq!("\ - if x { - y - - } else { - z - }", reindent_multiline(" if x { - y - - } else { - z - }".into(), false, None)); - } - - #[test] - #[rustfmt::skip] - fn test_reindent_multiline_lines_deeper() { - assert_eq!("\ - if x { - y - } else { - z - }", reindent_multiline("\ - if x { - y - } else { - z - }".into(), true, Some(8))); - } - - #[test] - fn test_without_block_comments_lines_without_block_comments() { - let result = without_block_comments(vec!["/*", "", "*/"]); - println!("result: {:?}", result); - assert!(result.is_empty()); - - let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]); - assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]); - - let result = without_block_comments(vec!["/* rust", "", "*/"]); - assert!(result.is_empty()); - - let result = without_block_comments(vec!["/* one-line comment */"]); - assert!(result.is_empty()); - - let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]); - assert!(result.is_empty()); - - let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]); - assert!(result.is_empty()); - - let result = without_block_comments(vec!["foo", "bar", "baz"]); - assert_eq!(result, vec!["foo", "bar", "baz"]); - } -} +pub use clippy_utils::*; diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml new file mode 100644 index 00000000000..9c01badb04c --- /dev/null +++ b/clippy_utils/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "clippy_utils" +version = "0.1.52" +authors = ["The Rust Clippy Developers"] +edition = "2018" +publish = false + +[dependencies] +if_chain = "1.0.0" +itertools = "0.9" +regex-syntax = "0.6" +serde = { version = "1.0", features = ["derive"] } +smallvec = { version = "1", features = ["union"] } +toml = "0.5.3" +unicode-normalization = "0.1" +rustc-semver="1.1.0" + +[features] +internal-lints = [] diff --git a/clippy_lints/src/utils/ast_utils.rs b/clippy_utils/src/ast_utils.rs similarity index 99% rename from clippy_lints/src/utils/ast_utils.rs rename to clippy_utils/src/ast_utils.rs index 44eb3968ae7..7ec0e103c00 100644 --- a/clippy_lints/src/utils/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -4,7 +4,7 @@ #![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)] -use crate::utils::{both, over}; +use crate::{both, over}; use rustc_ast::ptr::P; use rustc_ast::{self as ast, *}; use rustc_span::symbol::Ident; diff --git a/clippy_lints/src/utils/ast_utils/ident_iter.rs b/clippy_utils/src/ast_utils/ident_iter.rs similarity index 100% rename from clippy_lints/src/utils/ast_utils/ident_iter.rs rename to clippy_utils/src/ast_utils/ident_iter.rs diff --git a/clippy_lints/src/utils/attrs.rs b/clippy_utils/src/attrs.rs similarity index 100% rename from clippy_lints/src/utils/attrs.rs rename to clippy_utils/src/attrs.rs diff --git a/clippy_lints/src/utils/camel_case.rs b/clippy_utils/src/camel_case.rs similarity index 100% rename from clippy_lints/src/utils/camel_case.rs rename to clippy_utils/src/camel_case.rs diff --git a/clippy_lints/src/utils/comparisons.rs b/clippy_utils/src/comparisons.rs similarity index 100% rename from clippy_lints/src/utils/comparisons.rs rename to clippy_utils/src/comparisons.rs diff --git a/clippy_lints/src/utils/conf.rs b/clippy_utils/src/conf.rs similarity index 100% rename from clippy_lints/src/utils/conf.rs rename to clippy_utils/src/conf.rs diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs new file mode 100644 index 00000000000..802c01055a6 --- /dev/null +++ b/clippy_utils/src/consts.rs @@ -0,0 +1,574 @@ +#![allow(clippy::float_cmp)] + +use crate::{clip, sext, unsext}; +use if_chain::if_chain; +use rustc_ast::ast::{self, LitFloatType, LitKind}; +use rustc_data_structures::sync::Lrc; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, QPath, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::mir::interpret::Scalar; +use rustc_middle::ty::subst::{Subst, SubstsRef}; +use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt}; +use rustc_middle::{bug, span_bug}; +use rustc_span::symbol::Symbol; +use std::cmp::Ordering::{self, Equal}; +use std::convert::TryInto; +use std::hash::{Hash, Hasher}; + +/// A `LitKind`-like enum to fold constant `Expr`s into. +#[derive(Debug, Clone)] +pub enum Constant { + /// A `String` (e.g., "abc"). + Str(String), + /// A binary string (e.g., `b"abc"`). + Binary(Lrc<[u8]>), + /// A single `char` (e.g., `'a'`). + Char(char), + /// An integer's bit representation. + Int(u128), + /// An `f32`. + F32(f32), + /// An `f64`. + F64(f64), + /// `true` or `false`. + Bool(bool), + /// An array of constants. + Vec(Vec), + /// Also an array, but with only one constant, repeated N times. + Repeat(Box, u64), + /// A tuple of constants. + Tuple(Vec), + /// A raw pointer. + RawPtr(u128), + /// A reference + Ref(Box), + /// A literal with syntax error. + Err(Symbol), +} + +impl PartialEq for Constant { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => ls == rs, + (&Self::Binary(ref l), &Self::Binary(ref r)) => l == r, + (&Self::Char(l), &Self::Char(r)) => l == r, + (&Self::Int(l), &Self::Int(r)) => l == r, + (&Self::F64(l), &Self::F64(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + l.to_bits() == r.to_bits() + }, + (&Self::F32(l), &Self::F32(r)) => { + // We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have + // `Fw32 == Fw64`, so don’t compare them. + // `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs. + f64::from(l).to_bits() == f64::from(r).to_bits() + }, + (&Self::Bool(l), &Self::Bool(r)) => l == r, + (&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r, + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv, + (&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb, + // TODO: are there inter-type equalities? + _ => false, + } + } +} + +impl Hash for Constant { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + std::mem::discriminant(self).hash(state); + match *self { + Self::Str(ref s) => { + s.hash(state); + }, + Self::Binary(ref b) => { + b.hash(state); + }, + Self::Char(c) => { + c.hash(state); + }, + Self::Int(i) => { + i.hash(state); + }, + Self::F32(f) => { + f64::from(f).to_bits().hash(state); + }, + Self::F64(f) => { + f.to_bits().hash(state); + }, + Self::Bool(b) => { + b.hash(state); + }, + Self::Vec(ref v) | Self::Tuple(ref v) => { + v.hash(state); + }, + Self::Repeat(ref c, l) => { + c.hash(state); + l.hash(state); + }, + Self::RawPtr(u) => { + u.hash(state); + }, + Self::Ref(ref r) => { + r.hash(state); + }, + Self::Err(ref s) => { + s.hash(state); + }, + } + } +} + +impl Constant { + pub fn partial_cmp(tcx: TyCtxt<'_>, cmp_type: Ty<'_>, left: &Self, right: &Self) -> Option { + match (left, right) { + (&Self::Str(ref ls), &Self::Str(ref rs)) => Some(ls.cmp(rs)), + (&Self::Char(ref l), &Self::Char(ref r)) => Some(l.cmp(r)), + (&Self::Int(l), &Self::Int(r)) => { + if let ty::Int(int_ty) = *cmp_type.kind() { + Some(sext(tcx, l, int_ty).cmp(&sext(tcx, r, int_ty))) + } else { + Some(l.cmp(&r)) + } + }, + (&Self::F64(l), &Self::F64(r)) => l.partial_cmp(&r), + (&Self::F32(l), &Self::F32(r)) => l.partial_cmp(&r), + (&Self::Bool(ref l), &Self::Bool(ref r)) => Some(l.cmp(r)), + (&Self::Tuple(ref l), &Self::Tuple(ref r)) | (&Self::Vec(ref l), &Self::Vec(ref r)) => l + .iter() + .zip(r.iter()) + .map(|(li, ri)| Self::partial_cmp(tcx, cmp_type, li, ri)) + .find(|r| r.map_or(true, |o| o != Ordering::Equal)) + .unwrap_or_else(|| Some(l.len().cmp(&r.len()))), + (&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => { + match Self::partial_cmp(tcx, cmp_type, lv, rv) { + Some(Equal) => Some(ls.cmp(rs)), + x => x, + } + }, + (&Self::Ref(ref lb), &Self::Ref(ref rb)) => Self::partial_cmp(tcx, cmp_type, lb, rb), + // TODO: are there any useful inter-type orderings? + _ => None, + } + } +} + +/// Parses a `LitKind` to a `Constant`. +pub fn lit_to_constant(lit: &LitKind, ty: Option>) -> Constant { + match *lit { + LitKind::Str(ref is, _) => Constant::Str(is.to_string()), + LitKind::Byte(b) => Constant::Int(u128::from(b)), + LitKind::ByteStr(ref s) => Constant::Binary(Lrc::clone(s)), + LitKind::Char(c) => Constant::Char(c), + LitKind::Int(n, _) => Constant::Int(n), + LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty { + ast::FloatTy::F32 => Constant::F32(is.as_str().parse().unwrap()), + ast::FloatTy::F64 => Constant::F64(is.as_str().parse().unwrap()), + }, + LitKind::Float(ref is, LitFloatType::Unsuffixed) => match ty.expect("type of float is known").kind() { + ty::Float(FloatTy::F32) => Constant::F32(is.as_str().parse().unwrap()), + ty::Float(FloatTy::F64) => Constant::F64(is.as_str().parse().unwrap()), + _ => bug!(), + }, + LitKind::Bool(b) => Constant::Bool(b), + LitKind::Err(s) => Constant::Err(s), + } +} + +pub fn constant<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option<(Constant, bool)> { + let mut cx = ConstEvalLateContext { + lcx, + typeck_results, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + }; + cx.expr(e).map(|cst| (cst, cx.needed_resolution)) +} + +pub fn constant_simple<'tcx>( + lcx: &LateContext<'tcx>, + typeck_results: &ty::TypeckResults<'tcx>, + e: &Expr<'_>, +) -> Option { + constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) }) +} + +/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`. +pub fn constant_context<'a, 'tcx>( + lcx: &'a LateContext<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, +) -> ConstEvalLateContext<'a, 'tcx> { + ConstEvalLateContext { + lcx, + typeck_results, + param_env: lcx.param_env, + needed_resolution: false, + substs: lcx.tcx.intern_substs(&[]), + } +} + +pub struct ConstEvalLateContext<'a, 'tcx> { + lcx: &'a LateContext<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, + param_env: ty::ParamEnv<'tcx>, + needed_resolution: bool, + substs: SubstsRef<'tcx>, +} + +impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { + /// Simple constant folding: Insert an expression, get a constant or none. + pub fn expr(&mut self, e: &Expr<'_>) -> Option { + match e.kind { + ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), + ExprKind::Block(ref block, _) => self.block(block), + ExprKind::Lit(ref lit) => Some(lit_to_constant(&lit.node, self.typeck_results.expr_ty_opt(e))), + ExprKind::Array(ref vec) => self.multi(vec).map(Constant::Vec), + ExprKind::Tup(ref tup) => self.multi(tup).map(Constant::Tuple), + ExprKind::Repeat(ref value, _) => { + let n = match self.typeck_results.expr_ty(e).kind() { + ty::Array(_, n) => n.try_eval_usize(self.lcx.tcx, self.lcx.param_env)?, + _ => span_bug!(e.span, "typeck error"), + }; + self.expr(value).map(|v| Constant::Repeat(Box::new(v), n)) + }, + ExprKind::Unary(op, ref operand) => self.expr(operand).and_then(|o| match op { + UnOp::Not => self.constant_not(&o, self.typeck_results.expr_ty(e)), + UnOp::Neg => self.constant_negate(&o, self.typeck_results.expr_ty(e)), + UnOp::Deref => Some(if let Constant::Ref(r) = o { *r } else { o }), + }), + ExprKind::If(ref cond, ref then, ref otherwise) => self.ifthenelse(cond, then, *otherwise), + ExprKind::Binary(op, ref left, ref right) => self.binop(op, left, right), + ExprKind::Call(ref callee, ref args) => { + // We only handle a few const functions for now. + if_chain! { + if args.is_empty(); + if let ExprKind::Path(qpath) = &callee.kind; + let res = self.typeck_results.qpath_res(qpath, callee.hir_id); + if let Some(def_id) = res.opt_def_id(); + let def_path: Vec<_> = self.lcx.get_def_path(def_id).into_iter().map(Symbol::as_str).collect(); + let def_path: Vec<&str> = def_path.iter().take(4).map(|s| &**s).collect(); + if let ["core", "num", int_impl, "max_value"] = *def_path; + then { + let value = match int_impl { + "" => i8::MAX as u128, + "" => i16::MAX as u128, + "" => i32::MAX as u128, + "" => i64::MAX as u128, + "" => i128::MAX as u128, + _ => return None, + }; + Some(Constant::Int(value)) + } + else { + None + } + } + }, + ExprKind::Index(ref arr, ref index) => self.index(arr, index), + ExprKind::AddrOf(_, _, ref inner) => self.expr(inner).map(|r| Constant::Ref(Box::new(r))), + // TODO: add other expressions. + _ => None, + } + } + + #[allow(clippy::cast_possible_wrap)] + fn constant_not(&self, o: &Constant, ty: Ty<'_>) -> Option { + use self::Constant::{Bool, Int}; + match *o { + Bool(b) => Some(Bool(!b)), + Int(value) => { + let value = !value; + match *ty.kind() { + ty::Int(ity) => Some(Int(unsext(self.lcx.tcx, value as i128, ity))), + ty::Uint(ity) => Some(Int(clip(self.lcx.tcx, value, ity))), + _ => None, + } + }, + _ => None, + } + } + + fn constant_negate(&self, o: &Constant, ty: Ty<'_>) -> Option { + use self::Constant::{Int, F32, F64}; + match *o { + Int(value) => { + let ity = match *ty.kind() { + ty::Int(ity) => ity, + _ => return None, + }; + // sign extend + let value = sext(self.lcx.tcx, value, ity); + let value = value.checked_neg()?; + // clear unused bits + Some(Int(unsext(self.lcx.tcx, value, ity))) + }, + F32(f) => Some(F32(-f)), + F64(f) => Some(F64(-f)), + _ => None, + } + } + + /// Create `Some(Vec![..])` of all constants, unless there is any + /// non-constant part. + fn multi(&mut self, vec: &[Expr<'_>]) -> Option> { + vec.iter().map(|elem| self.expr(elem)).collect::>() + } + + /// Lookup a possibly constant expression from a `ExprKind::Path`. + fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option { + let res = self.typeck_results.qpath_res(qpath, id); + match res { + Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { + let substs = self.typeck_results.node_substs(id); + let substs = if self.substs.is_empty() { + substs + } else { + substs.subst(self.lcx.tcx, self.substs) + }; + + let result = self + .lcx + .tcx + .const_eval_resolve( + self.param_env, + ty::WithOptConstParam::unknown(def_id), + substs, + None, + None, + ) + .ok() + .map(|val| rustc_middle::ty::Const::from_value(self.lcx.tcx, val, ty))?; + let result = miri_to_const(&result); + if result.is_some() { + self.needed_resolution = true; + } + result + }, + // FIXME: cover all usable cases. + _ => None, + } + } + + fn index(&mut self, lhs: &'_ Expr<'_>, index: &'_ Expr<'_>) -> Option { + let lhs = self.expr(lhs); + let index = self.expr(index); + + match (lhs, index) { + (Some(Constant::Vec(vec)), Some(Constant::Int(index))) => match vec.get(index as usize) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + }, + (Some(Constant::Vec(vec)), _) => { + if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { + match vec.get(0) { + Some(Constant::F32(x)) => Some(Constant::F32(*x)), + Some(Constant::F64(x)) => Some(Constant::F64(*x)), + _ => None, + } + } else { + None + } + }, + _ => None, + } + } + + /// A block can only yield a constant if it only has one constant expression. + fn block(&mut self, block: &Block<'_>) -> Option { + if block.stmts.is_empty() { + block.expr.as_ref().and_then(|b| self.expr(b)) + } else { + None + } + } + + fn ifthenelse(&mut self, cond: &Expr<'_>, then: &Expr<'_>, otherwise: Option<&Expr<'_>>) -> Option { + if let Some(Constant::Bool(b)) = self.expr(cond) { + if b { + self.expr(&*then) + } else { + otherwise.as_ref().and_then(|expr| self.expr(expr)) + } + } else { + None + } + } + + fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option { + let l = self.expr(left)?; + let r = self.expr(right); + match (l, r) { + (Constant::Int(l), Some(Constant::Int(r))) => match *self.typeck_results.expr_ty_opt(left)?.kind() { + ty::Int(ity) => { + let l = sext(self.lcx.tcx, l, ity); + let r = sext(self.lcx.tcx, r, ity); + let zext = |n: i128| Constant::Int(unsext(self.lcx.tcx, n, ity)); + match op.node { + BinOpKind::Add => l.checked_add(r).map(zext), + BinOpKind::Sub => l.checked_sub(r).map(zext), + BinOpKind::Mul => l.checked_mul(r).map(zext), + BinOpKind::Div if r != 0 => l.checked_div(r).map(zext), + BinOpKind::Rem if r != 0 => l.checked_rem(r).map(zext), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("invalid shift")).map(zext), + BinOpKind::BitXor => Some(zext(l ^ r)), + BinOpKind::BitOr => Some(zext(l | r)), + BinOpKind::BitAnd => Some(zext(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + } + }, + ty::Uint(_) => match op.node { + BinOpKind::Add => l.checked_add(r).map(Constant::Int), + BinOpKind::Sub => l.checked_sub(r).map(Constant::Int), + BinOpKind::Mul => l.checked_mul(r).map(Constant::Int), + BinOpKind::Div => l.checked_div(r).map(Constant::Int), + BinOpKind::Rem => l.checked_rem(r).map(Constant::Int), + BinOpKind::Shr => l.checked_shr(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::Shl => l.checked_shl(r.try_into().expect("shift too large")).map(Constant::Int), + BinOpKind::BitXor => Some(Constant::Int(l ^ r)), + BinOpKind::BitOr => Some(Constant::Int(l | r)), + BinOpKind::BitAnd => Some(Constant::Int(l & r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + _ => None, + }, + (Constant::F32(l), Some(Constant::F32(r))) => match op.node { + BinOpKind::Add => Some(Constant::F32(l + r)), + BinOpKind::Sub => Some(Constant::F32(l - r)), + BinOpKind::Mul => Some(Constant::F32(l * r)), + BinOpKind::Div => Some(Constant::F32(l / r)), + BinOpKind::Rem => Some(Constant::F32(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (Constant::F64(l), Some(Constant::F64(r))) => match op.node { + BinOpKind::Add => Some(Constant::F64(l + r)), + BinOpKind::Sub => Some(Constant::F64(l - r)), + BinOpKind::Mul => Some(Constant::F64(l * r)), + BinOpKind::Div => Some(Constant::F64(l / r)), + BinOpKind::Rem => Some(Constant::F64(l % r)), + BinOpKind::Eq => Some(Constant::Bool(l == r)), + BinOpKind::Ne => Some(Constant::Bool(l != r)), + BinOpKind::Lt => Some(Constant::Bool(l < r)), + BinOpKind::Le => Some(Constant::Bool(l <= r)), + BinOpKind::Ge => Some(Constant::Bool(l >= r)), + BinOpKind::Gt => Some(Constant::Bool(l > r)), + _ => None, + }, + (l, r) => match (op.node, l, r) { + (BinOpKind::And, Constant::Bool(false), _) => Some(Constant::Bool(false)), + (BinOpKind::Or, Constant::Bool(true), _) => Some(Constant::Bool(true)), + (BinOpKind::And, Constant::Bool(true), Some(r)) | (BinOpKind::Or, Constant::Bool(false), Some(r)) => { + Some(r) + }, + (BinOpKind::BitXor, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l ^ r)), + (BinOpKind::BitAnd, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l & r)), + (BinOpKind::BitOr, Constant::Bool(l), Some(Constant::Bool(r))) => Some(Constant::Bool(l | r)), + _ => None, + }, + } + } +} + +pub fn miri_to_const(result: &ty::Const<'_>) -> Option { + use rustc_middle::mir::interpret::ConstValue; + match result.val { + ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => { + match result.ty.kind() { + ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), + ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), + ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( + int.try_into().expect("invalid f32 bit representation"), + ))), + ty::Float(FloatTy::F64) => Some(Constant::F64(f64::from_bits( + int.try_into().expect("invalid f64 bit representation"), + ))), + ty::RawPtr(type_and_mut) => { + if let ty::Uint(_) = type_and_mut.ty.kind() { + return Some(Constant::RawPtr(int.assert_bits(int.size()))); + } + None + }, + // FIXME: implement other conversions. + _ => None, + } + }, + ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() { + ty::Ref(_, tam, _) => match tam.kind() { + ty::Str => String::from_utf8( + data.inspect_with_uninit_and_ptr_outside_interpreter(start..end) + .to_owned(), + ) + .ok() + .map(Constant::Str), + _ => None, + }, + _ => None, + }, + ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() { + ty::Array(sub_type, len) => match sub_type.kind() { + ty::Float(FloatTy::F32) => match miri_to_const(len) { + Some(Constant::Int(len)) => alloc + .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) + .to_owned() + .chunks(4) + .map(|chunk| { + Some(Constant::F32(f32::from_le_bytes( + chunk.try_into().expect("this shouldn't happen"), + ))) + }) + .collect::>>() + .map(Constant::Vec), + _ => None, + }, + ty::Float(FloatTy::F64) => match miri_to_const(len) { + Some(Constant::Int(len)) => alloc + .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) + .to_owned() + .chunks(8) + .map(|chunk| { + Some(Constant::F64(f64::from_le_bytes( + chunk.try_into().expect("this shouldn't happen"), + ))) + }) + .collect::>>() + .map(Constant::Vec), + _ => None, + }, + // FIXME: implement other array type conversions. + _ => None, + }, + _ => None, + }, + // FIXME: implement other conversions. + _ => None, + } +} diff --git a/clippy_lints/src/utils/diagnostics.rs b/clippy_utils/src/diagnostics.rs similarity index 100% rename from clippy_lints/src/utils/diagnostics.rs rename to clippy_utils/src/diagnostics.rs diff --git a/clippy_lints/src/utils/eager_or_lazy.rs b/clippy_utils/src/eager_or_lazy.rs similarity index 97% rename from clippy_lints/src/utils/eager_or_lazy.rs rename to clippy_utils/src/eager_or_lazy.rs index 2f157c5030f..52a33e9b170 100644 --- a/clippy_lints/src/utils/eager_or_lazy.rs +++ b/clippy_utils/src/eager_or_lazy.rs @@ -9,7 +9,7 @@ //! - or-fun-call //! - option-if-let-else -use crate::utils::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths}; +use crate::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit; diff --git a/clippy_lints/src/utils/higher.rs b/clippy_utils/src/higher.rs similarity index 99% rename from clippy_lints/src/utils/higher.rs rename to clippy_utils/src/higher.rs index 1cf1aa363d5..be22df7109a 100644 --- a/clippy_lints/src/utils/higher.rs +++ b/clippy_utils/src/higher.rs @@ -3,7 +3,7 @@ #![deny(clippy::missing_docs_in_private_items)] -use crate::utils::{is_expn_of, match_def_path, paths}; +use crate::{is_expn_of, match_def_path, paths}; use if_chain::if_chain; use rustc_ast::ast; use rustc_hir as hir; diff --git a/clippy_lints/src/utils/hir_utils.rs b/clippy_utils/src/hir_utils.rs similarity index 99% rename from clippy_lints/src/utils/hir_utils.rs rename to clippy_utils/src/hir_utils.rs index c042990b759..81be9254cbe 100644 --- a/clippy_lints/src/utils/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1,5 +1,5 @@ use crate::consts::{constant_context, constant_simple}; -use crate::utils::differing_macro_contexts; +use crate::differing_macro_contexts; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs new file mode 100644 index 00000000000..5e0f0f084cb --- /dev/null +++ b/clippy_utils/src/lib.rs @@ -0,0 +1,1886 @@ +#![feature(box_patterns)] +#![feature(in_band_lifetimes)] +#![feature(once_cell)] +#![feature(or_patterns)] +#![feature(rustc_private)] +#![recursion_limit = "512"] +#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] + +// 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_data_structures; +extern crate rustc_errors; +extern crate rustc_hir; +extern crate rustc_hir_pretty; +extern crate rustc_infer; +extern crate rustc_lint; +extern crate rustc_middle; +extern crate rustc_mir; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; +extern crate rustc_trait_selection; +extern crate rustc_typeck; + +#[macro_use] +pub mod sym_helper; + +#[allow(clippy::module_name_repetitions)] +pub mod ast_utils; +pub mod attrs; +pub mod camel_case; +pub mod comparisons; +pub mod conf; +pub mod consts; +mod diagnostics; +pub mod eager_or_lazy; +pub mod higher; +mod hir_utils; +pub mod numeric_literal; +pub mod paths; +pub mod ptr; +pub mod qualify_min_const_fn; +pub mod sugg; +pub mod usage; +pub mod visitors; + +pub use self::attrs::*; +pub use self::diagnostics::*; +pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash}; + +use std::borrow::Cow; +use std::collections::hash_map::Entry; +use std::hash::BuildHasherDefault; + +use if_chain::if_chain; +use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind, Mutability}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; +use rustc_hir::Node; +use rustc_hir::{ + def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, HirId, ImplItem, ImplItemKind, Item, ItemKind, + MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind, Unsafety, +}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::{LateContext, Level, Lint, LintContext}; +use rustc_middle::hir::exports::Export; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; +use rustc_middle::ty::{self, layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable}; +use rustc_semver::RustcVersion; +use rustc_session::Session; +use rustc_span::hygiene::{ExpnKind, MacroKind}; +use rustc_span::source_map::original_sp; +use rustc_span::sym; +use rustc_span::symbol::{kw, Symbol}; +use rustc_span::{BytePos, Pos, Span, DUMMY_SP}; +use rustc_target::abi::Integer; +use rustc_trait_selection::traits::query::normalize::AtExt; +use smallvec::SmallVec; + +use crate::consts::{constant, Constant}; + +pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { + 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!("`{}` is not a valid Rust version", msrv)); + } + } + 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 { + (LateContext) => { + extract_msrv_attr!(@LateContext, ()); + }; + (EarlyContext) => { + extract_msrv_attr!(@EarlyContext); + }; + (@$context:ident$(, $call:tt)?) => { + fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) { + use $crate::get_unique_inner_attr; + match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") { + Some(msrv_attr) => { + if let Some(msrv) = msrv_attr.value_str() { + self.msrv = $crate::parse_msrv( + &msrv.to_string(), + Some(cx.sess$($call)?), + Some(msrv_attr.span), + ); + } else { + cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute"); + } + }, + _ => (), + } + } + }; +} + +/// Returns `true` if the two spans come from differing expansions (i.e., one is +/// from a macro and one isn't). +#[must_use] +pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool { + rhs.ctxt() != lhs.ctxt() +} + +/// Returns `true` if the given `NodeId` is inside a constant context +/// +/// # Example +/// +/// ```rust,ignore +/// if in_constant(cx, expr.hir_id) { +/// // Do something +/// } +/// ``` +pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool { + let parent_id = cx.tcx.hir().get_parent_item(id); + match cx.tcx.hir().get(parent_id) { + Node::Item(&Item { + kind: ItemKind::Const(..) | ItemKind::Static(..), + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + .. + }) + | Node::AnonConst(_) => true, + Node::Item(&Item { + kind: ItemKind::Fn(ref sig, ..), + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(ref sig, _), + .. + }) => sig.header.constness == Constness::Const, + _ => false, + } +} + +/// Returns `true` if this `span` was expanded by any macro. +#[must_use] +pub fn in_macro(span: Span) -> bool { + if span.from_expansion() { + !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..)) + } else { + false + } +} + +// If the snippet is empty, it's an attribute that was inserted during macro +// expansion and we want to ignore those, because they could come from external +// sources that the user has no control over. +// For some reason these attributes don't have any expansion info on them, so +// we have to check it this way until there is a better way. +pub fn is_present_in_source(cx: &T, span: Span) -> bool { + if let Some(snippet) = snippet_opt(cx, span) { + if snippet.is_empty() { + return false; + } + } + true +} + +/// Checks if given pattern is a wildcard (`_`) +pub fn is_wild<'tcx>(pat: &impl std::ops::Deref>) -> bool { + matches!(pat.kind, PatKind::Wild) +} + +/// Checks if type is struct, enum or union type with the given def path. +/// +/// If the type is a diagnostic item, use `is_type_diagnostic_item` instead. +/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` +pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool { + match ty.kind() { + ty::Adt(adt, _) => match_def_path(cx, adt.did, path), + _ => false, + } +} + +/// Checks if the type is equal to a diagnostic item +/// +/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem` +pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool { + match ty.kind() { + ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did), + _ => false, + } +} + +/// Checks if the type is equal to a lang item +pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool { + match ty.kind() { + ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).unwrap() == adt.did, + _ => false, + } +} + +/// Checks if the method call given in `expr` belongs to the given trait. +pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool { + let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + let trt_id = cx.tcx.trait_of_item(def_id); + trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path)) +} + +/// Checks if an expression references a variable of the given name. +pub fn match_var(expr: &Expr<'_>, var: Symbol) -> bool { + if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind { + if let [p] = path.segments { + return p.ident.name == var; + } + } + false +} + +pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { + match *path { + QPath::Resolved(_, ref path) => path.segments.last().expect("A path must have at least one segment"), + QPath::TypeRelative(_, ref seg) => seg, + QPath::LangItem(..) => panic!("last_path_segment: lang item has no path segments"), + } +} + +pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { + match *path { + QPath::Resolved(_, ref path) => path.segments.get(0), + QPath::TypeRelative(_, ref seg) => Some(seg), + QPath::LangItem(..) => None, + } +} + +/// 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(_, ref path) => match_path(path, segments), + QPath::TypeRelative(ref ty, ref 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, + } +} + +/// 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) +} + +/// Matches a `Path` against a slice of segment string literals, e.g. +/// +/// # Examples +/// ```rust,ignore +/// match_path_ast(path, &["std", "rt", "begin_unwind"]) +/// ``` +pub fn match_path_ast(path: &ast::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 { + if let ExprKind::Path(QPath::Resolved(None, ref 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) +} + +/// Gets the definition associated to a path. +#[allow(clippy::shadow_unrelated)] // false positive #6563 +pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res { + macro_rules! try_res { + ($e:expr) => { + match $e { + Some(e) => e, + None => return Res::Err, + } + }; + } + fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export> { + tcx.item_children(def_id) + .iter() + .find(|item| item.ident.name.as_str() == name) + } + + let (krate, first, path) = match *path { + [krate, first, ref path @ ..] => (krate, first, path), + _ => return Res::Err, + }; + let tcx = cx.tcx; + let crates = tcx.crates(); + let krate = try_res!(crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate)); + let first = try_res!(item_child_by_name(tcx, krate.as_def_id(), first)); + let last = path + .iter() + .copied() + // `get_def_path` seems to generate these empty segments for extern blocks. + // We can just ignore them. + .filter(|segment| !segment.is_empty()) + // for each segment, find the child item + .try_fold(first, |item, segment| { + let def_id = item.res.def_id(); + if let Some(item) = item_child_by_name(tcx, def_id, segment) { + Some(item) + } else if matches!(item.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)) + } else { + None + } + }); + try_res!(last).res +} + +/// Convenience function to get the `DefId` of a trait by path. +/// It could be a trait or trait alias. +pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option { + match path_to_res(cx, path) { + Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), + _ => None, + } +} + +/// Checks whether a type implements a trait. +/// See also `get_trait_def_id`. +pub fn implements_trait<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + trait_id: DefId, + ty_params: &[GenericArg<'tcx>], +) -> bool { + // Do not check on infer_types to avoid panic in evaluate_obligation. + if ty.has_infer_types() { + return false; + } + let ty = cx.tcx.erase_regions(ty); + if ty.has_escaping_bound_vars() { + return false; + } + let ty_params = cx.tcx.mk_substs(ty_params.iter()); + cx.tcx.type_implements_trait((trait_id, ty, ty_params, cx.param_env)) +} + +/// 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: +/// +/// ```rust +/// 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>, hir_id: HirId) -> Option<&'tcx TraitRef<'tcx>> { + // Get the implemented trait for the current function + let parent_impl = cx.tcx.hir().get_parent_item(hir_id); + if_chain! { + if parent_impl != hir::CRATE_HIR_ID; + if let hir::Node::Item(item) = cx.tcx.hir().get(parent_impl); + if let hir::ItemKind::Impl(impl_) = &item.kind; + then { return impl_.of_trait.as_ref(); } + } + None +} + +/// Checks whether this type implements `Drop`. +pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.ty_adt_def() { + Some(def) => def.has_dtor(cx.tcx), + None => false, + } +} + +/// 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, Vec<&'tcx [Expr<'tcx>]>, Vec) { + 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, span, args, _) = ¤t.kind { + if args.iter().any(|e| e.span.from_expansion()) { + break; + } + method_names.push(path.ident.name); + arg_lists.push(&**args); + spans.push(*span); + current = &args[0]; + } 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]>> { + 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(ref path, _, ref args, _) = current.kind { + if path.ident.name.as_str() == *method_name { + if args.iter().any(|e| e.span.from_expansion()) { + return None; + } + matched.push(&**args); // build up `matched` backwards + current = &args[0] // 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(LOCAL_CRATE) + .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id.to_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); + let def_id = cx.tcx.hir().local_def_id(parent).to_def_id(); + Some(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 { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + match cx.tcx.hir().find(parent_id) { + Some( + Node::Item(Item { ident, .. }) + | Node::TraitItem(TraitItem { ident, .. }) + | Node::ImplItem(ImplItem { ident, .. }), + ) => Some(ident.name), + _ => None, + } +} + +/// Gets the name of a `Pat`, if any. +pub fn get_pat_name(pat: &Pat<'_>) -> Option { + match pat.kind { + PatKind::Binding(.., ref spname, _) => Some(spname.name), + PatKind::Path(ref qpath) => single_segment_path(qpath).map(|ps| ps.ident.name), + PatKind::Box(ref p) | PatKind::Ref(ref p, _) => get_pat_name(&*p), + _ => None, + } +} + +struct ContainsName { + name: Symbol, + result: bool, +} + +impl<'tcx> Visitor<'tcx> for ContainsName { + type Map = Map<'tcx>; + + fn visit_name(&mut self, _: Span, name: Symbol) { + if self.name == name { + self.result = true; + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Checks if an `Expr` contains a certain name. +pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool { + let mut cn = ContainsName { name, result: false }; + cn.visit_expr(expr); + cn.result +} + +/// Returns `true` if `expr` contains a return expression +pub fn contains_return(expr: &hir::Expr<'_>) -> bool { + struct RetCallFinder { + found: bool, + } + + impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { + if self.found { + return; + } + if let hir::ExprKind::Ret(..) = &expr.kind { + self.found = true; + } else { + hir::intravisit::walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap { + hir::intravisit::NestedVisitorMap::None + } + } + + let mut visitor = RetCallFinder { found: false }; + visitor.visit_expr(expr); + visitor.found +} + +struct FindMacroCalls<'a, 'b> { + names: &'a [&'b str], + result: Vec, +} + +impl<'a, 'b, 'tcx> Visitor<'tcx> for FindMacroCalls<'a, 'b> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.names.iter().any(|fun| is_expn_of(expr.span, fun).is_some()) { + self.result.push(expr.span); + } + // and check sub-expressions + intravisit::walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Finds calls of the specified macros in a function body. +pub fn find_macro_calls(names: &[&str], body: &Body<'_>) -> Vec { + let mut fmc = FindMacroCalls { + names, + result: Vec::new(), + }; + fmc.visit_expr(&body.value); + fmc.result +} + +/// Converts a span to a code snippet if available, otherwise use default. +/// +/// This is useful if you want to provide suggestions for your lint or more generally, if you want +/// to convert a given `Span` to a `str`. +/// +/// # Example +/// ```rust,ignore +/// snippet(cx, expr.span, "..") +/// ``` +pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { + snippet_opt(cx, span).map_or_else(|| Cow::Borrowed(default), From::from) +} + +/// Same as `snippet`, but it adapts the applicability level by following rules: +/// +/// - Applicability level `Unspecified` will never be changed. +/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. +/// - If the default value is used and the applicability level is `MachineApplicable`, change it to +/// `HasPlaceholders` +pub fn snippet_with_applicability<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + applicability: &mut Applicability, +) -> Cow<'a, str> { + if *applicability != Applicability::Unspecified && span.from_expansion() { + *applicability = Applicability::MaybeIncorrect; + } + snippet_opt(cx, span).map_or_else( + || { + if *applicability == Applicability::MachineApplicable { + *applicability = Applicability::HasPlaceholders; + } + Cow::Borrowed(default) + }, + From::from, + ) +} + +/// Same as `snippet`, but should only be used when it's clear that the input span is +/// not a macro argument. +pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { + snippet(cx, span.source_callsite(), default) +} + +/// Converts a span to a code snippet. Returns `None` if not available. +pub fn snippet_opt(cx: &T, span: Span) -> Option { + cx.sess().source_map().span_to_snippet(span).ok() +} + +/// Converts a span (from a block) to a code snippet if available, otherwise use default. +/// +/// This trims the code of indentation, except for the first line. Use it for blocks or block-like +/// things which need to be printed as such. +/// +/// The `indent_relative_to` arg can be used, to provide a span, where the indentation of the +/// resulting snippet of the given span. +/// +/// # Example +/// +/// ```rust,ignore +/// snippet_block(cx, block.span, "..", None) +/// // where, `block` is the block of the if expr +/// if x { +/// y; +/// } +/// // will return the snippet +/// { +/// y; +/// } +/// ``` +/// +/// ```rust,ignore +/// snippet_block(cx, block.span, "..", Some(if_expr.span)) +/// // where, `block` is the block of the if expr +/// if x { +/// y; +/// } +/// // will return the snippet +/// { +/// y; +/// } // aligned with `if` +/// ``` +/// Note that the first line of the snippet always has 0 indentation. +pub fn snippet_block<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + indent_relative_to: Option, +) -> Cow<'a, str> { + let snip = snippet(cx, span, default); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + reindent_multiline(snip, true, indent) +} + +/// Same as `snippet_block`, but adapts the applicability level by the rules of +/// `snippet_with_applicability`. +pub fn snippet_block_with_applicability<'a, T: LintContext>( + cx: &T, + span: Span, + default: &'a str, + indent_relative_to: Option, + applicability: &mut Applicability, +) -> Cow<'a, str> { + let snip = snippet_with_applicability(cx, span, default, applicability); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + reindent_multiline(snip, true, indent) +} + +/// Returns a new Span that extends the original Span to the first non-whitespace char of the first +/// line. +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ +/// // will be converted to +/// let x = (); +/// // ^^^^^^^^^^ +/// ``` +pub fn first_line_of_span(cx: &T, span: Span) -> Span { + first_char_in_first_line(cx, span).map_or(span, |first_char_pos| span.with_lo(first_char_pos)) +} + +fn first_char_in_first_line(cx: &T, span: Span) -> Option { + let line_span = line_span(cx, span); + snippet_opt(cx, line_span).and_then(|snip| { + snip.find(|c: char| !c.is_whitespace()) + .map(|pos| line_span.lo() + BytePos::from_usize(pos)) + }) +} + +/// Returns the indentation of the line of a span +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ -- will return 0 +/// let x = (); +/// // ^^ -- will return 4 +/// ``` +pub fn indent_of(cx: &T, span: Span) -> Option { + snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace())) +} + +/// Returns the positon just before rarrow +/// +/// ```rust,ignore +/// fn into(self) -> () {} +/// ^ +/// // in case of unformatted code +/// fn into2(self)-> () {} +/// ^ +/// fn into3(self) -> () {} +/// ^ +/// ``` +pub fn position_before_rarrow(s: &str) -> Option { + s.rfind("->").map(|rpos| { + let mut rpos = rpos; + let chars: Vec = s.chars().collect(); + while rpos > 1 { + if let Some(c) = chars.get(rpos - 1) { + if c.is_whitespace() { + rpos -= 1; + continue; + } + } + break; + } + rpos + }) +} + +/// Extends the span to the beginning of the spans line, incl. whitespaces. +/// +/// ```rust,ignore +/// let x = (); +/// // ^^ +/// // will be converted to +/// let x = (); +/// // ^^^^^^^^^^^^^^ +/// ``` +fn line_span(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[line_no]; + Span::new(line_start, span.hi(), span.ctxt()) +} + +/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. +/// Also takes an `Option` which can be put inside the braces. +pub fn expr_block<'a, T: LintContext>( + cx: &T, + expr: &Expr<'_>, + option: Option, + default: &'a str, + indent_relative_to: Option, +) -> Cow<'a, str> { + let code = snippet_block(cx, expr.span, default, indent_relative_to); + let string = option.unwrap_or_default(); + if expr.span.from_expansion() { + Cow::Owned(format!("{{ {} }}", snippet_with_macro_callsite(cx, expr.span, default))) + } else if let ExprKind::Block(_, _) = expr.kind { + Cow::Owned(format!("{}{}", code, string)) + } else if string.is_empty() { + Cow::Owned(format!("{{ {} }}", code)) + } else { + Cow::Owned(format!("{{\n{};\n{}\n}}", code, string)) + } +} + +/// Reindent a multiline string with possibility of ignoring the first line. +#[allow(clippy::needless_pass_by_value)] +pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option) -> Cow<'_, str> { + let s_space = reindent_multiline_inner(&s, ignore_first, indent, ' '); + let s_tab = reindent_multiline_inner(&s_space, ignore_first, indent, '\t'); + reindent_multiline_inner(&s_tab, ignore_first, indent, ' ').into() +} + +fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option, ch: char) -> String { + let x = s + .lines() + .skip(ignore_first as usize) + .filter_map(|l| { + if l.is_empty() { + None + } else { + // ignore empty lines + Some(l.char_indices().find(|&(_, x)| x != ch).unwrap_or((l.len(), ch)).0) + } + }) + .min() + .unwrap_or(0); + let indent = indent.unwrap_or(0); + s.lines() + .enumerate() + .map(|(i, l)| { + if (ignore_first && i == 0) || l.is_empty() { + l.to_owned() + } else if x > indent { + l.split_at(x - indent).1.to_owned() + } else { + " ".repeat(indent - x) + l + } + }) + .collect::>() + .join("\n") +} + +/// 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>> { + let map = &cx.tcx.hir(); + let hir_id = e.hir_id; + let parent_id = map.get_parent_node(hir_id); + if hir_id == parent_id { + return None; + } + map.find(parent_id).and_then(|node| { + if let Node::Expr(parent) = node { + Some(parent) + } else { + None + } + }) +} + +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) + .and_then(|enclosing_id| map.find(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(ref block, _) => Some(block), + _ => None, + }, + _ => None, + }) +} + +/// Returns the base type for HIR references and pointers. +pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { + match ty.kind { + TyKind::Ptr(ref mut_ty) | TyKind::Rptr(_, ref mut_ty) => walk_ptrs_hir_ty(&mut_ty.ty), + _ => ty, + } +} + +/// Returns the base type for references and raw pointers, and count reference +/// depth. +pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { + fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { + match ty.kind() { + ty::Ref(_, ty, _) => inner(ty, depth + 1), + _ => (ty, depth), + } + } + inner(ty, 0) +} + +/// 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 map = cx.tcx.hir(); + let parent_item = map.get_parent_item(e.hir_id); + if let Some((Constant::Int(v), _)) = map + .maybe_body_owned_by(parent_item) + .and_then(|body_id| constant(cx, cx.tcx.typeck_body(body_id), e)) + { + value == v + } else { + 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(ref 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 +/// . +/// +/// See `rustc_middle::ty::adjustment::Adjustment` and `rustc_typeck::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 is 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 { + 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 +/// ```rust,ignore +/// foo!(bar!(42)); +/// ``` +/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only +/// `bar!` by +/// `is_direct_expn_of`. +#[must_use] +pub fn is_direct_expn_of(span: Span, name: &str) -> Option { + 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_item: hir::HirId) -> Ty<'tcx> { + let fn_def_id = cx.tcx.hir().local_def_id(fn_item); + let ret_ty = cx.tcx.fn_sig(fn_def_id).output(); + cx.tcx.erase_late_bound_regions(ret_ty) +} + +/// Walks into `ty` and returns `true` if any inner type is the same as `other_ty` +pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool { + ty.walk().any(|inner| match inner.unpack() { + GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty), + GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, + }) +} + +/// Returns `true` if the given type is an `unsafe` function. +pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe, + _ => false, + } +} + +pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + ty.is_copy_modulo_regions(cx.tcx.at(DUMMY_SP), cx.param_env) +} + +/// 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(ref fun, _) = expr.kind { + if let ExprKind::Path(ref qp) = fun.kind { + let res = cx.qpath_res(qp, fun.hir_id); + return match res { + def::Res::Def(DefKind::Variant | DefKind::Ctor(..), ..) => true, + def::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), + def::Res::Def(DefKind::Variant, ..) | Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), _) + ) + } + + fn are_refutable<'a, I: Iterator>>(cx: &LateContext<'_>, mut i: I) -> bool { + i.any(|pat| is_refutable(cx, pat)) + } + + match pat.kind { + PatKind::Wild => false, + PatKind::Binding(_, _, _, pat) => pat.map_or(false, |pat| is_refutable(cx, pat)), + PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => is_refutable(cx, pat), + PatKind::Lit(..) | PatKind::Range(..) => true, + PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), + PatKind::Or(ref pats) => { + // TODO: should be the honest check, that pats is exhaustive set + are_refutable(cx, pats.iter().map(|pat| &**pat)) + }, + PatKind::Tuple(ref pats, _) => are_refutable(cx, pats.iter().map(|pat| &**pat)), + PatKind::Struct(ref qpath, ref fields, _) => { + is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, fields.iter().map(|field| &*field.pat)) + }, + PatKind::TupleStruct(ref qpath, ref pats, _) => { + is_enum_variant(cx, qpath, pat.hir_id) || are_refutable(cx, pats.iter().map(|pat| &**pat)) + }, + PatKind::Slice(ref head, ref middle, ref tail) => { + match &cx.typeck_results().node_type(pat.hir_id).kind() { + ty::Slice(..) => { + // [..] is the only irrefutable slice pattern. + !head.is_empty() || middle.is_none() || !tail.is_empty() + }, + ty::Array(..) => are_refutable(cx, head.iter().chain(middle).chain(tail.iter()).map(|pat| &**pat)), + _ => { + // unreachable!() + true + }, + } + }, + } +} + +/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d +/// implementations have. +pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { + attrs.iter().any(|attr| attr.has_name(sym::automatically_derived)) +} + +/// Remove blocks around an expression. +/// +/// Ie. `x`, `{ x }` and `{{{{ x }}}}` all give `x`. `{ x; y }` and `{}` return +/// themselves. +pub fn remove_blocks<'tcx>(mut expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + while let ExprKind::Block(ref block, ..) = expr.kind { + match (block.stmts.is_empty(), block.expr.as_ref()) { + (true, Some(e)) => expr = e, + _ => break, + } + } + expr +} + +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_chain! { + if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind; + if let Res::SelfTy(..) = path.res; + then { + return true + } + } + false +} + +pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl Iterator> { + (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>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + fn is_ok(arm: &Arm<'_>) -> bool { + if_chain! { + if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind; + if match_qpath(path, &paths::RESULT_OK[1..]); + if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind; + if path_to_local_id(arm.body, hir_id); + then { + return true; + } + } + false + } + + fn is_err(arm: &Arm<'_>) -> bool { + if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind { + match_qpath(path, &paths::RESULT_ERR[1..]) + } else { + false + } + } + + if let ExprKind::Match(_, ref arms, ref source) = expr.kind { + // desugared from a `?` operator + if let MatchSource::TryDesugar = *source { + return Some(expr); + } + + if_chain! { + if arms.len() == 2; + if arms[0].guard.is_none(); + if arms[1].guard.is_none(); + if (is_ok(&arms[0]) && is_err(&arms[1])) || + (is_ok(&arms[1]) && is_err(&arms[0])); + then { + return Some(expr); + } + } + } + + None +} + +/// Returns `true` if the lint is allowed in the current context +/// +/// Useful for skipping long running code when it's unnecessary +pub fn is_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: ty::IntTy) -> u64 { + Integer::from_int_ty(&tcx, ity).size().bits() +} + +#[allow(clippy::cast_possible_wrap)] +/// Turn a constant int byte representation into an i128 +pub fn sext(tcx: TyCtxt<'_>, u: u128, ity: ty::IntTy) -> i128 { + let amt = 128 - int_bits(tcx, ity); + ((u as i128) << amt) >> amt +} + +#[allow(clippy::cast_sign_loss)] +/// clip unused bytes +pub fn unsext(tcx: TyCtxt<'_>, u: i128, ity: ty::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: ty::UintTy) -> u128 { + let bits = Integer::from_uint_ty(&tcx, ity).size().bits(); + let amt = 128 - bits; + (u << amt) >> amt +} + +/// Removes block comments from the given `Vec` of lines. +/// +/// # Examples +/// +/// ```rust,ignore +/// without_block_comments(vec!["/*", "foo", "*/"]); +/// // => vec![] +/// +/// without_block_comments(vec!["bar", "/*", "foo", "*/"]); +/// // => vec!["bar"] +/// ``` +pub fn without_block_comments(lines: Vec<&str>) -> Vec<&str> { + let mut without = vec![]; + + let mut nest_level = 0; + + for line in lines { + if line.contains("/*") { + nest_level += 1; + continue; + } else if line.contains("*/") { + nest_level -= 1; + continue; + } + + if nest_level == 0 { + without.push(line); + } + } + + without +} + +pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { + let map = &tcx.hir(); + let mut prev_enclosing_node = None; + let mut enclosing_node = node; + while Some(enclosing_node) != prev_enclosing_node { + if is_automatically_derived(map.attrs(enclosing_node)) { + return true; + } + prev_enclosing_node = Some(enclosing_node); + enclosing_node = map.get_parent_item(enclosing_node); + } + false +} + +/// Returns true if ty has `iter` or `iter_mut` methods +pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<&'static str> { + // FIXME: instead of this hard-coded list, we should check if `::iter` + // exists and has the desired signature. Unfortunately FnCtxt is not exported + // so we can't use its `lookup_method` method. + let into_iter_collections: [&[&str]; 13] = [ + &paths::VEC, + &paths::OPTION, + &paths::RESULT, + &paths::BTREESET, + &paths::BTREEMAP, + &paths::VEC_DEQUE, + &paths::LINKED_LIST, + &paths::BINARY_HEAP, + &paths::HASHSET, + &paths::HASHMAP, + &paths::PATH_BUF, + &paths::PATH, + &paths::RECEIVER, + ]; + + let ty_to_check = match probably_ref_ty.kind() { + ty::Ref(_, ty_to_check, _) => ty_to_check, + _ => probably_ref_ty, + }; + + let def_id = match ty_to_check.kind() { + ty::Array(..) => return Some("array"), + ty::Slice(..) => return Some("slice"), + ty::Adt(adt, _) => adt.did, + _ => return None, + }; + + for path in &into_iter_collections { + if match_def_path(cx, def_id, path) { + return Some(*path.last().unwrap()); + } + } + None +} + +/// 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); +/// ``` +pub fn match_function_call<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + path: &[&str], +) -> Option<&'tcx [Expr<'tcx>]> { + if_chain! { + if let ExprKind::Call(ref fun, ref args) = expr.kind; + if let ExprKind::Path(ref qpath) = fun.kind; + if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); + if match_def_path(cx, fun_def_id, path); + then { + return Some(&args) + } + }; + None +} + +/// Checks if `Ty` is normalizable. This function is useful +/// to avoid crashes on `layout_of`. +pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool { + cx.tcx.infer_ctxt().enter(|infcx| { + let cause = rustc_middle::traits::ObligationCause::dummy(); + infcx.at(&cause, param_env).normalize(ty).is_ok() + }) +} + +pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { + // We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path` + // accepts only that. We should probably move to Symbols in Clippy as well. + let syms = syms.iter().map(|p| Symbol::intern(p)).collect::>(); + cx.match_def_path(did, &syms) +} + +pub fn match_panic_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx [Expr<'tcx>]> { + match_function_call(cx, expr, &paths::BEGIN_PANIC) + .or_else(|| match_function_call(cx, expr, &paths::BEGIN_PANIC_FMT)) + .or_else(|| match_function_call(cx, expr, &paths::PANIC_ANY)) + .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC)) + .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_FMT)) + .or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_STR)) +} + +pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool { + match_def_path(cx, did, &paths::BEGIN_PANIC) + || match_def_path(cx, did, &paths::BEGIN_PANIC_FMT) + || match_def_path(cx, did, &paths::PANIC_ANY) + || match_def_path(cx, did, &paths::PANICKING_PANIC) + || match_def_path(cx, did, &paths::PANICKING_PANIC_FMT) + || match_def_path(cx, did, &paths::PANICKING_PANIC_STR) +} + +/// 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>, +) -> (SmallVec<[&'tcx Expr<'tcx>; 1]>, SmallVec<[&'tcx Block<'tcx>; 1]>) { + let mut conds = SmallVec::new(); + let mut blocks: SmallVec<[&Block<'_>; 1]> = SmallVec::new(); + + while let ExprKind::If(ref cond, ref then_expr, ref else_expr) = expr.kind { + conds.push(&**cond); + if let ExprKind::Block(ref block, _) = then_expr.kind { + blocks.push(block); + } else { + panic!("ExprKind::If node is not an ExprKind::Block"); + } + + if let Some(ref else_expr) = *else_expr { + expr = else_expr; + } else { + break; + } + } + + // final `else {..}` + if !blocks.is_empty() { + if let ExprKind::Block(ref block, _) = expr.kind { + blocks.push(&**block); + } + } + + (conds, blocks) +} + +pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool { + let map = cx.tcx.hir(); + let parent_id = map.get_parent_node(expr.hir_id); + let parent_node = map.get(parent_id); + matches!( + parent_node, + Node::Expr(Expr { + kind: ExprKind::If(_, _, _), + .. + }) + ) +} + +// Finds the attribute with the given name, if any +pub fn attr_by_name<'a>(attrs: &'a [Attribute], name: &'_ str) -> Option<&'a Attribute> { + attrs + .iter() + .find(|attr| attr.ident().map_or(false, |ident| ident.as_str() == name)) +} + +// Finds the `#[must_use]` attribute, if any +pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { + attr_by_name(attrs, "must_use") +} + +// Returns whether the type has #[must_use] attribute +pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + match ty.kind() { + ty::Adt(ref adt, _) => must_use_attr(&cx.tcx.get_attrs(adt.did)).is_some(), + ty::Foreign(ref did) => must_use_attr(&cx.tcx.get_attrs(*did)).is_some(), + ty::Slice(ref ty) + | ty::Array(ref ty, _) + | ty::RawPtr(ty::TypeAndMut { ref ty, .. }) + | ty::Ref(_, ref ty, _) => { + // for the Array case we don't need to care for the len == 0 case + // because we don't want to lint functions returning empty arrays + is_must_use_ty(cx, *ty) + }, + ty::Tuple(ref substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)), + ty::Opaque(ref def_id, _) => { + for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) { + if let ty::PredicateKind::Trait(trait_predicate, _) = predicate.kind().skip_binder() { + if must_use_attr(&cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() { + return true; + } + } + } + false + }, + ty::Dynamic(binder, _) => { + for predicate in binder.iter() { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { + if must_use_attr(&cx.tcx.get_attrs(trait_ref.def_id)).is_some() { + return true; + } + } + } + false + }, + _ => false, + } +} + +// 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(ref path, _) => if_chain! { + if let ExprKind::Path(ref qpath) = path.kind; + if let def::Res::Def(_, did) = cx.qpath_res(qpath, path.hir_id); + then { + Some(did) + } else { + None + } + }, + ExprKind::MethodCall(_, _, _, _) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }; + + did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some()) +} + +pub fn is_no_std_crate(krate: &Crate<'_>) -> bool { + krate.item.attrs.iter().any(|attr| { + if let ast::AttrKind::Normal(ref attr, _) = attr.kind { + attr.path == sym::no_std + } else { + false + } + }) +} + +/// Check if parent of a hir node is a trait implementation block. +/// For example, `f` in +/// ```rust,ignore +/// impl Trait for S { +/// fn f() {} +/// } +/// ``` +pub fn is_trait_impl_item(cx: &LateContext<'_>, hir_id: HirId) -> bool { + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + matches!(item.kind, ItemKind::Impl(hir::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_predicates(cx.tcx, predicates) + .map(|o| o.predicate) + .collect::>(), + ) +} + +/// 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 { + match &expr.kind { + ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + ExprKind::Call( + Expr { + kind: ExprKind::Path(qpath), + hir_id: path_hir_id, + .. + }, + .., + ) => cx.typeck_results().qpath_res(qpath, *path_hir_id).opt_def_id(), + _ => None, + } +} + +pub fn run_lints(cx: &LateContext<'_>, lints: &[&'static Lint], id: HirId) -> bool { + lints.iter().any(|lint| { + matches!( + cx.tcx.lint_level_at_node(lint, id), + (Level::Forbid | Level::Deny | Level::Warn, _) + ) + }) +} + +/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point +/// number type, a str, or an array, slice, or tuple of those types). +pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, + ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true, + ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type), + ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type), + _ => false, + } +} + +/// Returns Option 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 { + let expr_type = cx.typeck_results().expr_ty_adjusted(expr); + let expr_kind = expr_type.kind(); + let is_primitive = match expr_kind { + ty::Slice(element_type) => is_recursively_primitive_type(element_type), + ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &ty::Slice(_)) => { + if let 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() { + ty::Slice(..) => return Some("slice".into()), + ty::Array(..) => return Some("array".into()), + 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) from `exprs` such that `eq(a, b)` +/// `hash` must be comformed with `eq` +pub fn search_same(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)> +where + Hash: Fn(&T) -> u64, + Eq: Fn(&T, &T) -> bool, +{ + if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) { + return vec![(&exprs[0], &exprs[1])]; + } + + let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); + + let mut map: FxHashMap<_, Vec<&_>> = + FxHashMap::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(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { + fn peel(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 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(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { + fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) { + match expr.kind { + ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target), + _ => (expr, count), + } + } + f(expr, 0, count) +} + +/// Peels off all references on the expression. Returns the underlying expression and the number of +/// references removed. +pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) { + fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { + match expr.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1), + _ => (expr, count), + } + } + f(expr, 0) +} + +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { + fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) { + if let ty::Ref(_, ty, _) = ty.kind() { + peel(ty, count + 1) + } else { + (ty, count) + } + } + peel(ty, 0) +} + +/// Peels off all references on the type.Returns the underlying type, the number of references +/// removed, and whether the pointer is ultimately mutable or not. +pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { + fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { + match ty.kind() { + ty::Ref(_, ty, Mutability::Mut) => f(ty, count + 1, mutability), + ty::Ref(_, ty, Mutability::Not) => f(ty, count + 1, Mutability::Not), + _ => (ty, count, mutability), + } + } + f(ty, 0, Mutability::Mut) +} + +#[macro_export] +macro_rules! unwrap_cargo_metadata { + ($cx: ident, $lint: ident, $deps: expr) => {{ + let mut command = cargo_metadata::MetadataCommand::new(); + if !$deps { + command.no_deps(); + } + + match command.exec() { + Ok(metadata) => metadata, + Err(err) => { + span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err)); + return; + }, + } + }}; +} + +pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { + if_chain! { + if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind; + if let Res::Def(_, def_id) = path.res; + then { + cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr) + } else { + false + } + } +} + +/// Check if the resolution of a given path is an `Ok` variant of `Result`. +pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool { + if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { + if let Some(variant_id) = cx.tcx.parent(id) { + return variant_id == ok_id; + } + } + } + false +} + +/// Check if the resolution of a given path is a `Some` variant of `Option`. +pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool { + if let Some(some_id) = cx.tcx.lang_items().option_some_variant() { + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { + if let Some(variant_id) = cx.tcx.parent(id) { + return variant_id == some_id; + } + } + } + false +} + +#[cfg(test)] +mod test { + use super::{reindent_multiline, without_block_comments}; + + #[test] + fn test_reindent_multiline_single_line() { + assert_eq!("", reindent_multiline("".into(), false, None)); + assert_eq!("...", reindent_multiline("...".into(), false, None)); + assert_eq!("...", reindent_multiline(" ...".into(), false, None)); + assert_eq!("...", reindent_multiline("\t...".into(), false, None)); + assert_eq!("...", reindent_multiline("\t\t...".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_reindent_multiline_block() { + assert_eq!("\ + if x { + y + } else { + z + }", reindent_multiline(" if x { + y + } else { + z + }".into(), false, None)); + assert_eq!("\ + if x { + \ty + } else { + \tz + }", reindent_multiline(" if x { + \ty + } else { + \tz + }".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_reindent_multiline_empty_line() { + assert_eq!("\ + if x { + y + + } else { + z + }", reindent_multiline(" if x { + y + + } else { + z + }".into(), false, None)); + } + + #[test] + #[rustfmt::skip] + fn test_reindent_multiline_lines_deeper() { + assert_eq!("\ + if x { + y + } else { + z + }", reindent_multiline("\ + if x { + y + } else { + z + }".into(), true, Some(8))); + } + + #[test] + fn test_without_block_comments_lines_without_block_comments() { + let result = without_block_comments(vec!["/*", "", "*/"]); + println!("result: {:?}", result); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["", "/*", "", "*/", "#[crate_type = \"lib\"]", "/*", "", "*/", ""]); + assert_eq!(result, vec!["", "#[crate_type = \"lib\"]", ""]); + + let result = without_block_comments(vec!["/* rust", "", "*/"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* one-line comment */"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* nested", "/* multi-line", "comment", "*/", "test", "*/"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["/* nested /* inline /* comment */ test */ */"]); + assert!(result.is_empty()); + + let result = without_block_comments(vec!["foo", "bar", "baz"]); + assert_eq!(result, vec!["foo", "bar", "baz"]); + } +} diff --git a/clippy_lints/src/utils/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs similarity index 100% rename from clippy_lints/src/utils/numeric_literal.rs rename to clippy_utils/src/numeric_literal.rs diff --git a/clippy_lints/src/utils/paths.rs b/clippy_utils/src/paths.rs similarity index 100% rename from clippy_lints/src/utils/paths.rs rename to clippy_utils/src/paths.rs diff --git a/clippy_lints/src/utils/ptr.rs b/clippy_utils/src/ptr.rs similarity index 97% rename from clippy_lints/src/utils/ptr.rs rename to clippy_utils/src/ptr.rs index b330f3d890e..baeff08e02c 100644 --- a/clippy_lints/src/utils/ptr.rs +++ b/clippy_utils/src/ptr.rs @@ -1,4 +1,4 @@ -use crate::utils::{get_pat_name, match_var, snippet}; +use crate::{get_pat_name, match_var, snippet}; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{Body, BodyId, Expr, ExprKind, Param}; use rustc_lint::LateContext; diff --git a/clippy_lints/src/utils/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs similarity index 100% rename from clippy_lints/src/utils/qualify_min_const_fn.rs rename to clippy_utils/src/qualify_min_const_fn.rs diff --git a/clippy_lints/src/utils/sugg.rs b/clippy_utils/src/sugg.rs similarity index 99% rename from clippy_lints/src/utils/sugg.rs rename to clippy_utils/src/sugg.rs index 03678db575f..d4f6f4281d3 100644 --- a/clippy_lints/src/utils/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -1,7 +1,7 @@ //! Contains utility functions to generate suggestions. #![deny(clippy::missing_docs_in_private_items)] -use crate::utils::{higher, snippet, snippet_opt, snippet_with_macro_callsite}; +use crate::{higher, snippet, snippet_opt, snippet_with_macro_callsite}; use rustc_ast::util::parser::AssocOp; use rustc_ast::{ast, token}; use rustc_ast_pretty::pprust::token_kind_to_string; diff --git a/clippy_lints/src/utils/sym_helper.rs b/clippy_utils/src/sym_helper.rs similarity index 100% rename from clippy_lints/src/utils/sym_helper.rs rename to clippy_utils/src/sym_helper.rs diff --git a/clippy_lints/src/utils/usage.rs b/clippy_utils/src/usage.rs similarity index 99% rename from clippy_lints/src/utils/usage.rs rename to clippy_utils/src/usage.rs index 7c7580a2c66..d577827dcf3 100644 --- a/clippy_lints/src/utils/usage.rs +++ b/clippy_utils/src/usage.rs @@ -1,4 +1,4 @@ -use crate::utils; +use crate as utils; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def::Res; diff --git a/clippy_lints/src/utils/visitors.rs b/clippy_utils/src/visitors.rs similarity index 99% rename from clippy_lints/src/utils/visitors.rs rename to clippy_utils/src/visitors.rs index 085c1f9c0cb..5a8c629e333 100644 --- a/clippy_lints/src/utils/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,4 +1,4 @@ -use crate::utils::path_to_local_id; +use crate::path_to_local_id; use rustc_hir as hir; use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{Arm, Body, Expr, HirId, Stmt}; diff --git a/doc/adding_lints.md b/doc/adding_lints.md index e12e75d4a2b..f62c2d29c70 100644 --- a/doc/adding_lints.md +++ b/doc/adding_lints.md @@ -292,7 +292,7 @@ the next section. Let's worry about the details later and emit our lint for Depending on how complex we want our lint message to be, we can choose from a variety of lint emission functions. They can all be found in -[`clippy_lints/src/utils/diagnostics.rs`][diagnostics]. +[`clippy_utils/src/diagnostics.rs`][diagnostics]. `span_lint_and_help` seems most appropriate in this case. It allows us to provide an extra help message and we can't really suggest a better name @@ -321,7 +321,7 @@ When code or an identifier must appear in a message or label, it should be surrounded with single grave accents \`. [check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn -[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/diagnostics.rs +[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs [the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html ## Adding the lint logic @@ -537,7 +537,7 @@ directory. Adding a configuration to a lint can be useful for thresholds or to c behavior that can be seen as a false positive for some users. Adding a configuration is done in the following steps: -1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs) +1. Adding a new configuration entry to [clippy_utils::conf](/clippy_utils/src/conf.rs) like this: ```rust /// Lint: LINT_NAME. @@ -636,7 +636,7 @@ documentation currently. This is unfortunate, but in most cases you can probably get away with copying things from existing similar lints. If you are stuck, don't hesitate to ask on [Zulip] or in the issue/PR. -[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_lints/src/utils/mod.rs +[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs [if_chain]: https://docs.rs/if_chain/*/if_chain/ [from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion [in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index d56079a4ab7..abac1227b4f 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -78,7 +78,7 @@ impl LateLintPass<'_> for MyStructLint { There are two ways to do this, depending if the target trait is part of lang items. ```rust -use crate::utils::{implements_trait, match_trait_method, paths}; +use clippy_utils::{implements_trait, match_trait_method, paths}; impl LateLintPass<'_> for MyStructLint { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { @@ -112,7 +112,7 @@ We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`] To check if our type defines a method called `some_method`: ```rust -use crate::utils::{is_type_diagnostic_item, return_ty}; +use clippy_utils::{is_type_diagnostic_item, return_ty}; impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { @@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { # Dealing with macros -There are several helpers in Clippy's utils to deal with macros: +There are several helpers in [`clippy_utils`][utils] to deal with macros: - `in_macro()`: detect if the given span is expanded by a macro @@ -199,4 +199,5 @@ assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html [pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty -[paths]: ../clippy_lints/src/utils/paths.rs +[paths]: ../clippy_utils/src/paths.rs +[utils]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/lib.rs diff --git a/tests/versioncheck.rs b/tests/versioncheck.rs index bc5ed0816cc..1c954c57a85 100644 --- a/tests/versioncheck.rs +++ b/tests/versioncheck.rs @@ -2,21 +2,24 @@ use rustc_tools_util::VersionInfo; #[test] -fn check_that_clippy_lints_has_the_same_version_as_clippy() { +fn check_that_clippy_lints_and_clippy_utils_have_the_same_version_as_clippy() { let clippy_meta = cargo_metadata::MetadataCommand::new() .no_deps() .exec() .expect("could not obtain cargo metadata"); - std::env::set_current_dir(std::env::current_dir().unwrap().join("clippy_lints")).unwrap(); - let clippy_lints_meta = cargo_metadata::MetadataCommand::new() - .no_deps() - .exec() - .expect("could not obtain cargo metadata"); - assert_eq!(clippy_lints_meta.packages[0].version, clippy_meta.packages[0].version); - for package in &clippy_meta.packages[0].dependencies { - if package.name == "clippy_lints" { - assert!(package.req.matches(&clippy_lints_meta.packages[0].version)); - return; + + for krate in &["clippy_lints", "clippy_utils"] { + let krate_meta = clippy_meta + .packages + .iter() + .find(|package| package.name == *krate) + .expect("could not obtain cargo metadata"); + assert_eq!(krate_meta.version, clippy_meta.packages[0].version); + for package in &clippy_meta.packages[0].dependencies { + if package.name == *krate { + assert!(package.req.matches(&krate_meta.version)); + break; + } } } }