1
Fork 0

Merge commit '2b2190cb56' into clippyup

This commit is contained in:
Philipp Krones 2022-08-11 19:42:16 +02:00
parent f719599c0f
commit dc29cfb8d5
182 changed files with 2240 additions and 827 deletions

View file

@ -965,7 +965,7 @@ Released 2021-09-09
[#7407](https://github.com/rust-lang/rust-clippy/pull/7407) [#7407](https://github.com/rust-lang/rust-clippy/pull/7407)
* [`redundant_allocation`]: Now additionally supports the `Arc<>` type * [`redundant_allocation`]: Now additionally supports the `Arc<>` type
[#7308](https://github.com/rust-lang/rust-clippy/pull/7308) [#7308](https://github.com/rust-lang/rust-clippy/pull/7308)
* [`blacklisted_name`]: Now allows blacklisted names in test code * [`disallowed_names`]: Now allows disallowed names in test code
[#7379](https://github.com/rust-lang/rust-clippy/pull/7379) [#7379](https://github.com/rust-lang/rust-clippy/pull/7379)
* [`redundant_closure`]: Suggests `&mut` for `FnMut` * [`redundant_closure`]: Suggests `&mut` for `FnMut`
[#7437](https://github.com/rust-lang/rust-clippy/pull/7437) [#7437](https://github.com/rust-lang/rust-clippy/pull/7437)
@ -2066,7 +2066,7 @@ Released 2020-08-27
[#5692](https://github.com/rust-lang/rust-clippy/pull/5692) [#5692](https://github.com/rust-lang/rust-clippy/pull/5692)
* [`if_same_then_else`]: Don't assume multiplication is always commutative * [`if_same_then_else`]: Don't assume multiplication is always commutative
[#5702](https://github.com/rust-lang/rust-clippy/pull/5702) [#5702](https://github.com/rust-lang/rust-clippy/pull/5702)
* [`blacklisted_name`]: Remove `bar` from the default configuration * [`disallowed_names`]: Remove `bar` from the default configuration
[#5712](https://github.com/rust-lang/rust-clippy/pull/5712) [#5712](https://github.com/rust-lang/rust-clippy/pull/5712)
* [`redundant_pattern_matching`]: Avoid suggesting non-`const fn` calls in const contexts * [`redundant_pattern_matching`]: Avoid suggesting non-`const fn` calls in const contexts
[#5724](https://github.com/rust-lang/rust-clippy/pull/5724) [#5724](https://github.com/rust-lang/rust-clippy/pull/5724)
@ -3522,6 +3522,7 @@ Released 2018-09-13
[`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq [`derive_partial_eq_without_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq
[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method [`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
[`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods [`disallowed_methods`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods
[`disallowed_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names
[`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents [`disallowed_script_idents`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_script_idents
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
@ -3685,6 +3686,7 @@ Released 2018-09-13
[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find [`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy [`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
@ -3816,11 +3818,13 @@ Released 2018-09-13
[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap [`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing [`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional [`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
[`overly_complex_bool_expr`]: https://rust-lang.github.io/rust-clippy/master/index.html#overly_complex_bool_expr
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic [`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn [`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params [`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap [`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl [`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
[`partialeq_to_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_to_none
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite [`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch [`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma

View file

@ -30,10 +30,10 @@ All contributors are expected to follow the [Rust Code of Conduct].
## The Clippy book ## The Clippy book
If you're new to Clippy and don't know where to start the [Clippy book] includes If you're new to Clippy and don't know where to start the [Clippy book] includes
a developer guide and is a good place to start your journey. a [developer guide] and is a good place to start your journey.
<!-- FIXME: Link to the deployed book, once it is deployed through CI --> [Clippy book]: https://doc.rust-lang.org/nightly/clippy/index.html
[Clippy book]: book/src [developer guide]: https://doc.rust-lang.org/nightly/clippy/development/index.html
## High level approach ## High level approach

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy" name = "clippy"
version = "0.1.64" version = "0.1.65"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"

View file

@ -144,7 +144,7 @@ value` mapping e.g.
```toml ```toml
avoid-breaking-exported-api = false avoid-breaking-exported-api = false
blacklisted-names = ["toto", "tata", "titi"] disallowed-names = ["toto", "tata", "titi"]
cognitive-complexity-threshold = 30 cognitive-complexity-threshold = 30
``` ```

View file

@ -7,7 +7,7 @@ basic `variable = value` mapping eg.
```toml ```toml
avoid-breaking-exported-api = false avoid-breaking-exported-api = false
blacklisted-names = ["toto", "tata", "titi"] disallowed-names = ["toto", "tata", "titi"]
cognitive-complexity-threshold = 30 cognitive-complexity-threshold = 30
``` ```

View file

@ -438,7 +438,7 @@ fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str>
let mut lint_context = None; let mut lint_context = None;
let mut iter = rustc_lexer::tokenize(&file_contents).map(|t| { let mut iter = rustc_lexer::tokenize(&file_contents).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len as usize;
offset = range.end; offset = range.end;
LintDeclSearchResult { LintDeclSearchResult {

View file

@ -836,7 +836,7 @@ pub(crate) struct LintDeclSearchResult<'a> {
fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) { fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
let mut offset = 0usize; let mut offset = 0usize;
let mut iter = tokenize(contents).map(|t| { let mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len as usize;
offset = range.end; offset = range.end;
LintDeclSearchResult { LintDeclSearchResult {
@ -899,7 +899,7 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec<Lint>) {
fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) { fn parse_deprecated_contents(contents: &str, lints: &mut Vec<DeprecatedLint>) {
let mut offset = 0usize; let mut offset = 0usize;
let mut iter = tokenize(contents).map(|t| { let mut iter = tokenize(contents).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len as usize;
offset = range.end; offset = range.end;
LintDeclSearchResult { LintDeclSearchResult {
@ -946,7 +946,7 @@ fn parse_renamed_contents(contents: &str, lints: &mut Vec<RenamedLint>) {
for line in contents.lines() { for line in contents.lines() {
let mut offset = 0usize; let mut offset = 0usize;
let mut iter = tokenize(line).map(|t| { let mut iter = tokenize(line).map(|t| {
let range = offset..offset + t.len; let range = offset..offset + t.len as usize;
offset = range.end; offset = range.end;
LintDeclSearchResult { LintDeclSearchResult {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy_lints" name = "clippy_lints"
version = "0.1.64" version = "0.1.65"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"

View file

@ -53,13 +53,14 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
if result_type_with_refs != result_type { if result_type_with_refs != result_type {
return; return;
} else if let Res::Local(binding_id) = path_res(cx, recv) } else if let Res::Local(binding_id) = path_res(cx, recv)
&& local_used_after_expr(cx, binding_id, recv) { && local_used_after_expr(cx, binding_id, recv)
{
return; return;
} }
} }
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
match method_segment.ident.as_str() { match method_segment.ident.as_str() {
"is_ok" if has_debug_impl(cx, substs.type_at(1)) => { "is_ok" if type_suitable_to_unwrap(cx, substs.type_at(1)) => {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
ASSERTIONS_ON_RESULT_STATES, ASSERTIONS_ON_RESULT_STATES,
@ -73,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
app, app,
); );
} }
"is_err" if has_debug_impl(cx, substs.type_at(0)) => { "is_err" if type_suitable_to_unwrap(cx, substs.type_at(0)) => {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
ASSERTIONS_ON_RESULT_STATES, ASSERTIONS_ON_RESULT_STATES,
@ -99,3 +100,7 @@ fn has_debug_impl<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
.get_diagnostic_item(sym::Debug) .get_diagnostic_item(sym::Debug)
.map_or(false, |debug| implements_trait(cx, ty, debug, &[])) .map_or(false, |debug| implements_trait(cx, ty, debug, &[]))
} }
fn type_suitable_to_unwrap<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
has_debug_impl(cx, ty) && !ty.is_unit() && !ty.is_never()
}

View file

@ -64,7 +64,7 @@ declare_clippy_lint! {
/// if a {} /// if a {}
/// ``` /// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub LOGIC_BUG, pub OVERLY_COMPLEX_BOOL_EXPR,
correctness, correctness,
"boolean expressions that contain terminals which can be eliminated" "boolean expressions that contain terminals which can be eliminated"
} }
@ -72,7 +72,7 @@ declare_clippy_lint! {
// For each pairs, both orders are considered. // For each pairs, both orders are considered.
const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")]; const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")];
declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, LOGIC_BUG]); declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]);
impl<'tcx> LateLintPass<'tcx> for NonminimalBool { impl<'tcx> LateLintPass<'tcx> for NonminimalBool {
fn check_fn( fn check_fn(
@ -396,7 +396,7 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 { if stats.terminals[i] != 0 && simplified_stats.terminals[i] == 0 {
span_lint_hir_and_then( span_lint_hir_and_then(
self.cx, self.cx,
LOGIC_BUG, OVERLY_COMPLEX_BOOL_EXPR,
e.hir_id, e.hir_id,
e.span, e.span,
"this boolean expression contains a logic bug", "this boolean expression contains a logic bug",

View file

@ -37,7 +37,7 @@ pub(super) fn check(
span, span,
&format!("casting the result of `{cast_from}::abs()` to {cast_to}"), &format!("casting the result of `{cast_from}::abs()` to {cast_to}"),
"replace with", "replace with",
format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")), format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..").maybe_par()),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }

View file

@ -270,10 +270,7 @@ fn get_types_from_cast<'a>(
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
if_chain! { if_chain! {
// `from_type::from, to_type::max_value()` // `from_type::from, to_type::max_value()`
if let ExprKind::Call(from_func, args) = &expr.kind; if let ExprKind::Call(from_func, [limit]) = &expr.kind;
// `to_type::max_value()`
if args.len() == 1;
if let limit = &args[0];
// `from_type::from` // `from_type::from`
if let ExprKind::Path(ref path) = &from_func.kind; if let ExprKind::Path(ref path) = &from_func.kind;
if let Some(from_sym) = get_implementing_type(path, INTS, "from"); if let Some(from_sym) = get_implementing_type(path, INTS, "from");

View file

@ -34,7 +34,7 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir { impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(func, args) = expr.kind; if let ExprKind::Call(func, [arg, ..]) = expr.kind;
if let ExprKind::Path(ref path) = func.kind; if let ExprKind::Path(ref path) = func.kind;
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR); if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR);
@ -45,7 +45,7 @@ impl LateLintPass<'_> for CreateDir {
expr.span, expr.span,
"calling `std::fs::create_dir` where there may be a better way", "calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead", "consider calling `std::fs::create_dir_all` instead",
format!("create_dir_all({})", snippet(cx, args[0].span, "..")), format!("create_dir_all({})", snippet(cx, arg.span, "..")),
Applicability::MaybeIncorrect, Applicability::MaybeIncorrect,
) )
} }

View file

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::ty::{has_drop, is_copy}; use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, match_def_path, paths}; use clippy_utils::{
any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro, match_def_path, paths,
};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -94,6 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
if let QPath::Resolved(None, _path) = qpath; if let QPath::Resolved(None, _path) = qpath;
let expr_ty = cx.typeck_results().expr_ty(expr); let expr_ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, ..) = expr_ty.kind(); if let ty::Adt(def, ..) = expr_ty.kind();
if !is_from_proc_macro(cx, expr);
then { then {
// TODO: Work out a way to put "whatever the imported way of referencing // TODO: Work out a way to put "whatever the imported way of referencing
// this type in this file" rather than a fully-qualified type. // this type in this file" rather than a fully-qualified type.

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, variant_of_res}; use clippy_utils::ty::{expr_sig, peel_mid_ty_refs, ty_sig, variant_of_res};
use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage}; use clippy_utils::{get_parent_expr, is_lint_allowed, path_to_local, walk_to_expr_usage};
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
@ -15,9 +15,9 @@ use rustc_hir::{
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable, TypeckResults}; use rustc_middle::ty::{self, Binder, BoundVariableKind, List, Ty, TyCtxt, TypeVisitable, TypeckResults};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span, Symbol}; use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP};
use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::infer::InferCtxtExt;
declare_clippy_lint! { declare_clippy_lint! {
@ -183,24 +183,24 @@ enum State {
}, },
DerefedBorrow(DerefedBorrow), DerefedBorrow(DerefedBorrow),
ExplicitDeref { ExplicitDeref {
// Span and id of the top-level deref expression if the parent expression is a borrow. mutability: Option<Mutability>,
deref_span_id: Option<(Span, HirId)>,
}, },
ExplicitDerefField { ExplicitDerefField {
name: Symbol, name: Symbol,
}, },
Reborrow { Reborrow {
deref_span: Span, mutability: Mutability,
deref_hir_id: HirId, },
Borrow {
mutability: Mutability,
}, },
Borrow,
} }
// A reference operation considered by this lint pass // A reference operation considered by this lint pass
enum RefOp { enum RefOp {
Method(Mutability), Method(Mutability),
Deref, Deref,
AddrOf, AddrOf(Mutability),
} }
struct RefPat { struct RefPat {
@ -263,7 +263,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
)); ));
} else if position.is_deref_stable() { } else if position.is_deref_stable() {
self.state = Some(( self.state = Some((
State::ExplicitDeref { deref_span_id: None }, State::ExplicitDeref { mutability: None },
StateData { span: expr.span, hir_id: expr.hir_id, position }, StateData { span: expr.span, hir_id: expr.hir_id, position },
)); ));
} }
@ -289,7 +289,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
}, },
)); ));
}, },
RefOp::AddrOf => { RefOp::AddrOf(mutability) => {
// Find the number of times the borrow is auto-derefed. // Find the number of times the borrow is auto-derefed.
let mut iter = adjustments.iter(); let mut iter = adjustments.iter();
let mut deref_count = 0usize; let mut deref_count = 0usize;
@ -357,9 +357,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
}), }),
StateData { span: expr.span, hir_id: expr.hir_id, position }, StateData { span: expr.span, hir_id: expr.hir_id, position },
)); ));
} else if position.is_deref_stable() { } else if position.is_deref_stable()
// Auto-deref doesn't combine with other adjustments
&& next_adjust.map_or(true, |a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_)))
&& iter.all(|a| matches!(a.kind, Adjust::Deref(_) | Adjust::Borrow(_)))
{
self.state = Some(( self.state = Some((
State::Borrow, State::Borrow { mutability },
StateData { StateData {
span: expr.span, span: expr.span,
hir_id: expr.hir_id, hir_id: expr.hir_id,
@ -395,7 +399,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
data, data,
)); ));
}, },
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) if state.count != 0 => { (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(_)) if state.count != 0 => {
self.state = Some(( self.state = Some((
State::DerefedBorrow(DerefedBorrow { State::DerefedBorrow(DerefedBorrow {
count: state.count - 1, count: state.count - 1,
@ -404,12 +408,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
data, data,
)); ));
}, },
(Some((State::DerefedBorrow(state), data)), RefOp::AddrOf) => { (Some((State::DerefedBorrow(state), data)), RefOp::AddrOf(mutability)) => {
let position = data.position; let position = data.position;
report(cx, expr, State::DerefedBorrow(state), data); report(cx, expr, State::DerefedBorrow(state), data);
if position.is_deref_stable() { if position.is_deref_stable() {
self.state = Some(( self.state = Some((
State::Borrow, State::Borrow { mutability },
StateData { StateData {
span: expr.span, span: expr.span,
hir_id: expr.hir_id, hir_id: expr.hir_id,
@ -430,43 +434,28 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing {
)); ));
} else if position.is_deref_stable() { } else if position.is_deref_stable() {
self.state = Some(( self.state = Some((
State::ExplicitDeref { deref_span_id: None }, State::ExplicitDeref { mutability: None },
StateData { span: expr.span, hir_id: expr.hir_id, position }, StateData { span: expr.span, hir_id: expr.hir_id, position },
)); ));
} }
}, },
(Some((State::Borrow, data)), RefOp::Deref) => { (Some((State::Borrow { mutability }, data)), RefOp::Deref) => {
if typeck.expr_ty(sub_expr).is_ref() { if typeck.expr_ty(sub_expr).is_ref() {
self.state = Some(( self.state = Some((State::Reborrow { mutability }, data));
State::Reborrow {
deref_span: expr.span,
deref_hir_id: expr.hir_id,
},
data,
));
} else { } else {
self.state = Some(( self.state = Some((
State::ExplicitDeref { State::ExplicitDeref {
deref_span_id: Some((expr.span, expr.hir_id)), mutability: Some(mutability),
}, },
data, data,
)); ));
} }
}, },
( (Some((State::Reborrow { mutability }, data)), RefOp::Deref) => {
Some((
State::Reborrow {
deref_span,
deref_hir_id,
},
data,
)),
RefOp::Deref,
) => {
self.state = Some(( self.state = Some((
State::ExplicitDeref { State::ExplicitDeref {
deref_span_id: Some((deref_span, deref_hir_id)), mutability: Some(mutability),
}, },
data, data,
)); ));
@ -573,7 +562,7 @@ fn try_parse_ref_op<'tcx>(
ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => { ExprKind::Unary(UnOp::Deref, sub_expr) if !typeck.expr_ty(sub_expr).is_unsafe_ptr() => {
return Some((RefOp::Deref, sub_expr)); return Some((RefOp::Deref, sub_expr));
}, },
ExprKind::AddrOf(BorrowKind::Ref, _, sub_expr) => return Some((RefOp::AddrOf, sub_expr)), ExprKind::AddrOf(BorrowKind::Ref, mutability, sub_expr) => return Some((RefOp::AddrOf(mutability), sub_expr)),
_ => return None, _ => return None,
}; };
if tcx.is_diagnostic_item(sym::deref_method, def_id) { if tcx.is_diagnostic_item(sym::deref_method, def_id) {
@ -609,18 +598,21 @@ enum Position {
Postfix, Postfix,
Deref, Deref,
/// Any other location which will trigger auto-deref to a specific time. /// Any other location which will trigger auto-deref to a specific time.
DerefStable(i8), /// Contains the precedence of the parent expression and whether the target type is sized.
DerefStable(i8, bool),
/// Any other location which will trigger auto-reborrowing. /// Any other location which will trigger auto-reborrowing.
/// Contains the precedence of the parent expression.
ReborrowStable(i8), ReborrowStable(i8),
/// Contains the precedence of the parent expression.
Other(i8), Other(i8),
} }
impl Position { impl Position {
fn is_deref_stable(self) -> bool { fn is_deref_stable(self) -> bool {
matches!(self, Self::DerefStable(_)) matches!(self, Self::DerefStable(..))
} }
fn is_reborrow_stable(self) -> bool { fn is_reborrow_stable(self) -> bool {
matches!(self, Self::DerefStable(_) | Self::ReborrowStable(_)) matches!(self, Self::DerefStable(..) | Self::ReborrowStable(_))
} }
fn can_auto_borrow(self) -> bool { fn can_auto_borrow(self) -> bool {
@ -628,7 +620,7 @@ impl Position {
} }
fn lint_explicit_deref(self) -> bool { fn lint_explicit_deref(self) -> bool {
matches!(self, Self::Other(_) | Self::DerefStable(_) | Self::ReborrowStable(_)) matches!(self, Self::Other(_) | Self::DerefStable(..) | Self::ReborrowStable(_))
} }
fn precedence(self) -> i8 { fn precedence(self) -> i8 {
@ -639,7 +631,7 @@ impl Position {
| Self::FieldAccess(_) | Self::FieldAccess(_)
| Self::Postfix => PREC_POSTFIX, | Self::Postfix => PREC_POSTFIX,
Self::Deref => PREC_PREFIX, Self::Deref => PREC_PREFIX,
Self::DerefStable(p) | Self::ReborrowStable(p) | Self::Other(p) => p, Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p,
} }
} }
} }
@ -659,7 +651,7 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
} }
match parent { match parent {
Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => { Node::Local(Local { ty: Some(ty), span, .. }) if span.ctxt() == ctxt => {
Some(binding_ty_auto_deref_stability(ty, precedence)) Some(binding_ty_auto_deref_stability(cx, ty, precedence, List::empty()))
}, },
Node::Item(&Item { Node::Item(&Item {
kind: ItemKind::Static(..) | ItemKind::Const(..), kind: ItemKind::Static(..) | ItemKind::Const(..),
@ -680,11 +672,7 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
.. ..
}) if span.ctxt() == ctxt => { }) if span.ctxt() == ctxt => {
let ty = cx.tcx.type_of(def_id); let ty = cx.tcx.type_of(def_id);
Some(if ty.is_ref() { Some(ty_auto_deref_stability(cx, ty, precedence).position_for_result(cx))
Position::DerefStable(precedence)
} else {
Position::Other(precedence)
})
}, },
Node::Item(&Item { Node::Item(&Item {
@ -705,45 +693,38 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
span, span,
.. ..
}) if span.ctxt() == ctxt => { }) if span.ctxt() == ctxt => {
let output = cx.tcx.fn_sig(def_id.to_def_id()).skip_binder().output(); let output = cx
Some(if !output.is_ref() { .tcx
Position::Other(precedence) .erase_late_bound_regions(cx.tcx.fn_sig(def_id.to_def_id()).output());
} else if output.has_placeholders() || output.has_opaque_types() { Some(ty_auto_deref_stability(cx, output, precedence).position_for_result(cx))
Position::ReborrowStable(precedence)
} else {
Position::DerefStable(precedence)
})
}, },
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
ExprKind::Ret(_) => { ExprKind::Ret(_) => {
let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap()); let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap());
Some( Some(
if let Node::Expr(Expr { if let Node::Expr(
kind: ExprKind::Closure(&Closure { fn_decl, .. }), closure_expr @ Expr {
.. kind: ExprKind::Closure(closure),
}) = cx.tcx.hir().get(owner_id) ..
},
) = cx.tcx.hir().get(owner_id)
{ {
match fn_decl.output { closure_result_position(cx, closure, cx.typeck_results().expr_ty(closure_expr), precedence)
FnRetTy::Return(ty) => binding_ty_auto_deref_stability(ty, precedence),
FnRetTy::DefaultReturn(_) => Position::Other(precedence),
}
} else { } else {
let output = cx let output = cx
.tcx .tcx
.fn_sig(cx.tcx.hir().local_def_id(owner_id)) .erase_late_bound_regions(cx.tcx.fn_sig(cx.tcx.hir().local_def_id(owner_id)).output());
.skip_binder() ty_auto_deref_stability(cx, output, precedence).position_for_result(cx)
.output();
if !output.is_ref() {
Position::Other(precedence)
} else if output.has_placeholders() || output.has_opaque_types() {
Position::ReborrowStable(precedence)
} else {
Position::DerefStable(precedence)
}
}, },
) )
}, },
ExprKind::Closure(closure) => Some(closure_result_position(
cx,
closure,
cx.typeck_results().expr_ty(parent),
precedence,
)),
ExprKind::Call(func, _) if func.hir_id == child_id => { ExprKind::Call(func, _) if func.hir_id == child_id => {
(child_id == e.hir_id).then_some(Position::Callee) (child_id == e.hir_id).then_some(Position::Callee)
}, },
@ -755,8 +736,9 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
.map(|(hir_ty, ty)| match hir_ty { .map(|(hir_ty, ty)| match hir_ty {
// Type inference for closures can depend on how they're called. Only go by the explicit // Type inference for closures can depend on how they're called. Only go by the explicit
// types here. // types here.
Some(ty) => binding_ty_auto_deref_stability(ty, precedence), Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()),
None => param_auto_deref_stability(ty.skip_binder(), precedence), None => ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
.position_for_arg(),
}), }),
ExprKind::MethodCall(_, args, _) => { ExprKind::MethodCall(_, args, _) => {
let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(); let id = cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap();
@ -797,7 +779,12 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
Position::MethodReceiver Position::MethodReceiver
} }
} else { } else {
param_auto_deref_stability(cx.tcx.fn_sig(id).skip_binder().inputs()[i], precedence) ty_auto_deref_stability(
cx,
cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).input(i)),
precedence,
)
.position_for_arg()
} }
}) })
}, },
@ -808,7 +795,9 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
.find(|f| f.expr.hir_id == child_id) .find(|f| f.expr.hir_id == child_id)
.zip(variant) .zip(variant)
.and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name)) .and_then(|(field, variant)| variant.fields.iter().find(|f| f.name == field.ident.name))
.map(|field| param_auto_deref_stability(cx.tcx.type_of(field.did), precedence)) .map(|field| {
ty_auto_deref_stability(cx, cx.tcx.type_of(field.did), precedence).position_for_arg()
})
}, },
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)), ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)),
ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref), ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref),
@ -831,6 +820,26 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
(position, adjustments) (position, adjustments)
} }
fn closure_result_position<'tcx>(
cx: &LateContext<'tcx>,
closure: &'tcx Closure<'_>,
ty: Ty<'tcx>,
precedence: i8,
) -> Position {
match closure.fn_decl.output {
FnRetTy::Return(hir_ty) => {
if let Some(sig) = ty_sig(cx, ty)
&& let Some(output) = sig.output()
{
binding_ty_auto_deref_stability(cx, hir_ty, precedence, output.bound_vars())
} else {
Position::Other(precedence)
}
},
FnRetTy::DefaultReturn(_) => Position::Other(precedence),
}
}
// Checks the stability of auto-deref when assigned to a binding with the given explicit type. // Checks the stability of auto-deref when assigned to a binding with the given explicit type.
// //
// e.g. // e.g.
@ -840,7 +849,12 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
// //
// Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when // Here `y1` and `y2` would resolve to different types, so the type `&Box<_>` is not stable when
// switching to auto-dereferencing. // switching to auto-dereferencing.
fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>, precedence: i8) -> Position { fn binding_ty_auto_deref_stability<'tcx>(
cx: &LateContext<'tcx>,
ty: &'tcx hir::Ty<'_>,
precedence: i8,
binder_args: &'tcx List<BoundVariableKind>,
) -> Position {
let TyKind::Rptr(_, ty) = &ty.kind else { let TyKind::Rptr(_, ty) = &ty.kind else {
return Position::Other(precedence); return Position::Other(precedence);
}; };
@ -870,21 +884,33 @@ fn binding_ty_auto_deref_stability(ty: &hir::Ty<'_>, precedence: i8) -> Position
{ {
Position::ReborrowStable(precedence) Position::ReborrowStable(precedence)
} else { } else {
Position::DerefStable(precedence) Position::DerefStable(
precedence,
cx.tcx
.erase_late_bound_regions(Binder::bind_with_vars(
cx.typeck_results().node_type(ty.ty.hir_id),
binder_args,
))
.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env.without_caller_bounds()),
)
} }
}, },
TyKind::Slice(_) TyKind::Slice(_) => Position::DerefStable(precedence, false),
| TyKind::Array(..) TyKind::Array(..) | TyKind::Ptr(_) | TyKind::BareFn(_) => Position::DerefStable(precedence, true),
| TyKind::BareFn(_) TyKind::Never
| TyKind::Never
| TyKind::Tup(_) | TyKind::Tup(_)
| TyKind::Ptr(_) | TyKind::Path(_) => Position::DerefStable(
| TyKind::TraitObject(..) precedence,
| TyKind::Path(_) => Position::DerefStable(precedence), cx.tcx
TyKind::OpaqueDef(..) .erase_late_bound_regions(Binder::bind_with_vars(
| TyKind::Infer cx.typeck_results().node_type(ty.ty.hir_id),
| TyKind::Typeof(..) binder_args,
| TyKind::Err => Position::ReborrowStable(precedence), ))
.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env.without_caller_bounds()),
),
TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err => {
Position::ReborrowStable(precedence)
},
}; };
} }
} }
@ -920,10 +946,39 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool {
v.0 v.0
} }
struct TyPosition<'tcx> {
position: Position,
ty: Option<Ty<'tcx>>,
}
impl From<Position> for TyPosition<'_> {
fn from(position: Position) -> Self {
Self { position, ty: None }
}
}
impl<'tcx> TyPosition<'tcx> {
fn new_deref_stable_for_result(precedence: i8, ty: Ty<'tcx>) -> Self {
Self {
position: Position::ReborrowStable(precedence),
ty: Some(ty),
}
}
fn position_for_result(self, cx: &LateContext<'tcx>) -> Position {
match (self.position, self.ty) {
(Position::ReborrowStable(precedence), Some(ty)) => {
Position::DerefStable(precedence, ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env))
},
(position, _) => position,
}
}
fn position_for_arg(self) -> Position {
self.position
}
}
// Checks whether a type is stable when switching to auto dereferencing, // Checks whether a type is stable when switching to auto dereferencing,
fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position { fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedence: i8) -> TyPosition<'tcx> {
let ty::Ref(_, mut ty, _) = *ty.kind() else { let ty::Ref(_, mut ty, _) = *ty.kind() else {
return Position::Other(precedence); return Position::Other(precedence).into();
}; };
loop { loop {
@ -932,35 +987,38 @@ fn param_auto_deref_stability(ty: Ty<'_>, precedence: i8) -> Position {
ty = ref_ty; ty = ref_ty;
continue; continue;
}, },
ty::Infer(_) ty::Param(_) => TyPosition::new_deref_stable_for_result(precedence, ty),
| ty::Error(_) ty::Infer(_) | ty::Error(_) | ty::Bound(..) | ty::Opaque(..) | ty::Placeholder(_) | ty::Dynamic(..) => {
| ty::Param(_) Position::ReborrowStable(precedence).into()
| ty::Bound(..)
| ty::Opaque(..)
| ty::Placeholder(_)
| ty::Dynamic(..) => Position::ReborrowStable(precedence),
ty::Adt(..) if ty.has_placeholders() || ty.has_param_types_or_consts() => {
Position::ReborrowStable(precedence)
}, },
ty::Adt(..) ty::Adt(..) if ty.has_placeholders() || ty.has_opaque_types() => {
| ty::Bool Position::ReborrowStable(precedence).into()
},
ty::Adt(_, substs) if substs.has_param_types_or_consts() => {
TyPosition::new_deref_stable_for_result(precedence, ty)
},
ty::Bool
| ty::Char | ty::Char
| ty::Int(_) | ty::Int(_)
| ty::Uint(_) | ty::Uint(_)
| ty::Float(_)
| ty::Foreign(_)
| ty::Str
| ty::Array(..) | ty::Array(..)
| ty::Slice(..) | ty::Float(_)
| ty::RawPtr(..) | ty::RawPtr(..)
| ty::FnPtr(_) => Position::DerefStable(precedence, true).into(),
ty::Str | ty::Slice(..) => Position::DerefStable(precedence, false).into(),
ty::Adt(..)
| ty::Foreign(_)
| ty::FnDef(..) | ty::FnDef(..)
| ty::FnPtr(_)
| ty::Closure(..)
| ty::Generator(..) | ty::Generator(..)
| ty::GeneratorWitness(..) | ty::GeneratorWitness(..)
| ty::Closure(..)
| ty::Never | ty::Never
| ty::Tuple(_) | ty::Tuple(_)
| ty::Projection(_) => Position::DerefStable(precedence), | ty::Projection(_) => Position::DerefStable(
precedence,
ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env.without_caller_bounds()),
)
.into(),
}; };
} }
} }
@ -1040,34 +1098,64 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
diag.span_suggestion(data.span, "change this to", sugg, app); diag.span_suggestion(data.span, "change this to", sugg, app);
}); });
}, },
State::ExplicitDeref { deref_span_id } => { State::ExplicitDeref { mutability } => {
let (span, hir_id, precedence) = if let Some((span, hir_id)) = deref_span_id if matches!(
expr.kind,
ExprKind::Block(..)
| ExprKind::ConstBlock(_)
| ExprKind::If(..)
| ExprKind::Loop(..)
| ExprKind::Match(..)
) && matches!(data.position, Position::DerefStable(_, true))
{
// Rustc bug: auto deref doesn't work on block expression when targeting sized types.
return;
}
let (prefix, precedence) = if let Some(mutability) = mutability
&& !cx.typeck_results().expr_ty(expr).is_ref() && !cx.typeck_results().expr_ty(expr).is_ref()
{ {
(span, hir_id, PREC_PREFIX) let prefix = match mutability {
Mutability::Not => "&",
Mutability::Mut => "&mut ",
};
(prefix, 0)
} else { } else {
(data.span, data.hir_id, data.position.precedence()) ("", data.position.precedence())
}; };
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
EXPLICIT_AUTO_DEREF, EXPLICIT_AUTO_DEREF,
hir_id, data.hir_id,
span, data.span,
"deref which would be done by auto-deref", "deref which would be done by auto-deref",
|diag| { |diag| {
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, span.ctxt(), "..", &mut app); let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app);
let sugg = let sugg =
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) { if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
format!("({})", snip) format!("{}({})", prefix, snip)
} else { } else {
snip.into() format!("{}{}", prefix, snip)
}; };
diag.span_suggestion(span, "try this", sugg, app); diag.span_suggestion(data.span, "try this", sugg, app);
}, },
); );
}, },
State::ExplicitDerefField { .. } => { State::ExplicitDerefField { .. } => {
if matches!(
expr.kind,
ExprKind::Block(..)
| ExprKind::ConstBlock(_)
| ExprKind::If(..)
| ExprKind::Loop(..)
| ExprKind::Match(..)
) && matches!(data.position, Position::DerefStable(_, true))
{
// Rustc bug: auto deref doesn't work on block expression when targeting sized types.
return;
}
span_lint_hir_and_then( span_lint_hir_and_then(
cx, cx,
EXPLICIT_AUTO_DEREF, EXPLICIT_AUTO_DEREF,
@ -1081,7 +1169,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
}, },
); );
}, },
State::Borrow | State::Reborrow { .. } => (), State::Borrow { .. } | State::Reborrow { .. } => (),
} }
} }

View file

@ -6,7 +6,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for usage of blacklisted names for variables, such /// Checks for usage of disallowed names for variables, such
/// as `foo`. /// as `foo`.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
@ -18,21 +18,21 @@ declare_clippy_lint! {
/// let foo = 3.14; /// let foo = 3.14;
/// ``` /// ```
#[clippy::version = "pre 1.29.0"] #[clippy::version = "pre 1.29.0"]
pub BLACKLISTED_NAME, pub DISALLOWED_NAMES,
style, style,
"usage of a blacklisted/placeholder name" "usage of a disallowed/placeholder name"
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BlacklistedName { pub struct DisallowedNames {
blacklist: FxHashSet<String>, disallow: FxHashSet<String>,
test_modules_deep: u32, test_modules_deep: u32,
} }
impl BlacklistedName { impl DisallowedNames {
pub fn new(blacklist: FxHashSet<String>) -> Self { pub fn new(disallow: FxHashSet<String>) -> Self {
Self { Self {
blacklist, disallow,
test_modules_deep: 0, test_modules_deep: 0,
} }
} }
@ -42,9 +42,9 @@ impl BlacklistedName {
} }
} }
impl_lint_pass!(BlacklistedName => [BLACKLISTED_NAME]); impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]);
impl<'tcx> LateLintPass<'tcx> for BlacklistedName { impl<'tcx> LateLintPass<'tcx> for DisallowedNames {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
if is_test_module_or_function(cx.tcx, item) { if is_test_module_or_function(cx.tcx, item) {
self.test_modules_deep = self.test_modules_deep.saturating_add(1); self.test_modules_deep = self.test_modules_deep.saturating_add(1);
@ -58,12 +58,12 @@ impl<'tcx> LateLintPass<'tcx> for BlacklistedName {
} }
if let PatKind::Binding(.., ident, _) = pat.kind { if let PatKind::Binding(.., ident, _) = pat.kind {
if self.blacklist.contains(&ident.name.to_string()) { if self.disallow.contains(&ident.name.to_string()) {
span_lint( span_lint(
cx, cx,
BLACKLISTED_NAME, DISALLOWED_NAMES,
ident.span, ident.span,
&format!("use of a blacklisted/placeholder name `{}`", ident.name), &format!("use of a disallowed/placeholder name `{}`", ident.name),
); );
} }
} }

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
use clippy_utils::is_span_if;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
@ -297,12 +298,11 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) { fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
if_chain! { if_chain! {
if !first.span.from_expansion() && !second.span.from_expansion(); if !first.span.from_expansion() && !second.span.from_expansion();
if let ExprKind::If(cond_expr, ..) = &first.kind; if matches!(first.kind, ExprKind::If(..));
if is_block(second) || is_if(second); if is_block(second) || is_if(second);
// Proc-macros can give weird spans. Make sure this is actually an `if`. // Proc-macros can give weird spans. Make sure this is actually an `if`.
if let Some(if_snip) = snippet_opt(cx, first.span.until(cond_expr.span)); if is_span_if(cx, first.span);
if if_snip.starts_with("if");
// If there is a line break between the two expressions, don't lint. // If there is a line break between the two expressions, don't lint.
// If there is a non-whitespace character, this span came from a proc-macro. // If there is a non-whitespace character, this span came from a proc-macro.

View file

@ -46,7 +46,7 @@ declare_lint_pass!(FromStrRadix10 => [FROM_STR_RADIX_10]);
impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
if_chain! { if_chain! {
if let ExprKind::Call(maybe_path, arguments) = &exp.kind; if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind; if let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind;
// check if the first part of the path is some integer primitive // check if the first part of the path is some integer primitive
@ -60,20 +60,19 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
if pathseg.ident.name.as_str() == "from_str_radix"; if pathseg.ident.name.as_str() == "from_str_radix";
// check if the second argument is a primitive `10` // check if the second argument is a primitive `10`
if arguments.len() == 2; if let ExprKind::Lit(lit) = &radix.kind;
if let ExprKind::Lit(lit) = &arguments[1].kind;
if let rustc_ast::ast::LitKind::Int(10, _) = lit.node; if let rustc_ast::ast::LitKind::Int(10, _) = lit.node;
then { then {
let expr = if let ExprKind::AddrOf(_, _, expr) = &arguments[0].kind { let expr = if let ExprKind::AddrOf(_, _, expr) = &src.kind {
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
if is_ty_stringish(cx, ty) { if is_ty_stringish(cx, ty) {
expr expr
} else { } else {
&arguments[0] &src
} }
} else { } else {
&arguments[0] &src
}; };
let sugg = Sugg::hir_with_applicability( let sugg = Sugg::hir_with_applicability(

View file

@ -15,11 +15,10 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE), LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL), LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(booleans::OVERLY_COMPLEX_BOOL_EXPR),
LintId::of(borrow_deref_ref::BORROW_DEREF_REF), LintId::of(borrow_deref_ref::BORROW_DEREF_REF),
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN), LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CAST_ABS_TO_UNSIGNED), LintId::of(casts::CAST_ABS_TO_UNSIGNED),
@ -46,6 +45,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD), LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ), LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_methods::DISALLOWED_METHODS),
LintId::of(disallowed_names::DISALLOWED_NAMES),
LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(disallowed_types::DISALLOWED_TYPES),
LintId::of(doc::MISSING_SAFETY_DOC), LintId::of(doc::MISSING_SAFETY_DOC),
LintId::of(doc::NEEDLESS_DOCTEST_MAIN), LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
@ -144,7 +144,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(matches::MATCH_STR_CASE_MISMATCH), LintId::of(matches::MATCH_STR_CASE_MISMATCH),
LintId::of(matches::NEEDLESS_MATCH), LintId::of(matches::NEEDLESS_MATCH),
LintId::of(matches::REDUNDANT_PATTERN_MATCHING), LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(matches::SINGLE_MATCH), LintId::of(matches::SINGLE_MATCH),
LintId::of(matches::WILDCARD_IN_OR_PATTERNS), LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE), LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
@ -267,6 +266,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL), LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL), LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
LintId::of(partialeq_to_none::PARTIALEQ_TO_NONE),
LintId::of(precedence::PRECEDENCE), LintId::of(precedence::PRECEDENCE),
LintId::of(ptr::CMP_NULL), LintId::of(ptr::CMP_NULL),
LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::INVALID_NULL_PTR_USAGE),

View file

@ -8,7 +8,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(attrs::DEPRECATED_SEMVER), LintId::of(attrs::DEPRECATED_SEMVER),
LintId::of(attrs::MISMATCHED_TARGET_OS), LintId::of(attrs::MISMATCHED_TARGET_OS),
LintId::of(attrs::USELESS_ATTRIBUTE), LintId::of(attrs::USELESS_ATTRIBUTE),
LintId::of(booleans::LOGIC_BUG), LintId::of(booleans::OVERLY_COMPLEX_BOOL_EXPR),
LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_REF_TO_MUT),
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
LintId::of(copies::IFS_SAME_COND), LintId::of(copies::IFS_SAME_COND),

View file

@ -55,11 +55,10 @@ store.register_lints(&[
await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE, await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE,
await_holding_invalid::AWAIT_HOLDING_LOCK, await_holding_invalid::AWAIT_HOLDING_LOCK,
await_holding_invalid::AWAIT_HOLDING_REFCELL_REF, await_holding_invalid::AWAIT_HOLDING_REFCELL_REF,
blacklisted_name::BLACKLISTED_NAME,
blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS, blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
bool_assert_comparison::BOOL_ASSERT_COMPARISON, bool_assert_comparison::BOOL_ASSERT_COMPARISON,
booleans::LOGIC_BUG,
booleans::NONMINIMAL_BOOL, booleans::NONMINIMAL_BOOL,
booleans::OVERLY_COMPLEX_BOOL_EXPR,
borrow_as_ptr::BORROW_AS_PTR, borrow_as_ptr::BORROW_AS_PTR,
borrow_deref_ref::BORROW_DEREF_REF, borrow_deref_ref::BORROW_DEREF_REF,
bytecount::NAIVE_BYTECOUNT, bytecount::NAIVE_BYTECOUNT,
@ -116,6 +115,7 @@ store.register_lints(&[
derive::EXPL_IMPL_CLONE_ON_COPY, derive::EXPL_IMPL_CLONE_ON_COPY,
derive::UNSAFE_DERIVE_DESERIALIZE, derive::UNSAFE_DERIVE_DESERIALIZE,
disallowed_methods::DISALLOWED_METHODS, disallowed_methods::DISALLOWED_METHODS,
disallowed_names::DISALLOWED_NAMES,
disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS, disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS,
disallowed_types::DISALLOWED_TYPES, disallowed_types::DISALLOWED_TYPES,
doc::DOC_MARKDOWN, doc::DOC_MARKDOWN,
@ -244,6 +244,7 @@ store.register_lints(&[
manual_assert::MANUAL_ASSERT, manual_assert::MANUAL_ASSERT,
manual_async_fn::MANUAL_ASYNC_FN, manual_async_fn::MANUAL_ASYNC_FN,
manual_bits::MANUAL_BITS, manual_bits::MANUAL_BITS,
manual_instant_elapsed::MANUAL_INSTANT_ELAPSED,
manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE, manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
manual_ok_or::MANUAL_OK_OR, manual_ok_or::MANUAL_OK_OR,
manual_rem_euclid::MANUAL_REM_EUCLID, manual_rem_euclid::MANUAL_REM_EUCLID,
@ -453,6 +454,7 @@ store.register_lints(&[
panic_unimplemented::UNIMPLEMENTED, panic_unimplemented::UNIMPLEMENTED,
panic_unimplemented::UNREACHABLE, panic_unimplemented::UNREACHABLE,
partialeq_ne_impl::PARTIALEQ_NE_IMPL, partialeq_ne_impl::PARTIALEQ_NE_IMPL,
partialeq_to_none::PARTIALEQ_TO_NONE,
pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE, pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE,
pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF, pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF,
path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,

View file

@ -13,6 +13,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(future_not_send::FUTURE_NOT_SEND), LintId::of(future_not_send::FUTURE_NOT_SEND),
LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE),
LintId::of(let_if_seq::USELESS_LET_IF_SEQ), LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::ITER_WITH_DRAIN),
LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),

View file

@ -49,6 +49,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(loops::EXPLICIT_ITER_LOOP), LintId::of(loops::EXPLICIT_ITER_LOOP),
LintId::of(macro_use::MACRO_USE_IMPORTS), LintId::of(macro_use::MACRO_USE_IMPORTS),
LintId::of(manual_assert::MANUAL_ASSERT), LintId::of(manual_assert::MANUAL_ASSERT),
LintId::of(manual_instant_elapsed::MANUAL_INSTANT_ELAPSED),
LintId::of(manual_ok_or::MANUAL_OK_OR), LintId::of(manual_ok_or::MANUAL_OK_OR),
LintId::of(matches::MATCH_BOOL), LintId::of(matches::MATCH_BOOL),
LintId::of(matches::MATCH_ON_VEC_ITEMS), LintId::of(matches::MATCH_ON_VEC_ITEMS),

View file

@ -4,7 +4,6 @@
store.register_group(true, "clippy::style", Some("clippy_style"), vec![ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS), LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(casts::FN_TO_NUMERIC_CAST), LintId::of(casts::FN_TO_NUMERIC_CAST),
@ -17,6 +16,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(dereference::NEEDLESS_BORROW), LintId::of(dereference::NEEDLESS_BORROW),
LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ), LintId::of(derive::DERIVE_PARTIAL_EQ_WITHOUT_EQ),
LintId::of(disallowed_methods::DISALLOWED_METHODS), LintId::of(disallowed_methods::DISALLOWED_METHODS),
LintId::of(disallowed_names::DISALLOWED_NAMES),
LintId::of(disallowed_types::DISALLOWED_TYPES), LintId::of(disallowed_types::DISALLOWED_TYPES),
LintId::of(doc::MISSING_SAFETY_DOC), LintId::of(doc::MISSING_SAFETY_DOC),
LintId::of(doc::NEEDLESS_DOCTEST_MAIN), LintId::of(doc::NEEDLESS_DOCTEST_MAIN),
@ -100,6 +100,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(operators::ASSIGN_OP_PATTERN), LintId::of(operators::ASSIGN_OP_PATTERN),
LintId::of(operators::OP_REF), LintId::of(operators::OP_REF),
LintId::of(operators::PTR_EQ), LintId::of(operators::PTR_EQ),
LintId::of(partialeq_to_none::PARTIALEQ_TO_NONE),
LintId::of(ptr::CMP_NULL), LintId::of(ptr::CMP_NULL),
LintId::of(ptr::PTR_ARG), LintId::of(ptr::PTR_ARG),
LintId::of(question_mark::QUESTION_MARK), LintId::of(question_mark::QUESTION_MARK),

View file

@ -22,7 +22,6 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(loops::EMPTY_LOOP), LintId::of(loops::EMPTY_LOOP),
LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
LintId::of(loops::MUT_RANGE_BOUND), LintId::of(loops::MUT_RANGE_BOUND),
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(methods::NO_EFFECT_REPLACE), LintId::of(methods::NO_EFFECT_REPLACE),
LintId::of(methods::SUSPICIOUS_MAP), LintId::of(methods::SUSPICIOUS_MAP),
LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(mut_key::MUTABLE_KEY_TYPE),

View file

@ -178,7 +178,6 @@ mod assertions_on_result_states;
mod async_yields_async; mod async_yields_async;
mod attrs; mod attrs;
mod await_holding_invalid; mod await_holding_invalid;
mod blacklisted_name;
mod blocks_in_if_conditions; mod blocks_in_if_conditions;
mod bool_assert_comparison; mod bool_assert_comparison;
mod booleans; mod booleans;
@ -206,6 +205,7 @@ mod dereference;
mod derivable_impls; mod derivable_impls;
mod derive; mod derive;
mod disallowed_methods; mod disallowed_methods;
mod disallowed_names;
mod disallowed_script_idents; mod disallowed_script_idents;
mod disallowed_types; mod disallowed_types;
mod doc; mod doc;
@ -274,6 +274,7 @@ mod main_recursion;
mod manual_assert; mod manual_assert;
mod manual_async_fn; mod manual_async_fn;
mod manual_bits; mod manual_bits;
mod manual_instant_elapsed;
mod manual_non_exhaustive; mod manual_non_exhaustive;
mod manual_ok_or; mod manual_ok_or;
mod manual_rem_euclid; mod manual_rem_euclid;
@ -332,6 +333,7 @@ mod overflow_check_conditional;
mod panic_in_result_fn; mod panic_in_result_fn;
mod panic_unimplemented; mod panic_unimplemented;
mod partialeq_ne_impl; mod partialeq_ne_impl;
mod partialeq_to_none;
mod pass_by_ref_or_value; mod pass_by_ref_or_value;
mod path_buf_push_overwrite; mod path_buf_push_overwrite;
mod pattern_type_mismatch; mod pattern_type_mismatch;
@ -487,7 +489,7 @@ pub fn read_conf(sess: &Session) -> Conf {
}, },
}; };
let TryConf { conf, errors } = utils::conf::read(&file_name); let TryConf { conf, errors, warnings } = utils::conf::read(&file_name);
// all conf errors are non-fatal, we just use the default conf in case of error // all conf errors are non-fatal, we just use the default conf in case of error
for error in errors { for error in errors {
sess.err(&format!( sess.err(&format!(
@ -497,6 +499,15 @@ pub fn read_conf(sess: &Session) -> Conf {
)); ));
} }
for warning in warnings {
sess.struct_warn(&format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
format_error(warning)
))
.emit();
}
conf conf
} }
@ -675,8 +686,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(swap::Swap)); store.register_late_pass(|| Box::new(swap::Swap));
store.register_late_pass(|| Box::new(overflow_check_conditional::OverflowCheckConditional)); store.register_late_pass(|| Box::new(overflow_check_conditional::OverflowCheckConditional));
store.register_late_pass(|| Box::new(new_without_default::NewWithoutDefault::default())); store.register_late_pass(|| Box::new(new_without_default::NewWithoutDefault::default()));
let blacklisted_names = conf.blacklisted_names.iter().cloned().collect::<FxHashSet<_>>(); let disallowed_names = conf.disallowed_names.iter().cloned().collect::<FxHashSet<_>>();
store.register_late_pass(move || Box::new(blacklisted_name::BlacklistedName::new(blacklisted_names.clone()))); store.register_late_pass(move || Box::new(disallowed_names::DisallowedNames::new(disallowed_names.clone())));
let too_many_arguments_threshold = conf.too_many_arguments_threshold; let too_many_arguments_threshold = conf.too_many_arguments_threshold;
let too_many_lines_threshold = conf.too_many_lines_threshold; let too_many_lines_threshold = conf.too_many_lines_threshold;
store.register_late_pass(move || { store.register_late_pass(move || {
@ -921,6 +932,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold))); store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked)); store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports::default())); store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports::default()));
store.register_late_pass(|| Box::new(manual_instant_elapsed::ManualInstantElapsed));
store.register_late_pass(|| Box::new(partialeq_to_none::PartialeqToNone));
// add lints here, do not remove this comment, it's used in `new_lint` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View file

@ -119,11 +119,9 @@ fn build_manual_memcpy_suggestion<'tcx>(
let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| {
if_chain! { if_chain! {
if let ExprKind::MethodCall(method, len_args, _) = end.kind; if let ExprKind::MethodCall(method, [recv], _) = end.kind;
if method.ident.name == sym::len; if method.ident.name == sym::len;
if len_args.len() == 1; if path_to_local(recv) == path_to_local(base);
if let Some(arg) = len_args.get(0);
if path_to_local(arg) == path_to_local(base);
then { then {
if sugg.to_string() == end_str { if sugg.to_string() == end_str {
sugg::EMPTY.into() sugg::EMPTY.into()
@ -343,10 +341,8 @@ fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Opti
fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
if_chain! { if_chain! {
if let ExprKind::MethodCall(method, args, _) = expr.kind; if let ExprKind::MethodCall(method, [arg], _) = expr.kind;
if method.ident.name == sym::clone; if method.ident.name == sym::clone;
if args.len() == 1;
if let Some(arg) = args.get(0);
then { arg } else { expr } then { arg } else { expr }
} }
} }

View file

@ -188,10 +188,9 @@ pub(super) fn check<'tcx>(
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
if_chain! { if_chain! {
if let ExprKind::MethodCall(method, len_args, _) = expr.kind; if let ExprKind::MethodCall(method, [recv], _) = expr.kind;
if len_args.len() == 1;
if method.ident.name == sym::len; if method.ident.name == sym::len;
if let ExprKind::Path(QPath::Resolved(_, path)) = len_args[0].kind; if let ExprKind::Path(QPath::Resolved(_, path)) = recv.kind;
if path.segments.len() == 1; if path.segments.len() == 1;
if path.segments[0].ident.name == var; if path.segments[0].ident.name == var;
then { then {

View file

@ -0,0 +1,69 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
/// Lints subtraction between `Instant::now()` and another `Instant`.
///
/// ### Why is this bad?
/// It is easy to accidentally write `prev_instant - Instant::now()`, which will always be 0ns
/// as `Instant` subtraction saturates.
///
/// `prev_instant.elapsed()` also more clearly signals intention.
///
/// ### Example
/// ```rust
/// use std::time::Instant;
/// let prev_instant = Instant::now();
/// let duration = Instant::now() - prev_instant;
/// ```
/// Use instead:
/// ```rust
/// use std::time::Instant;
/// let prev_instant = Instant::now();
/// let duration = prev_instant.elapsed();
/// ```
#[clippy::version = "1.64.0"]
pub MANUAL_INSTANT_ELAPSED,
pedantic,
"subtraction between `Instant::now()` and previous `Instant`"
}
declare_lint_pass!(ManualInstantElapsed => [MANUAL_INSTANT_ELAPSED]);
impl LateLintPass<'_> for ManualInstantElapsed {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if let ExprKind::Binary(Spanned {node: BinOpKind::Sub, ..}, lhs, rhs) = expr.kind
&& check_instant_now_call(cx, lhs)
&& let ty_resolved = cx.typeck_results().expr_ty(rhs)
&& let rustc_middle::ty::Adt(def, _) = ty_resolved.kind()
&& clippy_utils::match_def_path(cx, def.did(), &clippy_utils::paths::INSTANT)
&& let Some(sugg) = clippy_utils::sugg::Sugg::hir_opt(cx, rhs)
{
span_lint_and_sugg(
cx,
MANUAL_INSTANT_ELAPSED,
expr.span,
"manual implementation of `Instant::elapsed`",
"try",
format!("{}.elapsed()", sugg.maybe_par()),
Applicability::MachineApplicable,
);
}
}
}
fn check_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool {
if let ExprKind::Call(fn_expr, []) = expr_block.kind
&& let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr)
&& clippy_utils::match_def_path(cx, fn_id, &clippy_utils::paths::INSTANT_NOW)
{
true
} else {
false
}
}

View file

@ -47,17 +47,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualOkOr {
} }
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_segment, args, _) = scrutinee.kind; if let ExprKind::MethodCall(method_segment, [receiver, or_expr, map_expr], _) = scrutinee.kind;
if method_segment.ident.name == sym!(map_or); if method_segment.ident.name == sym!(map_or);
if args.len() == 3; let ty = cx.typeck_results().expr_ty(receiver);
let method_receiver = &args[0];
let ty = cx.typeck_results().expr_ty(method_receiver);
if is_type_diagnostic_item(cx, ty, sym::Option); if is_type_diagnostic_item(cx, ty, sym::Option);
let or_expr = &args[1]; if is_ok_wrapping(cx, map_expr);
if is_ok_wrapping(cx, &args[2]);
if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind; if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind;
if is_lang_ctor(cx, err_path, ResultErr); if is_lang_ctor(cx, err_path, ResultErr);
if let Some(method_receiver_snippet) = snippet_opt(cx, method_receiver.span); if let Some(method_receiver_snippet) = snippet_opt(cx, receiver.span);
if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span); if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span);
if let Some(indent) = indent_of(cx, scrutinee.span); if let Some(indent) = indent_of(cx, scrutinee.span);
then { then {

View file

@ -113,10 +113,10 @@ impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
} }
// check if this is a method call (e.g. x.foo()) // check if this is a method call (e.g. x.foo())
if let ExprKind::MethodCall(method, args, _) = e.kind { if let ExprKind::MethodCall(method, [_, arg], _) = e.kind {
// only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1] // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
// Enum::Variant[2])) // Enum::Variant[2]))
if method.ident.as_str() == "map_err" && args.len() == 2 { if method.ident.name == sym!(map_err) {
// make sure the first argument is a closure, and grab the CaptureRef, BodyId, and fn_decl_span // make sure the first argument is a closure, and grab the CaptureRef, BodyId, and fn_decl_span
// fields // fields
if let ExprKind::Closure(&Closure { if let ExprKind::Closure(&Closure {
@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
body, body,
fn_decl_span, fn_decl_span,
.. ..
}) = args[1].kind }) = arg.kind
{ {
// check if this is by Reference (meaning there's no move statement) // check if this is by Reference (meaning there's no move statement)
if capture_clause == CaptureBy::Ref { if capture_clause == CaptureBy::Ref {

View file

@ -97,11 +97,7 @@ declare_clippy_lint! {
declare_lint_pass!(MapUnit => [OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN]); declare_lint_pass!(MapUnit => [OPTION_MAP_UNIT_FN, RESULT_MAP_UNIT_FN]);
fn is_unit_type(ty: Ty<'_>) -> bool { fn is_unit_type(ty: Ty<'_>) -> bool {
match ty.kind() { ty.is_unit() || ty.is_never()
ty::Tuple(slice) => slice.is_empty(),
ty::Never => true,
_ => false,
}
} }
fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {

View file

@ -72,10 +72,10 @@ fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotat
if is_lang_ctor(cx, qpath, LangItem::OptionSome); if is_lang_ctor(cx, qpath, LangItem::OptionSome);
if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; if let PatKind::Binding(rb, .., ident, _) = first_pat.kind;
if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; if let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind;
if let ExprKind::Path(ref some_path) = e.kind; if let ExprKind::Path(ref some_path) = e.kind;
if is_lang_ctor(cx, some_path, LangItem::OptionSome) && args.len() == 1; if is_lang_ctor(cx, some_path, LangItem::OptionSome);
if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; if let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind;
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
then { then {
return Some(rb) return Some(rb)

View file

@ -21,8 +21,8 @@ mod single_match;
mod try_err; mod try_err;
mod wild_in_or_pats; mod wild_in_or_pats;
use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context}; use clippy_utils::source::{snippet_opt, walk_span_to_context};
use clippy_utils::{higher, in_constant, meets_msrv, msrvs}; use clippy_utils::{higher, in_constant, is_span_match, meets_msrv, msrvs};
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat}; use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
use rustc_lexer::{tokenize, TokenKind}; use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -835,7 +835,7 @@ declare_clippy_lint! {
/// ``` /// ```
#[clippy::version = "1.60.0"] #[clippy::version = "1.60.0"]
pub SIGNIFICANT_DROP_IN_SCRUTINEE, pub SIGNIFICANT_DROP_IN_SCRUTINEE,
suspicious, nursery,
"warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime" "warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime"
} }
@ -949,7 +949,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
let from_expansion = expr.span.from_expansion(); let from_expansion = expr.span.from_expansion();
if let ExprKind::Match(ex, arms, source) = expr.kind { if let ExprKind::Match(ex, arms, source) = expr.kind {
if source == MatchSource::Normal && !span_starts_with(cx, expr.span, "match") { if source == MatchSource::Normal && !is_span_match(cx, expr.span) {
return; return;
} }
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) { if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {

View file

@ -23,12 +23,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine
// val, // val,
// }; // };
if_chain! { if_chain! {
if let ExprKind::Call(match_fun, try_args) = scrutinee.kind; if let ExprKind::Call(match_fun, [try_arg, ..]) = scrutinee.kind;
if let ExprKind::Path(ref match_fun_path) = match_fun.kind; if let ExprKind::Path(ref match_fun_path) = match_fun.kind;
if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..)); if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..));
if let Some(try_arg) = try_args.get(0); if let ExprKind::Call(err_fun, [err_arg, ..]) = try_arg.kind;
if let ExprKind::Call(err_fun, err_args) = try_arg.kind;
if let Some(err_arg) = err_args.get(0);
if let ExprKind::Path(ref err_fun_path) = err_fun.kind; if let ExprKind::Path(ref err_fun_path) = err_fun.kind;
if is_lang_ctor(cx, err_fun_path, ResultErr); if is_lang_ctor(cx, err_fun_path, ResultErr);
if let Some(return_ty) = find_return_type(cx, &expr.kind); if let Some(return_ty) = find_return_type(cx, &expr.kind);

View file

@ -163,8 +163,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
} }
if_chain! { if_chain! {
if let ExprKind::Call(repl_func, repl_args) = src.kind; if let ExprKind::Call(repl_func, []) = src.kind;
if repl_args.is_empty();
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
then { then {
@ -246,11 +245,10 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// Check that `expr` is a call to `mem::replace()` // Check that `expr` is a call to `mem::replace()`
if let ExprKind::Call(func, func_args) = expr.kind; if let ExprKind::Call(func, [dest, src]) = expr.kind;
if let ExprKind::Path(ref func_qpath) = func.kind; if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id); if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
if let [dest, src] = func_args;
then { then {
check_replace_option_with_none(cx, src, dest, expr.span); check_replace_option_with_none(cx, src, dest, expr.span);
check_replace_with_uninit(cx, src, dest, expr.span); check_replace_with_uninit(cx, src, dest, expr.span);

View file

@ -4,7 +4,7 @@ use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg; use clippy_utils::sugg;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind}; use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, adjustment::Adjust}; use rustc_middle::ty::{self, adjustment::Adjust};
use rustc_span::symbol::{sym, Symbol}; use rustc_span::symbol::{sym, Symbol};
@ -86,6 +86,11 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol,
{ {
return; return;
}, },
// ? is a Call, makes sure not to rec *x?, but rather (*x)?
ExprKind::Call(hir_callee, _) => matches!(
hir_callee.kind,
ExprKind::Path(QPath::LangItem(rustc_hir::LangItem::TryTraitBranch, _, _))
),
ExprKind::MethodCall(_, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true, ExprKind::MethodCall(_, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true,
ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
| ExprKind::Field(..) | ExprKind::Field(..)

View file

@ -12,9 +12,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) {
Some((EXPECT_USED, "an Option", "None")) Some((EXPECT_USED, "an Option", "None", ""))
} else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
Some((EXPECT_USED, "a Result", "Err")) Some((EXPECT_USED, "a Result", "Err", "an "))
} else { } else {
None None
}; };
@ -23,14 +23,14 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
return; return;
} }
if let Some((lint, kind, none_value)) = mess { if let Some((lint, kind, none_value, none_prefix)) = mess {
span_lint_and_help( span_lint_and_help(
cx, cx,
lint, lint,
expr.span, expr.span,
&format!("used `expect()` on `{}` value", kind,), &format!("used `expect()` on `{kind}` value"),
None, None,
&format!("if this value is an `{}`, it will panic", none_value,), &format!("if this value is {none_prefix}`{none_value}`, it will panic"),
); );
} }
} }

View file

@ -204,6 +204,17 @@ declare_clippy_lint! {
/// option.expect("more helpful message"); /// option.expect("more helpful message");
/// result.expect("more helpful message"); /// result.expect("more helpful message");
/// ``` /// ```
///
/// If [expect_used](#expect_used) is enabled, instead:
/// ```rust,ignore
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// option?;
///
/// // or
///
/// result?;
/// ```
#[clippy::version = "1.45.0"] #[clippy::version = "1.45.0"]
pub UNWRAP_USED, pub UNWRAP_USED,
restriction, restriction,

View file

@ -1,20 +1,20 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_in_test_function;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_in_test_function, is_lint_allowed};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::sym; use rustc_span::sym;
use super::UNWRAP_USED; use super::{EXPECT_USED, UNWRAP_USED};
/// lint use of `unwrap()` for `Option`s and `Result`s /// lint use of `unwrap()` for `Option`s and `Result`s
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) { pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, allow_unwrap_in_tests: bool) {
let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs(); let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) { let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) {
Some((UNWRAP_USED, "an Option", "None")) Some((UNWRAP_USED, "an Option", "None", ""))
} else if is_type_diagnostic_item(cx, obj_ty, sym::Result) { } else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
Some((UNWRAP_USED, "a Result", "Err")) Some((UNWRAP_USED, "a Result", "Err", "an "))
} else { } else {
None None
}; };
@ -23,18 +23,23 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
return; return;
} }
if let Some((lint, kind, none_value)) = mess { if let Some((lint, kind, none_value, none_prefix)) = mess {
let help = if is_lint_allowed(cx, EXPECT_USED, expr.hir_id) {
format!(
"if you don't want to handle the `{none_value}` case gracefully, consider \
using `expect()` to provide a better panic message"
)
} else {
format!("if this value is {none_prefix}`{none_value}`, it will panic")
};
span_lint_and_help( span_lint_and_help(
cx, cx,
lint, lint,
expr.span, expr.span,
&format!("used `unwrap()` on `{}` value", kind,), &format!("used `unwrap()` on `{kind}` value"),
None, None,
&format!( &help,
"if you don't want to handle the `{}` case gracefully, consider \
using `expect()` to provide a better panic message",
none_value,
),
); );
} }
} }

View file

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::qualify_min_const_fn::is_min_const_fn; use clippy_utils::qualify_min_const_fn::is_min_const_fn;
use clippy_utils::ty::has_drop; use clippy_utils::ty::has_drop;
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, meets_msrv, msrvs, trait_ref_of_method}; use clippy_utils::{
fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, meets_msrv, msrvs, trait_ref_of_method,
};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::CRATE_DEF_ID; use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
@ -86,10 +88,10 @@ impl MissingConstForFn {
impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
fn check_fn( fn check_fn(
&mut self, &mut self,
cx: &LateContext<'_>, cx: &LateContext<'tcx>,
kind: FnKind<'_>, kind: FnKind<'tcx>,
_: &FnDecl<'_>, _: &FnDecl<'_>,
_: &Body<'_>, body: &Body<'tcx>,
span: Span, span: Span,
hir_id: HirId, hir_id: HirId,
) { ) {
@ -124,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
FnKind::Method(_, sig, ..) => { FnKind::Method(_, sig, ..) => {
if trait_ref_of_method(cx, def_id).is_some() if trait_ref_of_method(cx, def_id).is_some()
|| already_const(sig.header) || already_const(sig.header)
|| method_accepts_dropable(cx, sig.decl.inputs) || method_accepts_droppable(cx, sig.decl.inputs)
{ {
return; return;
} }
@ -144,6 +146,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
} }
} }
if is_from_proc_macro(cx, &(&kind, body, hir_id, span)) {
return;
}
let mir = cx.tcx.optimized_mir(def_id); let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv) { if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv) {
@ -159,7 +165,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
/// Returns true if any of the method parameters is a type that implements `Drop`. The method /// Returns true if any of the method parameters is a type that implements `Drop`. The method
/// can't be made const then, because `drop` can't be const-evaluated. /// can't be made const then, because `drop` can't be const-evaluated.
fn method_accepts_dropable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool { fn method_accepts_droppable(cx: &LateContext<'_>, param_tys: &[hir::Ty<'_>]) -> bool {
// If any of the params are droppable, return true // If any of the params are droppable, return true
param_tys.iter().any(|hir_ty| { param_tys.iter().any(|hir_ty| {
let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty); let ty_ty = hir_ty_to_ty(cx.tcx, hir_ty);

View file

@ -7,7 +7,8 @@
use clippy_utils::attrs::is_doc_hidden; use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast; use clippy_utils::is_from_proc_macro;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::DefIdTree; use rustc_middle::ty::DefIdTree;
@ -57,6 +58,20 @@ impl MissingDoc {
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack") *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( fn check_missing_docs_attrs(
&self, &self,
cx: &LateContext<'_>, cx: &LateContext<'_>,
@ -80,7 +95,9 @@ impl MissingDoc {
return; 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 { if !has_doc {
span_lint( span_lint(
cx, cx,
@ -141,14 +158,18 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id()); let (article, desc) = cx.tcx.article_and_description(it.def_id.to_def_id());
let attrs = cx.tcx.hir().attrs(it.hir_id()); let attrs = cx.tcx.hir().attrs(it.hir_id());
self.check_missing_docs_attrs(cx, attrs, it.span, article, desc); if !is_from_proc_macro(cx, it) {
self.check_missing_docs_attrs(cx, attrs, it.span, article, desc);
}
} }
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, trait_item: &'tcx hir::TraitItem<'_>) {
let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id()); let (article, desc) = cx.tcx.article_and_description(trait_item.def_id.to_def_id());
let attrs = cx.tcx.hir().attrs(trait_item.hir_id()); let attrs = cx.tcx.hir().attrs(trait_item.hir_id());
self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc); if !is_from_proc_macro(cx, trait_item) {
self.check_missing_docs_attrs(cx, attrs, trait_item.span, article, desc);
}
} }
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) {
@ -163,18 +184,24 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc {
let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id()); let (article, desc) = cx.tcx.article_and_description(impl_item.def_id.to_def_id());
let attrs = cx.tcx.hir().attrs(impl_item.hir_id()); let attrs = cx.tcx.hir().attrs(impl_item.hir_id());
self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc); if !is_from_proc_macro(cx, impl_item) {
self.check_missing_docs_attrs(cx, attrs, impl_item.span, article, desc);
}
} }
fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) { fn check_field_def(&mut self, cx: &LateContext<'tcx>, sf: &'tcx hir::FieldDef<'_>) {
if !sf.is_positional() { if !sf.is_positional() {
let attrs = cx.tcx.hir().attrs(sf.hir_id); let attrs = cx.tcx.hir().attrs(sf.hir_id);
self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field"); if !is_from_proc_macro(cx, sf) {
self.check_missing_docs_attrs(cx, attrs, sf.span, "a", "struct field");
}
} }
} }
fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) { fn check_variant(&mut self, cx: &LateContext<'tcx>, v: &'tcx hir::Variant<'_>) {
let attrs = cx.tcx.hir().attrs(v.id); let attrs = cx.tcx.hir().attrs(v.id);
self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant"); if !is_from_proc_macro(cx, v) {
self.check_missing_docs_attrs(cx, attrs, v.span, "a", "variant");
}
} }
} }

View file

@ -0,0 +1,104 @@
use clippy_utils::{
diagnostics::span_lint_and_sugg, is_lang_ctor, peel_hir_expr_refs, peel_ref_operators, sugg,
ty::is_type_diagnostic_item,
};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for binary comparisons to a literal `Option::None`.
///
/// ### Why is this bad?
///
/// A programmer checking if some `foo` is `None` via a comparison `foo == None`
/// is usually inspired from other programming languages (e.g. `foo is None`
/// in Python).
/// Checking if a value of type `Option<T>` is (not) equal to `None` in that
/// way relies on `T: PartialEq` to do the comparison, which is unneeded.
///
/// ### Example
/// ```rust
/// fn foo(f: Option<u32>) -> &'static str {
/// if f != None { "yay" } else { "nay" }
/// }
/// ```
/// Use instead:
/// ```rust
/// fn foo(f: Option<u32>) -> &'static str {
/// if f.is_some() { "yay" } else { "nay" }
/// }
/// ```
#[clippy::version = "1.64.0"]
pub PARTIALEQ_TO_NONE,
style,
"Binary comparison to `Option<T>::None` relies on `T: PartialEq`, which is unneeded"
}
declare_lint_pass!(PartialeqToNone => [PARTIALEQ_TO_NONE]);
impl<'tcx> LateLintPass<'tcx> for PartialeqToNone {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
// Skip expanded code, as we have no control over it anyway...
if e.span.from_expansion() {
return;
}
// If the expression is of type `Option`
let is_ty_option =
|expr: &Expr<'_>| is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr).peel_refs(), sym::Option);
// If the expression is a literal `Option::None`
let is_none_ctor = |expr: &Expr<'_>| {
matches!(&peel_hir_expr_refs(expr).0.kind,
ExprKind::Path(p) if is_lang_ctor(cx, p, LangItem::OptionNone))
};
let mut applicability = Applicability::MachineApplicable;
if let ExprKind::Binary(op, left_side, right_side) = e.kind {
// All other comparisons (e.g. `>= None`) have special meaning wrt T
let is_eq = match op.node {
BinOpKind::Eq => true,
BinOpKind::Ne => false,
_ => return,
};
// We are only interested in comparisons between `Option` and a literal `Option::None`
let scrutinee = match (
is_none_ctor(left_side) && is_ty_option(right_side),
is_none_ctor(right_side) && is_ty_option(left_side),
) {
(true, false) => right_side,
(false, true) => left_side,
_ => return,
};
// Peel away refs/derefs (as long as we don't cross manual deref impls), as
// autoref/autoderef will take care of those
let sugg = format!(
"{}.{}",
sugg::Sugg::hir_with_applicability(cx, peel_ref_operators(cx, scrutinee), "..", &mut applicability)
.maybe_par(),
if is_eq { "is_none()" } else { "is_some()" }
);
span_lint_and_sugg(
cx,
PARTIALEQ_TO_NONE,
e.span,
"binary comparison to literal `Option::None`",
if is_eq {
"use `Option::is_none()` instead"
} else {
"use `Option::is_some()` instead"
},
sugg,
applicability,
);
}
}
}

View file

@ -46,11 +46,9 @@ declare_lint_pass!(PathBufPushOverwrite => [PATH_BUF_PUSH_OVERWRITE]);
impl<'tcx> LateLintPass<'tcx> for PathBufPushOverwrite { impl<'tcx> LateLintPass<'tcx> for PathBufPushOverwrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(path, args, _) = expr.kind; if let ExprKind::MethodCall(path, [recv, get_index_arg], _) = expr.kind;
if path.ident.name == sym!(push); if path.ident.name == sym!(push);
if args.len() == 2; if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv).peel_refs(), sym::PathBuf);
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::PathBuf);
if let Some(get_index_arg) = args.get(1);
if let ExprKind::Lit(ref lit) = get_index_arg.kind; if let ExprKind::Lit(ref lit) = get_index_arg.kind;
if let LitKind::Str(ref path_lit, _) = lit.node; if let LitKind::Str(ref path_lit, _) = lit.node;
if let pushed_path = Path::new(path_lit.as_str()); if let pushed_path = Path::new(path_lit.as_str());

View file

@ -86,8 +86,7 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex
if_chain! { if_chain! {
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr); if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
if !is_else_clause(cx.tcx, expr); if !is_else_clause(cx.tcx, expr);
if let ExprKind::MethodCall(segment, args, _) = &cond.kind; if let ExprKind::MethodCall(segment, [caller, ..], _) = &cond.kind;
if let Some(caller) = args.get(0);
let caller_ty = cx.typeck_results().expr_ty(caller); let caller_ty = cx.typeck_results().expr_ty(caller);
let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else); let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else);
if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block); if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block);

View file

@ -385,24 +385,24 @@ fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args:
if path.ident.as_str() == "zip"; if path.ident.as_str() == "zip";
if let [iter, zip_arg] = args; if let [iter, zip_arg] = args;
// `.iter()` call // `.iter()` call
if let ExprKind::MethodCall(iter_path, iter_args, _) = iter.kind; if let ExprKind::MethodCall(iter_path, [iter_caller, ..], _) = iter.kind;
if iter_path.ident.name == sym::iter; if iter_path.ident.name == sym::iter;
// range expression in `.zip()` call: `0..x.len()` // range expression in `.zip()` call: `0..x.len()`
if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg); if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::Range::hir(zip_arg);
if is_integer_const(cx, start, 0); if is_integer_const(cx, start, 0);
// `.len()` call // `.len()` call
if let ExprKind::MethodCall(len_path, len_args, _) = end.kind; if let ExprKind::MethodCall(len_path, [len_caller], _) = end.kind;
if len_path.ident.name == sym::len && len_args.len() == 1; if len_path.ident.name == sym::len;
// `.iter()` and `.len()` called on same `Path` // `.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(_, iter_path)) = iter_caller.kind;
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind; if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_caller.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 { then {
span_lint(cx, span_lint(cx,
RANGE_ZIP_WITH_LEN, RANGE_ZIP_WITH_LEN,
span, span,
&format!("it is more idiomatic to use `{}.iter().enumerate()`", &format!("it is more idiomatic to use `{}.iter().enumerate()`",
snippet(cx, iter_args[0].span, "_")) snippet(cx, iter_caller.span, "_"))
); );
} }
} }

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast; use rustc_ast::ast;
use rustc_ast::visit as ast_visit; use rustc_ast::visit as ast_visit;
@ -69,7 +69,7 @@ impl EarlyLintPass for RedundantClosureCall {
if_chain! { if_chain! {
if let ast::ExprKind::Call(ref paren, _) = expr.kind; if let ast::ExprKind::Call(ref paren, _) = expr.kind;
if let ast::ExprKind::Paren(ref closure) = paren.kind; if let ast::ExprKind::Paren(ref closure) = paren.kind;
if let ast::ExprKind::Closure(_, _, _, _, ref decl, ref block, _) = closure.kind; if let ast::ExprKind::Closure(_, _, ref r#async, _, ref decl, ref block, _) = closure.kind;
then { then {
let mut visitor = ReturnVisitor::new(); let mut visitor = ReturnVisitor::new();
visitor.visit_expr(block); visitor.visit_expr(block);
@ -81,10 +81,19 @@ impl EarlyLintPass for RedundantClosureCall {
"try not to call a closure in the expression where it is declared", "try not to call a closure in the expression where it is declared",
|diag| { |diag| {
if decl.inputs.is_empty() { if decl.inputs.is_empty() {
let mut app = Applicability::MachineApplicable; let app = Applicability::MachineApplicable;
let hint = let mut hint = Sugg::ast(cx, block, "..");
snippet_with_applicability(cx, block.span, "..", &mut app).into_owned();
diag.span_suggestion(expr.span, "try doing something like", hint, app); if r#async.is_async() {
// `async x` is a syntax error, so it becomes `async { x }`
if !matches!(block.kind, ast::ExprKind::Block(_, _)) {
hint = hint.blockify();
}
hint = hint.asyncify();
}
diag.span_suggestion(expr.span, "try doing something like", hint.to_string(), app);
} }
}, },
); );

View file

@ -57,21 +57,20 @@ declare_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]);
impl<'tcx> LateLintPass<'tcx> for Regex { impl<'tcx> LateLintPass<'tcx> for Regex {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(fun, args) = expr.kind; if let ExprKind::Call(fun, [arg]) = expr.kind;
if let ExprKind::Path(ref qpath) = fun.kind; if let ExprKind::Path(ref qpath) = fun.kind;
if args.len() == 1;
if let Some(def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
then { then {
if match_def_path(cx, def_id, &paths::REGEX_NEW) || if match_def_path(cx, def_id, &paths::REGEX_NEW) ||
match_def_path(cx, def_id, &paths::REGEX_BUILDER_NEW) { match_def_path(cx, def_id, &paths::REGEX_BUILDER_NEW) {
check_regex(cx, &args[0], true); check_regex(cx, arg, true);
} else if match_def_path(cx, def_id, &paths::REGEX_BYTES_NEW) || } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_NEW) ||
match_def_path(cx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) { match_def_path(cx, def_id, &paths::REGEX_BYTES_BUILDER_NEW) {
check_regex(cx, &args[0], false); check_regex(cx, arg, false);
} else if match_def_path(cx, def_id, &paths::REGEX_SET_NEW) { } else if match_def_path(cx, def_id, &paths::REGEX_SET_NEW) {
check_set(cx, &args[0], true); check_set(cx, arg, true);
} else if match_def_path(cx, def_id, &paths::REGEX_BYTES_SET_NEW) { } else if match_def_path(cx, def_id, &paths::REGEX_BYTES_SET_NEW) {
check_set(cx, &args[0], false); check_set(cx, arg, false);
} }
} }
} }

View file

@ -2,6 +2,7 @@
#[rustfmt::skip] #[rustfmt::skip]
pub static RENAMED_LINTS: &[(&str, &str)] = &[ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::blacklisted_name", "clippy::disallowed_names"),
("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"), ("clippy::block_in_if_condition_expr", "clippy::blocks_in_if_conditions"),
("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"), ("clippy::block_in_if_condition_stmt", "clippy::blocks_in_if_conditions"),
("clippy::box_vec", "clippy::box_collection"), ("clippy::box_vec", "clippy::box_collection"),
@ -14,6 +15,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"), ("clippy::for_loop_over_result", "clippy::for_loops_over_fallibles"),
("clippy::identity_conversion", "clippy::useless_conversion"), ("clippy::identity_conversion", "clippy::useless_conversion"),
("clippy::if_let_some_result", "clippy::match_result_ok"), ("clippy::if_let_some_result", "clippy::match_result_ok"),
("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
("clippy::new_without_default_derive", "clippy::new_without_default"), ("clippy::new_without_default_derive", "clippy::new_without_default"),
("clippy::option_and_then_some", "clippy::bind_instead_of_map"), ("clippy::option_and_then_some", "clippy::bind_instead_of_map"),
("clippy::option_expect_used", "clippy::expect_used"), ("clippy::option_expect_used", "clippy::expect_used"),

View file

@ -233,15 +233,10 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
/// Returns `true` if give expression is `repeat(0).take(...)` /// Returns `true` if give expression is `repeat(0).take(...)`
fn is_repeat_take(&self, expr: &Expr<'_>) -> bool { fn is_repeat_take(&self, expr: &Expr<'_>) -> bool {
if_chain! { if_chain! {
if let ExprKind::MethodCall(take_path, take_args, _) = expr.kind; if let ExprKind::MethodCall(take_path, [recv, len_arg, ..], _) = expr.kind;
if take_path.ident.name == sym!(take); if take_path.ident.name == sym!(take);
// Check that take is applied to `repeat(0)` // Check that take is applied to `repeat(0)`
if let Some(repeat_expr) = take_args.get(0); if self.is_repeat_zero(recv);
if self.is_repeat_zero(repeat_expr);
if let Some(len_arg) = take_args.get(1);
then { then {
// Check that len expression is equals to `with_capacity` expression // Check that len expression is equals to `with_capacity` expression
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {

View file

@ -97,12 +97,11 @@ struct LintDetection {
fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> { fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintDetection> {
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_name, args, _) = &expr.kind; if let ExprKind::MethodCall(method_name, [slice, args @ ..], _) = &expr.kind;
if let Some(slice) = &args.get(0);
if let Some(method) = SortingKind::from_stable_name(method_name.ident.name.as_str()); if let Some(method) = SortingKind::from_stable_name(method_name.ident.name.as_str());
if let Some(slice_type) = is_slice_of_primitives(cx, slice); if let Some(slice_type) = is_slice_of_primitives(cx, slice);
then { then {
let args_str = args.iter().skip(1).map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", "); let args_str = args.iter().map(|arg| Sugg::hir(cx, arg, "..").to_string()).collect::<Vec<String>>().join(", ");
Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type }) Some(LintDetection { slice_name: Sugg::hir(cx, slice, "..").to_string(), method, method_args: args_str, slice_type })
} else { } else {
None None

View file

@ -103,7 +103,7 @@ impl<'tcx> LateLintPass<'tcx> for UnitTypes {
let_unit_value::check(cx, local); let_unit_value::check(cx, local);
} }
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
unit_cmp::check(cx, expr); unit_cmp::check(cx, expr);
unit_arg::check(cx, expr); unit_arg::check(cx, expr);
} }

View file

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -7,7 +8,7 @@ use rustc_lint::LateContext;
use super::{utils, UNIT_ARG}; use super::{utils, UNIT_ARG};
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if expr.span.from_expansion() { if expr.span.from_expansion() {
return; return;
} }
@ -44,7 +45,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !args_to_recover.is_empty() { if !args_to_recover.is_empty() && !is_from_proc_macro(cx, expr) {
lint_unit_args(cx, expr, &args_to_recover); lint_unit_args(cx, expr, &args_to_recover);
} }
}, },

View file

@ -59,17 +59,17 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e)) => e, ExprKind::Ret(Some(e)) | ExprKind::Break(_, Some(e)) => e,
_ => return, _ => return,
}; };
if let ExprKind::Call(_, args) = e.kind { if let ExprKind::Call(_, [arg, ..]) = e.kind {
self.try_desugar_arm.push(args[0].hir_id); self.try_desugar_arm.push(arg.hir_id);
} }
}, },
ExprKind::MethodCall(name, .., args, _) => { ExprKind::MethodCall(name, .., [recv, ..], _) => {
if is_trait_method(cx, e, sym::Into) && name.ident.as_str() == "into" { if is_trait_method(cx, e, sym::Into) && name.ident.as_str() == "into" {
let a = cx.typeck_results().expr_ty(e); let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]); let b = cx.typeck_results().expr_ty(recv);
if same_type_and_consts(a, b) { if same_type_and_consts(a, b) {
let sugg = snippet_with_macro_callsite(cx, args[0].span, "<expr>").to_string(); let sugg = snippet_with_macro_callsite(cx, recv.span, "<expr>").to_string();
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
USELESS_CONVERSION, USELESS_CONVERSION,
@ -90,9 +90,9 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
} }
} }
let a = cx.typeck_results().expr_ty(e); let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]); let b = cx.typeck_results().expr_ty(recv);
if same_type_and_consts(a, b) { if same_type_and_consts(a, b) {
let sugg = snippet(cx, args[0].span, "<expr>").into_owned(); let sugg = snippet(cx, recv.span, "<expr>").into_owned();
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
USELESS_CONVERSION, USELESS_CONVERSION,
@ -107,7 +107,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
if_chain! { if_chain! {
if is_trait_method(cx, e, sym::TryInto) && name.ident.name == sym::try_into; if is_trait_method(cx, e, sym::TryInto) && name.ident.name == sym::try_into;
let a = cx.typeck_results().expr_ty(e); let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]); let b = cx.typeck_results().expr_ty(recv);
if is_type_diagnostic_item(cx, a, sym::Result); if is_type_diagnostic_item(cx, a, sym::Result);
if let ty::Adt(_, substs) = a.kind(); if let ty::Adt(_, substs) = a.kind();
if let Some(a_type) = substs.types().next(); if let Some(a_type) = substs.types().next();
@ -126,14 +126,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
} }
}, },
ExprKind::Call(path, args) => { ExprKind::Call(path, [arg]) => {
if_chain! { if_chain! {
if args.len() == 1;
if let ExprKind::Path(ref qpath) = path.kind; if let ExprKind::Path(ref qpath) = path.kind;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
then { then {
let a = cx.typeck_results().expr_ty(e); let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(&args[0]); let b = cx.typeck_results().expr_ty(arg);
if_chain! { if_chain! {
if match_def_path(cx, def_id, &paths::TRY_FROM); if match_def_path(cx, def_id, &paths::TRY_FROM);
if is_type_diagnostic_item(cx, a, sym::Result); if is_type_diagnostic_item(cx, a, sym::Result);
@ -159,7 +158,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
if same_type_and_consts(a, b); if same_type_and_consts(a, b);
then { then {
let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "<expr>").maybe_par(); let sugg = Sugg::hir_with_macro_callsite(cx, arg, "<expr>").maybe_par();
let sugg_msg = let sugg_msg =
format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
span_lint_and_sugg( span_lint_and_sugg(

View file

@ -30,7 +30,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
"MinGW", "MinGW",
"CamelCase", "CamelCase",
]; ];
const DEFAULT_BLACKLISTED_NAMES: &[&str] = &["foo", "baz", "quux"]; const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
/// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint. /// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint.
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@ -68,6 +68,7 @@ pub enum DisallowedType {
pub struct TryConf { pub struct TryConf {
pub conf: Conf, pub conf: Conf,
pub errors: Vec<Box<dyn Error>>, pub errors: Vec<Box<dyn Error>>,
pub warnings: Vec<Box<dyn Error>>,
} }
impl TryConf { impl TryConf {
@ -75,6 +76,7 @@ impl TryConf {
Self { Self {
conf: Conf::default(), conf: Conf::default(),
errors: vec![Box::new(error)], errors: vec![Box::new(error)],
warnings: vec![],
} }
} }
} }
@ -90,14 +92,14 @@ impl fmt::Display for ConfError {
impl Error for ConfError {} impl Error for ConfError {}
fn conf_error(s: String) -> Box<dyn Error> { fn conf_error(s: impl Into<String>) -> Box<dyn Error> {
Box::new(ConfError(s)) Box::new(ConfError(s.into()))
} }
macro_rules! define_Conf { macro_rules! define_Conf {
($( ($(
$(#[doc = $doc:literal])+ $(#[doc = $doc:literal])+
$(#[conf_deprecated($dep:literal)])? $(#[conf_deprecated($dep:literal, $new_conf:ident)])?
($name:ident: $ty:ty = $default:expr), ($name:ident: $ty:ty = $default:expr),
)*) => { )*) => {
/// Clippy lint configuration /// Clippy lint configuration
@ -137,17 +139,29 @@ macro_rules! define_Conf {
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> { fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
let mut errors = Vec::new(); let mut errors = Vec::new();
let mut warnings = Vec::new();
$(let mut $name = None;)* $(let mut $name = None;)*
// could get `Field` here directly, but get `str` first for diagnostics // could get `Field` here directly, but get `str` first for diagnostics
while let Some(name) = map.next_key::<&str>()? { while let Some(name) = map.next_key::<&str>()? {
match Field::deserialize(name.into_deserializer())? { match Field::deserialize(name.into_deserializer())? {
$(Field::$name => { $(Field::$name => {
$(errors.push(conf_error(format!("deprecated field `{}`. {}", name, $dep)));)? $(warnings.push(conf_error(format!("deprecated field `{}`. {}", name, $dep)));)?
match map.next_value() { match map.next_value() {
Err(e) => errors.push(conf_error(e.to_string())), Err(e) => errors.push(conf_error(e.to_string())),
Ok(value) => match $name { Ok(value) => match $name {
Some(_) => errors.push(conf_error(format!("duplicate field `{}`", name))), Some(_) => errors.push(conf_error(format!("duplicate field `{}`", name))),
None => $name = Some(value), None => {
$name = Some(value);
// $new_conf is the same as one of the defined `$name`s, so
// this variable is defined in line 2 of this function.
$(match $new_conf {
Some(_) => errors.push(conf_error(concat!(
"duplicate field `", stringify!($new_conf),
"` (provided as `", stringify!($name), "`)"
))),
None => $new_conf = $name.clone(),
})?
},
} }
} }
})* })*
@ -156,7 +170,7 @@ macro_rules! define_Conf {
} }
} }
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* }; let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
Ok(TryConf { conf, errors }) Ok(TryConf { conf, errors, warnings })
} }
} }
@ -203,12 +217,11 @@ define_Conf! {
/// ///
/// The minimum rust version that the project supports /// The minimum rust version that the project supports
(msrv: Option<String> = None), (msrv: Option<String> = None),
/// Lint: BLACKLISTED_NAME. /// DEPRECATED LINT: BLACKLISTED_NAME.
/// ///
/// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses. The value /// Use the Disallowed Names lint instead
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the #[conf_deprecated("Please use `disallowed-names` instead", disallowed_names)]
/// default configuration of Clippy. By default any configuraction will replace the default value. (blacklisted_names: Vec<String> = Vec::new()),
(blacklisted_names: Vec<String> = super::DEFAULT_BLACKLISTED_NAMES.iter().map(ToString::to_string).collect()),
/// Lint: COGNITIVE_COMPLEXITY. /// Lint: COGNITIVE_COMPLEXITY.
/// ///
/// The maximum cognitive complexity a function can have /// The maximum cognitive complexity a function can have
@ -216,8 +229,14 @@ define_Conf! {
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY.
/// ///
/// Use the Cognitive Complexity lint instead. /// Use the Cognitive Complexity lint instead.
#[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] #[conf_deprecated("Please use `cognitive-complexity-threshold` instead", cognitive_complexity_threshold)]
(cyclomatic_complexity_threshold: Option<u64> = None), (cyclomatic_complexity_threshold: u64 = 25),
/// Lint: DISALLOWED_NAMES.
///
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
/// default configuration of Clippy. By default any configuration will replace the default value.
(disallowed_names: Vec<String> = super::DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()),
/// Lint: DOC_MARKDOWN. /// Lint: DOC_MARKDOWN.
/// ///
/// The list of words this lint should not consider as identifiers needing ticks. The value /// The list of words this lint should not consider as identifiers needing ticks. The value
@ -420,7 +439,7 @@ pub fn read(path: &Path) -> TryConf {
match toml::from_str::<TryConf>(&content) { match toml::from_str::<TryConf>(&content) {
Ok(mut conf) => { Ok(mut conf) => {
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
extend_vec_if_indicator_present(&mut conf.conf.blacklisted_names, DEFAULT_BLACKLISTED_NAMES); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
conf conf
}, },

View file

@ -496,12 +496,14 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass {
cx, cx,
}; };
let body_id = cx.tcx.hir().body_owned_by( let body_id = cx.tcx.hir().body_owned_by(
impl_item_refs cx.tcx.hir().local_def_id(
.iter() impl_item_refs
.find(|iiref| iiref.ident.as_str() == "get_lints") .iter()
.expect("LintPass needs to implement get_lints") .find(|iiref| iiref.ident.as_str() == "get_lints")
.id .expect("LintPass needs to implement get_lints")
.hir_id(), .id
.hir_id(),
),
); );
collector.visit_expr(&cx.tcx.hir().body(body_id).value); collector.visit_expr(&cx.tcx.hir().body(body_id).value);
} }
@ -569,7 +571,7 @@ fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'
item.span, item.span,
"this item has an invalid `clippy::version` attribute", "this item has an invalid `clippy::version` attribute",
None, None,
"please use a valid sematic version, see `doc/adding_lints.md`", "please use a valid semantic version, see `doc/adding_lints.md`",
); );
} }
} else { } else {

View file

@ -619,7 +619,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
if_chain! { if_chain! {
// item validation // item validation
if is_lint_ref_type(cx, ty); if is_lint_ref_type(cx, ty);
// blacklist check // disallow check
let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
// metadata extraction // metadata extraction
@ -644,7 +644,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
if_chain! { if_chain! {
if is_deprecated_lint(cx, ty); if is_deprecated_lint(cx, ty);
// blacklist check // disallow check
let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
// Metadata the little we can get from a deprecated lint // Metadata the little we can get from a deprecated lint

View file

@ -61,10 +61,10 @@ impl<'tcx> LateLintPass<'tcx> for VerboseFileReads {
fn is_file_read_to_end<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { fn is_file_read_to_end<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_name, exprs, _) = expr.kind; if let ExprKind::MethodCall(method_name, [recv, ..], _) = expr.kind;
if method_name.ident.as_str() == "read_to_end"; if method_name.ident.as_str() == "read_to_end";
if let ExprKind::Path(QPath::Resolved(None, _)) = &exprs[0].kind; if let ExprKind::Path(QPath::Resolved(None, _)) = &recv.kind;
let ty = cx.typeck_results().expr_ty(&exprs[0]); let ty = cx.typeck_results().expr_ty(recv);
if match_type(cx, ty, &paths::FILE); if match_type(cx, ty, &paths::FILE);
then { then {
return true return true

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy_utils" name = "clippy_utils"
version = "0.1.64" version = "0.1.65"
edition = "2021" edition = "2021"
publish = false publish = false

View file

@ -0,0 +1,329 @@
//! This module handles checking if the span given is from a proc-macro or not.
//!
//! Proc-macros are capable of setting the span of every token they output to a few possible spans.
//! This includes spans we can detect easily as coming from a proc-macro (e.g. the call site
//! or the def site), and spans we can't easily detect as such (e.g. the span of any token
//! passed into the proc macro). This capability means proc-macros are capable of generating code
//! with a span that looks like it was written by the user, but which should not be linted by clippy
//! as it was generated by an external macro.
//!
//! That brings us to this module. The current approach is to determine a small bit of text which
//! must exist at both the start and the end of an item (e.g. an expression or a path) assuming the
//! code was written, and check if the span contains that text. Note this will only work correctly
//! if the span is not from a `macro_rules` based macro.
use rustc_ast::ast::{IntTy, LitIntType, LitKind, StrStyle, UintTy};
use rustc_hir::{
intravisit::FnKind, Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, HirId,
Impl, ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, Node, QPath, TraitItem,
TraitItemKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource,
};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::TyCtxt;
use rustc_session::Session;
use rustc_span::{Span, Symbol};
use rustc_target::spec::abi::Abi;
/// The search pattern to look for. Used by `span_matches_pat`
#[derive(Clone, Copy)]
pub enum Pat {
/// A single string.
Str(&'static str),
/// Any of the given strings.
MultiStr(&'static [&'static str]),
/// The string representation of the symbol.
Sym(Symbol),
/// Any decimal or hexadecimal digit depending on the location.
Num,
}
/// Checks if the start and the end of the span's text matches the patterns. This will return false
/// if the span crosses multiple files or if source is not available.
fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> bool {
let pos = sess.source_map().lookup_byte_offset(span.lo());
let Some(ref src) = pos.sf.src else {
return false;
};
let end = span.hi() - pos.sf.start_pos;
src.get(pos.pos.0 as usize..end.0 as usize).map_or(false, |s| {
// Spans can be wrapped in a mixture or parenthesis, whitespace, and trailing commas.
let start_str = s.trim_start_matches(|c: char| c.is_whitespace() || c == '(');
let end_str = s.trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ',');
(match start_pat {
Pat::Str(text) => start_str.starts_with(text),
Pat::MultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)),
Pat::Sym(sym) => start_str.starts_with(sym.as_str()),
Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit),
} && match end_pat {
Pat::Str(text) => end_str.ends_with(text),
Pat::MultiStr(texts) => texts.iter().any(|s| start_str.ends_with(s)),
Pat::Sym(sym) => end_str.ends_with(sym.as_str()),
Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit),
})
})
}
/// Get the search patterns to use for the given literal
fn lit_search_pat(lit: &LitKind) -> (Pat, Pat) {
match lit {
LitKind::Str(_, StrStyle::Cooked) => (Pat::Str("\""), Pat::Str("\"")),
LitKind::Str(_, StrStyle::Raw(0)) => (Pat::Str("r"), Pat::Str("\"")),
LitKind::Str(_, StrStyle::Raw(_)) => (Pat::Str("r#"), Pat::Str("#")),
LitKind::ByteStr(_) => (Pat::Str("b\""), Pat::Str("\"")),
LitKind::Byte(_) => (Pat::Str("b'"), Pat::Str("'")),
LitKind::Char(_) => (Pat::Str("'"), Pat::Str("'")),
LitKind::Int(_, LitIntType::Signed(IntTy::Isize)) => (Pat::Num, Pat::Str("isize")),
LitKind::Int(_, LitIntType::Unsigned(UintTy::Usize)) => (Pat::Num, Pat::Str("usize")),
LitKind::Int(..) => (Pat::Num, Pat::Num),
LitKind::Float(..) => (Pat::Num, Pat::Str("")),
LitKind::Bool(true) => (Pat::Str("true"), Pat::Str("true")),
LitKind::Bool(false) => (Pat::Str("false"), Pat::Str("false")),
_ => (Pat::Str(""), Pat::Str("")),
}
}
/// Get the search patterns to use for the given path
fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) {
match path {
QPath::Resolved(ty, path) => {
let start = if ty.is_some() {
Pat::Str("<")
} else {
path.segments
.first()
.map_or(Pat::Str(""), |seg| Pat::Sym(seg.ident.name))
};
let end = path.segments.last().map_or(Pat::Str(""), |seg| {
if seg.args.is_some() {
Pat::Str(">")
} else {
Pat::Sym(seg.ident.name)
}
});
(start, end)
},
QPath::TypeRelative(_, name) => (Pat::Str(""), Pat::Sym(name.ident.name)),
QPath::LangItem(..) => (Pat::Str(""), Pat::Str("")),
}
}
/// Get the search patterns to use for the given expression
fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) {
match e.kind {
ExprKind::Box(e) => (Pat::Str("box"), expr_search_pat(tcx, e).1),
ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")),
ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")),
ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat(tcx, e).1),
ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat(tcx, e).1),
ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat(tcx, e).1),
ExprKind::Lit(ref lit) => lit_search_pat(&lit.node),
ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")),
ExprKind::Call(e, []) | ExprKind::MethodCall(_, [e], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")),
ExprKind::Call(first, [.., last])
| ExprKind::MethodCall(_, [first, .., last], _)
| ExprKind::Binary(_, first, last)
| ExprKind::Tup([first, .., last])
| ExprKind::Assign(first, last, _)
| ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1),
ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e),
ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")),
ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1),
ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")),
ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")),
ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => {
(Pat::Str("for"), Pat::Str("}"))
},
ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")),
ExprKind::Match(e, _, MatchSource::TryDesugar) => (expr_search_pat(tcx, e).0, Pat::Str("?")),
ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => {
(expr_search_pat(tcx, e).0, Pat::Str("await"))
},
ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, &tcx.hir().body(body).value).1),
ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
},
None,
) => (Pat::Str("unsafe"), Pat::Str("}")),
ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")),
ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)),
ExprKind::Index(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")),
ExprKind::Path(ref path) => qpath_search_pat(path),
ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1),
ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")),
ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)),
ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1),
ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")),
ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)),
ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")),
ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat(tcx, e).1),
ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")),
ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1),
_ => (Pat::Str(""), Pat::Str("")),
}
}
fn fn_header_search_pat(header: FnHeader) -> Pat {
if header.is_async() {
Pat::Str("async")
} else if header.is_const() {
Pat::Str("const")
} else if header.is_unsafe() {
Pat::Str("unsafe")
} else if header.abi != Abi::Rust {
Pat::Str("extern")
} else {
Pat::MultiStr(&["fn", "extern"])
}
}
fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) {
let (start_pat, end_pat) = match &item.kind {
ItemKind::ExternCrate(_) => (Pat::Str("extern"), Pat::Str(";")),
ItemKind::Static(..) => (Pat::Str("static"), Pat::Str(";")),
ItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
ItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
ItemKind::ForeignMod { .. } => (Pat::Str("extern"), Pat::Str("}")),
ItemKind::TyAlias(..) | ItemKind::OpaqueTy(_) => (Pat::Str("type"), Pat::Str(";")),
ItemKind::Enum(..) => (Pat::Str("enum"), Pat::Str("}")),
ItemKind::Struct(VariantData::Struct(..), _) => (Pat::Str("struct"), Pat::Str("}")),
ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")),
ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")),
ItemKind::Trait(_, Unsafety::Unsafe, ..)
| ItemKind::Impl(Impl {
unsafety: Unsafety::Unsafe,
..
}) => (Pat::Str("unsafe"), Pat::Str("}")),
ItemKind::Trait(IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")),
ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")),
ItemKind::Impl(_) => (Pat::Str("impl"), Pat::Str("}")),
_ => return (Pat::Str(""), Pat::Str("")),
};
if item.vis_span.is_empty() {
(start_pat, end_pat)
} else {
(Pat::Str("pub"), end_pat)
}
}
fn trait_item_search_pat(item: &TraitItem<'_>) -> (Pat, Pat) {
match &item.kind {
TraitItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
TraitItemKind::Type(..) => (Pat::Str("type"), Pat::Str(";")),
TraitItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
}
}
fn impl_item_search_pat(item: &ImplItem<'_>) -> (Pat, Pat) {
let (start_pat, end_pat) = match &item.kind {
ImplItemKind::Const(..) => (Pat::Str("const"), Pat::Str(";")),
ImplItemKind::TyAlias(..) => (Pat::Str("type"), Pat::Str(";")),
ImplItemKind::Fn(sig, ..) => (fn_header_search_pat(sig.header), Pat::Str("")),
};
if item.vis_span.is_empty() {
(start_pat, end_pat)
} else {
(Pat::Str("pub"), end_pat)
}
}
fn field_def_search_pat(def: &FieldDef<'_>) -> (Pat, Pat) {
if def.vis_span.is_empty() {
if def.is_positional() {
(Pat::Str(""), Pat::Str(""))
} else {
(Pat::Sym(def.ident.name), Pat::Str(""))
}
} else {
(Pat::Str("pub"), Pat::Str(""))
}
}
fn variant_search_pat(v: &Variant<'_>) -> (Pat, Pat) {
match v.data {
VariantData::Struct(..) => (Pat::Sym(v.ident.name), Pat::Str("}")),
VariantData::Tuple(..) => (Pat::Sym(v.ident.name), Pat::Str("")),
VariantData::Unit(..) => (Pat::Sym(v.ident.name), Pat::Sym(v.ident.name)),
}
}
fn fn_kind_pat(tcx: TyCtxt<'_>, kind: &FnKind<'_>, body: &Body<'_>, hir_id: HirId) -> (Pat, Pat) {
let (start_pat, end_pat) = match kind {
FnKind::ItemFn(.., header) => (fn_header_search_pat(*header), Pat::Str("")),
FnKind::Method(.., sig) => (fn_header_search_pat(sig.header), Pat::Str("")),
FnKind::Closure => return (Pat::Str(""), expr_search_pat(tcx, &body.value).1),
};
let start_pat = match tcx.hir().get(hir_id) {
Node::Item(Item { vis_span, .. }) | Node::ImplItem(ImplItem { vis_span, .. }) => {
if vis_span.is_empty() {
start_pat
} else {
Pat::Str("pub")
}
},
Node::TraitItem(_) => start_pat,
_ => Pat::Str(""),
};
(start_pat, end_pat)
}
pub trait WithSearchPat {
type Context: LintContext;
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat);
fn span(&self) -> Span;
}
macro_rules! impl_with_search_pat {
($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => {
impl<'cx> WithSearchPat for $ty<'cx> {
type Context = $cx<'cx>;
#[allow(unused_variables)]
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
$(let $tcx = cx.tcx;)?
$fn($($tcx,)? self)
}
fn span(&self) -> Span {
self.span
}
}
};
}
impl_with_search_pat!(LateContext: Expr with expr_search_pat(tcx));
impl_with_search_pat!(LateContext: Item with item_search_pat);
impl_with_search_pat!(LateContext: TraitItem with trait_item_search_pat);
impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat);
impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat);
impl_with_search_pat!(LateContext: Variant with variant_search_pat);
impl<'cx> WithSearchPat for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
type Context = LateContext<'cx>;
fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) {
fn_kind_pat(cx.tcx, self.0, self.1, self.2)
}
fn span(&self) -> Span {
self.3
}
}
/// Checks if the item likely came from a proc-macro.
///
/// This should be called after `in_external_macro` and the initial pattern matching of the ast as
/// it is significantly slower than both of those.
pub fn is_from_proc_macro<T: WithSearchPat>(cx: &T::Context, item: &T) -> bool {
let (start_pat, end_pat) = item.search_pat(cx);
!span_matches_pat(cx.sess(), item.span(), start_pat, end_pat)
}
/// Checks if the span actually refers to a match expression
pub fn is_span_match(cx: &impl LintContext, span: Span) -> bool {
span_matches_pat(cx.sess(), span, Pat::Str("match"), Pat::Str("}"))
}
/// Checks if the span actually refers to an if expression
pub fn is_span_if(cx: &impl LintContext, span: Span) -> bool {
span_matches_pat(cx.sess(), span, Pat::Str("if"), Pat::Str("}"))
}

View file

@ -39,6 +39,7 @@ pub mod sym_helper;
pub mod ast_utils; pub mod ast_utils;
pub mod attrs; pub mod attrs;
mod check_proc_macro;
pub mod comparisons; pub mod comparisons;
pub mod consts; pub mod consts;
pub mod diagnostics; pub mod diagnostics;
@ -59,6 +60,7 @@ pub mod usage;
pub mod visitors; pub mod visitors;
pub use self::attrs::*; pub use self::attrs::*;
pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
pub use self::hir_utils::{ pub use self::hir_utils::{
both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash,
}; };

View file

@ -223,10 +223,12 @@ impl<'a> NumericLiteral<'a> {
fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) { fn split_suffix<'a>(src: &'a str, lit_kind: &LitKind) -> (&'a str, Option<&'a str>) {
debug_assert!(lit_kind.is_numeric()); debug_assert!(lit_kind.is_numeric());
lit_suffix_length(lit_kind).map_or((src, None), |suffix_length| { lit_suffix_length(lit_kind)
let (unsuffixed, suffix) = src.split_at(src.len() - suffix_length); .and_then(|suffix_length| src.len().checked_sub(suffix_length))
(unsuffixed, Some(suffix)) .map_or((src, None), |split_pos| {
}) let (unsuffixed, suffix) = src.split_at(split_pos);
(unsuffixed, Some(suffix))
})
} }
fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> { fn lit_suffix_length(lit_kind: &LitKind) -> Option<usize> {

View file

@ -194,3 +194,5 @@ pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"]; pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"];
pub const INSTANT_NOW: [&str; 4] = ["std", "time", "Instant", "now"];
pub const INSTANT: [&str; 3] = ["std", "time", "Instant"];

View file

@ -11,24 +11,6 @@ use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext}; use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext};
use std::borrow::Cow; use std::borrow::Cow;
/// Checks if the span starts with the given text. This will return false if the span crosses
/// multiple files or if source is not available.
///
/// This is used to check for proc macros giving unhelpful spans to things.
pub fn span_starts_with<T: LintContext>(cx: &T, span: Span, text: &str) -> bool {
fn helper(sm: &SourceMap, span: Span, text: &str) -> bool {
let pos = sm.lookup_byte_offset(span.lo());
let Some(ref src) = pos.sf.src else {
return false;
};
let end = span.hi() - pos.sf.start_pos;
src.get(pos.pos.0 as usize..end.0 as usize)
// Expression spans can include wrapping parenthesis. Remove them first.
.map_or(false, |s| s.trim_start_matches('(').starts_with(text))
}
helper(cx.sess().source_map(), span, text)
}
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
/// Also takes an `Option<String>` which can be put inside the braces. /// Also takes an `Option<String>` which can be put inside the braces.
pub fn expr_block<'a, T: LintContext>( pub fn expr_block<'a, T: LintContext>(

View file

@ -315,6 +315,12 @@ impl<'a> Sugg<'a> {
Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self))) Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
} }
/// Convenience method to prefix the expression with the `async` keyword.
/// Can be used after `blockify` to create an async block.
pub fn asyncify(self) -> Sugg<'static> {
Sugg::NonParen(Cow::Owned(format!("async {}", self)))
}
/// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>` /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
/// suggestion. /// suggestion.
pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> { pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {

View file

@ -503,7 +503,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(P
pub enum ExprFnSig<'tcx> { pub enum ExprFnSig<'tcx> {
Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>), Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>),
Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),
Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>), Trait(Binder<'tcx, Ty<'tcx>>, Option<Binder<'tcx, Ty<'tcx>>>, Option<DefId>),
} }
impl<'tcx> ExprFnSig<'tcx> { impl<'tcx> ExprFnSig<'tcx> {
/// Gets the argument type at the given offset. This will return `None` when the index is out of /// Gets the argument type at the given offset. This will return `None` when the index is out of
@ -518,7 +518,7 @@ impl<'tcx> ExprFnSig<'tcx> {
} }
}, },
Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])), Self::Closure(_, sig) => Some(sig.input(0).map_bound(|ty| ty.tuple_fields()[i])),
Self::Trait(inputs, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])), Self::Trait(inputs, _, _) => Some(inputs.map_bound(|ty| ty.tuple_fields()[i])),
} }
} }
@ -541,7 +541,7 @@ impl<'tcx> ExprFnSig<'tcx> {
decl.and_then(|decl| decl.inputs.get(i)), decl.and_then(|decl| decl.inputs.get(i)),
sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), sig.input(0).map_bound(|ty| ty.tuple_fields()[i]),
)), )),
Self::Trait(inputs, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))), Self::Trait(inputs, _, _) => Some((None, inputs.map_bound(|ty| ty.tuple_fields()[i]))),
} }
} }
@ -550,12 +550,16 @@ impl<'tcx> ExprFnSig<'tcx> {
pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> { pub fn output(self) -> Option<Binder<'tcx, Ty<'tcx>>> {
match self { match self {
Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()), Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()),
Self::Trait(_, output) => output, Self::Trait(_, output, _) => output,
} }
} }
pub fn predicates_id(&self) -> Option<DefId> { pub fn predicates_id(&self) -> Option<DefId> {
if let ExprFnSig::Sig(_, id) = *self { id } else { None } if let ExprFnSig::Sig(_, id) | ExprFnSig::Trait(_, _, id) = *self {
id
} else {
None
}
} }
} }
@ -568,7 +572,8 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnS
} }
} }
fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> { /// If the type is function like, get the signature for it.
pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> {
if ty.is_box() { if ty.is_box() {
return ty_sig(cx, ty.boxed_ty()); return ty_sig(cx, ty.boxed_ty());
} }
@ -580,7 +585,7 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>>
Some(ExprFnSig::Closure(decl, subs.as_closure().sig())) Some(ExprFnSig::Closure(decl, subs.as_closure().sig()))
}, },
ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))), ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))),
ty::Opaque(id, _) => ty_sig(cx, cx.tcx.type_of(id)), ty::Opaque(id, _) => sig_from_bounds(cx, ty, cx.tcx.item_bounds(id), cx.tcx.opt_parent(id)),
ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)), ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)),
ty::Dynamic(bounds, _) => { ty::Dynamic(bounds, _) => {
let lang_items = cx.tcx.lang_items(); let lang_items = cx.tcx.lang_items();
@ -594,26 +599,31 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>>
.projection_bounds() .projection_bounds()
.find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id()))
.map(|p| p.map_bound(|p| p.term.ty().unwrap())); .map(|p| p.map_bound(|p| p.term.ty().unwrap()));
Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output, None))
}, },
_ => None, _ => None,
} }
}, },
ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) { ty::Projection(proj) => match cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) {
Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty), Ok(normalized_ty) if normalized_ty != ty => ty_sig(cx, normalized_ty),
_ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty)), _ => sig_for_projection(cx, proj).or_else(|| sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None)),
}, },
ty::Param(_) => sig_from_bounds(cx, ty), ty::Param(_) => sig_from_bounds(cx, ty, cx.param_env.caller_bounds(), None),
_ => None, _ => None,
} }
} }
fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnSig<'tcx>> { fn sig_from_bounds<'tcx>(
cx: &LateContext<'tcx>,
ty: Ty<'tcx>,
predicates: &'tcx [Predicate<'tcx>],
predicates_id: Option<DefId>,
) -> Option<ExprFnSig<'tcx>> {
let mut inputs = None; let mut inputs = None;
let mut output = None; let mut output = None;
let lang_items = cx.tcx.lang_items(); let lang_items = cx.tcx.lang_items();
for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { for pred in predicates {
match pred.kind().skip_binder() { match pred.kind().skip_binder() {
PredicateKind::Trait(p) PredicateKind::Trait(p)
if (lang_items.fn_trait() == Some(p.def_id()) if (lang_items.fn_trait() == Some(p.def_id())
@ -621,11 +631,12 @@ fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnS
|| lang_items.fn_once_trait() == Some(p.def_id())) || lang_items.fn_once_trait() == Some(p.def_id()))
&& p.self_ty() == ty => && p.self_ty() == ty =>
{ {
if inputs.is_some() { let i = pred.kind().rebind(p.trait_ref.substs.type_at(1));
if inputs.map_or(false, |inputs| i != inputs) {
// Multiple different fn trait impls. Is this even allowed? // Multiple different fn trait impls. Is this even allowed?
return None; return None;
} }
inputs = Some(pred.kind().rebind(p.trait_ref.substs.type_at(1))); inputs = Some(i);
}, },
PredicateKind::Projection(p) PredicateKind::Projection(p)
if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output()
@ -641,7 +652,7 @@ fn sig_from_bounds<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<ExprFnS
} }
} }
inputs.map(|ty| ExprFnSig::Trait(ty, output)) inputs.map(|ty| ExprFnSig::Trait(ty, output, predicates_id))
} }
fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option<ExprFnSig<'tcx>> { fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> Option<ExprFnSig<'tcx>> {
@ -661,14 +672,15 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> O
|| lang_items.fn_mut_trait() == Some(p.def_id()) || lang_items.fn_mut_trait() == Some(p.def_id())
|| lang_items.fn_once_trait() == Some(p.def_id())) => || lang_items.fn_once_trait() == Some(p.def_id())) =>
{ {
if inputs.is_some() { let i = pred
.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1)))
.subst(cx.tcx, ty.substs);
if inputs.map_or(false, |inputs| inputs != i) {
// Multiple different fn trait impls. Is this even allowed? // Multiple different fn trait impls. Is this even allowed?
return None; return None;
} }
inputs = Some( inputs = Some(i);
pred.map_bound(|pred| pred.kind().rebind(p.trait_ref.substs.type_at(1)))
.subst(cx.tcx, ty.substs),
);
}, },
PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => { PredicateKind::Projection(p) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => {
if output.is_some() { if output.is_some() {
@ -684,7 +696,7 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> O
} }
} }
inputs.map(|ty| ExprFnSig::Trait(ty, output)) inputs.map(|ty| ExprFnSig::Trait(ty, output, None))
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2022-07-28" channel = "nightly-2022-08-11"
components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View file

@ -16,7 +16,7 @@ note: the lint level is defined here
LL | #![deny(clippy::internal)] LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
= note: `#[deny(clippy::invalid_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]` = note: `#[deny(clippy::invalid_clippy_version_attribute)]` implied by `#[deny(clippy::internal)]`
= help: please use a valid sematic version, see `doc/adding_lints.md` = help: please use a valid semantic version, see `doc/adding_lints.md`
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this item has an invalid `clippy::version` attribute error: this item has an invalid `clippy::version` attribute
@ -31,7 +31,7 @@ LL | | report_in_external_macro: true
LL | | } LL | | }
| |_^ | |_^
| |
= help: please use a valid sematic version, see `doc/adding_lints.md` = help: please use a valid semantic version, see `doc/adding_lints.md`
= note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `$crate::declare_tool_lint` which comes from the expansion of the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this lint is missing the `clippy::version` attribute or version value error: this lint is missing the `clippy::version` attribute or version value

View file

@ -1 +1 @@
blacklisted-names = 42 disallowed-names = 42

View file

@ -1,4 +1,4 @@
error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence for key `blacklisted-names` error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence for key `disallowed-names`
error: aborting due to previous error error: aborting due to previous error

View file

@ -1,16 +0,0 @@
error: use of a blacklisted/placeholder name `foo`
--> $DIR/blacklisted_names.rs:5:9
|
LL | let foo = "bar";
| ^^^
|
= note: `-D clippy::blacklisted-name` implied by `-D warnings`
error: use of a blacklisted/placeholder name `ducks`
--> $DIR/blacklisted_names.rs:7:9
|
LL | let ducks = ["quack", "quack"];
| ^^^^^
error: aborting due to 2 previous errors

View file

@ -1 +0,0 @@
blacklisted-names = ["ducks", ".."]

View file

@ -1,10 +0,0 @@
error: use of a blacklisted/placeholder name `ducks`
--> $DIR/blacklisted_names.rs:7:9
|
LL | let ducks = ["quack", "quack"];
| ^^^^^
|
= note: `-D clippy::blacklisted-name` implied by `-D warnings`
error: aborting due to previous error

View file

@ -1 +0,0 @@
blacklisted-names = ["ducks"]

View file

@ -1,5 +1,6 @@
# that one is an error # Expect errors from these deprecated configs
cyclomatic-complexity-threshold = 42 cyclomatic-complexity-threshold = 2
blacklisted-names = [ "..", "wibble" ]
# that one is white-listed # that one is white-listed
[third-party] [third-party]

View file

@ -1 +1,11 @@
fn main() {} fn main() {}
#[warn(clippy::cognitive_complexity)]
fn cognitive_complexity() {
let x = vec![1, 2, 3];
for i in x {
if i == 1 {
println!("{}", i);
}
}
}

View file

@ -1,4 +1,15 @@
error: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead warning: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead
error: aborting due to previous error warning: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `blacklisted-names`. Please use `disallowed-names` instead
error: the function has a cognitive complexity of (3/2)
--> $DIR/conf_deprecated_key.rs:4:4
|
LL | fn cognitive_complexity() {
| ^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::cognitive-complexity` implied by `-D warnings`
= help: you could split it up into multiple smaller functions
error: aborting due to previous error; 2 warnings emitted

View file

@ -0,0 +1 @@
disallowed-names = ["ducks", ".."]

View file

@ -1,9 +1,9 @@
#[warn(clippy::blacklisted_name)] #[warn(clippy::disallowed_names)]
fn main() { fn main() {
// `foo` is part of the default configuration // `foo` is part of the default configuration
let foo = "bar"; let foo = "bar";
// `ducks` was unrightfully blacklisted // `ducks` was unrightfully disallowed
let ducks = ["quack", "quack"]; let ducks = ["quack", "quack"];
// `fox` is okay // `fox` is okay
let fox = ["what", "does", "the", "fox", "say", "?"]; let fox = ["what", "does", "the", "fox", "say", "?"];

View file

@ -0,0 +1,16 @@
error: use of a disallowed/placeholder name `foo`
--> $DIR/disallowed_names.rs:5:9
|
LL | let foo = "bar";
| ^^^
|
= note: `-D clippy::disallowed-names` implied by `-D warnings`
error: use of a disallowed/placeholder name `ducks`
--> $DIR/disallowed_names.rs:7:9
|
LL | let ducks = ["quack", "quack"];
| ^^^^^
error: aborting due to 2 previous errors

View file

@ -0,0 +1 @@
disallowed-names = ["ducks"]

View file

@ -1,9 +1,9 @@
#[warn(clippy::blacklisted_name)] #[warn(clippy::disallowed_names)]
fn main() { fn main() {
// `foo` is part of the default configuration // `foo` is part of the default configuration
let foo = "bar"; let foo = "bar";
// `ducks` was unrightfully blacklisted // `ducks` was unrightfully disallowed
let ducks = ["quack", "quack"]; let ducks = ["quack", "quack"];
// `fox` is okay // `fox` is okay
let fox = ["what", "does", "the", "fox", "say", "?"]; let fox = ["what", "does", "the", "fox", "say", "?"];

View file

@ -0,0 +1,10 @@
error: use of a disallowed/placeholder name `ducks`
--> $DIR/disallowed_names.rs:7:9
|
LL | let ducks = ["quack", "quack"];
| ^^^^^
|
= note: `-D clippy::disallowed-names` implied by `-D warnings`
error: aborting due to previous error

View file

@ -0,0 +1,5 @@
cognitive-complexity-threshold = 2
# This is the deprecated name for the same key
cyclomatic-complexity-threshold = 3
# Check we get duplication warning regardless of order
cognitive-complexity-threshold = 4

View file

@ -0,0 +1 @@
fn main() {}

View file

@ -0,0 +1,8 @@
error: error reading Clippy's configuration file `$DIR/clippy.toml`: duplicate field `cognitive_complexity_threshold` (provided as `cyclomatic_complexity_threshold`)
error: error reading Clippy's configuration file `$DIR/clippy.toml`: duplicate field `cognitive-complexity-threshold`
warning: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead
error: aborting due to 2 previous errors; 1 warning emitted

View file

@ -5,7 +5,7 @@ LL | let _ = opt.expect("");
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^
| |
= note: `-D clippy::expect-used` implied by `-D warnings` = note: `-D clippy::expect-used` implied by `-D warnings`
= help: if this value is an `None`, it will panic = help: if this value is `None`, it will panic
error: used `expect()` on `a Result` value error: used `expect()` on `a Result` value
--> $DIR/expect_used.rs:11:13 --> $DIR/expect_used.rs:11:13

View file

@ -1 +0,0 @@
blacklisted-names = ["toto", "tata", "titi"]

View file

@ -1,46 +0,0 @@
error: use of a blacklisted/placeholder name `toto`
--> $DIR/conf_french_blacklisted_name.rs:6:9
|
LL | fn test(toto: ()) {}
| ^^^^
|
= note: `-D clippy::blacklisted-name` implied by `-D warnings`
error: use of a blacklisted/placeholder name `toto`
--> $DIR/conf_french_blacklisted_name.rs:9:9
|
LL | let toto = 42;
| ^^^^
error: use of a blacklisted/placeholder name `tata`
--> $DIR/conf_french_blacklisted_name.rs:10:9
|
LL | let tata = 42;
| ^^^^
error: use of a blacklisted/placeholder name `titi`
--> $DIR/conf_french_blacklisted_name.rs:11:9
|
LL | let titi = 42;
| ^^^^
error: use of a blacklisted/placeholder name `toto`
--> $DIR/conf_french_blacklisted_name.rs:17:10
|
LL | (toto, Some(tata), titi @ Some(_)) => (),
| ^^^^
error: use of a blacklisted/placeholder name `tata`
--> $DIR/conf_french_blacklisted_name.rs:17:21
|
LL | (toto, Some(tata), titi @ Some(_)) => (),
| ^^^^
error: use of a blacklisted/placeholder name `titi`
--> $DIR/conf_french_blacklisted_name.rs:17:28
|
LL | (toto, Some(tata), titi @ Some(_)) => (),
| ^^^^
error: aborting due to 7 previous errors

View file

@ -0,0 +1 @@
disallowed-names = ["toto", "tata", "titi"]

View file

@ -1,7 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
#![allow(clippy::single_match)] #![allow(clippy::single_match)]
#![allow(unused_variables)] #![allow(unused_variables)]
#![warn(clippy::blacklisted_name)] #![warn(clippy::disallowed_names)]
fn test(toto: ()) {} fn test(toto: ()) {}

View file

@ -0,0 +1,46 @@
error: use of a disallowed/placeholder name `toto`
--> $DIR/conf_french_disallowed_name.rs:6:9
|
LL | fn test(toto: ()) {}
| ^^^^
|
= note: `-D clippy::disallowed-names` implied by `-D warnings`
error: use of a disallowed/placeholder name `toto`
--> $DIR/conf_french_disallowed_name.rs:9:9
|
LL | let toto = 42;
| ^^^^
error: use of a disallowed/placeholder name `tata`
--> $DIR/conf_french_disallowed_name.rs:10:9
|
LL | let tata = 42;
| ^^^^
error: use of a disallowed/placeholder name `titi`
--> $DIR/conf_french_disallowed_name.rs:11:9
|
LL | let titi = 42;
| ^^^^
error: use of a disallowed/placeholder name `toto`
--> $DIR/conf_french_disallowed_name.rs:17:10
|
LL | (toto, Some(tata), titi @ Some(_)) => (),
| ^^^^
error: use of a disallowed/placeholder name `tata`
--> $DIR/conf_french_disallowed_name.rs:17:21
|
LL | (toto, Some(tata), titi @ Some(_)) => (),
| ^^^^
error: use of a disallowed/placeholder name `titi`
--> $DIR/conf_french_disallowed_name.rs:17:28
|
LL | (toto, Some(tata), titi @ Some(_)) => (),
| ^^^^
error: aborting due to 7 previous errors

View file

@ -12,6 +12,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie
cognitive-complexity-threshold cognitive-complexity-threshold
cyclomatic-complexity-threshold cyclomatic-complexity-threshold
disallowed-methods disallowed-methods
disallowed-names
disallowed-types disallowed-types
doc-valid-idents doc-valid-idents
enable-raw-pointer-heuristic-for-send enable-raw-pointer-heuristic-for-send

View file

@ -27,6 +27,14 @@ fn main() {
let r: Result<Foo, Foo> = Ok(Foo); let r: Result<Foo, Foo> = Ok(Foo);
assert!(r.is_ok()); assert!(r.is_ok());
// test ok with some messages
let r: Result<Foo, DebugFoo> = Ok(Foo);
assert!(r.is_ok(), "oops");
// test ok with unit error
let r: Result<Foo, ()> = Ok(Foo);
assert!(r.is_ok());
// test temporary ok // test temporary ok
fn get_ok() -> Result<Foo, DebugFoo> { fn get_ok() -> Result<Foo, DebugFoo> {
Ok(Foo) Ok(Foo)

View file

@ -27,6 +27,14 @@ fn main() {
let r: Result<Foo, Foo> = Ok(Foo); let r: Result<Foo, Foo> = Ok(Foo);
assert!(r.is_ok()); assert!(r.is_ok());
// test ok with some messages
let r: Result<Foo, DebugFoo> = Ok(Foo);
assert!(r.is_ok(), "oops");
// test ok with unit error
let r: Result<Foo, ()> = Ok(Foo);
assert!(r.is_ok());
// test temporary ok // test temporary ok
fn get_ok() -> Result<Foo, DebugFoo> { fn get_ok() -> Result<Foo, DebugFoo> {
Ok(Foo) Ok(Foo)

View file

@ -7,31 +7,31 @@ LL | assert!(r.is_ok());
= note: `-D clippy::assertions-on-result-states` implied by `-D warnings` = note: `-D clippy::assertions-on-result-states` implied by `-D warnings`
error: called `assert!` with `Result::is_ok` error: called `assert!` with `Result::is_ok`
--> $DIR/assertions_on_result_states.rs:34:5 --> $DIR/assertions_on_result_states.rs:42:5
| |
LL | assert!(get_ok().is_ok()); LL | assert!(get_ok().is_ok());
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get_ok().unwrap()` | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get_ok().unwrap()`
error: called `assert!` with `Result::is_ok` error: called `assert!` with `Result::is_ok`
--> $DIR/assertions_on_result_states.rs:37:5 --> $DIR/assertions_on_result_states.rs:45:5
| |
LL | assert!(get_ok_macro!().is_ok()); LL | assert!(get_ok_macro!().is_ok());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get_ok_macro!().unwrap()` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get_ok_macro!().unwrap()`
error: called `assert!` with `Result::is_ok` error: called `assert!` with `Result::is_ok`
--> $DIR/assertions_on_result_states.rs:50:5 --> $DIR/assertions_on_result_states.rs:58:5
| |
LL | assert!(r.is_ok()); LL | assert!(r.is_ok());
| ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()` | ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()`
error: called `assert!` with `Result::is_ok` error: called `assert!` with `Result::is_ok`
--> $DIR/assertions_on_result_states.rs:56:9 --> $DIR/assertions_on_result_states.rs:64:9
| |
LL | assert!(r.is_ok()); LL | assert!(r.is_ok());
| ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()` | ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()`
error: called `assert!` with `Result::is_err` error: called `assert!` with `Result::is_err`
--> $DIR/assertions_on_result_states.rs:64:5 --> $DIR/assertions_on_result_states.rs:72:5
| |
LL | assert!(r.is_err()); LL | assert!(r.is_err());
| ^^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap_err()` | ^^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap_err()`

View file

@ -1,88 +0,0 @@
error: use of a blacklisted/placeholder name `foo`
--> $DIR/blacklisted_name.rs:11:9
|
LL | fn test(foo: ()) {}
| ^^^
|
= note: `-D clippy::blacklisted-name` implied by `-D warnings`
error: use of a blacklisted/placeholder name `foo`
--> $DIR/blacklisted_name.rs:14:9
|
LL | let foo = 42;
| ^^^
error: use of a blacklisted/placeholder name `baz`
--> $DIR/blacklisted_name.rs:15:9
|
LL | let baz = 42;
| ^^^
error: use of a blacklisted/placeholder name `quux`
--> $DIR/blacklisted_name.rs:16:9
|
LL | let quux = 42;
| ^^^^
error: use of a blacklisted/placeholder name `foo`
--> $DIR/blacklisted_name.rs:27:10
|
LL | (foo, Some(baz), quux @ Some(_)) => (),
| ^^^
error: use of a blacklisted/placeholder name `baz`
--> $DIR/blacklisted_name.rs:27:20
|
LL | (foo, Some(baz), quux @ Some(_)) => (),
| ^^^
error: use of a blacklisted/placeholder name `quux`
--> $DIR/blacklisted_name.rs:27:26
|
LL | (foo, Some(baz), quux @ Some(_)) => (),
| ^^^^
error: use of a blacklisted/placeholder name `foo`
--> $DIR/blacklisted_name.rs:32:19
|
LL | fn issue_1647(mut foo: u8) {
| ^^^
error: use of a blacklisted/placeholder name `baz`
--> $DIR/blacklisted_name.rs:33:13
|
LL | let mut baz = 0;
| ^^^
error: use of a blacklisted/placeholder name `quux`
--> $DIR/blacklisted_name.rs:34:21
|
LL | if let Some(mut quux) = Some(42) {}
| ^^^^
error: use of a blacklisted/placeholder name `baz`
--> $DIR/blacklisted_name.rs:38:13
|
LL | let ref baz = 0;
| ^^^
error: use of a blacklisted/placeholder name `quux`
--> $DIR/blacklisted_name.rs:39:21
|
LL | if let Some(ref quux) = Some(42) {}
| ^^^^
error: use of a blacklisted/placeholder name `baz`
--> $DIR/blacklisted_name.rs:43:17
|
LL | let ref mut baz = 0;
| ^^^
error: use of a blacklisted/placeholder name `quux`
--> $DIR/blacklisted_name.rs:44:25
|
LL | if let Some(ref mut quux) = Some(42) {}
| ^^^^
error: aborting due to 14 previous errors

View file

@ -1,5 +1,5 @@
#![deny(clippy::borrowed_box)] #![deny(clippy::borrowed_box)]
#![allow(clippy::blacklisted_name)] #![allow(clippy::disallowed_names)]
#![allow(unused_variables)] #![allow(unused_variables)]
#![allow(dead_code)] #![allow(dead_code)]

View file

@ -2,7 +2,7 @@
#![allow( #![allow(
clippy::boxed_local, clippy::boxed_local,
clippy::needless_pass_by_value, clippy::needless_pass_by_value,
clippy::blacklisted_name, clippy::disallowed_names,
unused unused
)] )]

View file

@ -26,4 +26,6 @@ fn main() {
let _ = a.unsigned_abs() as u32; let _ = a.unsigned_abs() as u32;
let _ = a.unsigned_abs() as u64; let _ = a.unsigned_abs() as u64;
let _ = a.unsigned_abs() as u128; let _ = a.unsigned_abs() as u128;
let _ = (x as i64 - y as i64).unsigned_abs() as u32;
} }

Some files were not shown because too many files have changed in this diff Show more