Merge remote-tracking branch 'upstream/master' into sync-from-rust
This commit is contained in:
commit
c2799c861d
110 changed files with 3014 additions and 926 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use crate::utils::{
|
||||
get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method, SpanlessEq,
|
||||
eq_expr_value, get_trait_def_id, implements_trait, snippet_opt, span_lint_and_then, trait_ref_of_method,
|
||||
};
|
||||
use crate::utils::{higher, sugg};
|
||||
use if_chain::if_chain;
|
||||
|
|
@ -70,11 +70,11 @@ impl<'tcx> LateLintPass<'tcx> for AssignOps {
|
|||
return;
|
||||
}
|
||||
// lhs op= l op r
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, l) {
|
||||
if eq_expr_value(cx, lhs, l) {
|
||||
lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, r);
|
||||
}
|
||||
// lhs op= l commutative_op r
|
||||
if is_commutative(op.node) && SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, r) {
|
||||
if is_commutative(op.node) && eq_expr_value(cx, lhs, r) {
|
||||
lint_misrefactored_assign_op(cx, expr, *op, rhs, lhs, l);
|
||||
}
|
||||
}
|
||||
|
|
@ -161,14 +161,12 @@ impl<'tcx> LateLintPass<'tcx> for AssignOps {
|
|||
|
||||
if visitor.counter == 1 {
|
||||
// a = a op b
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, l) {
|
||||
if eq_expr_value(cx, assignee, l) {
|
||||
lint(assignee, r);
|
||||
}
|
||||
// a = b commutative_op a
|
||||
// Limited to primitive type as these ops are know to be commutative
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(assignee, r)
|
||||
&& cx.typeck_results().expr_ty(assignee).is_primitive_ty()
|
||||
{
|
||||
if eq_expr_value(cx, assignee, r) && cx.typeck_results().expr_ty(assignee).is_primitive_ty() {
|
||||
match op.node {
|
||||
hir::BinOpKind::Add
|
||||
| hir::BinOpKind::Mul
|
||||
|
|
@ -253,7 +251,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> {
|
|||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
|
||||
if SpanlessEq::new(self.cx).ignore_fn().eq_expr(self.assignee, expr) {
|
||||
if eq_expr_value(self.cx, self.assignee, expr) {
|
||||
self.counter += 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ use crate::utils::{
|
|||
span_lint_and_sugg, span_lint_and_then, without_block_comments,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
|
||||
use rustc_ast::util::lev_distance::find_best_match_for_name;
|
||||
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{
|
||||
Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::utils::{
|
||||
get_trait_def_id, implements_trait, in_macro, is_type_diagnostic_item, paths, snippet_opt, span_lint_and_sugg,
|
||||
span_lint_and_then, SpanlessEq,
|
||||
eq_expr_value, get_trait_def_id, implements_trait, in_macro, is_type_diagnostic_item, paths, snippet_opt,
|
||||
span_lint_and_sugg, span_lint_and_then,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
|
|
@ -128,7 +128,7 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
|
|||
}
|
||||
}
|
||||
for (n, expr) in self.terminals.iter().enumerate() {
|
||||
if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e, expr) {
|
||||
if eq_expr_value(self.cx, e, expr) {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Term(n as u8));
|
||||
}
|
||||
|
|
@ -138,8 +138,8 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> {
|
|||
if implements_ord(self.cx, e_lhs);
|
||||
if let ExprKind::Binary(expr_binop, expr_lhs, expr_rhs) = &expr.kind;
|
||||
if negate(e_binop.node) == Some(expr_binop.node);
|
||||
if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e_lhs, expr_lhs);
|
||||
if SpanlessEq::new(self.cx).ignore_fn().eq_expr(e_rhs, expr_rhs);
|
||||
if eq_expr_value(self.cx, e_lhs, expr_lhs);
|
||||
if eq_expr_value(self.cx, e_rhs, expr_rhs);
|
||||
then {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
return Ok(Bool::Not(Box::new(Bool::Term(n as u8))));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::utils::{eq_expr_value, SpanlessEq, SpanlessHash};
|
||||
use crate::utils::{get_parent_expr, higher, if_sequence, snippet, span_lint_and_note, span_lint_and_then};
|
||||
use crate::utils::{SpanlessEq, SpanlessHash};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{Arm, Block, Expr, ExprKind, MatchSource, Pat, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
@ -197,8 +197,7 @@ fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
|||
h.finish()
|
||||
};
|
||||
|
||||
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool =
|
||||
&|&lhs, &rhs| -> bool { SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) };
|
||||
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) };
|
||||
|
||||
for (i, j) in search_same(conds, hash, eq) {
|
||||
span_lint_and_note(
|
||||
|
|
@ -222,7 +221,7 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
|
|||
|
||||
let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool {
|
||||
// Do not spawn warning if `IFS_SAME_COND` already produced it.
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs, rhs) {
|
||||
if eq_expr_value(cx, lhs, rhs) {
|
||||
return false;
|
||||
}
|
||||
SpanlessEq::new(cx).eq_expr(lhs, rhs)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
use crate::utils::{implements_trait, is_entrypoint_fn, is_type_diagnostic_item, return_ty, span_lint};
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::ast::{AttrKind, Attribute};
|
||||
use rustc_ast::ast::{Async, AttrKind, Attribute, FnRetTy, ItemKind};
|
||||
use rustc_ast::token::CommentKind;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::emitter::EmitterWriter;
|
||||
use rustc_errors::Handler;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_parse::maybe_new_parser_from_source_str;
|
||||
use rustc_session::parse::ParseSess;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::{BytePos, MultiSpan, Span};
|
||||
use rustc_span::Pos;
|
||||
use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
|
||||
use rustc_span::{FileName, Pos};
|
||||
use std::io;
|
||||
use std::ops::Range;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -431,10 +437,67 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
|
|||
headers
|
||||
}
|
||||
|
||||
static LEAVE_MAIN_PATTERNS: &[&str] = &["static", "fn main() {}", "extern crate", "async fn main() {"];
|
||||
|
||||
fn check_code(cx: &LateContext<'_>, text: &str, span: Span) {
|
||||
if text.contains("fn main() {") && !LEAVE_MAIN_PATTERNS.iter().any(|p| text.contains(p)) {
|
||||
fn has_needless_main(code: &str) -> bool {
|
||||
let filename = FileName::anon_source_code(code);
|
||||
|
||||
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
|
||||
let handler = Handler::with_emitter(false, None, box emitter);
|
||||
let sess = ParseSess::with_span_handler(handler, sm);
|
||||
|
||||
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code.into()) {
|
||||
Ok(p) => p,
|
||||
Err(errs) => {
|
||||
for mut err in errs {
|
||||
err.cancel();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
let mut relevant_main_found = false;
|
||||
loop {
|
||||
match parser.parse_item() {
|
||||
Ok(Some(item)) => match &item.kind {
|
||||
// Tests with one of these items are ignored
|
||||
ItemKind::Static(..)
|
||||
| ItemKind::Const(..)
|
||||
| ItemKind::ExternCrate(..)
|
||||
| ItemKind::ForeignMod(..) => return false,
|
||||
// We found a main function ...
|
||||
ItemKind::Fn(_, 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,
|
||||
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if returns_nothing && !is_async && !block.stmts.is_empty() {
|
||||
// This main function should be linted, but only if there are no other functions
|
||||
relevant_main_found = true;
|
||||
} else {
|
||||
// This main function should not be linted, we're done
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// Another function was found; this case is ignored too
|
||||
ItemKind::Fn(..) => return false,
|
||||
_ => {},
|
||||
},
|
||||
Ok(None) => break,
|
||||
Err(mut e) => {
|
||||
e.cancel();
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
relevant_main_found
|
||||
}
|
||||
|
||||
if has_needless_main(text) {
|
||||
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
use crate::utils::{snippet_with_applicability, span_lint_and_sugg, SpanlessEq};
|
||||
use crate::utils::{eq_expr_value, snippet_with_applicability, span_lint_and_sugg};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for double comparisons that could be simplified to a single expression.
|
||||
|
|
@ -46,8 +46,7 @@ impl<'tcx> DoubleComparisons {
|
|||
},
|
||||
_ => return,
|
||||
};
|
||||
let mut spanless_eq = SpanlessEq::new(cx).ignore_fn();
|
||||
if !(spanless_eq.eq_expr(&llhs, &rlhs) && spanless_eq.eq_expr(&lrhs, &rrhs)) {
|
||||
if !(eq_expr_value(cx, &llhs, &rlhs) && eq_expr_value(cx, &lrhs, &rrhs)) {
|
||||
return;
|
||||
}
|
||||
macro_rules! lint_double_comparison {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
|
|||
cx,
|
||||
DURATION_SUBSEC,
|
||||
expr.span,
|
||||
&format!("Calling `{}()` is more concise than this calculation", suggested_fn),
|
||||
&format!("calling `{}()` is more concise than this calculation", suggested_fn),
|
||||
"try",
|
||||
format!(
|
||||
"{}.{}()",
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant {
|
|||
cx,
|
||||
ENUM_CLIKE_UNPORTABLE_VARIANT,
|
||||
var.span,
|
||||
"Clike enum variant discriminant is not portable to 32-bit targets",
|
||||
"C-like enum variant discriminant is not portable to 32-bit targets",
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,10 +183,10 @@ fn check_variant(
|
|||
&& name.chars().nth(item_name_chars).map_or(false, |c| !c.is_lowercase())
|
||||
&& name.chars().nth(item_name_chars + 1).map_or(false, |c| !c.is_numeric())
|
||||
{
|
||||
span_lint(cx, lint, var.span, "Variant name starts with the enum's name");
|
||||
span_lint(cx, lint, var.span, "variant name starts with the enum's name");
|
||||
}
|
||||
if partial_rmatch(item_name, &name) == item_name_chars {
|
||||
span_lint(cx, lint, var.span, "Variant name ends with the enum's name");
|
||||
span_lint(cx, lint, var.span, "variant name ends with the enum's name");
|
||||
}
|
||||
}
|
||||
let first = &def.variants[0].ident.name.as_str();
|
||||
|
|
@ -227,7 +227,7 @@ fn check_variant(
|
|||
cx,
|
||||
lint,
|
||||
span,
|
||||
&format!("All variants have the same {}fix: `{}`", what, value),
|
||||
&format!("all variants have the same {}fix: `{}`", what, value),
|
||||
None,
|
||||
&format!(
|
||||
"remove the {}fixes and use full paths to \
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::utils::{
|
||||
implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then, SpanlessEq,
|
||||
eq_expr_value, implements_trait, in_macro, is_copy, multispan_sugg, snippet, span_lint, span_lint_and_then,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind};
|
||||
|
|
@ -69,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
|||
if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
|
||||
return;
|
||||
}
|
||||
if is_valid_operator(op) && SpanlessEq::new(cx).ignore_fn().eq_expr(left, right) {
|
||||
if is_valid_operator(op) && eq_expr_value(cx, left, right) {
|
||||
span_lint(
|
||||
cx,
|
||||
EQ_OP,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::consts::{
|
|||
constant, constant_simple, Constant,
|
||||
Constant::{Int, F32, F64},
|
||||
};
|
||||
use crate::utils::{get_parent_expr, higher, numeric_literal, span_lint_and_sugg, sugg, SpanlessEq};
|
||||
use crate::utils::{eq_expr_value, get_parent_expr, higher, numeric_literal, span_lint_and_sugg, sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
|
||||
|
|
@ -363,8 +363,8 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
|
|||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind;
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind;
|
||||
if are_exprs_equal(cx, lmul_lhs, lmul_rhs);
|
||||
if are_exprs_equal(cx, rmul_lhs, rmul_rhs);
|
||||
if eq_expr_value(cx, lmul_lhs, lmul_rhs);
|
||||
if eq_expr_value(cx, rmul_lhs, rmul_rhs);
|
||||
then {
|
||||
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, "..")));
|
||||
}
|
||||
|
|
@ -502,8 +502,8 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
|
||||
match op {
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && are_exprs_equal(cx, left, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && are_exprs_equal(cx, right, test),
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right) && eq_expr_value(cx, left, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left) && eq_expr_value(cx, right, test),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -515,8 +515,8 @@ fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
|
|||
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
|
||||
match op {
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && are_exprs_equal(cx, right, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && are_exprs_equal(cx, left, test),
|
||||
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left) && eq_expr_value(cx, right, test),
|
||||
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right) && eq_expr_value(cx, left, test),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -524,10 +524,6 @@ fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -
|
|||
}
|
||||
}
|
||||
|
||||
fn are_exprs_equal(cx: &LateContext<'_>, expr1: &Expr<'_>, expr2: &Expr<'_>) -> bool {
|
||||
SpanlessEq::new(cx).ignore_fn().eq_expr(expr1, expr2)
|
||||
}
|
||||
|
||||
/// Returns true iff expr is some zero literal
|
||||
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
match constant_simple(cx, cx.typeck_results(), expr) {
|
||||
|
|
@ -546,12 +542,12 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
|||
/// returns None.
|
||||
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
|
||||
if let ExprKind::Unary(UnOp::UnNeg, expr1_negated) = &expr1.kind {
|
||||
if are_exprs_equal(cx, expr1_negated, expr2) {
|
||||
if eq_expr_value(cx, expr1_negated, expr2) {
|
||||
return Some((false, expr2));
|
||||
}
|
||||
}
|
||||
if let ExprKind::Unary(UnOp::UnNeg, expr2_negated) = &expr2.kind {
|
||||
if are_exprs_equal(cx, expr1, expr2_negated) {
|
||||
if eq_expr_value(cx, expr1, expr2_negated) {
|
||||
return Some((true, expr1));
|
||||
}
|
||||
}
|
||||
|
|
@ -614,7 +610,7 @@ fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>
|
|||
args_a.len() == args_b.len() &&
|
||||
(
|
||||
["ln", "log2", "log10"].contains(&&*method_name_a.as_str()) ||
|
||||
method_name_a.as_str() == "log" && args_a.len() == 2 && are_exprs_equal(cx, &args_a[1], &args_b[1])
|
||||
method_name_a.as_str() == "log" && args_a.len() == 2 && eq_expr_value(cx, &args_a[1], &args_b[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ impl<'tcx> LateLintPass<'tcx> for OkIfLet {
|
|||
cx,
|
||||
IF_LET_SOME_RESULT,
|
||||
expr.span.with_hi(op.span.hi()),
|
||||
"Matching on `Some` with `ok()` is redundant",
|
||||
&format!("Consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string),
|
||||
"matching on `Some` with `ok()` is redundant",
|
||||
&format!("consider matching on `Ok({})` and removing the call to `ok` instead", some_expr_string),
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ impl EarlyLintPass for IfNotElse {
|
|||
cx,
|
||||
IF_NOT_ELSE,
|
||||
item.span,
|
||||
"Unnecessary boolean `not` operation",
|
||||
"unnecessary boolean `not` operation",
|
||||
None,
|
||||
"remove the `!` and swap the blocks of the `if`/`else`",
|
||||
);
|
||||
|
|
@ -70,7 +70,7 @@ impl EarlyLintPass for IfNotElse {
|
|||
cx,
|
||||
IF_NOT_ELSE,
|
||||
item.span,
|
||||
"Unnecessary `!=` operation",
|
||||
"unnecessary `!=` operation",
|
||||
None,
|
||||
"change to `==` and swap the blocks of the `if`/`else`",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@ fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: &str, expr: &Expr<'_>) {
|
|||
cx,
|
||||
IMPLICIT_SATURATING_SUB,
|
||||
expr.span,
|
||||
"Implicitly performing saturating subtraction",
|
||||
"implicitly performing saturating subtraction",
|
||||
"try",
|
||||
format!("{} = {}.saturating_sub({});", var_name, var_name, 1.to_string()),
|
||||
format!("{} = {}.saturating_sub({});", var_name, var_name, '1'),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,9 +81,9 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl {
|
|||
cx,
|
||||
MULTIPLE_INHERENT_IMPL,
|
||||
*additional_span,
|
||||
"Multiple implementations of this structure",
|
||||
"multiple implementations of this structure",
|
||||
|diag| {
|
||||
diag.span_note(*initial_span, "First implementation here");
|
||||
diag.span_note(*initial_span, "first implementation here");
|
||||
},
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ impl IntPlusOne {
|
|||
cx,
|
||||
INT_PLUS_ONE,
|
||||
block.span,
|
||||
"Unnecessary `>= y + 1` or `x - 1 >=`",
|
||||
"unnecessary `>= y + 1` or `x - 1 >=`",
|
||||
"change it to",
|
||||
recommendation,
|
||||
Applicability::MachineApplicable, // snippet
|
||||
|
|
@ -163,8 +163,8 @@ impl IntPlusOne {
|
|||
impl EarlyLintPass for IntPlusOne {
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
|
||||
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind {
|
||||
if let Some(ref rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
|
||||
Self::emit_warning(cx, item, rec.clone());
|
||||
if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) {
|
||||
Self::emit_warning(cx, item, rec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,124 +0,0 @@
|
|||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Block, Expr, ExprKind, PatKind, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_then};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for `let`-bindings, which are subsequently
|
||||
/// returned.
|
||||
///
|
||||
/// **Why is this bad?** It is just extraneous code. Remove it to make your code
|
||||
/// more rusty.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// fn foo() -> String {
|
||||
/// let x = String::new();
|
||||
/// x
|
||||
/// }
|
||||
/// ```
|
||||
/// instead, use
|
||||
/// ```
|
||||
/// fn foo() -> String {
|
||||
/// String::new()
|
||||
/// }
|
||||
/// ```
|
||||
pub LET_AND_RETURN,
|
||||
style,
|
||||
"creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
|
||||
}
|
||||
|
||||
declare_lint_pass!(LetReturn => [LET_AND_RETURN]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LetReturn {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
// we need both a let-binding stmt and an expr
|
||||
if_chain! {
|
||||
if let Some(retexpr) = block.expr;
|
||||
if let Some(stmt) = block.stmts.iter().last();
|
||||
if let StmtKind::Local(local) = &stmt.kind;
|
||||
if local.ty.is_none();
|
||||
if local.attrs.is_empty();
|
||||
if let Some(initexpr) = &local.init;
|
||||
if let PatKind::Binding(.., ident, _) = local.pat.kind;
|
||||
if let ExprKind::Path(qpath) = &retexpr.kind;
|
||||
if match_qpath(qpath, &[&*ident.name.as_str()]);
|
||||
if !last_statement_borrows(cx, initexpr);
|
||||
if !in_external_macro(cx.sess(), initexpr.span);
|
||||
if !in_external_macro(cx.sess(), retexpr.span);
|
||||
if !in_external_macro(cx.sess(), local.span);
|
||||
if !in_macro(local.span);
|
||||
then {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LET_AND_RETURN,
|
||||
retexpr.span,
|
||||
"returning the result of a `let` binding from a block",
|
||||
|err| {
|
||||
err.span_label(local.span, "unnecessary `let` binding");
|
||||
|
||||
if let Some(snippet) = snippet_opt(cx, initexpr.span) {
|
||||
err.multipart_suggestion(
|
||||
"return the expression directly",
|
||||
vec![
|
||||
(local.span, String::new()),
|
||||
(retexpr.span, snippet),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
err.span_help(initexpr.span, "this expression can be directly returned");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
let mut visitor = BorrowVisitor { cx, borrows: false };
|
||||
walk_expr(&mut visitor, expr);
|
||||
visitor.borrows
|
||||
}
|
||||
|
||||
struct BorrowVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
borrows: bool,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if self.borrows {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(def_id) = fn_def_id(self.cx, expr) {
|
||||
self.borrows = self
|
||||
.cx
|
||||
.tcx
|
||||
.fn_sig(def_id)
|
||||
.output()
|
||||
.skip_binder()
|
||||
.walk()
|
||||
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
|
@ -218,7 +218,6 @@ mod large_const_arrays;
|
|||
mod large_enum_variant;
|
||||
mod large_stack_arrays;
|
||||
mod len_zero;
|
||||
mod let_and_return;
|
||||
mod let_if_seq;
|
||||
mod let_underscore;
|
||||
mod lifetimes;
|
||||
|
|
@ -285,6 +284,7 @@ mod reference;
|
|||
mod regex;
|
||||
mod repeat_once;
|
||||
mod returns;
|
||||
mod self_assignment;
|
||||
mod serde_api;
|
||||
mod shadow;
|
||||
mod single_component_path_imports;
|
||||
|
|
@ -296,6 +296,7 @@ mod swap;
|
|||
mod tabs_in_doc_comments;
|
||||
mod temporary_assignment;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_in_display;
|
||||
mod trait_bounds;
|
||||
mod transmute;
|
||||
mod transmuting_null;
|
||||
|
|
@ -310,6 +311,7 @@ mod unnested_or_patterns;
|
|||
mod unsafe_removed_from_name;
|
||||
mod unused_io_amount;
|
||||
mod unused_self;
|
||||
mod unused_unit;
|
||||
mod unwrap;
|
||||
mod use_self;
|
||||
mod useless_conversion;
|
||||
|
|
@ -586,7 +588,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
&large_stack_arrays::LARGE_STACK_ARRAYS,
|
||||
&len_zero::LEN_WITHOUT_IS_EMPTY,
|
||||
&len_zero::LEN_ZERO,
|
||||
&let_and_return::LET_AND_RETURN,
|
||||
&let_if_seq::USELESS_LET_IF_SEQ,
|
||||
&let_underscore::LET_UNDERSCORE_LOCK,
|
||||
&let_underscore::LET_UNDERSCORE_MUST_USE,
|
||||
|
|
@ -677,6 +678,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
&methods::SEARCH_IS_SOME,
|
||||
&methods::SHOULD_IMPLEMENT_TRAIT,
|
||||
&methods::SINGLE_CHAR_PATTERN,
|
||||
&methods::SINGLE_CHAR_PUSH_STR,
|
||||
&methods::SKIP_WHILE_NEXT,
|
||||
&methods::STRING_EXTEND_CHARS,
|
||||
&methods::SUSPICIOUS_MAP,
|
||||
|
|
@ -684,6 +686,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
&methods::UNINIT_ASSUMED_INIT,
|
||||
&methods::UNNECESSARY_FILTER_MAP,
|
||||
&methods::UNNECESSARY_FOLD,
|
||||
&methods::UNNECESSARY_LAZY_EVALUATIONS,
|
||||
&methods::UNWRAP_USED,
|
||||
&methods::USELESS_ASREF,
|
||||
&methods::WRONG_PUB_SELF_CONVENTION,
|
||||
|
|
@ -769,8 +772,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
®ex::INVALID_REGEX,
|
||||
®ex::TRIVIAL_REGEX,
|
||||
&repeat_once::REPEAT_ONCE,
|
||||
&returns::LET_AND_RETURN,
|
||||
&returns::NEEDLESS_RETURN,
|
||||
&returns::UNUSED_UNIT,
|
||||
&self_assignment::SELF_ASSIGNMENT,
|
||||
&serde_api::SERDE_API_MISUSE,
|
||||
&shadow::SHADOW_REUSE,
|
||||
&shadow::SHADOW_SAME,
|
||||
|
|
@ -788,6 +792,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
|
||||
&temporary_assignment::TEMPORARY_ASSIGNMENT,
|
||||
&to_digit_is_some::TO_DIGIT_IS_SOME,
|
||||
&to_string_in_display::TO_STRING_IN_DISPLAY,
|
||||
&trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
|
||||
&trait_bounds::TYPE_REPETITION_IN_BOUNDS,
|
||||
&transmute::CROSSPOINTER_TRANSMUTE,
|
||||
|
|
@ -840,6 +845,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
|
||||
&unused_io_amount::UNUSED_IO_AMOUNT,
|
||||
&unused_self::UNUSED_SELF,
|
||||
&unused_unit::UNUSED_UNIT,
|
||||
&unwrap::PANICKING_UNWRAP,
|
||||
&unwrap::UNNECESSARY_UNWRAP,
|
||||
&use_self::USE_SELF,
|
||||
|
|
@ -930,11 +936,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(move || box cognitive_complexity::CognitiveComplexity::new(cognitive_complexity_threshold));
|
||||
let too_large_for_stack = conf.too_large_for_stack;
|
||||
store.register_late_pass(move || box escape::BoxedLocal{too_large_for_stack});
|
||||
store.register_late_pass(move || box vec::UselessVec{too_large_for_stack});
|
||||
store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented);
|
||||
store.register_late_pass(|| box strings::StringLitAsBytes);
|
||||
store.register_late_pass(|| box derive::Derive);
|
||||
store.register_late_pass(|| box types::CharLitAsU8);
|
||||
store.register_late_pass(|| box vec::UselessVec);
|
||||
store.register_late_pass(|| box drop_bounds::DropBounds);
|
||||
store.register_late_pass(|| box get_last_with_len::GetLastWithLen);
|
||||
store.register_late_pass(|| box drop_forget_ref::DropForgetRef);
|
||||
|
|
@ -1017,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_early_pass(|| box reference::DerefAddrOf);
|
||||
store.register_early_pass(|| box reference::RefInDeref);
|
||||
store.register_early_pass(|| box double_parens::DoubleParens);
|
||||
store.register_late_pass(|| box to_string_in_display::ToStringInDisplay::new());
|
||||
store.register_early_pass(|| box unsafe_removed_from_name::UnsafeNameRemoval);
|
||||
store.register_early_pass(|| box if_not_else::IfNotElse);
|
||||
store.register_early_pass(|| box else_if_without_else::ElseIfWithoutElse);
|
||||
|
|
@ -1025,8 +1032,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_early_pass(|| box misc_early::MiscEarlyLints);
|
||||
store.register_early_pass(|| box redundant_closure_call::RedundantClosureCall);
|
||||
store.register_late_pass(|| box redundant_closure_call::RedundantClosureCall);
|
||||
store.register_early_pass(|| box returns::Return);
|
||||
store.register_late_pass(|| box let_and_return::LetReturn);
|
||||
store.register_early_pass(|| box unused_unit::UnusedUnit);
|
||||
store.register_late_pass(|| box returns::Return);
|
||||
store.register_early_pass(|| box collapsible_if::CollapsibleIf);
|
||||
store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
|
||||
store.register_early_pass(|| box precedence::Precedence);
|
||||
|
|
@ -1085,6 +1092,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch);
|
||||
store.register_late_pass(|| box stable_sort_primitive::StableSortPrimitive);
|
||||
store.register_late_pass(|| box repeat_once::RepeatOnce);
|
||||
store.register_late_pass(|| box self_assignment::SelfAssignment);
|
||||
|
||||
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
|
||||
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
|
||||
|
|
@ -1284,7 +1292,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
|
||||
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
|
||||
LintId::of(&len_zero::LEN_ZERO),
|
||||
LintId::of(&let_and_return::LET_AND_RETURN),
|
||||
LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
|
||||
LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
|
||||
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
|
||||
|
|
@ -1349,6 +1356,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&methods::SEARCH_IS_SOME),
|
||||
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
|
||||
LintId::of(&methods::SINGLE_CHAR_PATTERN),
|
||||
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
|
||||
LintId::of(&methods::SKIP_WHILE_NEXT),
|
||||
LintId::of(&methods::STRING_EXTEND_CHARS),
|
||||
LintId::of(&methods::SUSPICIOUS_MAP),
|
||||
|
|
@ -1356,6 +1364,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&methods::UNINIT_ASSUMED_INIT),
|
||||
LintId::of(&methods::UNNECESSARY_FILTER_MAP),
|
||||
LintId::of(&methods::UNNECESSARY_FOLD),
|
||||
LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS),
|
||||
LintId::of(&methods::USELESS_ASREF),
|
||||
LintId::of(&methods::WRONG_SELF_CONVENTION),
|
||||
LintId::of(&methods::ZST_OFFSET),
|
||||
|
|
@ -1413,8 +1422,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(®ex::INVALID_REGEX),
|
||||
LintId::of(®ex::TRIVIAL_REGEX),
|
||||
LintId::of(&repeat_once::REPEAT_ONCE),
|
||||
LintId::of(&returns::LET_AND_RETURN),
|
||||
LintId::of(&returns::NEEDLESS_RETURN),
|
||||
LintId::of(&returns::UNUSED_UNIT),
|
||||
LintId::of(&self_assignment::SELF_ASSIGNMENT),
|
||||
LintId::of(&serde_api::SERDE_API_MISUSE),
|
||||
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
|
||||
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
|
||||
|
|
@ -1427,6 +1437,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
|
||||
LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
|
||||
LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME),
|
||||
LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY),
|
||||
LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),
|
||||
LintId::of(&transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS),
|
||||
LintId::of(&transmute::TRANSMUTE_BYTES_TO_STR),
|
||||
|
|
@ -1460,6 +1471,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY),
|
||||
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(&useless_conversion::USELESS_CONVERSION),
|
||||
|
|
@ -1500,7 +1512,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
|
||||
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
|
||||
LintId::of(&len_zero::LEN_ZERO),
|
||||
LintId::of(&let_and_return::LET_AND_RETURN),
|
||||
LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),
|
||||
LintId::of(&loops::EMPTY_LOOP),
|
||||
LintId::of(&loops::FOR_KV_MAP),
|
||||
|
|
@ -1532,8 +1543,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&methods::OPTION_MAP_OR_NONE),
|
||||
LintId::of(&methods::RESULT_MAP_OR_INTO_OPTION),
|
||||
LintId::of(&methods::SHOULD_IMPLEMENT_TRAIT),
|
||||
LintId::of(&methods::SINGLE_CHAR_PUSH_STR),
|
||||
LintId::of(&methods::STRING_EXTEND_CHARS),
|
||||
LintId::of(&methods::UNNECESSARY_FOLD),
|
||||
LintId::of(&methods::UNNECESSARY_LAZY_EVALUATIONS),
|
||||
LintId::of(&methods::WRONG_SELF_CONVENTION),
|
||||
LintId::of(&misc::TOPLEVEL_REF_ARG),
|
||||
LintId::of(&misc::ZERO_PTR),
|
||||
|
|
@ -1554,8 +1567,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES),
|
||||
LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
|
||||
LintId::of(®ex::TRIVIAL_REGEX),
|
||||
LintId::of(&returns::LET_AND_RETURN),
|
||||
LintId::of(&returns::NEEDLESS_RETURN),
|
||||
LintId::of(&returns::UNUSED_UNIT),
|
||||
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
|
||||
LintId::of(&strings::STRING_LIT_AS_BYTES),
|
||||
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
|
||||
|
|
@ -1564,6 +1577,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&types::FN_TO_NUMERIC_CAST),
|
||||
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(&write::PRINTLN_EMPTY_STRING),
|
||||
LintId::of(&write::PRINT_LITERAL),
|
||||
LintId::of(&write::PRINT_WITH_NEWLINE),
|
||||
|
|
@ -1704,10 +1718,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
LintId::of(&ptr::MUT_FROM_REF),
|
||||
LintId::of(&ranges::REVERSED_EMPTY_RANGES),
|
||||
LintId::of(®ex::INVALID_REGEX),
|
||||
LintId::of(&self_assignment::SELF_ASSIGNMENT),
|
||||
LintId::of(&serde_api::SERDE_API_MISUSE),
|
||||
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
|
||||
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
|
||||
LintId::of(&swap::ALMOST_SWAPPED),
|
||||
LintId::of(&to_string_in_display::TO_STRING_IN_DISPLAY),
|
||||
LintId::of(&transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
||||
LintId::of(&transmute::WRONG_TRANSMUTE),
|
||||
LintId::of(&transmuting_null::TRANSMUTING_NULL),
|
||||
|
|
|
|||
|
|
@ -1141,11 +1141,31 @@ fn detect_same_item_push<'tcx>(
|
|||
if same_item_push_visitor.should_lint {
|
||||
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
|
||||
// Make sure that the push does not involve possibly mutating values
|
||||
if mutated_variables(pushed_item, cx).map_or(false, |mutvars| mutvars.is_empty()) {
|
||||
if let PatKind::Wild = pat.kind {
|
||||
let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
|
||||
let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
|
||||
|
||||
if let PatKind::Wild = pat.kind {
|
||||
let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
|
||||
let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
|
||||
if let ExprKind::Path(ref qpath) = pushed_item.kind {
|
||||
if_chain! {
|
||||
if let Res::Local(hir_id) = qpath_res(cx, qpath, pushed_item.hir_id);
|
||||
let node = cx.tcx.hir().get(hir_id);
|
||||
if let Node::Binding(pat) = node;
|
||||
if let PatKind::Binding(bind_ann, ..) = pat.kind;
|
||||
if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SAME_ITEM_PUSH,
|
||||
vec.span,
|
||||
"it looks like the same item is being pushed into this Vec",
|
||||
None,
|
||||
&format!(
|
||||
"try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
|
||||
item_str, vec_str, item_str
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if mutated_variables(pushed_item, cx).map_or(false, |mutvars| mutvars.is_empty()) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SAME_ITEM_PUSH,
|
||||
|
|
|
|||
|
|
@ -111,8 +111,8 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
|
|||
cx,
|
||||
MAP_CLONE,
|
||||
root.trim_start(receiver).unwrap(),
|
||||
"You are needlessly cloning iterator elements",
|
||||
"Remove the `map` call",
|
||||
"you are needlessly cloning iterator elements",
|
||||
"remove the `map` call",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
|
|
@ -125,8 +125,8 @@ fn lint(cx: &LateContext<'_>, replace: Span, root: Span, copied: bool) {
|
|||
cx,
|
||||
MAP_CLONE,
|
||||
replace,
|
||||
"You are using an explicit closure for copying elements",
|
||||
"Consider calling the dedicated `copied` method",
|
||||
"you are using an explicit closure for copying elements",
|
||||
"consider calling the dedicated `copied` method",
|
||||
format!(
|
||||
"{}.copied()",
|
||||
snippet_with_applicability(cx, root, "..", &mut applicability)
|
||||
|
|
@ -138,8 +138,8 @@ fn lint(cx: &LateContext<'_>, replace: Span, root: Span, copied: bool) {
|
|||
cx,
|
||||
MAP_CLONE,
|
||||
replace,
|
||||
"You are using an explicit closure for cloning elements",
|
||||
"Consider calling the dedicated `cloned` method",
|
||||
"you are using an explicit closure for cloning elements",
|
||||
"consider calling the dedicated `cloned` method",
|
||||
format!(
|
||||
"{}.cloned()",
|
||||
snippet_with_applicability(cx, root, "..", &mut applicability)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::utils::{is_type_diagnostic_item, is_type_lang_item, snippet, span_lint_and_sugg};
|
||||
use crate::utils::walk_ptrs_ty;
|
||||
use crate::utils::{is_type_diagnostic_item, is_type_lang_item, snippet, span_lint_and_sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ mod inefficient_to_string;
|
|||
mod manual_saturating_arithmetic;
|
||||
mod option_map_unwrap_or;
|
||||
mod unnecessary_filter_map;
|
||||
mod unnecessary_lazy_eval;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
|
|
@ -799,7 +800,7 @@ declare_clippy_lint! {
|
|||
/// call_some_ffi_func(c_str);
|
||||
/// }
|
||||
/// ```
|
||||
/// Here `c_str` point to a freed address. The correct use would be:
|
||||
/// Here `c_str` points to a freed address. The correct use would be:
|
||||
/// ```rust
|
||||
/// # use std::ffi::CString;
|
||||
/// # fn call_some_ffi_func(_: *const i8) {}
|
||||
|
|
@ -1306,6 +1307,65 @@ declare_clippy_lint! {
|
|||
"using `.iter().next()` on a sliced array, which can be shortened to just `.get()`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Warns when using push_str with a single-character string literal,
|
||||
/// and push with a char would work fine.
|
||||
///
|
||||
/// **Why is this bad?** It's less clear that we are pushing a single character
|
||||
///
|
||||
/// **Known problems:** None
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```
|
||||
/// let mut string = String::new();
|
||||
/// string.push_str("R");
|
||||
/// ```
|
||||
/// Could be written as
|
||||
/// ```
|
||||
/// let mut string = String::new();
|
||||
/// string.push('R');
|
||||
/// ```
|
||||
pub SINGLE_CHAR_PUSH_STR,
|
||||
style,
|
||||
"`push_str()` used with a single-character string literal as parameter"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** As the counterpart to `or_fun_call`, this lint looks for unnecessary
|
||||
/// lazily evaluated closures on `Option` and `Result`.
|
||||
///
|
||||
/// This lint suggests changing the following functions, when eager evaluation results in
|
||||
/// simpler code:
|
||||
/// - `unwrap_or_else` to `unwrap_or`
|
||||
/// - `and_then` to `and`
|
||||
/// - `or_else` to `or`
|
||||
/// - `get_or_insert_with` to `get_or_insert`
|
||||
/// - `ok_or_else` to `ok_or`
|
||||
///
|
||||
/// **Why is this bad?** Using eager evaluation is shorter and simpler in some cases.
|
||||
///
|
||||
/// **Known problems:** It is possible, but not recommended for `Deref` and `Index` to have
|
||||
/// side effects. Eagerly evaluating them can change the semantics of the program.
|
||||
///
|
||||
/// **Example:**
|
||||
///
|
||||
/// ```rust
|
||||
/// // example code where clippy issues a warning
|
||||
/// let opt: Option<u32> = None;
|
||||
///
|
||||
/// opt.unwrap_or_else(|| 42);
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let opt: Option<u32> = None;
|
||||
///
|
||||
/// opt.unwrap_or(42);
|
||||
/// ```
|
||||
pub UNNECESSARY_LAZY_EVALUATIONS,
|
||||
style,
|
||||
"using unnecessary lazy evaluation, which can be replaced with simpler eager evaluation"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Methods => [
|
||||
UNWRAP_USED,
|
||||
EXPECT_USED,
|
||||
|
|
@ -1327,6 +1387,7 @@ declare_lint_pass!(Methods => [
|
|||
INEFFICIENT_TO_STRING,
|
||||
NEW_RET_NO_SELF,
|
||||
SINGLE_CHAR_PATTERN,
|
||||
SINGLE_CHAR_PUSH_STR,
|
||||
SEARCH_IS_SOME,
|
||||
TEMPORARY_CSTRING_AS_PTR,
|
||||
FILTER_NEXT,
|
||||
|
|
@ -1354,6 +1415,7 @@ declare_lint_pass!(Methods => [
|
|||
ZST_OFFSET,
|
||||
FILETYPE_IS_FILE,
|
||||
OPTION_AS_REF_DEREF,
|
||||
UNNECESSARY_LAZY_EVALUATIONS,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Methods {
|
||||
|
|
@ -1374,13 +1436,19 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]),
|
||||
["expect", ..] => lint_expect(cx, expr, arg_lists[0]),
|
||||
["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]),
|
||||
["unwrap_or_else", "map"] => lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]),
|
||||
["unwrap_or_else", "map"] => {
|
||||
if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0]) {
|
||||
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or");
|
||||
}
|
||||
},
|
||||
["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]),
|
||||
["and_then", ..] => {
|
||||
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "and");
|
||||
bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]);
|
||||
bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]);
|
||||
},
|
||||
["or_else", ..] => {
|
||||
unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], false, "or");
|
||||
bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]);
|
||||
},
|
||||
["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]),
|
||||
|
|
@ -1424,6 +1492,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
|
||||
["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
|
||||
["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
|
||||
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "unwrap_or"),
|
||||
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "get_or_insert"),
|
||||
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], true, "ok_or"),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
|
|
@ -1441,6 +1512,12 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
inefficient_to_string::lint(cx, expr, &args[0], self_ty);
|
||||
}
|
||||
|
||||
if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
|
||||
if match_def_path(cx, fn_def_id, &paths::PUSH_STR) {
|
||||
lint_single_char_push_string(cx, expr, args);
|
||||
}
|
||||
}
|
||||
|
||||
match self_ty.kind {
|
||||
ty::Ref(_, ty, _) if ty.kind == ty::Str => {
|
||||
for &(method, pos) in &PATTERN_METHODS {
|
||||
|
|
@ -1470,6 +1547,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
|
||||
if in_external_macro(cx.sess(), impl_item.span) {
|
||||
return;
|
||||
|
|
@ -1495,16 +1573,31 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
|
|||
|
||||
then {
|
||||
if cx.access_levels.is_exported(impl_item.hir_id) {
|
||||
// check missing trait implementations
|
||||
for &(method_name, n_args, fn_header, self_kind, out_type, trait_name) in &TRAIT_METHODS {
|
||||
if name == method_name &&
|
||||
sig.decl.inputs.len() == n_args &&
|
||||
out_type.matches(cx, &sig.decl.output) &&
|
||||
self_kind.matches(cx, self_ty, first_arg_ty) &&
|
||||
fn_header_equals(*fn_header, sig.header) {
|
||||
span_lint(cx, SHOULD_IMPLEMENT_TRAIT, impl_item.span, &format!(
|
||||
"defining a method called `{}` on this type; consider implementing \
|
||||
the `{}` trait or choosing a less ambiguous name", name, trait_name));
|
||||
// check missing trait implementations
|
||||
for method_config in &TRAIT_METHODS {
|
||||
if name == method_config.method_name &&
|
||||
sig.decl.inputs.len() == method_config.param_count &&
|
||||
method_config.output_type.matches(cx, &sig.decl.output) &&
|
||||
method_config.self_kind.matches(cx, self_ty, first_arg_ty) &&
|
||||
fn_header_equals(method_config.fn_header, sig.header) &&
|
||||
method_config.lifetime_param_cond(&impl_item)
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
SHOULD_IMPLEMENT_TRAIT,
|
||||
impl_item.span,
|
||||
&format!(
|
||||
"method `{}` can be confused for the standard trait method `{}::{}`",
|
||||
method_config.method_name,
|
||||
method_config.trait_name,
|
||||
method_config.method_name
|
||||
),
|
||||
None,
|
||||
&format!(
|
||||
"consider implementing the trait `{}` or choosing a less ambiguous method name",
|
||||
method_config.trait_name
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2280,7 +2373,7 @@ fn lint_iter_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_
|
|||
cx,
|
||||
ITER_NEXT_SLICE,
|
||||
expr.span,
|
||||
"Using `.iter().next()` on a Slice without end index.",
|
||||
"using `.iter().next()` on a Slice without end index",
|
||||
"try calling",
|
||||
format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx),
|
||||
applicability,
|
||||
|
|
@ -2299,7 +2392,7 @@ fn lint_iter_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_
|
|||
cx,
|
||||
ITER_NEXT_SLICE,
|
||||
expr.span,
|
||||
"Using `.iter().next()` on an array",
|
||||
"using `.iter().next()` on an array",
|
||||
"try calling",
|
||||
format!(
|
||||
"{}.get(0)",
|
||||
|
|
@ -2618,12 +2711,13 @@ fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map
|
|||
}
|
||||
|
||||
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
|
||||
/// Return true if lint triggered
|
||||
fn lint_map_unwrap_or_else<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'_>,
|
||||
map_args: &'tcx [hir::Expr<'_>],
|
||||
unwrap_args: &'tcx [hir::Expr<'_>],
|
||||
) {
|
||||
) -> bool {
|
||||
// lint if the caller of `map()` is an `Option`
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(option_type));
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym!(result_type));
|
||||
|
|
@ -2635,10 +2729,10 @@ fn lint_map_unwrap_or_else<'tcx>(
|
|||
let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx);
|
||||
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
|
||||
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// lint message
|
||||
|
|
@ -2668,10 +2762,14 @@ fn lint_map_unwrap_or_else<'tcx>(
|
|||
map_snippet, unwrap_snippet,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} else if same_span && multiline {
|
||||
span_lint(cx, MAP_UNWRAP_OR, expr.span, msg);
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
|
||||
|
|
@ -3124,15 +3222,18 @@ fn lint_chars_last_cmp_with_unwrap<'tcx>(cx: &LateContext<'tcx>, info: &BinaryEx
|
|||
}
|
||||
}
|
||||
|
||||
/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
|
||||
fn lint_single_char_pattern<'tcx>(cx: &LateContext<'tcx>, _expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
|
||||
fn get_hint_if_single_char_arg(
|
||||
cx: &LateContext<'_>,
|
||||
arg: &hir::Expr<'_>,
|
||||
applicability: &mut Applicability,
|
||||
) -> Option<String> {
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Lit(lit) = &arg.kind;
|
||||
if let ast::LitKind::Str(r, style) = lit.node;
|
||||
if r.as_str().len() == 1;
|
||||
let string = r.as_str();
|
||||
if string.len() == 1;
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snip = snippet_with_applicability(cx, arg.span, "..", &mut applicability);
|
||||
let snip = snippet_with_applicability(cx, arg.span, &string, applicability);
|
||||
let ch = if let ast::StrStyle::Raw(nhash) = style {
|
||||
let nhash = nhash as usize;
|
||||
// for raw string: r##"a"##
|
||||
|
|
@ -3142,19 +3243,47 @@ fn lint_single_char_pattern<'tcx>(cx: &LateContext<'tcx>, _expr: &'tcx hir::Expr
|
|||
&snip[1..(snip.len() - 1)]
|
||||
};
|
||||
let hint = format!("'{}'", if ch == "'" { "\\'" } else { ch });
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_PATTERN,
|
||||
arg.span,
|
||||
"single-character string constant used as pattern",
|
||||
"try using a `char` instead",
|
||||
hint,
|
||||
applicability,
|
||||
);
|
||||
Some(hint)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// lint for length-1 `str`s for methods in `PATTERN_METHODS`
|
||||
fn lint_single_char_pattern(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_PATTERN,
|
||||
arg.span,
|
||||
"single-character string constant used as pattern",
|
||||
"try using a `char` instead",
|
||||
hint,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// lint for length-1 `str`s as argument for `push_str`
|
||||
fn lint_single_char_push_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) {
|
||||
let base_string_snippet = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
|
||||
let sugg = format!("{}.push({})", base_string_snippet, extension_string);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_CHAR_PUSH_STR,
|
||||
expr.span,
|
||||
"calling `push_str()` using a single-character string literal",
|
||||
"consider using `push` with a character literal",
|
||||
sugg,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for the `USELESS_ASREF` lint.
|
||||
fn lint_asref(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) {
|
||||
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
|
||||
|
|
@ -3403,38 +3532,85 @@ const FN_HEADER: hir::FnHeader = hir::FnHeader {
|
|||
abi: rustc_target::spec::abi::Abi::Rust,
|
||||
};
|
||||
|
||||
struct ShouldImplTraitCase {
|
||||
trait_name: &'static str,
|
||||
method_name: &'static str,
|
||||
param_count: usize,
|
||||
fn_header: hir::FnHeader,
|
||||
// implicit self kind expected (none, self, &self, ...)
|
||||
self_kind: SelfKind,
|
||||
// checks against the output type
|
||||
output_type: OutType,
|
||||
// certain methods with explicit lifetimes can't implement the equivalent trait method
|
||||
lint_explicit_lifetime: bool,
|
||||
}
|
||||
impl ShouldImplTraitCase {
|
||||
const fn new(
|
||||
trait_name: &'static str,
|
||||
method_name: &'static str,
|
||||
param_count: usize,
|
||||
fn_header: hir::FnHeader,
|
||||
self_kind: SelfKind,
|
||||
output_type: OutType,
|
||||
lint_explicit_lifetime: bool,
|
||||
) -> ShouldImplTraitCase {
|
||||
ShouldImplTraitCase {
|
||||
trait_name,
|
||||
method_name,
|
||||
param_count,
|
||||
fn_header,
|
||||
self_kind,
|
||||
output_type,
|
||||
lint_explicit_lifetime,
|
||||
}
|
||||
}
|
||||
|
||||
fn lifetime_param_cond(&self, impl_item: &hir::ImplItem<'_>) -> bool {
|
||||
self.lint_explicit_lifetime
|
||||
|| !impl_item.generics.params.iter().any(|p| {
|
||||
matches!(
|
||||
p.kind,
|
||||
hir::GenericParamKind::Lifetime {
|
||||
kind: hir::LifetimeParamKind::Explicit
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const TRAIT_METHODS: [(&str, usize, &hir::FnHeader, SelfKind, OutType, &str); 30] = [
|
||||
("add", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Add"),
|
||||
("as_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::convert::AsMut"),
|
||||
("as_ref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::convert::AsRef"),
|
||||
("bitand", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitAnd"),
|
||||
("bitor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitOr"),
|
||||
("bitxor", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::BitXor"),
|
||||
("borrow", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::borrow::Borrow"),
|
||||
("borrow_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::borrow::BorrowMut"),
|
||||
("clone", 1, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::clone::Clone"),
|
||||
("cmp", 2, &FN_HEADER, SelfKind::Ref, OutType::Any, "std::cmp::Ord"),
|
||||
("default", 0, &FN_HEADER, SelfKind::No, OutType::Any, "std::default::Default"),
|
||||
("deref", 1, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Deref"),
|
||||
("deref_mut", 1, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::DerefMut"),
|
||||
("div", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Div"),
|
||||
("drop", 1, &FN_HEADER, SelfKind::RefMut, OutType::Unit, "std::ops::Drop"),
|
||||
("eq", 2, &FN_HEADER, SelfKind::Ref, OutType::Bool, "std::cmp::PartialEq"),
|
||||
("from_iter", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::iter::FromIterator"),
|
||||
("from_str", 1, &FN_HEADER, SelfKind::No, OutType::Any, "std::str::FromStr"),
|
||||
("hash", 2, &FN_HEADER, SelfKind::Ref, OutType::Unit, "std::hash::Hash"),
|
||||
("index", 2, &FN_HEADER, SelfKind::Ref, OutType::Ref, "std::ops::Index"),
|
||||
("index_mut", 2, &FN_HEADER, SelfKind::RefMut, OutType::Ref, "std::ops::IndexMut"),
|
||||
("into_iter", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::iter::IntoIterator"),
|
||||
("mul", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Mul"),
|
||||
("neg", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Neg"),
|
||||
("next", 1, &FN_HEADER, SelfKind::RefMut, OutType::Any, "std::iter::Iterator"),
|
||||
("not", 1, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Not"),
|
||||
("rem", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Rem"),
|
||||
("shl", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shl"),
|
||||
("shr", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Shr"),
|
||||
("sub", 2, &FN_HEADER, SelfKind::Value, OutType::Any, "std::ops::Sub"),
|
||||
const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
|
||||
ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::convert::AsRef", "as_ref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::BitAnd", "bitand", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::BitOr", "bitor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::BitXor", "bitxor", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::borrow::Borrow", "borrow", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::borrow::BorrowMut", "borrow_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::clone::Clone", "clone", 1, FN_HEADER, SelfKind::Ref, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::cmp::Ord", "cmp", 2, FN_HEADER, SelfKind::Ref, OutType::Any, true),
|
||||
// FIXME: default doesn't work
|
||||
ShouldImplTraitCase::new("std::default::Default", "default", 0, FN_HEADER, SelfKind::No, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Deref", "deref", 1, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::DerefMut", "deref_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::Div", "div", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Drop", "drop", 1, FN_HEADER, SelfKind::RefMut, OutType::Unit, true),
|
||||
ShouldImplTraitCase::new("std::cmp::PartialEq", "eq", 2, FN_HEADER, SelfKind::Ref, OutType::Bool, true),
|
||||
ShouldImplTraitCase::new("std::iter::FromIterator", "from_iter", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::str::FromStr", "from_str", 1, FN_HEADER, SelfKind::No, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::hash::Hash", "hash", 2, FN_HEADER, SelfKind::Ref, OutType::Unit, true),
|
||||
ShouldImplTraitCase::new("std::ops::Index", "index", 2, FN_HEADER, SelfKind::Ref, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::ops::IndexMut", "index_mut", 2, FN_HEADER, SelfKind::RefMut, OutType::Ref, true),
|
||||
ShouldImplTraitCase::new("std::iter::IntoIterator", "into_iter", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Mul", "mul", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Neg", "neg", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::iter::Iterator", "next", 1, FN_HEADER, SelfKind::RefMut, OutType::Any, false),
|
||||
ShouldImplTraitCase::new("std::ops::Not", "not", 1, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Rem", "rem", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Shl", "shl", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Shr", "shr", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
|
|
|||
111
clippy_lints/src/methods/unnecessary_lazy_eval.rs
Normal file
111
clippy_lints/src/methods/unnecessary_lazy_eval.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use crate::utils::{is_type_diagnostic_item, match_qpath, snippet, span_lint_and_sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
use super::UNNECESSARY_LAZY_EVALUATIONS;
|
||||
|
||||
// Return true if the expression is an accessor of any of the arguments
|
||||
fn expr_uses_argument(expr: &hir::Expr<'_>, params: &[hir::Param<'_>]) -> bool {
|
||||
params.iter().any(|arg| {
|
||||
if_chain! {
|
||||
if let hir::PatKind::Binding(_, _, ident, _) = arg.pat.kind;
|
||||
if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = expr.kind;
|
||||
if let [p, ..] = path.segments;
|
||||
then {
|
||||
ident.name == p.ident.name
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn match_any_qpath(path: &hir::QPath<'_>, paths: &[&[&str]]) -> bool {
|
||||
paths.iter().any(|candidate| match_qpath(path, candidate))
|
||||
}
|
||||
|
||||
fn can_simplify(expr: &hir::Expr<'_>, params: &[hir::Param<'_>], variant_calls: bool) -> bool {
|
||||
match expr.kind {
|
||||
// Closures returning literals can be unconditionally simplified
|
||||
hir::ExprKind::Lit(_) => true,
|
||||
|
||||
hir::ExprKind::Index(ref object, ref index) => {
|
||||
// arguments are not being indexed into
|
||||
if expr_uses_argument(object, params) {
|
||||
false
|
||||
} else {
|
||||
// arguments are not used as index
|
||||
!expr_uses_argument(index, params)
|
||||
}
|
||||
},
|
||||
|
||||
// Reading fields can be simplified if the object is not an argument of the closure
|
||||
hir::ExprKind::Field(ref object, _) => !expr_uses_argument(object, params),
|
||||
|
||||
// Paths can be simplified if the root is not the argument, this also covers None
|
||||
hir::ExprKind::Path(_) => !expr_uses_argument(expr, params),
|
||||
|
||||
// Calls to Some, Ok, Err can be considered literals if they don't derive an argument
|
||||
hir::ExprKind::Call(ref func, ref args) => if_chain! {
|
||||
if variant_calls; // Disable lint when rules conflict with bind_instead_of_map
|
||||
if let hir::ExprKind::Path(ref path) = func.kind;
|
||||
if match_any_qpath(path, &[&["Some"], &["Ok"], &["Err"]]);
|
||||
then {
|
||||
// Recursively check all arguments
|
||||
args.iter().all(|arg| can_simplify(arg, params, variant_calls))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
|
||||
// For anything more complex than the above, a closure is probably the right solution,
|
||||
// or the case is handled by an other lint
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be
|
||||
/// replaced with `<fn>(return value of simple closure)`
|
||||
pub(super) fn lint<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx hir::Expr<'_>,
|
||||
args: &'tcx [hir::Expr<'_>],
|
||||
allow_variant_calls: bool,
|
||||
simplify_using: &str,
|
||||
) {
|
||||
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(option_type));
|
||||
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]), sym!(result_type));
|
||||
|
||||
if is_option || is_result {
|
||||
if let hir::ExprKind::Closure(_, _, eid, _, _) = args[1].kind {
|
||||
let body = cx.tcx.hir().body(eid);
|
||||
let ex = &body.value;
|
||||
let params = &body.params;
|
||||
|
||||
if can_simplify(ex, params, allow_variant_calls) {
|
||||
let msg = if is_option {
|
||||
"unnecessary closure used to substitute value for `Option::None`"
|
||||
} else {
|
||||
"unnecessary closure used to substitute value for `Result::Err`"
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNNECESSARY_LAZY_EVALUATIONS,
|
||||
expr.span,
|
||||
msg,
|
||||
&format!("Use `{}` instead", simplify_using),
|
||||
format!(
|
||||
"{0}.{1}({2})",
|
||||
snippet(cx, args[0].span, ".."),
|
||||
simplify_using,
|
||||
snippet(cx, ex.span, ".."),
|
||||
),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -433,8 +433,7 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
|
|||
return;
|
||||
}
|
||||
let binding = match expr.kind {
|
||||
ExprKind::Path(hir::QPath::LangItem(..)) => None,
|
||||
ExprKind::Path(ref qpath) => {
|
||||
ExprKind::Path(ref qpath) if !matches!(qpath, hir::QPath::LangItem(..)) => {
|
||||
let binding = last_path_segment(qpath).ident.as_str();
|
||||
if binding.starts_with('_') &&
|
||||
!binding.starts_with("__") &&
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ declare_clippy_lint! {
|
|||
/// **What it does:** Detects passing a mutable reference to a function that only
|
||||
/// requires an immutable reference.
|
||||
///
|
||||
/// **Why is this bad?** The immutable reference rules out all other references
|
||||
/// to the value. Also the code misleads about the intent of the call site.
|
||||
/// **Why is this bad?** The mutable reference rules out all other references to
|
||||
/// the value. Also the code misleads about the intent of the call site.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
|
|
@ -39,6 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
|
|||
arguments,
|
||||
cx.typeck_results().expr_ty(fn_expr),
|
||||
&rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)),
|
||||
"function",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -46,14 +47,20 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
|
|||
let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap();
|
||||
let substs = cx.typeck_results().node_substs(e.hir_id);
|
||||
let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs);
|
||||
check_arguments(cx, arguments, method_type, &path.ident.as_str())
|
||||
check_arguments(cx, arguments, method_type, &path.ident.as_str(), "method")
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_arguments<'tcx>(cx: &LateContext<'tcx>, arguments: &[Expr<'_>], type_definition: Ty<'tcx>, name: &str) {
|
||||
fn check_arguments<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
arguments: &[Expr<'_>],
|
||||
type_definition: Ty<'tcx>,
|
||||
name: &str,
|
||||
fn_kind: &str,
|
||||
) {
|
||||
match type_definition.kind {
|
||||
ty::FnDef(..) | ty::FnPtr(_) => {
|
||||
let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs();
|
||||
|
|
@ -68,7 +75,7 @@ fn check_arguments<'tcx>(cx: &LateContext<'tcx>, arguments: &[Expr<'_>], type_de
|
|||
cx,
|
||||
UNNECESSARY_MUT_PASSED,
|
||||
argument.span,
|
||||
&format!("The function/method `{}` doesn't need a mutable reference", name),
|
||||
&format!("the {} `{}` doesn't need a mutable reference", fn_kind, name),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ impl<'tcx> LateLintPass<'tcx> for Mutex {
|
|||
let mutex_param = subst.type_at(0);
|
||||
if let Some(atomic_name) = get_atomic_name(mutex_param) {
|
||||
let msg = format!(
|
||||
"Consider using an `{}` instead of a `Mutex` here. If you just want the locking \
|
||||
behavior and not the internal type, consider using `Mutex<()>`.",
|
||||
"consider using an `{}` instead of a `Mutex` here; if you just want the locking \
|
||||
behavior and not the internal type, consider using `Mutex<()>`",
|
||||
atomic_name
|
||||
);
|
||||
match mutex_param.kind {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
|
|
@ -102,36 +103,36 @@ impl EarlyLintPass for Precedence {
|
|||
}
|
||||
}
|
||||
|
||||
if let ExprKind::Unary(UnOp::Neg, ref rhs) = expr.kind {
|
||||
if let ExprKind::MethodCall(ref path_segment, ref args, _) = rhs.kind {
|
||||
if let ExprKind::Unary(UnOp::Neg, operand) = &expr.kind {
|
||||
let mut arg = operand;
|
||||
|
||||
let mut all_odd = true;
|
||||
while let ExprKind::MethodCall(path_segment, args, _) = &arg.kind {
|
||||
let path_segment_str = path_segment.ident.name.as_str();
|
||||
if let Some(slf) = args.first() {
|
||||
if let ExprKind::Lit(ref lit) = slf.kind {
|
||||
match lit.kind {
|
||||
LitKind::Int(..) | LitKind::Float(..) => {
|
||||
if ALLOWED_ODD_FUNCTIONS
|
||||
.iter()
|
||||
.any(|odd_function| **odd_function == *path_segment_str)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRECEDENCE,
|
||||
expr.span,
|
||||
"unary minus has lower precedence than method call",
|
||||
"consider adding parentheses to clarify your intent",
|
||||
format!(
|
||||
"-({})",
|
||||
snippet_with_applicability(cx, rhs.span, "..", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
all_odd &= ALLOWED_ODD_FUNCTIONS
|
||||
.iter()
|
||||
.any(|odd_function| **odd_function == *path_segment_str);
|
||||
arg = args.first().expect("A method always has a receiver.");
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
if !all_odd;
|
||||
if let ExprKind::Lit(lit) = &arg.kind;
|
||||
if let LitKind::Int(..) | LitKind::Float(..) = &lit.kind;
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PRECEDENCE,
|
||||
expr.span,
|
||||
"unary minus has lower precedence than method call",
|
||||
"consider adding parentheses to clarify your intent",
|
||||
format!(
|
||||
"-({})",
|
||||
snippet_with_applicability(cx, operand.span, "..", &mut applicability)
|
||||
),
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,14 +36,27 @@ declare_clippy_lint! {
|
|||
/// argument may also fail to compile if you change the argument. Applying
|
||||
/// this lint on them will fix the problem, but they may be in other crates.
|
||||
///
|
||||
/// One notable example of a function that may cause issues, and which cannot
|
||||
/// easily be changed due to being in the standard library is `Vec::contains`.
|
||||
/// when called on a `Vec<Vec<T>>`. If a `&Vec` is passed to that method then
|
||||
/// it will compile, but if a `&[T]` is passed then it will not compile.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn cannot_take_a_slice(v: &Vec<u8>) -> bool {
|
||||
/// let vec_of_vecs: Vec<Vec<u8>> = some_other_fn();
|
||||
///
|
||||
/// vec_of_vecs.contains(v)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Also there may be `fn(&Vec)`-typed references pointing to your function.
|
||||
/// If you have them, you will get a compiler error after applying this lint's
|
||||
/// suggestions. You then have the choice to undo your changes or change the
|
||||
/// type of the reference.
|
||||
///
|
||||
/// Note that if the function is part of your public interface, there may be
|
||||
/// other crates referencing it you may not be aware. Carefully deprecate the
|
||||
/// function before applying the lint suggestions in this case.
|
||||
/// other crates referencing it, of which you may not be aware. Carefully
|
||||
/// deprecate the function before applying the lint suggestions in this case.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```ignore
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
|
|||
|
||||
use crate::utils::sugg::Sugg;
|
||||
use crate::utils::{
|
||||
higher, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet_with_applicability,
|
||||
span_lint_and_sugg, SpanlessEq,
|
||||
eq_expr_value, higher, is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet_with_applicability,
|
||||
span_lint_and_sugg,
|
||||
};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
|
@ -65,7 +65,7 @@ impl QuestionMark {
|
|||
if let ExprKind::Block(block, None) = &else_.kind;
|
||||
if block.stmts.is_empty();
|
||||
if let Some(block_expr) = &block.expr;
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(subject, block_expr);
|
||||
if eq_expr_value(cx, subject, block_expr);
|
||||
then {
|
||||
replacement = Some(format!("Some({}?)", receiver_str));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,43 @@
|
|||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast::visit::FnKind;
|
||||
use rustc_ast::ast::Attribute;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::BytePos;
|
||||
|
||||
use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then};
|
||||
use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_sugg, span_lint_and_then};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for `let`-bindings, which are subsequently
|
||||
/// returned.
|
||||
///
|
||||
/// **Why is this bad?** It is just extraneous code. Remove it to make your code
|
||||
/// more rusty.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// fn foo() -> String {
|
||||
/// let x = String::new();
|
||||
/// x
|
||||
/// }
|
||||
/// ```
|
||||
/// instead, use
|
||||
/// ```
|
||||
/// fn foo() -> String {
|
||||
/// String::new()
|
||||
/// }
|
||||
/// ```
|
||||
pub LET_AND_RETURN,
|
||||
style,
|
||||
"creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for return statements at the end of a block.
|
||||
|
|
@ -16,8 +45,7 @@ declare_clippy_lint! {
|
|||
/// **Why is this bad?** Removing the `return` and semicolon will make the code
|
||||
/// more rusty.
|
||||
///
|
||||
/// **Known problems:** If the computation returning the value borrows a local
|
||||
/// variable, removing the `return` may run afoul of the borrow checker.
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
|
|
@ -36,248 +64,223 @@ declare_clippy_lint! {
|
|||
"using a return statement like `return expr;` where an expression would suffice"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for unit (`()`) expressions that can be removed.
|
||||
///
|
||||
/// **Why is this bad?** Such expressions add no value, but can make the code
|
||||
/// less readable. Depending on formatting they can make a `break` or `return`
|
||||
/// statement look like a function call.
|
||||
///
|
||||
/// **Known problems:** The lint currently misses unit return types in types,
|
||||
/// e.g., the `F` in `fn generic_unit<F: Fn() -> ()>(f: F) { .. }`.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// fn return_unit() -> () {
|
||||
/// ()
|
||||
/// }
|
||||
/// ```
|
||||
pub UNUSED_UNIT,
|
||||
style,
|
||||
"needless unit expression"
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
enum RetReplacement {
|
||||
Empty,
|
||||
Block,
|
||||
}
|
||||
|
||||
declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]);
|
||||
declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]);
|
||||
|
||||
impl Return {
|
||||
// Check the final stmt or expr in a block for unnecessary return.
|
||||
fn check_block_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
|
||||
if let Some(stmt) = block.stmts.last() {
|
||||
match stmt.kind {
|
||||
ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
|
||||
self.check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
|
||||
},
|
||||
_ => (),
|
||||
impl<'tcx> LateLintPass<'tcx> for Return {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
// we need both a let-binding stmt and an expr
|
||||
if_chain! {
|
||||
if let Some(retexpr) = block.expr;
|
||||
if let Some(stmt) = block.stmts.iter().last();
|
||||
if let StmtKind::Local(local) = &stmt.kind;
|
||||
if local.ty.is_none();
|
||||
if local.attrs.is_empty();
|
||||
if let Some(initexpr) = &local.init;
|
||||
if let PatKind::Binding(.., ident, _) = local.pat.kind;
|
||||
if let ExprKind::Path(qpath) = &retexpr.kind;
|
||||
if match_qpath(qpath, &[&*ident.name.as_str()]);
|
||||
if !last_statement_borrows(cx, initexpr);
|
||||
if !in_external_macro(cx.sess(), initexpr.span);
|
||||
if !in_external_macro(cx.sess(), retexpr.span);
|
||||
if !in_external_macro(cx.sess(), local.span);
|
||||
if !in_macro(local.span);
|
||||
then {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LET_AND_RETURN,
|
||||
retexpr.span,
|
||||
"returning the result of a `let` binding from a block",
|
||||
|err| {
|
||||
err.span_label(local.span, "unnecessary `let` binding");
|
||||
|
||||
if let Some(snippet) = snippet_opt(cx, initexpr.span) {
|
||||
err.multipart_suggestion(
|
||||
"return the expression directly",
|
||||
vec![
|
||||
(local.span, String::new()),
|
||||
(retexpr.span, snippet),
|
||||
],
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
err.span_help(initexpr.span, "this expression can be directly returned");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the final expression in a block if it's a return.
|
||||
fn check_final_expr(
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &EarlyContext<'_>,
|
||||
expr: &ast::Expr,
|
||||
span: Option<Span>,
|
||||
replacement: RetReplacement,
|
||||
cx: &LateContext<'tcx>,
|
||||
kind: FnKind<'tcx>,
|
||||
_: &'tcx FnDecl<'tcx>,
|
||||
body: &'tcx Body<'tcx>,
|
||||
_: Span,
|
||||
_: HirId,
|
||||
) {
|
||||
match expr.kind {
|
||||
// simple return is always "bad"
|
||||
ast::ExprKind::Ret(ref inner) => {
|
||||
// allow `#[cfg(a)] return a; #[cfg(b)] return b;`
|
||||
if !expr.attrs.iter().any(attr_is_cfg) {
|
||||
Self::emit_return_lint(
|
||||
match kind {
|
||||
FnKind::Closure(_) => check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty),
|
||||
FnKind::ItemFn(..) | FnKind::Method(..) => {
|
||||
if let ExprKind::Block(ref block, _) = body.value.kind {
|
||||
check_block_return(cx, block);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_is_cfg(attr: &Attribute) -> bool {
|
||||
attr.meta_item_list().is_some() && attr.has_name(sym!(cfg))
|
||||
}
|
||||
|
||||
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) {
|
||||
if let Some(expr) = block.expr {
|
||||
check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty);
|
||||
} else if let Some(stmt) = block.stmts.iter().last() {
|
||||
match stmt.kind {
|
||||
StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => {
|
||||
check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_final_expr<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'tcx>,
|
||||
span: Option<Span>,
|
||||
replacement: RetReplacement,
|
||||
) {
|
||||
match expr.kind {
|
||||
// simple return is always "bad"
|
||||
ExprKind::Ret(ref inner) => {
|
||||
// allow `#[cfg(a)] return a; #[cfg(b)] return b;`
|
||||
if !expr.attrs.iter().any(attr_is_cfg) {
|
||||
let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
|
||||
if !borrows {
|
||||
emit_return_lint(
|
||||
cx,
|
||||
span.expect("`else return` is not possible"),
|
||||
inner.as_ref().map(|i| i.span),
|
||||
replacement,
|
||||
);
|
||||
}
|
||||
},
|
||||
// a whole block? check it!
|
||||
ast::ExprKind::Block(ref block, _) => {
|
||||
self.check_block_return(cx, block);
|
||||
},
|
||||
// an if/if let expr, check both exprs
|
||||
// note, if without else is going to be a type checking error anyways
|
||||
// (except for unit type functions) so we don't match it
|
||||
ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => {
|
||||
self.check_block_return(cx, ifblock);
|
||||
self.check_final_expr(cx, elsexpr, None, RetReplacement::Empty);
|
||||
},
|
||||
// a match expr, check all arms
|
||||
ast::ExprKind::Match(_, ref arms) => {
|
||||
for arm in arms {
|
||||
self.check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block);
|
||||
}
|
||||
},
|
||||
// a whole block? check it!
|
||||
ExprKind::Block(ref block, _) => {
|
||||
check_block_return(cx, block);
|
||||
},
|
||||
// a match expr, check all arms
|
||||
// an if/if let expr, check both exprs
|
||||
// note, if without else is going to be a type checking error anyways
|
||||
// (except for unit type functions) so we don't match it
|
||||
ExprKind::Match(_, ref arms, source) => match source {
|
||||
MatchSource::Normal => {
|
||||
for arm in arms.iter() {
|
||||
check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block);
|
||||
}
|
||||
},
|
||||
MatchSource::IfDesugar {
|
||||
contains_else_clause: true,
|
||||
}
|
||||
| MatchSource::IfLetDesugar {
|
||||
contains_else_clause: true,
|
||||
} => {
|
||||
if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind {
|
||||
check_block_return(cx, ifblock);
|
||||
}
|
||||
check_final_expr(cx, arms[1].body, None, RetReplacement::Empty);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_return_lint(cx: &EarlyContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
|
||||
match inner_span {
|
||||
Some(inner_span) => {
|
||||
if in_external_macro(cx.sess(), inner_span) || inner_span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
|
||||
if let Some(snippet) = snippet_opt(cx, inner_span) {
|
||||
diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
|
||||
}
|
||||
})
|
||||
},
|
||||
None => match replacement {
|
||||
RetReplacement::Empty => {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"remove `return`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
RetReplacement::Block => {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"replace `return` with an empty block",
|
||||
"{}".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
impl EarlyLintPass for Return {
|
||||
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
|
||||
match kind {
|
||||
FnKind::Fn(.., Some(block)) => self.check_block_return(cx, block),
|
||||
FnKind::Closure(_, body) => self.check_final_expr(cx, body, Some(body.span), RetReplacement::Empty),
|
||||
FnKind::Fn(.., None) => {},
|
||||
}
|
||||
if_chain! {
|
||||
if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
|
||||
if let ast::TyKind::Tup(ref vals) = ty.kind;
|
||||
if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
|
||||
then {
|
||||
lint_unneeded_unit_return(cx, ty, span);
|
||||
fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option<Span>, replacement: RetReplacement) {
|
||||
match inner_span {
|
||||
Some(inner_span) => {
|
||||
if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
|
||||
if_chain! {
|
||||
if let Some(ref stmt) = block.stmts.last();
|
||||
if let ast::StmtKind::Expr(ref expr) = stmt.kind;
|
||||
if is_unit_expr(expr) && !stmt.span.from_expansion();
|
||||
then {
|
||||
let sp = expr.span;
|
||||
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
|
||||
if let Some(snippet) = snippet_opt(cx, inner_span) {
|
||||
diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
|
||||
}
|
||||
})
|
||||
},
|
||||
None => match replacement {
|
||||
RetReplacement::Empty => {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNUSED_UNIT,
|
||||
sp,
|
||||
"unneeded unit expression",
|
||||
"remove the final `()`",
|
||||
NEEDLESS_RETURN,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"remove `return`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
|
||||
match e.kind {
|
||||
ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
|
||||
if is_unit_expr(expr) && !expr.span.from_expansion() {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNUSED_UNIT,
|
||||
expr.span,
|
||||
"unneeded `()`",
|
||||
"remove the `()`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
|
||||
let segments = &poly.trait_ref.path.segments;
|
||||
|
||||
if_chain! {
|
||||
if segments.len() == 1;
|
||||
if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str());
|
||||
if let Some(args) = &segments[0].args;
|
||||
if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
|
||||
if let ast::FnRetTy::Ty(ty) = &generic_args.output;
|
||||
if ty.kind.is_unit();
|
||||
then {
|
||||
lint_unneeded_unit_return(cx, ty, generic_args.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_is_cfg(attr: &ast::Attribute) -> bool {
|
||||
attr.meta_item_list().is_some() && attr.has_name(sym!(cfg))
|
||||
}
|
||||
|
||||
// get the def site
|
||||
#[must_use]
|
||||
fn get_def(span: Span) -> Option<Span> {
|
||||
if span.from_expansion() {
|
||||
Some(span.ctxt().outer_expn_data().def_site)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// is this expr a `()` unit?
|
||||
fn is_unit_expr(expr: &ast::Expr) -> bool {
|
||||
if let ast::ExprKind::Tup(ref vals) = expr.kind {
|
||||
vals.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
|
||||
let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) {
|
||||
fn_source
|
||||
.rfind("->")
|
||||
.map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
|
||||
(
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
|
||||
RetReplacement::Block => {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_RETURN,
|
||||
ret_span,
|
||||
"unneeded `return` statement",
|
||||
"replace `return` with an empty block",
|
||||
"{}".to_string(),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
(ty.span, Applicability::MaybeIncorrect)
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNUSED_UNIT,
|
||||
ret_span,
|
||||
"unneeded unit return type",
|
||||
"remove the `-> ()`",
|
||||
String::new(),
|
||||
appl,
|
||||
);
|
||||
);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
let mut visitor = BorrowVisitor { cx, borrows: false };
|
||||
walk_expr(&mut visitor, expr);
|
||||
visitor.borrows
|
||||
}
|
||||
|
||||
struct BorrowVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
borrows: bool,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
|
||||
if self.borrows {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(def_id) = fn_def_id(self.cx, expr) {
|
||||
self.borrows = self
|
||||
.cx
|
||||
.tcx
|
||||
.fn_sig(def_id)
|
||||
.output()
|
||||
.skip_binder()
|
||||
.walk()
|
||||
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
clippy_lints/src/self_assignment.rs
Normal file
51
clippy_lints/src/self_assignment.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use crate::utils::{eq_expr_value, snippet, span_lint};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for explicit self-assignments.
|
||||
///
|
||||
/// **Why is this bad?** Self-assignments are redundant and unlikely to be
|
||||
/// intentional.
|
||||
///
|
||||
/// **Known problems:** If expression contains any deref coercions or
|
||||
/// indexing operations they are assumed not to have any side effects.
|
||||
///
|
||||
/// **Example:**
|
||||
///
|
||||
/// ```rust
|
||||
/// struct Event {
|
||||
/// id: usize,
|
||||
/// x: i32,
|
||||
/// y: i32,
|
||||
/// }
|
||||
///
|
||||
/// fn copy_position(a: &mut Event, b: &Event) {
|
||||
/// a.x = b.x;
|
||||
/// a.y = a.y;
|
||||
/// }
|
||||
/// ```
|
||||
pub SELF_ASSIGNMENT,
|
||||
correctness,
|
||||
"explicit self-assignment"
|
||||
}
|
||||
|
||||
declare_lint_pass!(SelfAssignment => [SELF_ASSIGNMENT]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for SelfAssignment {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if let ExprKind::Assign(lhs, rhs, _) = &expr.kind {
|
||||
if eq_expr_value(cx, lhs, rhs) {
|
||||
let lhs = snippet(cx, lhs.span, "<lhs>");
|
||||
let rhs = snippet(cx, rhs.span, "<rhs>");
|
||||
span_lint(
|
||||
cx,
|
||||
SELF_ASSIGNMENT,
|
||||
expr.span,
|
||||
&format!("self-assignment of `{}` to `{}`", rhs, lhs),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,9 +111,9 @@ impl LateLintPass<'_> for StableSortPrimitive {
|
|||
STABLE_SORT_PRIMITIVE,
|
||||
expr.span,
|
||||
format!(
|
||||
"Use {} instead of {}",
|
||||
detection.method.unstable_name(),
|
||||
detection.method.stable_name()
|
||||
"used {} instead of {}",
|
||||
detection.method.stable_name(),
|
||||
detection.method.unstable_name()
|
||||
)
|
||||
.as_str(),
|
||||
"try",
|
||||
|
|
|
|||
|
|
@ -86,12 +86,20 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl {
|
|||
cx,
|
||||
expr,
|
||||
binop.node,
|
||||
&["Add", "Sub", "Mul", "Div"],
|
||||
&[
|
||||
"Add", "Sub", "Mul", "Div", "Rem", "BitAnd", "BitOr", "BitXor", "Shl", "Shr",
|
||||
],
|
||||
&[
|
||||
hir::BinOpKind::Add,
|
||||
hir::BinOpKind::Sub,
|
||||
hir::BinOpKind::Mul,
|
||||
hir::BinOpKind::Div,
|
||||
hir::BinOpKind::Rem,
|
||||
hir::BinOpKind::BitAnd,
|
||||
hir::BinOpKind::BitOr,
|
||||
hir::BinOpKind::BitXor,
|
||||
hir::BinOpKind::Shl,
|
||||
hir::BinOpKind::Shr,
|
||||
],
|
||||
) {
|
||||
span_lint(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::utils::sugg::Sugg;
|
||||
use crate::utils::{
|
||||
differing_macro_contexts, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then, walk_ptrs_ty,
|
||||
SpanlessEq,
|
||||
differing_macro_contexts, eq_expr_value, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_then,
|
||||
walk_ptrs_ty,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
|
|
@ -92,8 +92,8 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
|
|||
if rhs2.segments.len() == 1;
|
||||
|
||||
if ident.as_str() == rhs2.segments[0].ident.as_str();
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(tmp_init, lhs1);
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(rhs1, lhs2);
|
||||
if eq_expr_value(cx, tmp_init, lhs1);
|
||||
if eq_expr_value(cx, rhs1, lhs2);
|
||||
then {
|
||||
if let ExprKind::Field(ref lhs1, _) = lhs1.kind {
|
||||
if let ExprKind::Field(ref lhs2, _) = lhs2.kind {
|
||||
|
|
@ -193,7 +193,7 @@ enum Slice<'a> {
|
|||
fn check_for_slice<'a>(cx: &LateContext<'_>, lhs1: &'a Expr<'_>, lhs2: &'a Expr<'_>) -> Slice<'a> {
|
||||
if let ExprKind::Index(ref lhs1, ref idx1) = lhs1.kind {
|
||||
if let ExprKind::Index(ref lhs2, ref idx2) = lhs2.kind {
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, lhs2) {
|
||||
if eq_expr_value(cx, lhs1, lhs2) {
|
||||
let ty = walk_ptrs_ty(cx.typeck_results().expr_ty(lhs1));
|
||||
|
||||
if matches!(ty.kind, ty::Slice(_))
|
||||
|
|
@ -221,8 +221,8 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
|
|||
if !differing_macro_contexts(first.span, second.span);
|
||||
if let ExprKind::Assign(ref lhs0, ref rhs0, _) = first.kind;
|
||||
if let ExprKind::Assign(ref lhs1, ref rhs1, _) = second.kind;
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs0, rhs1);
|
||||
if SpanlessEq::new(cx).ignore_fn().eq_expr(lhs1, rhs0);
|
||||
if eq_expr_value(cx, lhs0, rhs1);
|
||||
if eq_expr_value(cx, lhs1, rhs0);
|
||||
then {
|
||||
let lhs0 = Sugg::hir_opt(cx, lhs0);
|
||||
let rhs0 = Sugg::hir_opt(cx, rhs0);
|
||||
|
|
|
|||
100
clippy_lints/src/to_string_in_display.rs
Normal file
100
clippy_lints/src/to_string_in_display.rs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
use crate::utils::{match_def_path, match_trait_method, paths, span_lint};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for uses of `to_string()` in `Display` traits.
|
||||
///
|
||||
/// **Why is this bad?** Usually `to_string` is implemented indirectly
|
||||
/// via `Display`. Hence using it while implementing `Display` would
|
||||
/// lead to infinite recursion.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// struct Structure(i32);
|
||||
/// impl fmt::Display for Structure {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
/// write!(f, "{}", self.to_string())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// struct Structure(i32);
|
||||
/// impl fmt::Display for Structure {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
/// write!(f, "{}", self.0)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub TO_STRING_IN_DISPLAY,
|
||||
correctness,
|
||||
"to_string method used while implementing Display trait"
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ToStringInDisplay {
|
||||
in_display_impl: bool,
|
||||
}
|
||||
|
||||
impl ToStringInDisplay {
|
||||
pub fn new() -> Self {
|
||||
Self { in_display_impl: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]);
|
||||
|
||||
impl LateLintPass<'_> for ToStringInDisplay {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if is_display_impl(cx, item) {
|
||||
self.in_display_impl = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if is_display_impl(cx, item) {
|
||||
self.in_display_impl = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::MethodCall(ref path, _, _, _) = expr.kind;
|
||||
if path.ident.name == sym!(to_string);
|
||||
if match_trait_method(cx, expr, &paths::TO_STRING);
|
||||
if self.in_display_impl;
|
||||
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
TO_STRING_IN_DISPLAY,
|
||||
expr.span,
|
||||
"Using to_string in fmt::Display implementation might lead to infinite recursion",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ItemKind::Impl { of_trait: Some(trait_ref), .. } = &item.kind;
|
||||
if let Some(did) = trait_ref.trait_def_id();
|
||||
then {
|
||||
match_def_path(cx, did, &paths::DISPLAY_TRAIT)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::utils::{
|
||||
is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg,
|
||||
in_constant, is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg,
|
||||
span_lint_and_then, sugg,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
|
|
@ -331,6 +331,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
|||
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
|
||||
if match_def_path(cx, def_id, &paths::TRANSMUTE);
|
||||
then {
|
||||
// Avoid suggesting from/to bits in const contexts.
|
||||
// See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`.
|
||||
let const_context = in_constant(cx, e.hir_id);
|
||||
|
||||
let from_ty = cx.typeck_results().expr_ty(&args[0]);
|
||||
let to_ty = cx.typeck_results().expr_ty(e);
|
||||
|
||||
|
|
@ -544,7 +548,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
|||
},
|
||||
)
|
||||
},
|
||||
(ty::Int(_) | ty::Uint(_), ty::Float(_)) => span_lint_and_then(
|
||||
(ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context => span_lint_and_then(
|
||||
cx,
|
||||
TRANSMUTE_INT_TO_FLOAT,
|
||||
e.span,
|
||||
|
|
@ -567,7 +571,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
|||
);
|
||||
},
|
||||
),
|
||||
(ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) => span_lint_and_then(
|
||||
(ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) if !const_context => span_lint_and_then(
|
||||
cx,
|
||||
TRANSMUTE_FLOAT_TO_INT,
|
||||
e.span,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use crate::utils::{
|
||||
is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet,
|
||||
snippet_with_macro_callsite, span_lint_and_sugg,
|
||||
is_type_diagnostic_item, match_def_path, match_qpath, paths, snippet, snippet_with_macro_callsite,
|
||||
span_lint_and_sugg,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, QPath, LangItem, MatchSource};
|
||||
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
|
|
|||
|
|
@ -353,14 +353,25 @@ impl Types {
|
|||
);
|
||||
return; // don't recurse into the type
|
||||
}
|
||||
if let Some(span) = match_type_parameter(cx, qpath, &paths::BOX) {
|
||||
if match_type_parameter(cx, qpath, &paths::BOX).is_some() {
|
||||
let box_ty = match &last_path_segment(qpath).args.unwrap().args[0] {
|
||||
GenericArg::Type(ty) => match &ty.kind {
|
||||
TyKind::Path(qpath) => qpath,
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let inner_span = match &last_path_segment(&box_ty).args.unwrap().args[0] {
|
||||
GenericArg::Type(ty) => ty.span,
|
||||
_ => return,
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
REDUNDANT_ALLOCATION,
|
||||
hir_ty.span,
|
||||
"usage of `Rc<Box<T>>`",
|
||||
"try",
|
||||
snippet(cx, span, "..").to_string(),
|
||||
format!("Rc<{}>", snippet(cx, inner_span, "..")),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
return; // don't recurse into the type
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
use crate::utils::ast_utils::{eq_field_pat, eq_id, eq_pat, eq_path};
|
||||
use crate::utils::{over, span_lint_and_then};
|
||||
use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
|
||||
use rustc_ast::mut_visit::*;
|
||||
use rustc_ast::ptr::P;
|
||||
use rustc_ast::{self as ast, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
|
||||
use rustc_ast_pretty::pprust;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
|
|
|
|||
144
clippy_lints/src/unused_unit.rs
Normal file
144
clippy_lints/src/unused_unit.rs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_ast::visit::FnKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
use rustc_span::BytePos;
|
||||
|
||||
use crate::utils::span_lint_and_sugg;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for unit (`()`) expressions that can be removed.
|
||||
///
|
||||
/// **Why is this bad?** Such expressions add no value, but can make the code
|
||||
/// less readable. Depending on formatting they can make a `break` or `return`
|
||||
/// statement look like a function call.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ```rust
|
||||
/// fn return_unit() -> () {
|
||||
/// ()
|
||||
/// }
|
||||
/// ```
|
||||
pub UNUSED_UNIT,
|
||||
style,
|
||||
"needless unit expression"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]);
|
||||
|
||||
impl EarlyLintPass for UnusedUnit {
|
||||
fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) {
|
||||
if_chain! {
|
||||
if let ast::FnRetTy::Ty(ref ty) = kind.decl().output;
|
||||
if let ast::TyKind::Tup(ref vals) = ty.kind;
|
||||
if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span);
|
||||
then {
|
||||
lint_unneeded_unit_return(cx, ty, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
|
||||
if_chain! {
|
||||
if let Some(ref stmt) = block.stmts.last();
|
||||
if let ast::StmtKind::Expr(ref expr) = stmt.kind;
|
||||
if is_unit_expr(expr) && !stmt.span.from_expansion();
|
||||
then {
|
||||
let sp = expr.span;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNUSED_UNIT,
|
||||
sp,
|
||||
"unneeded unit expression",
|
||||
"remove the final `()`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
|
||||
match e.kind {
|
||||
ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => {
|
||||
if is_unit_expr(expr) && !expr.span.from_expansion() {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNUSED_UNIT,
|
||||
expr.span,
|
||||
"unneeded `()`",
|
||||
"remove the `()`",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) {
|
||||
let segments = &poly.trait_ref.path.segments;
|
||||
|
||||
if_chain! {
|
||||
if segments.len() == 1;
|
||||
if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str());
|
||||
if let Some(args) = &segments[0].args;
|
||||
if let ast::GenericArgs::Parenthesized(generic_args) = &**args;
|
||||
if let ast::FnRetTy::Ty(ty) = &generic_args.output;
|
||||
if ty.kind.is_unit();
|
||||
then {
|
||||
lint_unneeded_unit_return(cx, ty, generic_args.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the def site
|
||||
#[must_use]
|
||||
fn get_def(span: Span) -> Option<Span> {
|
||||
if span.from_expansion() {
|
||||
Some(span.ctxt().outer_expn_data().def_site)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// is this expr a `()` unit?
|
||||
fn is_unit_expr(expr: &ast::Expr) -> bool {
|
||||
if let ast::ExprKind::Tup(ref vals) = expr.kind {
|
||||
vals.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) {
|
||||
let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) {
|
||||
fn_source
|
||||
.rfind("->")
|
||||
.map_or((ty.span, Applicability::MaybeIncorrect), |rpos| {
|
||||
(
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
(ty.span, Applicability::MaybeIncorrect)
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNUSED_UNIT,
|
||||
ret_span,
|
||||
"unneeded unit return type",
|
||||
"remove the `-> ()`",
|
||||
String::new(),
|
||||
appl,
|
||||
);
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
pub USE_SELF,
|
||||
nursery,
|
||||
"Unnecessary structure name repetition whereas `Self` is applicable"
|
||||
"unnecessary structure name repetition whereas `Self` is applicable"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UseSelf => [USE_SELF]);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::utils::sugg::Sugg;
|
||||
use crate::utils::{
|
||||
get_parent_expr, is_type_diagnostic_item, match_def_path, match_trait_method, paths, snippet,
|
||||
snippet_with_macro_callsite, span_lint_and_help, span_lint_and_sugg,
|
||||
|
|
@ -158,7 +159,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
if TyS::same_type(a, b);
|
||||
|
||||
then {
|
||||
let sugg = snippet(cx, args[0].span.source_callsite(), "<expr>").into_owned();
|
||||
let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "<expr>").maybe_par();
|
||||
let sugg_msg =
|
||||
format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
|
||||
span_lint_and_sugg(
|
||||
|
|
@ -167,7 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
|||
e.span,
|
||||
"useless conversion to the same type",
|
||||
&sugg_msg,
|
||||
sugg,
|
||||
sugg.to_string(),
|
||||
Applicability::MachineApplicable, // snippet
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
|
||||
|
||||
use crate::utils::{both, over};
|
||||
use rustc_ast::{self as ast, *};
|
||||
use rustc_ast::ptr::P;
|
||||
use rustc_ast::{self as ast, *};
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::mem;
|
||||
|
||||
|
|
|
|||
|
|
@ -175,18 +175,15 @@ impl PrintVisitor {
|
|||
}
|
||||
|
||||
fn print_qpath(&mut self, path: &QPath<'_>) {
|
||||
match *path {
|
||||
QPath::LangItem(lang_item, _) => {
|
||||
println!(
|
||||
" if matches!({}, QPath::LangItem(LangItem::{:?}, _));",
|
||||
self.current, lang_item,
|
||||
);
|
||||
},
|
||||
_ => {
|
||||
print!(" if match_qpath({}, &[", self.current);
|
||||
print_path(path, &mut true);
|
||||
println!("]);");
|
||||
},
|
||||
if let QPath::LangItem(lang_item, _) = *path {
|
||||
println!(
|
||||
" if matches!({}, QPath::LangItem(LangItem::{:?}, _));",
|
||||
self.current, lang_item,
|
||||
);
|
||||
} else {
|
||||
print!(" if match_qpath({}, &[", self.current);
|
||||
print_path(path, &mut true);
|
||||
println!("]);");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ define_Conf! {
|
|||
(type_complexity_threshold, "type_complexity_threshold": u64, 250),
|
||||
/// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
|
||||
(single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4),
|
||||
/// Lint: BOXED_LOCAL. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
|
||||
/// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
|
||||
(too_large_for_stack, "too_large_for_stack": u64, 200),
|
||||
/// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
|
||||
(enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3),
|
||||
|
|
|
|||
|
|
@ -56,43 +56,45 @@ pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
|
|||
}
|
||||
|
||||
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,
|
||||
}
|
||||
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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ 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,
|
||||
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;
|
||||
|
|
@ -23,9 +23,7 @@ pub struct SpanlessEq<'a, 'tcx> {
|
|||
/// Context used to evaluate constant expressions.
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
|
||||
/// If is true, never consider as equal expressions containing function
|
||||
/// calls.
|
||||
ignore_fn: bool,
|
||||
allow_side_effects: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
|
|
@ -33,13 +31,14 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
Self {
|
||||
cx,
|
||||
maybe_typeck_results: cx.maybe_typeck_results(),
|
||||
ignore_fn: false,
|
||||
allow_side_effects: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ignore_fn(self) -> Self {
|
||||
/// Consider expressions containing potential side effects as not equal.
|
||||
pub fn deny_side_effects(self) -> Self {
|
||||
Self {
|
||||
ignore_fn: true,
|
||||
allow_side_effects: false,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +66,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
|
||||
#[allow(clippy::similar_names)]
|
||||
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
if self.ignore_fn && differing_macro_contexts(left.span, right.span) {
|
||||
if !self.allow_side_effects && differing_macro_contexts(left.span, right.span) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -90,10 +89,10 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
both(&li.label, &ri.label, |l, r| l.ident.as_str() == r.ident.as_str())
|
||||
},
|
||||
(&ExprKind::Assign(ref ll, ref lr, _), &ExprKind::Assign(ref rl, ref rr, _)) => {
|
||||
self.eq_expr(ll, rl) && self.eq_expr(lr, 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)) => {
|
||||
lo.node == ro.node && self.eq_expr(ll, rl) && self.eq_expr(lr, 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)) => {
|
||||
|
|
@ -108,7 +107,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
},
|
||||
(&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.ignore_fn && self.eq_expr(l_fun, r_fun) && self.eq_exprs(l_args, 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)) => {
|
||||
|
|
@ -134,7 +133,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
})
|
||||
},
|
||||
(&ExprKind::MethodCall(l_path, _, l_args, _), &ExprKind::MethodCall(r_path, _, r_args, _)) => {
|
||||
!self.ignore_fn && self.eq_path_segment(l_path, r_path) && self.eq_exprs(l_args, 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));
|
||||
|
|
@ -186,10 +185,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
}
|
||||
|
||||
pub fn eq_fieldpat(&mut self, left: &FieldPat<'_>, right: &FieldPat<'_>) -> bool {
|
||||
match (&left, &right) {
|
||||
(FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) =>
|
||||
li.name.as_str() == ri.name.as_str() && self.eq_pat(lp, rp),
|
||||
}
|
||||
let (FieldPat { ident: li, pat: lp, .. }, FieldPat { ident: ri, pat: rp, .. }) = (&left, &right);
|
||||
li.name.as_str() == ri.name.as_str() && self.eq_pat(lp, rp)
|
||||
}
|
||||
|
||||
/// Checks whether two patterns are the same.
|
||||
|
|
@ -233,8 +230,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
|||
(&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,
|
||||
(&QPath::LangItem(llang_item, _), &QPath::LangItem(rlang_item, _)) => llang_item == rlang_item,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -352,6 +348,11 @@ pub fn over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> 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.
|
||||
|
|
@ -615,7 +616,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
},
|
||||
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);
|
||||
}
|
||||
|
|
@ -727,7 +728,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
|
|||
},
|
||||
QPath::LangItem(lang_item, ..) => {
|
||||
lang_item.hash(&mut self.s);
|
||||
}
|
||||
},
|
||||
},
|
||||
TyKind::OpaqueDef(_, arg_list) => {
|
||||
self.hash_generic_args(arg_list);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::utils::SpanlessEq;
|
||||
use crate::utils::{
|
||||
is_expn_of, match_def_path, match_qpath, match_type, method_calls, paths, run_lints, snippet, span_lint,
|
||||
span_lint_and_help, span_lint_and_sugg, walk_ptrs_ty,
|
||||
span_lint_and_help, span_lint_and_sugg, walk_ptrs_ty, SpanlessEq,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, NodeId};
|
||||
|
|
@ -493,7 +492,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
|
|||
if let StmtKind::Semi(only_expr) = &stmts[0].kind;
|
||||
if let ExprKind::MethodCall(ref ps, _, ref span_call_args, _) = &only_expr.kind;
|
||||
let and_then_snippets = get_and_then_snippets(cx, and_then_args);
|
||||
let mut sle = SpanlessEq::new(cx).ignore_fn();
|
||||
let mut sle = SpanlessEq::new(cx).deny_side_effects();
|
||||
then {
|
||||
match &*ps.ident.as_str() {
|
||||
"span_suggestion" if sle.eq_expr(&and_then_args[2], &span_call_args[1]) => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub mod sugg;
|
|||
pub mod usage;
|
||||
pub use self::attrs::*;
|
||||
pub use self::diagnostics::*;
|
||||
pub use self::hir_utils::{both, over, SpanlessEq, SpanlessHash};
|
||||
pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::mem;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
|
|||
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
|
||||
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
|
||||
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
|
||||
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"];
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
use crate::consts::constant;
|
||||
use crate::consts::{constant, Constant};
|
||||
use crate::rustc_target::abi::LayoutOf;
|
||||
use crate::utils::{higher, is_copy, snippet_with_applicability, span_lint_and_sugg};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct UselessVec {
|
||||
pub too_large_for_stack: u64,
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for usage of `&vec![..]` when using `&[..]` would
|
||||
/// be possible.
|
||||
|
|
@ -31,7 +38,7 @@ declare_clippy_lint! {
|
|||
"useless `vec!`"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UselessVec => [USELESS_VEC]);
|
||||
impl_lint_pass!(UselessVec => [USELESS_VEC]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UselessVec {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
|
|
@ -42,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
|
|||
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref addressee) = expr.kind;
|
||||
if let Some(vec_args) = higher::vec_macro(cx, addressee);
|
||||
then {
|
||||
check_vec_macro(cx, &vec_args, expr.span);
|
||||
self.check_vec_macro(cx, &vec_args, expr.span);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,46 +67,62 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
|
|||
.ctxt()
|
||||
.outer_expn_data()
|
||||
.call_site;
|
||||
check_vec_macro(cx, &vec_args, span);
|
||||
self.check_vec_macro(cx, &vec_args, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_vec_macro<'tcx>(cx: &LateContext<'tcx>, vec_args: &higher::VecArgs<'tcx>, span: Span) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snippet = match *vec_args {
|
||||
higher::VecArgs::Repeat(elem, len) => {
|
||||
if constant(cx, cx.typeck_results(), len).is_some() {
|
||||
format!(
|
||||
"&[{}; {}]",
|
||||
snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
|
||||
snippet_with_applicability(cx, len.span, "len", &mut applicability)
|
||||
)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
higher::VecArgs::Vec(args) => {
|
||||
if let Some(last) = args.iter().last() {
|
||||
let span = args[0].span.to(last.span);
|
||||
impl UselessVec {
|
||||
fn check_vec_macro<'tcx>(self, cx: &LateContext<'tcx>, vec_args: &higher::VecArgs<'tcx>, span: Span) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snippet = match *vec_args {
|
||||
higher::VecArgs::Repeat(elem, len) => {
|
||||
if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
|
||||
return;
|
||||
}
|
||||
|
||||
format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
|
||||
} else {
|
||||
"&[]".into()
|
||||
}
|
||||
},
|
||||
};
|
||||
format!(
|
||||
"&[{}; {}]",
|
||||
snippet_with_applicability(cx, elem.span, "elem", &mut applicability),
|
||||
snippet_with_applicability(cx, len.span, "len", &mut applicability)
|
||||
)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
higher::VecArgs::Vec(args) => {
|
||||
if let Some(last) = args.iter().last() {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack {
|
||||
return;
|
||||
}
|
||||
let span = args[0].span.to(last.span);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
USELESS_VEC,
|
||||
span,
|
||||
"useless use of `vec!`",
|
||||
"you can use a slice directly",
|
||||
snippet,
|
||||
applicability,
|
||||
);
|
||||
format!("&[{}]", snippet_with_applicability(cx, span, "..", &mut applicability))
|
||||
} else {
|
||||
"&[]".into()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
USELESS_VEC,
|
||||
span,
|
||||
"useless use of `vec!`",
|
||||
"you can use a slice directly",
|
||||
snippet,
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn size_of(cx: &LateContext<'_>, expr: &Expr<'_>) -> u64 {
|
||||
let ty = cx.typeck_results().expr_ty_adjusted(expr);
|
||||
cx.layout_of(ty).map_or(0, |l| l.size.bytes())
|
||||
}
|
||||
|
||||
/// Returns the item type of the vector (i.e., the `T` in `Vec<T>`).
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ impl EarlyLintPass for Write {
|
|||
fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &MacCall) {
|
||||
if mac.path == sym!(println) {
|
||||
span_lint(cx, PRINT_STDOUT, mac.span(), "use of `println!`");
|
||||
if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
|
||||
if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
|
||||
if fmt_str.symbol == Symbol::intern("") {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
@ -252,7 +252,7 @@ impl EarlyLintPass for Write {
|
|||
}
|
||||
} else if mac.path == sym!(print) {
|
||||
span_lint(cx, PRINT_STDOUT, mac.span(), "use of `print!`");
|
||||
if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), false) {
|
||||
if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), false) {
|
||||
if check_newlines(&fmt_str) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
@ -273,7 +273,7 @@ impl EarlyLintPass for Write {
|
|||
}
|
||||
}
|
||||
} else if mac.path == sym!(write) {
|
||||
if let (Some(fmt_str), _) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
|
||||
if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), true) {
|
||||
if check_newlines(&fmt_str) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
|
|
@ -294,7 +294,7 @@ impl EarlyLintPass for Write {
|
|||
}
|
||||
}
|
||||
} else if mac.path == sym!(writeln) {
|
||||
if let (Some(fmt_str), expr) = self.check_tts(cx, &mac.args.inner_tokens(), true) {
|
||||
if let (Some(fmt_str), expr) = self.check_tts(cx, mac.args.inner_tokens(), true) {
|
||||
if fmt_str.symbol == Symbol::intern("") {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let suggestion = expr.map_or_else(
|
||||
|
|
@ -364,17 +364,11 @@ 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,
|
||||
};
|
||||
let tts = tts.clone();
|
||||
|
||||
let mut parser = parser::Parser::new(&cx.sess.parse_sess, tts, false, None);
|
||||
let mut expr: Option<Expr> = None;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue