Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
flip1995
2021-02-25 10:40:00 +01:00
111 changed files with 6361 additions and 3311 deletions

View File

@@ -1,4 +1,8 @@
use crate::utils::{differing_macro_contexts, snippet_block_with_applicability, span_lint, span_lint_and_sugg};
use crate::utils::{
differing_macro_contexts, get_parent_expr, get_trait_def_id, implements_trait, paths,
snippet_block_with_applicability, span_lint, span_lint_and_sugg,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{BlockCheckMode, Expr, ExprKind};
@@ -52,6 +56,18 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if let ExprKind::Closure(_, _, eid, _, _) = expr.kind {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(self.cx, expr);
if let ExprKind::MethodCall(_, _, args, _) = parent.kind;
let caller = self.cx.typeck_results().expr_ty(&args[0]);
if let Some(iter_id) = get_trait_def_id(self.cx, &paths::ITERATOR);
if implements_trait(self.cx, caller, iter_id, &[]);
then {
return;
}
}
let body = self.cx.tcx.hir().body(eid);
let ex = &body.value;
if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() {

View File

@@ -122,6 +122,7 @@ fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, else_: &ast::Expr) {
if let ast::ExprKind::Block(ref block, _) = else_.kind;
if !block_starts_with_comment(cx, block);
if let Some(else_) = expr_block(block);
if else_.attrs.is_empty();
if !else_.span.from_expansion();
if let ast::ExprKind::If(..) = else_.kind;
then {
@@ -143,16 +144,12 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
if_chain! {
if !block_starts_with_comment(cx, then);
if let Some(inner) = expr_block(then);
if inner.attrs.is_empty();
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
// Prevent triggering on `if c { if let a = b { .. } }`.
if !matches!(check_inner.kind, ast::ExprKind::Let(..));
if expr.span.ctxt() == inner.span.ctxt();
then {
if let ast::ExprKind::Let(..) = check_inner.kind {
// Prevent triggering on `if c { if let a = b { .. } }`.
return;
}
if expr.span.ctxt() != inner.span.ctxt() {
return;
}
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
let lhs = Sugg::ast(cx, check, "..");
let rhs = Sugg::ast(cx, check_inner, "..");

View File

@@ -96,12 +96,12 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
cx,
COLLAPSIBLE_MATCH,
expr.span,
"Unnecessary nested match",
"unnecessary nested match",
|diag| {
let mut help_span = MultiSpan::from_spans(vec![binding_span, non_wild_inner_arm.pat.span]);
help_span.push_span_label(binding_span, "Replace this binding".into());
help_span.push_span_label(binding_span, "replace this binding".into());
help_span.push_span_label(non_wild_inner_arm.pat.span, "with this pattern".into());
diag.span_help(help_span, "The outer pattern can be modified to include the inner pattern.");
diag.span_help(help_span, "the outer pattern can be modified to include the inner pattern");
},
);
}

View File

@@ -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<Constant>),
/// Also an array, but with only one constant, repeated N times.
Repeat(Box<Constant>, u64),
/// A tuple of constants.
Tuple(Vec<Constant>),
/// A raw pointer.
RawPtr(u128),
/// A reference
Ref(Box<Constant>),
/// 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 dont 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 dont 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<H>(&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<Ordering> {
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<Ty<'_>>) -> 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> {
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<Constant> {
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 {
"<impl i8>" => i8::MAX as u128,
"<impl i16>" => i16::MAX as u128,
"<impl i32>" => i32::MAX as u128,
"<impl i64>" => i64::MAX as u128,
"<impl i128>" => 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<Constant> {
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<Constant> {
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<Constant>> {
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
}
/// Lookup a possibly constant expression from a `ExprKind::Path`.
fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant> {
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<Constant> {
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<Constant> {
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<Constant> {
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<Constant> {
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<Constant> {
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::<Option<Vec<Constant>>>()
.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::<Option<Vec<Constant>>>()
.map(Constant::Vec),
_ => None,
},
// FIXME: implement other array type conversions.
_ => None,
},
_ => None,
},
// FIXME: implement other conversions.
_ => None,
}
}
pub use clippy_utils::consts::*;

View File

@@ -0,0 +1,237 @@
use rustc_ast::ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{
intravisit::{walk_expr, walk_stmt, NestedVisitorMap, Visitor},
Body, Expr, ExprKind, HirId, Lit, Stmt, StmtKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::{
hir::map::Map,
ty::{self, FloatTy, IntTy, PolyFnSig, Ty},
};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use if_chain::if_chain;
use crate::utils::{snippet, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for usage of unconstrained numeric literals which may cause default numeric fallback in type
/// inference.
///
/// Default numeric fallback means that if numeric types have not yet been bound to concrete
/// types at the end of type inference, then integer type is bound to `i32`, and similarly
/// floating type is bound to `f64`.
///
/// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback.
///
/// **Why is this bad?** For those who are very careful about types, default numeric fallback
/// can be a pitfall that cause unexpected runtime behavior.
///
/// **Known problems:** This lint can only be allowed at the function level or above.
///
/// **Example:**
/// ```rust
/// let i = 10;
/// let f = 1.23;
/// ```
///
/// Use instead:
/// ```rust
/// let i = 10i32;
/// let f = 1.23f64;
/// ```
pub DEFAULT_NUMERIC_FALLBACK,
restriction,
"usage of unconstrained numeric literals which may cause default numeric fallback."
}
declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]);
impl LateLintPass<'_> for DefaultNumericFallback {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
let mut visitor = NumericFallbackVisitor::new(cx);
visitor.visit_body(body);
}
}
struct NumericFallbackVisitor<'a, 'tcx> {
/// Stack manages type bound of exprs. The top element holds current expr type.
ty_bounds: Vec<TyBound<'tcx>>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
ty_bounds: vec![TyBound::Nothing],
cx,
}
}
/// Check whether a passed literal has potential to cause fallback or not.
fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) {
if_chain! {
if let Some(ty_bound) = self.ty_bounds.last();
if matches!(lit.node,
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed));
if !ty_bound.is_integral();
then {
let suffix = match lit_ty.kind() {
ty::Int(IntTy::I32) => "i32",
ty::Float(FloatTy::F64) => "f64",
// Default numeric fallback never results in other types.
_ => return,
};
let sugg = format!("{}_{}", snippet(self.cx, lit.span, ""), suffix);
span_lint_and_sugg(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
lit.span,
"default numeric fallback might occur",
"consider adding suffix",
sugg,
Applicability::MaybeIncorrect,
);
}
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
#[allow(clippy::too_many_lines)]
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
match &expr.kind {
ExprKind::Call(func, args) => {
if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) {
for (expr, bound) in args.iter().zip(fn_sig.skip_binder().inputs().iter()) {
// Push found arg type, then visit arg.
self.ty_bounds.push(TyBound::Ty(bound));
self.visit_expr(expr);
self.ty_bounds.pop();
}
return;
}
},
ExprKind::MethodCall(_, _, args, _) => {
if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) {
let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder();
for (expr, bound) in args.iter().zip(fn_sig.inputs().iter()) {
self.ty_bounds.push(TyBound::Ty(bound));
self.visit_expr(expr);
self.ty_bounds.pop();
}
return;
}
},
ExprKind::Struct(qpath, fields, base) => {
if_chain! {
if let Some(def_id) = self.cx.qpath_res(qpath, expr.hir_id).opt_def_id();
let ty = self.cx.tcx.type_of(def_id);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next();
then {
let fields_def = &variant.fields;
// Push field type then visit each field expr.
for field in fields.iter() {
let bound =
fields_def
.iter()
.find_map(|f_def| {
if f_def.ident == field.ident
{ Some(self.cx.tcx.type_of(f_def.did)) }
else { None }
});
self.ty_bounds.push(bound.into());
self.visit_expr(field.expr);
self.ty_bounds.pop();
}
// Visit base with no bound.
if let Some(base) = base {
self.ty_bounds.push(TyBound::Nothing);
self.visit_expr(base);
self.ty_bounds.pop();
}
return;
}
}
},
ExprKind::Lit(lit) => {
let ty = self.cx.typeck_results().expr_ty(expr);
self.check_lit(lit, ty);
return;
},
_ => {},
}
walk_expr(self, expr);
}
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
match stmt.kind {
StmtKind::Local(local) => {
if local.ty.is_some() {
self.ty_bounds.push(TyBound::Any)
} else {
self.ty_bounds.push(TyBound::Nothing)
}
},
_ => self.ty_bounds.push(TyBound::Nothing),
}
walk_stmt(self, stmt);
self.ty_bounds.pop();
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<PolyFnSig<'tcx>> {
let node_ty = cx.typeck_results().node_type_opt(hir_id)?;
// We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs.
match node_ty.kind() {
ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)),
ty::FnPtr(fn_sig) => Some(*fn_sig),
_ => None,
}
}
#[derive(Debug, Clone, Copy)]
enum TyBound<'tcx> {
Any,
Ty(Ty<'tcx>),
Nothing,
}
impl<'tcx> TyBound<'tcx> {
fn is_integral(self) -> bool {
match self {
TyBound::Any => true,
TyBound::Ty(t) => t.is_integral(),
TyBound::Nothing => false,
}
}
}
impl<'tcx> From<Option<Ty<'tcx>>> for TyBound<'tcx> {
fn from(v: Option<Ty<'tcx>>) -> Self {
match v {
Some(t) => TyBound::Ty(t),
None => TyBound::Nothing,
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::utils::{
implements_trait, is_entrypoint_fn, is_type_diagnostic_item, match_panic_def_id, method_chain_args, return_ty,
span_lint, span_lint_and_note,
implements_trait, is_entrypoint_fn, is_expn_of, is_type_diagnostic_item, match_panic_def_id, method_chain_args,
return_ty, span_lint, span_lint_and_note,
};
use if_chain::if_chain;
use itertools::Itertools;
@@ -216,9 +216,7 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
let headers = check_attrs(cx, &self.valid_idents, &item.attrs);
match item.kind {
hir::ItemKind::Fn(ref sig, _, body_id) => {
if !(is_entrypoint_fn(cx, item.def_id.to_def_id())
|| in_external_macro(cx.tcx.sess, item.span))
{
if !(is_entrypoint_fn(cx, item.def_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
let body = cx.tcx.hir().body(body_id);
let mut fpu = FindPanicUnwrap {
cx,
@@ -226,7 +224,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
panic_span: None,
};
fpu.visit_expr(&body.value);
lint_for_missing_headers(cx, item.hir_id(), item.span, sig, headers, Some(body_id), fpu.panic_span);
lint_for_missing_headers(
cx,
item.hir_id(),
item.span,
sig,
headers,
Some(body_id),
fpu.panic_span,
);
}
},
hir::ItemKind::Impl(ref impl_) => {
@@ -264,7 +270,15 @@ impl<'tcx> LateLintPass<'tcx> for DocMarkdown {
panic_span: None,
};
fpu.visit_expr(&body.value);
lint_for_missing_headers(cx, item.hir_id(), item.span, sig, headers, Some(body_id), fpu.panic_span);
lint_for_missing_headers(
cx,
item.hir_id(),
item.span,
sig,
headers,
Some(body_id),
fpu.panic_span,
);
}
}
}
@@ -561,9 +575,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
| ItemKind::ExternCrate(..)
| ItemKind::ForeignMod(..) => return false,
// We found a main function ...
ItemKind::Fn(box FnKind(_, sig, _, Some(block)))
if item.ident.name == sym::main =>
{
ItemKind::Fn(box FnKind(_, sig, _, Some(block))) if item.ident.name == sym::main => {
let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
let returns_nothing = match &sig.decl.output {
FnRetTy::Default(..) => true,
@@ -699,6 +711,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.cx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none();
then {
self.panic_span = Some(expr.span);
}

View File

@@ -1,7 +1,5 @@
use crate::utils::{attr_by_name, in_macro, match_path_ast, span_lint_and_help};
use rustc_ast::ast::{
AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind,
};
use rustc_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;

View File

@@ -0,0 +1,101 @@
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
use crate::utils::is_type_diagnostic_item;
use crate::utils::span_lint_and_sugg;
use crate::utils::sugg::Sugg;
declare_clippy_lint! {
/// **What it does:**
/// Checks for function invocations of the form `primitive::from_str_radix(s, 10)`
///
/// **Why is this bad?**
/// This specific common use case can be rewritten as `s.parse::<primitive>()`
/// (and in most cases, the turbofish can be removed), which reduces code length
/// and complexity.
///
/// **Known problems:**
/// This lint may suggest using (&<expression>).parse() instead of <expression>.parse() directly
/// in some cases, which is correct but adds unnecessary complexity to the code.
///
/// **Example:**
///
/// ```ignore
/// let input: &str = get_input();
/// let num = u16::from_str_radix(input, 10)?;
/// ```
/// Use instead:
/// ```ignore
/// let input: &str = get_input();
/// let num: u16 = input.parse()?;
/// ```
pub FROM_STR_RADIX_10,
style,
"from_str_radix with radix 10"
}
declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
impl LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
if_chain! {
if let ExprKind::Call(maybe_path, arguments) = &exp.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
// check if the first part of the path is some integer primitive
if let TyKind::Path(ty_qpath) = &ty.kind;
let ty_res = cx.qpath_res(ty_qpath, ty.hir_id);
if let def::Res::PrimTy(prim_ty) = ty_res;
if matches!(prim_ty, PrimTy::Int(_) | PrimTy::Uint(_));
// check if the second part of the path indeed calls the associated
// function `from_str_radix`
if pathseg.ident.name.as_str() == "from_str_radix";
// check if the second argument is a primitive `10`
if arguments.len() == 2;
if let ExprKind::Lit(lit) = &arguments[1].kind;
if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
then {
let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind {
let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) {
expr
} else {
&arguments[0]
}
} else {
&arguments[0]
};
let sugg = Sugg::hir_with_applicability(
cx,
expr,
"<string>",
&mut Applicability::MachineApplicable
).maybe_par();
span_lint_and_sugg(
cx,
FROM_STR_RADIX_10,
exp.span,
"this call to `from_str_radix` can be replaced with a call to `str::parse`",
"try",
format!("{}.parse::<{}>()", sugg, prim_ty.name_str()),
Applicability::MaybeIncorrect
);
}
}
}
}
/// Checks if a Ty is `String` or `&str`
fn is_ty_stringish(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
is_type_diagnostic_item(cx, ty, sym::string_type) || is_type_diagnostic_item(cx, ty, sym::str)
}

View File

@@ -1,7 +1,7 @@
use crate::utils::{
attr_by_name, attrs::is_proc_macro, is_must_use_ty, is_trait_impl_item, is_type_diagnostic_item, iter_input_pats,
last_path_segment, match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint,
span_lint_and_help, span_lint_and_then, trait_ref_of_method, type_is_unsafe_function,
match_def_path, must_use_attr, path_to_local, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help,
span_lint_and_then, trait_ref_of_method, type_is_unsafe_function,
};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
@@ -470,12 +470,11 @@ fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span
if_chain! {
if !in_external_macro(cx.sess(), item_span);
if let hir::FnRetTy::Return(ref ty) = decl.output;
if let hir::TyKind::Path(ref qpath) = ty.kind;
if is_type_diagnostic_item(cx, hir_ty_to_ty(cx.tcx, ty), sym::result_type);
if let Some(ref args) = last_path_segment(qpath).args;
if let [_, hir::GenericArg::Type(ref err_ty)] = args.args;
if let hir::TyKind::Tup(t) = err_ty.kind;
if t.is_empty();
let ty = hir_ty_to_ty(cx.tcx, ty);
if is_type_diagnostic_item(cx, ty, sym::result_type);
if let ty::Adt(_, substs) = ty.kind();
let err_ty = substs.type_at(1);
if err_ty.is_unit();
then {
span_lint_and_help(
cx,

View File

@@ -0,0 +1,134 @@
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
use if_chain::if_chain;
use crate::utils::{snippet, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for struct constructors where the order of the field init
/// shorthand in the constructor is inconsistent with the order in the struct definition.
///
/// **Why is this bad?** Since the order of fields in a constructor doesn't affect the
/// resulted instance as the below example indicates,
///
/// ```rust
/// #[derive(Debug, PartialEq, Eq)]
/// struct Foo {
/// x: i32,
/// y: i32,
/// }
/// let x = 1;
/// let y = 2;
///
/// // This assertion never fails.
/// assert_eq!(Foo { x, y }, Foo { y, x });
/// ```
///
/// inconsistent order means nothing and just decreases readability and consistency.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// struct Foo {
/// x: i32,
/// y: i32,
/// }
/// let x = 1;
/// let y = 2;
/// Foo { y, x };
/// ```
///
/// Use instead:
/// ```rust
/// # struct Foo {
/// # x: i32,
/// # y: i32,
/// # }
/// # let x = 1;
/// # let y = 2;
/// Foo { x, y };
/// ```
pub INCONSISTENT_STRUCT_CONSTRUCTOR,
style,
"the order of the field init shorthand is inconsistent with the order in the struct definition"
}
declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRUCTOR]);
impl LateLintPass<'_> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
if let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id();
let ty = cx.tcx.type_of(def_id);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next();
if fields.iter().all(|f| f.is_shorthand);
then {
let mut def_order_map = FxHashMap::default();
for (idx, field) in variant.fields.iter().enumerate() {
def_order_map.insert(field.ident.name, idx);
}
if is_consistent_order(fields, &def_order_map) {
return;
}
let mut ordered_fields: Vec<_> = fields.iter().map(|f| f.ident.name).collect();
ordered_fields.sort_unstable_by_key(|id| def_order_map[id]);
let mut fields_snippet = String::new();
let (last_ident, idents) = ordered_fields.split_last().unwrap();
for ident in idents {
fields_snippet.push_str(&format!("{}, ", ident));
}
fields_snippet.push_str(&last_ident.to_string());
let base_snippet = if let Some(base) = base {
format!(", ..{}", snippet(cx, base.span, ".."))
} else {
String::new()
};
let sugg = format!("{} {{ {}{} }}",
snippet(cx, qpath.span(), ".."),
fields_snippet,
base_snippet,
);
span_lint_and_sugg(
cx,
INCONSISTENT_STRUCT_CONSTRUCTOR,
expr.span,
"inconsistent struct constructor",
"try",
sugg,
Applicability::MachineApplicable,
)
}
}
}
}
// Check whether the order of the fields in the constructor is consistent with the order in the
// definition.
fn is_consistent_order<'tcx>(fields: &'tcx [hir::Field<'tcx>], def_order_map: &FxHashMap<Symbol, usize>) -> bool {
let mut cur_idx = usize::MIN;
for f in fields {
let next_idx = def_order_map[&f.ident.name];
if cur_idx > next_idx {
return false;
}
cur_idx = next_idx;
}
true
}

View File

@@ -106,6 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString {
let decl = &signature.decl;
if decl.implicit_self.has_implicit_self();
if decl.inputs.len() == 1;
if impl_item.generics.params.is_empty();
// Check if return type is String
if is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id()), sym::string_type);

View File

@@ -1,10 +1,7 @@
// 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)]
@@ -149,6 +146,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;
@@ -181,6 +192,7 @@ mod copy_iterator;
mod create_dir;
mod dbg_macro;
mod default;
mod default_numeric_fallback;
mod dereference;
mod derive;
mod disallowed_method;
@@ -210,6 +222,7 @@ mod floating_point_arithmetic;
mod format;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
mod functions;
mod future_not_send;
mod get_last_with_len;
@@ -219,6 +232,7 @@ mod if_let_some_result;
mod if_not_else;
mod implicit_return;
mod implicit_saturating_sub;
mod inconsistent_struct_constructor;
mod indexing_slicing;
mod infinite_iter;
mod inherent_impl;
@@ -239,6 +253,7 @@ mod loops;
mod macro_use;
mod main_recursion;
mod manual_async_fn;
mod manual_map;
mod manual_non_exhaustive;
mod manual_ok_or;
mod manual_strip;
@@ -585,6 +600,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&dbg_macro::DBG_MACRO,
&default::DEFAULT_TRAIT_ACCESS,
&default::FIELD_REASSIGN_WITH_DEFAULT,
&default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
&dereference::EXPLICIT_DEREF_METHODS,
&derive::DERIVE_HASH_XOR_EQ,
&derive::DERIVE_ORD_XOR_PARTIAL_ORD,
@@ -637,6 +653,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&formatting::SUSPICIOUS_ELSE_FORMATTING,
&formatting::SUSPICIOUS_UNARY_OP_FORMATTING,
&from_over_into::FROM_OVER_INTO,
&from_str_radix_10::FROM_STR_RADIX_10,
&functions::DOUBLE_MUST_USE,
&functions::MUST_USE_CANDIDATE,
&functions::MUST_USE_UNIT,
@@ -652,6 +669,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&if_not_else::IF_NOT_ELSE,
&implicit_return::IMPLICIT_RETURN,
&implicit_saturating_sub::IMPLICIT_SATURATING_SUB,
&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR,
&indexing_slicing::INDEXING_SLICING,
&indexing_slicing::OUT_OF_BOUNDS_INDEXING,
&infinite_iter::INFINITE_ITER,
@@ -702,6 +720,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&macro_use::MACRO_USE_IMPORTS,
&main_recursion::MAIN_RECURSION,
&manual_async_fn::MANUAL_ASYNC_FN,
&manual_map::MANUAL_MAP,
&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
&manual_ok_or::MANUAL_OK_OR,
&manual_strip::MANUAL_STRIP,
@@ -1031,6 +1050,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box strings::StringAdd);
store.register_late_pass(|| box implicit_return::ImplicitReturn);
store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback);
store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor);
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
@@ -1256,6 +1277,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || box types::PtrAsPtr::new(msrv));
store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons);
store.register_late_pass(|| box redundant_slicing::RedundantSlicing);
store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
store.register_late_pass(|| box manual_map::ManualMap);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1265,6 +1288,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
LintId::of(&create_dir::CREATE_DIR),
LintId::of(&dbg_macro::DBG_MACRO),
LintId::of(&default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE),
LintId::of(&exhaustive_items::EXHAUSTIVE_ENUMS),
LintId::of(&exhaustive_items::EXHAUSTIVE_STRUCTS),
@@ -1389,8 +1413,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::PTR_AS_PTR),
LintId::of(&unicode::NON_ASCII_LITERAL),
LintId::of(&unicode::UNICODE_NOT_NFC),
LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(&unnested_or_patterns::UNNESTED_OR_PATTERNS),
LintId::of(&unused_self::UNUSED_SELF),
LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS),
LintId::of(&wildcard_imports::ENUM_GLOB_USE),
LintId::of(&wildcard_imports::WILDCARD_IMPORTS),
LintId::of(&zero_sized_map_values::ZERO_SIZED_MAP_VALUES),
@@ -1468,6 +1494,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
LintId::of(&from_over_into::FROM_OVER_INTO),
LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
LintId::of(&functions::DOUBLE_MUST_USE),
LintId::of(&functions::MUST_USE_UNIT),
LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
@@ -1477,6 +1504,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&identity_op::IDENTITY_OP),
LintId::of(&if_let_mutex::IF_LET_MUTEX),
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
LintId::of(&infinite_iter::INFINITE_ITER),
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
@@ -1512,6 +1540,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
LintId::of(&main_recursion::MAIN_RECURSION),
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(&manual_map::MANUAL_MAP),
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(&manual_strip::MANUAL_STRIP),
LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR),
@@ -1682,13 +1711,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS),
LintId::of(&unnamed_address::VTABLE_ADDRESS_COMPARISONS),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT),
LintId::of(&unused_unit::UNUSED_UNIT),
LintId::of(&unwrap::PANICKING_UNWRAP),
LintId::of(&unwrap::UNNECESSARY_UNWRAP),
LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS),
LintId::of(&useless_conversion::USELESS_CONVERSION),
LintId::of(&vec::USELESS_VEC),
LintId::of(&vec_init_then_push::VEC_INIT_THEN_PUSH),
@@ -1724,10 +1751,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&formatting::SUSPICIOUS_ELSE_FORMATTING),
LintId::of(&formatting::SUSPICIOUS_UNARY_OP_FORMATTING),
LintId::of(&from_over_into::FROM_OVER_INTO),
LintId::of(&from_str_radix_10::FROM_STR_RADIX_10),
LintId::of(&functions::DOUBLE_MUST_USE),
LintId::of(&functions::MUST_USE_UNIT),
LintId::of(&functions::RESULT_UNIT_ERR),
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
LintId::of(&inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR),
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
LintId::of(&len_zero::COMPARISON_TO_EMPTY),
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
@@ -1741,6 +1770,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
LintId::of(&main_recursion::MAIN_RECURSION),
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(&manual_map::MANUAL_MAP),
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(&map_clone::MAP_CLONE),
LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
@@ -1805,7 +1835,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(&unused_unit::UNUSED_UNIT),
LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS),
LintId::of(&write::PRINTLN_EMPTY_STRING),
LintId::of(&write::PRINT_LITERAL),
LintId::of(&write::PRINT_WITH_NEWLINE),
@@ -1899,7 +1928,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&types::UNNECESSARY_CAST),
LintId::of(&types::VEC_BOX),
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS),
LintId::of(&unwrap::UNNECESSARY_UNWRAP),
LintId::of(&useless_conversion::USELESS_CONVERSION),
LintId::of(&zero_div_zero::ZERO_DIVIDED_BY_ZERO),

View File

@@ -0,0 +1,274 @@
use crate::{
map_unit_fn::OPTION_MAP_UNIT_FN,
matches::MATCH_AS_REF,
utils::{
is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, peel_hir_expr_refs,
peel_mid_ty_refs_is_mutable, snippet_with_applicability, span_lint_and_sugg,
},
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::{sym, Ident};
declare_clippy_lint! {
/// **What it does:** Checks for usages of `match` which could be implemented using `map`
///
/// **Why is this bad?** Using the `map` method is clearer and more concise.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// match Some(0) {
/// Some(x) => Some(x + 1),
/// None => None,
/// };
/// ```
/// Use instead:
/// ```rust
/// Some(0).map(|x| x + 1);
/// ```
pub MANUAL_MAP,
style,
"reimplementation of `map`"
}
declare_lint_pass!(ManualMap => [MANUAL_MAP]);
impl LateLintPass<'_> for ManualMap {
#[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Match(scrutinee, [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }], _) =
expr.kind
{
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
|| !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type)
{
return;
}
let (some_expr, some_pat, pat_ref_count, is_wild_none) =
match (try_parse_pattern(cx, arm1.pat), try_parse_pattern(cx, arm2.pat)) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count }))
if is_none_expr(cx, arm1.body) =>
{
(arm2.body, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count }))
if is_none_expr(cx, arm1.body) =>
{
(arm2.body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild))
if is_none_expr(cx, arm2.body) =>
{
(arm1.body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None))
if is_none_expr(cx, arm2.body) =>
{
(arm1.body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr) {
Some(expr) => expr,
None => return,
};
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
&& !is_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. `as_ref` will be called,
// the type is copyable, or the option is being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let scrutinee_str = snippet_with_applicability(cx, scrutinee.span, "_", &mut app);
let scrutinee_str = if expr.precedence().order() < PREC_POSTFIX {
// Parens are needed to chain method calls.
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind {
if let Some(func) = can_pass_as_func(cx, some_binding, some_expr) {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if match_var(some_expr, some_binding.name)
&& !is_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_applicability(cx, some_expr.span, "..", &mut app)
)
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_applicability(cx, some_pat.span, "..", &mut app),
snippet_with_applicability(cx, some_expr.span, "..", &mut app)
)
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str),
app,
);
}
}
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if match_var(arg, binding.name) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
{
Some(func)
},
_ => None,
}
}
enum OptionPat<'a> {
Wild,
None,
Some {
// The pattern contained in the `Some` tuple.
pattern: &'a Pat<'a>,
// The number of references before the `Some` tuple.
// e.g. `&&Some(_)` has a ref count of 2.
ref_count: usize,
},
}
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<OptionPat<'tcx>> {
fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1),
PatKind::Path(QPath::Resolved(None, path))
if path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)) =>
{
Some(OptionPat::None)
},
PatKind::TupleStruct(QPath::Resolved(None, path), [pattern], _)
if path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
}
f(cx, pat, 0)
}
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(QPath::Resolved(None, path)),
..
},
[arg],
) => {
if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) {
Some(arg)
} else {
None
}
},
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
..
},
_,
) => get_some_expr(cx, expr),
_ => None,
}
}
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
match expr.kind {
ExprKind::Path(QPath::Resolved(None, path)) => path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)),
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
..
},
_,
) => is_none_expr(cx, expr),
_ => false,
}
}

View File

@@ -3,19 +3,19 @@ use crate::utils::sugg::Sugg;
use crate::utils::visitors::LocalUsedVisitor;
use crate::utils::{
expr_block, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local_id,
peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, snippet_opt,
snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
strip_pat_refs,
is_type_diagnostic_item, is_wild, match_qpath, match_type, meets_msrv, multispan_sugg, path_to_local,
path_to_local_id, peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block,
snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
span_lint_and_then, strip_pat_refs,
};
use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::def::CtorKind;
use rustc_hir::{
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, Local, MatchSource, Mutability, Node, Pat,
Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, Mutability, Node, Pat,
PatKind, QPath, RangeEnd,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -24,7 +24,7 @@ use rustc_middle::ty::{self, Ty, TyS};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::{Span, Spanned};
use rustc_span::{sym, Symbol};
use rustc_span::sym;
use std::cmp::Ordering;
use std::collections::hash_map::Entry;
use std::collections::Bound;
@@ -1873,13 +1873,6 @@ fn test_overlapping() {
/// Implementation of `MATCH_SAME_ARMS`.
fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> bool {
lhs.len() == rhs.len()
&& lhs
.iter()
.all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty)))
}
if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind {
let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
let mut h = SpanlessHash::new(cx);
@@ -1891,12 +1884,38 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
let min_index = usize::min(lindex, rindex);
let max_index = usize::max(lindex, rindex);
let mut local_map: FxHashMap<HirId, HirId> = FxHashMap::default();
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
if_chain! {
if let Some(a_id) = path_to_local(a);
if let Some(b_id) = path_to_local(b);
let entry = match local_map.entry(a_id) {
Entry::Vacant(entry) => entry,
// check if using the same bindings as before
Entry::Occupied(entry) => return *entry.get() == b_id,
};
// the names technically don't have to match; this makes the lint more conservative
if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id);
if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b));
if pat_contains_local(lhs.pat, a_id);
if pat_contains_local(rhs.pat, b_id);
then {
entry.insert(b_id);
true
} else {
false
}
}
};
// Arms with a guard are ignored, those cant always be merged together
// This is also the case for arms in-between each there is an arm with a guard
(min_index..=max_index).all(|index| arms[index].guard.is_none()) &&
SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) &&
// all patterns should have the same bindings
same_bindings(&bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat))
(min_index..=max_index).all(|index| arms[index].guard.is_none())
&& SpanlessEq::new(cx)
.expr_fallback(eq_fallback)
.eq_expr(&lhs.body, &rhs.body)
// these checks could be removed to allow unused bindings
&& bindings_eq(lhs.pat, local_map.keys().copied().collect())
&& bindings_eq(rhs.pat, local_map.values().copied().collect())
};
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
@@ -1939,50 +1958,18 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
}
}
/// Returns the list of bindings in a pattern.
fn bindings<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> FxHashMap<Symbol, Ty<'tcx>> {
fn bindings_impl<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) {
match pat.kind {
PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map),
PatKind::TupleStruct(_, pats, _) => {
for pat in pats {
bindings_impl(cx, pat, map);
}
},
PatKind::Binding(.., ident, ref as_pat) => {
if let Entry::Vacant(v) = map.entry(ident.name) {
v.insert(cx.typeck_results().pat_ty(pat));
}
if let Some(ref as_pat) = *as_pat {
bindings_impl(cx, as_pat, map);
}
},
PatKind::Or(fields) | PatKind::Tuple(fields, _) => {
for pat in fields {
bindings_impl(cx, pat, map);
}
},
PatKind::Struct(_, fields, _) => {
for pat in fields {
bindings_impl(cx, &pat.pat, map);
}
},
PatKind::Slice(lhs, ref mid, rhs) => {
for pat in lhs {
bindings_impl(cx, pat, map);
}
if let Some(ref mid) = *mid {
bindings_impl(cx, mid, map);
}
for pat in rhs {
bindings_impl(cx, pat, map);
}
},
PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (),
}
}
let mut result = FxHashMap::default();
bindings_impl(cx, pat, &mut result);
fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
let mut result = false;
pat.walk_short(|p| {
result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id);
!result
});
result
}
/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
fn bindings_eq(pat: &Pat<'_>, mut ids: FxHashSet<HirId>) -> bool {
let mut result = true;
pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
result && ids.is_empty()
}

View File

@@ -90,10 +90,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View File

@@ -1,7 +1,6 @@
use crate::utils::{span_lint, span_lint_and_then};
use rustc_ast::ast::{
Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, FnKind, Item, ItemKind, Local, Pat,
PatKind,
Arm, AssocItem, AssocItemKind, Attribute, Block, FnDecl, FnKind, Item, ItemKind, Local, Pat, PatKind,
};
use rustc_ast::visit::{walk_block, walk_expr, walk_pat, Visitor};
use rustc_lint::{EarlyContext, EarlyLintPass};

View File

@@ -212,10 +212,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
if !expr_borrows(cx, left_expr) {
return Some(LintTrigger::SortByKey(SortByKeyDetection {
vec_name,
unstable,
closure_arg,
closure_body,
reverse
reverse,
unstable,
}));
}
}

View File

@@ -1,5 +1,5 @@
use crate::utils::{
contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then,
contains_return, in_macro, match_qpath, paths, return_ty, snippet, span_lint_and_then,
visitors::find_all_ret_expressions,
};
use if_chain::if_chain;
@@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
use rustc_span::Span;
@@ -17,8 +17,8 @@ declare_clippy_lint! {
///
/// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned.
///
/// **Known problems:** Since this lint changes function type signature, you may need to
/// adjust some code at callee side.
/// **Known problems:** There can be false positives if the function signature is designed to
/// fit some external requirement.
///
/// **Example:**
///
@@ -48,7 +48,7 @@ declare_clippy_lint! {
/// }
/// ```
pub UNNECESSARY_WRAPS,
complexity,
pedantic,
"functions that only return `Ok` or `Some`"
}
@@ -64,6 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
span: Span,
hir_id: HirId,
) {
// Abort if public function/method or closure.
match fn_kind {
FnKind::ItemFn(.., visibility, _) | FnKind::Method(.., Some(visibility), _) => {
if visibility.node.is_pub() {
@@ -74,6 +75,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
_ => (),
}
// Abort if the method is implementing a trait or of it a trait method.
if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
if matches!(
item.kind,
@@ -83,25 +85,44 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
}
}
let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::option_type) {
("Option", &paths::OPTION_SOME)
} else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym::result_type) {
("Result", &paths::RESULT_OK)
// Get the wrapper and inner types, if can't, abort.
let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) {
("Option", &paths::OPTION_SOME, subst.type_at(0))
} else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) {
("Result", &paths::RESULT_OK, subst.type_at(0))
} else {
return;
}
} else {
return;
};
// Check if all return expression respect the following condition and collect them.
let mut suggs = Vec::new();
let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
if_chain! {
if !in_macro(ret_expr.span);
// Check if a function call.
if let ExprKind::Call(ref func, ref args) = ret_expr.kind;
// Get the Path of the function call.
if let ExprKind::Path(ref qpath) = func.kind;
// Check if OPTION_SOME or RESULT_OK, depending on return type.
if match_qpath(qpath, path);
if args.len() == 1;
// Make sure the function argument does not contain a return expression.
if !contains_return(&args[0]);
then {
suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string()));
suggs.push(
(
ret_expr.span,
if inner_type.is_unit() {
"".to_string()
} else {
snippet(cx, args[0].span.source_callsite(), "..").to_string()
}
)
);
true
} else {
false
@@ -110,39 +131,34 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
});
if can_sugg && !suggs.is_empty() {
span_lint_and_then(
cx,
UNNECESSARY_WRAPS,
span,
format!(
"this function's return value is unnecessarily wrapped by `{}`",
return_type
let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
(
"this function's return value is unnecessary".to_string(),
"remove the return type...".to_string(),
snippet(cx, fn_decl.output.span(), "..").to_string(),
"...and then remove returned values",
)
.as_str(),
|diag| {
let inner_ty = return_ty(cx, hir_id)
.walk()
.skip(1) // skip `std::option::Option` or `std::result::Result`
.take(1) // take the first outermost inner type
.filter_map(|inner| match inner.unpack() {
GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()),
_ => None,
});
inner_ty.for_each(|inner_ty| {
diag.span_suggestion(
fn_decl.output.span(),
format!("remove `{}` from the return type...", return_type).as_str(),
inner_ty,
Applicability::MaybeIncorrect,
);
});
diag.multipart_suggestion(
"...and change the returning expressions",
suggs,
Applicability::MaybeIncorrect,
);
},
);
} else {
(
format!(
"this function's return value is unnecessarily wrapped by `{}`",
return_type_label
),
format!("remove `{}` from the return type...", return_type_label),
inner_type.to_string(),
"...and then change returning expressions",
)
};
span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
diag.span_suggestion(
fn_decl.output.span(),
return_type_sugg_msg.as_str(),
return_type_sugg,
Applicability::MaybeIncorrect,
);
diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);
});
}
}
}

View File

@@ -29,7 +29,7 @@ declare_clippy_lint! {
/// struct HttpResponse;
/// ```
pub UPPER_CASE_ACRONYMS,
style,
pedantic,
"capitalized acronyms are against the naming convention"
}

View File

@@ -1,24 +1,24 @@
use crate::utils::{in_macro, meets_msrv, snippet_opt, span_lint_and_sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_item, walk_path, walk_ty, NestedVisitorMap, Visitor};
use rustc_hir::def::DefKind;
use rustc_hir::{
def, FnDecl, FnRetTy, FnSig, GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Path, PathSegment, QPath,
TyKind,
def,
def_id::LocalDefId,
intravisit::{walk_ty, NestedVisitorMap, Visitor},
Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Node, Path, PathSegment,
QPath, TyKind,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_middle::ty::{DefIdTree, Ty};
use rustc_middle::ty::{AssocKind, Ty, TyS};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::kw;
use rustc_span::{BytePos, Span};
use rustc_typeck::hir_ty_to_ty;
use crate::utils::{differing_macro_contexts, meets_msrv, span_lint_and_sugg};
declare_clippy_lint! {
/// **What it does:** Checks for unnecessary repetition of structure name when a
/// replacement with `Self` is applicable.
@@ -28,10 +28,11 @@ declare_clippy_lint! {
/// feels inconsistent.
///
/// **Known problems:**
/// - False positive when using associated types ([#2843](https://github.com/rust-lang/rust-clippy/issues/2843))
/// - False positives in some situations when using generics ([#3410](https://github.com/rust-lang/rust-clippy/issues/3410))
/// - Unaddressed false negative in fn bodies of trait implementations
/// - False positive with assotiated types in traits (#4140)
///
/// **Example:**
///
/// ```rust
/// struct Foo {}
/// impl Foo {
@@ -54,52 +55,326 @@ declare_clippy_lint! {
"unnecessary structure name repetition whereas `Self` is applicable"
}
#[derive(Default)]
pub struct UseSelf {
msrv: Option<RustcVersion>,
stack: Vec<StackItem>,
}
const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0);
impl UseSelf {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self {
msrv,
..Self::default()
}
}
}
#[derive(Debug)]
enum StackItem {
Check {
hir_id: HirId,
impl_trait_ref_def_id: Option<LocalDefId>,
types_to_skip: Vec<HirId>,
types_to_lint: Vec<HirId>,
},
NoCheck,
}
impl_lint_pass!(UseSelf => [USE_SELF]);
const SEGMENTS_MSG: &str = "segments should be composed of at least 1 element";
fn span_use_self_lint(cx: &LateContext<'_>, path: &Path<'_>, last_segment: Option<&PathSegment<'_>>) {
let last_segment = last_segment.unwrap_or_else(|| path.segments.last().expect(SEGMENTS_MSG));
// Path segments only include actual path, no methods or fields.
let last_path_span = last_segment.ident.span;
if differing_macro_contexts(path.span, last_path_span) {
return;
impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
// We push the self types of `impl`s on a stack here. Only the top type on the stack is
// relevant for linting, since this is the self type of the `impl` we're currently in. To
// avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that
// we're in an `impl` or nested item, that we don't want to lint
//
// NB: If you push something on the stack in this method, remember to also pop it in the
// `check_item_post` method.
match &item.kind {
ItemKind::Impl(Impl {
self_ty: hir_self_ty,
of_trait,
..
}) => {
let should_check = if let TyKind::Path(QPath::Resolved(_, ref item_path)) = hir_self_ty.kind {
let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
parameters.as_ref().map_or(true, |params| {
!params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
})
} else {
false
};
let impl_trait_ref_def_id = of_trait.as_ref().map(|_| cx.tcx.hir().local_def_id(item.hir_id()));
if should_check {
self.stack.push(StackItem::Check {
hir_id: hir_self_ty.hir_id,
impl_trait_ref_def_id,
types_to_lint: Vec::new(),
types_to_skip: Vec::new(),
});
} else {
self.stack.push(StackItem::NoCheck);
}
},
ItemKind::Static(..)
| ItemKind::Const(..)
| ItemKind::Fn(..)
| ItemKind::Enum(..)
| ItemKind::Struct(..)
| ItemKind::Union(..)
| ItemKind::Trait(..) => {
self.stack.push(StackItem::NoCheck);
},
_ => (),
}
}
// Only take path up to the end of last_path_span.
let span = path.span.with_hi(last_path_span.hi());
fn check_item_post(&mut self, _: &LateContext<'_>, item: &Item<'_>) {
use ItemKind::{Const, Enum, Fn, Impl, Static, Struct, Trait, Union};
match item.kind {
Impl { .. } | Static(..) | Const(..) | Fn(..) | Enum(..) | Struct(..) | Union(..) | Trait(..) => {
self.stack.pop();
},
_ => (),
}
}
span_lint_and_sugg(
cx,
USE_SELF,
span,
"unnecessary structure name repetition",
"use the applicable keyword",
"Self".to_owned(),
Applicability::MachineApplicable,
);
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &hir::ImplItem<'_>) {
// We want to skip types in trait `impl`s that aren't declared as `Self` in the trait
// declaration. The collection of those types is all this method implementation does.
if_chain! {
if let ImplItemKind::Fn(FnSig { decl, .. }, ..) = impl_item.kind;
if let Some(&mut StackItem::Check {
impl_trait_ref_def_id: Some(def_id),
ref mut types_to_skip,
..
}) = self.stack.last_mut();
if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(def_id);
then {
// `self_ty` is the semantic self type of `impl <trait> for <type>`. This cannot be
// `Self`.
let self_ty = impl_trait_ref.self_ty();
// `trait_method_sig` is the signature of the function, how it is declared in the
// trait, not in the impl of the trait.
let trait_method = cx
.tcx
.associated_items(impl_trait_ref.def_id)
.find_by_name_and_kind(cx.tcx, impl_item.ident, AssocKind::Fn, impl_trait_ref.def_id)
.expect("impl method matches a trait method");
let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id);
let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
// `impl_inputs_outputs` is an iterator over the types (`hir::Ty`) declared in the
// implementation of the trait.
let output_hir_ty = if let FnRetTy::Return(ty) = &decl.output {
Some(&**ty)
} else {
None
};
let impl_inputs_outputs = decl.inputs.iter().chain(output_hir_ty);
// `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
//
// `trait_sem_ty` (of type `ty::Ty`) is the semantic type for the signature in the
// trait declaration. This is used to check if `Self` was used in the trait
// declaration.
//
// If `any`where in the `trait_sem_ty` the `self_ty` was used verbatim (as opposed
// to `Self`), we want to skip linting that type and all subtypes of it. This
// avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`, in an `impl Trait
// for u8`, when the trait always uses `Vec<u8>`.
//
// See also https://github.com/rust-lang/rust-clippy/issues/2894.
for (impl_hir_ty, trait_sem_ty) in impl_inputs_outputs.zip(trait_method_sig.inputs_and_output) {
if trait_sem_ty.walk().any(|inner| inner == self_ty.into()) {
let mut visitor = SkipTyCollector::default();
visitor.visit_ty(&impl_hir_ty);
types_to_skip.extend(visitor.types_to_skip);
}
}
}
}
}
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) {
// `hir_ty_to_ty` cannot be called in `Body`s or it will panic (sometimes). But in bodies
// we can use `cx.typeck_results.node_type(..)` to get the `ty::Ty` from a `hir::Ty`.
// However the `node_type()` method can *only* be called in bodies.
//
// This method implementation determines which types should get linted in a `Body` and
// which shouldn't, with a visitor. We could directly lint in the visitor, but then we
// could only allow this lint on item scope. And we would have to check if those types are
// already dealt with in `check_ty` anyway.
if let Some(StackItem::Check {
hir_id,
types_to_lint,
types_to_skip,
..
}) = self.stack.last_mut()
{
let self_ty = ty_from_hir_id(cx, *hir_id);
let mut visitor = LintTyCollector {
cx,
self_ty,
types_to_lint: vec![],
types_to_skip: vec![],
};
visitor.visit_expr(&body.value);
types_to_lint.extend(visitor.types_to_lint);
types_to_skip.extend(visitor.types_to_skip);
}
}
fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
if in_macro(hir_ty.span) | in_impl(cx, hir_ty) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
return;
}
let lint_dependend_on_expr_kind = if let Some(StackItem::Check {
hir_id,
types_to_lint,
types_to_skip,
..
}) = self.stack.last()
{
if types_to_skip.contains(&hir_ty.hir_id) {
false
} else if types_to_lint.contains(&hir_ty.hir_id) {
true
} else {
let self_ty = ty_from_hir_id(cx, *hir_id);
should_lint_ty(hir_ty, hir_ty_to_ty(cx.tcx, hir_ty), self_ty)
}
} else {
false
};
if lint_dependend_on_expr_kind {
// FIXME: this span manipulation should not be necessary
// @flip1995 found an ast lowering issue in
// https://github.com/rust-lang/rust/blob/master/src/librustc_ast_lowering/path.rs#l142-l162
match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_ty.hir_id)) {
Some(Node::Expr(Expr {
kind: ExprKind::Path(QPath::TypeRelative(_, segment)),
..
})) => span_lint_until_last_segment(cx, hir_ty.span, segment),
_ => span_lint(cx, hir_ty.span),
}
}
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
fn expr_ty_matches(cx: &LateContext<'_>, expr: &Expr<'_>, self_ty: Ty<'_>) -> bool {
let def_id = expr.hir_id.owner;
if cx.tcx.has_typeck_results(def_id) {
cx.tcx.typeck(def_id).expr_ty_opt(expr) == Some(self_ty)
} else {
false
}
}
if in_macro(expr.span) | !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
return;
}
if let Some(StackItem::Check { hir_id, .. }) = self.stack.last() {
let self_ty = ty_from_hir_id(cx, *hir_id);
match &expr.kind {
ExprKind::Struct(QPath::Resolved(_, path), ..) => {
if expr_ty_matches(cx, expr, self_ty) {
match path.res {
def::Res::SelfTy(..) => (),
def::Res::Def(DefKind::Variant, _) => span_lint_on_path_until_last_segment(cx, path),
_ => {
span_lint(cx, path.span);
},
}
}
},
// tuple struct instantiation (`Foo(arg)` or `Enum::Foo(arg)`)
ExprKind::Call(fun, _) => {
if let Expr {
kind: ExprKind::Path(ref qpath),
..
} = fun
{
if expr_ty_matches(cx, expr, self_ty) {
let res = cx.qpath_res(qpath, fun.hir_id);
if let def::Res::Def(DefKind::Ctor(ctor_of, _), ..) = res {
match ctor_of {
def::CtorOf::Variant => {
span_lint_on_qpath_resolved(cx, qpath, true);
},
def::CtorOf::Struct => {
span_lint_on_qpath_resolved(cx, qpath, false);
},
}
}
}
}
},
// unit enum variants (`Enum::A`)
ExprKind::Path(qpath) => {
if expr_ty_matches(cx, expr, self_ty) {
span_lint_on_qpath_resolved(cx, &qpath, true);
}
},
_ => (),
}
}
}
extract_msrv_attr!(LateContext);
}
// FIXME: always use this (more correct) visitor, not just in method signatures.
struct SemanticUseSelfVisitor<'a, 'tcx> {
#[derive(Default)]
struct SkipTyCollector {
types_to_skip: Vec<HirId>,
}
impl<'tcx> Visitor<'tcx> for SkipTyCollector {
type Map = Map<'tcx>;
fn visit_ty(&mut self, hir_ty: &hir::Ty<'_>) {
self.types_to_skip.push(hir_ty.hir_id);
walk_ty(self, hir_ty)
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
struct LintTyCollector<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
self_ty: Ty<'tcx>,
types_to_lint: Vec<HirId>,
types_to_skip: Vec<HirId>,
}
impl<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for LintTyCollector<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_ty(&mut self, hir_ty: &'tcx hir::Ty<'_>) {
if let TyKind::Path(QPath::Resolved(_, path)) = &hir_ty.kind {
match path.res {
def::Res::SelfTy(..) => {},
_ => {
if hir_ty_to_ty(self.cx.tcx, hir_ty) == self.self_ty {
span_use_self_lint(self.cx, path, None);
}
},
if_chain! {
if let Some(ty) = self.cx.typeck_results().node_type_opt(hir_ty.hir_id);
if should_lint_ty(hir_ty, ty, self.self_ty);
then {
self.types_to_lint.push(hir_ty.hir_id);
} else {
self.types_to_skip.push(hir_ty.hir_id);
}
}
@@ -111,177 +386,78 @@ impl<'a, 'tcx> Visitor<'tcx> for SemanticUseSelfVisitor<'a, 'tcx> {
}
}
fn check_trait_method_impl_decl<'tcx>(
cx: &LateContext<'tcx>,
impl_item: &ImplItem<'_>,
impl_decl: &'tcx FnDecl<'_>,
impl_trait_ref: ty::TraitRef<'tcx>,
) {
let trait_method = cx
.tcx
.associated_items(impl_trait_ref.def_id)
.find_by_name_and_kind(cx.tcx, impl_item.ident, ty::AssocKind::Fn, impl_trait_ref.def_id)
.expect("impl method matches a trait method");
fn span_lint(cx: &LateContext<'_>, span: Span) {
span_lint_and_sugg(
cx,
USE_SELF,
span,
"unnecessary structure name repetition",
"use the applicable keyword",
"Self".to_owned(),
Applicability::MachineApplicable,
);
}
let trait_method_sig = cx.tcx.fn_sig(trait_method.def_id);
let trait_method_sig = cx.tcx.erase_late_bound_regions(trait_method_sig);
let output_hir_ty = if let FnRetTy::Return(ty) = &impl_decl.output {
Some(&**ty)
} else {
None
#[allow(clippy::cast_possible_truncation)]
fn span_lint_until_last_segment(cx: &LateContext<'_>, span: Span, segment: &PathSegment<'_>) {
let sp = span.with_hi(segment.ident.span.lo());
// remove the trailing ::
let span_without_last_segment = match snippet_opt(cx, sp) {
Some(snippet) => match snippet.rfind("::") {
Some(bidx) => sp.with_hi(sp.lo() + BytePos(bidx as u32)),
None => sp,
},
None => sp,
};
span_lint(cx, span_without_last_segment);
}
// `impl_hir_ty` (of type `hir::Ty`) represents the type written in the signature.
// `trait_ty` (of type `ty::Ty`) is the semantic type for the signature in the trait.
// We use `impl_hir_ty` to see if the type was written as `Self`,
// `hir_ty_to_ty(...)` to check semantic types of paths, and
// `trait_ty` to determine which parts of the signature in the trait, mention
// the type being implemented verbatim (as opposed to `Self`).
for (impl_hir_ty, trait_ty) in impl_decl
.inputs
.iter()
.chain(output_hir_ty)
.zip(trait_method_sig.inputs_and_output)
{
// Check if the input/output type in the trait method specifies the implemented
// type verbatim, and only suggest `Self` if that isn't the case.
// This avoids suggestions to e.g. replace `Vec<u8>` with `Vec<Self>`,
// in an `impl Trait for u8`, when the trait always uses `Vec<u8>`.
// See also https://github.com/rust-lang/rust-clippy/issues/2894.
let self_ty = impl_trait_ref.self_ty();
if !trait_ty.walk().any(|inner| inner == self_ty.into()) {
let mut visitor = SemanticUseSelfVisitor { cx, self_ty };
fn span_lint_on_path_until_last_segment(cx: &LateContext<'_>, path: &Path<'_>) {
if path.segments.len() > 1 {
span_lint_until_last_segment(cx, path.span, path.segments.last().unwrap());
}
}
visitor.visit_ty(&impl_hir_ty);
fn span_lint_on_qpath_resolved(cx: &LateContext<'_>, qpath: &QPath<'_>, until_last_segment: bool) {
if let QPath::Resolved(_, path) = qpath {
if until_last_segment {
span_lint_on_path_until_last_segment(cx, path);
} else {
span_lint(cx, path.span);
}
}
}
const USE_SELF_MSRV: RustcVersion = RustcVersion::new(1, 37, 0);
pub struct UseSelf {
msrv: Option<RustcVersion>,
}
impl UseSelf {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
fn ty_from_hir_id<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Ty<'tcx> {
if let Some(Node::Ty(hir_ty)) = cx.tcx.hir().find(hir_id) {
hir_ty_to_ty(cx.tcx, hir_ty)
} else {
unreachable!("This function should only be called with `HirId`s that are for sure `Node::Ty`")
}
}
impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !meets_msrv(self.msrv.as_ref(), &USE_SELF_MSRV) {
return;
}
if in_external_macro(cx.sess(), item.span) {
return;
}
if_chain! {
if let ItemKind::Impl(impl_) = &item.kind;
if let TyKind::Path(QPath::Resolved(_, ref item_path)) = impl_.self_ty.kind;
then {
let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
let should_check = parameters.as_ref().map_or(
true,
|params| !params.parenthesized
&&!params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
);
if should_check {
let visitor = &mut UseSelfVisitor {
item_path,
cx,
};
let impl_trait_ref = cx.tcx.impl_trait_ref(item.def_id);
if let Some(impl_trait_ref) = impl_trait_ref {
for impl_item_ref in impl_.items {
let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
if let ImplItemKind::Fn(FnSig{ decl: impl_decl, .. }, impl_body_id)
= &impl_item.kind {
check_trait_method_impl_decl(cx, impl_item, impl_decl, impl_trait_ref);
let body = cx.tcx.hir().body(*impl_body_id);
visitor.visit_body(body);
} else {
visitor.visit_impl_item(impl_item);
}
}
} else {
for impl_item_ref in impl_.items {
let impl_item = cx.tcx.hir().impl_item(impl_item_ref.id);
visitor.visit_impl_item(impl_item);
}
}
}
}
fn in_impl(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> bool {
let map = cx.tcx.hir();
let parent = map.get_parent_node(hir_ty.hir_id);
if_chain! {
if let Some(Node::Item(item)) = map.find(parent);
if let ItemKind::Impl { .. } = item.kind;
then {
true
} else {
false
}
}
extract_msrv_attr!(LateContext);
}
struct UseSelfVisitor<'a, 'tcx> {
item_path: &'a Path<'a>,
cx: &'a LateContext<'tcx>,
}
impl<'a, 'tcx> Visitor<'tcx> for UseSelfVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_path(&mut self, path: &'tcx Path<'_>, _id: HirId) {
if !path.segments.iter().any(|p| p.ident.span.is_dummy()) {
if path.segments.len() >= 2 {
let last_but_one = &path.segments[path.segments.len() - 2];
if last_but_one.ident.name != kw::SelfUpper {
let enum_def_id = match path.res {
Res::Def(DefKind::Variant, variant_def_id) => self.cx.tcx.parent(variant_def_id),
Res::Def(DefKind::Ctor(def::CtorOf::Variant, _), ctor_def_id) => {
let variant_def_id = self.cx.tcx.parent(ctor_def_id);
variant_def_id.and_then(|def_id| self.cx.tcx.parent(def_id))
},
_ => None,
};
if self.item_path.res.opt_def_id() == enum_def_id {
span_use_self_lint(self.cx, path, Some(last_but_one));
}
}
}
if path.segments.last().expect(SEGMENTS_MSG).ident.name != kw::SelfUpper {
if self.item_path.res == path.res {
span_use_self_lint(self.cx, path, None);
} else if let Res::Def(DefKind::Ctor(def::CtorOf::Struct, _), ctor_def_id) = path.res {
if self.item_path.res.opt_def_id() == self.cx.tcx.parent(ctor_def_id) {
span_use_self_lint(self.cx, path, None);
}
}
}
}
walk_path(self, path);
}
fn visit_item(&mut self, item: &'tcx Item<'_>) {
match item.kind {
ItemKind::Use(..)
| ItemKind::Static(..)
| ItemKind::Enum(..)
| ItemKind::Struct(..)
| ItemKind::Union(..)
| ItemKind::Impl { .. }
| ItemKind::Fn(..) => {
// Don't check statements that shadow `Self` or where `Self` can't be used
},
_ => walk_item(self, item),
fn should_lint_ty(hir_ty: &hir::Ty<'_>, ty: Ty<'_>, self_ty: Ty<'_>) -> bool {
if_chain! {
if TyS::same_type(ty, self_ty);
if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
then {
!matches!(path.res, def::Res::SelfTy(..))
} else {
false
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::All(self.cx.tcx.hir())
}
}

View File

@@ -1,579 +0,0 @@
//! Utilities for manipulating and extracting information from `rustc_ast::ast`.
//!
//! - The `eq_foobar` functions test for semantic equality but ignores `NodeId`s and `Span`s.
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::utils::{both, over};
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, *};
use rustc_span::symbol::Ident;
use std::mem;
pub mod ident_iter;
pub use ident_iter::IdentIter;
pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
use BinOpKind::*;
matches!(
kind,
Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
)
}
/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))
}
pub fn eq_id(l: Ident, r: Ident) -> bool {
l.name == r.name
}
pub fn eq_pat(l: &Pat, r: &Pat) -> bool {
use PatKind::*;
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_pat(l, r),
(_, Paren(r)) => eq_pat(l, r),
(Wild, Wild) | (Rest, Rest) => true,
(Lit(l), Lit(r)) => eq_expr(l, r),
(Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)),
(Range(lf, lt, le), Range(rf, rt, re)) => {
eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node)
},
(Box(l), Box(r))
| (Ref(l, Mutability::Not), Ref(r, Mutability::Not))
| (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r),
(Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(TupleStruct(lp, lfs), TupleStruct(rp, rfs)) => eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)),
(Struct(lp, lfs, lr), Struct(rp, rfs, rr)) => {
lr == rr && eq_path(lp, rp) && unordered_over(lfs, rfs, |lf, rf| eq_field_pat(lf, rf))
},
(Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_range_end(l: &RangeEnd, r: &RangeEnd) -> bool {
match (l, r) {
(RangeEnd::Excluded, RangeEnd::Excluded) => true,
(RangeEnd::Included(l), RangeEnd::Included(r)) => {
matches!(l, RangeSyntax::DotDotEq) == matches!(r, RangeSyntax::DotDotEq)
},
_ => false,
}
}
pub fn eq_field_pat(l: &FieldPat, r: &FieldPat) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& eq_pat(&l.pat, &r.pat)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_qself(l: &QSelf, r: &QSelf) -> bool {
l.position == r.position && eq_ty(&l.ty, &r.ty)
}
pub fn eq_path(l: &Path, r: &Path) -> bool {
over(&l.segments, &r.segments, |l, r| eq_path_seg(l, r))
}
pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool {
eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r))
}
pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool {
match (l, r) {
(GenericArgs::AngleBracketed(l), GenericArgs::AngleBracketed(r)) => {
over(&l.args, &r.args, |l, r| eq_angle_arg(l, r))
},
(GenericArgs::Parenthesized(l), GenericArgs::Parenthesized(r)) => {
over(&l.inputs, &r.inputs, |l, r| eq_ty(l, r)) && eq_fn_ret_ty(&l.output, &r.output)
},
_ => false,
}
}
pub fn eq_angle_arg(l: &AngleBracketedArg, r: &AngleBracketedArg) -> bool {
match (l, r) {
(AngleBracketedArg::Arg(l), AngleBracketedArg::Arg(r)) => eq_generic_arg(l, r),
(AngleBracketedArg::Constraint(l), AngleBracketedArg::Constraint(r)) => eq_assoc_constraint(l, r),
_ => false,
}
}
pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool {
match (l, r) {
(GenericArg::Lifetime(l), GenericArg::Lifetime(r)) => eq_id(l.ident, r.ident),
(GenericArg::Type(l), GenericArg::Type(r)) => eq_ty(l, r),
(GenericArg::Const(l), GenericArg::Const(r)) => eq_expr(&l.value, &r.value),
_ => false,
}
}
pub fn eq_expr_opt(l: &Option<P<Expr>>, r: &Option<P<Expr>>) -> bool {
both(l, r, |l, r| eq_expr(l, r))
}
pub fn eq_struct_rest(l: &StructRest, r: &StructRest) -> bool {
match (l, r) {
(StructRest::Base(lb), StructRest::Base(rb)) => eq_expr(lb, rb),
(StructRest::Rest(_), StructRest::Rest(_)) | (StructRest::None, StructRest::None) => true,
_ => false,
}
}
pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
use ExprKind::*;
if !over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r)) {
return false;
}
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_expr(l, r),
(_, Paren(r)) => eq_expr(l, r),
(Err, Err) => true,
(Box(l), Box(r)) | (Try(l), Try(r)) | (Await(l), Await(r)) => eq_expr(l, r),
(Array(l), Array(r)) | (Tup(l), Tup(r)) => over(l, r, |l, r| eq_expr(l, r)),
(Repeat(le, ls), Repeat(re, rs)) => eq_expr(le, re) && eq_expr(&ls.value, &rs.value),
(Call(lc, la), Call(rc, ra)) => eq_expr(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
(MethodCall(lc, la, _), MethodCall(rc, ra, _)) => eq_path_seg(lc, rc) && over(la, ra, |l, r| eq_expr(l, r)),
(Binary(lo, ll, lr), Binary(ro, rl, rr)) => lo.node == ro.node && eq_expr(ll, rl) && eq_expr(lr, rr),
(Unary(lo, l), Unary(ro, r)) => mem::discriminant(lo) == mem::discriminant(ro) && eq_expr(l, r),
(Lit(l), Lit(r)) => l.kind == r.kind,
(Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt),
(Let(lp, le), Let(rp, re)) => eq_pat(lp, rp) && eq_expr(le, re),
(If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re),
(While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt),
(ForLoop(lp, li, lt, ll), ForLoop(rp, ri, rt, rl)) => {
eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt)
},
(Loop(lt, ll), Loop(rt, rl)) => eq_label(ll, rl) && eq_block(lt, rt),
(Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb),
(TryBlock(l), TryBlock(r)) => eq_block(l, r),
(Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r),
(Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re),
(Continue(ll), Continue(rl)) => eq_label(ll, rl),
(Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2), Index(r1, r2)) => eq_expr(l1, r1) && eq_expr(l2, r2),
(AssignOp(lo, lp, lv), AssignOp(ro, rp, rv)) => lo.node == ro.node && eq_expr(lp, rp) && eq_expr(lv, rv),
(Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp),
(Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, |l, r| eq_arm(l, r)),
(Closure(lc, la, lm, lf, lb, _), Closure(rc, ra, rm, rf, rb, _)) => {
lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(lb, rb)
},
(Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb),
(Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt),
(AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
(Struct(lp, lfs, lb), Struct(rp, rfs, rb)) => {
eq_path(lp, rp) && eq_struct_rest(lb, rb) && unordered_over(lfs, rfs, |l, r| eq_field(l, r))
},
_ => false,
}
}
pub fn eq_field(l: &Field, r: &Field) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& eq_expr(&l.expr, &r.expr)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_arm(l: &Arm, r: &Arm) -> bool {
l.is_placeholder == r.is_placeholder
&& eq_pat(&l.pat, &r.pat)
&& eq_expr(&l.body, &r.body)
&& eq_expr_opt(&l.guard, &r.guard)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_label(l: &Option<Label>, r: &Option<Label>) -> bool {
both(l, r, |l, r| eq_id(l.ident, r.ident))
}
pub fn eq_block(l: &Block, r: &Block) -> bool {
l.rules == r.rules && over(&l.stmts, &r.stmts, |l, r| eq_stmt(l, r))
}
pub fn eq_stmt(l: &Stmt, r: &Stmt) -> bool {
use StmtKind::*;
match (&l.kind, &r.kind) {
(Local(l), Local(r)) => {
eq_pat(&l.pat, &r.pat)
&& both(&l.ty, &r.ty, |l, r| eq_ty(l, r))
&& eq_expr_opt(&l.init, &r.init)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
},
(Item(l), Item(r)) => eq_item(l, r, eq_item_kind),
(Expr(l), Expr(r)) | (Semi(l), Semi(r)) => eq_expr(l, r),
(Empty, Empty) => true,
(MacCall(l), MacCall(r)) => {
l.style == r.style && eq_mac_call(&l.mac, &r.mac) && over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
},
_ => false,
}
}
pub fn eq_item<K>(l: &Item<K>, r: &Item<K>, mut eq_kind: impl FnMut(&K, &K) -> bool) -> bool {
eq_id(l.ident, r.ident)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& eq_kind(&l.kind, &r.kind)
}
pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
use ItemKind::*;
match (l, r) {
(ExternCrate(l), ExternCrate(r)) => l == r,
(Use(l), Use(r)) => eq_use_tree(l, r),
(Static(lt, lm, le), Static(rt, rm, re)) => {
lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re)
}
(Const(ld, lt, le), Const(rd, rt, re)) => {
eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re)
}
(Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& both(lb, rb, |l, r| eq_block(l, r))
}
(Mod(lu, lmk), Mod(ru, rmk)) => lu == ru && match (lmk, rmk) {
(ModKind::Loaded(litems, linline, _), ModKind::Loaded(ritems, rinline, _)) =>
linline == rinline && over(litems, ritems, |l, r| eq_item(l, r, eq_item_kind)),
(ModKind::Unloaded, ModKind::Unloaded) => true,
_ => false,
},
(ForeignMod(l), ForeignMod(r)) => {
both(&l.abi, &r.abi, |l, r| eq_str_lit(l, r))
&& over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
},
(TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(Enum(le, lg), Enum(re, rg)) => {
over(&le.variants, &re.variants, |l, r| eq_variant(l, r)) && eq_generics(lg, rg)
},
(Struct(lv, lg), Struct(rv, rg)) | (Union(lv, lg), Union(rv, rg)) => {
eq_variant_data(lv, rv) && eq_generics(lg, rg)
},
(Trait(box TraitKind(la, lu, lg, lb, li)), Trait(box TraitKind(ra, ru, rg, rb, ri))) => {
la == ra
&& matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
},
(TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, |l, r| eq_generic_bound(l, r)),
(
Impl(box ImplKind {
unsafety: lu,
polarity: lp,
defaultness: ld,
constness: lc,
generics: lg,
of_trait: lot,
self_ty: lst,
items: li,
}),
Impl(box ImplKind {
unsafety: ru,
polarity: rp,
defaultness: rd,
constness: rc,
generics: rg,
of_trait: rot,
self_ty: rst,
items: ri,
}),
) => {
matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No)
&& matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive)
&& eq_defaultness(*ld, *rd)
&& matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No)
&& eq_generics(lg, rg)
&& both(lot, rot, |l, r| eq_path(&l.path, &r.path))
&& eq_ty(lst, rst)
&& over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
(MacroDef(l), MacroDef(r)) => l.macro_rules == r.macro_rules && eq_mac_args(&l.body, &r.body),
_ => false,
}
}
pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
use ForeignItemKind::*;
match (l, r) {
(Static(lt, lm, le), Static(rt, rm, re)) => {
lm == rm && eq_ty(lt, rt) && eq_expr_opt(le, re)
}
(Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => {
eq_defaultness(*ld, *rd)
&& eq_fn_sig(lf, rf)
&& eq_generics(lg, rg)
&& both(lb, rb, |l, r| eq_block(l, r))
}
(TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
use AssocItemKind::*;
match (l, r) {
(Const(ld, lt, le), Const(rd, rt, re)) => eq_defaultness(*ld, *rd) && eq_ty(lt, rt) && eq_expr_opt(le, re),
(Fn(box FnKind(ld, lf, lg, lb)), Fn(box FnKind(rd, rf, rg, rb))) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(box TyAliasKind(ld, lg, lb, lt)), TyAlias(box TyAliasKind(rd, rg, rb, rt))) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, |l, r| eq_generic_bound(l, r))
&& both(lt, rt, |l, r| eq_ty(l, r))
},
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_variant(l: &Variant, r: &Variant) -> bool {
l.is_placeholder == r.is_placeholder
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& eq_id(l.ident, r.ident)
&& eq_variant_data(&l.data, &r.data)
&& both(&l.disr_expr, &r.disr_expr, |l, r| eq_expr(&l.value, &r.value))
}
pub fn eq_variant_data(l: &VariantData, r: &VariantData) -> bool {
use VariantData::*;
match (l, r) {
(Unit(_), Unit(_)) => true,
(Struct(l, _), Struct(r, _)) | (Tuple(l, _), Tuple(r, _)) => over(l, r, |l, r| eq_struct_field(l, r)),
_ => false,
}
}
pub fn eq_struct_field(l: &StructField, r: &StructField) -> bool {
l.is_placeholder == r.is_placeholder
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
&& eq_vis(&l.vis, &r.vis)
&& both(&l.ident, &r.ident, |l, r| eq_id(*l, *r))
&& eq_ty(&l.ty, &r.ty)
}
pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
eq_fn_decl(&l.decl, &r.decl) && eq_fn_header(&l.header, &r.header)
}
pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
&& l.asyncness.is_async() == r.asyncness.is_async()
&& matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
&& eq_ext(&l.ext, &r.ext)
}
pub fn eq_generics(l: &Generics, r: &Generics) -> bool {
over(&l.params, &r.params, |l, r| eq_generic_param(l, r))
&& over(&l.where_clause.predicates, &r.where_clause.predicates, |l, r| {
eq_where_predicate(l, r)
})
}
pub fn eq_where_predicate(l: &WherePredicate, r: &WherePredicate) -> bool {
use WherePredicate::*;
match (l, r) {
(BoundPredicate(l), BoundPredicate(r)) => {
over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
eq_generic_param(l, r)
}) && eq_ty(&l.bounded_ty, &r.bounded_ty)
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
},
(RegionPredicate(l), RegionPredicate(r)) => {
eq_id(l.lifetime.ident, r.lifetime.ident) && over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
},
(EqPredicate(l), EqPredicate(r)) => eq_ty(&l.lhs_ty, &r.lhs_ty) && eq_ty(&l.rhs_ty, &r.rhs_ty),
_ => false,
}
}
pub fn eq_use_tree(l: &UseTree, r: &UseTree) -> bool {
eq_path(&l.prefix, &r.prefix) && eq_use_tree_kind(&l.kind, &r.kind)
}
pub fn eq_use_tree_kind(l: &UseTreeKind, r: &UseTreeKind) -> bool {
use UseTreeKind::*;
match (l, r) {
(Glob, Glob) => true,
(Simple(l, _, _), Simple(r, _, _)) => both(l, r, |l, r| eq_id(*l, *r)),
(Nested(l), Nested(r)) => over(l, r, |(l, _), (r, _)| eq_use_tree(l, r)),
_ => false,
}
}
pub fn eq_anon_const(l: &AnonConst, r: &AnonConst) -> bool {
eq_expr(&l.value, &r.value)
}
pub fn eq_defaultness(l: Defaultness, r: Defaultness) -> bool {
matches!(
(l, r),
(Defaultness::Final, Defaultness::Final) | (Defaultness::Default(_), Defaultness::Default(_))
)
}
pub fn eq_vis(l: &Visibility, r: &Visibility) -> bool {
use VisibilityKind::*;
match (&l.kind, &r.kind) {
(Public, Public) | (Inherited, Inherited) | (Crate(_), Crate(_)) => true,
(Restricted { path: l, .. }, Restricted { path: r, .. }) => eq_path(l, r),
_ => false,
}
}
pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool {
eq_fn_ret_ty(&l.output, &r.output)
&& over(&l.inputs, &r.inputs, |l, r| {
l.is_placeholder == r.is_placeholder
&& eq_pat(&l.pat, &r.pat)
&& eq_ty(&l.ty, &r.ty)
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
})
}
pub fn eq_fn_ret_ty(l: &FnRetTy, r: &FnRetTy) -> bool {
match (l, r) {
(FnRetTy::Default(_), FnRetTy::Default(_)) => true,
(FnRetTy::Ty(l), FnRetTy::Ty(r)) => eq_ty(l, r),
_ => false,
}
}
pub fn eq_ty(l: &Ty, r: &Ty) -> bool {
use TyKind::*;
match (&l.kind, &r.kind) {
(Paren(l), _) => eq_ty(l, r),
(_, Paren(r)) => eq_ty(l, r),
(Never, Never) | (Infer, Infer) | (ImplicitSelf, ImplicitSelf) | (Err, Err) | (CVarArgs, CVarArgs) => true,
(Slice(l), Slice(r)) => eq_ty(l, r),
(Array(le, ls), Array(re, rs)) => eq_ty(le, re) && eq_expr(&ls.value, &rs.value),
(Ptr(l), Ptr(r)) => l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty),
(Rptr(ll, l), Rptr(rl, r)) => {
both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty)
},
(BareFn(l), BareFn(r)) => {
l.unsafety == r.unsafety
&& eq_ext(&l.ext, &r.ext)
&& over(&l.generic_params, &r.generic_params, |l, r| eq_generic_param(l, r))
&& eq_fn_decl(&l.decl, &r.decl)
},
(Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)),
(Path(lq, lp), Path(rq, rp)) => both(lq, rq, |l, r| eq_qself(l, r)) && eq_path(lp, rp),
(TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, |l, r| eq_generic_bound(l, r)),
(ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, |l, r| eq_generic_bound(l, r)),
(Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value),
(MacCall(l), MacCall(r)) => eq_mac_call(l, r),
_ => false,
}
}
pub fn eq_ext(l: &Extern, r: &Extern) -> bool {
use Extern::*;
match (l, r) {
(None, None) | (Implicit, Implicit) => true,
(Explicit(l), Explicit(r)) => eq_str_lit(l, r),
_ => false,
}
}
pub fn eq_str_lit(l: &StrLit, r: &StrLit) -> bool {
l.style == r.style && l.symbol == r.symbol && l.suffix == r.suffix
}
pub fn eq_poly_ref_trait(l: &PolyTraitRef, r: &PolyTraitRef) -> bool {
eq_path(&l.trait_ref.path, &r.trait_ref.path)
&& over(&l.bound_generic_params, &r.bound_generic_params, |l, r| {
eq_generic_param(l, r)
})
}
pub fn eq_generic_param(l: &GenericParam, r: &GenericParam) -> bool {
use GenericParamKind::*;
l.is_placeholder == r.is_placeholder
&& eq_id(l.ident, r.ident)
&& over(&l.bounds, &r.bounds, |l, r| eq_generic_bound(l, r))
&& match (&l.kind, &r.kind) {
(Lifetime, Lifetime) => true,
(Type { default: l }, Type { default: r }) => both(l, r, |l, r| eq_ty(l, r)),
(
Const {
ty: lt,
kw_span: _,
default: ld,
},
Const {
ty: rt,
kw_span: _,
default: rd,
},
) => eq_ty(lt, rt) && both(ld, rd, |ld, rd| eq_anon_const(ld, rd)),
_ => false,
}
&& over(&l.attrs, &r.attrs, |l, r| eq_attr(l, r))
}
pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool {
use GenericBound::*;
match (l, r) {
(Trait(ptr1, tbm1), Trait(ptr2, tbm2)) => tbm1 == tbm2 && eq_poly_ref_trait(ptr1, ptr2),
(Outlives(l), Outlives(r)) => eq_id(l.ident, r.ident),
_ => false,
}
}
pub fn eq_assoc_constraint(l: &AssocTyConstraint, r: &AssocTyConstraint) -> bool {
use AssocTyConstraintKind::*;
eq_id(l.ident, r.ident)
&& match (&l.kind, &r.kind) {
(Equality { ty: l }, Equality { ty: r }) => eq_ty(l, r),
(Bound { bounds: l }, Bound { bounds: r }) => over(l, r, |l, r| eq_generic_bound(l, r)),
_ => false,
}
}
pub fn eq_mac_call(l: &MacCall, r: &MacCall) -> bool {
eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args)
}
pub fn eq_attr(l: &Attribute, r: &Attribute) -> bool {
use AttrKind::*;
l.style == r.style
&& match (&l.kind, &r.kind) {
(DocComment(l1, l2), DocComment(r1, r2)) => l1 == r1 && l2 == r2,
(Normal(l, _), Normal(r, _)) => eq_path(&l.path, &r.path) && eq_mac_args(&l.args, &r.args),
_ => false,
}
}
pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
use MacArgs::*;
match (l, r) {
(Empty, Empty) => true,
(Delimited(_, ld, lts), Delimited(_, rd, rts)) => ld == rd && lts.eq_unspanned(rts),
(Eq(_, lt), Eq(_, rt)) => lt.kind == rt.kind,
_ => false,
}
}

View File

@@ -1,45 +0,0 @@
use core::iter::FusedIterator;
use rustc_ast::visit::{walk_attribute, walk_expr, Visitor};
use rustc_ast::{Attribute, Expr};
use rustc_span::symbol::Ident;
pub struct IdentIter(std::vec::IntoIter<Ident>);
impl Iterator for IdentIter {
type Item = Ident;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl FusedIterator for IdentIter {}
impl From<&Expr> for IdentIter {
fn from(expr: &Expr) -> Self {
let mut visitor = IdentCollector::default();
walk_expr(&mut visitor, expr);
IdentIter(visitor.0.into_iter())
}
}
impl From<&Attribute> for IdentIter {
fn from(attr: &Attribute) -> Self {
let mut visitor = IdentCollector::default();
walk_attribute(&mut visitor, attr);
IdentIter(visitor.0.into_iter())
}
}
#[derive(Default)]
struct IdentCollector(Vec<Ident>);
impl Visitor<'_> for IdentCollector {
fn visit_ident(&mut self, ident: Ident) {
self.0.push(ident);
}
}

View File

@@ -1,150 +0,0 @@
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_session::Session;
use rustc_span::sym;
use std::str::FromStr;
/// Deprecation status of attributes known by Clippy.
#[allow(dead_code)]
pub enum DeprecationStatus {
/// Attribute is deprecated
Deprecated,
/// Attribute is deprecated and was replaced by the named attribute
Replaced(&'static str),
None,
}
pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
("author", DeprecationStatus::None),
("cognitive_complexity", DeprecationStatus::None),
(
"cyclomatic_complexity",
DeprecationStatus::Replaced("cognitive_complexity"),
),
("dump", DeprecationStatus::None),
("msrv", DeprecationStatus::None),
];
pub struct LimitStack {
stack: Vec<u64>,
}
impl Drop for LimitStack {
fn drop(&mut self) {
assert_eq!(self.stack.len(), 1);
}
}
impl LimitStack {
#[must_use]
pub fn new(limit: u64) -> Self {
Self { stack: vec![limit] }
}
pub fn limit(&self) -> u64 {
*self.stack.last().expect("there should always be a value in the stack")
}
pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| stack.push(val));
}
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val)));
}
}
pub fn get_attr<'a>(
sess: &'a Session,
attrs: &'a [ast::Attribute],
name: &'static str,
) -> impl Iterator<Item = &'a ast::Attribute> {
attrs.iter().filter(move |attr| {
let attr = if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
attr
} else {
return false;
};
let attr_segments = &attr.path.segments;
if attr_segments.len() == 2 && attr_segments[0].ident.name == sym::clippy {
BUILTIN_ATTRIBUTES
.iter()
.find_map(|&(builtin_name, ref deprecation_status)| {
if attr_segments[1].ident.name.as_str() == builtin_name {
Some(deprecation_status)
} else {
None
}
})
.map_or_else(
|| {
sess.span_err(attr_segments[1].ident.span, "usage of unknown attribute");
false
},
|deprecation_status| {
let mut diag =
sess.struct_span_err(attr_segments[1].ident.span, "usage of deprecated attribute");
match *deprecation_status {
DeprecationStatus::Deprecated => {
diag.emit();
false
},
DeprecationStatus::Replaced(new_name) => {
diag.span_suggestion(
attr_segments[1].ident.span,
"consider using",
new_name.to_string(),
Applicability::MachineApplicable,
);
diag.emit();
false
},
DeprecationStatus::None => {
diag.cancel();
attr_segments[1].ident.name.as_str() == name
},
}
},
)
} else {
false
}
})
}
fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
for attr in get_attr(sess, attrs, name) {
if let Some(ref value) = attr.value_str() {
if let Ok(value) = FromStr::from_str(&value.as_str()) {
f(value)
} else {
sess.span_err(attr.span, "not a number");
}
} else {
sess.span_err(attr.span, "bad clippy attribute");
}
}
}
pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
let mut unique_attr = None;
for attr in get_attr(sess, attrs, name) {
match attr.style {
ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
ast::AttrStyle::Inner => {
sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
.span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
.emit();
},
ast::AttrStyle::Outer => {
sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
},
}
}
unique_attr
}
/// Return true if the attributes contain any of `proc_macro`,
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
attrs.iter().any(|attr| sess.is_proc_macro_attr(attr))
}

View File

@@ -1,115 +0,0 @@
/// Returns the index of the character after the first camel-case component of `s`.
#[must_use]
pub fn until(s: &str) -> usize {
let mut iter = s.char_indices();
if let Some((_, first)) = iter.next() {
if !first.is_uppercase() {
return 0;
}
} else {
return 0;
}
let mut up = true;
let mut last_i = 0;
for (i, c) in iter {
if up {
if c.is_lowercase() {
up = false;
} else {
return last_i;
}
} else if c.is_uppercase() {
up = true;
last_i = i;
} else if !c.is_lowercase() {
return i;
}
}
if up {
last_i
} else {
s.len()
}
}
/// Returns index of the last camel-case component of `s`.
#[must_use]
pub fn from(s: &str) -> usize {
let mut iter = s.char_indices().rev();
if let Some((_, first)) = iter.next() {
if !first.is_lowercase() {
return s.len();
}
} else {
return s.len();
}
let mut down = true;
let mut last_i = s.len();
for (i, c) in iter {
if down {
if c.is_uppercase() {
down = false;
last_i = i;
} else if !c.is_lowercase() {
return last_i;
}
} else if c.is_lowercase() {
down = true;
} else {
return last_i;
}
}
last_i
}
#[cfg(test)]
mod test {
use super::{from, until};
#[test]
fn from_full() {
assert_eq!(from("AbcDef"), 0);
assert_eq!(from("Abc"), 0);
}
#[test]
fn from_partial() {
assert_eq!(from("abcDef"), 3);
assert_eq!(from("aDbc"), 1);
}
#[test]
fn from_not() {
assert_eq!(from("AbcDef_"), 7);
assert_eq!(from("AbcDD"), 5);
}
#[test]
fn from_caps() {
assert_eq!(from("ABCD"), 4);
}
#[test]
fn until_full() {
assert_eq!(until("AbcDef"), 6);
assert_eq!(until("Abc"), 3);
}
#[test]
fn until_not() {
assert_eq!(until("abcDef"), 0);
assert_eq!(until("aDbc"), 0);
}
#[test]
fn until_partial() {
assert_eq!(until("AbcDef_"), 6);
assert_eq!(until("CallTypeC"), 8);
assert_eq!(until("AbcDD"), 3);
}
#[test]
fn until_caps() {
assert_eq!(until("ABCD"), 0);
}
}

View File

@@ -1,36 +0,0 @@
//! Utility functions about comparison operators.
#![deny(clippy::missing_docs_in_private_items)]
use rustc_hir::{BinOpKind, Expr};
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
/// Represent a normalized comparison operator.
pub enum Rel {
/// `<`
Lt,
/// `<=`
Le,
/// `==`
Eq,
/// `!=`
Ne,
}
/// Put the expression in the form `lhs < rhs`, `lhs <= rhs`, `lhs == rhs` or
/// `lhs != rhs`.
pub fn normalize_comparison<'a>(
op: BinOpKind,
lhs: &'a Expr<'a>,
rhs: &'a Expr<'a>,
) -> Option<(Rel, &'a Expr<'a>, &'a Expr<'a>)> {
match op {
BinOpKind::Lt => Some((Rel::Lt, lhs, rhs)),
BinOpKind::Le => Some((Rel::Le, lhs, rhs)),
BinOpKind::Gt => Some((Rel::Lt, rhs, lhs)),
BinOpKind::Ge => Some((Rel::Le, rhs, lhs)),
BinOpKind::Eq => Some((Rel::Eq, rhs, lhs)),
BinOpKind::Ne => Some((Rel::Ne, rhs, lhs)),
_ => None,
}
}

View File

@@ -126,7 +126,7 @@ define_Conf! {
"NaN", "NaNs",
"OAuth", "GraphQL",
"OCaml",
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap",
"OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS",
"WebGL",
"TensorFlow",
"TrueType",

View File

@@ -1,226 +0,0 @@
//! Clippy wrappers around rustc's diagnostic functions.
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir::HirId;
use rustc_lint::{LateContext, Lint, LintContext};
use rustc_span::source_map::{MultiSpan, Span};
use std::env;
fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
diag.help(&format!(
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
// extract just major + minor version and ignore patch versions
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
}),
lint.name_lower().replacen("clippy::", "", 1)
));
}
}
/// Emit a basic lint message with a `msg` and a `span`.
///
/// This is the most primitive of our lint emission methods and can
/// be a good way to get a new lint started.
///
/// Usually it's nicer to provide more context for lint messages.
/// Be sure the output is understandable when you use this method.
///
/// # Example
///
/// ```ignore
/// error: usage of mem::forget on Drop type
/// --> $DIR/mem_forget.rs:17:5
/// |
/// 17 | std::mem::forget(seven);
/// | ^^^^^^^^^^^^^^^^^^^^^^^
/// ```
pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<MultiSpan>, msg: &str) {
cx.struct_span_lint(lint, sp, |diag| {
let mut diag = diag.build(msg);
docs_link(&mut diag, lint);
diag.emit();
});
}
/// Same as `span_lint` but with an extra `help` message.
///
/// Use this if you want to provide some general help but
/// can't provide a specific machine applicable suggestion.
///
/// The `help` message can be optionally attached to a `Span`.
///
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// # Example
///
/// ```ignore
/// error: constant division of 0.0 with 0.0 will always result in NaN
/// --> $DIR/zero_div_zero.rs:6:25
/// |
/// 6 | let other_f64_nan = 0.0f64 / 0.0;
/// | ^^^^^^^^^^^^
/// |
/// = help: Consider using `f64::NAN` if you would like a constant representing NaN
/// ```
pub fn span_lint_and_help<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
span: Span,
msg: &str,
help_span: Option<Span>,
help: &str,
) {
cx.struct_span_lint(lint, span, |diag| {
let mut diag = diag.build(msg);
if let Some(help_span) = help_span {
diag.span_help(help_span, help);
} else {
diag.help(help);
}
docs_link(&mut diag, lint);
diag.emit();
});
}
/// Like `span_lint` but with a `note` section instead of a `help` message.
///
/// The `note` message is presented separately from the main lint message
/// and is attached to a specific span:
///
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// # Example
///
/// ```ignore
/// error: calls to `std::mem::forget` with a reference instead of an owned value. Forgetting a reference does nothing.
/// --> $DIR/drop_forget_ref.rs:10:5
/// |
/// 10 | forget(&SomeStruct);
/// | ^^^^^^^^^^^^^^^^^^^
/// |
/// = note: `-D clippy::forget-ref` implied by `-D warnings`
/// note: argument has type &SomeStruct
/// --> $DIR/drop_forget_ref.rs:10:12
/// |
/// 10 | forget(&SomeStruct);
/// | ^^^^^^^^^^^
/// ```
pub fn span_lint_and_note<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
span: impl Into<MultiSpan>,
msg: &str,
note_span: Option<Span>,
note: &str,
) {
cx.struct_span_lint(lint, span, |diag| {
let mut diag = diag.build(msg);
if let Some(note_span) = note_span {
diag.span_note(note_span, note);
} else {
diag.note(note);
}
docs_link(&mut diag, lint);
diag.emit();
});
}
/// Like `span_lint` but allows to add notes, help and suggestions using a closure.
///
/// If you need to customize your lint output a lot, use this function.
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
where
F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
{
cx.struct_span_lint(lint, sp, |diag| {
let mut diag = diag.build(msg);
f(&mut diag);
docs_link(&mut diag, lint);
diag.emit();
});
}
pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: &str) {
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
let mut diag = diag.build(msg);
docs_link(&mut diag, lint);
diag.emit();
});
}
pub fn span_lint_hir_and_then(
cx: &LateContext<'_>,
lint: &'static Lint,
hir_id: HirId,
sp: Span,
msg: &str,
f: impl FnOnce(&mut DiagnosticBuilder<'_>),
) {
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
let mut diag = diag.build(msg);
f(&mut diag);
docs_link(&mut diag, lint);
diag.emit();
});
}
/// Add a span lint with a suggestion on how to fix it.
///
/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.
/// In the example below, `help` is `"try"` and `sugg` is the suggested replacement `".any(|x| x >
/// 2)"`.
///
/// If you change the signature, remember to update the internal lint `CollapsibleCalls`
///
/// # Example
///
/// ```ignore
/// error: This `.fold` can be more succinctly expressed as `.any`
/// --> $DIR/methods.rs:390:13
/// |
/// 390 | let _ = (0..3).fold(false, |acc, x| acc || x > 2);
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `.any(|x| x > 2)`
/// |
/// = note: `-D fold-any` implied by `-D warnings`
/// ```
#[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))]
pub fn span_lint_and_sugg<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
sp: Span,
msg: &str,
help: &str,
sugg: String,
applicability: Applicability,
) {
span_lint_and_then(cx, lint, sp, msg, |diag| {
diag.span_suggestion(sp, help, sugg, applicability);
});
}
/// Create a suggestion made from several `span → replacement`.
///
/// Note: in the JSON format (used by `compiletest_rs`), the help message will
/// appear once per
/// replacement. In human-readable format though, it only appears once before
/// the whole suggestion.
pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
where
I: IntoIterator<Item = (Span, String)>,
{
multispan_sugg_with_applicability(diag, help_msg, Applicability::Unspecified, sugg)
}
pub fn multispan_sugg_with_applicability<I>(
diag: &mut DiagnosticBuilder<'_>,
help_msg: &str,
applicability: Applicability,
sugg: I,
) where
I: IntoIterator<Item = (Span, String)>,
{
diag.multipart_suggestion(help_msg, sugg.into_iter().collect(), applicability);
}

View File

@@ -1,134 +0,0 @@
//! Utilities for evaluating whether eagerly evaluated expressions can be made lazy and vice versa.
//!
//! Things to consider:
//! - has the expression side-effects?
//! - is the expression computationally expensive?
//!
//! See lints:
//! - unnecessary-lazy-evaluations
//! - or-fun-call
//! - option-if-let-else
use crate::utils::{is_ctor_or_promotable_const_function, is_type_diagnostic_item, match_type, paths};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{Block, Expr, ExprKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
/// Is the expr pure (is it free from side-effects)?
/// This function is named so to stress that it isn't exhaustive and returns FNs.
fn identify_some_pure_patterns(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Lit(..) | ExprKind::ConstBlock(..) | ExprKind::Path(..) | ExprKind::Field(..) => true,
ExprKind::AddrOf(_, _, addr_of_expr) => identify_some_pure_patterns(addr_of_expr),
ExprKind::Tup(tup_exprs) => tup_exprs.iter().all(|expr| identify_some_pure_patterns(expr)),
ExprKind::Struct(_, fields, expr) => {
fields.iter().all(|f| identify_some_pure_patterns(f.expr))
&& expr.map_or(true, |e| identify_some_pure_patterns(e))
},
ExprKind::Call(
&Expr {
kind:
ExprKind::Path(QPath::Resolved(
_,
Path {
res: Res::Def(DefKind::Ctor(..) | DefKind::Variant, ..),
..
},
)),
..
},
args,
) => args.iter().all(|expr| identify_some_pure_patterns(expr)),
ExprKind::Block(
&Block {
stmts,
expr: Some(expr),
..
},
_,
) => stmts.is_empty() && identify_some_pure_patterns(expr),
ExprKind::Box(..)
| ExprKind::Array(..)
| ExprKind::Call(..)
| ExprKind::MethodCall(..)
| ExprKind::Binary(..)
| ExprKind::Unary(..)
| ExprKind::Cast(..)
| ExprKind::Type(..)
| ExprKind::DropTemps(..)
| ExprKind::Loop(..)
| ExprKind::If(..)
| ExprKind::Match(..)
| ExprKind::Closure(..)
| ExprKind::Block(..)
| ExprKind::Assign(..)
| ExprKind::AssignOp(..)
| ExprKind::Index(..)
| ExprKind::Break(..)
| ExprKind::Continue(..)
| ExprKind::Ret(..)
| ExprKind::InlineAsm(..)
| ExprKind::LlvmInlineAsm(..)
| ExprKind::Repeat(..)
| ExprKind::Yield(..)
| ExprKind::Err => false,
}
}
/// Identify some potentially computationally expensive patterns.
/// This function is named so to stress that its implementation is non-exhaustive.
/// It returns FNs and FPs.
fn identify_some_potentially_expensive_patterns<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
// Searches an expression for method calls or function calls that aren't ctors
struct FunCallFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
found: bool,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for FunCallFinder<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
let call_found = match &expr.kind {
// ignore enum and struct constructors
ExprKind::Call(..) => !is_ctor_or_promotable_const_function(self.cx, expr),
ExprKind::Index(obj, _) => {
let ty = self.cx.typeck_results().expr_ty(obj);
is_type_diagnostic_item(self.cx, ty, sym!(hashmap_type))
|| match_type(self.cx, ty, &paths::BTREEMAP)
},
ExprKind::MethodCall(..) => true,
_ => false,
};
if call_found {
self.found |= true;
}
if !self.found {
intravisit::walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
let mut finder = FunCallFinder { cx, found: false };
finder.visit_expr(expr);
finder.found
}
pub fn is_eagerness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
!identify_some_potentially_expensive_patterns(cx, expr) && identify_some_pure_patterns(expr)
}
pub fn is_lazyness_candidate<'a, 'tcx>(cx: &'a LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
identify_some_potentially_expensive_patterns(cx, expr)
}

View File

@@ -1,268 +0,0 @@
//! This module contains functions for retrieve the original AST from lowered
//! `hir`.
#![deny(clippy::missing_docs_in_private_items)]
use crate::utils::{is_expn_of, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_hir as hir;
use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
/// Converts a hir binary operator to the corresponding `ast` type.
#[must_use]
pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
match op {
hir::BinOpKind::Eq => ast::BinOpKind::Eq,
hir::BinOpKind::Ge => ast::BinOpKind::Ge,
hir::BinOpKind::Gt => ast::BinOpKind::Gt,
hir::BinOpKind::Le => ast::BinOpKind::Le,
hir::BinOpKind::Lt => ast::BinOpKind::Lt,
hir::BinOpKind::Ne => ast::BinOpKind::Ne,
hir::BinOpKind::Or => ast::BinOpKind::Or,
hir::BinOpKind::Add => ast::BinOpKind::Add,
hir::BinOpKind::And => ast::BinOpKind::And,
hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
hir::BinOpKind::Div => ast::BinOpKind::Div,
hir::BinOpKind::Mul => ast::BinOpKind::Mul,
hir::BinOpKind::Rem => ast::BinOpKind::Rem,
hir::BinOpKind::Shl => ast::BinOpKind::Shl,
hir::BinOpKind::Shr => ast::BinOpKind::Shr,
hir::BinOpKind::Sub => ast::BinOpKind::Sub,
}
}
/// Represent a range akin to `ast::ExprKind::Range`.
#[derive(Debug, Copy, Clone)]
pub struct Range<'a> {
/// The lower bound of the range, or `None` for ranges such as `..X`.
pub start: Option<&'a hir::Expr<'a>>,
/// The upper bound of the range, or `None` for ranges such as `X..`.
pub end: Option<&'a hir::Expr<'a>>,
/// Whether the interval is open or closed.
pub limits: ast::RangeLimits,
}
/// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
/// Finds the field named `name` in the field. Always return `Some` for
/// convenience.
fn get_field<'c>(name: &str, fields: &'c [hir::Field<'_>]) -> Option<&'c hir::Expr<'c>> {
let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
Some(expr)
}
match expr.kind {
hir::ExprKind::Call(ref path, ref args)
if matches!(
path.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
) =>
{
Some(Range {
start: Some(&args[0]),
end: Some(&args[1]),
limits: ast::RangeLimits::Closed,
})
},
hir::ExprKind::Struct(ref path, ref fields, None) => match path {
hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
start: None,
end: None,
limits: ast::RangeLimits::HalfOpen,
}),
hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
start: Some(get_field("start", fields)?),
end: None,
limits: ast::RangeLimits::HalfOpen,
}),
hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
start: Some(get_field("start", fields)?),
end: Some(get_field("end", fields)?),
limits: ast::RangeLimits::HalfOpen,
}),
hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
start: None,
end: Some(get_field("end", fields)?),
limits: ast::RangeLimits::Closed,
}),
hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
start: None,
end: Some(get_field("end", fields)?),
limits: ast::RangeLimits::HalfOpen,
}),
_ => None,
},
_ => None,
}
}
/// Checks if a `let` statement is from a `for` loop desugaring.
pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
// This will detect plain for-loops without an actual variable binding:
//
// ```
// for x in some_vec {
// // do stuff
// }
// ```
if_chain! {
if let Some(ref expr) = local.init;
if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
then {
return true;
}
}
// This detects a variable binding in for loop to avoid `let_unit_value`
// lint (see issue #1964).
//
// ```
// for _ in vec![()] {
// // anything
// }
// ```
if let hir::LocalSource::ForLoopDesugar = local.source {
return true;
}
false
}
/// Recover the essential nodes of a desugared for loop as well as the entire span:
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
pub fn for_loop<'tcx>(
expr: &'tcx hir::Expr<'tcx>,
) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
if_chain! {
if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
if let hir::ExprKind::Loop(ref block, ..) = arms[0].body.kind;
if block.expr.is_none();
if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
if let hir::StmtKind::Local(ref local) = let_stmt.kind;
if let hir::StmtKind::Expr(ref expr) = body.kind;
then {
return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
}
}
None
}
/// Recover the essential nodes of a desugared while loop:
/// `while cond { body }` becomes `(cond, body)`.
pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
if_chain! {
if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind;
if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
if let hir::ExprKind::DropTemps(cond) = &cond.kind;
if let [hir::Arm { body, .. }, ..] = &arms[..];
then {
return Some((cond, body));
}
}
None
}
/// Represent the pre-expansion arguments of a `vec!` invocation.
pub enum VecArgs<'a> {
/// `vec![elem; len]`
Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
/// `vec![a, b, c]`
Vec(&'a [hir::Expr<'a>]),
}
/// Returns the arguments of the `vec!` macro if this expression was expanded
/// from `vec!`.
pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
if_chain! {
if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
if let hir::ExprKind::Path(ref qpath) = fun.kind;
if is_expn_of(fun.span, "vec").is_some();
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
then {
return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
// `vec![elem; size]` case
Some(VecArgs::Repeat(&args[0], &args[1]))
}
else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
// `vec![a, b, c]` case
if_chain! {
if let hir::ExprKind::Box(ref boxed) = args[0].kind;
if let hir::ExprKind::Array(ref args) = boxed.kind;
then {
return Some(VecArgs::Vec(&*args));
}
}
None
}
else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
Some(VecArgs::Vec(&[]))
}
else {
None
};
}
}
None
}
/// Extract args from an assert-like macro.
/// Currently working with:
/// - `assert!`, `assert_eq!` and `assert_ne!`
/// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
/// For example:
/// `assert!(expr)` will return Some([expr])
/// `debug_assert_eq!(a, b)` will return Some([a, b])
pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
/// Try to match the AST for a pattern that contains a match, for example when two args are
/// compared
fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
if_chain! {
if let ExprKind::Match(ref headerexpr, _, _) = &matchblock_expr.kind;
if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
then {
return Some(vec![lhs, rhs]);
}
}
None
}
if let ExprKind::Block(ref block, _) = e.kind {
if block.stmts.len() == 1 {
if let StmtKind::Semi(ref matchexpr) = block.stmts.get(0)?.kind {
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
if_chain! {
if let ExprKind::If(ref clause, _, _) = matchexpr.kind;
if let ExprKind::Unary(UnOp::Not, condition) = clause.kind;
then {
return Some(vec![condition]);
}
}
// debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
if_chain! {
if let ExprKind::Block(ref matchblock,_) = matchexpr.kind;
if let Some(ref matchblock_expr) = matchblock.expr;
then {
return ast_matchblock(matchblock_expr);
}
}
}
} else if let Some(matchblock_expr) = block.expr {
// macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
return ast_matchblock(&matchblock_expr);
}
}
None
}

View File

@@ -1,808 +0,0 @@
use crate::consts::{constant_context, constant_simple};
use crate::utils::differing_macro_contexts;
use rustc_ast::ast::InlineAsmTemplatePiece;
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_hir::{
BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FieldPat, FnRetTy,
GenericArg, GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path,
PathSegment, QPath, Stmt, StmtKind, Ty, TyKind, TypeBinding,
};
use rustc_lint::LateContext;
use rustc_middle::ich::StableHashingContextProvider;
use rustc_middle::ty::TypeckResults;
use rustc_span::Symbol;
use std::hash::Hash;
/// Type used to check whether two ast are the same. This is different from the
/// operator
/// `==` on ast types as this operator would compare true equality with ID and
/// span.
///
/// Note that some expressions kinds are not considered but could be added.
pub struct SpanlessEq<'a, 'tcx> {
/// Context used to evaluate constant expressions.
cx: &'a LateContext<'tcx>,
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
allow_side_effects: bool,
expr_fallback: Option<Box<dyn Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a>>,
}
impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
pub fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
cx,
maybe_typeck_results: cx.maybe_typeck_results(),
allow_side_effects: true,
expr_fallback: None,
}
}
/// Consider expressions containing potential side effects as not equal.
pub fn deny_side_effects(self) -> Self {
Self {
allow_side_effects: false,
..self
}
}
pub fn expr_fallback(self, expr_fallback: impl Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self {
Self {
expr_fallback: Some(Box::new(expr_fallback)),
..self
}
}
/// Checks whether two statements are the same.
pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
match (&left.kind, &right.kind) {
(&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
self.eq_pat(&l.pat, &r.pat)
&& both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r))
&& both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
},
(&StmtKind::Expr(ref l), &StmtKind::Expr(ref r)) | (&StmtKind::Semi(ref l), &StmtKind::Semi(ref r)) => {
self.eq_expr(l, r)
},
_ => false,
}
}
/// Checks whether two blocks are the same.
pub fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
over(&left.stmts, &right.stmts, |l, r| self.eq_stmt(l, r))
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
}
#[allow(clippy::similar_names)]
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
if !self.allow_side_effects && differing_macro_contexts(left.span, right.span) {
return false;
}
if let Some(typeck_results) = self.maybe_typeck_results {
if let (Some(l), Some(r)) = (
constant_simple(self.cx, typeck_results, left),
constant_simple(self.cx, typeck_results, right),
) {
if l == r {
return true;
}
}
}
let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
(&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
},
(&ExprKind::Continue(li), &ExprKind::Continue(ri)) => {
both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
},
(&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
self.allow_side_effects && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
},
(&ExprKind::AssignOp(ref lo, ref ll, ref lr), &ExprKind::AssignOp(ref ro, ref rl, ref rr)) => {
self.allow_side_effects && lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
},
(&ExprKind::Block(ref l, _), &ExprKind::Block(ref r, _)) => self.eq_block(l, r),
(&ExprKind::Binary(l_op, ref ll, ref lr), &ExprKind::Binary(r_op, ref rl, ref rr)) => {
l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
|| swap_binop(l_op.node, ll, lr).map_or(false, |(l_op, ll, lr)| {
l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
})
},
(&ExprKind::Break(li, ref le), &ExprKind::Break(ri, ref re)) => {
both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name)
&& both(le, re, |l, r| self.eq_expr(l, r))
},
(&ExprKind::Box(ref l), &ExprKind::Box(ref r)) => self.eq_expr(l, r),
(&ExprKind::Call(l_fun, l_args), &ExprKind::Call(r_fun, r_args)) => {
self.allow_side_effects && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, r_args)
},
(&ExprKind::Cast(ref lx, ref lt), &ExprKind::Cast(ref rx, ref rt))
| (&ExprKind::Type(ref lx, ref lt), &ExprKind::Type(ref rx, ref rt)) => {
self.eq_expr(lx, rx) && self.eq_ty(lt, rt)
},
(&ExprKind::Field(ref l_f_exp, ref l_f_ident), &ExprKind::Field(ref r_f_exp, ref r_f_ident)) => {
l_f_ident.name == r_f_ident.name && self.eq_expr(l_f_exp, r_f_exp)
},
(&ExprKind::Index(ref la, ref li), &ExprKind::Index(ref ra, ref ri)) => {
self.eq_expr(la, ra) && self.eq_expr(li, ri)
},
(&ExprKind::If(ref lc, ref lt, ref le), &ExprKind::If(ref rc, ref rt, ref re)) => {
self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
},
(&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
(&ExprKind::Loop(ref lb, ref ll, ref lls, _), &ExprKind::Loop(ref rb, ref rl, ref rls, _)) => {
lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
},
(&ExprKind::Match(ref le, ref la, ref ls), &ExprKind::Match(ref re, ref ra, ref rs)) => {
ls == rs
&& self.eq_expr(le, re)
&& over(la, ra, |l, r| {
self.eq_expr(&l.body, &r.body)
&& both(&l.guard, &r.guard, |l, r| self.eq_guard(l, r))
&& self.eq_pat(&l.pat, &r.pat)
})
},
(&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
self.allow_side_effects && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, r_args)
},
(&ExprKind::Repeat(ref le, ref ll_id), &ExprKind::Repeat(ref re, ref rl_id)) => {
let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(ll_id.body));
let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value);
let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(rl_id.body));
let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value);
self.eq_expr(le, re) && ll == rl
},
(&ExprKind::Ret(ref l), &ExprKind::Ret(ref r)) => both(l, r, |l, r| self.eq_expr(l, r)),
(&ExprKind::Path(ref l), &ExprKind::Path(ref r)) => self.eq_qpath(l, r),
(&ExprKind::Struct(ref l_path, ref lf, ref lo), &ExprKind::Struct(ref r_path, ref rf, ref ro)) => {
self.eq_qpath(l_path, r_path)
&& both(lo, ro, |l, r| self.eq_expr(l, r))
&& over(lf, rf, |l, r| self.eq_field(l, r))
},
(&ExprKind::Tup(l_tup), &ExprKind::Tup(r_tup)) => self.eq_exprs(l_tup, r_tup),
(&ExprKind::Unary(l_op, ref le), &ExprKind::Unary(r_op, ref re)) => l_op == r_op && self.eq_expr(le, re),
(&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
(&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re),
_ => false,
};
is_eq || self.expr_fallback.as_ref().map_or(false, |f| f(left, right))
}
fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool {
over(left, right, |l, r| self.eq_expr(l, r))
}
fn eq_field(&mut self, left: &Field<'_>, right: &Field<'_>) -> bool {
left.ident.name == right.ident.name && self.eq_expr(&left.expr, &right.expr)
}
fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool {
match (left, right) {
(Guard::If(l), Guard::If(r)) => self.eq_expr(l, r),
(Guard::IfLet(lp, le), Guard::IfLet(rp, re)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
_ => false,
}
}
fn eq_generic_arg(&mut self, left: &GenericArg<'_>, right: &GenericArg<'_>) -> bool {
match (left, right) {
(GenericArg::Lifetime(l_lt), GenericArg::Lifetime(r_lt)) => Self::eq_lifetime(l_lt, r_lt),
(GenericArg::Type(l_ty), GenericArg::Type(r_ty)) => self.eq_ty(l_ty, r_ty),
_ => false,
}
}
fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
left.name == right.name
}
pub fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool {
let (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) = (&left, &right);
li.name == ri.name && self.eq_pat(lp, rp)
}
/// Checks whether two patterns are the same.
pub fn eq_pat(&mut self, left: &Pat<'_>, right: &Pat<'_>) -> bool {
match (&left.kind, &right.kind) {
(&PatKind::Box(ref l), &PatKind::Box(ref r)) => self.eq_pat(l, r),
(&PatKind::Struct(ref lp, ref la, ..), &PatKind::Struct(ref rp, ref ra, ..)) => {
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_fieldpat(l, r))
},
(&PatKind::TupleStruct(ref lp, ref la, ls), &PatKind::TupleStruct(ref rp, ref ra, rs)) => {
self.eq_qpath(lp, rp) && over(la, ra, |l, r| self.eq_pat(l, r)) && ls == rs
},
(&PatKind::Binding(ref lb, .., ref li, ref lp), &PatKind::Binding(ref rb, .., ref ri, ref rp)) => {
lb == rb && li.name == ri.name && both(lp, rp, |l, r| self.eq_pat(l, r))
},
(&PatKind::Path(ref l), &PatKind::Path(ref r)) => self.eq_qpath(l, r),
(&PatKind::Lit(ref l), &PatKind::Lit(ref r)) => self.eq_expr(l, r),
(&PatKind::Tuple(ref l, ls), &PatKind::Tuple(ref r, rs)) => {
ls == rs && over(l, r, |l, r| self.eq_pat(l, r))
},
(&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => {
both(ls, rs, |a, b| self.eq_expr(a, b)) && both(le, re, |a, b| self.eq_expr(a, b)) && (li == ri)
},
(&PatKind::Ref(ref le, ref lm), &PatKind::Ref(ref re, ref rm)) => lm == rm && self.eq_pat(le, re),
(&PatKind::Slice(ref ls, ref li, ref le), &PatKind::Slice(ref rs, ref ri, ref re)) => {
over(ls, rs, |l, r| self.eq_pat(l, r))
&& over(le, re, |l, r| self.eq_pat(l, r))
&& both(li, ri, |l, r| self.eq_pat(l, r))
},
(&PatKind::Wild, &PatKind::Wild) => true,
_ => false,
}
}
#[allow(clippy::similar_names)]
fn eq_qpath(&mut self, left: &QPath<'_>, right: &QPath<'_>) -> bool {
match (left, right) {
(&QPath::Resolved(ref lty, ref lpath), &QPath::Resolved(ref rty, ref rpath)) => {
both(lty, rty, |l, r| self.eq_ty(l, r)) && self.eq_path(lpath, rpath)
},
(&QPath::TypeRelative(ref lty, ref lseg), &QPath::TypeRelative(ref rty, ref rseg)) => {
self.eq_ty(lty, rty) && self.eq_path_segment(lseg, rseg)
},
(&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
_ => false,
}
}
fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
left.is_global() == right.is_global()
&& over(&left.segments, &right.segments, |l, r| self.eq_path_segment(l, r))
}
fn eq_path_parameters(&mut self, left: &GenericArgs<'_>, right: &GenericArgs<'_>) -> bool {
if !(left.parenthesized || right.parenthesized) {
over(&left.args, &right.args, |l, r| self.eq_generic_arg(l, r)) // FIXME(flip1995): may not work
&& over(&left.bindings, &right.bindings, |l, r| self.eq_type_binding(l, r))
} else if left.parenthesized && right.parenthesized {
over(left.inputs(), right.inputs(), |l, r| self.eq_ty(l, r))
&& both(&Some(&left.bindings[0].ty()), &Some(&right.bindings[0].ty()), |l, r| {
self.eq_ty(l, r)
})
} else {
false
}
}
pub fn eq_path_segments(&mut self, left: &[PathSegment<'_>], right: &[PathSegment<'_>]) -> bool {
left.len() == right.len() && left.iter().zip(right).all(|(l, r)| self.eq_path_segment(l, r))
}
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
// The == of idents doesn't work with different contexts,
// we have to be explicit about hygiene
left.ident.name == right.ident.name && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r))
}
pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool {
self.eq_ty_kind(&left.kind, &right.kind)
}
#[allow(clippy::similar_names)]
pub fn eq_ty_kind(&mut self, left: &TyKind<'_>, right: &TyKind<'_>) -> bool {
match (left, right) {
(&TyKind::Slice(ref l_vec), &TyKind::Slice(ref r_vec)) => self.eq_ty(l_vec, r_vec),
(&TyKind::Array(ref lt, ref ll_id), &TyKind::Array(ref rt, ref rl_id)) => {
let old_maybe_typeck_results = self.maybe_typeck_results;
let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(ll_id.body));
self.maybe_typeck_results = Some(self.cx.tcx.typeck_body(ll_id.body));
let ll = celcx.expr(&self.cx.tcx.hir().body(ll_id.body).value);
let mut celcx = constant_context(self.cx, self.cx.tcx.typeck_body(rl_id.body));
self.maybe_typeck_results = Some(self.cx.tcx.typeck_body(rl_id.body));
let rl = celcx.expr(&self.cx.tcx.hir().body(rl_id.body).value);
let eq_ty = self.eq_ty(lt, rt);
self.maybe_typeck_results = old_maybe_typeck_results;
eq_ty && ll == rl
},
(&TyKind::Ptr(ref l_mut), &TyKind::Ptr(ref r_mut)) => {
l_mut.mutbl == r_mut.mutbl && self.eq_ty(&*l_mut.ty, &*r_mut.ty)
},
(&TyKind::Rptr(_, ref l_rmut), &TyKind::Rptr(_, ref r_rmut)) => {
l_rmut.mutbl == r_rmut.mutbl && self.eq_ty(&*l_rmut.ty, &*r_rmut.ty)
},
(&TyKind::Path(ref l), &TyKind::Path(ref r)) => self.eq_qpath(l, r),
(&TyKind::Tup(ref l), &TyKind::Tup(ref r)) => over(l, r, |l, r| self.eq_ty(l, r)),
(&TyKind::Infer, &TyKind::Infer) => true,
_ => false,
}
}
fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
left.ident.name == right.ident.name && self.eq_ty(&left.ty(), &right.ty())
}
}
/// Some simple reductions like `{ return }` => `return`
fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
if let ExprKind::Block(block, _) = kind {
match (block.stmts, block.expr) {
// `{}` => `()`
([], None) => &ExprKind::Tup(&[]),
([], Some(expr)) => match expr.kind {
// `{ return .. }` => `return ..`
ExprKind::Ret(..) => &expr.kind,
_ => kind,
},
([stmt], None) => match stmt.kind {
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
// `{ return ..; }` => `return ..`
ExprKind::Ret(..) => &expr.kind,
_ => kind,
},
_ => kind,
},
_ => kind,
}
} else {
kind
}
}
fn swap_binop<'a>(
binop: BinOpKind,
lhs: &'a Expr<'a>,
rhs: &'a Expr<'a>,
) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
match binop {
BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
Some((binop, rhs, lhs))
},
BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
| BinOpKind::Shl
| BinOpKind::Shr
| BinOpKind::Rem
| BinOpKind::Sub
| BinOpKind::Div
| BinOpKind::And
| BinOpKind::Or => None,
}
}
/// Checks if the two `Option`s are both `None` or some equal values as per
/// `eq_fn`.
pub fn both<X>(l: &Option<X>, r: &Option<X>, mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
l.as_ref()
.map_or_else(|| r.is_none(), |x| r.as_ref().map_or(false, |y| eq_fn(x, y)))
}
/// Checks if two slices are equal as per `eq_fn`.
pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
left.len() == right.len() && left.iter().zip(right).all(|(x, y)| eq_fn(x, y))
}
/// Checks if two expressions evaluate to the same value, and don't contain any side effects.
pub fn eq_expr_value(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>) -> bool {
SpanlessEq::new(cx).deny_side_effects().eq_expr(left, right)
}
/// Type used to hash an ast element. This is different from the `Hash` trait
/// on ast types as this
/// trait would consider IDs and spans.
///
/// All expressions kind are hashed, but some might have a weaker hash.
pub struct SpanlessHash<'a, 'tcx> {
/// Context used to evaluate constant expressions.
cx: &'a LateContext<'tcx>,
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
s: StableHasher,
}
impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
pub fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
cx,
maybe_typeck_results: cx.maybe_typeck_results(),
s: StableHasher::new(),
}
}
pub fn finish(self) -> u64 {
self.s.finish()
}
pub fn hash_block(&mut self, b: &Block<'_>) {
for s in b.stmts {
self.hash_stmt(s);
}
if let Some(ref e) = b.expr {
self.hash_expr(e);
}
match b.rules {
BlockCheckMode::DefaultBlock => 0,
BlockCheckMode::UnsafeBlock(_) => 1,
BlockCheckMode::PushUnsafeBlock(_) => 2,
BlockCheckMode::PopUnsafeBlock(_) => 3,
}
.hash(&mut self.s);
}
#[allow(clippy::many_single_char_names, clippy::too_many_lines)]
pub fn hash_expr(&mut self, e: &Expr<'_>) {
let simple_const = self
.maybe_typeck_results
.and_then(|typeck_results| constant_simple(self.cx, typeck_results, e));
// const hashing may result in the same hash as some unrelated node, so add a sort of
// discriminant depending on which path we're choosing next
simple_const.is_some().hash(&mut self.s);
if let Some(e) = simple_const {
return e.hash(&mut self.s);
}
std::mem::discriminant(&e.kind).hash(&mut self.s);
match e.kind {
ExprKind::AddrOf(kind, m, ref e) => {
match kind {
BorrowKind::Ref => 0,
BorrowKind::Raw => 1,
}
.hash(&mut self.s);
m.hash(&mut self.s);
self.hash_expr(e);
},
ExprKind::Continue(i) => {
if let Some(i) = i.label {
self.hash_name(i.ident.name);
}
},
ExprKind::Assign(ref l, ref r, _) => {
self.hash_expr(l);
self.hash_expr(r);
},
ExprKind::AssignOp(ref o, ref l, ref r) => {
o.node
.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
self.hash_expr(l);
self.hash_expr(r);
},
ExprKind::Block(ref b, _) => {
self.hash_block(b);
},
ExprKind::Binary(op, ref l, ref r) => {
op.node
.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
self.hash_expr(l);
self.hash_expr(r);
},
ExprKind::Break(i, ref j) => {
if let Some(i) = i.label {
self.hash_name(i.ident.name);
}
if let Some(ref j) = *j {
self.hash_expr(&*j);
}
},
ExprKind::Box(ref e) | ExprKind::DropTemps(ref e) | ExprKind::Yield(ref e, _) => {
self.hash_expr(e);
},
ExprKind::Call(ref fun, args) => {
self.hash_expr(fun);
self.hash_exprs(args);
},
ExprKind::Cast(ref e, ref ty) | ExprKind::Type(ref e, ref ty) => {
self.hash_expr(e);
self.hash_ty(ty);
},
ExprKind::Closure(cap, _, eid, _, _) => {
match cap {
CaptureBy::Value => 0,
CaptureBy::Ref => 1,
}
.hash(&mut self.s);
// closures inherit TypeckResults
self.hash_expr(&self.cx.tcx.hir().body(eid).value);
},
ExprKind::Field(ref e, ref f) => {
self.hash_expr(e);
self.hash_name(f.name);
},
ExprKind::Index(ref a, ref i) => {
self.hash_expr(a);
self.hash_expr(i);
},
ExprKind::InlineAsm(ref asm) => {
for piece in asm.template {
match piece {
InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s),
InlineAsmTemplatePiece::Placeholder {
operand_idx,
modifier,
span: _,
} => {
operand_idx.hash(&mut self.s);
modifier.hash(&mut self.s);
},
}
}
asm.options.hash(&mut self.s);
for (op, _op_sp) in asm.operands {
match op {
InlineAsmOperand::In { reg, expr } => {
reg.hash(&mut self.s);
self.hash_expr(expr);
},
InlineAsmOperand::Out { reg, late, expr } => {
reg.hash(&mut self.s);
late.hash(&mut self.s);
if let Some(expr) = expr {
self.hash_expr(expr);
}
},
InlineAsmOperand::InOut { reg, late, expr } => {
reg.hash(&mut self.s);
late.hash(&mut self.s);
self.hash_expr(expr);
},
InlineAsmOperand::SplitInOut {
reg,
late,
in_expr,
out_expr,
} => {
reg.hash(&mut self.s);
late.hash(&mut self.s);
self.hash_expr(in_expr);
if let Some(out_expr) = out_expr {
self.hash_expr(out_expr);
}
},
InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr),
}
}
},
ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {},
ExprKind::Lit(ref l) => {
l.node.hash(&mut self.s);
},
ExprKind::Loop(ref b, ref i, ..) => {
self.hash_block(b);
if let Some(i) = *i {
self.hash_name(i.ident.name);
}
},
ExprKind::If(ref cond, ref then, ref else_opt) => {
let c: fn(_, _, _) -> _ = ExprKind::If;
c.hash(&mut self.s);
self.hash_expr(cond);
self.hash_expr(&**then);
if let Some(ref e) = *else_opt {
self.hash_expr(e);
}
},
ExprKind::Match(ref e, arms, ref s) => {
self.hash_expr(e);
for arm in arms {
// TODO: arm.pat?
if let Some(ref e) = arm.guard {
self.hash_guard(e);
}
self.hash_expr(&arm.body);
}
s.hash(&mut self.s);
},
ExprKind::MethodCall(ref path, ref _tys, args, ref _fn_span) => {
self.hash_name(path.ident.name);
self.hash_exprs(args);
},
ExprKind::ConstBlock(ref l_id) => {
self.hash_body(l_id.body);
},
ExprKind::Repeat(ref e, ref l_id) => {
self.hash_expr(e);
self.hash_body(l_id.body);
},
ExprKind::Ret(ref e) => {
if let Some(ref e) = *e {
self.hash_expr(e);
}
},
ExprKind::Path(ref qpath) => {
self.hash_qpath(qpath);
},
ExprKind::Struct(ref path, fields, ref expr) => {
self.hash_qpath(path);
for f in fields {
self.hash_name(f.ident.name);
self.hash_expr(&f.expr);
}
if let Some(ref e) = *expr {
self.hash_expr(e);
}
},
ExprKind::Tup(tup) => {
self.hash_exprs(tup);
},
ExprKind::Array(v) => {
self.hash_exprs(v);
},
ExprKind::Unary(lop, ref le) => {
lop.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
self.hash_expr(le);
},
}
}
pub fn hash_exprs(&mut self, e: &[Expr<'_>]) {
for e in e {
self.hash_expr(e);
}
}
pub fn hash_name(&mut self, n: Symbol) {
n.as_str().hash(&mut self.s);
}
pub fn hash_qpath(&mut self, p: &QPath<'_>) {
match *p {
QPath::Resolved(_, ref path) => {
self.hash_path(path);
},
QPath::TypeRelative(_, ref path) => {
self.hash_name(path.ident.name);
},
QPath::LangItem(lang_item, ..) => {
lang_item.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
},
}
// self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
}
pub fn hash_path(&mut self, p: &Path<'_>) {
p.is_global().hash(&mut self.s);
for p in p.segments {
self.hash_name(p.ident.name);
}
}
pub fn hash_stmt(&mut self, b: &Stmt<'_>) {
std::mem::discriminant(&b.kind).hash(&mut self.s);
match &b.kind {
StmtKind::Local(local) => {
if let Some(ref init) = local.init {
self.hash_expr(init);
}
},
StmtKind::Item(..) => {},
StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
self.hash_expr(expr);
},
}
}
pub fn hash_guard(&mut self, g: &Guard<'_>) {
match g {
Guard::If(ref expr) | Guard::IfLet(_, ref expr) => {
self.hash_expr(expr);
},
}
}
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
std::mem::discriminant(&lifetime.name).hash(&mut self.s);
if let LifetimeName::Param(ref name) = lifetime.name {
std::mem::discriminant(name).hash(&mut self.s);
match name {
ParamName::Plain(ref ident) => {
ident.name.hash(&mut self.s);
},
ParamName::Fresh(ref size) => {
size.hash(&mut self.s);
},
ParamName::Error => {},
}
}
}
pub fn hash_ty(&mut self, ty: &Ty<'_>) {
self.hash_tykind(&ty.kind);
}
pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
std::mem::discriminant(ty).hash(&mut self.s);
match ty {
TyKind::Slice(ty) => {
self.hash_ty(ty);
},
TyKind::Array(ty, anon_const) => {
self.hash_ty(ty);
self.hash_body(anon_const.body);
},
TyKind::Ptr(mut_ty) => {
self.hash_ty(&mut_ty.ty);
mut_ty.mutbl.hash(&mut self.s);
},
TyKind::Rptr(lifetime, mut_ty) => {
self.hash_lifetime(lifetime);
self.hash_ty(&mut_ty.ty);
mut_ty.mutbl.hash(&mut self.s);
},
TyKind::BareFn(bfn) => {
bfn.unsafety.hash(&mut self.s);
bfn.abi.hash(&mut self.s);
for arg in bfn.decl.inputs {
self.hash_ty(&arg);
}
match bfn.decl.output {
FnRetTy::DefaultReturn(_) => {
().hash(&mut self.s);
},
FnRetTy::Return(ref ty) => {
self.hash_ty(ty);
},
}
bfn.decl.c_variadic.hash(&mut self.s);
},
TyKind::Tup(ty_list) => {
for ty in *ty_list {
self.hash_ty(ty);
}
},
TyKind::Path(qpath) => match qpath {
QPath::Resolved(ref maybe_ty, ref path) => {
if let Some(ref ty) = maybe_ty {
self.hash_ty(ty);
}
for segment in path.segments {
segment.ident.name.hash(&mut self.s);
self.hash_generic_args(segment.args().args);
}
},
QPath::TypeRelative(ref ty, ref segment) => {
self.hash_ty(ty);
segment.ident.name.hash(&mut self.s);
},
QPath::LangItem(lang_item, ..) => {
lang_item.hash(&mut self.s);
},
},
TyKind::OpaqueDef(_, arg_list) => {
self.hash_generic_args(arg_list);
},
TyKind::TraitObject(_, lifetime) => {
self.hash_lifetime(lifetime);
},
TyKind::Typeof(anon_const) => {
self.hash_body(anon_const.body);
},
TyKind::Err | TyKind::Infer | TyKind::Never => {},
}
}
pub fn hash_body(&mut self, body_id: BodyId) {
// swap out TypeckResults when hashing a body
let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body_id));
self.hash_expr(&self.cx.tcx.hir().body(body_id).value);
self.maybe_typeck_results = old_maybe_typeck_results;
}
fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
for arg in arg_list {
match arg {
GenericArg::Lifetime(ref l) => self.hash_lifetime(l),
GenericArg::Type(ref ty) => self.hash_ty(&ty),
GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
}
}
}
}

View File

@@ -4,7 +4,7 @@ use crate::utils::{
span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq,
};
use if_chain::if_chain;
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, NodeId};
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId};
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
@@ -301,17 +301,12 @@ declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
impl EarlyLintPass for ClippyLintsInternal {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &AstCrate) {
if let Some(utils) = krate
.module
.items
.iter()
.find(|item| item.ident.name.as_str() == "utils")
{
if let ItemKind::Mod(ref utils_mod) = utils.kind {
if let Some(paths) = utils_mod.items.iter().find(|item| item.ident.name.as_str() == "paths") {
if let ItemKind::Mod(ref paths_mod) = paths.kind {
if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") {
if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind {
if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") {
if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind {
let mut last_name: Option<SymbolStr> = None;
for item in &*paths_mod.items {
for item in items {
let name = item.ident.as_str();
if let Some(ref last_name) = last_name {
if **last_name > *name {
@@ -343,7 +338,7 @@ impl_lint_pass!(LintWithoutLintPass => [DEFAULT_LINT, LINT_WITHOUT_LINT_PASS]);
impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !run_lints(cx, &[DEFAULT_LINT], item.hir_id) {
if !run_lints(cx, &[DEFAULT_LINT], item.hir_id()) {
return;
}
@@ -393,7 +388,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
.find(|iiref| iiref.ident.as_str() == "get_lints")
.expect("LintPass needs to implement get_lints")
.id
.hir_id,
.hir_id(),
);
collector.visit_expr(&cx.tcx.hir().body(body_id).value);
}
@@ -861,7 +856,7 @@ declare_lint_pass!(InvalidPaths => [INVALID_PATHS]);
impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let local_def_id = &cx.tcx.parent_module(item.hir_id);
let local_def_id = &cx.tcx.parent_module(item.hir_id());
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
if_chain! {
if mod_name.as_str() == "paths";

File diff suppressed because it is too large Load Diff

View File

@@ -1,228 +0,0 @@
use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind};
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Radix {
Binary,
Octal,
Decimal,
Hexadecimal,
}
impl Radix {
/// Returns a reasonable digit group size for this radix.
#[must_use]
fn suggest_grouping(self) -> usize {
match self {
Self::Binary | Self::Hexadecimal => 4,
Self::Octal | Self::Decimal => 3,
}
}
}
/// A helper method to format numeric literals with digit grouping.
/// `lit` must be a valid numeric literal without suffix.
pub fn format(lit: &str, type_suffix: Option<&str>, float: bool) -> String {
NumericLiteral::new(lit, type_suffix, float).format()
}
#[derive(Debug)]
pub struct NumericLiteral<'a> {
/// Which radix the literal was represented in.
pub radix: Radix,
/// The radix prefix, if present.
pub prefix: Option<&'a str>,
/// The integer part of the number.
pub integer: &'a str,
/// The fraction part of the number.
pub fraction: Option<&'a str>,
/// The exponent separator (b'e' or b'E') including preceding underscore if present
/// and the exponent part.
pub exponent: Option<(&'a str, &'a str)>,
/// The type suffix, including preceding underscore if present.
pub suffix: Option<&'a str>,
}
impl<'a> NumericLiteral<'a> {
pub fn from_lit(src: &'a str, lit: &Lit) -> Option<NumericLiteral<'a>> {
NumericLiteral::from_lit_kind(src, &lit.kind)
}
pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option<NumericLiteral<'a>> {
if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) {
let (unsuffixed, suffix) = split_suffix(&src, lit_kind);
let float = matches!(lit_kind, LitKind::Float(..));
Some(NumericLiteral::new(unsuffixed, suffix, float))
} else {
None
}
}
#[must_use]
pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self {
// Determine delimiter for radix prefix, if present, and radix.
let radix = if lit.starts_with("0x") {
Radix::Hexadecimal
} else if lit.starts_with("0b") {
Radix::Binary
} else if lit.starts_with("0o") {
Radix::Octal
} else {
Radix::Decimal
};
// Grab part of the literal after prefix, if present.
let (prefix, mut sans_prefix) = if let Radix::Decimal = radix {
(None, lit)
} else {
let (p, s) = lit.split_at(2);
(Some(p), s)
};
if suffix.is_some() && sans_prefix.ends_with('_') {
// The '_' before the suffix isn't part of the digits
sans_prefix = &sans_prefix[..sans_prefix.len() - 1];
}
let (integer, fraction, exponent) = Self::split_digit_parts(sans_prefix, float);
Self {
radix,
prefix,
integer,
fraction,
exponent,
suffix,
}
}
pub fn is_decimal(&self) -> bool {
self.radix == Radix::Decimal
}
pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) {
let mut integer = digits;
let mut fraction = None;
let mut exponent = None;
if float {
for (i, c) in digits.char_indices() {
match c {
'.' => {
integer = &digits[..i];
fraction = Some(&digits[i + 1..]);
},
'e' | 'E' => {
let exp_start = if digits[..i].ends_with('_') { i - 1 } else { i };
if integer.len() > exp_start {
integer = &digits[..exp_start];
} else {
fraction = Some(&digits[integer.len() + 1..exp_start]);
};
exponent = Some((&digits[exp_start..=i], &digits[i + 1..]));
break;
},
_ => {},
}
}
}
(integer, fraction, exponent)
}
/// Returns literal formatted in a sensible way.
pub fn format(&self) -> String {
let mut output = String::new();
if let Some(prefix) = self.prefix {
output.push_str(prefix);
}
let group_size = self.radix.suggest_grouping();
Self::group_digits(
&mut output,
self.integer,
group_size,
true,
self.radix == Radix::Hexadecimal,
);
if let Some(fraction) = self.fraction {
output.push('.');
Self::group_digits(&mut output, fraction, group_size, false, false);
}
if let Some((separator, exponent)) = self.exponent {
output.push_str(separator);
Self::group_digits(&mut output, exponent, group_size, true, false);
}
if let Some(suffix) = self.suffix {
output.push('_');
output.push_str(suffix);
}
output
}
pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) {
debug_assert!(group_size > 0);
let mut digits = input.chars().filter(|&c| c != '_');
let first_group_size;
if partial_group_first {
first_group_size = (digits.clone().count() - 1) % group_size + 1;
if pad {
for _ in 0..group_size - first_group_size {
output.push('0');
}
}
} else {
first_group_size = group_size;
}
for _ in 0..first_group_size {
if let Some(digit) = digits.next() {
output.push(digit);
}
}
for (c, i) in digits.zip((0..group_size).cycle()) {
if i == 0 {
output.push('_');
}
output.push(c);
}
}
}
fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
debug_assert!(lit_kind.is_numeric());
lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| {
let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length);
(unsuffixed, Some(suffix))
})
}
fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {
debug_assert!(lit_kind.is_numeric());
let suffix = match lit_kind {
LitKind::Int(_, int_lit_kind) => match int_lit_kind {
LitIntType::Signed(int_ty) => Some(int_ty.name_str()),
LitIntType::Unsigned(uint_ty) => Some(uint_ty.name_str()),
LitIntType::Unsuffixed => None,
},
LitKind::Float(_, float_lit_kind) => match float_lit_kind {
LitFloatType::Suffixed(float_ty) => Some(float_ty.name_str()),
LitFloatType::Unsuffixed => None,
},
_ => None,
};
suffix.map(str::len)
}

View File

@@ -1,181 +0,0 @@
//! This module contains paths to types and functions Clippy needs to know
//! about.
//!
//! Whenever possible, please consider diagnostic items over hardcoded paths.
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_fmt"];
pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
pub const BOX: [&str; 3] = ["alloc", "boxed", "Box"];
pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
pub const COPY: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
pub const COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy"];
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
pub const DROP: [&str; 3] = ["core", "mem", "drop"];
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
#[cfg(feature = "internal-lints")]
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
pub const FILE: [&str; 3] = ["std", "fs", "File"];
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
pub const FN: [&str; 3] = ["core", "ops", "Fn"];
pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
#[cfg(feature = "internal-lints")]
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
#[cfg(feature = "internal-lints")]
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
pub const INDEX: [&str; 3] = ["core", "ops", "Index"];
pub const INDEX_MUT: [&str; 3] = ["core", "ops", "IndexMut"];
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"];
pub const INTO: [&str; 3] = ["core", "convert", "Into"];
pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
#[cfg(feature = "internal-lints")]
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal-lints")]
pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
#[cfg(feature = "internal-lints")]
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"];
pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
pub const OPTION: [&str; 3] = ["core", "option", "Option"];
pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
pub(super) const PANICKING_PANIC: [&str; 3] = ["core", "panicking", "panic"];
pub(super) const PANICKING_PANIC_FMT: [&str; 3] = ["core", "panicking", "panic_fmt"];
pub(super) const PANICKING_PANIC_STR: [&str; 3] = ["core", "panicking", "panic_str"];
pub(super) const PANIC_ANY: [&str; 3] = ["std", "panic", "panic_any"];
pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"];
pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"];
pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"];
pub const PATH: [&str; 3] = ["std", "path", "Path"];
pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"];
pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"];
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"];
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
pub const STRING: [&str; 3] = ["alloc", "string", "String"];
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
#[cfg(feature = "internal-lints")]
pub const SYMBOL: [&str; 3] = ["rustc_span", "symbol", "Symbol"];
#[cfg(feature = "internal-lints")]
pub const SYMBOL_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Symbol", "as_str"];
#[cfg(feature = "internal-lints")]
pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern"];
#[cfg(feature = "internal-lints")]
pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"];
#[cfg(feature = "internal-lints")]
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
#[cfg(feature = "internal-lints")]
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
pub const TRY_FROM: [&str; 4] = ["core", "convert", "TryFrom", "try_from"];
pub const TRY_INTO_TRAIT: [&str; 3] = ["core", "convert", "TryInto"];
pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];

View File

@@ -1,86 +0,0 @@
use crate::utils::{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;
use rustc_middle::hir::map::Map;
use rustc_span::{Span, Symbol};
use std::borrow::Cow;
pub fn get_spans(
cx: &LateContext<'_>,
opt_body_id: Option<BodyId>,
idx: usize,
replacements: &[(&'static str, &'static str)],
) -> Option<Vec<(Span, Cow<'static, str>)>> {
if let Some(body) = opt_body_id.map(|id| cx.tcx.hir().body(id)) {
get_binding_name(&body.params[idx]).map_or_else(
|| Some(vec![]),
|name| extract_clone_suggestions(cx, name, replacements, body),
)
} else {
Some(vec![])
}
}
fn extract_clone_suggestions<'tcx>(
cx: &LateContext<'tcx>,
name: Symbol,
replace: &[(&'static str, &'static str)],
body: &'tcx Body<'_>,
) -> Option<Vec<(Span, Cow<'static, str>)>> {
let mut visitor = PtrCloneVisitor {
cx,
name,
replace,
spans: vec![],
abort: false,
};
visitor.visit_body(body);
if visitor.abort {
None
} else {
Some(visitor.spans)
}
}
struct PtrCloneVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
name: Symbol,
replace: &'a [(&'static str, &'static str)],
spans: Vec<(Span, Cow<'static, str>)>,
abort: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for PtrCloneVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.abort {
return;
}
if let ExprKind::MethodCall(ref seg, _, ref args, _) = expr.kind {
if args.len() == 1 && match_var(&args[0], self.name) {
if seg.ident.name.as_str() == "capacity" {
self.abort = true;
return;
}
for &(fn_name, suffix) in self.replace {
if seg.ident.name.as_str() == fn_name {
self.spans
.push((expr.span, snippet(self.cx, args[0].span, "_") + suffix));
return;
}
}
}
}
walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
fn get_binding_name(arg: &Param<'_>) -> Option<Symbol> {
get_pat_name(&arg.pat)
}

View File

@@ -1,347 +0,0 @@
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::{
Body, CastKind, NullOp, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
TerminatorKind,
};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_target::spec::abi::Abi::RustIntrinsic;
use std::borrow::Cow;
type McfResult = Result<(), (Span, Cow<'static, str>)>;
pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
let def_id = body.source.def_id();
let mut current = def_id;
loop {
let predicates = tcx.predicates_of(current);
for (predicate, _) in predicates.predicates {
match predicate.kind().skip_binder() {
ty::PredicateKind::RegionOutlives(_)
| ty::PredicateKind::TypeOutlives(_)
| ty::PredicateKind::WellFormed(_)
| ty::PredicateKind::Projection(_)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
ty::PredicateKind::Trait(pred, _) => {
if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
continue;
}
match pred.self_ty().kind() {
ty::Param(ref p) => {
let generics = tcx.generics_of(current);
let def = generics.type_param(p, tcx);
let span = tcx.def_span(def.def_id);
return Err((
span,
"trait bounds other than `Sized` \
on const fn parameters are unstable"
.into(),
));
},
// other kinds of bounds are either tautologies
// or cause errors in other passes
_ => continue,
}
},
}
}
match predicates.parent {
Some(parent) => current = parent,
None => break,
}
}
for local in &body.local_decls {
check_ty(tcx, local.ty, local.source_info.span)?;
}
// impl trait is gone in MIR, so check the return type manually
check_ty(
tcx,
tcx.fn_sig(def_id).output().skip_binder(),
body.local_decls.iter().next().unwrap().source_info.span,
)?;
for bb in body.basic_blocks() {
check_terminator(tcx, body, bb.terminator())?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt)?;
}
}
Ok(())
}
fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult {
for arg in ty.walk() {
let ty = match arg.unpack() {
GenericArgKind::Type(ty) => ty,
// No constraints on lifetimes or constants, except potentially
// constants' types, but `walk` will get to them as well.
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => continue,
};
match ty.kind() {
ty::Ref(_, _, hir::Mutability::Mut) => {
return Err((span, "mutable references in const fn are unstable".into()));
},
ty::Opaque(..) => return Err((span, "`impl Trait` in const fn is unstable".into())),
ty::FnPtr(..) => {
return Err((span, "function pointers in const fn are unstable".into()));
},
ty::Dynamic(preds, _) => {
for pred in preds.iter() {
match pred.skip_binder() {
ty::ExistentialPredicate::AutoTrait(_) | ty::ExistentialPredicate::Projection(_) => {
return Err((
span,
"trait bounds other than `Sized` \
on const fn parameters are unstable"
.into(),
));
},
ty::ExistentialPredicate::Trait(trait_ref) => {
if Some(trait_ref.def_id) != tcx.lang_items().sized_trait() {
return Err((
span,
"trait bounds other than `Sized` \
on const fn parameters are unstable"
.into(),
));
}
},
}
}
},
_ => {},
}
}
Ok(())
}
fn check_rvalue(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, rvalue: &Rvalue<'tcx>, span: Span) -> McfResult {
match rvalue {
Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => check_operand(tcx, operand, span, body),
Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
check_place(tcx, *place, span, body)
},
Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
use rustc_middle::ty::cast::CastTy;
let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast");
let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
match (cast_in, cast_out) {
(CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => {
Err((span, "casting pointers to ints is unstable in const fn".into()))
},
_ => check_operand(tcx, operand, span, body),
}
},
Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer | PointerCast::ArrayToPointer), operand, _) => {
check_operand(tcx, operand, span, body)
},
Rvalue::Cast(
CastKind::Pointer(
PointerCast::UnsafeFnPointer | PointerCast::ClosureFnPointer(_) | PointerCast::ReifyFnPointer,
),
_,
_,
) => Err((span, "function pointer casts are not allowed in const fn".into())),
Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), op, cast_ty) => {
let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
deref_ty.ty
} else {
// We cannot allow this for now.
return Err((span, "unsizing casts are only allowed for references right now".into()));
};
let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
check_operand(tcx, op, span, body)?;
// Casting/coercing things to slices is fine.
Ok(())
} else {
// We just can't allow trait objects until we have figured out trait method calls.
Err((span, "unsizing casts are not allowed in const fn".into()))
}
},
// binops are fine on integers
Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
check_operand(tcx, lhs, span, body)?;
check_operand(tcx, rhs, span, body)?;
let ty = lhs.ty(body, tcx);
if ty.is_integral() || ty.is_bool() || ty.is_char() {
Ok(())
} else {
Err((
span,
"only int, `bool` and `char` operations are stable in const fn".into(),
))
}
},
Rvalue::NullaryOp(NullOp::SizeOf, _) => Ok(()),
Rvalue::NullaryOp(NullOp::Box, _) => Err((span, "heap allocations are not allowed in const fn".into())),
Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx);
if ty.is_integral() || ty.is_bool() {
check_operand(tcx, operand, span, body)
} else {
Err((span, "only int and `bool` operations are stable in const fn".into()))
}
},
Rvalue::Aggregate(_, operands) => {
for operand in operands {
check_operand(tcx, operand, span, body)?;
}
Ok(())
},
}
}
fn check_statement(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, def_id: DefId, statement: &Statement<'tcx>) -> McfResult {
let span = statement.source_info.span;
match &statement.kind {
StatementKind::Assign(box (place, rval)) => {
check_place(tcx, *place, span, body)?;
check_rvalue(tcx, body, def_id, rval, span)
},
StatementKind::FakeRead(_, place) |
// just an assignment
StatementKind::SetDiscriminant { place, .. } => check_place(tcx, **place, span, body),
StatementKind::LlvmInlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
// These are all NOPs
StatementKind::StorageLive(_)
| StatementKind::StorageDead(_)
| StatementKind::Retag { .. }
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::Nop => Ok(()),
}
}
fn check_operand(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
match operand {
Operand::Move(place) | Operand::Copy(place) => check_place(tcx, *place, span, body),
Operand::Constant(c) => match c.check_static_ptr(tcx) {
Some(_) => Err((span, "cannot access `static` items in const fn".into())),
None => Ok(()),
},
}
}
fn check_place(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
let mut cursor = place.projection.as_ref();
while let [ref proj_base @ .., elem] = *cursor {
cursor = proj_base;
match elem {
ProjectionElem::Field(..) => {
let base_ty = Place::ty_from(place.local, &proj_base, body, tcx).ty;
if let Some(def) = base_ty.ty_adt_def() {
// No union field accesses in `const fn`
if def.is_union() {
return Err((span, "accessing union fields is unstable".into()));
}
}
},
ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Downcast(..)
| ProjectionElem::Subslice { .. }
| ProjectionElem::Deref
| ProjectionElem::Index(_) => {},
}
}
Ok(())
}
fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Terminator<'tcx>) -> McfResult {
let span = terminator.source_info.span;
match &terminator.kind {
TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Goto { .. }
| TerminatorKind::Return
| TerminatorKind::Resume
| TerminatorKind::Unreachable => Ok(()),
TerminatorKind::Drop { place, .. } => check_place(tcx, *place, span, body),
TerminatorKind::DropAndReplace { place, value, .. } => {
check_place(tcx, *place, span, body)?;
check_operand(tcx, value, span, body)
},
TerminatorKind::SwitchInt {
discr,
switch_ty: _,
targets: _,
} => check_operand(tcx, discr, span, body),
TerminatorKind::Abort => Err((span, "abort is not stable in const fn".into())),
TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } => {
Err((span, "const fn generators are unstable".into()))
},
TerminatorKind::Call {
func,
args,
from_hir_call: _,
destination: _,
cleanup: _,
fn_span: _,
} => {
let fn_ty = func.ty(body, tcx);
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) {
return Err((
span,
format!(
"can only call other `const fn` within a `const fn`, \
but `{:?}` is not stable as `const fn`",
func,
)
.into(),
));
}
// HACK: This is to "unstabilize" the `transmute` intrinsic
// within const fns. `transmute` is allowed in all other const contexts.
// This won't really scale to more intrinsics or functions. Let's allow const
// transmutes in const fn before we add more hacks to this.
if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic && tcx.item_name(fn_def_id) == sym::transmute {
return Err((
span,
"can only call `transmute` from const items, not `const fn`".into(),
));
}
check_operand(tcx, func, span, body)?;
for arg in args {
check_operand(tcx, arg, span, body)?;
}
Ok(())
} else {
Err((span, "can only call other const fns within const fn".into()))
}
},
TerminatorKind::Assert {
cond,
expected: _,
msg: _,
target: _,
cleanup: _,
} => check_operand(tcx, cond, span, body),
TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
}
}

View File

@@ -1,683 +0,0 @@
//! 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 rustc_ast::util::parser::AssocOp;
use rustc_ast::{ast, token};
use rustc_ast_pretty::pprust::token_kind_to_string;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{EarlyContext, LateContext, LintContext};
use rustc_span::source_map::{CharPos, Span};
use rustc_span::{BytePos, Pos};
use std::borrow::Cow;
use std::convert::TryInto;
use std::fmt::Display;
use std::ops::{Add, Neg, Not, Sub};
/// A helper type to build suggestion correctly handling parenthesis.
#[derive(Clone, PartialEq)]
pub enum Sugg<'a> {
/// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
NonParen(Cow<'a, str>),
/// An expression that does not fit in other variants.
MaybeParen(Cow<'a, str>),
/// A binary operator expression, including `as`-casts and explicit type
/// coercion.
BinOp(AssocOp, Cow<'a, str>),
}
/// Literal constant `0`, for convenience.
pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
/// Literal constant `1`, for convenience.
pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
/// a constant represents an empty string, for convenience.
pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
impl Display for Sugg<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match *self {
Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
}
}
}
#[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
impl<'a> Sugg<'a> {
/// Prepare a suggestion from an expression.
pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
snippet_opt(cx, expr.span).map(|snippet| {
let snippet = Cow::Owned(snippet);
Self::hir_from_snippet(expr, snippet)
})
}
/// Convenience function around `hir_opt` for suggestions with a default
/// text.
pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
}
/// Same as `hir`, 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 hir_with_applicability(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
default: &'a str,
applicability: &mut Applicability,
) -> Self {
if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
*applicability = Applicability::MaybeIncorrect;
}
Self::hir_opt(cx, expr).unwrap_or_else(|| {
if *applicability == Applicability::MachineApplicable {
*applicability = Applicability::HasPlaceholders;
}
Sugg::NonParen(Cow::Borrowed(default))
})
}
/// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
let snippet = snippet_with_macro_callsite(cx, expr.span, default);
Self::hir_from_snippet(expr, snippet)
}
/// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
/// function variants of `Sugg`, since these use different snippet functions.
fn hir_from_snippet(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
if let Some(range) = higher::range(expr) {
let op = match range.limits {
ast::RangeLimits::HalfOpen => AssocOp::DotDot,
ast::RangeLimits::Closed => AssocOp::DotDotEq,
};
return Sugg::BinOp(op, snippet);
}
match expr.kind {
hir::ExprKind::AddrOf(..)
| hir::ExprKind::Box(..)
| hir::ExprKind::If(..)
| hir::ExprKind::Closure(..)
| hir::ExprKind::Unary(..)
| hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
hir::ExprKind::Continue(..)
| hir::ExprKind::Yield(..)
| hir::ExprKind::Array(..)
| hir::ExprKind::Block(..)
| hir::ExprKind::Break(..)
| hir::ExprKind::Call(..)
| hir::ExprKind::Field(..)
| hir::ExprKind::Index(..)
| hir::ExprKind::InlineAsm(..)
| hir::ExprKind::LlvmInlineAsm(..)
| hir::ExprKind::ConstBlock(..)
| hir::ExprKind::Lit(..)
| hir::ExprKind::Loop(..)
| hir::ExprKind::MethodCall(..)
| hir::ExprKind::Path(..)
| hir::ExprKind::Repeat(..)
| hir::ExprKind::Ret(..)
| hir::ExprKind::Struct(..)
| hir::ExprKind::Tup(..)
| hir::ExprKind::DropTemps(_)
| hir::ExprKind::Err => Sugg::NonParen(snippet),
hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
}
}
/// Prepare a suggestion from an expression.
pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
use rustc_ast::ast::RangeLimits;
let snippet = if expr.span.from_expansion() {
snippet_with_macro_callsite(cx, expr.span, default)
} else {
snippet(cx, expr.span, default)
};
match expr.kind {
ast::ExprKind::AddrOf(..)
| ast::ExprKind::Box(..)
| ast::ExprKind::Closure(..)
| ast::ExprKind::If(..)
| ast::ExprKind::Let(..)
| ast::ExprKind::Unary(..)
| ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
ast::ExprKind::Async(..)
| ast::ExprKind::Block(..)
| ast::ExprKind::Break(..)
| ast::ExprKind::Call(..)
| ast::ExprKind::Continue(..)
| ast::ExprKind::Yield(..)
| ast::ExprKind::Field(..)
| ast::ExprKind::ForLoop(..)
| ast::ExprKind::Index(..)
| ast::ExprKind::InlineAsm(..)
| ast::ExprKind::LlvmInlineAsm(..)
| ast::ExprKind::ConstBlock(..)
| ast::ExprKind::Lit(..)
| ast::ExprKind::Loop(..)
| ast::ExprKind::MacCall(..)
| ast::ExprKind::MethodCall(..)
| ast::ExprKind::Paren(..)
| ast::ExprKind::Underscore
| ast::ExprKind::Path(..)
| ast::ExprKind::Repeat(..)
| ast::ExprKind::Ret(..)
| ast::ExprKind::Struct(..)
| ast::ExprKind::Try(..)
| ast::ExprKind::TryBlock(..)
| ast::ExprKind::Tup(..)
| ast::ExprKind::Array(..)
| ast::ExprKind::While(..)
| ast::ExprKind::Await(..)
| ast::ExprKind::Err => Sugg::NonParen(snippet),
ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
}
}
/// Convenience method to create the `<lhs> && <rhs>` suggestion.
pub fn and(self, rhs: &Self) -> Sugg<'static> {
make_binop(ast::BinOpKind::And, &self, rhs)
}
/// Convenience method to create the `<lhs> & <rhs>` suggestion.
pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
make_binop(ast::BinOpKind::BitAnd, &self, rhs)
}
/// Convenience method to create the `<lhs> as <rhs>` suggestion.
pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
}
/// Convenience method to create the `&<expr>` suggestion.
pub fn addr(self) -> Sugg<'static> {
make_unop("&", self)
}
/// Convenience method to create the `&mut <expr>` suggestion.
pub fn mut_addr(self) -> Sugg<'static> {
make_unop("&mut ", self)
}
/// Convenience method to create the `*<expr>` suggestion.
pub fn deref(self) -> Sugg<'static> {
make_unop("*", self)
}
/// Convenience method to create the `&*<expr>` suggestion. Currently this
/// is needed because `sugg.deref().addr()` produces an unnecessary set of
/// parentheses around the deref.
pub fn addr_deref(self) -> Sugg<'static> {
make_unop("&*", self)
}
/// Convenience method to create the `&mut *<expr>` suggestion. Currently
/// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
/// set of parentheses around the deref.
pub fn mut_addr_deref(self) -> Sugg<'static> {
make_unop("&mut *", self)
}
/// Convenience method to transform suggestion into a return call
pub fn make_return(self) -> Sugg<'static> {
Sugg::NonParen(Cow::Owned(format!("return {}", self)))
}
/// Convenience method to transform suggestion into a block
/// where the suggestion is a trailing expression
pub fn blockify(self) -> Sugg<'static> {
Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
}
/// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
/// suggestion.
#[allow(dead_code)]
pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
match limit {
ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
}
}
/// Adds parenthesis to any expression that might need them. Suitable to the
/// `self` argument of a method call
/// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
pub fn maybe_par(self) -> Self {
match self {
Sugg::NonParen(..) => self,
// `(x)` and `(x).y()` both don't need additional parens.
Sugg::MaybeParen(sugg) => {
if sugg.starts_with('(') && sugg.ends_with(')') {
Sugg::MaybeParen(sugg)
} else {
Sugg::NonParen(format!("({})", sugg).into())
}
},
Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
}
}
}
// Copied from the rust standart library, and then edited
macro_rules! forward_binop_impls_to_ref {
(impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
impl $imp<$t> for &$t {
type Output = $o;
fn $method(self, other: $t) -> $o {
$imp::$method(self, &other)
}
}
impl $imp<&$t> for $t {
type Output = $o;
fn $method(self, other: &$t) -> $o {
$imp::$method(&self, other)
}
}
impl $imp for $t {
type Output = $o;
fn $method(self, other: $t) -> $o {
$imp::$method(&self, &other)
}
}
};
}
impl Add for &Sugg<'_> {
type Output = Sugg<'static>;
fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
make_binop(ast::BinOpKind::Add, self, rhs)
}
}
impl Sub for &Sugg<'_> {
type Output = Sugg<'static>;
fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
make_binop(ast::BinOpKind::Sub, self, rhs)
}
}
forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
impl Neg for Sugg<'_> {
type Output = Sugg<'static>;
fn neg(self) -> Sugg<'static> {
make_unop("-", self)
}
}
impl Not for Sugg<'_> {
type Output = Sugg<'static>;
fn not(self) -> Sugg<'static> {
make_unop("!", self)
}
}
/// Helper type to display either `foo` or `(foo)`.
struct ParenHelper<T> {
/// `true` if parentheses are needed.
paren: bool,
/// The main thing to display.
wrapped: T,
}
impl<T> ParenHelper<T> {
/// Builds a `ParenHelper`.
fn new(paren: bool, wrapped: T) -> Self {
Self { paren, wrapped }
}
}
impl<T: Display> Display for ParenHelper<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
if self.paren {
write!(f, "({})", self.wrapped)
} else {
self.wrapped.fmt(f)
}
}
}
/// Builds the string for `<op><expr>` adding parenthesis when necessary.
///
/// For convenience, the operator is taken as a string because all unary
/// operators have the same
/// precedence.
pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
}
/// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
///
/// Precedence of shift operator relative to other arithmetic operation is
/// often confusing so
/// parenthesis will always be added for a mix of these.
pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
/// Returns `true` if the operator is a shift operator `<<` or `>>`.
fn is_shift(op: AssocOp) -> bool {
matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
}
/// Returns `true` if the operator is a arithmetic operator
/// (i.e., `+`, `-`, `*`, `/`, `%`).
fn is_arith(op: AssocOp) -> bool {
matches!(
op,
AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
)
}
/// Returns `true` if the operator `op` needs parenthesis with the operator
/// `other` in the direction `dir`.
fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
other.precedence() < op.precedence()
|| (other.precedence() == op.precedence()
&& ((op != other && associativity(op) != dir)
|| (op == other && associativity(op) != Associativity::Both)))
|| is_shift(op) && is_arith(other)
|| is_shift(other) && is_arith(op)
}
let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
needs_paren(op, lop, Associativity::Left)
} else {
false
};
let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
needs_paren(op, rop, Associativity::Right)
} else {
false
};
let lhs = ParenHelper::new(lhs_paren, lhs);
let rhs = ParenHelper::new(rhs_paren, rhs);
let sugg = match op {
AssocOp::Add
| AssocOp::BitAnd
| AssocOp::BitOr
| AssocOp::BitXor
| AssocOp::Divide
| AssocOp::Equal
| AssocOp::Greater
| AssocOp::GreaterEqual
| AssocOp::LAnd
| AssocOp::LOr
| AssocOp::Less
| AssocOp::LessEqual
| AssocOp::Modulus
| AssocOp::Multiply
| AssocOp::NotEqual
| AssocOp::ShiftLeft
| AssocOp::ShiftRight
| AssocOp::Subtract => format!(
"{} {} {}",
lhs,
op.to_ast_binop().expect("Those are AST ops").to_string(),
rhs
),
AssocOp::Assign => format!("{} = {}", lhs, rhs),
AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
AssocOp::As => format!("{} as {}", lhs, rhs),
AssocOp::DotDot => format!("{}..{}", lhs, rhs),
AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
AssocOp::Colon => format!("{}: {}", lhs, rhs),
};
Sugg::BinOp(op, sugg.into())
}
/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
}
#[derive(PartialEq, Eq, Clone, Copy)]
/// Operator associativity.
enum Associativity {
/// The operator is both left-associative and right-associative.
Both,
/// The operator is left-associative.
Left,
/// The operator is not associative.
None,
/// The operator is right-associative.
Right,
}
/// Returns the associativity/fixity of an operator. The difference with
/// `AssocOp::fixity` is that an operator can be both left and right associative
/// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
///
/// Chained `as` and explicit `:` type coercion never need inner parenthesis so
/// they are considered
/// associative.
#[must_use]
fn associativity(op: AssocOp) -> Associativity {
use rustc_ast::util::parser::AssocOp::{
Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
};
match op {
Assign | AssignOp(_) => Associativity::Right,
Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
| Subtract => Associativity::Left,
DotDot | DotDotEq => Associativity::None,
}
}
/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
AssocOp::AssignOp(match op.node {
hir::BinOpKind::Add => Plus,
hir::BinOpKind::BitAnd => And,
hir::BinOpKind::BitOr => Or,
hir::BinOpKind::BitXor => Caret,
hir::BinOpKind::Div => Slash,
hir::BinOpKind::Mul => Star,
hir::BinOpKind::Rem => Percent,
hir::BinOpKind::Shl => Shl,
hir::BinOpKind::Shr => Shr,
hir::BinOpKind::Sub => Minus,
hir::BinOpKind::And
| hir::BinOpKind::Eq
| hir::BinOpKind::Ge
| hir::BinOpKind::Gt
| hir::BinOpKind::Le
| hir::BinOpKind::Lt
| hir::BinOpKind::Ne
| hir::BinOpKind::Or => panic!("This operator does not exist"),
})
}
/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
use rustc_ast::ast::BinOpKind::{
Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
};
use rustc_ast::token::BinOpToken;
AssocOp::AssignOp(match op.node {
Add => BinOpToken::Plus,
BitAnd => BinOpToken::And,
BitOr => BinOpToken::Or,
BitXor => BinOpToken::Caret,
Div => BinOpToken::Slash,
Mul => BinOpToken::Star,
Rem => BinOpToken::Percent,
Shl => BinOpToken::Shl,
Shr => BinOpToken::Shr,
Sub => BinOpToken::Minus,
And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
})
}
/// Returns the indentation before `span` if there are nothing but `[ \t]`
/// before it on its line.
fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
let lo = cx.sess().source_map().lookup_char_pos(span.lo());
lo.file
.get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
.and_then(|line| {
if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
// We can mix char and byte positions here because we only consider `[ \t]`.
if lo.col == CharPos(pos) {
Some(line[..pos].into())
} else {
None
}
} else {
None
}
})
}
/// Convenience extension trait for `DiagnosticBuilder`.
pub trait DiagnosticBuilderExt<T: LintContext> {
/// Suggests to add an attribute to an item.
///
/// Correctly handles indentation of the attribute and item.
///
/// # Example
///
/// ```rust,ignore
/// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
/// ```
fn suggest_item_with_attr<D: Display + ?Sized>(
&mut self,
cx: &T,
item: Span,
msg: &str,
attr: &D,
applicability: Applicability,
);
/// Suggest to add an item before another.
///
/// The item should not be indented (except for inner indentation).
///
/// # Example
///
/// ```rust,ignore
/// diag.suggest_prepend_item(cx, item,
/// "fn foo() {
/// bar();
/// }");
/// ```
fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
/// Suggest to completely remove an item.
///
/// This will remove an item and all following whitespace until the next non-whitespace
/// character. This should work correctly if item is on the same indentation level as the
/// following item.
///
/// # Example
///
/// ```rust,ignore
/// diag.suggest_remove_item(cx, item, "remove this")
/// ```
fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
}
impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
fn suggest_item_with_attr<D: Display + ?Sized>(
&mut self,
cx: &T,
item: Span,
msg: &str,
attr: &D,
applicability: Applicability,
) {
if let Some(indent) = indentation(cx, item) {
let span = item.with_hi(item.lo());
self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
}
}
fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
if let Some(indent) = indentation(cx, item) {
let span = item.with_hi(item.lo());
let mut first = true;
let new_item = new_item
.lines()
.map(|l| {
if first {
first = false;
format!("{}\n", l)
} else {
format!("{}{}\n", indent, l)
}
})
.collect::<String>();
self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
}
}
fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
let mut remove_span = item;
let hi = cx.sess().source_map().next_point(remove_span).hi();
let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
if let Some(ref src) = fmpos.sf.src {
let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
if let Some(non_whitespace_offset) = non_whitespace_offset {
remove_span = remove_span
.with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")))
}
}
self.span_suggestion(remove_span, msg, String::new(), applicability);
}
}
#[cfg(test)]
mod test {
use super::Sugg;
use std::borrow::Cow;
const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
#[test]
fn make_return_transform_sugg_into_a_return_call() {
assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
}
#[test]
fn blockify_transforms_sugg_into_a_block() {
assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
}
}

View File

@@ -1,7 +0,0 @@
#[macro_export]
/// Convenience wrapper around rustc's `Symbol::intern`
macro_rules! sym {
($tt:tt) => {
rustc_span::symbol::Symbol::intern(stringify!($tt))
};
}

View File

@@ -1,198 +0,0 @@
use crate::utils;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{Expr, ExprKind, HirId, Path};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_middle::ty;
use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<FxHashSet<HirId>> {
let mut delegate = MutVarsDelegate {
used_mutably: FxHashSet::default(),
skip: false,
};
cx.tcx.infer_ctxt().enter(|infcx| {
ExprUseVisitor::new(
&mut delegate,
&infcx,
expr.hir_id.owner,
cx.param_env,
cx.typeck_results(),
)
.walk_expr(expr);
});
if delegate.skip {
return None;
}
Some(delegate.used_mutably)
}
pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
if let Res::Local(id) = variable.res {
mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
} else {
true
}
}
struct MutVarsDelegate {
used_mutably: FxHashSet<HirId>,
skip: bool,
}
impl<'tcx> MutVarsDelegate {
#[allow(clippy::similar_names)]
fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
match cat.place.base {
PlaceBase::Local(id) => {
self.used_mutably.insert(id);
},
PlaceBase::Upvar(_) => {
//FIXME: This causes false negatives. We can't get the `NodeId` from
//`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
//`while`-body, not just the ones in the condition.
self.skip = true
},
_ => {},
}
}
}
impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {}
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
if let ty::BorrowKind::MutBorrow = bk {
self.update(&cmt)
}
}
fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
self.update(&cmt)
}
}
pub struct ParamBindingIdCollector {
binding_hir_ids: Vec<hir::HirId>,
}
impl<'tcx> ParamBindingIdCollector {
fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
let mut hir_ids: Vec<hir::HirId> = Vec::new();
for param in body.params.iter() {
let mut finder = ParamBindingIdCollector {
binding_hir_ids: Vec::new(),
};
finder.visit_param(param);
for hir_id in &finder.binding_hir_ids {
hir_ids.push(*hir_id);
}
}
hir_ids
}
}
impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
type Map = Map<'tcx>;
fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
self.binding_hir_ids.push(hir_id);
}
intravisit::walk_pat(self, pat);
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
pub struct BindingUsageFinder<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
binding_ids: Vec<hir::HirId>,
usage_found: bool,
}
impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
let mut finder = BindingUsageFinder {
cx,
binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
usage_found: false,
};
finder.visit_body(body);
finder.usage_found
}
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
if !self.usage_found {
intravisit::walk_expr(self, expr);
}
}
fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
if let hir::def::Res::Local(id) = path.res {
if self.binding_ids.contains(&id) {
self.usage_found = true;
}
}
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
}
struct ReturnBreakContinueMacroVisitor {
seen_return_break_continue: bool,
}
impl ReturnBreakContinueMacroVisitor {
fn new() -> ReturnBreakContinueMacroVisitor {
ReturnBreakContinueMacroVisitor {
seen_return_break_continue: false,
}
}
}
impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if self.seen_return_break_continue {
// No need to look farther if we've already seen one of them
return;
}
match &ex.kind {
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
self.seen_return_break_continue = true;
},
// Something special could be done here to handle while or for loop
// desugaring, as this will detect a break if there's a while loop
// or a for loop inside the expression.
_ => {
if utils::in_macro(ex.span) {
self.seen_return_break_continue = true;
} else {
rustc_hir::intravisit::walk_expr(self, ex);
}
},
}
}
}
pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
recursive_visitor.visit_expr(expression);
recursive_visitor.seen_return_break_continue
}

View File

@@ -1,190 +0,0 @@
use crate::utils::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};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
/// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool {
struct TryFinder {
found: bool,
}
impl<'hir> intravisit::Visitor<'hir> for TryFinder {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
if self.found {
return;
}
match expr.kind {
hir::ExprKind::Match(_, _, hir::MatchSource::TryDesugar) => self.found = true,
_ => intravisit::walk_expr(self, expr),
}
}
}
let mut visitor = TryFinder { found: false };
visitor.visit_expr(expr);
visitor.found
}
pub fn find_all_ret_expressions<'hir, F>(_cx: &LateContext<'_>, expr: &'hir hir::Expr<'hir>, callback: F) -> bool
where
F: FnMut(&'hir hir::Expr<'hir>) -> bool,
{
struct RetFinder<F> {
in_stmt: bool,
failed: bool,
cb: F,
}
struct WithStmtGuarg<'a, F> {
val: &'a mut RetFinder<F>,
prev_in_stmt: bool,
}
impl<F> RetFinder<F> {
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
WithStmtGuarg {
val: self,
prev_in_stmt,
}
}
}
impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
type Target = RetFinder<F>;
fn deref(&self) -> &Self::Target {
self.val
}
}
impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.val
}
}
impl<F> Drop for WithStmtGuarg<'_, F> {
fn drop(&mut self) {
self.val.in_stmt = self.prev_in_stmt;
}
}
impl<'hir, F: FnMut(&'hir hir::Expr<'hir>) -> bool> intravisit::Visitor<'hir> for RetFinder<F> {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
fn visit_stmt(&mut self, stmt: &'hir hir::Stmt<'_>) {
intravisit::walk_stmt(&mut *self.inside_stmt(true), stmt)
}
fn visit_expr(&mut self, expr: &'hir hir::Expr<'_>) {
if self.failed {
return;
}
if self.in_stmt {
match expr.kind {
hir::ExprKind::Ret(Some(expr)) => self.inside_stmt(false).visit_expr(expr),
_ => intravisit::walk_expr(self, expr),
}
} else {
match expr.kind {
hir::ExprKind::If(cond, then, else_opt) => {
self.inside_stmt(true).visit_expr(cond);
self.visit_expr(then);
if let Some(el) = else_opt {
self.visit_expr(el);
}
},
hir::ExprKind::Match(cond, arms, _) => {
self.inside_stmt(true).visit_expr(cond);
for arm in arms {
self.visit_expr(arm.body);
}
},
hir::ExprKind::Block(..) => intravisit::walk_expr(self, expr),
hir::ExprKind::Ret(Some(expr)) => self.visit_expr(expr),
_ => self.failed |= !(self.cb)(expr),
}
}
}
}
!contains_try(expr) && {
let mut ret_finder = RetFinder {
in_stmt: false,
failed: false,
cb: callback,
};
ret_finder.visit_expr(expr);
!ret_finder.failed
}
}
pub struct LocalUsedVisitor<'hir> {
hir: Map<'hir>,
pub local_hir_id: HirId,
pub used: bool,
}
impl<'hir> LocalUsedVisitor<'hir> {
pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
Self {
hir: cx.tcx.hir(),
local_hir_id,
used: false,
}
}
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
visit(self, t);
std::mem::replace(&mut self.used, false)
}
pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
self.check(arm, Self::visit_arm)
}
pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
self.check(body, Self::visit_body)
}
pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
self.check(expr, Self::visit_expr)
}
pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
self.check(stmt, Self::visit_stmt)
}
}
impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
type Map = Map<'v>;
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
if self.used {
return;
}
if path_to_local_id(expr, self.local_hir_id) {
self.used = true;
} else {
walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.hir)
}
}

View File

@@ -1,12 +1,14 @@
use crate::utils::{is_type_diagnostic_item, match_def_path, paths, snippet, span_lint_and_sugg};
use crate::utils::{
is_type_diagnostic_item, match_def_path, path_to_local, path_to_local_id, paths, snippet, span_lint_and_sugg,
};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Local, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span, Symbol};
use rustc_span::{symbol::sym, Span};
use std::convert::TryInto;
declare_clippy_lint! {
@@ -45,8 +47,8 @@ enum VecInitKind {
WithCapacity(u64),
}
struct VecPushSearcher {
local_id: HirId,
init: VecInitKind,
name: Symbol,
lhs_is_local: bool,
lhs_span: Span,
err_span: Span,
@@ -81,17 +83,20 @@ impl VecPushSearcher {
}
impl LateLintPass<'_> for VecInitThenPush {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
fn check_block(&mut self, _: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
self.searcher = None;
}
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
if_chain! {
if !in_external_macro(cx.sess(), local.span);
if let Some(init) = local.init;
if let PatKind::Binding(BindingAnnotation::Mutable, _, ident, None) = local.pat.kind;
if let PatKind::Binding(BindingAnnotation::Mutable, id, _, None) = local.pat.kind;
if let Some(init_kind) = get_vec_init_kind(cx, init);
then {
self.searcher = Some(VecPushSearcher {
local_id: id,
init: init_kind,
name: ident.name,
lhs_is_local: true,
lhs_span: local.ty.map_or(local.pat.span, |t| local.pat.span.to(t.span)),
err_span: local.span,
@@ -106,13 +111,12 @@ impl LateLintPass<'_> for VecInitThenPush {
if_chain! {
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Assign(left, right, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = left.kind;
if let Some(name) = path.segments.get(0);
if let Some(id) = path_to_local(left);
if let Some(init_kind) = get_vec_init_kind(cx, right);
then {
self.searcher = Some(VecPushSearcher {
local_id: id,
init: init_kind,
name: name.ident.name,
lhs_is_local: false,
lhs_span: left.span,
err_span: expr.span,
@@ -128,10 +132,8 @@ impl LateLintPass<'_> for VecInitThenPush {
if_chain! {
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind;
if let ExprKind::MethodCall(path, _, [self_arg, _], _) = expr.kind;
if path_to_local_id(self_arg, searcher.local_id);
if path.ident.name.as_str() == "push";
if let ExprKind::Path(QPath::Resolved(_, self_path)) = self_arg.kind;
if let [self_name] = self_path.segments;
if self_name.ident.name == searcher.name;
then {
self.searcher = Some(VecPushSearcher {
found: searcher.found + 1,

View File

@@ -3,9 +3,7 @@ use std::ops::Range;
use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then};
use if_chain::if_chain;
use rustc_ast::ast::{
Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, StrLit, StrStyle,
};
use rustc_ast::ast::{Expr, ExprKind, ImplKind, Item, ItemKind, LitKind, MacCall, StrLit, StrStyle};
use rustc_ast::token;
use rustc_ast::tokenstream::TokenStream;
use rustc_errors::Applicability;
@@ -149,7 +147,6 @@ declare_clippy_lint! {
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
///
/// // Bad
/// writeln!(buf, "");
///
@@ -176,7 +173,6 @@ declare_clippy_lint! {
/// # use std::fmt::Write;
/// # let mut buf = String::new();
/// # let name = "World";
///
/// // Bad
/// write!(buf, "Hello {}!\n", name);
///
@@ -202,7 +198,6 @@ declare_clippy_lint! {
/// ```rust
/// # use std::fmt::Write;
/// # let mut buf = String::new();
///
/// // Bad
/// writeln!(buf, "{}", "foo");
///
@@ -379,15 +374,10 @@ impl Write {
/// (Some("string to write: {}"), Some(buf))
/// ```
#[allow(clippy::too_many_lines)]
fn check_tts<'a>(
&self,
cx: &EarlyContext<'a>,
tts: TokenStream,
is_write: bool,
) -> (Option<StrLit>, Option<Expr>) {
fn check_tts<'a>(&self, cx: &EarlyContext<'a>, tts: TokenStream, is_write: bool) -> (Option<StrLit>, Option<Expr>) {
use rustc_parse_format::{
AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied,
FormatSpec, ParseMode, Parser, Piece,
AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser,
Piece,
};
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
@@ -417,12 +407,7 @@ impl Write {
if let Piece::NextArgument(arg) = piece {
if !self.in_debug_impl && arg.format.ty == "?" {
// FIXME: modify rustc's fmt string parser to give us the current span
span_lint(
cx,
USE_DEBUG,
parser.prev_token.span,
"use of `Debug`-based formatting",
);
span_lint(cx, USE_DEBUG, parser.prev_token.span, "use of `Debug`-based formatting");
}
args.push(arg);
}
@@ -450,9 +435,7 @@ impl Write {
return (Some(fmtstr), None);
};
match &token_expr.kind {
ExprKind::Lit(lit)
if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) =>
{
ExprKind::Lit(lit) if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) => {
let mut all_simple = true;
let mut seen = false;
for arg in &args {
@@ -462,15 +445,15 @@ impl Write {
all_simple &= arg.format == SIMPLE;
seen = true;
}
}
ArgumentNamed(_) => {}
},
ArgumentNamed(_) => {},
}
}
if all_simple && seen {
span_lint(cx, lint, token_expr.span, "literal with an empty format string");
}
idx += 1;
}
},
ExprKind::Assign(lhs, rhs, _) => {
if_chain! {
if let ExprKind::Lit(ref lit) = rhs.kind;
@@ -495,7 +478,7 @@ impl Write {
}
}
}
}
},
_ => idx += 1,
}
}
@@ -527,17 +510,11 @@ impl Write {
cx,
PRINT_WITH_NEWLINE,
mac.span(),
&format!(
"using `{}!()` with a format string that ends in a single newline",
name
),
&format!("using `{}!()` with a format string that ends in a single newline", name),
|err| {
err.multipart_suggestion(
&format!("use `{}!` instead", suggested),
vec![
(mac.path.span, suggested),
(newline_span(&fmt_str), String::new()),
],
vec![(mac.path.span, suggested), (newline_span(&fmt_str), String::new())],
Applicability::MachineApplicable,
);
},