Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
669fddab37
168 changed files with 2906 additions and 1457 deletions
|
|
@ -1,7 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_hir_ty_cfg_dependant;
|
||||
use clippy_utils::ty::is_c_void;
|
||||
use if_chain::if_chain;
|
||||
use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, match_any_def_paths, paths};
|
||||
use rustc_hir::{Expr, ExprKind, GenericArg};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
|
|
@ -20,45 +19,78 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
);
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
} else if let ExprKind::MethodCall(method_path, [self_arg, ..], _) = &expr.kind {
|
||||
if_chain! {
|
||||
if method_path.ident.name == sym!(cast);
|
||||
if let Some(generic_args) = method_path.args;
|
||||
if let [GenericArg::Type(cast_to)] = generic_args.args;
|
||||
if method_path.ident.name == sym!(cast)
|
||||
&& let Some(generic_args) = method_path.args
|
||||
&& let [GenericArg::Type(cast_to)] = generic_args.args
|
||||
// There probably is no obvious reason to do this, just to be consistent with `as` cases.
|
||||
if !is_hir_ty_cfg_dependant(cx, cast_to);
|
||||
then {
|
||||
let (cast_from, cast_to) =
|
||||
(cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
}
|
||||
&& !is_hir_ty_cfg_dependant(cx, cast_to)
|
||||
{
|
||||
let (cast_from, cast_to) =
|
||||
(cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
|
||||
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) {
|
||||
if_chain! {
|
||||
if let ty::RawPtr(from_ptr_ty) = &cast_from.kind();
|
||||
if let ty::RawPtr(to_ptr_ty) = &cast_to.kind();
|
||||
if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty);
|
||||
if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty);
|
||||
if from_layout.align.abi < to_layout.align.abi;
|
||||
if let ty::RawPtr(from_ptr_ty) = &cast_from.kind()
|
||||
&& let ty::RawPtr(to_ptr_ty) = &cast_to.kind()
|
||||
&& let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty)
|
||||
&& let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty)
|
||||
&& from_layout.align.abi < to_layout.align.abi
|
||||
// with c_void, we inherently need to trust the user
|
||||
if !is_c_void(cx, from_ptr_ty.ty);
|
||||
&& !is_c_void(cx, from_ptr_ty.ty)
|
||||
// when casting from a ZST, we don't know enough to properly lint
|
||||
if !from_layout.is_zst();
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
CAST_PTR_ALIGNMENT,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
|
||||
cast_from,
|
||||
cast_to,
|
||||
from_layout.align.abi.bytes(),
|
||||
to_layout.align.abi.bytes(),
|
||||
),
|
||||
);
|
||||
}
|
||||
&& !from_layout.is_zst()
|
||||
&& !is_used_as_unaligned(cx, expr)
|
||||
{
|
||||
span_lint(
|
||||
cx,
|
||||
CAST_PTR_ALIGNMENT,
|
||||
expr.span,
|
||||
&format!(
|
||||
"casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)",
|
||||
cast_from,
|
||||
cast_to,
|
||||
from_layout.align.abi.bytes(),
|
||||
to_layout.align.abi.bytes(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
let Some(parent) = get_parent_expr(cx, e) else {
|
||||
return false;
|
||||
};
|
||||
match parent.kind {
|
||||
ExprKind::MethodCall(name, [self_arg, ..], _) if self_arg.hir_id == e.hir_id => {
|
||||
if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned")
|
||||
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
|
||||
&& let Some(def_id) = cx.tcx.impl_of_method(def_id)
|
||||
&& cx.tcx.type_of(def_id).is_unsafe_ptr()
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => {
|
||||
static PATHS: &[&[&str]] = &[
|
||||
paths::PTR_READ_UNALIGNED.as_slice(),
|
||||
paths::PTR_WRITE_UNALIGNED.as_slice(),
|
||||
paths::PTR_UNALIGNED_VOLATILE_LOAD.as_slice(),
|
||||
paths::PTR_UNALIGNED_VOLATILE_STORE.as_slice(),
|
||||
];
|
||||
if let ExprKind::Path(path) = &func.kind
|
||||
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
|
||||
&& match_any_def_paths(cx, def_id, PATHS).is_some()
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use clippy_utils::source::snippet_opt;
|
|||
use if_chain::if_chain;
|
||||
use rustc_ast::{LitFloatType, LitIntType, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Lit, UnOp};
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{Expr, ExprKind, Lit, QPath, TyKind, UnOp};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::{self, FloatTy, InferTy, Ty};
|
||||
|
|
@ -18,6 +19,17 @@ pub(super) fn check(
|
|||
cast_from: Ty<'_>,
|
||||
cast_to: Ty<'_>,
|
||||
) -> bool {
|
||||
// skip non-primitive type cast
|
||||
if_chain! {
|
||||
if let ExprKind::Cast(_, cast_to) = expr.kind;
|
||||
if let TyKind::Path(QPath::Resolved(_, path)) = &cast_to.kind;
|
||||
if let Res::PrimTy(_) = path.res;
|
||||
then {}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(lit) = get_numeric_literal(cast_expr) {
|
||||
let literal_str = snippet_opt(cx, cast_expr.span).unwrap_or_default();
|
||||
|
||||
|
|
|
|||
125
clippy_lints/src/crate_in_macro_def.rs
Normal file
125
clippy_lints/src/crate_in_macro_def.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use rustc_ast::ast::{AttrKind, Attribute, Item, ItemKind};
|
||||
use rustc_ast::token::{Token, TokenKind};
|
||||
use rustc_ast::tokenstream::{TokenStream, TokenTree};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{symbol::sym, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for use of `crate` as opposed to `$crate` in a macro definition.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `crate` refers to the macro call's crate, whereas `$crate` refers to the macro definition's
|
||||
/// crate. Rarely is the former intended. See:
|
||||
/// https://doc.rust-lang.org/reference/macros-by-example.html#hygiene
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// #[macro_export]
|
||||
/// macro_rules! print_message {
|
||||
/// () => {
|
||||
/// println!("{}", crate::MESSAGE);
|
||||
/// };
|
||||
/// }
|
||||
/// pub const MESSAGE: &str = "Hello!";
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// #[macro_export]
|
||||
/// macro_rules! print_message {
|
||||
/// () => {
|
||||
/// println!("{}", $crate::MESSAGE);
|
||||
/// };
|
||||
/// }
|
||||
/// pub const MESSAGE: &str = "Hello!";
|
||||
/// ```
|
||||
///
|
||||
/// Note that if the use of `crate` is intentional, an `allow` attribute can be applied to the
|
||||
/// macro definition, e.g.:
|
||||
/// ```rust,ignore
|
||||
/// #[allow(clippy::crate_in_macro_def)]
|
||||
/// macro_rules! ok { ... crate::foo ... }
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub CRATE_IN_MACRO_DEF,
|
||||
suspicious,
|
||||
"using `crate` in a macro definition"
|
||||
}
|
||||
declare_lint_pass!(CrateInMacroDef => [CRATE_IN_MACRO_DEF]);
|
||||
|
||||
impl EarlyLintPass for CrateInMacroDef {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
if_chain! {
|
||||
if item.attrs.iter().any(is_macro_export);
|
||||
if let ItemKind::MacroDef(macro_def) = &item.kind;
|
||||
let tts = macro_def.body.inner_tokens();
|
||||
if let Some(span) = contains_unhygienic_crate_reference(&tts);
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
CRATE_IN_MACRO_DEF,
|
||||
span,
|
||||
"`crate` references the macro call's crate",
|
||||
"to reference the macro definition's crate, use",
|
||||
String::from("$crate"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_macro_export(attr: &Attribute) -> bool {
|
||||
if_chain! {
|
||||
if let AttrKind::Normal(attr_item, _) = &attr.kind;
|
||||
if let [segment] = attr_item.path.segments.as_slice();
|
||||
then {
|
||||
segment.ident.name == sym::macro_export
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
|
||||
let mut prev_is_dollar = false;
|
||||
let mut cursor = tts.trees();
|
||||
while let Some(curr) = cursor.next() {
|
||||
if_chain! {
|
||||
if !prev_is_dollar;
|
||||
if let Some(span) = is_crate_keyword(&curr);
|
||||
if let Some(next) = cursor.look_ahead(0);
|
||||
if is_token(next, &TokenKind::ModSep);
|
||||
then {
|
||||
return Some(span);
|
||||
}
|
||||
}
|
||||
if let TokenTree::Delimited(_, _, tts) = &curr {
|
||||
let span = contains_unhygienic_crate_reference(tts);
|
||||
if span.is_some() {
|
||||
return span;
|
||||
}
|
||||
}
|
||||
prev_is_dollar = is_token(&curr, &TokenKind::Dollar);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn is_crate_keyword(tt: &TokenTree) -> Option<Span> {
|
||||
if_chain! {
|
||||
if let TokenTree::Token(Token { kind: TokenKind::Ident(symbol, _), span }) = tt;
|
||||
if symbol.as_str() == "crate";
|
||||
then { Some(*span) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
fn is_token(tt: &TokenTree, kind: &TokenKind) -> bool {
|
||||
if let TokenTree::Token(Token { kind: other, .. }) = tt {
|
||||
kind == other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -621,8 +621,8 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
|
|||
let filename = FileName::anon_source_code(&code);
|
||||
|
||||
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
|
||||
let fallback_bundle = rustc_errors::fallback_fluent_bundle(false)
|
||||
.expect("failed to load fallback fluent bundle");
|
||||
let fallback_bundle =
|
||||
rustc_errors::fallback_fluent_bundle(false).expect("failed to load fallback fluent bundle");
|
||||
let emitter = EmitterWriter::new(
|
||||
Box::new(io::sink()),
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
|
||||
use clippy_utils::is_must_use_func_call;
|
||||
use clippy_utils::ty::{is_copy, is_must_use_ty, is_type_lang_item};
|
||||
use rustc_hir::{Expr, ExprKind, LangItem};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
|
|
@ -103,6 +102,75 @@ declare_clippy_lint! {
|
|||
"calls to `std::mem::forget` with a value that implements Copy"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to `std::mem::drop` with a value that does not implement `Drop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Calling `std::mem::drop` is no different than dropping such a type. A different value may
|
||||
/// have been intended.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Foo;
|
||||
/// let x = Foo;
|
||||
/// std::mem::drop(x);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub DROP_NON_DROP,
|
||||
suspicious,
|
||||
"call to `std::mem::drop` with a value which does not implement `Drop`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for calls to `std::mem::forget` with a value that does not implement `Drop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Calling `std::mem::forget` is no different than dropping such a type. A different value may
|
||||
/// have been intended.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Foo;
|
||||
/// let x = Foo;
|
||||
/// std::mem::forget(x);
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub FORGET_NON_DROP,
|
||||
suspicious,
|
||||
"call to `std::mem::forget` with a value which does not implement `Drop`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Does not catch cases if the user binds `std::mem::drop`
|
||||
/// to a different name and calls it that way.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// drop(std::mem::ManuallyDrop::new(S));
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// unsafe {
|
||||
/// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.49.0"]
|
||||
pub UNDROPPED_MANUALLY_DROPS,
|
||||
correctness,
|
||||
"use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
|
||||
}
|
||||
|
||||
const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \
|
||||
Dropping a reference does nothing";
|
||||
const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \
|
||||
|
|
@ -111,60 +179,65 @@ const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that imp
|
|||
Dropping a copy leaves the original intact";
|
||||
const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \
|
||||
Forgetting a copy leaves the original intact";
|
||||
const DROP_NON_DROP_SUMMARY: &str = "call to `std::mem::drop` with a value that does not implement `Drop`. \
|
||||
Dropping such a type only extends it's contained lifetimes";
|
||||
const FORGET_NON_DROP_SUMMARY: &str = "call to `std::mem::forget` with a value that does not implement `Drop`. \
|
||||
Forgetting such a type is the same as dropping it";
|
||||
|
||||
declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COPY]);
|
||||
declare_lint_pass!(DropForgetRef => [
|
||||
DROP_REF,
|
||||
FORGET_REF,
|
||||
DROP_COPY,
|
||||
FORGET_COPY,
|
||||
DROP_NON_DROP,
|
||||
FORGET_NON_DROP,
|
||||
UNDROPPED_MANUALLY_DROPS
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(path, args) = expr.kind;
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if args.len() == 1;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
then {
|
||||
let lint;
|
||||
let msg;
|
||||
let arg = &args[0];
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
|
||||
if let ty::Ref(..) = arg_ty.kind() {
|
||||
match cx.tcx.get_diagnostic_name(def_id) {
|
||||
Some(sym::mem_drop) => {
|
||||
lint = DROP_REF;
|
||||
msg = DROP_REF_SUMMARY.to_string();
|
||||
},
|
||||
Some(sym::mem_forget) => {
|
||||
lint = FORGET_REF;
|
||||
msg = FORGET_REF_SUMMARY.to_string();
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
span_lint_and_note(cx,
|
||||
lint,
|
||||
expr.span,
|
||||
&msg,
|
||||
Some(arg.span),
|
||||
&format!("argument has type `{}`", arg_ty));
|
||||
} else if is_copy(cx, arg_ty) {
|
||||
match cx.tcx.get_diagnostic_name(def_id) {
|
||||
Some(sym::mem_drop) => {
|
||||
lint = DROP_COPY;
|
||||
msg = DROP_COPY_SUMMARY.to_string();
|
||||
},
|
||||
Some(sym::mem_forget) => {
|
||||
lint = FORGET_COPY;
|
||||
msg = FORGET_COPY_SUMMARY.to_string();
|
||||
},
|
||||
_ => return,
|
||||
}
|
||||
span_lint_and_note(cx,
|
||||
lint,
|
||||
expr.span,
|
||||
&msg,
|
||||
Some(arg.span),
|
||||
&format!("argument has type {}", arg_ty));
|
||||
if let ExprKind::Call(path, [arg]) = expr.kind
|
||||
&& let ExprKind::Path(ref qpath) = path.kind
|
||||
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
|
||||
&& let Some(fn_name) = cx.tcx.get_diagnostic_name(def_id)
|
||||
{
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
let (lint, msg) = match fn_name {
|
||||
sym::mem_drop if arg_ty.is_ref() => (DROP_REF, DROP_REF_SUMMARY),
|
||||
sym::mem_forget if arg_ty.is_ref() => (FORGET_REF, FORGET_REF_SUMMARY),
|
||||
sym::mem_drop if is_copy(cx, arg_ty) => (DROP_COPY, DROP_COPY_SUMMARY),
|
||||
sym::mem_forget if is_copy(cx, arg_ty) => (FORGET_COPY, FORGET_COPY_SUMMARY),
|
||||
sym::mem_drop if is_type_lang_item(cx, arg_ty, LangItem::ManuallyDrop) => {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNDROPPED_MANUALLY_DROPS,
|
||||
expr.span,
|
||||
"the inner value of this ManuallyDrop will not be dropped",
|
||||
None,
|
||||
"to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sym::mem_drop
|
||||
if !(arg_ty.needs_drop(cx.tcx, cx.param_env)
|
||||
|| is_must_use_func_call(cx, arg)
|
||||
|| is_must_use_ty(cx, arg_ty)) =>
|
||||
{
|
||||
(DROP_NON_DROP, DROP_NON_DROP_SUMMARY)
|
||||
},
|
||||
sym::mem_forget if !arg_ty.needs_drop(cx.tcx, cx.param_env) => {
|
||||
(FORGET_NON_DROP, FORGET_NON_DROP_SUMMARY)
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
span_lint_and_note(
|
||||
cx,
|
||||
lint,
|
||||
expr.span,
|
||||
msg,
|
||||
Some(arg.span),
|
||||
&format!("argument has type `{}`", arg_ty),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
99
clippy_lints/src/empty_structs_with_brackets.rs
Normal file
99
clippy_lints/src/empty_structs_with_brackets.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use clippy_utils::{diagnostics::span_lint_and_then, source::snippet_opt};
|
||||
use rustc_ast::ast::{Item, ItemKind, VariantData};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Finds structs without fields (a so-called "empty struct") that are declared with brackets.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Empty brackets after a struct declaration can be omitted.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Cookie {}
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct Cookie;
|
||||
/// ```
|
||||
#[clippy::version = "1.62.0"]
|
||||
pub EMPTY_STRUCTS_WITH_BRACKETS,
|
||||
restriction,
|
||||
"finds struct declarations with empty brackets"
|
||||
}
|
||||
declare_lint_pass!(EmptyStructsWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS]);
|
||||
|
||||
impl EarlyLintPass for EmptyStructsWithBrackets {
|
||||
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
|
||||
let span_after_ident = item.span.with_lo(item.ident.span.hi());
|
||||
|
||||
if let ItemKind::Struct(var_data, _) = &item.kind
|
||||
&& has_brackets(var_data)
|
||||
&& has_no_fields(cx, var_data, span_after_ident) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
EMPTY_STRUCTS_WITH_BRACKETS,
|
||||
span_after_ident,
|
||||
"found empty brackets on struct declaration",
|
||||
|diagnostic| {
|
||||
diagnostic.span_suggestion_hidden(
|
||||
span_after_ident,
|
||||
"remove the brackets",
|
||||
";".to_string(),
|
||||
Applicability::MachineApplicable);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_no_ident_token(braces_span_str: &str) -> bool {
|
||||
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
|
||||
}
|
||||
|
||||
fn has_brackets(var_data: &VariantData) -> bool {
|
||||
!matches!(var_data, VariantData::Unit(_))
|
||||
}
|
||||
|
||||
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
|
||||
if !var_data.fields().is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// there might still be field declarations hidden from the AST
|
||||
// (conditionaly compiled code using #[cfg(..)])
|
||||
|
||||
let Some(braces_span_str) = snippet_opt(cx, braces_span) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
has_no_ident_token(braces_span_str.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_has_no_ident_token() {
|
||||
let input = "{ field: u8 }";
|
||||
assert!(!has_no_ident_token(input));
|
||||
|
||||
let input = "(u8, String);";
|
||||
assert!(!has_no_ident_token(input));
|
||||
|
||||
let input = " {
|
||||
// test = 5
|
||||
}
|
||||
";
|
||||
assert!(has_no_ident_token(input));
|
||||
|
||||
let input = " ();";
|
||||
assert!(has_no_ident_token(input));
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ use rustc_middle::ty;
|
|||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::Span;
|
||||
|
||||
use clippy_utils::consts::{constant_simple, Constant};
|
||||
use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{clip, unsext};
|
||||
|
||||
|
|
@ -54,6 +54,7 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
|
|||
check(cx, left, -1, e.span, right.span);
|
||||
check(cx, right, -1, e.span, left.span);
|
||||
},
|
||||
BinOpKind::Rem => check_remainder(cx, left, right, e.span, left.span),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
@ -70,6 +71,18 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_
|
|||
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
|
||||
}
|
||||
|
||||
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
|
||||
let lhs_const = constant_full_int(cx, cx.typeck_results(), left);
|
||||
let rhs_const = constant_full_int(cx, cx.typeck_results(), right);
|
||||
if match (lhs_const, rhs_const) {
|
||||
(Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(),
|
||||
(Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
|
||||
_ => return,
|
||||
} {
|
||||
span_ineffective_operation(cx, span, arg);
|
||||
}
|
||||
}
|
||||
|
||||
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).map(Constant::peel_refs) {
|
||||
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
|
||||
|
|
@ -83,15 +96,19 @@ fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
|
|||
1 => v == 1,
|
||||
_ => unreachable!(),
|
||||
} {
|
||||
span_lint(
|
||||
cx,
|
||||
IDENTITY_OP,
|
||||
span,
|
||||
&format!(
|
||||
"the operation is ineffective. Consider reducing it to `{}`",
|
||||
snippet(cx, arg, "..")
|
||||
),
|
||||
);
|
||||
span_ineffective_operation(cx, span, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span) {
|
||||
span_lint(
|
||||
cx,
|
||||
IDENTITY_OP,
|
||||
span,
|
||||
&format!(
|
||||
"the operation is ineffective. Consider reducing it to `{}`",
|
||||
snippet(cx, arg, "..")
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if cx.tcx.hir().is_inside_const_context(expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ExprKind::Index(array, index) = &expr.kind {
|
||||
let ty = cx.typeck_results().expr_ty(array).peel_refs();
|
||||
if let Some(range) = higher::Range::hir(index) {
|
||||
|
|
@ -151,6 +155,10 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
|||
} else {
|
||||
// Catchall non-range index, i.e., [n] or [n << m]
|
||||
if let ty::Array(..) = ty.kind() {
|
||||
// Index is a const block.
|
||||
if let ExprKind::ConstBlock(..) = index.kind {
|
||||
return;
|
||||
}
|
||||
// Index is a constant uint.
|
||||
if let Some(..) = constant(cx, cx.typeck_results(), index) {
|
||||
// Let rustc's `const_err` lint handle constant `usize` indexing on arrays.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(comparison_chain::COMPARISON_CHAIN),
|
||||
LintId::of(copies::IFS_SAME_COND),
|
||||
LintId::of(copies::IF_SAME_THEN_ELSE),
|
||||
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
|
||||
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
|
||||
LintId::of(dereference::NEEDLESS_BORROW),
|
||||
LintId::of(derivable_impls::DERIVABLE_IMPLS),
|
||||
|
|
@ -49,9 +50,12 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(double_comparison::DOUBLE_COMPARISONS),
|
||||
LintId::of(double_parens::DOUBLE_PARENS),
|
||||
LintId::of(drop_forget_ref::DROP_COPY),
|
||||
LintId::of(drop_forget_ref::DROP_NON_DROP),
|
||||
LintId::of(drop_forget_ref::DROP_REF),
|
||||
LintId::of(drop_forget_ref::FORGET_COPY),
|
||||
LintId::of(drop_forget_ref::FORGET_NON_DROP),
|
||||
LintId::of(drop_forget_ref::FORGET_REF),
|
||||
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
|
||||
LintId::of(duration_subsec::DURATION_SUBSEC),
|
||||
LintId::of(entry::MAP_ENTRY),
|
||||
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
|
||||
|
|
@ -296,7 +300,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(types::REDUNDANT_ALLOCATION),
|
||||
LintId::of(types::TYPE_COMPLEXITY),
|
||||
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_hash::UNIT_HASH),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
|||
LintId::of(drop_forget_ref::DROP_REF),
|
||||
LintId::of(drop_forget_ref::FORGET_COPY),
|
||||
LintId::of(drop_forget_ref::FORGET_REF),
|
||||
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
|
||||
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
|
||||
LintId::of(eq_op::EQ_OP),
|
||||
LintId::of(erasing_op::ERASING_OP),
|
||||
|
|
@ -62,7 +63,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
|||
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
||||
LintId::of(transmute::WRONG_TRANSMUTE),
|
||||
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_hash::UNIT_HASH),
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ store.register_lints(&[
|
|||
copies::IF_SAME_THEN_ELSE,
|
||||
copies::SAME_FUNCTIONS_IN_IF_CONDITION,
|
||||
copy_iterator::COPY_ITERATOR,
|
||||
crate_in_macro_def::CRATE_IN_MACRO_DEF,
|
||||
create_dir::CREATE_DIR,
|
||||
dbg_macro::DBG_MACRO,
|
||||
default::DEFAULT_TRAIT_ACCESS,
|
||||
|
|
@ -122,12 +123,16 @@ store.register_lints(&[
|
|||
double_comparison::DOUBLE_COMPARISONS,
|
||||
double_parens::DOUBLE_PARENS,
|
||||
drop_forget_ref::DROP_COPY,
|
||||
drop_forget_ref::DROP_NON_DROP,
|
||||
drop_forget_ref::DROP_REF,
|
||||
drop_forget_ref::FORGET_COPY,
|
||||
drop_forget_ref::FORGET_NON_DROP,
|
||||
drop_forget_ref::FORGET_REF,
|
||||
drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
|
||||
duration_subsec::DURATION_SUBSEC,
|
||||
else_if_without_else::ELSE_IF_WITHOUT_ELSE,
|
||||
empty_enum::EMPTY_ENUM,
|
||||
empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS,
|
||||
entry::MAP_ENTRY,
|
||||
enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT,
|
||||
enum_variants::ENUM_VARIANT_NAMES,
|
||||
|
|
@ -505,7 +510,6 @@ store.register_lints(&[
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
|||
LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION),
|
||||
LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
|
||||
LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
|
||||
LintId::of(empty_structs_with_brackets::EMPTY_STRUCTS_WITH_BRACKETS),
|
||||
LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
|
||||
LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
|
||||
LintId::of(exit::EXIT),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
|
|||
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
|
||||
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
|
||||
LintId::of(casts::CAST_ENUM_TRUNCATION),
|
||||
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
|
||||
LintId::of(drop_forget_ref::DROP_NON_DROP),
|
||||
LintId::of(drop_forget_ref::FORGET_NON_DROP),
|
||||
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
|
||||
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
|
||||
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// error-pattern:cargo-clippy
|
||||
|
||||
#![feature(array_windows)]
|
||||
#![feature(binary_heap_into_iter_sorted)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(control_flow_enum)]
|
||||
|
|
@ -190,6 +191,7 @@ mod collapsible_match;
|
|||
mod comparison_chain;
|
||||
mod copies;
|
||||
mod copy_iterator;
|
||||
mod crate_in_macro_def;
|
||||
mod create_dir;
|
||||
mod dbg_macro;
|
||||
mod default;
|
||||
|
|
@ -208,6 +210,7 @@ mod drop_forget_ref;
|
|||
mod duration_subsec;
|
||||
mod else_if_without_else;
|
||||
mod empty_enum;
|
||||
mod empty_structs_with_brackets;
|
||||
mod entry;
|
||||
mod enum_clike;
|
||||
mod enum_variants;
|
||||
|
|
@ -375,7 +378,6 @@ mod transmuting_null;
|
|||
mod try_err;
|
||||
mod types;
|
||||
mod undocumented_unsafe_blocks;
|
||||
mod undropped_manually_drops;
|
||||
mod unicode;
|
||||
mod uninit_vec;
|
||||
mod unit_hash;
|
||||
|
|
@ -812,7 +814,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(move || Box::new(disallowed_methods::DisallowedMethods::new(disallowed_methods.clone())));
|
||||
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax));
|
||||
store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax));
|
||||
store.register_late_pass(|| Box::new(undropped_manually_drops::UndroppedManuallyDrops));
|
||||
store.register_late_pass(|| Box::new(strings::StrToString));
|
||||
store.register_late_pass(|| Box::new(strings::StringToString));
|
||||
store.register_late_pass(|| Box::new(zero_sized_map_values::ZeroSizedMapValues));
|
||||
|
|
@ -847,7 +848,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
enable_raw_pointer_heuristic_for_send,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
|
||||
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
|
||||
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));
|
||||
|
|
@ -867,6 +868,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
ignore_publish: cargo_ignore_publish,
|
||||
})
|
||||
});
|
||||
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
|
||||
store.register_early_pass(|| Box::new(empty_structs_with_brackets::EmptyStructsWithBrackets));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ use super::SINGLE_ELEMENT_LOOP;
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::{indent_of, snippet_with_applicability};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::util::parser::PREC_PREFIX;
|
||||
use rustc_ast::Mutability;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Pat};
|
||||
use rustc_hir::{is_range_literal, BorrowKind, Expr, ExprKind, Pat};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::edition::Edition;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
|
|
@ -13,31 +16,84 @@ pub(super) fn check<'tcx>(
|
|||
body: &'tcx Expr<'_>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
) {
|
||||
let arg_expr = match arg.kind {
|
||||
ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg,
|
||||
ExprKind::MethodCall(method, [arg], _) if method.ident.name == rustc_span::sym::iter => arg,
|
||||
let (arg_expression, prefix) = match arg.kind {
|
||||
ExprKind::AddrOf(
|
||||
BorrowKind::Ref,
|
||||
Mutability::Not,
|
||||
Expr {
|
||||
kind: ExprKind::Array([arg]),
|
||||
..
|
||||
},
|
||||
) => (arg, "&"),
|
||||
ExprKind::AddrOf(
|
||||
BorrowKind::Ref,
|
||||
Mutability::Mut,
|
||||
Expr {
|
||||
kind: ExprKind::Array([arg]),
|
||||
..
|
||||
},
|
||||
) => (arg, "&mut "),
|
||||
ExprKind::MethodCall(
|
||||
method,
|
||||
[
|
||||
Expr {
|
||||
kind: ExprKind::Array([arg]),
|
||||
..
|
||||
},
|
||||
],
|
||||
_,
|
||||
) if method.ident.name == rustc_span::sym::iter => (arg, "&"),
|
||||
ExprKind::MethodCall(
|
||||
method,
|
||||
[
|
||||
Expr {
|
||||
kind: ExprKind::Array([arg]),
|
||||
..
|
||||
},
|
||||
],
|
||||
_,
|
||||
) if method.ident.name.as_str() == "iter_mut" => (arg, "&mut "),
|
||||
ExprKind::MethodCall(
|
||||
method,
|
||||
[
|
||||
Expr {
|
||||
kind: ExprKind::Array([arg]),
|
||||
..
|
||||
},
|
||||
],
|
||||
_,
|
||||
) if method.ident.name == rustc_span::sym::into_iter => (arg, ""),
|
||||
// Only check for arrays edition 2021 or later, as this case will trigger a compiler error otherwise.
|
||||
ExprKind::Array([arg]) if cx.tcx.sess.edition() >= Edition::Edition2021 => (arg, ""),
|
||||
_ => return,
|
||||
};
|
||||
if_chain! {
|
||||
if let ExprKind::Array([arg_expression]) = arg_expr.kind;
|
||||
if let ExprKind::Block(block, _) = body.kind;
|
||||
if !block.stmts.is_empty();
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
|
||||
let arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
|
||||
let mut arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
|
||||
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
|
||||
block_str.remove(0);
|
||||
block_str.pop();
|
||||
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
|
||||
|
||||
// Reference iterator from `&(mut) []` or `[].iter(_mut)()`.
|
||||
if !prefix.is_empty() && (
|
||||
// Precedence of internal expression is less than or equal to precedence of `&expr`.
|
||||
arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression)
|
||||
) {
|
||||
arg_snip = format!("({arg_snip})").into();
|
||||
}
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
SINGLE_ELEMENT_LOOP,
|
||||
expr.span,
|
||||
"for loop over a single element",
|
||||
"try",
|
||||
format!("{{\n{}let {} = &{};{}}}", indent, pat_snip, arg_snip, block_str),
|
||||
format!("{{\n{indent}let {pat_snip} = {prefix}{arg_snip};{block_str}}}"),
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{iter_input_pats, method_chain_args};
|
||||
use if_chain::if_chain;
|
||||
|
|
@ -217,36 +217,33 @@ fn lint_map_unit_fn(cx: &LateContext<'_>, stmt: &hir::Stmt<'_>, expr: &hir::Expr
|
|||
let fn_arg = &map_args[1];
|
||||
|
||||
if is_unit_function(cx, fn_arg) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let msg = suggestion_msg("function", map_type);
|
||||
let suggestion = format!(
|
||||
"if let {0}({binding}) = {1} {{ {2}({binding}) }}",
|
||||
variant,
|
||||
snippet(cx, var_arg.span, "_"),
|
||||
snippet(cx, fn_arg.span, "_"),
|
||||
snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
|
||||
snippet_with_applicability(cx, fn_arg.span, "_", &mut applicability),
|
||||
binding = let_binding_name(cx, var_arg)
|
||||
);
|
||||
|
||||
span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
|
||||
diag.span_suggestion(stmt.span, "try this", suggestion, Applicability::MachineApplicable);
|
||||
diag.span_suggestion(stmt.span, "try this", suggestion, applicability);
|
||||
});
|
||||
} else if let Some((binding, closure_expr)) = unit_closure(cx, fn_arg) {
|
||||
let msg = suggestion_msg("closure", map_type);
|
||||
|
||||
span_lint_and_then(cx, lint, expr.span, &msg, |diag| {
|
||||
if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let suggestion = format!(
|
||||
"if let {0}({1}) = {2} {{ {3} }}",
|
||||
variant,
|
||||
snippet(cx, binding.pat.span, "_"),
|
||||
snippet(cx, var_arg.span, "_"),
|
||||
snippet(cx, reduced_expr_span, "_")
|
||||
);
|
||||
diag.span_suggestion(
|
||||
stmt.span,
|
||||
"try this",
|
||||
suggestion,
|
||||
Applicability::MachineApplicable, // snippet
|
||||
snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability),
|
||||
snippet_with_applicability(cx, var_arg.span, "_", &mut applicability),
|
||||
snippet_with_context(cx, reduced_expr_span, var_arg.span.ctxt(), "_", &mut applicability).0,
|
||||
);
|
||||
diag.span_suggestion(stmt.span, "try this", suggestion, applicability);
|
||||
} else {
|
||||
let suggestion = format!(
|
||||
"if let {0}({1}) = {2} {{ ... }}",
|
||||
|
|
|
|||
|
|
@ -667,7 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
|
|||
overlapping_arms::check(cx, ex, arms);
|
||||
match_wild_enum::check(cx, ex, arms);
|
||||
match_as_ref::check(cx, ex, arms, expr);
|
||||
needless_match::check_match(cx, ex, arms);
|
||||
needless_match::check_match(cx, ex, arms, expr);
|
||||
|
||||
if self.infallible_destructuring_match_linted {
|
||||
self.infallible_destructuring_match_linted = false;
|
||||
|
|
|
|||
|
|
@ -1,37 +1,25 @@
|
|||
use super::NEEDLESS_MATCH;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts};
|
||||
use clippy_utils::{
|
||||
eq_expr_value, get_parent_expr_for_hir, get_parent_node, higher, is_else_clause, is_lang_ctor, over,
|
||||
peel_blocks_with_stmt,
|
||||
};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::LangItem::OptionNone;
|
||||
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
|
||||
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, FnRetTy, Node, Pat, PatKind, Path, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::sym;
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
|
||||
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
|
||||
// This is for avoiding collision with `match_single_binding`.
|
||||
if arms.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
for arm in arms {
|
||||
if let PatKind::Wild = arm.pat.kind {
|
||||
let ret_expr = strip_return(arm.body);
|
||||
if !eq_expr_value(cx, ex, ret_expr) {
|
||||
return;
|
||||
}
|
||||
} else if !pat_same_as_expr(arm.pat, arm.body) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(match_expr) = get_parent_expr(cx, ex) {
|
||||
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
|
||||
if arms.len() > 1 && expr_ty_matches_p_ty(cx, ex, expr) && check_all_arms(cx, ex, arms) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_MATCH,
|
||||
match_expr.span,
|
||||
expr.span,
|
||||
"this match expression is unnecessary",
|
||||
"replace it with",
|
||||
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
|
||||
|
|
@ -60,11 +48,8 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
|
|||
/// }
|
||||
/// ```
|
||||
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
|
||||
if !is_else_clause(cx.tcx, ex);
|
||||
if check_if_let(cx, if_let);
|
||||
then {
|
||||
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
|
||||
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
|
@ -79,6 +64,19 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
|
||||
for arm in arms {
|
||||
let arm_expr = peel_blocks_with_stmt(arm.body);
|
||||
if let PatKind::Wild = arm.pat.kind {
|
||||
return eq_expr_value(cx, match_expr, strip_return(arm_expr));
|
||||
} else if !pat_same_as_expr(arm.pat, arm_expr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
|
||||
if let Some(if_else) = if_let.if_else {
|
||||
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
|
||||
|
|
@ -92,18 +90,21 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
|
|||
|
||||
if matches!(if_else.kind, ExprKind::Block(..)) {
|
||||
let else_expr = peel_blocks_with_stmt(if_else);
|
||||
if matches!(else_expr.kind, ExprKind::Block(..)) {
|
||||
return false;
|
||||
}
|
||||
let ret = strip_return(else_expr);
|
||||
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
|
||||
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
|
||||
if let ExprKind::Path(ref qpath) = ret.kind {
|
||||
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
|
||||
}
|
||||
} else {
|
||||
return eq_expr_value(cx, if_let.let_expr, ret);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return eq_expr_value(cx, if_let.let_expr, ret);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -116,48 +117,70 @@ fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Manually check for coercion casting by checking if the type of the match operand or let expr
|
||||
/// differs with the assigned local variable or the funtion return type.
|
||||
fn expr_ty_matches_p_ty(cx: &LateContext<'_>, expr: &Expr<'_>, p_expr: &Expr<'_>) -> bool {
|
||||
if let Some(p_node) = get_parent_node(cx.tcx, p_expr.hir_id) {
|
||||
match p_node {
|
||||
// Compare match_expr ty with local in `let local = match match_expr {..}`
|
||||
Node::Local(local) => {
|
||||
let results = cx.typeck_results();
|
||||
return same_type_and_consts(results.node_type(local.hir_id), results.expr_ty(expr));
|
||||
},
|
||||
// compare match_expr ty with RetTy in `fn foo() -> RetTy`
|
||||
Node::Item(..) => {
|
||||
if let Some(fn_decl) = p_node.fn_decl() {
|
||||
if let FnRetTy::Return(ret_ty) = fn_decl.output {
|
||||
return same_type_and_consts(hir_ty_to_ty(cx.tcx, ret_ty), cx.typeck_results().expr_ty(expr));
|
||||
}
|
||||
}
|
||||
},
|
||||
// check the parent expr for this whole block `{ match match_expr {..} }`
|
||||
Node::Block(block) => {
|
||||
if let Some(block_parent_expr) = get_parent_expr_for_hir(cx, block.hir_id) {
|
||||
return expr_ty_matches_p_ty(cx, expr, block_parent_expr);
|
||||
}
|
||||
},
|
||||
// recursively call on `if xxx {..}` etc.
|
||||
Node::Expr(p_expr) => {
|
||||
return expr_ty_matches_p_ty(cx, expr, p_expr);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
|
||||
let expr = strip_return(expr);
|
||||
match (&pat.kind, &expr.kind) {
|
||||
// Example: `Some(val) => Some(val)`
|
||||
(
|
||||
PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
|
||||
ExprKind::Call(call_expr, [first_param, ..]),
|
||||
) => {
|
||||
(PatKind::TupleStruct(QPath::Resolved(_, path), tuple_params, _), ExprKind::Call(call_expr, call_params)) => {
|
||||
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
|
||||
if has_identical_segments(path.segments, call_path.segments)
|
||||
&& has_same_non_ref_symbol(first_pat, first_param)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return over(path.segments, call_path.segments, |pat_seg, call_seg| {
|
||||
pat_seg.ident.name == call_seg.ident.name
|
||||
}) && same_non_ref_symbols(tuple_params, call_params);
|
||||
}
|
||||
},
|
||||
// Example: `val => val`, or `ref val => *val`
|
||||
(PatKind::Binding(annot, _, pat_ident, _), _) => {
|
||||
let new_expr = if let (
|
||||
BindingAnnotation::Ref | BindingAnnotation::RefMut,
|
||||
ExprKind::Unary(UnOp::Deref, operand_expr),
|
||||
) = (annot, &expr.kind)
|
||||
{
|
||||
operand_expr
|
||||
} else {
|
||||
expr
|
||||
};
|
||||
|
||||
if let ExprKind::Path(QPath::Resolved(
|
||||
// Example: `val => val`
|
||||
(
|
||||
PatKind::Binding(annot, _, pat_ident, _),
|
||||
ExprKind::Path(QPath::Resolved(
|
||||
_,
|
||||
Path {
|
||||
segments: [first_seg, ..],
|
||||
..
|
||||
},
|
||||
)) = new_expr.kind
|
||||
{
|
||||
return pat_ident.name == first_seg.ident.name;
|
||||
}
|
||||
)),
|
||||
) => {
|
||||
return !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut)
|
||||
&& pat_ident.name == first_seg.ident.name;
|
||||
},
|
||||
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
|
||||
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
|
||||
return has_identical_segments(p_path.segments, e_path.segments);
|
||||
return over(p_path.segments, e_path.segments, |p_seg, e_seg| {
|
||||
p_seg.ident.name == e_seg.ident.name
|
||||
});
|
||||
},
|
||||
// Example: `5 => 5`
|
||||
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
|
||||
|
|
@ -171,27 +194,16 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
|
||||
if left_segs.len() != right_segs.len() {
|
||||
fn same_non_ref_symbols(pats: &[Pat<'_>], exprs: &[Expr<'_>]) -> bool {
|
||||
if pats.len() != exprs.len() {
|
||||
return false;
|
||||
}
|
||||
for i in 0..left_segs.len() {
|
||||
if left_segs[i].ident.name != right_segs[i].ident.name {
|
||||
|
||||
for i in 0..pats.len() {
|
||||
if !pat_same_as_expr(&pats[i], &exprs[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
|
||||
if_chain! {
|
||||
if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
|
||||
if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
|
||||
if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
|
||||
then {
|
||||
return pat_ident.name == first_seg.ident.name;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E
|
|||
cx,
|
||||
BYTES_NTH,
|
||||
expr.span,
|
||||
&format!("called `.byte().nth()` on a `{}`", caller_type),
|
||||
&format!("called `.bytes().nth()` on a `{}`", caller_type),
|
||||
"try",
|
||||
format!(
|
||||
"{}.as_bytes().get({})",
|
||||
|
|
|
|||
60
clippy_lints/src/methods/err_expect.rs
Normal file
60
clippy_lints/src/methods/err_expect.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use super::ERR_EXPECT;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
pub(super) fn check(
|
||||
cx: &LateContext<'_>,
|
||||
_expr: &rustc_hir::Expr<'_>,
|
||||
recv: &rustc_hir::Expr<'_>,
|
||||
msrv: Option<&RustcVersion>,
|
||||
expect_span: Span,
|
||||
err_span: Span,
|
||||
) {
|
||||
if_chain! {
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
|
||||
// Test the version to make sure the lint can be showed (expect_err has been
|
||||
// introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
|
||||
if meets_msrv(msrv, &msrvs::EXPECT_ERR);
|
||||
|
||||
// Grabs the `Result<T, E>` type
|
||||
let result_type = cx.typeck_results().expr_ty(recv);
|
||||
// Tests if the T type in a `Result<T, E>` is not None
|
||||
if let Some(data_type) = get_data_type(cx, result_type);
|
||||
// Tests if the T type in a `Result<T, E>` implements debug
|
||||
if has_debug_impl(data_type, cx);
|
||||
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
ERR_EXPECT,
|
||||
err_span.to(expect_span),
|
||||
"called `.err().expect()` on a `Result` value",
|
||||
"try",
|
||||
"expect_err".to_string(),
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Given a `Result<T, E>` type, return its data (`T`).
|
||||
fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> {
|
||||
match ty.kind() {
|
||||
ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::Result) => substs.types().next(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a type, very if the Debug trait has been impl'd
|
||||
fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
|
||||
cx.tcx
|
||||
.get_diagnostic_item(sym::Debug)
|
||||
.map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
|
||||
}
|
||||
|
|
@ -48,13 +48,11 @@ pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir
|
|||
"to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr),
|
||||
"to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
|
||||
"to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path),
|
||||
"to_vec" => {
|
||||
cx.tcx.impl_of_method(method_def_id)
|
||||
.filter(|&impl_did| {
|
||||
cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none()
|
||||
})
|
||||
.is_some()
|
||||
},
|
||||
"to_vec" => cx
|
||||
.tcx
|
||||
.impl_of_method(method_def_id)
|
||||
.filter(|&impl_did| cx.tcx.type_of(impl_did).is_slice() && cx.tcx.impl_trait_ref(impl_did).is_none())
|
||||
.is_some(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::{get_iterator_item_ty, is_copy};
|
||||
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy};
|
||||
use itertools::Itertools;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
use std::ops::Not;
|
||||
|
||||
use super::ITER_OVEREAGER_CLONED;
|
||||
|
|
@ -20,9 +21,16 @@ pub(super) fn check<'tcx>(
|
|||
map_arg: &[hir::Expr<'_>],
|
||||
) {
|
||||
// Check if it's iterator and get type associated with `Item`.
|
||||
let inner_ty = match get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv)) {
|
||||
Some(ty) => ty,
|
||||
_ => return,
|
||||
let inner_ty = if_chain! {
|
||||
if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
|
||||
let recv_ty = cx.typeck_results().expr_ty(recv);
|
||||
if implements_trait(cx, recv_ty, iterator_trait_id, &[]);
|
||||
if let Some(inner_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv));
|
||||
then {
|
||||
inner_ty
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match inner_ty.kind() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub(super) fn check(
|
|||
expr: &hir::Expr<'_>,
|
||||
caller: &hir::Expr<'_>,
|
||||
map_arg: &hir::Expr<'_>,
|
||||
name: &str,
|
||||
_map_span: Span,
|
||||
) {
|
||||
let caller_ty = cx.typeck_results().expr_ty(caller);
|
||||
|
|
@ -29,7 +30,7 @@ pub(super) fn check(
|
|||
MAP_IDENTITY,
|
||||
sugg_span,
|
||||
"unnecessary map of the identity function",
|
||||
"remove the call to `map`",
|
||||
&format!("remove the call to `{}`", name),
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod chars_next_cmp_with_unwrap;
|
|||
mod clone_on_copy;
|
||||
mod clone_on_ref_ptr;
|
||||
mod cloned_instead_of_copied;
|
||||
mod err_expect;
|
||||
mod expect_fun_call;
|
||||
mod expect_used;
|
||||
mod extend_with_drain;
|
||||
|
|
@ -362,6 +363,29 @@ declare_clippy_lint! {
|
|||
"using `ok().expect()`, which gives worse error messages than calling `expect` directly on the Result"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `.err().expect()` calls on the `Result` type.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `.expect_err()` can be called directly to avoid the extra type conversion from `err()`.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```should_panic
|
||||
/// let x: Result<u32, &str> = Ok(10);
|
||||
/// x.err().expect("Testing err().expect()");
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```should_panic
|
||||
/// let x: Result<u32, &str> = Ok(10);
|
||||
/// x.expect_err("Testing expect_err");
|
||||
/// ```
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub ERR_EXPECT,
|
||||
style,
|
||||
r#"using `.err().expect("")` when `.expect_err("")` can be used"#
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usages of `_.unwrap_or_else(Default::default)` on `Option` and
|
||||
|
|
@ -2055,7 +2079,7 @@ declare_clippy_lint! {
|
|||
/// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `.collect::<String>()` is more concise and usually more performant
|
||||
/// `.collect::<String>()` is more concise and might be more performant
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
|
|
@ -2070,9 +2094,12 @@ declare_clippy_lint! {
|
|||
/// println!("{}", output);
|
||||
/// ```
|
||||
/// ### Known problems
|
||||
/// While `.collect::<String>()` is more performant in most cases, there are cases where
|
||||
/// While `.collect::<String>()` is sometimes more performant, there are cases where
|
||||
/// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
|
||||
/// will prevent loop unrolling and will result in a negative performance impact.
|
||||
///
|
||||
/// Additionlly, differences have been observed between aarch64 and x86_64 assembly output,
|
||||
/// with aarch64 tending to producing faster assembly in more cases when using `.collect::<String>()`
|
||||
#[clippy::version = "1.61.0"]
|
||||
pub UNNECESSARY_JOIN,
|
||||
pedantic,
|
||||
|
|
@ -2165,6 +2192,7 @@ impl_lint_pass!(Methods => [
|
|||
NEEDLESS_SPLITN,
|
||||
UNNECESSARY_TO_OWNED,
|
||||
UNNECESSARY_JOIN,
|
||||
ERR_EXPECT,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
|
|
@ -2428,6 +2456,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
|
|||
},
|
||||
("expect", [_]) => match method_call(recv) {
|
||||
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
|
||||
Some(("err", [recv], err_span)) => err_expect::check(cx, expr, recv, msrv, span, err_span),
|
||||
_ => expect_used::check(cx, expr, recv),
|
||||
},
|
||||
("extend", [arg]) => {
|
||||
|
|
@ -2472,7 +2501,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
|
|||
}
|
||||
}
|
||||
},
|
||||
("map", [m_arg]) => {
|
||||
(name @ ("map" | "map_err"), [m_arg]) => {
|
||||
if let Some((name, [recv2, args @ ..], span2)) = method_call(recv) {
|
||||
match (name, args) {
|
||||
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, msrv),
|
||||
|
|
@ -2484,7 +2513,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
|
|||
_ => {},
|
||||
}
|
||||
}
|
||||
map_identity::check(cx, expr, recv, m_arg, span);
|
||||
map_identity::check(cx, expr, recv, m_arg, name, span);
|
||||
},
|
||||
("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map),
|
||||
(name @ "next", args @ []) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -57,6 +58,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<'_>,
|
||||
|
|
@ -80,7 +95,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,
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
use std::{
|
||||
ffi::OsString,
|
||||
path::{Component, Path},
|
||||
};
|
||||
|
||||
use rustc_ast::ast;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
|
||||
use std::ffi::OsStr;
|
||||
use std::path::{Component, Path};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks that module layout uses only self named module files, bans mod.rs files.
|
||||
/// Checks that module layout uses only self named module files, bans `mod.rs` files.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Having multiple module layout styles in a project can be confusing.
|
||||
|
|
@ -40,7 +37,7 @@ declare_clippy_lint! {
|
|||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks that module layout uses only mod.rs files.
|
||||
/// Checks that module layout uses only `mod.rs` files.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Having multiple module layout styles in a project can be confusing.
|
||||
|
|
@ -82,11 +79,7 @@ impl EarlyLintPass for ModStyle {
|
|||
|
||||
let files = cx.sess().source_map().files();
|
||||
|
||||
let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess().opts.working_dir {
|
||||
p.to_string_lossy()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let RealFileName::LocalPath(trim_to_src) = &cx.sess().opts.working_dir else { return };
|
||||
|
||||
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
|
||||
// `[path, to]` but not foo
|
||||
|
|
@ -97,26 +90,27 @@ impl EarlyLintPass for ModStyle {
|
|||
// `{ foo => path/to/foo.rs, .. }
|
||||
let mut file_map = FxHashMap::default();
|
||||
for file in files.iter() {
|
||||
match &file.name {
|
||||
FileName::Real(RealFileName::LocalPath(lp))
|
||||
if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
|
||||
{
|
||||
let p = lp.to_string_lossy();
|
||||
let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
|
||||
if let Some(stem) = path.file_stem() {
|
||||
file_map.insert(stem.to_os_string(), (file, path.to_owned()));
|
||||
}
|
||||
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
|
||||
check_self_named_mod_exists(cx, path, file);
|
||||
},
|
||||
_ => {},
|
||||
if let FileName::Real(RealFileName::LocalPath(lp)) = &file.name {
|
||||
let path = if lp.is_relative() {
|
||||
lp
|
||||
} else if let Ok(relative) = lp.strip_prefix(trim_to_src) {
|
||||
relative
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Some(stem) = path.file_stem() {
|
||||
file_map.insert(stem, (file, path));
|
||||
}
|
||||
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
|
||||
check_self_named_mod_exists(cx, path, file);
|
||||
}
|
||||
}
|
||||
|
||||
for folder in &folder_segments {
|
||||
if !mod_folders.contains(folder) {
|
||||
if let Some((file, path)) = file_map.get(folder) {
|
||||
let mut correct = path.clone();
|
||||
let mut correct = path.to_path_buf();
|
||||
correct.pop();
|
||||
correct.push(folder);
|
||||
correct.push("mod.rs");
|
||||
|
|
@ -138,25 +132,17 @@ impl EarlyLintPass for ModStyle {
|
|||
|
||||
/// For each `path` we add each folder component to `folder_segments` and if the file name
|
||||
/// is `mod.rs` we add it's parent folder to `mod_folders`.
|
||||
fn process_paths_for_mod_files(
|
||||
path: &Path,
|
||||
folder_segments: &mut FxHashSet<OsString>,
|
||||
mod_folders: &mut FxHashSet<OsString>,
|
||||
fn process_paths_for_mod_files<'a>(
|
||||
path: &'a Path,
|
||||
folder_segments: &mut FxHashSet<&'a OsStr>,
|
||||
mod_folders: &mut FxHashSet<&'a OsStr>,
|
||||
) {
|
||||
let mut comp = path.components().rev().peekable();
|
||||
let _ = comp.next();
|
||||
if path.ends_with("mod.rs") {
|
||||
mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
|
||||
mod_folders.insert(comp.peek().map(|c| c.as_os_str()).unwrap_or_default());
|
||||
}
|
||||
let folders = comp
|
||||
.filter_map(|c| {
|
||||
if let Component::Normal(s) = c {
|
||||
Some(s.to_os_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let folders = comp.filter_map(|c| if let Component::Normal(s) = c { Some(s) } else { None });
|
||||
folder_segments.extend(folders);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,10 +92,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
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
|
|||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
|
||||
if is_panic(cx, macro_call.def_id) {
|
||||
if cx.tcx.hir().is_inside_const_context(expr.hir_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint(
|
||||
cx,
|
||||
PANIC,
|
||||
|
|
|
|||
|
|
@ -601,9 +601,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
|
|||
},
|
||||
// If the types match check for methods which exist on both types. e.g. `Vec::len` and
|
||||
// `slice::len`
|
||||
ty::Adt(def, _)
|
||||
if def.did() == args.ty_did =>
|
||||
{
|
||||
ty::Adt(def, _) if def.did() == args.ty_did => {
|
||||
set_skip_flag();
|
||||
},
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -330,7 +330,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,
|
||||
|
|
|
|||
|
|
@ -410,9 +410,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
|||
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
|
||||
then {
|
||||
// Avoid suggesting from/to bits and dereferencing raw pointers in const contexts.
|
||||
// See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`.
|
||||
// And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
|
||||
// Avoid suggesting non-const operations in const contexts:
|
||||
// - from/to bits (https://github.com/rust-lang/rust/issues/73736)
|
||||
// - dereferencing raw pointers (https://github.com/rust-lang/rust/issues/51911)
|
||||
// - char conversions (https://github.com/rust-lang/rust/issues/89259)
|
||||
let const_context = in_constant(cx, e.hir_id);
|
||||
|
||||
let from_ty = cx.typeck_results().expr_ty_adjusted(arg);
|
||||
|
|
@ -427,7 +428,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
|||
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
|
||||
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
|
||||
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
|
||||
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg)
|
||||
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
|
||||
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,10 @@ pub(super) fn check<'tcx>(
|
|||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
arg: &'tcx Expr<'_>,
|
||||
const_context: bool,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => {
|
||||
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) if !const_context => {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
TRANSMUTE_INT_TO_CHAR,
|
||||
|
|
|
|||
|
|
@ -32,18 +32,20 @@ pub(super) fn check<'tcx>(
|
|||
""
|
||||
};
|
||||
|
||||
let snippet = snippet(cx, arg.span, "..");
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TRANSMUTE_BYTES_TO_STR,
|
||||
e.span,
|
||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||
"consider using",
|
||||
format!(
|
||||
"std::str::from_utf8{}({}).unwrap()",
|
||||
postfix,
|
||||
snippet(cx, arg.span, ".."),
|
||||
),
|
||||
Applicability::Unspecified,
|
||||
if const_context {
|
||||
format!("std::str::from_utf8_unchecked{postfix}({snippet})")
|
||||
} else {
|
||||
format!("std::str::from_utf8{postfix}({snippet}).unwrap()")
|
||||
},
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
triggered = true;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource};
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use clippy_utils::source::walk_span_to_context;
|
||||
use rustc_hir::{Block, BlockCheckMode, UnsafeSource};
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
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;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{BytePos, Pos, SyntaxContext};
|
||||
use std::rc::Rc;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -18,6 +15,24 @@ declare_clippy_lint! {
|
|||
/// explaining why the unsafe operations performed inside
|
||||
/// the block are safe.
|
||||
///
|
||||
/// Note the comment must appear on the line(s) preceding the unsafe block
|
||||
/// with nothing appearing in between. The following is ok:
|
||||
/// ```ignore
|
||||
/// foo(
|
||||
/// // SAFETY:
|
||||
/// // This is a valid safety comment
|
||||
/// unsafe { *x }
|
||||
/// )
|
||||
/// ```
|
||||
/// But neither of these are:
|
||||
/// ```ignore
|
||||
/// // SAFETY:
|
||||
/// // This is not a valid safety comment
|
||||
/// foo(
|
||||
/// /* SAFETY: Neither is this */ unsafe { *x },
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Undocumented unsafe blocks can make it difficult to
|
||||
/// read and maintain code, as well as uncover unsoundness
|
||||
|
|
@ -44,179 +59,139 @@ declare_clippy_lint! {
|
|||
"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,
|
||||
}
|
||||
declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
|
||||
|
||||
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 block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
|
||||
&& !in_external_macro(cx.tcx.sess, block.span)
|
||||
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
|
||||
&& !is_unsafe_from_proc_macro(cx, block)
|
||||
&& !block_has_safety_comment(cx, block)
|
||||
{
|
||||
let source_map = cx.tcx.sess.source_map();
|
||||
let span = if source_map.is_multiline(block.span) {
|
||||
source_map.span_until_char(block.span, '\n')
|
||||
} else {
|
||||
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<'v> Visitor<'v> for UndocumentedUnsafeBlocks {
|
||||
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 block_span.from_expansion() {
|
||||
self.macro_expansion = true;
|
||||
enclosing_scope_span.with_hi(block_span.hi()).source_callsite()
|
||||
} else {
|
||||
self.macro_expansion = false;
|
||||
enclosing_scope_span.to(block_span).source_callsite()
|
||||
};
|
||||
|
||||
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 - source_file.start_pos.0 + 1) as usize;
|
||||
let lex_end = (between_span.hi().0 - source_file.start_pos.0) as usize;
|
||||
let src_str = source_file.src.as_ref()?[lex_start..lex_end].to_string();
|
||||
|
||||
let source_start_pos = source_file.start_pos.0 as usize + lex_start;
|
||||
|
||||
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(BytePos((source_start_pos + 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,
|
||||
None,
|
||||
"consider adding a safety comment on the preceding line",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
|
||||
let source_map = cx.sess().source_map();
|
||||
let file_pos = source_map.lookup_byte_offset(block.span.lo());
|
||||
file_pos
|
||||
.sf
|
||||
.src
|
||||
.as_deref()
|
||||
.and_then(|src| src.get(file_pos.pos.to_usize()..))
|
||||
.map_or(true, |src| !src.starts_with("unsafe"))
|
||||
}
|
||||
|
||||
/// Checks if the lines immediately preceding the block contain a safety comment.
|
||||
fn block_has_safety_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool {
|
||||
// This intentionally ignores text before the start of a function so something like:
|
||||
// ```
|
||||
// // SAFETY: reason
|
||||
// fn foo() { unsafe { .. } }
|
||||
// ```
|
||||
// won't work. This is to avoid dealing with where such a comment should be place relative to
|
||||
// attributes and doc comments.
|
||||
|
||||
let source_map = cx.sess().source_map();
|
||||
let ctxt = block.span.ctxt();
|
||||
if ctxt != SyntaxContext::root() {
|
||||
// From a macro expansion. Get the text from the start of the macro declaration to start of the unsafe block.
|
||||
// macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
|
||||
// ^--------------------------------------------^
|
||||
if let Ok(unsafe_line) = source_map.lookup_line(block.span.lo())
|
||||
&& let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo())
|
||||
&& Rc::ptr_eq(&unsafe_line.sf, ¯o_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
macro_line.line < unsafe_line.line && text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines[macro_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos.to_usize(),
|
||||
)
|
||||
} else {
|
||||
// Problem getting source text. Pretend a comment was found.
|
||||
true
|
||||
}
|
||||
} else if let Ok(unsafe_line) = source_map.lookup_line(block.span.lo())
|
||||
&& let Some(body) = cx.enclosing_body
|
||||
&& let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root())
|
||||
&& let Ok(body_line) = source_map.lookup_line(body_span.lo())
|
||||
&& Rc::ptr_eq(&unsafe_line.sf, &body_line.sf)
|
||||
&& let Some(src) = unsafe_line.sf.src.as_deref()
|
||||
{
|
||||
// Get the text from the start of function body to the unsafe block.
|
||||
// fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
|
||||
// ^-------------^
|
||||
body_line.line < unsafe_line.line && text_has_safety_comment(
|
||||
src,
|
||||
&unsafe_line.sf.lines[body_line.line + 1..=unsafe_line.line],
|
||||
unsafe_line.sf.start_pos.to_usize(),
|
||||
)
|
||||
} else {
|
||||
// Problem getting source text. Pretend a comment was found.
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given text has a safety comment for the immediately proceeding line.
|
||||
fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> bool {
|
||||
let mut lines = line_starts
|
||||
.array_windows::<2>()
|
||||
.rev()
|
||||
.map_while(|[start, end]| {
|
||||
src.get(start.to_usize() - offset..end.to_usize() - offset)
|
||||
.map(|text| (start.to_usize(), text.trim_start()))
|
||||
})
|
||||
.filter(|(_, text)| !text.is_empty());
|
||||
|
||||
let Some((line_start, line)) = lines.next() else {
|
||||
return false;
|
||||
};
|
||||
// Check for a sequence of line comments.
|
||||
if line.starts_with("//") {
|
||||
let mut line = line;
|
||||
loop {
|
||||
if line.to_ascii_uppercase().contains("SAFETY:") {
|
||||
return true;
|
||||
}
|
||||
match lines.next() {
|
||||
Some((_, x)) if x.starts_with("//") => line = x,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
// No line comments; look for the start of a block comment.
|
||||
// This will only find them if they are at the start of a line.
|
||||
let (mut line_start, mut line) = (line_start, line);
|
||||
loop {
|
||||
if line.starts_with("/*") {
|
||||
let src = src[line_start..line_starts.last().unwrap().to_usize()].trim_start();
|
||||
let mut tokens = tokenize(src);
|
||||
return src[..tokens.next().unwrap().len]
|
||||
.to_ascii_uppercase()
|
||||
.contains("SAFETY:")
|
||||
&& tokens.all(|t| t.kind == TokenKind::Whitespace);
|
||||
}
|
||||
match lines.next() {
|
||||
Some(x) => (line_start, line) = x,
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::path_res;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
use rustc_hir::{lang_items, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The safe `drop` function does not drop the inner value of a `ManuallyDrop`.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Does not catch cases if the user binds `std::mem::drop`
|
||||
/// to a different name and calls it that way.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// drop(std::mem::ManuallyDrop::new(S));
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct S;
|
||||
/// unsafe {
|
||||
/// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S));
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.49.0"]
|
||||
pub UNDROPPED_MANUALLY_DROPS,
|
||||
correctness,
|
||||
"use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value"
|
||||
}
|
||||
|
||||
declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for UndroppedManuallyDrops {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(fun, [arg_0, ..]) = expr.kind;
|
||||
if path_res(cx, fun).opt_def_id() == cx.tcx.get_diagnostic_item(sym::mem_drop);
|
||||
let ty = cx.typeck_results().expr_ty(arg_0);
|
||||
if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop);
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
UNDROPPED_MANUALLY_DROPS,
|
||||
expr.span,
|
||||
"the inner value of this ManuallyDrop will not be dropped",
|
||||
None,
|
||||
"to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Foo {}
|
||||
/// struct Foo;
|
||||
/// impl Foo {
|
||||
/// fn new() -> Foo {
|
||||
/// Foo {}
|
||||
|
|
@ -43,7 +43,7 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
/// could be
|
||||
/// ```rust
|
||||
/// struct Foo {}
|
||||
/// struct Foo;
|
||||
/// impl Foo {
|
||||
/// fn new() -> Self {
|
||||
/// Self {}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ define_Conf! {
|
|||
///
|
||||
/// Suppress lints whenever the suggested change would cause breakage for other crates.
|
||||
(avoid_breaking_exported_api: bool = true),
|
||||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS.
|
||||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, EXPECT_ERR.
|
||||
///
|
||||
/// The minimum rust version that the project supports
|
||||
(msrv: Option<String> = None),
|
||||
|
|
|
|||
|
|
@ -889,7 +889,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
|
|||
}
|
||||
}
|
||||
},
|
||||
Res::Def(DefKind::Const | DefKind::Static, def_id) => {
|
||||
Res::Def(DefKind::Const | DefKind::Static(..), def_id) => {
|
||||
if let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(def_id) {
|
||||
if let ItemKind::Const(.., body_id) | ItemKind::Static(.., body_id) = item.kind {
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
|
|
|
|||
|
|
@ -756,7 +756,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
|
|||
let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(expr));
|
||||
if match_type(self.cx, expr_ty, &paths::LINT);
|
||||
then {
|
||||
if let hir::def::Res::Def(DefKind::Static, _) = path.res {
|
||||
if let hir::def::Res::Def(DefKind::Static(..), _) = path.res {
|
||||
let lint_name = last_path_segment(qpath).ident.name;
|
||||
self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
|
||||
} else if let Some(local) = get_parent_local(self.cx, expr) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue