Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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, "..");
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 don’t compare them.
|
||||
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
|
||||
l.to_bits() == r.to_bits()
|
||||
},
|
||||
(&Self::F32(l), &Self::F32(r)) => {
|
||||
// We want `Fw32 == FwAny` and `FwAny == Fw64`, and by transitivity we must have
|
||||
// `Fw32 == Fw64`, so don’t compare them.
|
||||
// `to_bits` is required to catch non-matching 0.0, -0.0, and NaNs.
|
||||
f64::from(l).to_bits() == f64::from(r).to_bits()
|
||||
},
|
||||
(&Self::Bool(l), &Self::Bool(r)) => l == r,
|
||||
(&Self::Vec(ref l), &Self::Vec(ref r)) | (&Self::Tuple(ref l), &Self::Tuple(ref r)) => l == r,
|
||||
(&Self::Repeat(ref lv, ref ls), &Self::Repeat(ref rv, ref rs)) => ls == rs && lv == rv,
|
||||
(&Self::Ref(ref lb), &Self::Ref(ref rb)) => *lb == *rb,
|
||||
// TODO: are there inter-type equalities?
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Constant {
|
||||
fn hash<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::*;
|
||||
|
||||
237
clippy_lints/src/default_numeric_fallback.rs
Normal file
237
clippy_lints/src/default_numeric_fallback.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
101
clippy_lints/src/from_str_radix_10.rs
Normal file
101
clippy_lints/src/from_str_radix_10.rs
Normal 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)
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
134
clippy_lints/src/inconsistent_struct_constructor.rs
Normal file
134
clippy_lints/src/inconsistent_struct_constructor.rs
Normal 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
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
¯o_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),
|
||||
|
||||
274
clippy_lints/src/manual_map.rs
Normal file
274
clippy_lints/src/manual_map.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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 can’t 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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ declare_clippy_lint! {
|
||||
/// struct HttpResponse;
|
||||
/// ```
|
||||
pub UPPER_CASE_ACRONYMS,
|
||||
style,
|
||||
pedantic,
|
||||
"capitalized acronyms are against the naming convention"
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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"];
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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())),
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user