Merge commit 'fdb84cbfd2' into clippyup

This commit is contained in:
Philipp Krones 2022-07-18 09:39:37 +02:00
parent f88a1399bb
commit 7d4daaa8fa
165 changed files with 3427 additions and 963 deletions

View file

@ -161,7 +161,7 @@ declare_clippy_lint! {
/// baz().await; // Lint violation
/// }
/// ```
#[clippy::version = "1.49.0"]
#[clippy::version = "1.62.0"]
pub AWAIT_HOLDING_INVALID_TYPE,
suspicious,
"holding a type across an await point which is not allowed to be held as per the configuration"

View file

@ -22,7 +22,7 @@ declare_clippy_lint! {
/// ```
/// let x = &12;
/// let addr_x = &x as *const _ as usize;
/// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggerd.
/// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggered.
/// // But if we fix it, assert will fail.
/// assert_ne!(addr_x, addr_y);
/// ```

View file

@ -500,7 +500,7 @@ declare_clippy_lint! {
/// let x: i32 = -42;
/// let y: u32 = x.unsigned_abs();
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub CAST_ABS_TO_UNSIGNED,
suspicious,
"casting the result of `abs()` to an unsigned integer can panic"

View file

@ -1,13 +1,16 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
use clippy_utils::ty::needs_ordered_drop;
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed,
search_same, ContainsName, HirEqInterExpr, SpanlessEq,
capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause,
is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
};
use core::iter;
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::intravisit;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind};
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::hygiene::walk_chain;
@ -214,7 +217,7 @@ fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&
fn lint_branches_sharing_code<'tcx>(
cx: &LateContext<'tcx>,
conds: &[&'tcx Expr<'_>],
blocks: &[&Block<'tcx>],
blocks: &[&'tcx Block<'_>],
expr: &'tcx Expr<'_>,
) {
// We only lint ifs with multiple blocks
@ -340,6 +343,21 @@ fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool {
}
}
/// Checks if the statement modifies or moves any of the given locals.
fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool {
for_each_expr(s, |e| {
if let Some(id) = path_to_local(e)
&& locals.contains(&id)
&& !capture_local_usage(cx, e).is_imm_ref()
{
ControlFlow::Break(())
} else {
ControlFlow::Continue(())
}
})
.is_some()
}
/// Checks if the given statement should be considered equal to the statement in the same position
/// for each block.
fn eq_stmts(
@ -365,18 +383,52 @@ fn eq_stmts(
.all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt)))
}
fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq {
#[expect(clippy::too_many_lines)]
fn scan_block_for_eq<'tcx>(
cx: &LateContext<'tcx>,
conds: &[&'tcx Expr<'_>],
block: &'tcx Block<'_>,
blocks: &[&'tcx Block<'_>],
) -> BlockEq {
let mut eq = SpanlessEq::new(cx);
let mut eq = eq.inter_expr();
let mut moved_locals = Vec::new();
let mut cond_locals = HirIdSet::default();
for &cond in conds {
let _: Option<!> = for_each_expr(cond, |e| {
if let Some(id) = path_to_local(e) {
cond_locals.insert(id);
}
ControlFlow::Continue(())
});
}
let mut local_needs_ordered_drop = false;
let start_end_eq = block
.stmts
.iter()
.enumerate()
.find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals))
.find(|&(i, stmt)| {
if let StmtKind::Local(l) = stmt.kind
&& needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id))
{
local_needs_ordered_drop = true;
return true;
}
modifies_any_local(cx, stmt, &cond_locals)
|| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)
})
.map_or(block.stmts.len(), |(i, _)| i);
if local_needs_ordered_drop {
return BlockEq {
start_end_eq,
end_begin_eq: None,
moved_locals,
};
}
// Walk backwards through the final expression/statements so long as their hashes are equal. Note
// `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block
// to match those in other blocks. e.g. If each block ends with the following the hash value will be

View file

@ -43,7 +43,7 @@ declare_clippy_lint! {
/// #[allow(clippy::crate_in_macro_def)]
/// macro_rules! ok { ... crate::foo ... }
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub CRATE_IN_MACRO_DEF,
suspicious,
"using `crate` in a macro definition"

View file

@ -8,8 +8,8 @@ use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem,
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId,
ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
TraitItemKind, TyKind, UnOp,
};
use rustc_infer::infer::TyCtxtInferExt;
@ -717,20 +717,36 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
ExprKind::Ret(_) => {
let output = cx
.tcx
.fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()))
.skip_binder()
.output();
Some(if !output.is_ref() {
Position::Other(precedence)
} else if output.has_placeholders() || output.has_opaque_types() {
Position::ReborrowStable(precedence)
} else {
Position::DerefStable(precedence)
})
let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap());
Some(
if let Node::Expr(Expr {
kind: ExprKind::Closure(&Closure { fn_decl, .. }),
..
}) = cx.tcx.hir().get(owner_id)
{
match fn_decl.output {
FnRetTy::Return(ty) => binding_ty_auto_deref_stability(ty, precedence),
FnRetTy::DefaultReturn(_) => Position::Other(precedence),
}
} else {
let output = cx
.tcx
.fn_sig(cx.tcx.hir().local_def_id(owner_id))
.skip_binder()
.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::Call(func, _) if func.hir_id == child_id => {
(child_id == e.hir_id).then_some(Position::Callee)
},
ExprKind::Call(func, _) if func.hir_id == child_id => (child_id == e.hir_id).then(|| Position::Callee),
ExprKind::Call(func, args) => args
.iter()
.position(|arg| arg.hir_id == child_id)
@ -756,9 +772,14 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, &
} else if let Some(trait_id) = cx.tcx.trait_of_item(id)
&& let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e))
&& let ty::Ref(_, sub_ty, _) = *arg_ty.kind()
&& let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else(
|| cx.tcx.mk_substs([].iter())
) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
&& let subs = match cx
.typeck_results()
.node_substs_opt(parent.hir_id)
.and_then(|subs| subs.get(1..))
{
Some(subs) => cx.tcx.mk_substs(subs.iter().copied()),
None => cx.tcx.mk_substs([].iter()),
} && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() {
// Trait methods taking `&self`
sub_ty
} else {

View file

@ -189,7 +189,7 @@ declare_clippy_lint! {
/// i_am_eq_too: Vec<String>,
/// }
/// ```
#[clippy::version = "1.62.0"]
#[clippy::version = "1.63.0"]
pub DERIVE_PARTIAL_EQ_WITHOUT_EQ,
style,
"deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`"

View file

@ -116,7 +116,7 @@ declare_clippy_lint! {
/// let x = Foo;
/// std::mem::drop(x);
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub DROP_NON_DROP,
suspicious,
"call to `std::mem::drop` with a value which does not implement `Drop`"
@ -136,7 +136,7 @@ declare_clippy_lint! {
/// let x = Foo;
/// std::mem::forget(x);
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub FORGET_NON_DROP,
suspicious,
"call to `std::mem::forget` with a value which does not implement `Drop`"

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
use rustc_errors::MultiSpan;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext, Level};
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{FileName, Span};
use std::collections::BTreeMap;
@ -79,21 +79,29 @@ impl EarlyLintPass for DuplicateMod {
}
fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
for Modules { local_path, spans, lint_levels } in self.modules.values() {
for Modules {
local_path,
spans,
lint_levels,
} in self.modules.values()
{
if spans.len() < 2 {
continue;
}
// At this point the lint would be emitted
assert_eq!(spans.len(), lint_levels.len());
let spans: Vec<_> = spans.into_iter().zip(lint_levels).filter_map(|(span, lvl)|{
if let Some(id) = lvl.get_expectation_id() {
cx.fulfill_expectation(id);
}
let spans: Vec<_> = spans
.iter()
.zip(lint_levels)
.filter_map(|(span, lvl)| {
if let Some(id) = lvl.get_expectation_id() {
cx.fulfill_expectation(id);
}
(!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
})
.collect();
(!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
})
.collect();
if spans.len() < 2 {
continue;

View file

@ -26,7 +26,7 @@ declare_clippy_lint! {
/// ```rust
/// struct S;
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub EMPTY_DROP,
restriction,
"empty `Drop` implementations"

View file

@ -650,7 +650,7 @@ fn find_insert_calls<'tcx>(
let allow_insert_closure = s.allow_insert_closure;
let is_single_insert = s.is_single_insert;
let edits = s.edits;
s.can_use_entry.then(|| InsertSearchResults {
s.can_use_entry.then_some(InsertSearchResults {
edits,
allow_insert_closure,
is_single_insert,

View file

@ -4,7 +4,8 @@ use clippy_utils::ty::implements_trait;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -67,6 +68,7 @@ fn is_structural_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: T
impl<'tcx> LateLintPass<'tcx> for PatternEquality {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Let(let_expr) = expr.kind;
if unary_pattern(let_expr.pat);
let exp_ty = cx.typeck_results().expr_ty(let_expr.init);

View file

@ -14,6 +14,12 @@ declare_clippy_lint! {
/// ### Why is this bad?
/// Introduces an extra, avoidable heap allocation.
///
/// ### Known problems
/// `format!` returns a `String` but `write!` returns a `Result`.
/// Thus you are forced to ignore the `Err` variant to achieve the same API.
///
/// While using `write!` in the suggested way should never fail, this isn't necessarily clear to the programmer.
///
/// ### Example
/// ```rust
/// let mut s = String::new();
@ -27,9 +33,9 @@ declare_clippy_lint! {
/// let mut s = String::new();
/// let _ = write!(s, "0x{:X}", 1024);
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub FORMAT_PUSH_STRING,
perf,
restriction,
"`format!(..)` appended to existing `String`"
}
declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]);

View file

@ -127,7 +127,7 @@ fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option<Span> {
(!span.from_expansion()
&& impl_item.generics.params.is_empty()
&& !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id))
.then(|| span)
.then_some(span)
} else {
None
}

View file

@ -0,0 +1,74 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{match_function_call, paths};
use rustc_ast::{BorrowKind, LitKind};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for `std::str::from_utf8_unchecked` with an invalid UTF-8 literal
///
/// ### Why is this bad?
/// Creating such a `str` would result in undefined behavior
///
/// ### Example
/// ```rust
/// # #[allow(unused)]
/// unsafe {
/// std::str::from_utf8_unchecked(b"cl\x82ippy");
/// }
/// ```
#[clippy::version = "1.64.0"]
pub INVALID_UTF8_IN_UNCHECKED,
correctness,
"using a non UTF-8 literal in `std::std::from_utf8_unchecked`"
}
declare_lint_pass!(InvalidUtf8InUnchecked => [INVALID_UTF8_IN_UNCHECKED]);
impl<'tcx> LateLintPass<'tcx> for InvalidUtf8InUnchecked {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some([arg]) = match_function_call(cx, expr, &paths::STR_FROM_UTF8_UNCHECKED) {
match &arg.kind {
ExprKind::Lit(Spanned { node: lit, .. }) => {
if let LitKind::ByteStr(bytes) = &lit
&& std::str::from_utf8(bytes).is_err()
{
lint(cx, expr.span);
}
},
ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => {
let elements = args.iter().map(|e|{
match &e.kind {
ExprKind::Lit(Spanned { node: lit, .. }) => match lit {
LitKind::Byte(b) => Some(*b),
#[allow(clippy::cast_possible_truncation)]
LitKind::Int(b, _) => Some(*b as u8),
_ => None
}
_ => None
}
}).collect::<Option<Vec<_>>>();
if let Some(elements) = elements
&& std::str::from_utf8(&elements).is_err()
{
lint(cx, expr.span);
}
}
_ => {}
}
}
}
}
fn lint(cx: &LateContext<'_>, span: Span) {
span_lint(
cx,
INVALID_UTF8_IN_UNCHECKED,
span,
"non UTF-8 literal in `std::str::from_utf8_unchecked`",
);
}

View file

@ -30,7 +30,7 @@ declare_clippy_lint! {
/// For types that implement `Copy`, the suggestion to `Box` a variant's
/// data would require removing the trait impl. The types can of course
/// still be `Clone`, but that is worse ergonomically. Depending on the
/// use case it may be possible to store the large data in an auxillary
/// use case it may be possible to store the large data in an auxiliary
/// structure (e.g. Arena or ECS).
///
/// The lint will ignore generic types if the layout depends on the

View file

@ -71,7 +71,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
@ -92,6 +91,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
LintId::of(int_plus_one::INT_PLUS_ONE),
LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
LintId::of(len_zero::COMPARISON_TO_EMPTY),

View file

@ -29,6 +29,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(infinite_iter::INFINITE_ITER),
LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),
LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY),
LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED),
LintId::of(let_underscore::LET_UNDERSCORE_LOCK),
LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES),
LintId::of(loops::ITER_NEXT_LOOP),

View file

@ -196,6 +196,7 @@ store.register_lints(&[
inline_fn_without_body::INLINE_FN_WITHOUT_BODY,
int_plus_one::INT_PLUS_ONE,
invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS,
invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED,
items_after_statements::ITEMS_AFTER_STATEMENTS,
iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR,
large_const_arrays::LARGE_CONST_ARRAYS,
@ -496,6 +497,9 @@ store.register_lints(&[
size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
stable_sort_primitive::STABLE_SORT_PRIMITIVE,
std_instead_of_core::ALLOC_INSTEAD_OF_CORE,
std_instead_of_core::STD_INSTEAD_OF_ALLOC,
std_instead_of_core::STD_INSTEAD_OF_CORE,
strings::STRING_ADD,
strings::STRING_ADD_ASSIGN,
strings::STRING_FROM_UTF8_AS_BYTES,

View file

@ -7,7 +7,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(escape::BOXED_LOCAL),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
LintId::of(loops::MANUAL_MEMCPY),

View file

@ -21,6 +21,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS),
LintId::of(exit::EXIT),
LintId::of(float_literal::LOSSY_FLOAT_LITERAL),
LintId::of(format_push_string::FORMAT_PUSH_STRING),
LintId::of(if_then_some_else_none::IF_THEN_SOME_ELSE_NONE),
LintId::of(implicit_return::IMPLICIT_RETURN),
LintId::of(indexing_slicing::INDEXING_SLICING),
@ -65,6 +66,9 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(shadow::SHADOW_SAME),
LintId::of(shadow::SHADOW_UNRELATED),
LintId::of(single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES),
LintId::of(std_instead_of_core::ALLOC_INSTEAD_OF_CORE),
LintId::of(std_instead_of_core::STD_INSTEAD_OF_ALLOC),
LintId::of(std_instead_of_core::STD_INSTEAD_OF_CORE),
LintId::of(strings::STRING_ADD),
LintId::of(strings::STRING_SLICE),
LintId::of(strings::STRING_TO_STRING),

View file

@ -255,6 +255,7 @@ mod init_numbered_fields;
mod inline_fn_without_body;
mod int_plus_one;
mod invalid_upcast_comparisons;
mod invalid_utf8_in_unchecked;
mod items_after_statements;
mod iter_not_returning_iterator;
mod large_const_arrays;
@ -364,6 +365,7 @@ mod single_component_path_imports;
mod size_of_in_element_count;
mod slow_vector_initialization;
mod stable_sort_primitive;
mod std_instead_of_core;
mod strings;
mod strlen_on_c_strings;
mod suspicious_operation_groupings;
@ -913,6 +915,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv)));
let verbose_bit_mask_threshold = conf.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(std_instead_of_core::StdReexports));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -34,7 +34,8 @@ pub(super) fn check<'tcx>(
if let Some((name, ty, initializer)) = initialize_visitor.get_result();
if is_integer_const(cx, initializer, 0);
then {
let mut applicability = Applicability::MachineApplicable;
let mut applicability = Applicability::MaybeIncorrect;
let span = expr.span.with_hi(arg.span.hi());
let int_name = match ty.map(Ty::kind) {
// usize or inferred
@ -42,7 +43,7 @@ pub(super) fn check<'tcx>(
span_lint_and_sugg(
cx,
EXPLICIT_COUNTER_LOOP,
expr.span.with_hi(arg.span.hi()),
span,
&format!("the variable `{}` is used as a loop counter", name),
"consider using",
format!(
@ -63,11 +64,11 @@ pub(super) fn check<'tcx>(
span_lint_and_then(
cx,
EXPLICIT_COUNTER_LOOP,
expr.span.with_hi(arg.span.hi()),
span,
&format!("the variable `{}` is used as a loop counter", name),
|diag| {
diag.span_suggestion(
expr.span.with_hi(arg.span.hi()),
span,
"consider using",
format!(
"for ({}, {}) in (0_{}..).zip({})",

View file

@ -139,7 +139,7 @@ fn last_stmt_and_ret<'tcx>(
if_chain! {
// This should be the loop
if let Some((node_hir, Node::Stmt(..))) = parent_iter.next();
// This should be the funciton body
// This should be the function body
if let Some((_, Node::Block(block))) = parent_iter.next();
if let Some((last_stmt, last_ret)) = extract(block);
if last_stmt.hir_id == node_hir;

View file

@ -51,22 +51,32 @@ pub(super) fn check<'tcx>(
_ => ""
};
let sugg = format!("{arg_snippet}{copied}.flatten()");
// If suggestion is not a one-liner, it won't be shown inline within the error message. In that case,
// it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs
// to refer to the correct relative position of the suggestion.
let help_msg = if sugg.contains('\n') {
"remove the `if let` statement in the for loop and then..."
} else {
"...and remove the `if let` statement in the for loop"
};
span_lint_and_then(
cx,
MANUAL_FLATTEN,
span,
&msg,
|diag| {
let sugg = format!("{}{}.flatten()", arg_snippet, copied);
diag.span_suggestion(
arg.span,
"try",
sugg,
Applicability::MaybeIncorrect,
applicability,
);
diag.span_help(
inner_expr.span,
"...and remove the `if let` statement in the for loop",
help_msg,
);
}
);

View file

@ -3,13 +3,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{
get_enclosing_loop_or_closure, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used,
get_enclosing_loop_or_multi_call_closure, is_refutable, is_trait_method, match_def_path, paths,
visitors::is_res_used,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Closure, def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
use rustc_hir::{def::Res, Closure, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::{symbol::sym, Symbol};
@ -249,6 +251,11 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
used_iter: bool,
}
impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> {
type NestedFilter = OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.used_iter {
return;
@ -283,6 +290,11 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
used_after: bool,
}
impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> {
type NestedFilter = OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_local(&mut self, l: &'tcx Local<'_>) {
if !self.after_loop {
l.pat.each_binding_or_first(&mut |_, id, _, _| {
@ -320,10 +332,7 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: &
}
}
if let Some(e) = get_enclosing_loop_or_closure(cx.tcx, loop_expr) {
// The iterator expression will be used on the next iteration (for loops), or on the next call (for
// closures) unless it is declared within the enclosing expression. TODO: Check for closures
// used where an `FnOnce` type is expected.
if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) {
let local_id = match iter_expr.path {
Res::Local(id) => id,
_ => return true,

View file

@ -6,8 +6,8 @@ use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId,
IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind,
AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound,
HirId, IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};

View file

@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
(matches!(v.data, hir::VariantData::Unit(_))
&& v.ident.as_str().starts_with('_')
&& is_doc_hidden(cx.tcx.hir().attrs(v.id)))
.then(|| (id, v.span))
.then_some((id, v.span))
});
if let Some((id, span)) = iter.next()
&& iter.next().is_none()

View file

@ -71,7 +71,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
&& let Some(const3) = check_for_unsigned_int_constant(cx, right)
// Also ensures the const is nonzero since zero can't be a divisor
&& const1 == const2 && const2 == const3
&& let Some(hir_id) = path_to_local(expr3) {
&& let Some(hir_id) = path_to_local(expr3)
&& let Some(Node::Pat(_)) = cx.tcx.hir().find(hir_id) {
// Apply only to params or locals with annotated types
match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
Some(Node::Param(..)) => (),

View file

@ -105,7 +105,7 @@ fn check<'tcx>(
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",

View file

@ -81,14 +81,14 @@ where
if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back();
let iter_without_last = iter.clone();
if let Some((first_attrs, _, first_expr, first_guard)) = iter.next();
if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let);
if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let);
if let Some(b0) = find_bool_lit(&first_expr.kind);
if let Some(b1) = find_bool_lit(&last_expr.kind);
if b0 != b1;
if first_guard.is_none() || iter.len() == 0;
if first_attrs.is_empty();
if iter
.all(|arm| {
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
});
then {
if let Some(last_pat) = last_pat_opt {
@ -144,7 +144,7 @@ where
}
/// Extract a `bool` or `{ bool }`
fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> {
match ex {
ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
@ -156,7 +156,7 @@ fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option<bool> {
..
},
_,
) if is_if_let => {
) => {
if let ExprKind::Lit(Spanned {
node: LitKind::Bool(b), ..
}) = exp.kind

View file

@ -38,7 +38,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
normalized_pats[i + 1..]
.iter()
.enumerate()
.find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j))
.find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j))
.unwrap_or(normalized_pats.len())
})
.collect();
@ -55,7 +55,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
.zip(forwards_blocking_idxs[..i].iter().copied().rev())
.skip_while(|&(_, forward_block)| forward_block > i)
.find_map(|((j, other), forward_block)| {
(forward_block == i || pat.has_overlapping_values(other)).then(|| j)
(forward_block == i || pat.has_overlapping_values(other)).then_some(j)
})
.unwrap_or(0)
})
@ -365,7 +365,7 @@ impl<'a> NormalizedPat<'a> {
(Self::Slice(pats, None), Self::Slice(front, Some(back)))
| (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
// Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
// then the minium length required will be greater than the length of `pats`.
// then the minimum length required will be greater than the length of `pats`.
if pats.len() < front.len() + back.len() {
return false;
}

View file

@ -1062,7 +1062,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar
let start = scrutinee_span.hi();
let mut arm_spans = arms.iter().map(|arm| {
let data = arm.span.data();
(data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi))
(data.ctxt == SyntaxContext::root()).then_some((data.lo, data.hi))
});
let end = e.span.hi();
@ -1096,7 +1096,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar
parent: None,
}
.span();
(!span_contains_cfg(cx, span)).then(|| next_start).ok_or(())
(!span_contains_cfg(cx, span)).then_some(next_start).ok_or(())
});
match found {
Ok(start) => {

View file

@ -89,6 +89,10 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
source: MatchSource,
) -> Option<(Vec<FoundSigDrop>, &'static str)> {
let mut helper = SigDropHelper::new(cx);
let scrutinee = match (source, &scrutinee.kind) {
(MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e,
_ => scrutinee,
};
helper.find_sig_drop(scrutinee).map(|drops| {
let message = if source == MatchSource::Normal {
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"

View file

@ -8,6 +8,7 @@ use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::source_map::Span;
use rustc_span::symbol::{sym, Symbol};
use std::borrow::Cow;
@ -49,35 +50,18 @@ fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_>
is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some))
}
/// lint use of `filter().map()` for `Iterators`
fn lint_filter_some_map_unwrap(
/// is `filter(|x| x.is_some()).map(|x| x.unwrap())`
fn is_filter_some_map_unwrap(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
filter_recv: &hir::Expr<'_>,
filter_arg: &hir::Expr<'_>,
map_arg: &hir::Expr<'_>,
target_span: Span,
methods_span: Span,
) {
) -> bool {
let iterator = is_trait_method(cx, expr, sym::Iterator);
let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option);
if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) {
let msg = "`filter` for `Some` followed by `unwrap`";
let help = "consider using `flatten` instead";
let sugg = format!(
"{}",
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),)
);
span_lint_and_sugg(
cx,
OPTION_FILTER_MAP,
methods_span,
msg,
help,
sugg,
Applicability::MachineApplicable,
);
}
(iterator || option) && is_option_filter_map(cx, filter_arg, map_arg)
}
/// lint use of `filter().map()` or `find().map()` for `Iterators`
@ -93,15 +77,20 @@ pub(super) fn check<'tcx>(
map_span: Span,
is_find: bool,
) {
lint_filter_some_map_unwrap(
cx,
expr,
filter_recv,
filter_arg,
map_arg,
map_span,
filter_span.with_hi(expr.span.hi()),
);
if is_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, map_arg) {
span_lint_and_sugg(
cx,
OPTION_FILTER_MAP,
filter_span.with_hi(expr.span.hi()),
"`filter` for `Some` followed by `unwrap`",
"consider using `flatten` instead",
reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(),
Applicability::MachineApplicable,
);
return;
}
if_chain! {
if is_trait_method(cx, map_recv, sym::Iterator);
@ -118,7 +107,7 @@ pub(super) fn check<'tcx>(
// closure ends with is_some() or is_ok()
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind;
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def();
if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
Some(false)
} else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
@ -137,6 +126,19 @@ pub(super) fn check<'tcx>(
if let ExprKind::MethodCall(seg, [map_arg, ..], _) = map_body.value.kind;
if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or);
// .filter(..).map(|y| f(y).copied().unwrap())
// ~~~~
let map_arg_peeled = match map_arg.kind {
ExprKind::MethodCall(method, [original_arg], _) if acceptable_methods(method) => {
original_arg
},
_ => map_arg,
};
// .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap())
let simple_equal = path_to_local_id(filter_arg, filter_param_id)
&& path_to_local_id(map_arg_peeled, map_param_id);
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
// in `filter(|x| ..)`, replace `*x` with `x`
let a_path = if_chain! {
@ -145,25 +147,12 @@ pub(super) fn check<'tcx>(
then { expr_path } else { a }
};
// let the filter closure arg and the map closure arg be equal
if_chain! {
if path_to_local_id(a_path, filter_param_id);
if path_to_local_id(b, map_param_id);
if cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b);
then {
return true;
}
}
false
};
if match map_arg.kind {
ExprKind::MethodCall(method, [original_arg], _) => {
acceptable_methods(method)
&& SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, original_arg)
},
_ => SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg)
path_to_local_id(a_path, filter_param_id)
&& path_to_local_id(b, map_param_id)
&& cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b)
};
if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled);
then {
let span = filter_span.with_hi(expr.span.hi());
let (filter_name, lint) = if is_find {
@ -171,10 +160,22 @@ pub(super) fn check<'tcx>(
} else {
("filter", MANUAL_FILTER_MAP)
};
let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name);
let to_opt = if is_result { ".ok()" } else { "" };
let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident,
snippet(cx, map_arg.span, ".."), to_opt);
let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`");
let (to_opt, deref) = if is_result {
(".ok()", String::new())
} else {
let derefs = cx.typeck_results()
.expr_adjustments(map_arg)
.iter()
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
.count();
("", "*".repeat(derefs))
};
let sugg = format!(
"{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})",
snippet(cx, map_arg.span, ".."),
);
span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable);
}
}

View file

@ -43,7 +43,7 @@ fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option<RepeatKind> {
Some(RepeatKind::String)
} else {
let ty = ty.peel_refs();
(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then(|| RepeatKind::String)
(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then_some(RepeatKind::String)
}
}
}

View file

@ -369,7 +369,7 @@ declare_clippy_lint! {
/// let x: Result<u32, &str> = Ok(10);
/// x.expect_err("Testing expect_err");
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub ERR_EXPECT,
style,
r#"using `.err().expect("")` when `.expect_err("")` can be used"#
@ -2196,12 +2196,9 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Finds usages of [`char::is_digit`]
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
/// can be replaced with [`is_ascii_digit`]
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
/// [`is_ascii_hexdigit`]
/// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
/// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that
/// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or
/// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit).
///
/// ### Why is this bad?
/// `is_digit(..)` is slower and requires specifying the radix.
@ -2218,15 +2215,19 @@ declare_clippy_lint! {
/// c.is_ascii_digit();
/// c.is_ascii_hexdigit();
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub IS_DIGIT_ASCII_RADIX,
style,
"use of `char::is_digit(..)` with literal radix of 10 or 16"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calling `take` function after `as_ref`.
///
/// ### Why is this bad?
/// Redundant code. `take` writes `None` to its argument.
/// In this case the modification is useless as it's a temporary that cannot be read from afterwards.
///
/// ### Example
/// ```rust
@ -2238,7 +2239,7 @@ declare_clippy_lint! {
/// let x = Some(3);
/// x.as_ref();
/// ```
#[clippy::version = "1.61.0"]
#[clippy::version = "1.62.0"]
pub NEEDLESS_OPTION_TAKE,
complexity,
"using `.as_ref().take()` on a temporary value"
@ -2740,6 +2741,12 @@ impl Methods {
}
},
("take", []) => needless_option_take::check(cx, expr, recv),
("then", [arg]) => {
if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
return;
}
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
},
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
implicit_clone::check(cx, name, expr, recv);
},

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite};
use clippy_utils::source::{snippet, snippet_with_macro_callsite};
use clippy_utils::ty::{implements_trait, match_type};
use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths};
use if_chain::if_chain;
@ -28,10 +28,10 @@ pub(super) fn check<'tcx>(
cx: &LateContext<'_>,
name: &str,
fun: &hir::Expr<'_>,
self_expr: &hir::Expr<'_>,
arg: &hir::Expr<'_>,
or_has_args: bool,
span: Span,
method_span: Span,
) -> bool {
let is_default_default = || is_trait_item(cx, fun, sym::Default);
@ -52,24 +52,14 @@ pub(super) fn check<'tcx>(
|| (matches!(path, sym::new) && implements_default(arg, default_trait_id));
then {
let mut applicability = Applicability::MachineApplicable;
let hint = "unwrap_or_default()";
let sugg_span = span;
let sugg: String = format!(
"{}.{}",
snippet_with_applicability(cx, self_expr.span, "..", &mut applicability),
hint
);
span_lint_and_sugg(
cx,
OR_FUN_CALL,
sugg_span,
method_span.with_hi(span.hi()),
&format!("use of `{}` followed by a call to `{}`", name, path),
"try this",
sugg,
applicability,
"unwrap_or_default()".to_string(),
Applicability::MachineApplicable,
);
true
@ -171,7 +161,7 @@ pub(super) fn check<'tcx>(
match inner_arg.kind {
hir::ExprKind::Call(fun, or_args) => {
let or_has_args = !or_args.is_empty();
if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span) {
if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) {
let fun_span = if or_has_args { None } else { Some(fun.span) };
check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span);
}

View file

@ -20,8 +20,9 @@ pub(super) fn check<'tcx>(
) {
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
let is_bool = cx.typeck_results().expr_ty(recv).is_bool();
if is_option || is_result {
if is_option || is_result || is_bool {
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
let body = cx.tcx.hir().body(body);
let body_expr = &body.value;
@ -33,8 +34,10 @@ pub(super) fn check<'tcx>(
if eager_or_lazy::switch_to_eager_eval(cx, body_expr) {
let msg = if is_option {
"unnecessary closure used to substitute value for `Option::None`"
} else {
} else if is_result {
"unnecessary closure used to substitute value for `Result::Err`"
} else {
"unnecessary closure used with `bool::then`"
};
let applicability = if body
.params

View file

@ -301,7 +301,7 @@ fn in_attributes_expansion(expr: &Expr<'_>) -> bool {
use rustc_span::hygiene::MacroKind;
if expr.span.from_expansion() {
let data = expr.span.ctxt().outer_expn_data();
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr|MacroKind::Derive, _))
matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _))
} else {
false
}

View file

@ -9,7 +9,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for type parameters which are positioned inconsistently between
/// a type definition and impl block. Specifically, a paramater in an impl
/// a type definition and impl block. Specifically, a parameter in an impl
/// block which has the same name as a parameter in the type def, but is in
/// a different place.
///

View file

@ -88,15 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault {
// shouldn't be implemented when it is hidden in docs
return;
}
if impl_item
.generics
.params
.iter()
.any(|gen| matches!(gen.kind, hir::GenericParamKind::Type { .. }))
{
// when the result of `new()` depends on a type parameter we should not require
// an
// impl of `Default`
if !impl_item.generics.params.is_empty() {
// when the result of `new()` depends on a parameter we should not require
// an impl of `Default`
return;
}
if_chain! {

View file

@ -31,7 +31,7 @@ declare_clippy_lint! {
/// and friends since the string is already preprocessed when Clippy lints
/// can see it.
///
/// # Example
/// ### Example
/// ```rust
/// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape
/// let two = "\033\0"; // \033 intended as null-3-3

View file

@ -11,8 +11,8 @@ use rustc_hir::def_id::DefId;
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor};
use rustc_hir::{
Arm, Closure, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment,
QPath, Stmt, StmtKind, TyKind, UnOp,
Arm, Block, Body, Closure, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path,
PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;

View file

@ -146,7 +146,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
match some_captures.get(local_id)
.or_else(|| (method_sugg == "map_or_else").then(|| ()).and_then(|_| none_captures.get(local_id)))
.or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|_| none_captures.get(local_id)))
{
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,

View file

@ -502,7 +502,7 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio
.iter()
.filter_map(get_rptr_lm)
.filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region)
.map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
.map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span))
.collect();
if let Some(args) = args
&& !args.is_empty()

View file

@ -1,16 +1,19 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt};
use clippy_utils::{
eq_expr_value, get_parent_node, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks,
peel_blocks_with_stmt,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, PatKind};
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, PathSegment, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
use rustc_span::{sym, symbol::Symbol};
declare_clippy_lint! {
/// ### What it does
@ -39,135 +42,190 @@ declare_clippy_lint! {
declare_lint_pass!(QuestionMark => [QUESTION_MARK]);
impl QuestionMark {
/// Checks if the given expression on the given context matches the following structure:
enum IfBlockType<'hir> {
/// An `if x.is_xxx() { a } else { b } ` expression.
///
/// ```ignore
/// if option.is_none() {
/// return None;
/// }
/// ```
/// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b)
IfIs(
&'hir Expr<'hir>,
Ty<'hir>,
Symbol,
&'hir Expr<'hir>,
Option<&'hir Expr<'hir>>,
),
/// An `if let Xxx(a) = b { c } else { d }` expression.
///
/// ```ignore
/// if result.is_err() {
/// return result;
/// }
/// ```
///
/// If it matches, it will suggest to use the question mark operator instead
fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
if let ExprKind::MethodCall(segment, args, _) = &cond.kind;
if let Some(subject) = args.get(0);
if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) ||
(Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err));
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
let mut replacement: Option<String> = None;
if let Some(else_inner) = r#else {
if eq_expr_value(cx, subject, peel_blocks(else_inner)) {
replacement = Some(format!("Some({}?)", receiver_str));
}
} else if Self::moves_by_default(cx, subject)
&& !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
{
replacement = Some(format!("{}.as_ref()?;", receiver_str));
/// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c),
/// if_else (d)
IfLet(
&'hir QPath<'hir>,
Ty<'hir>,
Symbol,
&'hir Expr<'hir>,
&'hir Expr<'hir>,
Option<&'hir Expr<'hir>>,
),
}
/// Checks if the given expression on the given context matches the following structure:
///
/// ```ignore
/// if option.is_none() {
/// return None;
/// }
/// ```
///
/// ```ignore
/// if result.is_err() {
/// return result;
/// }
/// ```
///
/// If it matches, it will suggest to use the question mark operator instead
fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
if !is_else_clause(cx.tcx, expr);
if let ExprKind::MethodCall(segment, args, _) = &cond.kind;
if let Some(caller) = args.get(0);
let caller_ty = cx.typeck_results().expr_ty(caller);
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);
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability);
let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx.at(caller.span), cx.param_env) &&
!matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..));
let sugg = if let Some(else_inner) = r#else {
if eq_expr_value(cx, caller, peel_blocks(else_inner)) {
format!("Some({}?)", receiver_str)
} else {
replacement = Some(format!("{}?;", receiver_str));
return;
}
} else {
format!("{}{}?;", receiver_str, if by_ref { ".as_ref()" } else { "" })
};
if let Some(replacement_str) = replacement {
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
replacement_str,
applicability,
);
}
}
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
);
}
}
}
fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
= higher::IfLet::hir(cx, expr);
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
(Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr);
if !is_else_clause(cx.tcx, expr);
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
if let PatKind::Binding(annot, bind_id, ident, _) = fields[0].kind;
let caller_ty = cx.typeck_results().expr_ty(let_expr);
let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_else);
if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id))
|| is_early_return(sym::Result, cx, &if_block);
if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none();
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if path_to_local_id(peel_blocks(if_then), bind_id);
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_)));
let sugg = format!(
"{}{}?{}",
receiver_str,
if by_ref { ".as_ref()" } else { "" },
if requires_semi { ";" } else { "" }
);
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this block may be rewritten with the `?` operator",
"replace it with",
sugg,
applicability,
);
}
}
}
span_lint_and_sugg(
cx,
QUESTION_MARK,
expr.span,
"this if-let-else may be rewritten with the `?` operator",
"replace it with",
replacement,
applicability,
);
fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool {
match *if_block {
IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => {
// If the block could be identified as `if x.is_none()/is_err()`,
// we then only need to check the if_then return to see if it is none/err.
is_type_diagnostic_item(cx, caller_ty, smbl)
&& expr_return_none_or_err(smbl, cx, if_then, caller, None)
&& match smbl {
sym::Option => call_sym == sym!(is_none),
sym::Result => call_sym == sym!(is_err),
_ => false,
}
},
IfBlockType::IfLet(qpath, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => {
is_type_diagnostic_item(cx, let_expr_ty, smbl)
&& match smbl {
sym::Option => {
// We only need to check `if let Some(x) = option` not `if let None = option`,
// because the later one will be suggested as `if option.is_none()` thus causing conflict.
is_lang_ctor(cx, qpath, OptionSome)
&& if_else.is_some()
&& expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None)
},
sym::Result => {
(is_lang_ctor(cx, qpath, ResultOk)
&& if_else.is_some()
&& expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym)))
|| is_lang_ctor(cx, qpath, ResultErr)
&& expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym))
},
_ => false,
}
},
}
}
fn expr_return_none_or_err(
smbl: Symbol,
cx: &LateContext<'_>,
expr: &Expr<'_>,
cond_expr: &Expr<'_>,
err_sym: Option<Symbol>,
) -> bool {
match peel_blocks_with_stmt(expr).kind {
ExprKind::Ret(Some(ret_expr)) => expr_return_none_or_err(smbl, cx, ret_expr, cond_expr, err_sym),
ExprKind::Path(ref qpath) => match smbl {
sym::Option => is_lang_ctor(cx, qpath, OptionNone),
sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
_ => false,
},
ExprKind::Call(call_expr, args_expr) => {
if_chain! {
if smbl == sym::Result;
if let ExprKind::Path(QPath::Resolved(_, path)) = &call_expr.kind;
if let Some(segment) = path.segments.first();
if let Some(err_sym) = err_sym;
if let Some(arg) = args_expr.first();
if let ExprKind::Path(QPath::Resolved(_, arg_path)) = &arg.kind;
if let Some(PathSegment { ident, .. }) = arg_path.segments.first();
then {
return segment.ident.name == sym::Err && err_sym == ident.name;
}
}
}
}
fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr)
}
fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
}
fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expression);
!expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env)
}
fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expression);
is_type_diagnostic_item(cx, expr_ty, sym::Option)
}
fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expression);
is_type_diagnostic_item(cx, expr_ty, sym::Result)
}
fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
match peel_blocks_with_stmt(expression).kind {
ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr),
ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
_ => false,
}
}
fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
match peel_blocks_with_stmt(expr).kind {
ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr),
ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
_ => false,
}
false
},
_ => false,
}
}
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
Self::check_is_none_or_err_and_early_return(cx, expr);
Self::check_if_let_some_or_err_and_early_return(cx, expr);
check_is_none_or_err_and_early_return(cx, expr);
check_if_let_some_or_err_and_early_return(cx, expr);
}
}

View file

@ -19,7 +19,7 @@ declare_clippy_lint! {
///
/// ### Example
/// ```ignore
/// Regex::new("|")
/// Regex::new("(")
/// ```
#[clippy::version = "pre 1.29.0"]
pub INVALID_REGEX,

View file

@ -99,7 +99,7 @@ declare_clippy_lint! {
#[derive(Default)]
pub(crate) struct Shadow {
bindings: Vec<FxHashMap<Symbol, Vec<ItemLocalId>>>,
bindings: Vec<(FxHashMap<Symbol, Vec<ItemLocalId>>, LocalDefId)>,
}
impl_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]);
@ -121,7 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
let HirId { owner, local_id } = id;
// get (or insert) the list of items for this owner and symbol
let data = self.bindings.last_mut().unwrap();
let (ref mut data, scope_owner) = *self.bindings.last_mut().unwrap();
let items_with_name = data.entry(ident.name).or_default();
// check other bindings with the same name, most recently seen first
@ -131,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
return;
}
if is_shadow(cx, owner, prev, local_id) {
if is_shadow(cx, scope_owner, prev, local_id) {
let prev_hir_id = HirId { owner, local_id: prev };
lint_shadow(cx, pat, prev_hir_id, ident.span);
// only lint against the "nearest" shadowed binding
@ -144,11 +144,9 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
let hir = cx.tcx.hir();
if !matches!(
hir.body_owner_kind(hir.body_owner_def_id(body.id())),
BodyOwnerKind::Closure
) {
self.bindings.push(FxHashMap::default());
let owner_id = hir.body_owner_def_id(body.id());
if !matches!(hir.body_owner_kind(owner_id), BodyOwnerKind::Closure) {
self.bindings.push((FxHashMap::default(), owner_id));
}
}

View file

@ -0,0 +1,134 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{def::Res, HirId, Path, PathSegment};
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, symbol::kw, Symbol};
declare_clippy_lint! {
/// ### What it does
///
/// Finds items imported through `std` when available through `core`.
///
/// ### Why is this bad?
///
/// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure
/// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates
/// migrating to become `no_std` compatible.
///
/// ### Example
/// ```rust
/// use std::hash::Hasher;
/// ```
/// Use instead:
/// ```rust
/// use core::hash::Hasher;
/// ```
#[clippy::version = "1.64.0"]
pub STD_INSTEAD_OF_CORE,
restriction,
"type is imported from std when available in core"
}
declare_clippy_lint! {
/// ### What it does
///
/// Finds items imported through `std` when available through `alloc`.
///
/// ### Why is this bad?
///
/// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from
/// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful
/// for crates migrating to become `no_std` compatible.
///
/// ### Example
/// ```rust
/// use std::vec::Vec;
/// ```
/// Use instead:
/// ```rust
/// # extern crate alloc;
/// use alloc::vec::Vec;
/// ```
#[clippy::version = "1.64.0"]
pub STD_INSTEAD_OF_ALLOC,
restriction,
"type is imported from std when available in alloc"
}
declare_clippy_lint! {
/// ### What it does
///
/// Finds items imported through `alloc` when available through `core`.
///
/// ### Why is this bad?
///
/// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are
/// imported from alloc to ensure disabling `alloc` does not cause the crate to fail to compile. This lint
/// is also useful for crates migrating to become `no_std` compatible.
///
/// ### Example
/// ```rust
/// # extern crate alloc;
/// use alloc::slice::from_ref;
/// ```
/// Use instead:
/// ```rust
/// use core::slice::from_ref;
/// ```
#[clippy::version = "1.64.0"]
pub ALLOC_INSTEAD_OF_CORE,
restriction,
"type is imported from alloc when available in core"
}
declare_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]);
impl<'tcx> LateLintPass<'tcx> for StdReexports {
fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
// std_instead_of_core
check_path(cx, path, sym::std, sym::core, STD_INSTEAD_OF_CORE);
// std_instead_of_alloc
check_path(cx, path, sym::std, sym::alloc, STD_INSTEAD_OF_ALLOC);
// alloc_instead_of_core
check_path(cx, path, sym::alloc, sym::core, ALLOC_INSTEAD_OF_CORE);
}
}
fn check_path(cx: &LateContext<'_>, path: &Path<'_>, krate: Symbol, suggested_crate: Symbol, lint: &'static Lint) {
if_chain! {
// check if path resolves to the suggested crate.
if let Res::Def(_, def_id) = path.res;
if suggested_crate == cx.tcx.crate_name(def_id.krate);
// check if the first segment of the path is the crate we want to identify
if let Some(path_root_segment) = get_first_segment(path);
// check if the path matches the crate we want to suggest the other path for.
if krate == path_root_segment.ident.name;
then {
span_lint_and_help(
cx,
lint,
path.span,
&format!("used import from `{}` instead of `{}`", krate, suggested_crate),
None,
&format!("consider importing the item from `{}`", suggested_crate),
);
}
}
}
/// Returns the first named segment of a [`Path`].
///
/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
/// is returned.
fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
let segment = path.segments.first()?;
// A global path will have PathRoot as the first segment. In this case, return the segment after.
if segment.ident.name == kw::PathRoot {
path.segments.get(1)
} else {
Some(segment)
}
}

View file

@ -73,7 +73,7 @@ fn is_ptr_to_ref(cx: &LateContext<'_>, e: &Expr<'_>, ctxt: SyntaxContext) -> (bo
&& let ExprKind::Unary(UnOp::Deref, derefed_expr) = borrowed_expr.kind
&& cx.typeck_results().expr_ty(derefed_expr).is_unsafe_ptr()
{
(true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then(|| derefed_expr.span))
(true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then_some(derefed_expr.span))
} else {
(false, None)
}

View file

@ -1,20 +1,20 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
use clippy_utils::{SpanlessEq, SpanlessHash};
use core::hash::{Hash, Hasher};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::unhash::UnhashMap;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{
GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitItem, Ty, TyKind,
WherePredicate,
GenericArg, GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath,
TraitBoundModifier, TraitItem, TraitRef, Ty, TyKind, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
use std::fmt::Write as _;
use rustc_span::{BytePos, Span};
declare_clippy_lint! {
/// ### What it does
@ -35,8 +35,8 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.38.0"]
pub TYPE_REPETITION_IN_BOUNDS,
pedantic,
"Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
nursery,
"types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
}
declare_clippy_lint! {
@ -63,10 +63,26 @@ declare_clippy_lint! {
///
/// fn func<T>(arg: T) where T: Clone + Default {}
/// ```
///
/// ```rust
/// fn foo<T: Default + Default>(bar: T) {}
/// ```
/// Use instead:
/// ```rust
/// fn foo<T: Default>(bar: T) {}
/// ```
///
/// ```rust
/// fn foo<T>(bar: T) where T: Default + Default {}
/// ```
/// Use instead:
/// ```rust
/// fn foo<T>(bar: T) where T: Default {}
/// ```
#[clippy::version = "1.47.0"]
pub TRAIT_DUPLICATION_IN_BOUNDS,
pedantic,
"Check if the same trait bounds are specified twice during a function declaration"
nursery,
"check if the same trait bounds are specified more than once during a generic declaration"
}
#[derive(Copy, Clone)]
@ -87,6 +103,19 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) {
self.check_type_repetition(cx, gen);
check_trait_bound_duplication(cx, gen);
check_bounds_or_where_duplication(cx, gen);
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
// special handling for self trait bounds as these are not considered generics
// ie. trait Foo: Display {}
if let Item {
kind: ItemKind::Trait(_, _, _, bounds, ..),
..
} = item
{
rollup_traits(cx, bounds, "these bounds contain repeated elements");
}
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) {
@ -178,30 +207,18 @@ impl TraitBounds {
);
then {
let mut hint_string = format!(
"consider combining the bounds: `{}:",
snippet(cx, p.bounded_ty.span, "_")
let trait_bounds = v
.iter()
.copied()
.chain(p.bounds.iter())
.filter_map(get_trait_info_from_bound)
.map(|(_, _, span)| snippet_with_applicability(cx, span, "..", &mut applicability))
.join(" + ");
let hint_string = format!(
"consider combining the bounds: `{}: {}`",
snippet(cx, p.bounded_ty.span, "_"),
trait_bounds,
);
for b in v.iter() {
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
let path = &poly_trait_ref.trait_ref.path;
let _ = write!(hint_string,
" {} +",
snippet_with_applicability(cx, path.span, "..", &mut applicability)
);
}
}
for b in p.bounds.iter() {
if let GenericBound::Trait(ref poly_trait_ref, _) = b {
let path = &poly_trait_ref.trait_ref.path;
let _ = write!(hint_string,
" {} +",
snippet_with_applicability(cx, path.span, "..", &mut applicability)
);
}
}
hint_string.truncate(hint_string.len() - 2);
hint_string.push('`');
span_lint_and_help(
cx,
TYPE_REPETITION_IN_BOUNDS,
@ -253,10 +270,107 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
}
}
#[derive(PartialEq, Eq, Hash, Debug)]
struct ComparableTraitRef(Res, Vec<Res>);
fn check_bounds_or_where_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
if gen.span.from_expansion() {
return;
}
for predicate in gen.predicates {
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate {
let msg = if predicate.in_where_clause() {
"these where clauses contain repeated elements"
} else {
"these bounds contain repeated elements"
};
rollup_traits(cx, bound_predicate.bounds, msg);
}
}
}
fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> {
if let GenericBound::Trait(t, _) = bound {
Some((t.trait_ref.path.res, t.trait_ref.path.segments, t.span))
if let GenericBound::Trait(t, tbm) = bound {
let trait_path = t.trait_ref.path;
let trait_span = {
let path_span = trait_path.span;
if let TraitBoundModifier::Maybe = tbm {
path_span.with_lo(path_span.lo() - BytePos(1)) // include the `?`
} else {
path_span
}
};
Some((trait_path.res, trait_path.segments, trait_span))
} else {
None
}
}
// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds
fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef {
ComparableTraitRef(
trait_ref.path.res,
trait_ref
.path
.segments
.iter()
.filter_map(|segment| {
// get trait bound type arguments
Some(segment.args?.args.iter().filter_map(|arg| {
if_chain! {
if let GenericArg::Type(ty) = arg;
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
then { return Some(path.res) }
}
None
}))
})
.flatten()
.collect(),
)
}
fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) {
let mut map = FxHashMap::default();
let mut repeated_res = false;
let only_comparable_trait_refs = |bound: &GenericBound<'_>| {
if let GenericBound::Trait(t, _) = bound {
Some((into_comparable_trait_ref(&t.trait_ref), t.span))
} else {
None
}
};
for bound in bounds.iter().filter_map(only_comparable_trait_refs) {
let (comparable_bound, span_direct) = bound;
if map.insert(comparable_bound, span_direct).is_some() {
repeated_res = true;
}
}
if_chain! {
if repeated_res;
if let [first_trait, .., last_trait] = bounds;
then {
let all_trait_span = first_trait.span().to(last_trait.span());
let mut traits = map.values()
.filter_map(|span| snippet_opt(cx, *span))
.collect::<Vec<_>>();
traits.sort_unstable();
let traits = traits.join(" + ");
span_lint_and_sugg(
cx,
TRAIT_DUPLICATION_IN_BOUNDS,
all_trait_span,
msg,
"try",
traits,
Applicability::MachineApplicable
);
}
}
}

View file

@ -15,19 +15,17 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
sym::String => "",
_ => "<..>",
};
let box_content = format!("{outer}{generic}", outer = item_type);
span_lint_and_help(
cx,
BOX_COLLECTION,
hir_ty.span,
&format!(
"you seem to be trying to use `Box<{outer}{generic}>`. Consider using just `{outer}{generic}`",
outer=item_type,
generic = generic),
"you seem to be trying to use `Box<{box_content}>`. Consider using just `{box_content}`"),
None,
&format!(
"`{outer}{generic}` is already on the heap, `Box<{outer}{generic}>` makes an extra allocation",
outer=item_type,
generic = generic)
"`{box_content}` is already on the heap, `Box<{box_content}>` makes an extra allocation")
);
true
} else {
@ -39,7 +37,18 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Symbol> {
let param = qpath_generic_tys(qpath).next()?;
let id = path_def_id(cx, param)?;
cx.tcx
.get_diagnostic_name(id)
.filter(|&name| matches!(name, sym::HashMap | sym::String | sym::Vec))
cx.tcx.get_diagnostic_name(id).filter(|&name| {
matches!(
name,
sym::HashMap
| sym::String
| sym::Vec
| sym::HashSet
| sym::VecDeque
| sym::LinkedList
| sym::BTreeMap
| sym::BTreeSet
| sym::BinaryHeap
)
})
}

View file

@ -265,14 +265,28 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span
}
}
fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
let body = cx.enclosing_body?;
let map = cx.tcx.hir();
let mut span = map.body(body).value.span;
for (_, node) in map.parent_iter(body.hir_id) {
match node {
Node::Expr(e) => span = e.span,
Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::Local(_) => (),
_ => break,
}
}
Some(span)
}
fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
let source_map = cx.sess().source_map();
let ctxt = span.ctxt();
if ctxt == SyntaxContext::root()
&& let Some(body) = cx.enclosing_body
&& let Some(search_span) = get_body_search_span(cx)
{
if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
&& let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root())
&& let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root())
&& let Ok(body_line) = source_map.lookup_line(body_span.lo())
&& Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()

View file

@ -1,43 +1,40 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_node;
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::visitors::for_each_value_source;
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind};
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty, TypeVisitable, TypeSuperVisitable, TypeVisitor};
use rustc_middle::ty;
use super::LET_UNIT_VALUE;
pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
if let StmtKind::Local(local) = stmt.kind
&& let Some(init) = local.init
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
if let Some(init) = local.init
&& !local.pat.span.from_expansion()
&& !in_external_macro(cx.sess(), stmt.span)
&& !in_external_macro(cx.sess(), local.span)
&& cx.typeck_results().pat_ty(local.pat).is_unit()
{
let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}).is_continue();
if needs_inferred {
if !matches!(local.pat.kind, PatKind::Wild) {
if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer))
|| matches!(local.pat.kind, PatKind::Tuple([], None)))
&& expr_needs_inferred_result(cx, init)
{
if !matches!(local.pat.kind, PatKind::Wild | PatKind::Tuple([], None)) {
span_lint_and_then(
cx,
LET_UNIT_VALUE,
stmt.span,
local.span,
"this let-binding has unit value",
|diag| {
diag.span_suggestion(
local.pat.span,
"use a wild (`_`) binding",
"_",
Applicability::MaybeIncorrect, // snippet
);
diag.span_suggestion(
local.pat.span,
"use a wild (`_`) binding",
"_",
Applicability::MaybeIncorrect, // snippet
);
},
);
}
@ -45,15 +42,15 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
span_lint_and_then(
cx,
LET_UNIT_VALUE,
stmt.span,
local.span,
"this let-binding has unit value",
|diag| {
if let Some(expr) = &local.init {
let snip = snippet_with_macro_callsite(cx, expr.span, "()");
diag.span_suggestion(
stmt.span,
local.span,
"omit the `let` binding",
format!("{};", snip),
format!("{snip};"),
Applicability::MachineApplicable, // snippet
);
}
@ -63,48 +60,106 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
}
}
fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
let id = match e.kind {
/// Checks sub-expressions which create the value returned by the given expression for whether
/// return value inference is needed. This checks through locals to see if they also need inference
/// at this point.
///
/// e.g.
/// ```rust,ignore
/// let bar = foo();
/// let x: u32 = if true { baz() } else { bar };
/// ```
/// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the
/// initialization of `bar`. If both `foo` and `baz` have a return type which require type
/// inference then this function would return `true`.
fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
// The locals used for initialization which have yet to be checked.
let mut locals_to_check = Vec::new();
// All the locals which have been added to `locals_to_check`. Needed to prevent cycles.
let mut seen_locals = HirIdSet::default();
if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
return false;
}
while let Some(id) = locals_to_check.pop() {
if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) {
if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) {
return false;
}
if let Some(e) = l.init {
if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
return false;
}
} else if for_each_local_assignment(cx, id, |e| {
if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
})
.is_break()
{
return false;
}
}
}
true
}
fn each_value_source_needs_inference(
cx: &LateContext<'_>,
e: &Expr<'_>,
locals_to_check: &mut Vec<HirId>,
seen_locals: &mut HirIdSet,
) -> bool {
for_each_value_source(e, &mut |e| {
if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
})
.is_continue()
}
fn needs_inferred_result_ty(
cx: &LateContext<'_>,
e: &Expr<'_>,
locals_to_check: &mut Vec<HirId>,
seen_locals: &mut HirIdSet,
) -> bool {
let (id, args) = match e.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(ref path),
hir_id,
..
},
_,
args,
) => match cx.qpath_res(path, *hir_id) {
Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id,
Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args),
_ => return false,
},
ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
Some(id) => id,
ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) {
Some(id) => (id, args),
None => return false,
},
ExprKind::Path(QPath::Resolved(None, path)) => {
if let Res::Local(id) = path.res
&& seen_locals.insert(id)
{
locals_to_check.push(id);
}
return true;
},
_ => return false,
};
let sig = cx.tcx.fn_sig(id).skip_binder();
if let ty::Param(output_ty) = *sig.output().kind() {
sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index))
sig.inputs().iter().zip(args).all(|(&ty, arg)| {
!ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals)
})
} else {
false
}
}
fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool {
struct Visitor(u32);
impl<'tcx> TypeVisitor<'tcx> for Visitor {
type BreakTy = ();
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::Param(ty) = *ty.kind() {
if ty.index == self.0 {
ControlFlow::BREAK
} else {
ControlFlow::CONTINUE
}
} else {
ty.super_visit_with(self)
}
}
}
ty.visit_with(&mut Visitor(index)).is_break()
}

View file

@ -3,7 +3,7 @@ mod unit_arg;
mod unit_cmp;
mod utils;
use rustc_hir::{Expr, Stmt};
use rustc_hir::{Expr, Local};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -98,9 +98,9 @@ declare_clippy_lint! {
declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]);
impl LateLintPass<'_> for UnitTypes {
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) {
let_unit_value::check(cx, stmt);
impl<'tcx> LateLintPass<'tcx> for UnitTypes {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
let_unit_value::check(cx, local);
}
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {

View file

@ -20,8 +20,8 @@ use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{
BinOpKind, Block, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty, TyKind,
UnOp,
BinOpKind, Block, Closure, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty,
TyKind, UnOp,
};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
@ -730,8 +730,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
if let ExprKind::Call(func, and_then_args) = expr.kind;
if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
if and_then_args.len() == 5;
if let ExprKind::Closure { body, .. } = &and_then_args[4].kind;
let body = cx.tcx.hir().body(*body);
if let ExprKind::Closure(&Closure { body, .. }) = &and_then_args[4].kind;
let body = cx.tcx.hir().body(body);
let only_expr = peel_blocks_with_stmt(&body.value);
if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind;
if let ExprKind::Path(..) = span_call_args[0].kind;

View file

@ -17,7 +17,7 @@ use if_chain::if_chain;
use rustc_ast as ast;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{
self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath,
self as hir, def::DefKind, intravisit, intravisit::Visitor, Closure, ExprKind, Item, ItemKind, Mutability, QPath,
};
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
use rustc_middle::hir::nested_filter;
@ -843,7 +843,7 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> {
DEFAULT_LINT_LEVELS
.iter()
.find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level))
.find_map(|(group_name, group_level)| (*group_name == lint_group).then_some(*group_level))
}
pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
@ -958,7 +958,7 @@ fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hi
}
fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
if let ExprKind::Closure { body, .. } = closure_expr.kind {
if let ExprKind::Closure(&Closure { body, .. }) = closure_expr.kind {
let mut scanner = IsMultiSpanScanner::new(cx);
intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body));
return scanner.is_multi_part();
@ -1018,7 +1018,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
/// This visitor finds the highest applicability value in the visited expressions
struct ApplicabilityResolver<'a, 'hir> {
cx: &'a LateContext<'hir>,
/// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES`
/// This is the index of highest `Applicability` for `paths::APPLICABILITY_VALUES`
applicability_index: Option<usize>,
}

View file

@ -515,7 +515,7 @@ impl Write {
args.push(arg, span);
}
parser.errors.is_empty().then(move || args)
parser.errors.is_empty().then_some(args)
}
/// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two