Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
34
clippy_lints/src/casts/fn_to_numeric_cast_any.rs
Normal file
34
clippy_lints/src/casts/fn_to_numeric_cast_any.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
use super::FN_TO_NUMERIC_CAST_ANY;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
// We allow casts from any function type to any function type.
|
||||
match cast_to.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(..) => return,
|
||||
_ => { /* continue to checks */ },
|
||||
}
|
||||
|
||||
match cast_from.kind() {
|
||||
ty::FnDef(..) | ty::FnPtr(_) => {
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FN_TO_NUMERIC_CAST_ANY,
|
||||
expr.span,
|
||||
&format!("casting function pointer `{}` to `{}`", from_snippet, cast_to),
|
||||
"did you mean to invoke the function?",
|
||||
format!("{}() as {}", from_snippet, cast_to),
|
||||
applicability,
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ mod cast_ref_to_mut;
|
||||
mod cast_sign_loss;
|
||||
mod char_lit_as_u8;
|
||||
mod fn_to_numeric_cast;
|
||||
mod fn_to_numeric_cast_any;
|
||||
mod fn_to_numeric_cast_with_truncation;
|
||||
mod ptr_as_ptr;
|
||||
mod unnecessary_cast;
|
||||
@@ -251,6 +252,42 @@ declare_clippy_lint! {
|
||||
"casting a function pointer to a numeric type not wide enough to store the address"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts of a function pointer to any integer type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Casting a function pointer to an integer can have surprising results and can occur
|
||||
/// accidentally if parantheses are omitted from a function call. If you aren't doing anything
|
||||
/// low-level with function pointers then you can opt-out of casting functions to integers in
|
||||
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
|
||||
/// pointer casts in your code.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// // Bad: fn1 is cast as `usize`
|
||||
/// fn fn1() -> u16 {
|
||||
/// 1
|
||||
/// };
|
||||
/// let _ = fn1 as usize;
|
||||
///
|
||||
/// // Good: maybe you intended to call the function?
|
||||
/// fn fn2() -> u16 {
|
||||
/// 1
|
||||
/// };
|
||||
/// let _ = fn2() as usize;
|
||||
///
|
||||
/// // Good: maybe you intended to cast it to a function type?
|
||||
/// fn fn3() -> u16 {
|
||||
/// 1
|
||||
/// }
|
||||
/// let _ = fn3 as fn() -> u16;
|
||||
/// ```
|
||||
pub FN_TO_NUMERIC_CAST_ANY,
|
||||
restriction,
|
||||
"casting a function pointer to any integer type"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for casts of `&T` to `&mut T` anywhere in the code.
|
||||
@@ -360,6 +397,7 @@ impl_lint_pass!(Casts => [
|
||||
CAST_REF_TO_MUT,
|
||||
CAST_PTR_ALIGNMENT,
|
||||
UNNECESSARY_CAST,
|
||||
FN_TO_NUMERIC_CAST_ANY,
|
||||
FN_TO_NUMERIC_CAST,
|
||||
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
|
||||
CHAR_LIT_AS_U8,
|
||||
@@ -385,6 +423,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
||||
return;
|
||||
}
|
||||
|
||||
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
|
||||
|
||||
@@ -459,12 +459,10 @@ fn emit_branches_sharing_code_lint(
|
||||
} else {
|
||||
sm.stmt_span(block.stmts[block.stmts.len() - end_stmts].span, block.span)
|
||||
};
|
||||
let moved_end = block
|
||||
.expr
|
||||
.map_or_else(
|
||||
|| sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span),
|
||||
|expr| expr.span.source_callsite(),
|
||||
);
|
||||
let moved_end = block.expr.map_or_else(
|
||||
|| sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span),
|
||||
|expr| expr.span.source_callsite(),
|
||||
);
|
||||
|
||||
let moved_span = moved_start.to(moved_end);
|
||||
let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
|
||||
use clippy_utils::source::snippet_with_macro_callsite;
|
||||
use clippy_utils::ty::{has_drop, is_copy};
|
||||
use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
@@ -139,6 +140,13 @@ impl LateLintPass<'_> for Default {
|
||||
.fields
|
||||
.iter()
|
||||
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
|
||||
let all_fields_are_copy = variant
|
||||
.fields
|
||||
.iter()
|
||||
.all(|field| {
|
||||
is_copy(cx, cx.tcx.type_of(field.did))
|
||||
});
|
||||
if !has_drop(cx, binding_type) || all_fields_are_copy;
|
||||
then {
|
||||
(local, variant, ident.name, binding_type, expr.span)
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{
|
||||
def::Res, def_id::DefId, Item, ItemKind, PolyTraitRef, PrimTy, TraitBoundModifier, Ty, TyKind, UseKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_span::Span;
|
||||
|
||||
use crate::utils::conf;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -19,7 +21,15 @@ declare_clippy_lint! {
|
||||
/// An example clippy.toml configuration:
|
||||
/// ```toml
|
||||
/// # clippy.toml
|
||||
/// disallowed-types = ["std::collections::BTreeMap"]
|
||||
/// disallowed-types = [
|
||||
/// # Can use a string as the path of the disallowed type.
|
||||
/// "std::collections::BTreeMap",
|
||||
/// # Can also use an inline table with a `path` key.
|
||||
/// { path = "std::net::TcpListener" },
|
||||
/// # When using an inline table, can add a `reason` for why the type
|
||||
/// # is disallowed.
|
||||
/// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
|
||||
/// ]
|
||||
/// ```
|
||||
///
|
||||
/// ```rust,ignore
|
||||
@@ -38,33 +48,30 @@ declare_clippy_lint! {
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DisallowedType {
|
||||
disallowed: FxHashSet<Vec<Symbol>>,
|
||||
def_ids: FxHashSet<DefId>,
|
||||
prim_tys: FxHashSet<PrimTy>,
|
||||
conf_disallowed: Vec<conf::DisallowedType>,
|
||||
def_ids: FxHashMap<DefId, Option<String>>,
|
||||
prim_tys: FxHashMap<PrimTy, Option<String>>,
|
||||
}
|
||||
|
||||
impl DisallowedType {
|
||||
pub fn new(disallowed: &FxHashSet<String>) -> Self {
|
||||
pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self {
|
||||
Self {
|
||||
disallowed: disallowed
|
||||
.iter()
|
||||
.map(|s| s.split("::").map(Symbol::intern).collect::<Vec<_>>())
|
||||
.collect(),
|
||||
def_ids: FxHashSet::default(),
|
||||
prim_tys: FxHashSet::default(),
|
||||
conf_disallowed,
|
||||
def_ids: FxHashMap::default(),
|
||||
prim_tys: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
|
||||
match res {
|
||||
Res::Def(_, did) => {
|
||||
if self.def_ids.contains(did) {
|
||||
emit(cx, &cx.tcx.def_path_str(*did), span);
|
||||
if let Some(reason) = self.def_ids.get(did) {
|
||||
emit(cx, &cx.tcx.def_path_str(*did), span, reason.as_deref());
|
||||
}
|
||||
},
|
||||
Res::PrimTy(prim) => {
|
||||
if self.prim_tys.contains(prim) {
|
||||
emit(cx, prim.name_str(), span);
|
||||
if let Some(reason) = self.prim_tys.get(prim) {
|
||||
emit(cx, prim.name_str(), span, reason.as_deref());
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
@@ -76,14 +83,21 @@ impl_lint_pass!(DisallowedType => [DISALLOWED_TYPE]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for DisallowedType {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
for path in &self.disallowed {
|
||||
let segs = path.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
match clippy_utils::path_to_res(cx, &segs.iter().map(String::as_str).collect::<Vec<_>>()) {
|
||||
for conf in &self.conf_disallowed {
|
||||
let (path, reason) = match conf {
|
||||
conf::DisallowedType::Simple(path) => (path, None),
|
||||
conf::DisallowedType::WithReason { path, reason } => (
|
||||
path,
|
||||
reason.as_ref().map(|reason| format!("{} (from clippy.toml)", reason)),
|
||||
),
|
||||
};
|
||||
let segs: Vec<_> = path.split("::").collect();
|
||||
match clippy_utils::path_to_res(cx, &segs) {
|
||||
Res::Def(_, id) => {
|
||||
self.def_ids.insert(id);
|
||||
self.def_ids.insert(id, reason);
|
||||
},
|
||||
Res::PrimTy(ty) => {
|
||||
self.prim_tys.insert(ty);
|
||||
self.prim_tys.insert(ty, reason);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
@@ -107,11 +121,16 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedType {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit(cx: &LateContext<'_>, name: &str, span: Span) {
|
||||
span_lint(
|
||||
fn emit(cx: &LateContext<'_>, name: &str, span: Span, reason: Option<&str>) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
DISALLOWED_TYPE,
|
||||
span,
|
||||
&format!("`{}` is not allowed according to config", name),
|
||||
|diag| {
|
||||
if let Some(reason) = reason {
|
||||
diag.note(reason);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note};
|
||||
use clippy_utils::source::first_line_of_span;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
@@ -297,6 +298,17 @@ fn lint_for_missing_headers<'tcx>(
|
||||
if !cx.access_levels.is_exported(def_id) {
|
||||
return; // Private functions do not require doc comments
|
||||
}
|
||||
|
||||
// do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
|
||||
if cx
|
||||
.tcx
|
||||
.hir()
|
||||
.parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id))
|
||||
.any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
|
||||
span_lint(
|
||||
cx,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, in_macro, is_expn_of};
|
||||
use clippy_utils::{
|
||||
ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, in_macro, is_expn_of, is_in_test_function,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
|
||||
@@ -81,7 +83,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
||||
if macro_args.len() == 2;
|
||||
let (lhs, rhs) = (macro_args[0], macro_args[1]);
|
||||
if eq_expr_value(cx, lhs, rhs);
|
||||
|
||||
if !is_in_test_function(cx.tcx, e.hir_id);
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
@@ -108,7 +110,10 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
|
||||
if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
|
||||
return;
|
||||
}
|
||||
if is_useless_with_eq_exprs(op.node.into()) && eq_expr_value(cx, left, right) {
|
||||
if is_useless_with_eq_exprs(op.node.into())
|
||||
&& eq_expr_value(cx, left, right)
|
||||
&& !is_in_test_function(cx.tcx, e.hir_id)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
EQ_OP,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
@@ -77,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for PatternEquality {
|
||||
let pat_str = match pat.kind {
|
||||
PatKind::Struct(..) => format!(
|
||||
"({})",
|
||||
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
|
||||
snippet_with_context(cx, pat.span, expr.span.ctxt(), "..", &mut applicability).0,
|
||||
),
|
||||
_ => snippet_with_applicability(cx, pat.span, "..", &mut applicability).to_string(),
|
||||
_ => snippet_with_context(cx, pat.span, expr.span.ctxt(), "..", &mut applicability).0.to_string(),
|
||||
};
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
@@ -89,7 +89,7 @@ impl<'tcx> LateLintPass<'tcx> for PatternEquality {
|
||||
"try",
|
||||
format!(
|
||||
"{} == {}",
|
||||
snippet_with_applicability(cx, exp.span, "..", &mut applicability),
|
||||
snippet_with_context(cx, exp.span, expr.span.ctxt(), "..", &mut applicability).0,
|
||||
pat_str,
|
||||
),
|
||||
applicability,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher::FormatExpn;
|
||||
use clippy_utils::last_path_segment;
|
||||
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, QPath};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@@ -69,8 +68,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
||||
ty::Str => true,
|
||||
_ => false,
|
||||
};
|
||||
if format_args.args.iter().all(is_display_arg);
|
||||
if format_args.fmt_expr.map_or(true, check_unformatted);
|
||||
if let Some(args) = format_args.args();
|
||||
if args.iter().all(|arg| arg.is_display() && !arg.has_string_formatting());
|
||||
then {
|
||||
let is_new_string = match value.kind {
|
||||
ExprKind::Binary(..) => true,
|
||||
@@ -101,47 +100,3 @@ fn span_useless_format(cx: &LateContext<'_>, span: Span, sugg: String, applicabi
|
||||
applicability,
|
||||
);
|
||||
}
|
||||
|
||||
fn is_display_arg(expr: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(_, [_, fmt]) = expr.kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, path)) = fmt.kind;
|
||||
if let [.., t, _] = path.segments;
|
||||
if t.ident.name == sym::Display;
|
||||
then { true } else { false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the expression matches
|
||||
/// ```rust,ignore
|
||||
/// &[_ {
|
||||
/// format: _ {
|
||||
/// width: _::Implied,
|
||||
/// precision: _::Implied,
|
||||
/// ...
|
||||
/// },
|
||||
/// ...,
|
||||
/// }]
|
||||
/// ```
|
||||
fn check_unformatted(expr: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
|
||||
if let ExprKind::Array([expr]) = expr.kind;
|
||||
// struct `core::fmt::rt::v1::Argument`
|
||||
if let ExprKind::Struct(_, fields, _) = expr.kind;
|
||||
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
|
||||
// struct `core::fmt::rt::v1::FormatSpec`
|
||||
if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
|
||||
if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
|
||||
if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
|
||||
if last_path_segment(precision_path).ident.name == sym::Implied;
|
||||
if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym::width);
|
||||
if let ExprKind::Path(ref width_qpath) = width_field.expr.kind;
|
||||
if last_path_segment(width_qpath).ident.name == sym::Implied;
|
||||
then {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
223
clippy_lints/src/format_args.rs
Normal file
223
clippy_lints/src/format_args.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{is_diag_trait_item, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detects `format!` within the arguments of another macro that does
|
||||
/// formatting such as `format!` itself, `write!` or `println!`. Suggests
|
||||
/// inlining the `format!` call.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The recommended code is both shorter and avoids a temporary allocation.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # use std::panic::Location;
|
||||
/// println!("error: {}", format!("something failed at {}", Location::caller()));
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # use std::panic::Location;
|
||||
/// println!("error: something failed at {}", Location::caller());
|
||||
/// ```
|
||||
pub FORMAT_IN_FORMAT_ARGS,
|
||||
perf,
|
||||
"`format!` used in a macro that does formatting"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
|
||||
/// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
|
||||
/// in a macro that does formatting.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Since the type implements `Display`, the use of `to_string` is
|
||||
/// unnecessary.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # use std::panic::Location;
|
||||
/// println!("error: something failed at {}", Location::caller().to_string());
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # use std::panic::Location;
|
||||
/// println!("error: something failed at {}", Location::caller());
|
||||
/// ```
|
||||
pub TO_STRING_IN_FORMAT_ARGS,
|
||||
perf,
|
||||
"`to_string` applied to a type that implements `Display` in format args"
|
||||
}
|
||||
|
||||
declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
|
||||
|
||||
const FORMAT_MACRO_PATHS: &[&[&str]] = &[
|
||||
&paths::FORMAT_ARGS_MACRO,
|
||||
&paths::ASSERT_EQ_MACRO,
|
||||
&paths::ASSERT_MACRO,
|
||||
&paths::ASSERT_NE_MACRO,
|
||||
&paths::EPRINT_MACRO,
|
||||
&paths::EPRINTLN_MACRO,
|
||||
&paths::PRINT_MACRO,
|
||||
&paths::PRINTLN_MACRO,
|
||||
&paths::WRITE_MACRO,
|
||||
&paths::WRITELN_MACRO,
|
||||
];
|
||||
|
||||
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro];
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if let Some(format_args) = FormatArgsExpn::parse(expr);
|
||||
let expr_expn_data = expr.span.ctxt().outer_expn_data();
|
||||
let outermost_expn_data = outermost_expn_data(expr_expn_data);
|
||||
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
|
||||
if FORMAT_MACRO_PATHS
|
||||
.iter()
|
||||
.any(|path| match_def_path(cx, macro_def_id, path))
|
||||
|| FORMAT_MACRO_DIAG_ITEMS
|
||||
.iter()
|
||||
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id));
|
||||
if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
|
||||
if let Some(args) = format_args.args();
|
||||
then {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if !arg.is_display() {
|
||||
continue;
|
||||
}
|
||||
if arg.has_string_formatting() {
|
||||
continue;
|
||||
}
|
||||
if is_aliased(&args, i) {
|
||||
continue;
|
||||
}
|
||||
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
|
||||
check_to_string_in_format_args(cx, name, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
|
||||
if expn_data.call_site.from_expansion() {
|
||||
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
|
||||
} else {
|
||||
expn_data
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
|
||||
if_chain! {
|
||||
if FormatExpn::parse(arg.value).is_some();
|
||||
if !arg.value.span.ctxt().outer_expn_data().call_site.from_expansion();
|
||||
then {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
FORMAT_IN_FORMAT_ARGS,
|
||||
trim_semicolon(cx, call_site),
|
||||
&format!("`format!` in `{}!` args", name),
|
||||
|diag| {
|
||||
diag.help(&format!(
|
||||
"combine the `format!(..)` arguments with the outer `{}!(..)` call",
|
||||
name
|
||||
));
|
||||
diag.help("or consider changing `format!` to `format_args!`");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
|
||||
let value = arg.value;
|
||||
if_chain! {
|
||||
if !value.span.from_expansion();
|
||||
if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;
|
||||
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
|
||||
if is_diag_trait_item(cx, method_def_id, sym::ToString);
|
||||
let receiver_ty = cx.typeck_results().expr_ty(receiver);
|
||||
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
|
||||
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
|
||||
then {
|
||||
let (n_needed_derefs, target) = count_needed_derefs(
|
||||
receiver_ty,
|
||||
cx.typeck_results().expr_adjustments(receiver).iter(),
|
||||
);
|
||||
if implements_trait(cx, target, display_trait_id, &[]) {
|
||||
if n_needed_derefs == 0 {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span.with_lo(receiver.span.hi()),
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
|
||||
"remove this",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TO_STRING_IN_FORMAT_ARGS,
|
||||
value.span,
|
||||
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
|
||||
"use this",
|
||||
format!("{:*>width$}{}", "", receiver_snippet, width = n_needed_derefs),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
|
||||
fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
|
||||
let value = args[i].value;
|
||||
args.iter()
|
||||
.enumerate()
|
||||
.any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
|
||||
}
|
||||
|
||||
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
|
||||
snippet_opt(cx, span).map_or(span, |snippet| {
|
||||
let snippet = snippet.trim_end_matches(';');
|
||||
span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap()))
|
||||
})
|
||||
}
|
||||
|
||||
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
|
||||
where
|
||||
I: Iterator<Item = &'tcx Adjustment<'tcx>>,
|
||||
{
|
||||
let mut n_total = 0;
|
||||
let mut n_needed = 0;
|
||||
loop {
|
||||
if let Some(Adjustment {
|
||||
kind: Adjust::Deref(overloaded_deref),
|
||||
target,
|
||||
}) = iter.next()
|
||||
{
|
||||
n_total += 1;
|
||||
if overloaded_deref.is_some() {
|
||||
n_needed = n_total;
|
||||
}
|
||||
ty = target;
|
||||
} else {
|
||||
return (n_needed, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,11 +91,9 @@ declare_clippy_lint! {
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for a [`#[must_use]`] attribute on
|
||||
/// Checks for a `#[must_use]` attribute on
|
||||
/// unit-returning functions and methods.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Unit values are useless. The attribute is likely
|
||||
/// a remnant of a refactoring that removed the return type.
|
||||
@@ -112,12 +110,10 @@ declare_clippy_lint! {
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for a [`#[must_use]`] attribute without
|
||||
/// Checks for a `#[must_use]` attribute without
|
||||
/// further information on functions and methods that return a type already
|
||||
/// marked as `#[must_use]`.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The attribute isn't needed. Not using the result
|
||||
/// will already be reported. Alternatively, one can add some text to the
|
||||
@@ -138,11 +134,9 @@ declare_clippy_lint! {
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for public functions that have no
|
||||
/// [`#[must_use]`] attribute, but return something not already marked
|
||||
/// `#[must_use]` attribute, but return something not already marked
|
||||
/// must-use, have no mutable arg and mutate no statics.
|
||||
///
|
||||
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Not bad at all, this lint just shows places where
|
||||
/// you could add the attribute.
|
||||
|
||||
@@ -66,7 +66,6 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_
|
||||
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
|
||||
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e) {
|
||||
let check = match *cx.typeck_results().expr_ty(e).kind() {
|
||||
|
||||
@@ -78,10 +78,10 @@ impl LateLintPass<'_> for IfThenPanic {
|
||||
if let Expr{kind: ExprKind::Unary(UnOp::Not, not_expr), ..} = e {
|
||||
sugg::Sugg::hir_with_applicability(cx, not_expr, "..", &mut applicability).maybe_par().to_string()
|
||||
} else {
|
||||
format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par().to_string())
|
||||
format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par())
|
||||
}
|
||||
} else {
|
||||
format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par().to_string())
|
||||
format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par())
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
|
||||
@@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
|
||||
return;
|
||||
}
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
|
||||
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
|
||||
|
||||
// Check if the conditional expression is a binary operation
|
||||
if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
|
||||
|
||||
@@ -138,10 +138,10 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
|
||||
item.span,
|
||||
&format!(
|
||||
"type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
|
||||
self_type.to_string()
|
||||
self_type
|
||||
),
|
||||
None,
|
||||
&format!("remove the inherent method from type `{}`", self_type.to_string()),
|
||||
&format!("remove the inherent method from type `{}`", self_type),
|
||||
);
|
||||
} else {
|
||||
span_lint_and_help(
|
||||
@@ -150,10 +150,10 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
|
||||
item.span,
|
||||
&format!(
|
||||
"implementation of inherent method `to_string(&self) -> String` for type `{}`",
|
||||
self_type.to_string()
|
||||
self_type
|
||||
),
|
||||
None,
|
||||
&format!("implement trait `Display` for type `{}` instead", self_type.to_string()),
|
||||
&format!("implement trait `Display` for type `{}` instead", self_type),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `let _ = <expr>`
|
||||
/// where expr is #[must_use]
|
||||
/// Checks for `let _ = <expr>` where expr is `#[must_use]`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It's better to explicitly
|
||||
/// handle the value of a #[must_use] expr
|
||||
/// It's better to explicitly handle the value of a `#[must_use]`
|
||||
/// expr
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
|
||||
@@ -60,6 +60,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(float_literal::EXCESSIVE_PRECISION),
|
||||
LintId::of(format::USELESS_FORMAT),
|
||||
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
|
||||
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
|
||||
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
|
||||
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
|
||||
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
|
||||
@@ -118,6 +120,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
|
||||
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
|
||||
LintId::of(match_result_ok::MATCH_RESULT_OK),
|
||||
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
|
||||
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
|
||||
LintId::of(matches::MATCH_AS_REF),
|
||||
LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
|
||||
@@ -265,6 +268,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
|
||||
LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
|
||||
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
|
||||
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
|
||||
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
|
||||
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
||||
LintId::of(transmute::WRONG_TRANSMUTE),
|
||||
@@ -277,6 +281,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
||||
LintId::of(types::VEC_BOX),
|
||||
LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
|
||||
LintId::of(unicode::INVISIBLE_CHARACTERS),
|
||||
LintId::of(uninit_vec::UNINIT_VEC),
|
||||
LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
|
||||
LintId::of(unit_types::UNIT_ARG),
|
||||
LintId::of(unit_types::UNIT_CMP),
|
||||
|
||||
@@ -82,6 +82,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
|
||||
LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
|
||||
LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
|
||||
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
|
||||
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
|
||||
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
|
||||
LintId::of(types::BORROWED_BOX),
|
||||
LintId::of(types::TYPE_COMPLEXITY),
|
||||
|
||||
@@ -36,6 +36,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
||||
LintId::of(loops::ITER_NEXT_LOOP),
|
||||
LintId::of(loops::NEVER_LOOP),
|
||||
LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
|
||||
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
|
||||
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
|
||||
LintId::of(methods::CLONE_DOUBLE_REF),
|
||||
LintId::of(methods::ITERATOR_STEP_BY_ZERO),
|
||||
@@ -62,6 +63,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
||||
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
||||
LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
|
||||
LintId::of(unicode::INVISIBLE_CHARACTERS),
|
||||
LintId::of(uninit_vec::UNINIT_VEC),
|
||||
LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
|
||||
LintId::of(unit_types::UNIT_CMP),
|
||||
LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),
|
||||
|
||||
@@ -67,6 +67,7 @@ store.register_lints(&[
|
||||
casts::CAST_SIGN_LOSS,
|
||||
casts::CHAR_LIT_AS_U8,
|
||||
casts::FN_TO_NUMERIC_CAST,
|
||||
casts::FN_TO_NUMERIC_CAST_ANY,
|
||||
casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
|
||||
casts::PTR_AS_PTR,
|
||||
casts::UNNECESSARY_CAST,
|
||||
@@ -138,6 +139,8 @@ store.register_lints(&[
|
||||
floating_point_arithmetic::IMPRECISE_FLOPS,
|
||||
floating_point_arithmetic::SUBOPTIMAL_FLOPS,
|
||||
format::USELESS_FORMAT,
|
||||
format_args::FORMAT_IN_FORMAT_ARGS,
|
||||
format_args::TO_STRING_IN_FORMAT_ARGS,
|
||||
formatting::POSSIBLE_MISSING_COMMA,
|
||||
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
|
||||
formatting::SUSPICIOUS_ELSE_FORMATTING,
|
||||
@@ -225,6 +228,7 @@ store.register_lints(&[
|
||||
map_unit_fn::RESULT_MAP_UNIT_FN,
|
||||
match_on_vec_items::MATCH_ON_VEC_ITEMS,
|
||||
match_result_ok::MATCH_RESULT_OK,
|
||||
match_str_case_mismatch::MATCH_STR_CASE_MISMATCH,
|
||||
matches::INFALLIBLE_DESTRUCTURING_MATCH,
|
||||
matches::MATCH_AS_REF,
|
||||
matches::MATCH_BOOL,
|
||||
@@ -359,6 +363,7 @@ store.register_lints(&[
|
||||
neg_multiply::NEG_MULTIPLY,
|
||||
new_without_default::NEW_WITHOUT_DEFAULT,
|
||||
no_effect::NO_EFFECT,
|
||||
no_effect::NO_EFFECT_UNDERSCORE_BINDING,
|
||||
no_effect::UNNECESSARY_OPERATION,
|
||||
non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
|
||||
non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
|
||||
@@ -438,6 +443,7 @@ store.register_lints(&[
|
||||
temporary_assignment::TEMPORARY_ASSIGNMENT,
|
||||
to_digit_is_some::TO_DIGIT_IS_SOME,
|
||||
to_string_in_display::TO_STRING_IN_DISPLAY,
|
||||
trailing_empty_array::TRAILING_EMPTY_ARRAY,
|
||||
trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
|
||||
trait_bounds::TYPE_REPETITION_IN_BOUNDS,
|
||||
transmute::CROSSPOINTER_TRANSMUTE,
|
||||
@@ -447,6 +453,7 @@ store.register_lints(&[
|
||||
transmute::TRANSMUTE_INT_TO_BOOL,
|
||||
transmute::TRANSMUTE_INT_TO_CHAR,
|
||||
transmute::TRANSMUTE_INT_TO_FLOAT,
|
||||
transmute::TRANSMUTE_NUM_TO_BYTES,
|
||||
transmute::TRANSMUTE_PTR_TO_PTR,
|
||||
transmute::TRANSMUTE_PTR_TO_REF,
|
||||
transmute::UNSOUND_COLLECTION_TRANSMUTE,
|
||||
@@ -463,10 +470,12 @@ store.register_lints(&[
|
||||
types::REDUNDANT_ALLOCATION,
|
||||
types::TYPE_COMPLEXITY,
|
||||
types::VEC_BOX,
|
||||
undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
|
||||
undropped_manually_drops::UNDROPPED_MANUALLY_DROPS,
|
||||
unicode::INVISIBLE_CHARACTERS,
|
||||
unicode::NON_ASCII_LITERAL,
|
||||
unicode::UNICODE_NOT_NFC,
|
||||
uninit_vec::UNINIT_VEC,
|
||||
unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
|
||||
unit_types::LET_UNIT_VALUE,
|
||||
unit_types::UNIT_ARG,
|
||||
|
||||
@@ -25,6 +25,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
|
||||
LintId::of(regex::TRIVIAL_REGEX),
|
||||
LintId::of(strings::STRING_LIT_AS_BYTES),
|
||||
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
|
||||
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
|
||||
LintId::of(transmute::USELESS_TRANSMUTE),
|
||||
LintId::of(use_self::USE_SELF),
|
||||
])
|
||||
|
||||
@@ -72,6 +72,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
|
||||
LintId::of(needless_continue::NEEDLESS_CONTINUE),
|
||||
LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
|
||||
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
|
||||
LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
|
||||
LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
|
||||
LintId::of(non_expressive_names::SIMILAR_NAMES),
|
||||
LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
|
||||
LintId::of(entry::MAP_ENTRY),
|
||||
LintId::of(escape::BOXED_LOCAL),
|
||||
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
|
||||
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
|
||||
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
|
||||
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
|
||||
LintId::of(loops::MANUAL_MEMCPY),
|
||||
|
||||
@@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
||||
LintId::of(as_conversions::AS_CONVERSIONS),
|
||||
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
|
||||
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
|
||||
LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
|
||||
LintId::of(create_dir::CREATE_DIR),
|
||||
LintId::of(dbg_macro::DBG_MACRO),
|
||||
LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
|
||||
@@ -56,6 +57,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
||||
LintId::of(strings::STR_TO_STRING),
|
||||
LintId::of(types::RC_BUFFER),
|
||||
LintId::of(types::RC_MUTEX),
|
||||
LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),
|
||||
LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS),
|
||||
LintId::of(unwrap_in_result::UNWRAP_IN_RESULT),
|
||||
LintId::of(verbose_file_reads::VERBOSE_FILE_READS),
|
||||
|
||||
@@ -218,6 +218,7 @@ mod float_equality_without_abs;
|
||||
mod float_literal;
|
||||
mod floating_point_arithmetic;
|
||||
mod format;
|
||||
mod format_args;
|
||||
mod formatting;
|
||||
mod from_over_into;
|
||||
mod from_str_radix_10;
|
||||
@@ -265,6 +266,7 @@ mod map_err_ignore;
|
||||
mod map_unit_fn;
|
||||
mod match_on_vec_items;
|
||||
mod match_result_ok;
|
||||
mod match_str_case_mismatch;
|
||||
mod matches;
|
||||
mod mem_forget;
|
||||
mod mem_replace;
|
||||
@@ -352,13 +354,16 @@ mod tabs_in_doc_comments;
|
||||
mod temporary_assignment;
|
||||
mod to_digit_is_some;
|
||||
mod to_string_in_display;
|
||||
mod trailing_empty_array;
|
||||
mod trait_bounds;
|
||||
mod transmute;
|
||||
mod transmuting_null;
|
||||
mod try_err;
|
||||
mod types;
|
||||
mod undocumented_unsafe_blocks;
|
||||
mod undropped_manually_drops;
|
||||
mod unicode;
|
||||
mod uninit_vec;
|
||||
mod unit_return_expecting_ord;
|
||||
mod unit_types;
|
||||
mod unnamed_address;
|
||||
@@ -516,6 +521,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
|
||||
store.register_late_pass(|| Box::new(collapsible_match::CollapsibleMatch));
|
||||
store.register_late_pass(|| Box::new(unicode::Unicode));
|
||||
store.register_late_pass(|| Box::new(uninit_vec::UninitVec));
|
||||
store.register_late_pass(|| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd));
|
||||
store.register_late_pass(|| Box::new(strings::StringAdd));
|
||||
store.register_late_pass(|| Box::new(implicit_return::ImplicitReturn));
|
||||
@@ -754,8 +760,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
|
||||
store.register_early_pass(move || Box::new(module_style::ModStyle));
|
||||
store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
|
||||
let disallowed_types = conf.disallowed_types.iter().cloned().collect::<FxHashSet<_>>();
|
||||
store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(&disallowed_types)));
|
||||
let disallowed_types = conf.disallowed_types.clone();
|
||||
store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(disallowed_types.clone())));
|
||||
let import_renames = conf.enforced_import_renames.clone();
|
||||
store.register_late_pass(move || Box::new(missing_enforced_import_rename::ImportRename::new(import_renames.clone())));
|
||||
let scripts = conf.allowed_scripts.clone();
|
||||
@@ -767,6 +773,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(move || Box::new(if_then_panic::IfThenPanic));
|
||||
let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
|
||||
store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
|
||||
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
|
||||
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
|
||||
store.register_late_pass(move || Box::new(format_args::FormatArgs));
|
||||
store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
|
||||
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
||||
@@ -204,11 +204,8 @@ struct MinifyingSugg<'a>(Sugg<'a>);
|
||||
|
||||
impl<'a> MinifyingSugg<'a> {
|
||||
fn as_str(&self) -> &str {
|
||||
// HACK: Don't sync to Clippy! Required because something with the `or_patterns` feature
|
||||
// changed and this would now require parentheses.
|
||||
match &self.0 {
|
||||
Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) => s.as_ref(),
|
||||
}
|
||||
let (Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s)) = &self.0;
|
||||
s.as_ref()
|
||||
}
|
||||
|
||||
fn into_sugg(self) -> Sugg<'a> {
|
||||
|
||||
@@ -35,7 +35,8 @@ struct PathAndSpan {
|
||||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
||||
@@ -35,7 +35,7 @@ declare_clippy_lint! {
|
||||
/// }
|
||||
///
|
||||
/// if let Ok(value) = iter.next() {
|
||||
/// vec.push_value)
|
||||
/// vec.push(value)
|
||||
/// }
|
||||
/// ```
|
||||
pub MATCH_RESULT_OK,
|
||||
|
||||
171
clippy_lints/src/match_str_case_mismatch.rs
Normal file
171
clippy_lints/src/match_str_case_mismatch.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::SymbolStr;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `match` expressions modifying the case of a string with non-compliant arms
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The arm is unreachable, which is likely a mistake
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let text = "Foo";
|
||||
///
|
||||
/// match &*text.to_ascii_lowercase() {
|
||||
/// "foo" => {},
|
||||
/// "Bar" => {},
|
||||
/// _ => {},
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let text = "Foo";
|
||||
///
|
||||
/// match &*text.to_ascii_lowercase() {
|
||||
/// "foo" => {},
|
||||
/// "bar" => {},
|
||||
/// _ => {},
|
||||
/// }
|
||||
/// ```
|
||||
pub MATCH_STR_CASE_MISMATCH,
|
||||
correctness,
|
||||
"creation of a case altering match expression with non-compliant arms"
|
||||
}
|
||||
|
||||
declare_lint_pass!(MatchStrCaseMismatch => [MATCH_STR_CASE_MISMATCH]);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CaseMethod {
|
||||
LowerCase,
|
||||
AsciiLowerCase,
|
||||
UpperCase,
|
||||
AsciiUppercase,
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for MatchStrCaseMismatch {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if !in_external_macro(cx.tcx.sess, expr.span);
|
||||
if let ExprKind::Match(match_expr, arms, MatchSource::Normal) = expr.kind;
|
||||
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(match_expr).kind();
|
||||
if let ty::Str = ty.kind();
|
||||
then {
|
||||
let mut visitor = MatchExprVisitor {
|
||||
cx,
|
||||
case_method: None,
|
||||
};
|
||||
|
||||
visitor.visit_expr(match_expr);
|
||||
|
||||
if let Some(case_method) = visitor.case_method {
|
||||
if let Some((bad_case_span, bad_case_str)) = verify_case(&case_method, arms) {
|
||||
lint(cx, &case_method, bad_case_span, &bad_case_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MatchExprVisitor<'a, 'tcx> {
|
||||
cx: &'a LateContext<'tcx>,
|
||||
case_method: Option<CaseMethod>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for MatchExprVisitor<'a, 'tcx> {
|
||||
type Map = Map<'tcx>;
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
|
||||
match ex.kind {
|
||||
ExprKind::MethodCall(segment, _, [receiver], _)
|
||||
if self.case_altered(&*segment.ident.as_str(), receiver) => {},
|
||||
_ => walk_expr(self, ex),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MatchExprVisitor<'a, 'tcx> {
|
||||
fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool {
|
||||
if let Some(case_method) = get_case_method(segment_ident) {
|
||||
let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();
|
||||
|
||||
if is_type_diagnostic_item(self.cx, ty, sym::String) || ty.kind() == &ty::Str {
|
||||
self.case_method = Some(case_method);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> {
|
||||
match segment_ident_str {
|
||||
"to_lowercase" => Some(CaseMethod::LowerCase),
|
||||
"to_ascii_lowercase" => Some(CaseMethod::AsciiLowerCase),
|
||||
"to_uppercase" => Some(CaseMethod::UpperCase),
|
||||
"to_ascii_uppercase" => Some(CaseMethod::AsciiUppercase),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(Span, SymbolStr)> {
|
||||
let case_check = match case_method {
|
||||
CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(char::is_lowercase) },
|
||||
CaseMethod::AsciiLowerCase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'a'..='z')) },
|
||||
CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(char::is_uppercase) },
|
||||
CaseMethod::AsciiUppercase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'A'..='Z')) },
|
||||
};
|
||||
|
||||
for arm in arms {
|
||||
if_chain! {
|
||||
if let PatKind::Lit(Expr {
|
||||
kind: ExprKind::Lit(lit),
|
||||
..
|
||||
}) = arm.pat.kind;
|
||||
if let LitKind::Str(symbol, _) = lit.node;
|
||||
let input = symbol.as_str();
|
||||
if !case_check(&input);
|
||||
then {
|
||||
return Some((lit.span, input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad_case_str: &str) {
|
||||
let (method_str, suggestion) = match case_method {
|
||||
CaseMethod::LowerCase => ("to_lower_case", bad_case_str.to_lowercase()),
|
||||
CaseMethod::AsciiLowerCase => ("to_ascii_lowercase", bad_case_str.to_ascii_lowercase()),
|
||||
CaseMethod::UpperCase => ("to_uppercase", bad_case_str.to_uppercase()),
|
||||
CaseMethod::AsciiUppercase => ("to_ascii_uppercase", bad_case_str.to_ascii_uppercase()),
|
||||
};
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
MATCH_STR_CASE_MISMATCH,
|
||||
bad_case_span,
|
||||
"this `match` arm has a differing case than its expression",
|
||||
&*format!("consider changing the case of this arm to respect `{}`", method_str),
|
||||
format!("\"{}\"", suggestion),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
@@ -1187,7 +1187,7 @@ where
|
||||
'b: 'a,
|
||||
I: Clone + Iterator<Item = &'a Pat<'b>>,
|
||||
{
|
||||
if !has_only_ref_pats(pats.clone()) {
|
||||
if !has_multiple_ref_pats(pats.clone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1693,12 +1693,12 @@ fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotat
|
||||
None
|
||||
}
|
||||
|
||||
fn has_only_ref_pats<'a, 'b, I>(pats: I) -> bool
|
||||
fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
|
||||
where
|
||||
'b: 'a,
|
||||
I: Iterator<Item = &'a Pat<'b>>,
|
||||
{
|
||||
let mut at_least_one_is_true = false;
|
||||
let mut ref_count = 0;
|
||||
for opt in pats.map(|pat| match pat.kind {
|
||||
PatKind::Ref(..) => Some(true), // &-patterns
|
||||
PatKind::Wild => Some(false), // an "anything" wildcard is also fine
|
||||
@@ -1706,13 +1706,13 @@ where
|
||||
}) {
|
||||
if let Some(inner) = opt {
|
||||
if inner {
|
||||
at_least_one_is_true = true;
|
||||
ref_count += 1;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
at_least_one_is_true
|
||||
ref_count > 1
|
||||
}
|
||||
|
||||
pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
|
||||
|
||||
@@ -1777,14 +1777,13 @@ declare_clippy_lint! {
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// **What it does:** Checks for usages of `str::splitn(2, _)`
|
||||
/// ### What it does
|
||||
/// Checks for usages of `str::splitn(2, _)`
|
||||
///
|
||||
/// **Why is this bad?** `split_once` is both clearer in intent and slightly more efficient.
|
||||
///
|
||||
/// **Known problems:** None.
|
||||
///
|
||||
/// **Example:**
|
||||
/// ### Why is this bad?
|
||||
/// `split_once` is both clearer in intent and slightly more efficient.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// // Bad
|
||||
/// let (key, value) = _.splitn(2, '=').next_tuple()?;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{is_expr_path_def_path, match_def_path, paths};
|
||||
use clippy_utils::{is_expr_path_def_path, paths, ty::is_uninit_value_valid_for_ty};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
|
||||
use super::UNINIT_ASSUMED_INIT;
|
||||
|
||||
@@ -13,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
|
||||
if let hir::ExprKind::Call(callee, args) = recv.kind;
|
||||
if args.is_empty();
|
||||
if is_expr_path_def_path(cx, callee, &paths::MEM_MAYBEUNINIT_UNINIT);
|
||||
if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(expr));
|
||||
if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr));
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
@@ -24,12 +23,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
|
||||
match ty.kind() {
|
||||
ty::Array(component, _) => is_maybe_uninit_ty_valid(cx, component),
|
||||
ty::Tuple(types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)),
|
||||
ty::Adt(adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use rustc_ast::ast;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty;
|
||||
@@ -56,6 +57,20 @@ impl MissingDoc {
|
||||
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
|
||||
}
|
||||
|
||||
fn has_include(meta: Option<MetaItem>) -> bool {
|
||||
if_chain! {
|
||||
if let Some(meta) = meta;
|
||||
if let MetaItemKind::List(list) = meta.kind;
|
||||
if let Some(meta) = list.get(0);
|
||||
if let Some(name) = meta.ident();
|
||||
then {
|
||||
name.name == sym::include
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_missing_docs_attrs(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
@@ -79,7 +94,9 @@ impl MissingDoc {
|
||||
return;
|
||||
}
|
||||
|
||||
let has_doc = attrs.iter().any(|a| a.doc_str().is_some());
|
||||
let has_doc = attrs
|
||||
.iter()
|
||||
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
|
||||
if !has_doc {
|
||||
span_lint(
|
||||
cx,
|
||||
|
||||
@@ -8,7 +8,7 @@ use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// it lints if an exported function, method, trait method with default impl,
|
||||
/// It lints if an exported function, method, trait method with default impl,
|
||||
/// or trait method impl is not `#[inline]`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
|
||||
@@ -82,6 +82,10 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
|
||||
if in_external_macro(self.cx.sess(), ty.span) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let hir::TyKind::Rptr(
|
||||
_,
|
||||
hir::MutTy {
|
||||
|
||||
@@ -87,10 +87,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::has_drop;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, Stmt, StmtKind, UnsafeSource};
|
||||
use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, PatKind, Stmt, StmtKind, UnsafeSource};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use std::ops::Deref;
|
||||
@@ -13,7 +14,7 @@ declare_clippy_lint! {
|
||||
/// Checks for statements which have no effect.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Similar to dead code, these statements are actually
|
||||
/// Unlike dead code, these statements are actually
|
||||
/// executed. However, as they have no effect, all they do is make the code less
|
||||
/// readable.
|
||||
///
|
||||
@@ -26,6 +27,28 @@ declare_clippy_lint! {
|
||||
"statements with no effect"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for binding to underscore prefixed variable without side-effects.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Unlike dead code, these bindings are actually
|
||||
/// executed. However, as they have no effect and shouldn't be used further on, all they
|
||||
/// do is make the code less readable.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Further usage of this variable is not checked, which can lead to false positives if it is
|
||||
/// used later in the code.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// let _i_serve_no_purpose = 1;
|
||||
/// ```
|
||||
pub NO_EFFECT_UNDERSCORE_BINDING,
|
||||
pedantic,
|
||||
"binding to `_` prefixed variable with no side-effect"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for expression statements that can be reduced to a
|
||||
@@ -44,6 +67,46 @@ declare_clippy_lint! {
|
||||
"outer expressions with no effect"
|
||||
}
|
||||
|
||||
declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NoEffect {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if check_no_effect(cx, stmt) {
|
||||
return;
|
||||
}
|
||||
check_unnecessary_operation(cx, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_no_effect(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> bool {
|
||||
if let StmtKind::Semi(expr) = stmt.kind {
|
||||
if has_no_effect(cx, expr) {
|
||||
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
|
||||
return true;
|
||||
}
|
||||
} else if let StmtKind::Local(local) = stmt.kind {
|
||||
if_chain! {
|
||||
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id);
|
||||
if let Some(init) = local.init;
|
||||
if !local.pat.span.from_expansion();
|
||||
if has_no_effect(cx, init);
|
||||
if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
|
||||
if ident.name.to_ident_string().starts_with('_');
|
||||
then {
|
||||
span_lint_hir(
|
||||
cx,
|
||||
NO_EFFECT_UNDERSCORE_BINDING,
|
||||
init.hir_id,
|
||||
stmt.span,
|
||||
"binding to `_` prefixed variable with no side-effect"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if expr.span.from_expansion() {
|
||||
return false;
|
||||
@@ -88,71 +151,59 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NoEffect {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if let StmtKind::Semi(expr) = stmt.kind {
|
||||
if has_no_effect(cx, expr) {
|
||||
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
|
||||
} else if let Some(reduced) = reduce_expression(cx, expr) {
|
||||
for e in &reduced {
|
||||
if e.span.from_expansion() {
|
||||
fn check_unnecessary_operation(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
|
||||
if_chain! {
|
||||
if let StmtKind::Semi(expr) = stmt.kind;
|
||||
if let Some(reduced) = reduce_expression(cx, expr);
|
||||
if !&reduced.iter().any(|e| e.span.from_expansion());
|
||||
then {
|
||||
if let ExprKind::Index(..) = &expr.kind {
|
||||
let snippet;
|
||||
if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) {
|
||||
snippet = format!("assert!({}.len() > {});", &arr, &func);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
UNNECESSARY_OPERATION,
|
||||
expr.hir_id,
|
||||
stmt.span,
|
||||
"unnecessary operation",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
stmt.span,
|
||||
"statement can be written as",
|
||||
snippet,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let mut snippet = String::new();
|
||||
for e in reduced {
|
||||
if let Some(snip) = snippet_opt(cx, e.span) {
|
||||
snippet.push_str(&snip);
|
||||
snippet.push(';');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let ExprKind::Index(..) = &expr.kind {
|
||||
let snippet;
|
||||
if_chain! {
|
||||
if let Some(arr) = snippet_opt(cx, reduced[0].span);
|
||||
if let Some(func) = snippet_opt(cx, reduced[1].span);
|
||||
then {
|
||||
snippet = format!("assert!({}.len() > {});", &arr, &func);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
UNNECESSARY_OPERATION,
|
||||
expr.hir_id,
|
||||
stmt.span,
|
||||
"unnecessary operation",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
stmt.span,
|
||||
"statement can be written as",
|
||||
snippet,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let mut snippet = String::new();
|
||||
for e in reduced {
|
||||
if let Some(snip) = snippet_opt(cx, e.span) {
|
||||
snippet.push_str(&snip);
|
||||
snippet.push(';');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
UNNECESSARY_OPERATION,
|
||||
expr.hir_id,
|
||||
stmt.span,
|
||||
"unnecessary operation",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
stmt.span,
|
||||
"statement can be reduced to",
|
||||
snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
span_lint_hir_and_then(
|
||||
cx,
|
||||
UNNECESSARY_OPERATION,
|
||||
expr.hir_id,
|
||||
stmt.span,
|
||||
"unnecessary operation",
|
||||
|diag| {
|
||||
diag.span_suggestion(
|
||||
stmt.span,
|
||||
"statement can be reduced to",
|
||||
snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ declare_clippy_lint! {
|
||||
/// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
|
||||
/// ```
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Good
|
||||
/// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
|
||||
/// ```
|
||||
|
||||
@@ -4,10 +4,10 @@ use clippy_utils::is_lang_ctor;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{eq_expr_value, path_to_local_id};
|
||||
use clippy_utils::{eq_expr_value, path_to_local, path_to_local_id};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
|
||||
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
@@ -48,16 +48,20 @@ impl QuestionMark {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```ignore
|
||||
/// if result.is_err() {
|
||||
/// return result;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// If it matches, it will suggest to use the question mark operator instead
|
||||
fn check_is_none_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
|
||||
if let ExprKind::MethodCall(segment, _, args, _) = &cond.kind;
|
||||
if segment.ident.name == sym!(is_none);
|
||||
if Self::expression_returns_none(cx, then);
|
||||
if let Some(subject) = args.get(0);
|
||||
if Self::is_option(cx, subject);
|
||||
|
||||
if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) ||
|
||||
(Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err));
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
|
||||
@@ -95,31 +99,24 @@ impl QuestionMark {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
|
||||
= higher::IfLet::hir(cx, expr);
|
||||
if Self::is_option(cx, let_expr);
|
||||
|
||||
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
|
||||
if is_lang_ctor(cx, path1, OptionSome);
|
||||
if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
|
||||
(Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
|
||||
|
||||
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
|
||||
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
|
||||
|
||||
if let ExprKind::Block(block, None) = if_then.kind;
|
||||
if block.stmts.is_empty();
|
||||
if let Some(trailing_expr) = &block.expr;
|
||||
if path_to_local_id(trailing_expr, bind_id);
|
||||
|
||||
if Self::expression_returns_none(cx, if_else);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
|
||||
let replacement = format!(
|
||||
"{}{}?",
|
||||
receiver_str,
|
||||
if by_ref { ".as_ref()" } else { "" },
|
||||
);
|
||||
let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
@@ -134,6 +131,14 @@ impl QuestionMark {
|
||||
}
|
||||
}
|
||||
|
||||
fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
|
||||
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
|
||||
}
|
||||
|
||||
fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
|
||||
Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
|
||||
}
|
||||
|
||||
fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expression);
|
||||
|
||||
@@ -146,6 +151,12 @@ impl QuestionMark {
|
||||
is_type_diagnostic_item(cx, expr_ty, sym::Option)
|
||||
}
|
||||
|
||||
fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
let expr_ty = cx.typeck_results().expr_ty(expression);
|
||||
|
||||
is_type_diagnostic_item(cx, expr_ty, sym::Result)
|
||||
}
|
||||
|
||||
fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
|
||||
match expression.kind {
|
||||
ExprKind::Block(block, _) => {
|
||||
@@ -161,6 +172,27 @@ impl QuestionMark {
|
||||
}
|
||||
}
|
||||
|
||||
fn expression_returns_unmodified_err(
|
||||
cx: &LateContext<'_>,
|
||||
expression: &Expr<'_>,
|
||||
origin_hir_id: &Expr<'_>,
|
||||
) -> bool {
|
||||
match expression.kind {
|
||||
ExprKind::Block(block, _) => {
|
||||
if let Some(return_expression) = Self::return_expression(block) {
|
||||
return Self::expression_returns_unmodified_err(cx, return_expression, origin_hir_id);
|
||||
}
|
||||
|
||||
false
|
||||
},
|
||||
ExprKind::Ret(Some(expr)) | ExprKind::Call(expr, _) => {
|
||||
Self::expression_returns_unmodified_err(cx, expr, origin_hir_id)
|
||||
},
|
||||
ExprKind::Path(_) => path_to_local(expression) == path_to_local(origin_hir_id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
|
||||
// Check if last expression is a return statement. Then, return the expression
|
||||
if_chain! {
|
||||
@@ -189,7 +221,7 @@ impl QuestionMark {
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
Self::check_is_none_and_early_return_none(cx, expr);
|
||||
Self::check_if_let_some_and_early_return_none(cx, expr);
|
||||
Self::check_is_none_or_err_and_early_return(cx, expr);
|
||||
Self::check_if_let_some_or_err_and_early_return(cx, expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args:
|
||||
// `.iter()` and `.len()` called on same `Path`
|
||||
if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind;
|
||||
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
|
||||
if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
|
||||
if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments);
|
||||
then {
|
||||
span_lint(cx,
|
||||
RANGE_ZIP_WITH_LEN,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::rustc_lint::LintContext;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_macro_callsite;
|
||||
use clippy_utils::{in_macro, sugg};
|
||||
use clippy_utils::sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Block, ExprKind};
|
||||
@@ -39,7 +39,7 @@ declare_lint_pass!(SemicolonIfNothingReturned => [SEMICOLON_IF_NOTHING_RETURNED]
|
||||
impl LateLintPass<'_> for SemicolonIfNothingReturned {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
|
||||
if_chain! {
|
||||
if !in_macro(block.span);
|
||||
if !block.span.from_expansion();
|
||||
if let Some(expr) = block.expr;
|
||||
let t_expr = cx.typeck_results().expr_ty(expr);
|
||||
if t_expr.is_unit();
|
||||
|
||||
@@ -162,11 +162,7 @@ fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span)
|
||||
(SHADOW_SAME, msg)
|
||||
},
|
||||
Some(expr) if is_local_used(cx, expr, shadowed) => {
|
||||
let msg = format!(
|
||||
"`{}` is shadowed by `{}` which reuses the original value",
|
||||
snippet(cx, pat.span, "_"),
|
||||
snippet(cx, expr.span, "..")
|
||||
);
|
||||
let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
|
||||
(SHADOW_REUSE, msg)
|
||||
},
|
||||
_ => {
|
||||
|
||||
@@ -678,7 +678,7 @@ fn suggestion_with_swapped_ident(
|
||||
Some(format!(
|
||||
"{}{}{}",
|
||||
snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability),
|
||||
new_ident.to_string(),
|
||||
new_ident,
|
||||
snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability),
|
||||
))
|
||||
})
|
||||
|
||||
77
clippy_lints/src/trailing_empty_array.rs
Normal file
77
clippy_lints/src/trailing_empty_array.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_hir::{HirId, Item, ItemKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty::Const;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct RarelyUseful {
|
||||
/// some_field: u32,
|
||||
/// last: [u32; 0],
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// #[repr(C)]
|
||||
/// struct MoreOftenUseful {
|
||||
/// some_field: usize,
|
||||
/// last: [u32; 0],
|
||||
/// }
|
||||
/// ```
|
||||
pub TRAILING_EMPTY_ARRAY,
|
||||
nursery,
|
||||
"struct with a trailing zero-sized array but without `#[repr(C)]` or another `repr` attribute"
|
||||
}
|
||||
declare_lint_pass!(TrailingEmptyArray => [TRAILING_EMPTY_ARRAY]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for TrailingEmptyArray {
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
|
||||
if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_attr(cx, item.hir_id()) {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
TRAILING_EMPTY_ARRAY,
|
||||
item.span,
|
||||
"trailing zero-sized array in a struct which is not marked with a `repr` attribute",
|
||||
None,
|
||||
&format!(
|
||||
"consider annotating `{}` with `#[repr(C)]` or another `repr` attribute",
|
||||
cx.tcx.def_path_str(item.def_id.to_def_id())
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
|
||||
if_chain! {
|
||||
// First check if last field is an array
|
||||
if let ItemKind::Struct(data, _) = &item.kind;
|
||||
if let Some(last_field) = data.fields().last();
|
||||
if let rustc_hir::TyKind::Array(_, length) = last_field.ty.kind;
|
||||
|
||||
// Then check if that that array zero-sized
|
||||
let length_ldid = cx.tcx.hir().local_def_id(length.hir_id);
|
||||
let length = Const::from_anon_const(cx.tcx, length_ldid);
|
||||
let length = length.try_eval_usize(cx.tcx, cx.param_env);
|
||||
if let Some(length) = length;
|
||||
then {
|
||||
length == 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
|
||||
cx.tcx.hir().attrs(hir_id).iter().any(|attr| attr.has_name(sym::repr))
|
||||
}
|
||||
@@ -3,6 +3,7 @@ mod transmute_float_to_int;
|
||||
mod transmute_int_to_bool;
|
||||
mod transmute_int_to_char;
|
||||
mod transmute_int_to_float;
|
||||
mod transmute_num_to_bytes;
|
||||
mod transmute_ptr_to_ptr;
|
||||
mod transmute_ptr_to_ref;
|
||||
mod transmute_ref_to_ref;
|
||||
@@ -261,6 +262,28 @@ declare_clippy_lint! {
|
||||
"transmutes from a float to an integer"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for transmutes from a number to an array of `u8`
|
||||
///
|
||||
/// ### Why this is bad?
|
||||
/// Transmutes are dangerous and error-prone, whereas `to_ne_bytes`
|
||||
/// is intuitive and safe.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// unsafe {
|
||||
/// let x: [u8; 8] = std::mem::transmute(1i64);
|
||||
/// }
|
||||
///
|
||||
/// // should be
|
||||
/// let x: [u8; 8] = 0i64.to_ne_bytes();
|
||||
/// ```
|
||||
pub TRANSMUTE_NUM_TO_BYTES,
|
||||
complexity,
|
||||
"transmutes from a number to an array of `u8`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for transmutes from a pointer to a pointer, or
|
||||
@@ -330,6 +353,7 @@ declare_lint_pass!(Transmute => [
|
||||
TRANSMUTE_INT_TO_BOOL,
|
||||
TRANSMUTE_INT_TO_FLOAT,
|
||||
TRANSMUTE_FLOAT_TO_INT,
|
||||
TRANSMUTE_NUM_TO_BYTES,
|
||||
UNSOUND_COLLECTION_TRANSMUTE,
|
||||
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
|
||||
]);
|
||||
@@ -365,6 +389,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
||||
linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args);
|
||||
linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty);
|
||||
|
||||
if !linted {
|
||||
|
||||
@@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"consider using",
|
||||
format!("std::char::from_u32({}).unwrap()", arg.to_string()),
|
||||
format!("std::char::from_u32({}).unwrap()", arg),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"consider using",
|
||||
format!("{}::from_bits({})", to_ty, arg.to_string()),
|
||||
format!("{}::from_bits({})", to_ty, arg),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
},
|
||||
|
||||
49
clippy_lints/src/transmute/transmute_num_to_bytes.rs
Normal file
49
clippy_lints/src/transmute/transmute_num_to_bytes.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use super::TRANSMUTE_NUM_TO_BYTES;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::sugg;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty, UintTy};
|
||||
|
||||
/// Checks for `transmute_int_to_float` lint.
|
||||
/// Returns `true` if it's triggered, otherwise returns `false`.
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
const_context: bool,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::Int(_) | ty::Uint(_) | ty::Float(_), ty::Array(arr_ty, _)) => {
|
||||
if !matches!(arr_ty.kind(), ty::Uint(UintTy::U8)) {
|
||||
return false;
|
||||
}
|
||||
if matches!(from_ty.kind(), ty::Float(_)) && const_context {
|
||||
// TODO: Remove when const_float_bits_conv is stabilized
|
||||
// rust#72447
|
||||
return false;
|
||||
}
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
TRANSMUTE_NUM_TO_BYTES,
|
||||
e.span,
|
||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"consider using `to_ne_bytes()`",
|
||||
format!("{}.to_ne_bytes()", arg),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
},
|
||||
);
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
225
clippy_lints/src/undocumented_unsafe_blocks.rs
Normal file
225
clippy_lints/src/undocumented_unsafe_blocks.rs
Normal file
@@ -0,0 +1,225 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
|
||||
use clippy_utils::{in_macro, is_lint_allowed};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
|
||||
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource};
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{BytePos, Span};
|
||||
use std::borrow::Cow;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `unsafe` blocks without a `// Safety: ` comment
|
||||
/// explaining why the unsafe operations performed inside
|
||||
/// the block are safe.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Undocumented unsafe blocks can make it difficult to
|
||||
/// read and maintain code, as well as uncover unsoundness
|
||||
/// and bugs.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// use std::ptr::NonNull;
|
||||
/// let a = &mut 42;
|
||||
///
|
||||
/// let ptr = unsafe { NonNull::new_unchecked(a) };
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// use std::ptr::NonNull;
|
||||
/// let a = &mut 42;
|
||||
///
|
||||
/// // Safety: references are guaranteed to be non-null.
|
||||
/// let ptr = unsafe { NonNull::new_unchecked(a) };
|
||||
/// ```
|
||||
pub UNDOCUMENTED_UNSAFE_BLOCKS,
|
||||
restriction,
|
||||
"creating an unsafe block without explaining why it is safe"
|
||||
}
|
||||
|
||||
impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UndocumentedUnsafeBlocks {
|
||||
pub local_level: u32,
|
||||
pub local_span: Option<Span>,
|
||||
// The local was already checked for an overall safety comment
|
||||
// There is no need to continue checking the blocks in the local
|
||||
pub local_checked: bool,
|
||||
// Since we can only check the blocks from expanded macros
|
||||
// We have to omit the suggestion due to the actual definition
|
||||
// Not being available to us
|
||||
pub macro_expansion: bool,
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
|
||||
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
|
||||
if_chain! {
|
||||
if !self.local_checked;
|
||||
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
|
||||
if !in_external_macro(cx.tcx.sess, block.span);
|
||||
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
|
||||
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
|
||||
if self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, block.span) == Some(false);
|
||||
then {
|
||||
let mut span = block.span;
|
||||
|
||||
if let Some(local_span) = self.local_span {
|
||||
span = local_span;
|
||||
|
||||
let result = self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, span);
|
||||
|
||||
if result.unwrap_or(true) {
|
||||
self.local_checked = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.lint(cx, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_local(&mut self, cx: &LateContext<'_>, local: &'_ Local<'_>) {
|
||||
if_chain! {
|
||||
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, local.hir_id);
|
||||
if !in_external_macro(cx.tcx.sess, local.span);
|
||||
if let Some(init) = local.init;
|
||||
then {
|
||||
self.visit_expr(init);
|
||||
|
||||
if self.local_level > 0 {
|
||||
self.local_span = Some(local.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_block_post(&mut self, _: &LateContext<'_>, _: &'_ Block<'_>) {
|
||||
self.local_level = self.local_level.saturating_sub(1);
|
||||
|
||||
if self.local_level == 0 {
|
||||
self.local_checked = false;
|
||||
self.local_span = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'hir> Visitor<'hir> for UndocumentedUnsafeBlocks {
|
||||
type Map = Map<'hir>;
|
||||
|
||||
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, ex: &'v Expr<'v>) {
|
||||
match ex.kind {
|
||||
ExprKind::Block(_, _) => self.local_level = self.local_level.saturating_add(1),
|
||||
_ => walk_expr(self, ex),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UndocumentedUnsafeBlocks {
|
||||
fn block_has_safety_comment(&mut self, tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<bool> {
|
||||
let map = tcx.hir();
|
||||
let source_map = tcx.sess.source_map();
|
||||
|
||||
let enclosing_scope_span = map.opt_span(enclosing_hir_id)?;
|
||||
|
||||
let between_span = if in_macro(block_span) {
|
||||
self.macro_expansion = true;
|
||||
enclosing_scope_span.with_hi(block_span.hi())
|
||||
} else {
|
||||
self.macro_expansion = false;
|
||||
enclosing_scope_span.to(block_span)
|
||||
};
|
||||
|
||||
let file_name = source_map.span_to_filename(between_span);
|
||||
let source_file = source_map.get_source_file(&file_name)?;
|
||||
|
||||
let lex_start = (between_span.lo().0 + 1) as usize;
|
||||
let src_str = source_file.src.as_ref()?[lex_start..between_span.hi().0 as usize].to_string();
|
||||
|
||||
let mut pos = 0;
|
||||
let mut comment = false;
|
||||
|
||||
for token in rustc_lexer::tokenize(&src_str) {
|
||||
match token.kind {
|
||||
TokenKind::LineComment { doc_style: None }
|
||||
| TokenKind::BlockComment {
|
||||
doc_style: None,
|
||||
terminated: true,
|
||||
} => {
|
||||
let comment_str = src_str[pos + 2..pos + token.len].to_ascii_uppercase();
|
||||
|
||||
if comment_str.contains("SAFETY:") {
|
||||
comment = true;
|
||||
}
|
||||
},
|
||||
// We need to add all whitespace to `pos` before checking the comment's line number
|
||||
TokenKind::Whitespace => {},
|
||||
_ => {
|
||||
if comment {
|
||||
// Get the line number of the "comment" (really wherever the trailing whitespace ended)
|
||||
let comment_line_num = source_file
|
||||
.lookup_file_pos_with_col_display(BytePos((lex_start + pos).try_into().unwrap()))
|
||||
.0;
|
||||
// Find the block/local's line number
|
||||
let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line;
|
||||
|
||||
// Check the comment is immediately followed by the block/local
|
||||
if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
comment = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
pos += token.len;
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
fn lint(&self, cx: &LateContext<'_>, mut span: Span) {
|
||||
let source_map = cx.tcx.sess.source_map();
|
||||
|
||||
if source_map.is_multiline(span) {
|
||||
span = source_map.span_until_char(span, '\n');
|
||||
}
|
||||
|
||||
if self.macro_expansion {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNDOCUMENTED_UNSAFE_BLOCKS,
|
||||
span,
|
||||
"unsafe block in macro expansion missing a safety comment",
|
||||
None,
|
||||
"consider adding a safety comment in the macro definition",
|
||||
);
|
||||
} else {
|
||||
let block_indent = indent_of(cx, span);
|
||||
let suggestion = format!("// Safety: ...\n{}", snippet(cx, span, ".."));
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
UNDOCUMENTED_UNSAFE_BLOCKS,
|
||||
span,
|
||||
"unsafe block missing a safety comment",
|
||||
"consider adding a safety comment",
|
||||
reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
|
||||
Applicability::HasPlaceholders,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
223
clippy_lints/src/uninit_vec.rs
Normal file
223
clippy_lints/src/uninit_vec.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
|
||||
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
|
||||
use clippy_utils::{is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
|
||||
use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
|
||||
/// This is commonly caused by calling `set_len()` right after allocating or
|
||||
/// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It creates a `Vec` with uninitialized data, which leads to
|
||||
/// undefined behavior with most safe operations. Notably, uninitialized
|
||||
/// `Vec<u8>` must not be used with generic `Read`.
|
||||
///
|
||||
/// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
|
||||
/// creates out-of-bound values that lead to heap memory corruption when used.
|
||||
///
|
||||
/// ### Known Problems
|
||||
/// This lint only checks directly adjacent statements.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust,ignore
|
||||
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
|
||||
/// unsafe { vec.set_len(1000); }
|
||||
/// reader.read(&mut vec); // undefined behavior!
|
||||
/// ```
|
||||
///
|
||||
/// ### How to fix?
|
||||
/// 1. Use an initialized buffer:
|
||||
/// ```rust,ignore
|
||||
/// let mut vec: Vec<u8> = vec![0; 1000];
|
||||
/// reader.read(&mut vec);
|
||||
/// ```
|
||||
/// 2. Wrap the content in `MaybeUninit`:
|
||||
/// ```rust,ignore
|
||||
/// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
|
||||
/// vec.set_len(1000); // `MaybeUninit` can be uninitialized
|
||||
/// ```
|
||||
/// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
|
||||
/// ```rust,ignore
|
||||
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
|
||||
/// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
|
||||
/// // perform initialization with `remaining`
|
||||
/// vec.set_len(...); // Safe to call `set_len()` on initialized part
|
||||
/// ```
|
||||
pub UNINIT_VEC,
|
||||
correctness,
|
||||
"Vec with uninitialized data"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UninitVec => [UNINIT_VEC]);
|
||||
|
||||
// FIXME: update to a visitor-based implementation.
|
||||
// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
|
||||
impl<'tcx> LateLintPass<'tcx> for UninitVec {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
|
||||
if !in_external_macro(cx.tcx.sess, block.span) {
|
||||
for w in block.stmts.windows(2) {
|
||||
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
|
||||
handle_uninit_vec_pair(cx, &w[0], expr);
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
|
||||
handle_uninit_vec_pair(cx, stmt, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_uninit_vec_pair(
|
||||
cx: &LateContext<'tcx>,
|
||||
maybe_init_or_reserve: &'tcx Stmt<'tcx>,
|
||||
maybe_set_len: &'tcx Expr<'tcx>,
|
||||
) {
|
||||
if_chain! {
|
||||
if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
|
||||
if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
|
||||
if vec.location.eq_expr(cx, set_len_self);
|
||||
if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
|
||||
if let ty::Adt(_, substs) = vec_ty.kind();
|
||||
// `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
|
||||
if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id);
|
||||
then {
|
||||
if vec.has_capacity() {
|
||||
// with_capacity / reserve -> set_len
|
||||
|
||||
// Check T of Vec<T>
|
||||
if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) {
|
||||
// FIXME: #7698, false positive of the internal lints
|
||||
#[allow(clippy::collapsible_span_lint_calls)]
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
UNINIT_VEC,
|
||||
vec![call_span, maybe_init_or_reserve.span],
|
||||
"calling `set_len()` immediately after reserving a buffer creates uninitialized values",
|
||||
|diag| {
|
||||
diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// new / default -> set_len
|
||||
span_lint(
|
||||
cx,
|
||||
UNINIT_VEC,
|
||||
vec![call_span, maybe_init_or_reserve.span],
|
||||
"calling `set_len()` on empty `Vec` creates out-of-bound values",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The target `Vec` that is initialized or reserved
|
||||
#[derive(Clone, Copy)]
|
||||
struct TargetVec<'tcx> {
|
||||
location: VecLocation<'tcx>,
|
||||
/// `None` if `reserve()`
|
||||
init_kind: Option<VecInitKind>,
|
||||
}
|
||||
|
||||
impl TargetVec<'_> {
|
||||
pub fn has_capacity(self) -> bool {
|
||||
!matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum VecLocation<'tcx> {
|
||||
Local(HirId),
|
||||
Expr(&'tcx Expr<'tcx>),
|
||||
}
|
||||
|
||||
impl<'tcx> VecLocation<'tcx> {
|
||||
pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
|
||||
match self {
|
||||
VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id),
|
||||
VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the target location where the result of `Vec` initialization is stored
|
||||
/// or `self` expression for `Vec::reserve()`.
|
||||
fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> {
|
||||
match stmt.kind {
|
||||
StmtKind::Local(local) => {
|
||||
if_chain! {
|
||||
if let Some(init_expr) = local.init;
|
||||
if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
|
||||
if let Some(init_kind) = get_vec_init_kind(cx, init_expr);
|
||||
then {
|
||||
return Some(TargetVec {
|
||||
location: VecLocation::Local(hir_id),
|
||||
init_kind: Some(init_kind),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
|
||||
ExprKind::Assign(lhs, rhs, _span) => {
|
||||
if let Some(init_kind) = get_vec_init_kind(cx, rhs) {
|
||||
return Some(TargetVec {
|
||||
location: VecLocation::Expr(lhs),
|
||||
init_kind: Some(init_kind),
|
||||
});
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
|
||||
return Some(TargetVec {
|
||||
location: VecLocation::Expr(self_expr),
|
||||
init_kind: None,
|
||||
});
|
||||
},
|
||||
_ => (),
|
||||
},
|
||||
StmtKind::Item(_) => (),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
|
||||
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
|
||||
&& path.ident.name.as_str() == "reserve"
|
||||
}
|
||||
|
||||
/// Returns self if the expression is `Vec::set_len()`
|
||||
fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> {
|
||||
// peel unsafe blocks in `unsafe { vec.set_len() }`
|
||||
let expr = peel_hir_expr_while(expr, |e| {
|
||||
if let ExprKind::Block(block, _) = e.kind {
|
||||
// Extract the first statement/expression
|
||||
match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) {
|
||||
(None, Some(expr)) => Some(expr),
|
||||
(Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
match expr.kind {
|
||||
ExprKind::MethodCall(path, _, [self_expr, _], _) => {
|
||||
let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
|
||||
if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
|
||||
Some((self_expr, expr.span))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
@@ -193,10 +193,15 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
|
||||
let vec_name = Sugg::hir(cx, &args[0], "..").to_string();
|
||||
let unstable = name == "sort_unstable_by";
|
||||
|
||||
if_chain! {
|
||||
if let ExprKind::Path(QPath::Resolved(_, Path {
|
||||
segments: [PathSegment { ident: left_name, .. }], ..
|
||||
})) = &left_expr.kind {
|
||||
if left_name == left_ident {
|
||||
})) = &left_expr.kind;
|
||||
if left_name == left_ident;
|
||||
if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| {
|
||||
implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[])
|
||||
});
|
||||
then {
|
||||
return Some(LintTrigger::Sort(SortDetection { vec_name, unstable }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ pub enum DisallowedMethod {
|
||||
WithReason { path: String, reason: Option<String> },
|
||||
}
|
||||
|
||||
/// A single disallowed type, used by the `DISALLOWED_TYPE` lint.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DisallowedType {
|
||||
Simple(String),
|
||||
WithReason { path: String, reason: Option<String> },
|
||||
}
|
||||
|
||||
/// Conf with parse errors
|
||||
#[derive(Default)]
|
||||
pub struct TryConf {
|
||||
@@ -255,7 +263,7 @@ define_Conf! {
|
||||
/// Lint: DISALLOWED_TYPE.
|
||||
///
|
||||
/// The list of disallowed types, written as fully qualified paths.
|
||||
(disallowed_types: Vec<String> = Vec::new()),
|
||||
(disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
|
||||
/// Lint: UNREADABLE_LITERAL.
|
||||
///
|
||||
/// Should the fraction of a decimal be linted to include separators.
|
||||
|
||||
@@ -770,8 +770,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
|
||||
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
|
||||
if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id();
|
||||
// Check if the matched type is a diagnostic item
|
||||
let diag_items = cx.tcx.diagnostic_items(ty_did.krate);
|
||||
if let Some(item_name) = diag_items.iter().find_map(|(k, v)| if *v == ty_did { Some(k) } else { None });
|
||||
if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
|
||||
then {
|
||||
// TODO: check paths constants from external crates.
|
||||
let cx_snippet = snippet(cx, context.span, "_");
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{match_def_path, path_to_local, path_to_local_id, paths};
|
||||
use clippy_utils::{path_to_local, path_to_local_id};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
|
||||
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{symbol::sym, Span};
|
||||
use std::convert::TryInto;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@@ -41,11 +39,6 @@ pub struct VecInitThenPush {
|
||||
searcher: Option<VecPushSearcher>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum VecInitKind {
|
||||
New,
|
||||
WithCapacity(u64),
|
||||
}
|
||||
struct VecPushSearcher {
|
||||
local_id: HirId,
|
||||
init: VecInitKind,
|
||||
@@ -58,7 +51,8 @@ impl VecPushSearcher {
|
||||
fn display_err(&self, cx: &LateContext<'_>) {
|
||||
match self.init {
|
||||
_ if self.found == 0 => return,
|
||||
VecInitKind::WithCapacity(x) if x > self.found => return,
|
||||
VecInitKind::WithLiteralCapacity(x) if x > self.found => return,
|
||||
VecInitKind::WithExprCapacity(_) => return,
|
||||
_ => (),
|
||||
};
|
||||
|
||||
@@ -152,37 +146,3 @@ impl LateLintPass<'_> for VecInitThenPush {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
|
||||
if let ExprKind::Call(func, args) = expr.kind {
|
||||
match func.kind {
|
||||
ExprKind::Path(QPath::TypeRelative(ty, name))
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
|
||||
{
|
||||
if name.ident.name == sym::new {
|
||||
return Some(VecInitKind::New);
|
||||
} else if name.ident.name.as_str() == "with_capacity" {
|
||||
return args.get(0).and_then(|arg| {
|
||||
if_chain! {
|
||||
if let ExprKind::Lit(lit) = &arg.kind;
|
||||
if let LitKind::Int(num, _) = lit.node;
|
||||
then {
|
||||
Some(VecInitKind::WithCapacity(num.try_into().ok()?))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
ExprKind::Path(QPath::Resolved(_, path))
|
||||
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
|
||||
{
|
||||
return Some(VecInitKind::New);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user