mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-26 16:54:01 +00:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
96b32b1cb8
2
.github/workflows/clippy.yml
vendored
2
.github/workflows/clippy.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
|
10
.github/workflows/clippy_bors.yml
vendored
10
.github/workflows/clippy_bors.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
@ -83,7 +83,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -149,7 +149,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -173,7 +173,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
@ -233,7 +233,7 @@ jobs:
|
||||
github_token: "${{ secrets.github_token }}"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install toolchain
|
||||
run: rustup show active-toolchain
|
||||
|
2
.github/workflows/clippy_dev.yml
vendored
2
.github/workflows/clippy_dev.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Run
|
||||
- name: Build
|
||||
|
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@ -21,10 +21,10 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ env.TARGET_BRANCH }}
|
||||
path: 'out'
|
||||
|
4
.github/workflows/remark.yml
vendored
4
.github/workflows/remark.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1.4.4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
|
@ -4620,6 +4620,7 @@ Released 2018-09-13
|
||||
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else
|
||||
[`empty_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_drop
|
||||
[`empty_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_enum
|
||||
[`empty_line_after_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_doc_comments
|
||||
[`empty_line_after_outer_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_line_after_outer_attr
|
||||
[`empty_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_loop
|
||||
[`empty_structs_with_brackets`]: https://rust-lang.github.io/rust-clippy/master/index.html#empty_structs_with_brackets
|
||||
@ -4785,6 +4786,7 @@ Released 2018-09-13
|
||||
[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str
|
||||
[`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_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back
|
||||
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
|
||||
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
|
||||
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
|
||||
@ -4897,6 +4899,7 @@ Released 2018-09-13
|
||||
[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
|
||||
[`no_mangle_with_rust_abi`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_mangle_with_rust_abi
|
||||
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
|
||||
[`non_minimal_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_minimal_cfg
|
||||
[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
|
||||
[`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty
|
||||
[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
|
||||
@ -4978,6 +4981,7 @@ Released 2018-09-13
|
||||
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
|
||||
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
|
||||
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
|
||||
[`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns
|
||||
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
|
||||
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
|
||||
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
|
||||
|
@ -278,7 +278,7 @@ If you want to contribute to Clippy, you can find more information in [CONTRIBUT
|
||||
|
||||
<!-- REUSE-IgnoreStart -->
|
||||
|
||||
Copyright 2014-2022 The Rust Project Developers
|
||||
Copyright 2014-2023 The Rust Project Developers
|
||||
|
||||
Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)> or the MIT license
|
||||
|
@ -17,10 +17,10 @@ if_chain = "1.0"
|
||||
itertools = "0.10.1"
|
||||
pulldown-cmark = { version = "0.9", default-features = false }
|
||||
quine-mc_cluskey = "0.2"
|
||||
regex-syntax = "0.6"
|
||||
regex-syntax = "0.7"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
tempfile = { version = "3.2", optional = true }
|
||||
tempfile = { version = "3.3.0", optional = true }
|
||||
toml = "0.5"
|
||||
unicode-normalization = "0.1"
|
||||
unicode-script = { version = "0.5", default-features = false }
|
||||
|
@ -38,7 +38,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
|
||||
_ => return,
|
||||
};
|
||||
let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return };
|
||||
let Some((Constant::Bool(val), _)) = constant(cx, cx.typeck_results(), condition) else { return };
|
||||
let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { return };
|
||||
if val {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -176,6 +176,52 @@ declare_clippy_lint! {
|
||||
"empty line after outer attribute"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for empty lines after documenation comments.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The documentation comment was most likely meant to be an inner attribute or regular comment.
|
||||
/// If it was intended to be a documentation comment, then the empty line should be removed to
|
||||
/// be more idiomatic.
|
||||
///
|
||||
/// ### Known problems
|
||||
/// Only detects empty lines immediately following the documentation. If the doc comment is followed
|
||||
/// by an attribute and then an empty line, this lint will not trigger. Use `empty_line_after_outer_attr`
|
||||
/// in combination with this lint to detect both cases.
|
||||
///
|
||||
/// Does not detect empty lines after doc attributes (e.g. `#[doc = ""]`).
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// /// Some doc comment with a blank line after it.
|
||||
///
|
||||
/// fn not_quite_good_code() { }
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// /// Good (no blank line)
|
||||
/// fn this_is_fine() { }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// // Good (convert to a regular comment)
|
||||
///
|
||||
/// fn this_is_fine_too() { }
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// //! Good (convert to a comment on an inner attribute)
|
||||
///
|
||||
/// fn this_is_fine_as_well() { }
|
||||
/// ```
|
||||
#[clippy::version = "1.70.0"]
|
||||
pub EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
nursery,
|
||||
"empty line after documentation comments"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
|
||||
@ -292,6 +338,30 @@ declare_clippy_lint! {
|
||||
"ensures that all `allow` and `expect` attributes have a reason"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `any` and `all` combinators in `cfg` with only one condition.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// If there is only one condition, no need to wrap it into `any` or `all` combinators.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// #[cfg(any(unix))]
|
||||
/// pub struct Bar;
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// #[cfg(unix)]
|
||||
/// pub struct Bar;
|
||||
/// ```
|
||||
#[clippy::version = "1.71.0"]
|
||||
pub NON_MINIMAL_CFG,
|
||||
style,
|
||||
"ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
|
||||
}
|
||||
|
||||
declare_lint_pass!(Attributes => [
|
||||
ALLOW_ATTRIBUTES_WITHOUT_REASON,
|
||||
INLINE_ALWAYS,
|
||||
@ -604,6 +674,8 @@ impl_lint_pass!(EarlyAttributes => [
|
||||
DEPRECATED_CFG_ATTR,
|
||||
MISMATCHED_TARGET_OS,
|
||||
EMPTY_LINE_AFTER_OUTER_ATTR,
|
||||
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
NON_MINIMAL_CFG,
|
||||
]);
|
||||
|
||||
impl EarlyLintPass for EarlyAttributes {
|
||||
@ -614,15 +686,22 @@ impl EarlyLintPass for EarlyAttributes {
|
||||
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
check_deprecated_cfg_attr(cx, attr, &self.msrv);
|
||||
check_mismatched_target_os(cx, attr);
|
||||
check_minimal_cfg_condition(cx, attr);
|
||||
}
|
||||
|
||||
extract_msrv_attr!(EarlyContext);
|
||||
}
|
||||
|
||||
/// Check for empty lines after outer attributes.
|
||||
///
|
||||
/// Attributes and documenation comments are both considered outer attributes
|
||||
/// by the AST. However, the average user likely considers them to be different.
|
||||
/// Checking for empty lines after each of these attributes is split into two different
|
||||
/// lints but can share the same logic.
|
||||
fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
|
||||
let mut iter = item.attrs.iter().peekable();
|
||||
while let Some(attr) = iter.next() {
|
||||
if matches!(attr.kind, AttrKind::Normal(..))
|
||||
if (matches!(attr.kind, AttrKind::Normal(..)) || matches!(attr.kind, AttrKind::DocComment(..)))
|
||||
&& attr.style == AttrStyle::Outer
|
||||
&& is_present_in_source(cx, attr.span)
|
||||
{
|
||||
@ -639,13 +718,20 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
|
||||
let lines = without_block_comments(lines);
|
||||
|
||||
if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
|
||||
span_lint(
|
||||
cx,
|
||||
EMPTY_LINE_AFTER_OUTER_ATTR,
|
||||
begin_of_attr_to_item,
|
||||
"found an empty line after an outer attribute. \
|
||||
Perhaps you forgot to add a `!` to make it an inner attribute?",
|
||||
);
|
||||
let (lint_msg, lint_type) = match attr.kind {
|
||||
AttrKind::DocComment(..) => (
|
||||
"found an empty line after a doc comment. \
|
||||
Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?",
|
||||
EMPTY_LINE_AFTER_DOC_COMMENTS,
|
||||
),
|
||||
AttrKind::Normal(..) => (
|
||||
"found an empty line after an outer attribute. \
|
||||
Perhaps you forgot to add a `!` to make it an inner attribute?",
|
||||
EMPTY_LINE_AFTER_OUTER_ATTR,
|
||||
),
|
||||
};
|
||||
|
||||
span_lint(cx, lint_type, begin_of_attr_to_item, lint_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -690,6 +776,48 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msr
|
||||
}
|
||||
}
|
||||
|
||||
fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
|
||||
for item in items.iter() {
|
||||
if let NestedMetaItem::MetaItem(meta) = item {
|
||||
if !meta.has_name(sym::any) && !meta.has_name(sym::all) {
|
||||
continue;
|
||||
}
|
||||
if let MetaItemKind::List(list) = &meta.kind {
|
||||
check_nested_cfg(cx, list);
|
||||
if list.len() == 1 {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NON_MINIMAL_CFG,
|
||||
meta.span,
|
||||
"unneeded sub `cfg` when there is only one condition",
|
||||
|diag| {
|
||||
if let Some(snippet) = snippet_opt(cx, list[0].span()) {
|
||||
diag.span_suggestion(meta.span, "try", snippet, Applicability::MaybeIncorrect);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if list.is_empty() && meta.has_name(sym::all) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
NON_MINIMAL_CFG,
|
||||
meta.span,
|
||||
"unneeded sub `cfg` when there is no condition",
|
||||
|_| {},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
if attr.has_name(sym::cfg) &&
|
||||
let Some(items) = attr.meta_item_list()
|
||||
{
|
||||
check_nested_cfg(cx, &items);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
|
||||
fn find_os(name: &str) -> Option<&'static str> {
|
||||
UNIX_SYSTEMS
|
||||
|
@ -85,8 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let span =
|
||||
block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
||||
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
||||
if span.from_expansion() || expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::reference::DEREF_ADDROF;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{get_parent_expr, is_lint_allowed};
|
||||
@ -47,8 +48,8 @@ declare_clippy_lint! {
|
||||
|
||||
declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
|
||||
|
||||
impl LateLintPass<'_> for BorrowDerefRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, e: &rustc_hir::Expr<'_>) {
|
||||
impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) {
|
||||
if_chain! {
|
||||
if !e.span.from_expansion();
|
||||
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind;
|
||||
@ -58,6 +59,7 @@ impl LateLintPass<'_> for BorrowDerefRef {
|
||||
if !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..) );
|
||||
let ref_ty = cx.typeck_results().expr_ty(deref_target);
|
||||
if let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind();
|
||||
if !is_from_proc_macro(cx, e);
|
||||
then{
|
||||
|
||||
if let Some(parent_expr) = get_parent_expr(cx, e){
|
||||
|
@ -8,7 +8,9 @@ use rustc_hir::{
|
||||
Block, Expr, ExprKind, Local, Node, QPath, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::{lint::in_external_macro, ty::print::with_forced_trimmed_paths};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
use rustc_middle::ty::IsSuggestable;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
|
||||
@ -49,7 +51,6 @@ impl LateLintPass<'_> for BoxDefault {
|
||||
&& path_def_id(cx, ty).map_or(false, |id| Some(id) == cx.tcx.lang_items().owned_box())
|
||||
&& is_default_equivalent(cx, arg)
|
||||
{
|
||||
let arg_ty = cx.typeck_results().expr_ty(arg);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
BOX_DEFAULT,
|
||||
@ -58,8 +59,10 @@ impl LateLintPass<'_> for BoxDefault {
|
||||
"try",
|
||||
if is_plain_default(arg_path) || given_type(cx, expr) {
|
||||
"Box::default()".into()
|
||||
} else {
|
||||
} else if let Some(arg_ty) = cx.typeck_results().expr_ty(arg).make_suggestable(cx.tcx, true) {
|
||||
with_forced_trimmed_paths!(format!("Box::<{arg_ty}>::default()"))
|
||||
} else {
|
||||
return
|
||||
},
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
|
@ -21,8 +21,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
|
||||
|
||||
fn is_known_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
match constant(cx, cx.typeck_results(), e) {
|
||||
Some((Constant::F64(n), _)) => n.is_nan(),
|
||||
Some((Constant::F32(n), _)) => n.is_nan(),
|
||||
Some(Constant::F64(n)) => n.is_nan(),
|
||||
Some(Constant::F32(n)) => n.is_nan(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use rustc_target::abi::IntegerType;
|
||||
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
|
||||
|
||||
fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) {
|
||||
if let Some(Constant::Int(c)) = constant(cx, cx.typeck_results(), expr) {
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
|
@ -29,7 +29,7 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
|
||||
// Don't lint for positive constants.
|
||||
let const_val = constant(cx, cx.typeck_results(), cast_op);
|
||||
if_chain! {
|
||||
if let Some((Constant::Int(n), _)) = const_val;
|
||||
if let Some(Constant::Int(n)) = const_val;
|
||||
if let ty::Int(ity) = *cast_from.kind();
|
||||
if sext(cx.tcx, n, ity) >= 0;
|
||||
then {
|
||||
|
@ -3,10 +3,10 @@ use clippy_utils::macros::root_macro_call_first_node;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_in_cfg_test, is_in_test_function};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_hir::{Expr, ExprKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{sym, BytePos, Pos, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -31,6 +31,31 @@ declare_clippy_lint! {
|
||||
"`dbg!` macro is intended as a debugging tool"
|
||||
}
|
||||
|
||||
/// Gets the span of the statement up to the next semicolon, if and only if the next
|
||||
/// non-whitespace character actually is a semicolon.
|
||||
/// E.g.
|
||||
/// ```rust,ignore
|
||||
///
|
||||
/// dbg!();
|
||||
/// ^^^^^^^ this span is returned
|
||||
///
|
||||
/// foo!(dbg!());
|
||||
/// no span is returned
|
||||
/// ```
|
||||
fn span_including_semi(cx: &LateContext<'_>, span: Span) -> Option<Span> {
|
||||
let sm = cx.sess().source_map();
|
||||
let sf = sm.lookup_source_file(span.hi());
|
||||
let src = sf.src.as_ref()?.get(span.hi().to_usize()..)?;
|
||||
let first_non_whitespace = src.find(|c: char| !c.is_whitespace())?;
|
||||
|
||||
if src.as_bytes()[first_non_whitespace] == b';' {
|
||||
let hi = span.hi() + BytePos::from_usize(first_non_whitespace + 1);
|
||||
Some(span.with_hi(hi))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DbgMacro {
|
||||
allow_dbg_in_tests: bool,
|
||||
@ -55,13 +80,25 @@ impl LateLintPass<'_> for DbgMacro {
|
||||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let suggestion = match expr.peel_drop_temps().kind {
|
||||
|
||||
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
|
||||
// dbg!()
|
||||
ExprKind::Block(_, _) => String::new(),
|
||||
// dbg!(1)
|
||||
ExprKind::Match(val, ..) => {
|
||||
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string()
|
||||
ExprKind::Block(..) => {
|
||||
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
|
||||
// remove the whole statement.
|
||||
if let Some(Node::Stmt(stmt)) = cx.tcx.hir().find_parent(expr.hir_id)
|
||||
&& let Some(span) = span_including_semi(cx, stmt.span.source_callsite())
|
||||
{
|
||||
(span, String::new())
|
||||
} else {
|
||||
(macro_call.span, String::from("()"))
|
||||
}
|
||||
},
|
||||
// dbg!(1)
|
||||
ExprKind::Match(val, ..) => (
|
||||
macro_call.span,
|
||||
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string(),
|
||||
),
|
||||
// dbg!(2, 3)
|
||||
ExprKind::Tup(
|
||||
[
|
||||
@ -82,7 +119,7 @@ impl LateLintPass<'_> for DbgMacro {
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
format!("({snippet})")
|
||||
(macro_call.span, format!("({snippet})"))
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
@ -90,7 +127,7 @@ impl LateLintPass<'_> for DbgMacro {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
DBG_MACRO,
|
||||
macro_call.span,
|
||||
sugg_span,
|
||||
"the `dbg!` macro is intended as a debugging tool",
|
||||
"remove the invocation before committing it to a version control system",
|
||||
suggestion,
|
||||
|
@ -48,9 +48,11 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::attrs::BLANKET_CLIPPY_RESTRICTION_LINTS_INFO,
|
||||
crate::attrs::DEPRECATED_CFG_ATTR_INFO,
|
||||
crate::attrs::DEPRECATED_SEMVER_INFO,
|
||||
crate::attrs::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO,
|
||||
crate::attrs::EMPTY_LINE_AFTER_OUTER_ATTR_INFO,
|
||||
crate::attrs::INLINE_ALWAYS_INFO,
|
||||
crate::attrs::MISMATCHED_TARGET_OS_INFO,
|
||||
crate::attrs::NON_MINIMAL_CFG_INFO,
|
||||
crate::attrs::USELESS_ATTRIBUTE_INFO,
|
||||
crate::await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE_INFO,
|
||||
crate::await_holding_invalid::AWAIT_HOLDING_LOCK_INFO,
|
||||
@ -347,6 +349,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::methods::ITER_WITH_DRAIN_INFO,
|
||||
crate::methods::MANUAL_FILTER_MAP_INFO,
|
||||
crate::methods::MANUAL_FIND_MAP_INFO,
|
||||
crate::methods::MANUAL_NEXT_BACK_INFO,
|
||||
crate::methods::MANUAL_OK_OR_INFO,
|
||||
crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO,
|
||||
crate::methods::MANUAL_SPLIT_ONCE_INFO,
|
||||
@ -485,7 +488,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::operators::FLOAT_EQUALITY_WITHOUT_ABS_INFO,
|
||||
crate::operators::IDENTITY_OP_INFO,
|
||||
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
|
||||
crate::operators::INTEGER_ARITHMETIC_INFO,
|
||||
crate::operators::INTEGER_DIVISION_INFO,
|
||||
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
|
||||
crate::operators::MODULO_ARITHMETIC_INFO,
|
||||
@ -535,6 +537,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
||||
crate::redundant_slicing::REDUNDANT_SLICING_INFO,
|
||||
crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO,
|
||||
crate::ref_option_ref::REF_OPTION_REF_INFO,
|
||||
crate::ref_patterns::REF_PATTERNS_INFO,
|
||||
crate::reference::DEREF_ADDROF_INFO,
|
||||
crate::regex::INVALID_REGEX_INFO,
|
||||
crate::regex::TRIVIAL_REGEX_INFO,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::{diagnostics::span_lint_and_sugg, is_from_proc_macro, match_def_path, paths};
|
||||
use clippy_utils::{diagnostics::span_lint_and_sugg, match_def_path, paths};
|
||||
use hir::{def::Res, ExprKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
@ -55,7 +55,8 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs {
|
||||
if let ty::Adt(def, ..) = cx.typeck_results().expr_ty(expr).kind();
|
||||
if def.is_struct();
|
||||
if let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant();
|
||||
if !var.is_field_list_non_exhaustive() && !is_from_proc_macro(cx, expr);
|
||||
if !var.is_field_list_non_exhaustive();
|
||||
if !expr.span.from_expansion() && !qpath.span().from_expansion();
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -114,7 +114,7 @@ declare_lint_pass!(FloatingPointArithmetic => [
|
||||
// Returns the specialized log method for a given base if base is constant
|
||||
// and is one of 2, 10 and e
|
||||
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>) -> Option<&'static str> {
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), base) {
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), base) {
|
||||
if F32(2.0) == value || F64(2.0) == value {
|
||||
return Some("log2");
|
||||
} else if F32(10.0) == value || F64(10.0) == value {
|
||||
@ -193,8 +193,8 @@ fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
|
||||
constant(cx, cx.typeck_results(), lhs),
|
||||
constant(cx, cx.typeck_results(), rhs),
|
||||
) {
|
||||
(Some((value, _)), _) if F32(1.0) == value || F64(1.0) == value => rhs,
|
||||
(_, Some((value, _))) if F32(1.0) == value || F64(1.0) == value => lhs,
|
||||
(Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs,
|
||||
(_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@ -237,7 +237,7 @@ fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
|
||||
|
||||
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
// Check receiver
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), receiver) {
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), receiver) {
|
||||
if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
|
||||
Some("exp")
|
||||
} else if F32(2.0) == value || F64(2.0) == value {
|
||||
@ -258,7 +258,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
|
||||
}
|
||||
|
||||
// Check argument
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) {
|
||||
let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
|
||||
(
|
||||
SUBOPTIMAL_FLOPS,
|
||||
@ -298,7 +298,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args:
|
||||
}
|
||||
|
||||
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), &args[0]) {
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), &args[0]) {
|
||||
if value == Int(2) {
|
||||
if let Some(parent) = get_parent_expr(cx, expr) {
|
||||
if let Some(grandparent) = get_parent_expr(cx, parent) {
|
||||
@ -384,8 +384,8 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
|
||||
_
|
||||
) = &add_rhs.kind;
|
||||
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
|
||||
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
|
||||
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
|
||||
if let Some(lvalue) = constant(cx, cx.typeck_results(), largs_1);
|
||||
if let Some(rvalue) = constant(cx, cx.typeck_results(), rargs_1);
|
||||
if Int(2) == lvalue && Int(2) == rvalue;
|
||||
then {
|
||||
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, "..").maybe_par(), Sugg::hir(cx, rargs_0, "..")));
|
||||
@ -416,7 +416,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
|
||||
if cx.typeck_results().expr_ty(lhs).is_floating_point();
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), rhs);
|
||||
if F32(1.0) == value || F64(1.0) == value;
|
||||
if let ExprKind::MethodCall(path, self_arg, ..) = &lhs.kind;
|
||||
if cx.typeck_results().expr_ty(self_arg).is_floating_point();
|
||||
@ -669,8 +669,8 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
mul_lhs,
|
||||
mul_rhs,
|
||||
) = &div_lhs.kind;
|
||||
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), div_rhs);
|
||||
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), mul_rhs);
|
||||
if let Some(rvalue) = constant(cx, cx.typeck_results(), div_rhs);
|
||||
if let Some(lvalue) = constant(cx, cx.typeck_results(), mul_rhs);
|
||||
then {
|
||||
// TODO: also check for constant values near PI/180 or 180/PI
|
||||
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) &&
|
||||
|
@ -89,11 +89,7 @@ impl<'tcx> LateLintPass<'tcx> for FnNullCheck {
|
||||
|
||||
// Catching:
|
||||
// (fn_ptr as *<const/mut> <ty>) == <const that evaluates to null_ptr>
|
||||
_ if matches!(
|
||||
constant(cx, cx.typeck_results(), to_check),
|
||||
Some((Constant::RawPtr(0), _))
|
||||
) =>
|
||||
{
|
||||
_ if matches!(constant(cx, cx.typeck_results(), to_check), Some(Constant::RawPtr(0))) => {
|
||||
lint_expr(cx, expr);
|
||||
},
|
||||
|
||||
|
@ -101,10 +101,10 @@ fn get_int_max(ty: Ty<'_>) -> Option<u128> {
|
||||
fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, BinOpKind, &'tcx Expr<'tcx>)> {
|
||||
if let ExprKind::Binary(op, l, r) = expr.kind {
|
||||
let tr = cx.typeck_results();
|
||||
if let Some((Constant::Int(c), _)) = constant(cx, tr, r) {
|
||||
if let Some(Constant::Int(c)) = constant(cx, tr, r) {
|
||||
return Some((c, op.node, l));
|
||||
};
|
||||
if let Some((Constant::Int(c), _)) = constant(cx, tr, l) {
|
||||
if let Some(Constant::Int(c)) = constant(cx, tr, l) {
|
||||
return Some((c, invert_op(op.node)?, r));
|
||||
}
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> {
|
||||
let parent_id = map.parent_id(expr.hir_id);
|
||||
if let Some(hir::Node::Expr(parent_expr)) = map.find(parent_id);
|
||||
if let hir::ExprKind::Index(_, index_expr) = parent_expr.kind;
|
||||
if let Some((Constant::Int(index_value), _)) = constant(cx, cx.typeck_results(), index_expr);
|
||||
if let Some(Constant::Int(index_value)) = constant(cx, cx.typeck_results(), index_expr);
|
||||
if let Ok(index_value) = index_value.try_into();
|
||||
if index_value < max_suggested_slice;
|
||||
|
||||
|
@ -191,18 +191,14 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
|
||||
/// Returns a tuple of options with the start and end (exclusive) values of
|
||||
/// the range. If the start or end is not constant, None is returned.
|
||||
fn to_const_range(cx: &LateContext<'_>, range: higher::Range<'_>, array_size: u128) -> (Option<u128>, Option<u128>) {
|
||||
let s = range
|
||||
.start
|
||||
.map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
|
||||
let s = range.start.map(|expr| constant(cx, cx.typeck_results(), expr));
|
||||
let start = match s {
|
||||
Some(Some(Constant::Int(x))) => Some(x),
|
||||
Some(_) => None,
|
||||
None => Some(0),
|
||||
};
|
||||
|
||||
let e = range
|
||||
.end
|
||||
.map(|expr| constant(cx, cx.typeck_results(), expr).map(|(c, _)| c));
|
||||
let e = range.end.map(|expr| constant(cx, cx.typeck_results(), expr));
|
||||
let end = match e {
|
||||
Some(Some(Constant::Int(x))) => {
|
||||
if range.limits == RangeLimits::Closed {
|
||||
|
@ -1,10 +1,12 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::ty::{implements_trait, is_must_use_ty, match_type};
|
||||
use clippy_utils::{is_must_use_func_call, paths};
|
||||
use rustc_hir::{ExprKind, Local, PatKind};
|
||||
use rustc_hir::{Local, PatKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::subst::GenericArgKind;
|
||||
use rustc_middle::ty::IsSuggestable;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
@ -138,7 +140,7 @@ const SYNC_GUARD_PATHS: [&[&str]; 3] = [
|
||||
];
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
|
||||
fn check_local(&mut self, cx: &LateContext<'_>, local: &Local<'_>) {
|
||||
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &Local<'tcx>) {
|
||||
if !in_external_macro(cx.tcx.sess, local.span)
|
||||
&& let PatKind::Wild = local.pat.kind
|
||||
&& let Some(init) = local.init
|
||||
@ -191,15 +193,17 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
|
||||
if local.pat.default_binding_modes && local.ty.is_none() {
|
||||
// When `default_binding_modes` is true, the `let` keyword is present.
|
||||
|
||||
// Ignore function calls that return impl traits...
|
||||
if let Some(init) = local.init &&
|
||||
matches!(init.kind, ExprKind::Call(_, _) | ExprKind::MethodCall(_, _, _, _)) {
|
||||
let expr_ty = cx.typeck_results().expr_ty(init);
|
||||
if expr_ty.is_impl_trait() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Ignore unnameable types
|
||||
if let Some(init) = local.init
|
||||
&& !cx.typeck_results().expr_ty(init).is_suggestable(cx.tcx, true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore if it is from a procedural macro...
|
||||
if is_from_proc_macro(cx, init) {
|
||||
return;
|
||||
}
|
||||
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -266,6 +266,7 @@ mod redundant_pub_crate;
|
||||
mod redundant_slicing;
|
||||
mod redundant_static_lifetimes;
|
||||
mod ref_option_ref;
|
||||
mod ref_patterns;
|
||||
mod reference;
|
||||
mod regex;
|
||||
mod return_self_not_must_use;
|
||||
@ -331,8 +332,11 @@ mod zero_div_zero;
|
||||
mod zero_sized_map_values;
|
||||
// end lints modules, do not remove this comment, it’s used in `update_lints`
|
||||
|
||||
use crate::utils::conf::{format_error, TryConf};
|
||||
pub use crate::utils::conf::{lookup_conf_file, Conf};
|
||||
use crate::utils::{
|
||||
conf::{format_error, metadata::get_configuration_metadata, TryConf},
|
||||
FindAll,
|
||||
};
|
||||
|
||||
/// Register all pre expansion lints
|
||||
///
|
||||
@ -471,7 +475,22 @@ pub(crate) struct LintInfo {
|
||||
pub fn explain(name: &str) {
|
||||
let target = format!("clippy::{}", name.to_ascii_uppercase());
|
||||
match declared_lints::LINTS.iter().find(|info| info.lint.name == target) {
|
||||
Some(info) => print!("{}", info.explanation),
|
||||
Some(info) => {
|
||||
println!("{}", info.explanation);
|
||||
// Check if the lint has configuration
|
||||
let mdconf = get_configuration_metadata();
|
||||
if let Some(config_vec_positions) = mdconf
|
||||
.iter()
|
||||
.find_all(|cconf| cconf.lints.contains(&info.lint.name_lower()[8..].to_owned()))
|
||||
{
|
||||
// If it has, print it
|
||||
println!("### Configuration for {}:\n", info.lint.name_lower());
|
||||
for position in config_vec_positions {
|
||||
let conf = &mdconf[position];
|
||||
println!(" - {}: {} (default: {})", conf.name, conf.doc, conf.default);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => println!("unknown lint: {name}"),
|
||||
}
|
||||
}
|
||||
@ -971,6 +990,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
||||
store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
|
||||
store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
|
||||
store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule));
|
||||
store.register_early_pass(|| Box::new(ref_patterns::RefPatterns));
|
||||
store.register_late_pass(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ declare_clippy_lint! {
|
||||
///
|
||||
/// ### Known problems
|
||||
/// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
|
||||
/// instance in all cases. There two cases where the suggestion might not be
|
||||
/// instance in all cases. There are two cases where the suggestion might not be
|
||||
/// appropriate or necessary:
|
||||
///
|
||||
/// - If the `Lines` instance can never produce any error, or if an error is produced
|
||||
|
@ -35,7 +35,8 @@ struct PathAndSpan {
|
||||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
@ -38,7 +38,6 @@ declare_clippy_lint! {
|
||||
/// Could be written:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #![feature(let_else)]
|
||||
/// # fn main () {
|
||||
/// # let w = Some(0);
|
||||
/// let Some(v) = w else { return };
|
||||
@ -69,29 +68,23 @@ impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
|
||||
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
|
||||
let if_let_or_match = if_chain! {
|
||||
if self.msrv.meets(msrvs::LET_ELSE);
|
||||
if !in_external_macro(cx.sess(), stmt.span);
|
||||
if let StmtKind::Local(local) = stmt.kind;
|
||||
if let Some(init) = local.init;
|
||||
if local.els.is_none();
|
||||
if local.ty.is_none();
|
||||
if init.span.ctxt() == stmt.span.ctxt();
|
||||
if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init);
|
||||
then {
|
||||
if_let_or_match
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let StmtKind::Local(local) = stmt.kind &&
|
||||
let Some(init) = local.init &&
|
||||
local.els.is_none() &&
|
||||
local.ty.is_none() &&
|
||||
init.span.ctxt() == stmt.span.ctxt() &&
|
||||
let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init) {
|
||||
match if_let_or_match {
|
||||
IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else) => if_chain! {
|
||||
if expr_is_simple_identity(let_pat, if_then);
|
||||
if let Some(if_else) = if_else;
|
||||
if expr_diverges(cx, if_else);
|
||||
then {
|
||||
emit_manual_let_else(cx, stmt.span, if_let_expr, let_pat, if_else);
|
||||
emit_manual_let_else(cx, stmt.span, if_let_expr, local.pat, let_pat, if_else);
|
||||
}
|
||||
},
|
||||
IfLetOrMatch::Match(match_expr, arms, source) => {
|
||||
@ -128,15 +121,23 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
|
||||
return;
|
||||
}
|
||||
|
||||
emit_manual_let_else(cx, stmt.span, match_expr, pat_arm.pat, diverging_arm.body);
|
||||
emit_manual_let_else(cx, stmt.span, match_expr, local.pat, pat_arm.pat, diverging_arm.body);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extract_msrv_attr!(LateContext);
|
||||
}
|
||||
|
||||
fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat: &Pat<'_>, else_body: &Expr<'_>) {
|
||||
fn emit_manual_let_else(
|
||||
cx: &LateContext<'_>,
|
||||
span: Span,
|
||||
expr: &Expr<'_>,
|
||||
local: &Pat<'_>,
|
||||
pat: &Pat<'_>,
|
||||
else_body: &Expr<'_>,
|
||||
) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
MANUAL_LET_ELSE,
|
||||
@ -145,12 +146,11 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat:
|
||||
|diag| {
|
||||
// This is far from perfect, for example there needs to be:
|
||||
// * mut additions for the bindings
|
||||
// * renamings of the bindings
|
||||
// * renamings of the bindings for `PatKind::Or`
|
||||
// * unused binding collision detection with existing ones
|
||||
// * putting patterns with at the top level | inside ()
|
||||
// for this to be machine applicable.
|
||||
let mut app = Applicability::HasPlaceholders;
|
||||
let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", &mut app);
|
||||
let (sn_expr, _) = snippet_with_context(cx, expr.span, span.ctxt(), "", &mut app);
|
||||
let (sn_else, _) = snippet_with_context(cx, else_body.span, span.ctxt(), "", &mut app);
|
||||
|
||||
@ -159,10 +159,21 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat:
|
||||
} else {
|
||||
format!("{{ {sn_else} }}")
|
||||
};
|
||||
let sn_bl = if matches!(pat.kind, PatKind::Or(..)) {
|
||||
format!("({sn_pat})")
|
||||
} else {
|
||||
sn_pat.into_owned()
|
||||
let sn_bl = match pat.kind {
|
||||
PatKind::Or(..) => {
|
||||
let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", &mut app);
|
||||
format!("({sn_pat})")
|
||||
},
|
||||
// Replace the variable name iff `TupleStruct` has one argument like `Variant(v)`.
|
||||
PatKind::TupleStruct(ref w, args, ..) if args.len() == 1 => {
|
||||
let sn_wrapper = cx.sess().source_map().span_to_snippet(w.span()).unwrap_or_default();
|
||||
let (sn_inner, _) = snippet_with_context(cx, local.span, span.ctxt(), "", &mut app);
|
||||
format!("{sn_wrapper}({sn_inner})")
|
||||
},
|
||||
_ => {
|
||||
let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", &mut app);
|
||||
sn_pat.into_owned()
|
||||
},
|
||||
};
|
||||
let sugg = format!("let {sn_bl} = {sn_expr} else {else_bl};");
|
||||
diag.span_suggestion(span, "consider writing", sugg, app);
|
||||
|
@ -144,7 +144,7 @@ fn len_arg<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E
|
||||
|
||||
// Returns the length of the `expr` if it's a constant string or char.
|
||||
fn constant_length(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<u128> {
|
||||
let (value, _) = constant(cx, cx.typeck_results(), expr)?;
|
||||
let value = constant(cx, cx.typeck_results(), expr)?;
|
||||
match value {
|
||||
Constant::Str(value) => Some(value.len() as u128),
|
||||
Constant::Char(value) => Some(value.len_utf8() as u128),
|
||||
|
@ -1,10 +1,12 @@
|
||||
use super::REDUNDANT_PATTERN_MATCHING;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use clippy_utils::is_wild;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::span_contains_comment;
|
||||
use rustc_ast::{Attribute, LitKind};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat};
|
||||
use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat, PatKind, QPath};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::source_map::Spanned;
|
||||
@ -99,6 +101,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
for arm in iter_without_last.clone() {
|
||||
if let Some(pat) = arm.1 {
|
||||
if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some(pat.kind) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The suggestion may be incorrect, because some arms can have `cfg` attributes
|
||||
// evaluated into `false` and so such arms will be stripped before.
|
||||
let mut applicability = Applicability::MaybeIncorrect;
|
||||
@ -170,3 +180,13 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_some(path_kind: PatKind<'_>) -> bool {
|
||||
match path_kind {
|
||||
PatKind::TupleStruct(QPath::Resolved(_, path), [first, ..], _) if is_wild(first) => {
|
||||
let name = path.segments[0].ident;
|
||||
name.name == rustc_span::sym::Some
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -282,9 +282,8 @@ impl<'a> NormalizedPat<'a> {
|
||||
// TODO: Handle negative integers. They're currently treated as a wild match.
|
||||
ExprKind::Lit(lit) => match lit.node {
|
||||
LitKind::Str(sym, _) => Self::LitStr(sym),
|
||||
LitKind::ByteStr(ref bytes, _) => Self::LitBytes(bytes),
|
||||
LitKind::ByteStr(ref bytes, _) | LitKind::CStr(ref bytes, _) => Self::LitBytes(bytes),
|
||||
LitKind::Byte(val) => Self::LitInt(val.into()),
|
||||
LitKind::CStr(ref bytes, _) => Self::LitBytes(bytes),
|
||||
LitKind::Char(val) => Self::LitInt(val.into()),
|
||||
LitKind::Int(val, _) => Self::LitInt(val),
|
||||
LitKind::Bool(val) => Self::LitBool(val),
|
||||
|
@ -25,9 +25,9 @@ mod wild_in_or_pats;
|
||||
|
||||
use clippy_utils::msrvs::{self, Msrv};
|
||||
use clippy_utils::source::{snippet_opt, walk_span_to_context};
|
||||
use clippy_utils::{higher, in_constant, is_span_match};
|
||||
use clippy_utils::{higher, in_constant, is_span_match, tokenize_with_text};
|
||||
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lexer::TokenKind;
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
@ -1147,12 +1147,7 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
|
||||
// Assume true. This would require either an invalid span, or one which crosses file boundaries.
|
||||
return true;
|
||||
};
|
||||
let mut pos = 0usize;
|
||||
let mut iter = tokenize(&snip).map(|t| {
|
||||
let start = pos;
|
||||
pos += t.len as usize;
|
||||
(t.kind, start..pos)
|
||||
});
|
||||
let mut iter = tokenize_with_text(&snip);
|
||||
|
||||
// Search for the token sequence [`#`, `[`, `cfg`]
|
||||
while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
|
||||
@ -1163,7 +1158,7 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
|
||||
)
|
||||
});
|
||||
if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
|
||||
&& matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")
|
||||
&& matches!(iter.next(), Some((TokenKind::Ident, "cfg")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
|
||||
if let Arm { pat, guard: None, .. } = *arm {
|
||||
if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
|
||||
let lhs_const = match lhs {
|
||||
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0,
|
||||
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?,
|
||||
None => {
|
||||
let min_val_const = ty.numeric_min_val(cx.tcx)?;
|
||||
let min_constant = mir::ConstantKind::from_value(
|
||||
@ -45,7 +45,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
|
||||
},
|
||||
};
|
||||
let rhs_const = match rhs {
|
||||
Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0,
|
||||
Some(rhs) => constant(cx, cx.typeck_results(), rhs)?,
|
||||
None => {
|
||||
let max_val_const = ty.numeric_max_val(cx.tcx)?;
|
||||
let max_constant = mir::ConstantKind::from_value(
|
||||
|
@ -189,73 +189,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
|
||||
if arms.len() == 2 {
|
||||
let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind);
|
||||
|
||||
let found_good_method = match node_pair {
|
||||
(
|
||||
PatKind::TupleStruct(ref path_left, patterns_left, _),
|
||||
PatKind::TupleStruct(ref path_right, patterns_right, _),
|
||||
) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
|
||||
if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Lang(ResultOk),
|
||||
Item::Lang(ResultErr),
|
||||
"is_ok()",
|
||||
"is_err()",
|
||||
)
|
||||
.or_else(|| {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Diag(sym::IpAddr, sym!(V4)),
|
||||
Item::Diag(sym::IpAddr, sym!(V6)),
|
||||
"is_ipv4()",
|
||||
"is_ipv6()",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
|
||||
| (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
|
||||
if patterns.len() == 1 =>
|
||||
{
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Lang(OptionSome),
|
||||
Item::Lang(OptionNone),
|
||||
"is_some()",
|
||||
"is_none()",
|
||||
)
|
||||
.or_else(|| {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Lang(PollReady),
|
||||
Item::Lang(PollPending),
|
||||
"is_ready()",
|
||||
"is_pending()",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(good_method) = found_good_method {
|
||||
if let Some(good_method) = found_good_method(cx, arms, node_pair) {
|
||||
let span = expr.span.to(op.span);
|
||||
let result_expr = match &op.kind {
|
||||
ExprKind::AddrOf(_, _, borrowed) => borrowed,
|
||||
@ -279,6 +213,127 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op
|
||||
}
|
||||
}
|
||||
|
||||
fn found_good_method<'a>(
|
||||
cx: &LateContext<'_>,
|
||||
arms: &[Arm<'_>],
|
||||
node: (&PatKind<'_>, &PatKind<'_>),
|
||||
) -> Option<&'a str> {
|
||||
match node {
|
||||
(
|
||||
PatKind::TupleStruct(ref path_left, patterns_left, _),
|
||||
PatKind::TupleStruct(ref path_right, patterns_right, _),
|
||||
) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
|
||||
if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Lang(ResultOk),
|
||||
Item::Lang(ResultErr),
|
||||
"is_ok()",
|
||||
"is_err()",
|
||||
)
|
||||
.or_else(|| {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Diag(sym::IpAddr, sym!(V4)),
|
||||
Item::Diag(sym::IpAddr, sym!(V6)),
|
||||
"is_ipv4()",
|
||||
"is_ipv6()",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
|
||||
| (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
|
||||
if patterns.len() == 1 =>
|
||||
{
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Lang(OptionSome),
|
||||
Item::Lang(OptionNone),
|
||||
"is_some()",
|
||||
"is_none()",
|
||||
)
|
||||
.or_else(|| {
|
||||
find_good_method_for_match(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
path_right,
|
||||
Item::Lang(PollReady),
|
||||
Item::Lang(PollPending),
|
||||
"is_ready()",
|
||||
"is_pending()",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => {
|
||||
if let PatKind::Wild = patterns[0].kind {
|
||||
get_good_method(cx, arms, path_left)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(PatKind::Path(ref path_left), PatKind::Wild) => get_good_method(cx, arms, path_left),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_ident(path: &QPath<'_>) -> Option<rustc_span::symbol::Ident> {
|
||||
match path {
|
||||
QPath::Resolved(_, path) => {
|
||||
let name = path.segments[0].ident;
|
||||
Some(name)
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_good_method<'a>(cx: &LateContext<'_>, arms: &[Arm<'_>], path_left: &QPath<'_>) -> Option<&'a str> {
|
||||
if let Some(name) = get_ident(path_left) {
|
||||
return match name.as_str() {
|
||||
"Ok" => {
|
||||
find_good_method_for_matches_macro(cx, arms, path_left, Item::Lang(ResultOk), "is_ok()", "is_err()")
|
||||
},
|
||||
"Err" => {
|
||||
find_good_method_for_matches_macro(cx, arms, path_left, Item::Lang(ResultErr), "is_err()", "is_ok()")
|
||||
},
|
||||
"Some" => find_good_method_for_matches_macro(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
Item::Lang(OptionSome),
|
||||
"is_some()",
|
||||
"is_none()",
|
||||
),
|
||||
"None" => find_good_method_for_matches_macro(
|
||||
cx,
|
||||
arms,
|
||||
path_left,
|
||||
Item::Lang(OptionNone),
|
||||
"is_none()",
|
||||
"is_some()",
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Item {
|
||||
Lang(LangItem),
|
||||
@ -346,3 +401,29 @@ fn find_good_method_for_match<'a>(
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_good_method_for_matches_macro<'a>(
|
||||
cx: &LateContext<'_>,
|
||||
arms: &[Arm<'_>],
|
||||
path_left: &QPath<'_>,
|
||||
expected_item_left: Item,
|
||||
should_be_left: &'a str,
|
||||
should_be_right: &'a str,
|
||||
) -> Option<&'a str> {
|
||||
let first_pat = arms[0].pat;
|
||||
|
||||
let body_node_pair = if is_pat_variant(cx, first_pat, path_left, expected_item_left) {
|
||||
(&arms[0].body.kind, &arms[1].body.kind)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match body_node_pair {
|
||||
(ExprKind::Lit(lit_left), ExprKind::Lit(lit_right)) => match (&lit_left.node, &lit_right.node) {
|
||||
(LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left),
|
||||
(LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::symbol::{Symbol, sym};
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
|
||||
use super::INEFFICIENT_TO_STRING;
|
||||
|
||||
|
@ -13,7 +13,7 @@ use super::ITER_NTH_ZERO;
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
|
||||
if_chain! {
|
||||
if is_trait_method(cx, expr, sym::Iterator);
|
||||
if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg);
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), arg);
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
span_lint_and_sugg(
|
||||
|
@ -9,7 +9,7 @@ use super::ITERATOR_STEP_BY_ZERO;
|
||||
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) {
|
||||
if is_trait_method(cx, expr, sym::Iterator) {
|
||||
if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), arg) {
|
||||
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), arg) {
|
||||
span_lint(
|
||||
cx,
|
||||
ITERATOR_STEP_BY_ZERO,
|
||||
|
38
clippy_lints/src/methods/manual_next_back.rs
Normal file
38
clippy_lints/src/methods/manual_next_back.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_trait_method;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
expr: &'tcx Expr<'_>,
|
||||
rev_call: &'tcx Expr<'_>,
|
||||
rev_recv: &'tcx Expr<'_>,
|
||||
) {
|
||||
let rev_recv_ty = cx.typeck_results().expr_ty(rev_recv);
|
||||
|
||||
// check that the receiver of `rev` implements `DoubleEndedIterator` and
|
||||
// that `rev` and `next` come from `Iterator`
|
||||
if cx
|
||||
.tcx
|
||||
.get_diagnostic_item(sym::DoubleEndedIterator)
|
||||
.map_or(false, |double_ended_iterator| {
|
||||
implements_trait(cx, rev_recv_ty, double_ended_iterator, &[])
|
||||
})
|
||||
&& is_trait_method(cx, rev_call, sym::Iterator)
|
||||
&& is_trait_method(cx, expr, sym::Iterator)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
super::MANUAL_NEXT_BACK,
|
||||
expr.span.with_lo(rev_recv.span.hi()),
|
||||
"manual backwards iteration",
|
||||
"use",
|
||||
String::from(".next_back()"),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ mod iter_overeager_cloned;
|
||||
mod iter_skip_next;
|
||||
mod iter_with_drain;
|
||||
mod iterator_step_by_zero;
|
||||
mod manual_next_back;
|
||||
mod manual_ok_or;
|
||||
mod manual_saturating_arithmetic;
|
||||
mod manual_str_repeat;
|
||||
@ -3132,8 +3133,11 @@ declare_clippy_lint! {
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let iterator = vec![1].into_iter();
|
||||
/// let len = iterator.clone().collect::<Vec<_>>().len();
|
||||
/// // should be
|
||||
/// let len = iterator.collect::<Vec<_>>().len();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let iterator = vec![1].into_iter();
|
||||
/// let len = iterator.count();
|
||||
/// ```
|
||||
#[clippy::version = "1.30.0"]
|
||||
@ -3193,6 +3197,29 @@ declare_clippy_lint! {
|
||||
"calling `drain` in order to `clear` a container"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for `.rev().next()` on a `DoubleEndedIterator`
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// `.next_back()` is cleaner.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let foo = [0; 10];
|
||||
/// foo.iter().rev().next();
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// # let foo = [0; 10];
|
||||
/// foo.iter().next_back();
|
||||
/// ```
|
||||
#[clippy::version = "1.71.0"]
|
||||
pub MANUAL_NEXT_BACK,
|
||||
style,
|
||||
"manual reverse iteration of `DoubleEndedIterator`"
|
||||
}
|
||||
|
||||
pub struct Methods {
|
||||
avoid_breaking_exported_api: bool,
|
||||
msrv: Msrv,
|
||||
@ -3321,6 +3348,7 @@ impl_lint_pass!(Methods => [
|
||||
NEEDLESS_COLLECT,
|
||||
SUSPICIOUS_COMMAND_ARG_SPACE,
|
||||
CLEAR_WITH_DRAIN,
|
||||
MANUAL_NEXT_BACK,
|
||||
]);
|
||||
|
||||
/// Extracts a method call name, args, and `Span` of the method name.
|
||||
@ -3677,6 +3705,7 @@ impl Methods {
|
||||
("iter", []) => iter_next_slice::check(cx, expr, recv2),
|
||||
("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
|
||||
("skip_while", [_]) => skip_while_next::check(cx, expr),
|
||||
("rev", [])=> manual_next_back::check(cx, expr, recv, recv2),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
@ -3741,13 +3770,13 @@ impl Methods {
|
||||
unnecessary_sort_by::check(cx, expr, recv, arg, true);
|
||||
},
|
||||
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
|
||||
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
|
||||
if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
|
||||
suspicious_splitn::check(cx, name, expr, recv, count);
|
||||
str_splitn::check(cx, name, expr, recv, pat_arg, count, &self.msrv);
|
||||
}
|
||||
},
|
||||
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
|
||||
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
|
||||
if let Some(Constant::Int(count)) = constant(cx, cx.typeck_results(), count_arg) {
|
||||
suspicious_splitn::check(cx, name, expr, recv, count);
|
||||
}
|
||||
},
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::NEEDLESS_COLLECT;
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, make_normalized_projection, make_projection};
|
||||
@ -8,6 +7,7 @@ use clippy_utils::{
|
||||
can_move_expr_to_closure, get_enclosing_block, get_parent_node, is_trait_method, path_to_local, path_to_local_id,
|
||||
CaptureKind,
|
||||
};
|
||||
use clippy_utils::{fn_def_id, higher};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::{Applicability, MultiSpan};
|
||||
use rustc_hir::intravisit::{walk_block, walk_expr, Visitor};
|
||||
@ -16,7 +16,7 @@ use rustc_hir::{
|
||||
};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::{self, AssocKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
|
||||
use rustc_middle::ty::{self, AssocKind, Clause, EarlyBinder, GenericArg, GenericArgKind, PredicateKind, Ty};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{sym, Span, Symbol};
|
||||
|
||||
@ -32,6 +32,8 @@ pub(super) fn check<'tcx>(
|
||||
if let Some(parent) = get_parent_node(cx.tcx, collect_expr.hir_id) {
|
||||
match parent {
|
||||
Node::Expr(parent) => {
|
||||
check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr);
|
||||
|
||||
if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
let name = name.ident.as_str();
|
||||
@ -134,6 +136,68 @@ pub(super) fn check<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
/// checks for for collecting into a (generic) method or function argument
|
||||
/// taking an `IntoIterator`
|
||||
fn check_collect_into_intoiterator<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
parent: &'tcx Expr<'tcx>,
|
||||
collect_expr: &'tcx Expr<'tcx>,
|
||||
call_span: Span,
|
||||
iter_expr: &'tcx Expr<'tcx>,
|
||||
) {
|
||||
if let Some(id) = fn_def_id(cx, parent) {
|
||||
let args = match parent.kind {
|
||||
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) => args,
|
||||
_ => &[],
|
||||
};
|
||||
// find the argument index of the `collect_expr` in the
|
||||
// function / method call
|
||||
if let Some(arg_idx) = args.iter().position(|e| e.hir_id == collect_expr.hir_id).map(|i| {
|
||||
if matches!(parent.kind, ExprKind::MethodCall(_, _, _, _)) {
|
||||
i + 1
|
||||
} else {
|
||||
i
|
||||
}
|
||||
}) {
|
||||
// extract the input types of the function/method call
|
||||
// that contains `collect_expr`
|
||||
let inputs = cx
|
||||
.tcx
|
||||
.liberate_late_bound_regions(id, cx.tcx.fn_sig(id).subst_identity())
|
||||
.inputs();
|
||||
|
||||
// map IntoIterator generic bounds to their signature
|
||||
// types and check whether the argument type is an
|
||||
// `IntoIterator`
|
||||
if cx
|
||||
.tcx
|
||||
.param_env(id)
|
||||
.caller_bounds()
|
||||
.into_iter()
|
||||
.filter_map(|p| {
|
||||
if let PredicateKind::Clause(Clause::Trait(t)) = p.kind().skip_binder()
|
||||
&& cx.tcx.is_diagnostic_item(sym::IntoIterator,t.trait_ref.def_id) {
|
||||
Some(t.self_ty())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.any(|ty| ty == inputs[arg_idx])
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
NEEDLESS_COLLECT,
|
||||
call_span.with_lo(iter_expr.span.hi()),
|
||||
NEEDLESS_COLLECT_MSG,
|
||||
"remove this call",
|
||||
String::new(),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool`
|
||||
fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool {
|
||||
cx.typeck_results().type_dependent_def_id(call_id).map_or(false, |id| {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::consts::{constant_context, Constant};
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_lang_item;
|
||||
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
||||
recv: &'tcx Expr<'_>,
|
||||
repeat_arg: &'tcx Expr<'_>,
|
||||
) {
|
||||
if constant_context(cx, cx.typeck_results()).expr(repeat_arg) == Some(Constant::Int(1)) {
|
||||
if constant(cx, cx.typeck_results(), repeat_arg) == Some(Constant::Int(1)) {
|
||||
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
|
||||
if ty.is_str() {
|
||||
span_lint_and_sugg(
|
||||
|
@ -316,7 +316,7 @@ fn parse_iter_usage<'tcx>(
|
||||
};
|
||||
},
|
||||
("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
|
||||
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
|
||||
if let Some(Constant::Int(idx)) = constant(cx, cx.typeck_results(), idx_expr) {
|
||||
let span = if name.ident.as_str() == "nth" {
|
||||
e.span
|
||||
} else {
|
||||
|
@ -16,9 +16,12 @@ use rustc_span::source_map::{ExpnKind, Span};
|
||||
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
get_parent_expr, in_constant, is_integer_literal, is_no_std_crate, iter_input_pats, last_path_segment, SpanlessEq,
|
||||
get_parent_expr, in_constant, is_integer_literal, is_lint_allowed, is_no_std_crate, iter_input_pats,
|
||||
last_path_segment, SpanlessEq,
|
||||
};
|
||||
|
||||
use crate::ref_patterns::REF_PATTERNS;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for function arguments and let bindings denoted as
|
||||
@ -162,6 +165,10 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
|
||||
return;
|
||||
}
|
||||
for arg in iter_input_pats(decl, body) {
|
||||
// Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue.
|
||||
if !is_lint_allowed(cx, REF_PATTERNS, arg.pat.hir_id) {
|
||||
return;
|
||||
}
|
||||
if let PatKind::Binding(BindingAnnotation(ByRef::Yes, _), ..) = arg.pat.kind {
|
||||
span_lint(
|
||||
cx,
|
||||
@ -180,6 +187,8 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
|
||||
if let StmtKind::Local(local) = stmt.kind;
|
||||
if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., name, None) = local.pat.kind;
|
||||
if let Some(init) = local.init;
|
||||
// Do not emit if clippy::ref_patterns is not allowed to avoid having two lints for the same issue.
|
||||
if is_lint_allowed(cx, REF_PATTERNS, local.pat.hir_id);
|
||||
then {
|
||||
let ctxt = local.span.ctxt();
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
@ -92,10 +92,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
@ -146,7 +146,7 @@ fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
|
||||
impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
use self::Expression::{Bool, RetBool};
|
||||
if e.span.from_expansion() {
|
||||
if e.span.from_expansion() || !span_extract_comment(cx.tcx.sess.source_map(), e.span).is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some(higher::If {
|
||||
@ -209,8 +209,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
|
||||
}
|
||||
if let Some((lhs_a, a)) = fetch_assign(then) &&
|
||||
let Some((lhs_b, b)) = fetch_assign(r#else) &&
|
||||
SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b) &&
|
||||
span_extract_comment(cx.tcx.sess.source_map(), e.span).is_empty()
|
||||
SpanlessEq::new(cx).eq_expr(lhs_a, lhs_b)
|
||||
{
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let cond = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability);
|
||||
|
@ -121,7 +121,7 @@ fn detect_absurd_comparison<'tcx>(
|
||||
fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
|
||||
let ty = cx.typeck_results().expr_ty(expr);
|
||||
|
||||
let cv = constant(cx, cx.typeck_results(), expr)?.0;
|
||||
let cv = constant(cx, cx.typeck_results(), expr)?;
|
||||
|
||||
let which = match (ty.kind(), cv) {
|
||||
(&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,
|
||||
|
@ -21,7 +21,7 @@ const HARD_CODED_ALLOWED_BINARY: &[[&str; 2]] = &[
|
||||
["f64", "f64"],
|
||||
["std::num::Saturating", "std::num::Saturating"],
|
||||
["std::num::Wrapping", "std::num::Wrapping"],
|
||||
["std::string::String", "&str"],
|
||||
["std::string::String", "str"],
|
||||
];
|
||||
const HARD_CODED_ALLOWED_UNARY: &[&str] = &["f32", "f64", "std::num::Saturating", "std::num::Wrapping"];
|
||||
const INTEGER_METHODS: &[&str] = &["saturating_div", "wrapping_div", "wrapping_rem", "wrapping_rem_euclid"];
|
||||
@ -113,7 +113,7 @@ impl ArithmeticSideEffects {
|
||||
if let hir::ExprKind::Lit(lit) = actual.kind && let ast::LitKind::Int(n, _) = lit.node {
|
||||
return Some(n)
|
||||
}
|
||||
if let Some((Constant::Int(n), _)) = constant(cx, cx.typeck_results(), expr) {
|
||||
if let Some(Constant::Int(n)) = constant(cx, cx.typeck_results(), expr) {
|
||||
return Some(n);
|
||||
}
|
||||
None
|
||||
@ -144,8 +144,10 @@ impl ArithmeticSideEffects {
|
||||
) {
|
||||
return;
|
||||
};
|
||||
let lhs_ty = cx.typeck_results().expr_ty(lhs);
|
||||
let rhs_ty = cx.typeck_results().expr_ty(rhs);
|
||||
let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
|
||||
let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
|
||||
let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs();
|
||||
let rhs_ty = cx.typeck_results().expr_ty(actual_rhs).peel_refs();
|
||||
if self.has_allowed_binary(lhs_ty, rhs_ty) {
|
||||
return;
|
||||
}
|
||||
@ -154,8 +156,6 @@ impl ArithmeticSideEffects {
|
||||
// At least for integers, shifts are already handled by the CTFE
|
||||
return;
|
||||
}
|
||||
let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
|
||||
let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
|
||||
match (
|
||||
Self::literal_integer(cx, actual_lhs),
|
||||
Self::literal_integer(cx, actual_rhs),
|
||||
|
@ -166,7 +166,7 @@ fn check_ineffective_gt(cx: &LateContext<'_>, span: Span, m: u128, c: u128, op:
|
||||
}
|
||||
|
||||
fn fetch_int_literal(cx: &LateContext<'_>, lit: &Expr<'_>) -> Option<u128> {
|
||||
match constant(cx, cx.typeck_results(), lit)?.0 {
|
||||
match constant(cx, cx.typeck_results(), lit)? {
|
||||
Constant::Int(n) => Some(n),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ pub(super) fn check(cx: &LateContext<'_>, e: &Expr<'_>, op: BinOpKind, lhs: &Exp
|
||||
}
|
||||
|
||||
fn is_nan(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
|
||||
if let Some((value, _)) = constant(cx, cx.typeck_results(), e) {
|
||||
if let Some(value) = constant(cx, cx.typeck_results(), e) {
|
||||
match value {
|
||||
Constant::F32(num) => num.is_nan(),
|
||||
Constant::F64(num) => num.is_nan(),
|
||||
|
@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>(
|
||||
if op == BinOpKind::Div
|
||||
&& let ExprKind::MethodCall(method_path, self_arg, [], _) = left.kind
|
||||
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_arg).peel_refs(), sym::Duration)
|
||||
&& let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right)
|
||||
&& let Some(Constant::Int(divisor)) = constant(cx, cx.typeck_results(), right)
|
||||
{
|
||||
let suggested_fn = match (method_path.ident.as_str(), divisor) {
|
||||
("subsec_micros", 1_000) | ("subsec_nanos", 1_000_000) => "subsec_millis",
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::consts::{constant_with_source, Constant};
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::get_item_name;
|
||||
use clippy_utils::sugg::Sugg;
|
||||
@ -18,9 +18,16 @@ pub(crate) fn check<'tcx>(
|
||||
right: &'tcx Expr<'_>,
|
||||
) {
|
||||
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
|
||||
if is_allowed(cx, left) || is_allowed(cx, right) {
|
||||
return;
|
||||
}
|
||||
let left_is_local = match constant_with_source(cx, cx.typeck_results(), left) {
|
||||
Some((c, s)) if !is_allowed(&c) => s.is_local(),
|
||||
Some(_) => return,
|
||||
None => true,
|
||||
};
|
||||
let right_is_local = match constant_with_source(cx, cx.typeck_results(), right) {
|
||||
Some((c, s)) if !is_allowed(&c) => s.is_local(),
|
||||
Some(_) => return,
|
||||
None => true,
|
||||
};
|
||||
|
||||
// Allow comparing the results of signum()
|
||||
if is_signum(cx, left) && is_signum(cx, right) {
|
||||
@ -34,10 +41,7 @@ pub(crate) fn check<'tcx>(
|
||||
}
|
||||
}
|
||||
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
|
||||
let (lint, msg) = get_lint_and_message(
|
||||
is_named_constant(cx, left) || is_named_constant(cx, right),
|
||||
is_comparing_arrays,
|
||||
);
|
||||
let (lint, msg) = get_lint_and_message(left_is_local && right_is_local, is_comparing_arrays);
|
||||
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
|
||||
let lhs = Sugg::hir(cx, left, "..");
|
||||
let rhs = Sugg::hir(cx, right, "..");
|
||||
@ -59,20 +63,8 @@ pub(crate) fn check<'tcx>(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_lint_and_message(
|
||||
is_comparing_constants: bool,
|
||||
is_comparing_arrays: bool,
|
||||
) -> (&'static rustc_lint::Lint, &'static str) {
|
||||
if is_comparing_constants {
|
||||
(
|
||||
FLOAT_CMP_CONST,
|
||||
if is_comparing_arrays {
|
||||
"strict comparison of `f32` or `f64` constant arrays"
|
||||
} else {
|
||||
"strict comparison of `f32` or `f64` constant"
|
||||
},
|
||||
)
|
||||
} else {
|
||||
fn get_lint_and_message(is_local: bool, is_comparing_arrays: bool) -> (&'static rustc_lint::Lint, &'static str) {
|
||||
if is_local {
|
||||
(
|
||||
FLOAT_CMP,
|
||||
if is_comparing_arrays {
|
||||
@ -81,22 +73,23 @@ fn get_lint_and_message(
|
||||
"strict comparison of `f32` or `f64`"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_named_constant<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
if let Some((_, res)) = constant(cx, cx.typeck_results(), expr) {
|
||||
res
|
||||
} else {
|
||||
false
|
||||
(
|
||||
FLOAT_CMP_CONST,
|
||||
if is_comparing_arrays {
|
||||
"strict comparison of `f32` or `f64` constant arrays"
|
||||
} else {
|
||||
"strict comparison of `f32` or `f64` constant"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_allowed<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
|
||||
match constant(cx, cx.typeck_results(), expr) {
|
||||
Some((Constant::F32(f), _)) => f == 0.0 || f.is_infinite(),
|
||||
Some((Constant::F64(f), _)) => f == 0.0 || f.is_infinite(),
|
||||
Some((Constant::Vec(vec), _)) => vec.iter().all(|f| match f {
|
||||
fn is_allowed(val: &Constant) -> bool {
|
||||
match val {
|
||||
&Constant::F32(f) => f == 0.0 || f.is_infinite(),
|
||||
&Constant::F64(f) => f == 0.0 || f.is_infinite(),
|
||||
Constant::Vec(vec) => vec.iter().all(|f| match f {
|
||||
Constant::F32(f) => *f == 0.0 || (*f).is_infinite(),
|
||||
Constant::F64(f) => *f == 0.0 || (*f).is_infinite(),
|
||||
_ => false,
|
||||
|
@ -96,32 +96,6 @@ declare_clippy_lint! {
|
||||
"any arithmetic expression that can cause side effects like overflows or panics"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for integer arithmetic operations which could overflow or panic.
|
||||
///
|
||||
/// Specifically, checks for any operators (`+`, `-`, `*`, `<<`, etc) which are capable
|
||||
/// of overflowing according to the [Rust
|
||||
/// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow),
|
||||
/// or which can panic (`/`, `%`). No bounds analysis or sophisticated reasoning is
|
||||
/// attempted.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Integer overflow will trigger a panic in debug builds or will wrap in
|
||||
/// release mode. Division by zero will cause a panic in either mode. In some applications one
|
||||
/// wants explicitly checked, wrapping or saturating arithmetic.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// # let a = 0;
|
||||
/// a + 1;
|
||||
/// ```
|
||||
#[clippy::version = "pre 1.29.0"]
|
||||
pub INTEGER_ARITHMETIC,
|
||||
restriction,
|
||||
"any integer arithmetic expression which could overflow or panic"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for float arithmetic.
|
||||
@ -787,7 +761,6 @@ pub struct Operators {
|
||||
impl_lint_pass!(Operators => [
|
||||
ABSURD_EXTREME_COMPARISONS,
|
||||
ARITHMETIC_SIDE_EFFECTS,
|
||||
INTEGER_ARITHMETIC,
|
||||
FLOAT_ARITHMETIC,
|
||||
ASSIGN_OP_PATTERN,
|
||||
MISREFACTORED_ASSIGN_OP,
|
||||
|
@ -40,7 +40,7 @@ struct OperandInfo {
|
||||
|
||||
fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<OperandInfo> {
|
||||
match constant(cx, cx.typeck_results(), operand) {
|
||||
Some((Constant::Int(v), _)) => match *cx.typeck_results().expr_ty(expr).kind() {
|
||||
Some(Constant::Int(v)) => match *cx.typeck_results().expr_ty(expr).kind() {
|
||||
ty::Int(ity) => {
|
||||
let value = sext(cx.tcx, v, ity);
|
||||
return Some(OperandInfo {
|
||||
@ -58,10 +58,10 @@ fn analyze_operand(operand: &Expr<'_>, cx: &LateContext<'_>, expr: &Expr<'_>) ->
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
Some((Constant::F32(f), _)) => {
|
||||
Some(Constant::F32(f)) => {
|
||||
return Some(floating_point_operand_info(&f));
|
||||
},
|
||||
Some((Constant::F64(f), _)) => {
|
||||
Some(Constant::F64(f)) => {
|
||||
return Some(floating_point_operand_info(&f));
|
||||
},
|
||||
_ => {},
|
||||
|
@ -1,8 +1,6 @@
|
||||
use super::{FLOAT_ARITHMETIC, INTEGER_ARITHMETIC};
|
||||
use super::FLOAT_ARITHMETIC;
|
||||
use clippy_utils::consts::constant_simple;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_from_proc_macro;
|
||||
use clippy_utils::is_integer_literal;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::source_map::Span;
|
||||
@ -45,31 +43,8 @@ impl Context {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (l_ty, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
|
||||
if l_ty.peel_refs().is_integral() && r_ty.peel_refs().is_integral() {
|
||||
if is_from_proc_macro(cx, expr) {
|
||||
return;
|
||||
}
|
||||
match op {
|
||||
hir::BinOpKind::Div | hir::BinOpKind::Rem => match &r.kind {
|
||||
hir::ExprKind::Lit(_lit) => (),
|
||||
hir::ExprKind::Unary(hir::UnOp::Neg, expr) => {
|
||||
if is_integer_literal(expr, 1) {
|
||||
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
},
|
||||
}
|
||||
} else if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
|
||||
let (_, r_ty) = (cx.typeck_results().expr_ty(l), cx.typeck_results().expr_ty(r));
|
||||
if r_ty.peel_refs().is_floating_point() && r_ty.peel_refs().is_floating_point() {
|
||||
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
}
|
||||
@ -80,17 +55,9 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
let ty = cx.typeck_results().expr_ty(arg);
|
||||
if constant_simple(cx, cx.typeck_results(), expr).is_none() {
|
||||
if ty.is_integral() {
|
||||
if is_from_proc_macro(cx, expr) {
|
||||
return;
|
||||
}
|
||||
span_lint(cx, INTEGER_ARITHMETIC, expr.span, "integer arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
} else if ty.is_floating_point() {
|
||||
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
}
|
||||
if constant_simple(cx, cx.typeck_results(), expr).is_none() && ty.is_floating_point() {
|
||||
span_lint(cx, FLOAT_ARITHMETIC, expr.span, "floating-point arithmetic detected");
|
||||
self.expr_id = Some(expr.hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ fn try_get_option_occurrence<'tcx>(
|
||||
ExprKind::Unary(UnOp::Deref, inner_expr) | ExprKind::AddrOf(_, _, inner_expr) => inner_expr,
|
||||
_ => expr,
|
||||
};
|
||||
let inner_pat = try_get_inner_pat(cx, pat)?;
|
||||
let (inner_pat, is_result) = try_get_inner_pat_and_is_result(cx, pat)?;
|
||||
if_chain! {
|
||||
if let PatKind::Binding(bind_annotation, _, id, None) = inner_pat.kind;
|
||||
if let Some(some_captures) = can_move_expr_to_closure(cx, if_then);
|
||||
@ -176,7 +176,7 @@ fn try_get_option_occurrence<'tcx>(
|
||||
),
|
||||
none_expr: format!(
|
||||
"{}{}",
|
||||
if method_sugg == "map_or" { "" } else { "|| " },
|
||||
if method_sugg == "map_or" { "" } else if is_result { "|_| " } else { "|| "},
|
||||
Sugg::hir_with_context(cx, none_body, ctxt, "..", &mut app),
|
||||
),
|
||||
});
|
||||
@ -186,11 +186,13 @@ fn try_get_option_occurrence<'tcx>(
|
||||
None
|
||||
}
|
||||
|
||||
fn try_get_inner_pat<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<&'tcx Pat<'tcx>> {
|
||||
fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> {
|
||||
if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind {
|
||||
let res = cx.qpath_res(qpath, pat.hir_id);
|
||||
if is_res_lang_ctor(cx, res, OptionSome) || is_res_lang_ctor(cx, res, ResultOk) {
|
||||
return Some(inner_pat);
|
||||
if is_res_lang_ctor(cx, res, OptionSome) {
|
||||
return Some((inner_pat, false));
|
||||
} else if is_res_lang_ctor(cx, res, ResultOk) {
|
||||
return Some((inner_pat, true));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -319,7 +319,7 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<R
|
||||
_ => return None,
|
||||
};
|
||||
if let Some(id) = path_to_local(l) {
|
||||
if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
|
||||
if let Some(c) = constant(cx, cx.typeck_results(), r) {
|
||||
return Some(RangeBounds {
|
||||
val: c,
|
||||
expr: r,
|
||||
@ -331,7 +331,7 @@ fn check_range_bounds<'a>(cx: &'a LateContext<'_>, ex: &'a Expr<'_>) -> Option<R
|
||||
});
|
||||
}
|
||||
} else if let Some(id) = path_to_local(r) {
|
||||
if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
|
||||
if let Some(c) = constant(cx, cx.typeck_results(), l) {
|
||||
return Some(RangeBounds {
|
||||
val: c,
|
||||
expr: l,
|
||||
@ -451,8 +451,8 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
if let Some(higher::Range { start: Some(start), end: Some(end), limits }) = higher::Range::hir(expr);
|
||||
let ty = cx.typeck_results().expr_ty(start);
|
||||
if let ty::Int(_) | ty::Uint(_) = ty.kind();
|
||||
if let Some((start_idx, _)) = constant(cx, cx.typeck_results(), start);
|
||||
if let Some((end_idx, _)) = constant(cx, cx.typeck_results(), end);
|
||||
if let Some(start_idx) = constant(cx, cx.typeck_results(), start);
|
||||
if let Some(end_idx) = constant(cx, cx.typeck_results(), end);
|
||||
if let Some(ordering) = Constant::partial_cmp(cx.tcx, ty, &start_idx, &end_idx);
|
||||
if is_empty_range(limits, ordering);
|
||||
then {
|
||||
|
44
clippy_lints/src/ref_patterns.rs
Normal file
44
clippy_lints/src/ref_patterns.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use rustc_ast::ast::{BindingAnnotation, Pat, PatKind};
|
||||
use rustc_lint::{EarlyContext, EarlyLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for usages of the `ref` keyword.
|
||||
/// ### Why is this bad?
|
||||
/// The `ref` keyword can be confusing for people unfamiliar with it, and often
|
||||
/// it is more concise to use `&` instead.
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let opt = Some(5);
|
||||
/// if let Some(ref foo) = opt {}
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// let opt = Some(5);
|
||||
/// if let Some(foo) = &opt {}
|
||||
/// ```
|
||||
#[clippy::version = "1.71.0"]
|
||||
pub REF_PATTERNS,
|
||||
restriction,
|
||||
"use of a ref pattern, e.g. Some(ref value)"
|
||||
}
|
||||
declare_lint_pass!(RefPatterns => [REF_PATTERNS]);
|
||||
|
||||
impl EarlyLintPass for RefPatterns {
|
||||
fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
|
||||
if let PatKind::Ident(BindingAnnotation::REF, _, _) = pat.kind
|
||||
&& !pat.span.from_expansion()
|
||||
{
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
REF_PATTERNS,
|
||||
pat.span,
|
||||
"usage of ref pattern",
|
||||
None,
|
||||
"consider using `&` for clarity instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -122,37 +122,39 @@ fn lint_syntax_error(cx: &LateContext<'_>, error: ®ex_syntax::Error, unescape
|
||||
}
|
||||
|
||||
fn const_str<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<String> {
|
||||
constant(cx, cx.typeck_results(), e).and_then(|(c, _)| match c {
|
||||
constant(cx, cx.typeck_results(), e).and_then(|c| match c {
|
||||
Constant::Str(s) => Some(s),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_trivial_regex(s: ®ex_syntax::hir::Hir) -> Option<&'static str> {
|
||||
use regex_syntax::hir::Anchor::{EndText, StartText};
|
||||
use regex_syntax::hir::HirKind::{Alternation, Anchor, Concat, Empty, Literal};
|
||||
use regex_syntax::hir::HirKind::{Alternation, Concat, Empty, Literal, Look};
|
||||
use regex_syntax::hir::Look as HirLook;
|
||||
|
||||
let is_literal = |e: &[regex_syntax::hir::Hir]| e.iter().all(|e| matches!(*e.kind(), Literal(_)));
|
||||
|
||||
match *s.kind() {
|
||||
Empty | Anchor(_) => Some("the regex is unlikely to be useful as it is"),
|
||||
Empty | Look(_) => Some("the regex is unlikely to be useful as it is"),
|
||||
Literal(_) => Some("consider using `str::contains`"),
|
||||
Alternation(ref exprs) => {
|
||||
if exprs.iter().all(|e| e.kind().is_empty()) {
|
||||
if exprs.iter().all(|e| matches!(e.kind(), Empty)) {
|
||||
Some("the regex is unlikely to be useful as it is")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
Concat(ref exprs) => match (exprs[0].kind(), exprs[exprs.len() - 1].kind()) {
|
||||
(&Anchor(StartText), &Anchor(EndText)) if exprs[1..(exprs.len() - 1)].is_empty() => {
|
||||
(&Look(HirLook::Start), &Look(HirLook::End)) if exprs[1..(exprs.len() - 1)].is_empty() => {
|
||||
Some("consider using `str::is_empty`")
|
||||
},
|
||||
(&Anchor(StartText), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
|
||||
(&Look(HirLook::Start), &Look(HirLook::End)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
|
||||
Some("consider using `==` on `str`s")
|
||||
},
|
||||
(&Anchor(StartText), &Literal(_)) if is_literal(&exprs[1..]) => Some("consider using `str::starts_with`"),
|
||||
(&Literal(_), &Anchor(EndText)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
|
||||
(&Look(HirLook::Start), &Literal(_)) if is_literal(&exprs[1..]) => {
|
||||
Some("consider using `str::starts_with`")
|
||||
},
|
||||
(&Literal(_), &Look(HirLook::End)) if is_literal(&exprs[1..(exprs.len() - 1)]) => {
|
||||
Some("consider using `str::ends_with`")
|
||||
},
|
||||
_ if is_literal(exprs) => Some("consider using `str::contains`"),
|
||||
@ -175,10 +177,7 @@ fn check_set<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
|
||||
}
|
||||
|
||||
fn check_regex<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, utf8: bool) {
|
||||
let mut parser = regex_syntax::ParserBuilder::new()
|
||||
.unicode(true)
|
||||
.allow_invalid_utf8(!utf8)
|
||||
.build();
|
||||
let mut parser = regex_syntax::ParserBuilder::new().unicode(true).utf8(!utf8).build();
|
||||
|
||||
if let ExprKind::Lit(lit) = expr.kind {
|
||||
if let LitKind::Str(ref r, style) = lit.node {
|
||||
|
@ -15,6 +15,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[
|
||||
("clippy::eval_order_dependence", "clippy::mixed_read_write_in_expression"),
|
||||
("clippy::identity_conversion", "clippy::useless_conversion"),
|
||||
("clippy::if_let_some_result", "clippy::match_result_ok"),
|
||||
("clippy::integer_arithmetic", "clippy::arithmetic_side_effects"),
|
||||
("clippy::logic_bug", "clippy::overly_complex_bool_expr"),
|
||||
("clippy::new_without_default_derive", "clippy::new_without_default"),
|
||||
("clippy::option_and_then_some", "clippy::bind_instead_of_map"),
|
||||
|
@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
||||
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. })
|
||||
&& let item = cx.tcx.hir().item(id)
|
||||
&& let ItemKind::Impl(Impl {
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
|
||||
{
|
||||
if !map.contains_key(res) {
|
||||
|
@ -132,7 +132,7 @@ declare_clippy_lint! {
|
||||
/// Probably lots of false positives. If an index comes from a known valid position (e.g.
|
||||
/// obtained via `char_indices` over the same string), it is totally OK.
|
||||
///
|
||||
/// # Example
|
||||
/// ### Example
|
||||
/// ```rust,should_panic
|
||||
/// &"Ölkanne"[1..];
|
||||
/// ```
|
||||
|
@ -37,12 +37,12 @@ declare_clippy_lint! {
|
||||
#[clippy::version = "1.38.0"]
|
||||
pub TYPE_REPETITION_IN_BOUNDS,
|
||||
nursery,
|
||||
"types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`"
|
||||
"types are repeated unnecessarily in trait bounds, use `+` instead of using `T: _, T: _`"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for cases where generics are being used and multiple
|
||||
/// Checks for cases where generics or trait objects are being used and multiple
|
||||
/// syntax specifications for trait bounds are used simultaneously.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
@ -167,6 +167,61 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) {
|
||||
if_chain! {
|
||||
if let TyKind::Ref(.., mut_ty) = &ty.kind;
|
||||
if let TyKind::TraitObject(bounds, ..) = mut_ty.ty.kind;
|
||||
if bounds.len() > 2;
|
||||
then {
|
||||
|
||||
// Build up a hash of every trait we've seen
|
||||
// When we see a trait for the first time, add it to unique_traits
|
||||
// so we can later use it to build a string of all traits exactly once, without duplicates
|
||||
|
||||
let mut seen_def_ids = FxHashSet::default();
|
||||
let mut unique_traits = Vec::new();
|
||||
|
||||
// Iterate the bounds and add them to our seen hash
|
||||
// If we haven't yet seen it, add it to the fixed traits
|
||||
for bound in bounds.iter() {
|
||||
let Some(def_id) = bound.trait_ref.trait_def_id() else { continue; };
|
||||
|
||||
let new_trait = seen_def_ids.insert(def_id);
|
||||
|
||||
if new_trait {
|
||||
unique_traits.push(bound);
|
||||
}
|
||||
}
|
||||
|
||||
// If the number of unique traits isn't the same as the number of traits in the bounds,
|
||||
// there must be 1 or more duplicates
|
||||
if bounds.len() != unique_traits.len() {
|
||||
let mut bounds_span = bounds[0].span;
|
||||
|
||||
for bound in bounds.iter().skip(1) {
|
||||
bounds_span = bounds_span.to(bound.span);
|
||||
}
|
||||
|
||||
let fixed_trait_snippet = unique_traits
|
||||
.iter()
|
||||
.filter_map(|b| snippet_opt(cx, b.span))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" + ");
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
TRAIT_DUPLICATION_IN_BOUNDS,
|
||||
bounds_span,
|
||||
"this trait bound is already specified in trait declaration",
|
||||
"try",
|
||||
fixed_trait_snippet,
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TraitBounds {
|
||||
|
@ -31,9 +31,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
|
||||
match arg.kind {
|
||||
// Catching:
|
||||
// transmute over constants that resolve to `null`.
|
||||
ExprKind::Path(ref _qpath)
|
||||
if matches!(constant(cx, cx.typeck_results(), arg), Some((Constant::RawPtr(0), _))) =>
|
||||
{
|
||||
ExprKind::Path(ref _qpath) if matches!(constant(cx, cx.typeck_results(), arg), Some(Constant::RawPtr(0))) => {
|
||||
lint_expr(cx, expr);
|
||||
true
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
use clippy_utils::consts::{constant_context, Constant};
|
||||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{is_integer_literal, is_path_diagnostic_item};
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
@ -16,9 +16,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
|
||||
}
|
||||
|
||||
// Catching transmute over constants that resolve to `null`.
|
||||
let mut const_eval_context = constant_context(cx, cx.typeck_results());
|
||||
if let ExprKind::Path(ref _qpath) = arg.kind &&
|
||||
let Some(Constant::RawPtr(0)) = const_eval_context.expr(arg)
|
||||
let Some(Constant::RawPtr(0)) = constant(cx, cx.typeck_results(), arg)
|
||||
{
|
||||
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
|
||||
return true;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::is_ty_alias;
|
||||
use clippy_utils::source::{snippet, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
|
||||
@ -138,6 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
|
||||
if_chain! {
|
||||
if let ExprKind::Path(ref qpath) = path.kind;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
|
||||
if !is_ty_alias(qpath);
|
||||
then {
|
||||
let a = cx.typeck_results().expr_ty(e);
|
||||
let b = cx.typeck_results().expr_ty(arg);
|
||||
|
@ -308,7 +308,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
||||
bind!(self, vec);
|
||||
kind!("CStr(ref {vec})");
|
||||
chain!(self, "let [{:?}] = **{vec}", vec.value);
|
||||
}
|
||||
},
|
||||
LitKind::Str(s, _) => {
|
||||
bind!(self, s);
|
||||
kind!("Str({s}, _)");
|
||||
|
@ -174,16 +174,15 @@ macro_rules! define_Conf {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
pub mod metadata {
|
||||
use crate::utils::internal_lints::metadata_collector::ClippyConfiguration;
|
||||
use crate::utils::ClippyConfiguration;
|
||||
|
||||
macro_rules! wrap_option {
|
||||
() => (None);
|
||||
($x:literal) => (Some($x));
|
||||
}
|
||||
|
||||
pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
|
||||
pub fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
|
||||
vec![
|
||||
$(
|
||||
{
|
||||
|
@ -8,7 +8,11 @@
|
||||
//! a simple mistake)
|
||||
|
||||
use crate::renamed_lints::RENAMED_LINTS;
|
||||
use crate::utils::internal_lints::lint_without_lint_pass::{extract_clippy_version_value, is_lint_ref_type};
|
||||
use crate::utils::{
|
||||
collect_configs,
|
||||
internal_lints::lint_without_lint_pass::{extract_clippy_version_value, is_lint_ref_type},
|
||||
ClippyConfiguration,
|
||||
};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::{match_type, walk_ptrs_ty_depth};
|
||||
@ -520,111 +524,6 @@ impl Serialize for ApplicabilityInfo {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// Configuration
|
||||
// ==================================================================
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ClippyConfiguration {
|
||||
name: String,
|
||||
config_type: &'static str,
|
||||
default: String,
|
||||
lints: Vec<String>,
|
||||
doc: String,
|
||||
#[allow(dead_code)]
|
||||
deprecation_reason: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl ClippyConfiguration {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
config_type: &'static str,
|
||||
default: String,
|
||||
doc_comment: &'static str,
|
||||
deprecation_reason: Option<&'static str>,
|
||||
) -> Self {
|
||||
let (lints, doc) = parse_config_field_doc(doc_comment)
|
||||
.unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
|
||||
|
||||
Self {
|
||||
name: to_kebab(name),
|
||||
lints,
|
||||
doc,
|
||||
config_type,
|
||||
default,
|
||||
deprecation_reason,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_markdown_paragraph(&self) -> String {
|
||||
format!(
|
||||
"### {}\n{}\n\n**Default Value:** `{}` (`{}`)\n\n{}\n\n",
|
||||
self.name,
|
||||
self.doc
|
||||
.lines()
|
||||
.map(|line| line.strip_prefix(" ").unwrap_or(line))
|
||||
.join("\n"),
|
||||
self.default,
|
||||
self.config_type,
|
||||
self.lints
|
||||
.iter()
|
||||
.map(|name| name.to_string().split_whitespace().next().unwrap().to_string())
|
||||
.map(|name| format!("* [{name}](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"))
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
fn to_markdown_table_entry(&self) -> String {
|
||||
format!("| [{}](#{}) | `{}` |", self.name, self.name, self.default)
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_configs() -> Vec<ClippyConfiguration> {
|
||||
crate::utils::conf::metadata::get_configuration_metadata()
|
||||
}
|
||||
|
||||
/// This parses the field documentation of the config struct.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
|
||||
/// ```
|
||||
///
|
||||
/// Would yield:
|
||||
/// ```rust, ignore
|
||||
/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
|
||||
/// ```
|
||||
fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
|
||||
const DOC_START: &str = " Lint: ";
|
||||
if_chain! {
|
||||
if doc_comment.starts_with(DOC_START);
|
||||
if let Some(split_pos) = doc_comment.find('.');
|
||||
then {
|
||||
let mut doc_comment = doc_comment.to_string();
|
||||
let mut documentation = doc_comment.split_off(split_pos);
|
||||
|
||||
// Extract lints
|
||||
doc_comment.make_ascii_lowercase();
|
||||
let lints: Vec<String> = doc_comment
|
||||
.split_off(DOC_START.len())
|
||||
.split(", ")
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
|
||||
// Format documentation correctly
|
||||
// split off leading `.` from lint name list and indent for correct formatting
|
||||
documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");
|
||||
|
||||
Some((lints, documentation))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
|
||||
fn to_kebab(config_name: &str) -> String {
|
||||
config_name.replace('_', "-")
|
||||
}
|
||||
|
||||
impl fmt::Display for ClippyConfiguration {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(
|
||||
|
@ -4,3 +4,143 @@ pub mod dump_hir;
|
||||
pub mod format_args_collector;
|
||||
#[cfg(feature = "internal")]
|
||||
pub mod internal_lints;
|
||||
#[cfg(feature = "internal")]
|
||||
use itertools::Itertools;
|
||||
|
||||
/// Transforms a given `snake_case_string` to a tasty `kebab-case-string`
|
||||
fn to_kebab(config_name: &str) -> String {
|
||||
config_name.replace('_', "-")
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// Configuration
|
||||
// ==================================================================
|
||||
#[derive(Debug, Clone, Default)] //~ ERROR no such field
|
||||
pub struct ClippyConfiguration {
|
||||
pub name: String,
|
||||
#[allow(dead_code)]
|
||||
config_type: &'static str,
|
||||
pub default: String,
|
||||
pub lints: Vec<String>,
|
||||
pub doc: String,
|
||||
#[allow(dead_code)]
|
||||
deprecation_reason: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl ClippyConfiguration {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
config_type: &'static str,
|
||||
default: String,
|
||||
doc_comment: &'static str,
|
||||
deprecation_reason: Option<&'static str>,
|
||||
) -> Self {
|
||||
let (lints, doc) = parse_config_field_doc(doc_comment)
|
||||
.unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string()));
|
||||
|
||||
Self {
|
||||
name: to_kebab(name),
|
||||
lints,
|
||||
doc,
|
||||
config_type,
|
||||
default,
|
||||
deprecation_reason,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
fn to_markdown_paragraph(&self) -> String {
|
||||
format!(
|
||||
"### {}\n{}\n\n**Default Value:** `{}` (`{}`)\n\n{}\n\n",
|
||||
self.name,
|
||||
self.doc
|
||||
.lines()
|
||||
.map(|line| line.strip_prefix(" ").unwrap_or(line))
|
||||
.join("\n"),
|
||||
self.default,
|
||||
self.config_type,
|
||||
self.lints
|
||||
.iter()
|
||||
.map(|name| name.to_string().split_whitespace().next().unwrap().to_string())
|
||||
.map(|name| format!("* [{name}](https://rust-lang.github.io/rust-clippy/master/index.html#{name})"))
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
fn to_markdown_table_entry(&self) -> String {
|
||||
format!("| [{}](#{}) | `{}` |", self.name, self.name, self.default)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "internal")]
|
||||
fn collect_configs() -> Vec<ClippyConfiguration> {
|
||||
crate::utils::conf::metadata::get_configuration_metadata()
|
||||
}
|
||||
|
||||
/// This parses the field documentation of the config struct.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin")
|
||||
/// ```
|
||||
///
|
||||
/// Would yield:
|
||||
/// ```rust, ignore
|
||||
/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin")
|
||||
/// ```
|
||||
fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec<String>, String)> {
|
||||
const DOC_START: &str = " Lint: ";
|
||||
if_chain! {
|
||||
if doc_comment.starts_with(DOC_START);
|
||||
if let Some(split_pos) = doc_comment.find('.');
|
||||
then {
|
||||
let mut doc_comment = doc_comment.to_string();
|
||||
let mut documentation = doc_comment.split_off(split_pos);
|
||||
|
||||
// Extract lints
|
||||
doc_comment.make_ascii_lowercase();
|
||||
let lints: Vec<String> = doc_comment
|
||||
.split_off(DOC_START.len())
|
||||
.split(", ")
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
|
||||
// Format documentation correctly
|
||||
// split off leading `.` from lint name list and indent for correct formatting
|
||||
documentation = documentation.trim_start_matches('.').trim().replace("\n ", "\n ");
|
||||
|
||||
Some((lints, documentation))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shamelessly stolen from find_all (https://github.com/nectariner/find_all)
|
||||
pub trait FindAll: Iterator + Sized {
|
||||
fn find_all<P>(&mut self, predicate: P) -> Option<Vec<usize>>
|
||||
where
|
||||
P: FnMut(&Self::Item) -> bool;
|
||||
}
|
||||
|
||||
impl<I> FindAll for I
|
||||
where
|
||||
I: Iterator,
|
||||
{
|
||||
fn find_all<P>(&mut self, mut predicate: P) -> Option<Vec<usize>>
|
||||
where
|
||||
P: FnMut(&Self::Item) -> bool,
|
||||
{
|
||||
let mut occurences = Vec::<usize>::default();
|
||||
for (index, element) in self.enumerate() {
|
||||
if predicate(&element) {
|
||||
occurences.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
match occurences.len() {
|
||||
0 => None,
|
||||
_ => Some(occurences),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ impl UselessVec {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let snippet = match *vec_args {
|
||||
higher::VecArgs::Repeat(elem, len) => {
|
||||
if let Some((Constant::Int(len_constant), _)) = constant(cx, cx.typeck_results(), len) {
|
||||
if let Some(Constant::Int(len_constant)) = constant(cx, cx.typeck_results(), len) {
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack {
|
||||
return;
|
||||
|
@ -7,7 +7,7 @@ use rustc_hir::{
|
||||
def::{DefKind, Res},
|
||||
Item, ItemKind, PathSegment, UseKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::symbol::kw;
|
||||
@ -117,6 +117,10 @@ impl_lint_pass!(WildcardImports => [ENUM_GLOB_USE, WILDCARD_IMPORTS]);
|
||||
|
||||
impl LateLintPass<'_> for WildcardImports {
|
||||
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
|
||||
if cx.sess().is_test_crate() {
|
||||
return;
|
||||
}
|
||||
|
||||
if is_test_module_or_function(cx.tcx, item) {
|
||||
self.test_modules_deep = self.test_modules_deep.saturating_add(1);
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
use crate::source::{get_source_text, walk_span_to_context};
|
||||
use crate::{clip, is_direct_expn_of, sext, unsext};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{self, LitFloatType, LitKind};
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp};
|
||||
use rustc_lexer::tokenize;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::mir::interpret::Scalar;
|
||||
use rustc_middle::ty::SubstsRef;
|
||||
use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt};
|
||||
use rustc_middle::ty::{List, SubstsRef};
|
||||
use rustc_middle::{bug, span_bug};
|
||||
use rustc_span::symbol::Symbol;
|
||||
use rustc_span::SyntaxContext;
|
||||
use std::cmp::Ordering::{self, Equal};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter;
|
||||
@ -210,8 +213,7 @@ pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
|
||||
match *lit {
|
||||
LitKind::Str(ref is, _) => Constant::Str(is.to_string()),
|
||||
LitKind::Byte(b) => Constant::Int(u128::from(b)),
|
||||
LitKind::ByteStr(ref s, _) => Constant::Binary(Lrc::clone(s)),
|
||||
LitKind::CStr(ref s, _) => Constant::Binary(Lrc::clone(s)),
|
||||
LitKind::ByteStr(ref s, _) | LitKind::CStr(ref s, _) => Constant::Binary(Lrc::clone(s)),
|
||||
LitKind::Char(c) => Constant::Char(c),
|
||||
LitKind::Int(n, _) => Constant::Int(n),
|
||||
LitKind::Float(ref is, LitFloatType::Suffixed(fty)) => match fty {
|
||||
@ -228,27 +230,46 @@ pub fn lit_to_mir_constant(lit: &LitKind, ty: Option<Ty<'_>>) -> Constant {
|
||||
}
|
||||
}
|
||||
|
||||
/// The source of a constant value.
|
||||
pub enum ConstantSource {
|
||||
/// The value is determined solely from the expression.
|
||||
Local,
|
||||
/// The value is dependent on a defined constant.
|
||||
Constant,
|
||||
}
|
||||
impl ConstantSource {
|
||||
pub fn is_local(&self) -> bool {
|
||||
matches!(self, Self::Local)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to evaluate the expression as a constant.
|
||||
pub fn constant<'tcx>(
|
||||
lcx: &LateContext<'tcx>,
|
||||
typeck_results: &ty::TypeckResults<'tcx>,
|
||||
e: &Expr<'_>,
|
||||
) -> Option<(Constant, bool)> {
|
||||
let mut cx = ConstEvalLateContext {
|
||||
lcx,
|
||||
typeck_results,
|
||||
param_env: lcx.param_env,
|
||||
needed_resolution: false,
|
||||
substs: ty::List::empty(),
|
||||
};
|
||||
cx.expr(e).map(|cst| (cst, cx.needed_resolution))
|
||||
) -> Option<Constant> {
|
||||
ConstEvalLateContext::new(lcx, typeck_results).expr(e)
|
||||
}
|
||||
|
||||
/// Attempts to evaluate the expression as a constant.
|
||||
pub fn constant_with_source<'tcx>(
|
||||
lcx: &LateContext<'tcx>,
|
||||
typeck_results: &ty::TypeckResults<'tcx>,
|
||||
e: &Expr<'_>,
|
||||
) -> Option<(Constant, ConstantSource)> {
|
||||
let mut ctxt = ConstEvalLateContext::new(lcx, typeck_results);
|
||||
let res = ctxt.expr(e);
|
||||
res.map(|x| (x, ctxt.source))
|
||||
}
|
||||
|
||||
/// Attempts to evaluate an expression only if it's value is not dependent on other items.
|
||||
pub fn constant_simple<'tcx>(
|
||||
lcx: &LateContext<'tcx>,
|
||||
typeck_results: &ty::TypeckResults<'tcx>,
|
||||
e: &Expr<'_>,
|
||||
) -> Option<Constant> {
|
||||
constant(lcx, typeck_results, e).and_then(|(cst, res)| if res { None } else { Some(cst) })
|
||||
constant_with_source(lcx, typeck_results, e).and_then(|(c, s)| s.is_local().then_some(c))
|
||||
}
|
||||
|
||||
pub fn constant_full_int<'tcx>(
|
||||
@ -297,29 +318,25 @@ impl Ord for FullInt {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `ConstEvalLateContext` from the given `LateContext` and `TypeckResults`.
|
||||
pub fn constant_context<'a, 'tcx>(
|
||||
lcx: &'a LateContext<'tcx>,
|
||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
) -> ConstEvalLateContext<'a, 'tcx> {
|
||||
ConstEvalLateContext {
|
||||
lcx,
|
||||
typeck_results,
|
||||
param_env: lcx.param_env,
|
||||
needed_resolution: false,
|
||||
substs: ty::List::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstEvalLateContext<'a, 'tcx> {
|
||||
lcx: &'a LateContext<'tcx>,
|
||||
typeck_results: &'a ty::TypeckResults<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
needed_resolution: bool,
|
||||
source: ConstantSource,
|
||||
substs: SubstsRef<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
|
||||
fn new(lcx: &'a LateContext<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>) -> Self {
|
||||
Self {
|
||||
lcx,
|
||||
typeck_results,
|
||||
param_env: lcx.param_env,
|
||||
source: ConstantSource::Local,
|
||||
substs: List::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple constant folding: Insert an expression, get a constant or none.
|
||||
pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant> {
|
||||
match e.kind {
|
||||
@ -454,11 +471,9 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
|
||||
.const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, substs), None)
|
||||
.ok()
|
||||
.map(|val| rustc_middle::mir::ConstantKind::from_value(val, ty))?;
|
||||
let result = miri_to_const(self.lcx.tcx, result);
|
||||
if result.is_some() {
|
||||
self.needed_resolution = true;
|
||||
}
|
||||
result
|
||||
let result = miri_to_const(self.lcx.tcx, result)?;
|
||||
self.source = ConstantSource::Constant;
|
||||
Some(result)
|
||||
},
|
||||
// FIXME: cover all usable cases.
|
||||
_ => None,
|
||||
@ -492,8 +507,33 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
|
||||
|
||||
/// A block can only yield a constant if it only has one constant expression.
|
||||
fn block(&mut self, block: &Block<'_>) -> Option<Constant> {
|
||||
if block.stmts.is_empty() {
|
||||
block.expr.as_ref().and_then(|b| self.expr(b))
|
||||
if block.stmts.is_empty()
|
||||
&& let Some(expr) = block.expr
|
||||
{
|
||||
// Try to detect any `cfg`ed statements or empty macro expansions.
|
||||
let span = block.span.data();
|
||||
if span.ctxt == SyntaxContext::root() {
|
||||
if let Some(expr_span) = walk_span_to_context(expr.span, span.ctxt)
|
||||
&& let expr_lo = expr_span.lo()
|
||||
&& expr_lo >= span.lo
|
||||
&& let Some(src) = get_source_text(self.lcx, span.lo..expr_lo)
|
||||
&& let Some(src) = src.as_str()
|
||||
{
|
||||
use rustc_lexer::TokenKind::{Whitespace, LineComment, BlockComment, Semi, OpenBrace};
|
||||
if !tokenize(src)
|
||||
.map(|t| t.kind)
|
||||
.filter(|t| !matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi))
|
||||
.eq([OpenBrace])
|
||||
{
|
||||
self.source = ConstantSource::Constant;
|
||||
}
|
||||
} else {
|
||||
// Unable to access the source. Assume a non-local dependency.
|
||||
self.source = ConstantSource::Constant;
|
||||
}
|
||||
}
|
||||
|
||||
self.expr(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::consts::constant_simple;
|
||||
use crate::macros::macro_backtrace;
|
||||
use crate::source::snippet_opt;
|
||||
use crate::source::{get_source_text, snippet_opt, walk_span_to_context, SpanRange};
|
||||
use crate::tokenize_with_text;
|
||||
use rustc_ast::ast::InlineAsmTemplatePiece;
|
||||
use rustc_data_structures::fx::FxHasher;
|
||||
use rustc_hir::def::Res;
|
||||
@ -13,8 +14,9 @@ use rustc_hir::{
|
||||
use rustc_lexer::{tokenize, TokenKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::TypeckResults;
|
||||
use rustc_span::{sym, Symbol};
|
||||
use rustc_span::{sym, BytePos, ExpnKind, MacroKind, Symbol, SyntaxContext};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Range;
|
||||
|
||||
/// Callback that is called when two expressions are not equal in the sense of `SpanlessEq`, but
|
||||
/// other conditions would make them equal.
|
||||
@ -65,6 +67,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
pub fn inter_expr(&mut self) -> HirEqInterExpr<'_, 'a, 'tcx> {
|
||||
HirEqInterExpr {
|
||||
inner: self,
|
||||
left_ctxt: SyntaxContext::root(),
|
||||
right_ctxt: SyntaxContext::root(),
|
||||
locals: HirIdMap::default(),
|
||||
}
|
||||
}
|
||||
@ -92,6 +96,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
|
||||
|
||||
pub struct HirEqInterExpr<'a, 'b, 'tcx> {
|
||||
inner: &'a mut SpanlessEq<'b, 'tcx>,
|
||||
left_ctxt: SyntaxContext,
|
||||
right_ctxt: SyntaxContext,
|
||||
|
||||
// When binding are declared, the binding ID in the left expression is mapped to the one on the
|
||||
// right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`,
|
||||
@ -126,52 +132,88 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
}
|
||||
|
||||
/// Checks whether two blocks are the same.
|
||||
#[expect(clippy::similar_names)]
|
||||
fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool {
|
||||
match (left.stmts, left.expr, right.stmts, right.expr) {
|
||||
([], None, [], None) => {
|
||||
// For empty blocks, check to see if the tokens are equal. This will catch the case where a macro
|
||||
// expanded to nothing, or the cfg attribute was used.
|
||||
let (Some(left), Some(right)) = (
|
||||
snippet_opt(self.inner.cx, left.span),
|
||||
snippet_opt(self.inner.cx, right.span),
|
||||
) else { return true };
|
||||
let mut left_pos = 0;
|
||||
let left = tokenize(&left)
|
||||
.map(|t| {
|
||||
let end = left_pos + t.len as usize;
|
||||
let s = &left[left_pos..end];
|
||||
left_pos = end;
|
||||
(t, s)
|
||||
})
|
||||
.filter(|(t, _)| {
|
||||
!matches!(
|
||||
t.kind,
|
||||
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
|
||||
)
|
||||
})
|
||||
.map(|(_, s)| s);
|
||||
let mut right_pos = 0;
|
||||
let right = tokenize(&right)
|
||||
.map(|t| {
|
||||
let end = right_pos + t.len as usize;
|
||||
let s = &right[right_pos..end];
|
||||
right_pos = end;
|
||||
(t, s)
|
||||
})
|
||||
.filter(|(t, _)| {
|
||||
!matches!(
|
||||
t.kind,
|
||||
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
|
||||
)
|
||||
})
|
||||
.map(|(_, s)| s);
|
||||
left.eq(right)
|
||||
},
|
||||
_ => {
|
||||
over(left.stmts, right.stmts, |l, r| self.eq_stmt(l, r))
|
||||
&& both(&left.expr, &right.expr, |l, r| self.eq_expr(l, r))
|
||||
},
|
||||
use TokenKind::{BlockComment, LineComment, Semi, Whitespace};
|
||||
if left.stmts.len() != right.stmts.len() {
|
||||
return false;
|
||||
}
|
||||
let lspan = left.span.data();
|
||||
let rspan = right.span.data();
|
||||
if lspan.ctxt != SyntaxContext::root() && rspan.ctxt != SyntaxContext::root() {
|
||||
// Don't try to check in between statements inside macros.
|
||||
return over(left.stmts, right.stmts, |left, right| self.eq_stmt(left, right))
|
||||
&& both(&left.expr, &right.expr, |left, right| self.eq_expr(left, right));
|
||||
}
|
||||
if lspan.ctxt != rspan.ctxt {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut lstart = lspan.lo;
|
||||
let mut rstart = rspan.lo;
|
||||
|
||||
for (left, right) in left.stmts.iter().zip(right.stmts) {
|
||||
if !self.eq_stmt(left, right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to detect any `cfg`ed statements or empty macro expansions.
|
||||
let Some(lstmt_span) = walk_span_to_context(left.span, lspan.ctxt) else {
|
||||
return false;
|
||||
};
|
||||
let Some(rstmt_span) = walk_span_to_context(right.span, rspan.ctxt) else {
|
||||
return false;
|
||||
};
|
||||
let lstmt_span = lstmt_span.data();
|
||||
let rstmt_span = rstmt_span.data();
|
||||
|
||||
if lstmt_span.lo < lstart && rstmt_span.lo < rstart {
|
||||
// Can happen when macros expand to multiple statements, or rearrange statements.
|
||||
// Nothing in between the statements to check in this case.
|
||||
continue;
|
||||
}
|
||||
if lstmt_span.lo < lstart || rstmt_span.lo < rstart {
|
||||
// Only one of the blocks had a weird macro.
|
||||
return false;
|
||||
}
|
||||
if !eq_span_tokens(self.inner.cx, lstart..lstmt_span.lo, rstart..rstmt_span.lo, |t| {
|
||||
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lstart = lstmt_span.hi;
|
||||
rstart = rstmt_span.hi;
|
||||
}
|
||||
|
||||
let (lend, rend) = match (left.expr, right.expr) {
|
||||
(Some(left), Some(right)) => {
|
||||
if !self.eq_expr(left, right) {
|
||||
return false;
|
||||
}
|
||||
let Some(lexpr_span) = walk_span_to_context(left.span, lspan.ctxt) else {
|
||||
return false;
|
||||
};
|
||||
let Some(rexpr_span) = walk_span_to_context(right.span, rspan.ctxt) else {
|
||||
return false;
|
||||
};
|
||||
(lexpr_span.lo(), rexpr_span.lo())
|
||||
},
|
||||
(None, None) => (lspan.hi, rspan.hi),
|
||||
(Some(_), None) | (None, Some(_)) => return false,
|
||||
};
|
||||
|
||||
if lend < lstart && rend < rstart {
|
||||
// Can happen when macros rearrange the input.
|
||||
// Nothing in between the statements to check in this case.
|
||||
return true;
|
||||
} else if lend < lstart || rend < rstart {
|
||||
// Only one of the blocks had a weird macro
|
||||
return false;
|
||||
}
|
||||
eq_span_tokens(self.inner.cx, lstart..lend, rstart..rend, |t| {
|
||||
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. } | Semi)
|
||||
})
|
||||
}
|
||||
|
||||
fn should_ignore(&mut self, expr: &Expr<'_>) -> bool {
|
||||
@ -207,7 +249,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() {
|
||||
if !self.check_ctxt(left.span.ctxt(), right.span.ctxt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -440,6 +482,45 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||
fn eq_type_binding(&mut self, left: &TypeBinding<'_>, right: &TypeBinding<'_>) -> bool {
|
||||
left.ident.name == right.ident.name && self.eq_ty(left.ty(), right.ty())
|
||||
}
|
||||
|
||||
fn check_ctxt(&mut self, left: SyntaxContext, right: SyntaxContext) -> bool {
|
||||
if self.left_ctxt == left && self.right_ctxt == right {
|
||||
return true;
|
||||
} else if self.left_ctxt == left || self.right_ctxt == right {
|
||||
// Only one context has changed. This can only happen if the two nodes are written differently.
|
||||
return false;
|
||||
} else if left != SyntaxContext::root() {
|
||||
let mut left_data = left.outer_expn_data();
|
||||
let mut right_data = right.outer_expn_data();
|
||||
loop {
|
||||
use TokenKind::{BlockComment, LineComment, Whitespace};
|
||||
if left_data.macro_def_id != right_data.macro_def_id
|
||||
|| (matches!(left_data.kind, ExpnKind::Macro(MacroKind::Bang, name) if name == sym::cfg)
|
||||
&& !eq_span_tokens(self.inner.cx, left_data.call_site, right_data.call_site, |t| {
|
||||
!matches!(t, Whitespace | LineComment { .. } | BlockComment { .. })
|
||||
}))
|
||||
{
|
||||
// Either a different chain of macro calls, or different arguments to the `cfg` macro.
|
||||
return false;
|
||||
}
|
||||
let left_ctxt = left_data.call_site.ctxt();
|
||||
let right_ctxt = right_data.call_site.ctxt();
|
||||
if left_ctxt == SyntaxContext::root() && right_ctxt == SyntaxContext::root() {
|
||||
break;
|
||||
}
|
||||
if left_ctxt == SyntaxContext::root() || right_ctxt == SyntaxContext::root() {
|
||||
// Different lengths for the expansion stack. This can only happen if nodes are written differently,
|
||||
// or shouldn't be compared to start with.
|
||||
return false;
|
||||
}
|
||||
left_data = left_ctxt.outer_expn_data();
|
||||
right_data = right_ctxt.outer_expn_data();
|
||||
}
|
||||
}
|
||||
self.left_ctxt = left;
|
||||
self.right_ctxt = right;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Some simple reductions like `{ return }` => `return`
|
||||
@ -1038,3 +1119,34 @@ pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 {
|
||||
h.hash_expr(e);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
#[expect(clippy::similar_names)]
|
||||
fn eq_span_tokens(
|
||||
cx: &LateContext<'_>,
|
||||
left: impl SpanRange,
|
||||
right: impl SpanRange,
|
||||
pred: impl Fn(TokenKind) -> bool,
|
||||
) -> bool {
|
||||
fn f(cx: &LateContext<'_>, left: Range<BytePos>, right: Range<BytePos>, pred: impl Fn(TokenKind) -> bool) -> bool {
|
||||
if let Some(lsrc) = get_source_text(cx, left)
|
||||
&& let Some(lsrc) = lsrc.as_str()
|
||||
&& let Some(rsrc) = get_source_text(cx, right)
|
||||
&& let Some(rsrc) = rsrc.as_str()
|
||||
{
|
||||
let pred = |t: &(_, _)| pred(t.0);
|
||||
let map = |(_, x)| x;
|
||||
|
||||
let ltok = tokenize_with_text(lsrc)
|
||||
.filter(pred)
|
||||
.map(map);
|
||||
let rtok = tokenize_with_text(rsrc)
|
||||
.filter(pred)
|
||||
.map(map);
|
||||
ltok.eq(rtok)
|
||||
} else {
|
||||
// Unable to access the source. Conservatively assume the blocks aren't equal.
|
||||
false
|
||||
}
|
||||
}
|
||||
f(cx, left.into_range(), right.into_range(), pred)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#![feature(array_chunks)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(never_type)]
|
||||
@ -76,6 +77,7 @@ use std::sync::OnceLock;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
use if_chain::if_chain;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::ast::{self, LitKind, RangeLimits};
|
||||
use rustc_ast::Attribute;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
@ -282,6 +284,15 @@ pub fn is_wild(pat: &Pat<'_>) -> bool {
|
||||
matches!(pat.kind, PatKind::Wild)
|
||||
}
|
||||
|
||||
/// Checks if the given `QPath` belongs to a type alias.
|
||||
pub fn is_ty_alias(qpath: &QPath<'_>) -> bool {
|
||||
match *qpath {
|
||||
QPath::Resolved(_, path) => matches!(path.res, Res::Def(DefKind::TyAlias, ..)),
|
||||
QPath::TypeRelative(ty, _) if let TyKind::Path(qpath) = ty.kind => { is_ty_alias(&qpath) },
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the method call given in `expr` belongs to the given trait.
|
||||
/// This is a deprecated function, consider using [`is_trait_method`].
|
||||
pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
|
||||
@ -1488,7 +1499,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti
|
||||
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree()))
|
||||
&& let min_const_kind = ConstantKind::from_value(const_val, bnd_ty)
|
||||
&& let Some(min_const) = miri_to_const(cx.tcx, min_const_kind)
|
||||
&& let Some((start_const, _)) = constant(cx, cx.typeck_results(), start)
|
||||
&& let Some(start_const) = constant(cx, cx.typeck_results(), start)
|
||||
{
|
||||
start_const == min_const
|
||||
} else {
|
||||
@ -1504,7 +1515,7 @@ pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Opti
|
||||
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree()))
|
||||
&& let max_const_kind = ConstantKind::from_value(const_val, bnd_ty)
|
||||
&& let Some(max_const) = miri_to_const(cx.tcx, max_const_kind)
|
||||
&& let Some((end_const, _)) = constant(cx, cx.typeck_results(), end)
|
||||
&& let Some(end_const) = constant(cx, cx.typeck_results(), end)
|
||||
{
|
||||
end_const == max_const
|
||||
} else {
|
||||
@ -1536,7 +1547,7 @@ pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool
|
||||
return true;
|
||||
}
|
||||
let enclosing_body = cx.tcx.hir().enclosing_body_owner(e.hir_id);
|
||||
if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
|
||||
if let Some(Constant::Int(v)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
|
||||
return value == v;
|
||||
}
|
||||
false
|
||||
@ -2480,6 +2491,17 @@ pub fn walk_to_expr_usage<'tcx, T>(
|
||||
None
|
||||
}
|
||||
|
||||
/// Tokenizes the input while keeping the text associated with each token.
|
||||
pub fn tokenize_with_text(s: &str) -> impl Iterator<Item = (TokenKind, &str)> {
|
||||
let mut pos = 0;
|
||||
tokenize(s).map(move |t| {
|
||||
let end = pos + t.len;
|
||||
let range = pos as usize..end as usize;
|
||||
pos = end;
|
||||
(t.kind, s.get(range).unwrap_or_default())
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether a given span has any comment token
|
||||
/// This checks for all types of comment: line "//", block "/**", doc "///" "//!"
|
||||
pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
|
||||
@ -2496,23 +2518,11 @@ pub fn span_contains_comment(sm: &SourceMap, span: Span) -> bool {
|
||||
/// Comments are returned wrapped with their relevant delimiters
|
||||
pub fn span_extract_comment(sm: &SourceMap, span: Span) -> String {
|
||||
let snippet = sm.span_to_snippet(span).unwrap_or_default();
|
||||
let mut comments_buf: Vec<String> = Vec::new();
|
||||
let mut index: usize = 0;
|
||||
|
||||
for token in tokenize(&snippet) {
|
||||
let token_range = index..(index + token.len as usize);
|
||||
index += token.len as usize;
|
||||
match token.kind {
|
||||
TokenKind::BlockComment { .. } | TokenKind::LineComment { .. } => {
|
||||
if let Some(comment) = snippet.get(token_range) {
|
||||
comments_buf.push(comment.to_string());
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
comments_buf.join("\n")
|
||||
let res = tokenize_with_text(&snippet)
|
||||
.filter(|(t, _)| matches!(t, TokenKind::BlockComment { .. } | TokenKind::LineComment { .. }))
|
||||
.map(|(_, s)| s)
|
||||
.join("\n");
|
||||
res
|
||||
}
|
||||
|
||||
pub fn span_find_starting_semi(sm: &SourceMap, span: Span) -> Span {
|
||||
|
@ -2,14 +2,64 @@
|
||||
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::hygiene;
|
||||
use rustc_span::source_map::{original_sp, SourceMap};
|
||||
use rustc_span::{hygiene, SourceFile};
|
||||
use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Range;
|
||||
|
||||
/// A type which can be converted to the range portion of a `Span`.
|
||||
pub trait SpanRange {
|
||||
fn into_range(self) -> Range<BytePos>;
|
||||
}
|
||||
impl SpanRange for Span {
|
||||
fn into_range(self) -> Range<BytePos> {
|
||||
let data = self.data();
|
||||
data.lo..data.hi
|
||||
}
|
||||
}
|
||||
impl SpanRange for SpanData {
|
||||
fn into_range(self) -> Range<BytePos> {
|
||||
self.lo..self.hi
|
||||
}
|
||||
}
|
||||
impl SpanRange for Range<BytePos> {
|
||||
fn into_range(self) -> Range<BytePos> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SourceFileRange {
|
||||
pub sf: Lrc<SourceFile>,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
impl SourceFileRange {
|
||||
/// Attempts to get the text from the source file. This can fail if the source text isn't
|
||||
/// loaded.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
self.sf.src.as_ref().and_then(|x| x.get(self.range.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the source file, and range in the file, of the given span. Returns `None` if the span
|
||||
/// extends through multiple files, or is malformed.
|
||||
pub fn get_source_text(cx: &impl LintContext, sp: impl SpanRange) -> Option<SourceFileRange> {
|
||||
fn f(sm: &SourceMap, sp: Range<BytePos>) -> Option<SourceFileRange> {
|
||||
let start = sm.lookup_byte_offset(sp.start);
|
||||
let end = sm.lookup_byte_offset(sp.end);
|
||||
if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos {
|
||||
return None;
|
||||
}
|
||||
let range = start.pos.to_usize()..end.pos.to_usize();
|
||||
Some(SourceFileRange { sf: start.sf, range })
|
||||
}
|
||||
f(cx.sess().source_map(), sp.into_range())
|
||||
}
|
||||
|
||||
/// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
|
||||
pub fn expr_block<T: LintContext>(
|
||||
|
@ -3,6 +3,7 @@
|
||||
//@normalize-stderr-test: "produce_ice.rs:\d*:\d*" -> "produce_ice.rs"
|
||||
//@normalize-stderr-test: "', .*clippy_lints" -> "', clippy_lints"
|
||||
//@normalize-stderr-test: "'rustc'" -> "'<unnamed>'"
|
||||
//@normalize-stderr-test: "running on .*" -> "running on <target>"
|
||||
//@normalize-stderr-test: "(?ms)query stack during panic:\n.*end of query stack\n" -> ""
|
||||
|
||||
#![deny(clippy::internal)]
|
||||
|
@ -1,12 +1,14 @@
|
||||
thread '<unnamed>' panicked at 'Would you like some help with that?', clippy_lints/src/utils/internal_lints/produce_ice.rs
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
error: internal compiler error: unexpected panic
|
||||
|
||||
note: the compiler unexpectedly panicked. this is a bug.
|
||||
error: the compiler unexpectedly panicked. this is a bug.
|
||||
|
||||
note: we would appreciate a bug report: https://github.com/rust-lang/rust-clippy/issues/new
|
||||
|
||||
note: rustc 1.71.0-nightly (521f4dae1 2023-05-19) running on <target>
|
||||
|
||||
note: compiler flags: -C prefer-dynamic -Z ui-testing
|
||||
|
||||
note: Clippy version: foo
|
||||
|
||||
thread panicked while panicking. aborting.
|
||||
|
@ -458,4 +458,12 @@ pub fn issue_10583(a: u16) -> u16 {
|
||||
10 / a
|
||||
}
|
||||
|
||||
pub fn issue_10767() {
|
||||
let n = &1.0;
|
||||
n + n;
|
||||
3.1_f32 + &1.2_f32;
|
||||
&3.4_f32 + 1.5_f32;
|
||||
&3.5_f32 + &1.3_f32;
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
@ -1,7 +1,11 @@
|
||||
//@run-rustfix
|
||||
//@aux-build: proc_macros.rs
|
||||
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
extern crate proc_macros;
|
||||
use proc_macros::with_span;
|
||||
|
||||
fn main() {}
|
||||
|
||||
mod should_lint {
|
||||
@ -47,6 +51,17 @@ mod should_not_lint2 {
|
||||
}
|
||||
}
|
||||
|
||||
with_span!(
|
||||
span
|
||||
|
||||
fn just_returning(x: &u32) -> &u32 {
|
||||
x
|
||||
}
|
||||
|
||||
fn dont_lint_proc_macro() {
|
||||
let a = &mut &*just_returning(&12);
|
||||
}
|
||||
);
|
||||
// this mod explains why we should not lint `& &* (&T)`
|
||||
mod false_negative {
|
||||
fn foo() {
|
||||
|
@ -1,7 +1,11 @@
|
||||
//@run-rustfix
|
||||
//@aux-build: proc_macros.rs
|
||||
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
extern crate proc_macros;
|
||||
use proc_macros::with_span;
|
||||
|
||||
fn main() {}
|
||||
|
||||
mod should_lint {
|
||||
@ -47,6 +51,17 @@ mod should_not_lint2 {
|
||||
}
|
||||
}
|
||||
|
||||
with_span!(
|
||||
span
|
||||
|
||||
fn just_returning(x: &u32) -> &u32 {
|
||||
x
|
||||
}
|
||||
|
||||
fn dont_lint_proc_macro() {
|
||||
let a = &mut &*just_returning(&12);
|
||||
}
|
||||
);
|
||||
// this mod explains why we should not lint `& &* (&T)`
|
||||
mod false_negative {
|
||||
fn foo() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: deref on an immutable reference
|
||||
--> $DIR/borrow_deref_ref.rs:10:17
|
||||
--> $DIR/borrow_deref_ref.rs:14:17
|
||||
|
|
||||
LL | let b = &*a;
|
||||
| ^^^ help: if you would like to reborrow, try removing `&*`: `a`
|
||||
@ -7,13 +7,13 @@ LL | let b = &*a;
|
||||
= note: `-D clippy::borrow-deref-ref` implied by `-D warnings`
|
||||
|
||||
error: deref on an immutable reference
|
||||
--> $DIR/borrow_deref_ref.rs:12:22
|
||||
--> $DIR/borrow_deref_ref.rs:16:22
|
||||
|
|
||||
LL | let b = &mut &*bar(&12);
|
||||
| ^^^^^^^^^^ help: if you would like to reborrow, try removing `&*`: `bar(&12)`
|
||||
|
||||
error: deref on an immutable reference
|
||||
--> $DIR/borrow_deref_ref.rs:55:23
|
||||
--> $DIR/borrow_deref_ref.rs:70:23
|
||||
|
|
||||
LL | let addr_y = &&*x as *const _ as usize; // assert ok
|
||||
| ^^^ help: if you would like to reborrow, try removing `&*`: `x`
|
||||
|
@ -35,6 +35,13 @@ fn main() {
|
||||
let _more = ret_ty_fn();
|
||||
call_ty_fn(Box::default());
|
||||
issue_10381();
|
||||
|
||||
// `Box::<Option<_>>::default()` would be valid here, but not `Box::default()` or
|
||||
// `Box::<Option<[closure@...]>::default()`
|
||||
//
|
||||
// Would have a suggestion after https://github.com/rust-lang/rust/blob/fdd030127cc68afec44a8d3f6341525dd34e50ae/compiler/rustc_middle/src/ty/diagnostics.rs#L554-L563
|
||||
let mut unnameable = Box::new(Option::default());
|
||||
let _ = unnameable.insert(|| {});
|
||||
}
|
||||
|
||||
fn ret_ty_fn() -> Box<bool> {
|
||||
|
@ -35,6 +35,13 @@ fn main() {
|
||||
let _more = ret_ty_fn();
|
||||
call_ty_fn(Box::new(u8::default()));
|
||||
issue_10381();
|
||||
|
||||
// `Box::<Option<_>>::default()` would be valid here, but not `Box::default()` or
|
||||
// `Box::<Option<[closure@...]>::default()`
|
||||
//
|
||||
// Would have a suggestion after https://github.com/rust-lang/rust/blob/fdd030127cc68afec44a8d3f6341525dd34e50ae/compiler/rustc_middle/src/ty/diagnostics.rs#L554-L563
|
||||
let mut unnameable = Box::new(Option::default());
|
||||
let _ = unnameable.insert(|| {});
|
||||
}
|
||||
|
||||
fn ret_ty_fn() -> Box<bool> {
|
||||
|
@ -73,25 +73,25 @@ LL | call_ty_fn(Box::new(u8::default()));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::default()`
|
||||
|
||||
error: `Box::new(_)` of default value
|
||||
--> $DIR/box_default.rs:41:5
|
||||
--> $DIR/box_default.rs:48:5
|
||||
|
|
||||
LL | Box::new(bool::default())
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<bool>::default()`
|
||||
|
||||
error: `Box::new(_)` of default value
|
||||
--> $DIR/box_default.rs:58:28
|
||||
--> $DIR/box_default.rs:65:28
|
||||
|
|
||||
LL | let _: Box<dyn Read> = Box::new(ImplementsDefault::default());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<ImplementsDefault>::default()`
|
||||
|
||||
error: `Box::new(_)` of default value
|
||||
--> $DIR/box_default.rs:67:17
|
||||
--> $DIR/box_default.rs:74:17
|
||||
|
|
||||
LL | let _ = Box::new(WeirdPathed::default());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<WeirdPathed>::default()`
|
||||
|
||||
error: `Box::new(_)` of default value
|
||||
--> $DIR/box_default.rs:79:18
|
||||
--> $DIR/box_default.rs:86:18
|
||||
|
|
||||
LL | Some(Box::new(Foo::default()))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Box::<Foo>::default()`
|
||||
|
@ -1,5 +1,10 @@
|
||||
//@run-rustfix
|
||||
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let)]
|
||||
#![allow(
|
||||
clippy::assertions_on_constants,
|
||||
clippy::equatable_if_let,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::eq_op
|
||||
)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[warn(clippy::collapsible_if)]
|
||||
|
@ -1,5 +1,10 @@
|
||||
//@run-rustfix
|
||||
#![allow(clippy::assertions_on_constants, clippy::equatable_if_let)]
|
||||
#![allow(
|
||||
clippy::assertions_on_constants,
|
||||
clippy::equatable_if_let,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::eq_op
|
||||
)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[warn(clippy::collapsible_if)]
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:9:5
|
||||
--> $DIR/collapsible_if.rs:14:5
|
||||
|
|
||||
LL | / if x == "hello" {
|
||||
LL | | if y == "world" {
|
||||
@ -17,7 +17,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:15:5
|
||||
--> $DIR/collapsible_if.rs:20:5
|
||||
|
|
||||
LL | / if x == "hello" || x == "world" {
|
||||
LL | | if y == "world" || y == "hello" {
|
||||
@ -34,7 +34,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:21:5
|
||||
--> $DIR/collapsible_if.rs:26:5
|
||||
|
|
||||
LL | / if x == "hello" && x == "world" {
|
||||
LL | | if y == "world" || y == "hello" {
|
||||
@ -51,7 +51,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:27:5
|
||||
--> $DIR/collapsible_if.rs:32:5
|
||||
|
|
||||
LL | / if x == "hello" || x == "world" {
|
||||
LL | | if y == "world" && y == "hello" {
|
||||
@ -68,7 +68,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:33:5
|
||||
--> $DIR/collapsible_if.rs:38:5
|
||||
|
|
||||
LL | / if x == "hello" && x == "world" {
|
||||
LL | | if y == "world" && y == "hello" {
|
||||
@ -85,7 +85,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:39:5
|
||||
--> $DIR/collapsible_if.rs:44:5
|
||||
|
|
||||
LL | / if 42 == 1337 {
|
||||
LL | | if 'a' != 'A' {
|
||||
@ -102,7 +102,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:95:5
|
||||
--> $DIR/collapsible_if.rs:100:5
|
||||
|
|
||||
LL | / if x == "hello" {
|
||||
LL | | if y == "world" { // Collapsible
|
||||
@ -119,7 +119,7 @@ LL + }
|
||||
|
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:154:5
|
||||
--> $DIR/collapsible_if.rs:159:5
|
||||
|
|
||||
LL | / if matches!(true, true) {
|
||||
LL | | if matches!(true, true) {}
|
||||
@ -127,7 +127,7 @@ LL | | }
|
||||
| |_____^ help: collapse nested if block: `if matches!(true, true) && matches!(true, true) {}`
|
||||
|
||||
error: this `if` statement can be collapsed
|
||||
--> $DIR/collapsible_if.rs:159:5
|
||||
--> $DIR/collapsible_if.rs:164:5
|
||||
|
|
||||
LL | / if matches!(true, true) && truth() {
|
||||
LL | | if matches!(true, true) {}
|
||||
|
@ -4,6 +4,7 @@
|
||||
fn foo(n: u32) -> u32 {
|
||||
if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
|
||||
}
|
||||
fn bar(_: ()) {}
|
||||
|
||||
fn factorial(n: u32) -> u32 {
|
||||
if dbg!(n <= 1) {
|
||||
@ -21,6 +22,32 @@ fn main() {
|
||||
dbg!(1, 2, 3, 4, 5);
|
||||
}
|
||||
|
||||
fn issue9914() {
|
||||
macro_rules! foo {
|
||||
($x:expr) => {
|
||||
$x;
|
||||
};
|
||||
}
|
||||
macro_rules! foo2 {
|
||||
($x:expr) => {
|
||||
$x;
|
||||
};
|
||||
}
|
||||
macro_rules! expand_to_dbg {
|
||||
() => {
|
||||
dbg!();
|
||||
};
|
||||
}
|
||||
|
||||
dbg!();
|
||||
#[allow(clippy::let_unit_value)]
|
||||
let _ = dbg!();
|
||||
bar(dbg!());
|
||||
foo!(dbg!());
|
||||
foo2!(foo!(dbg!()));
|
||||
expand_to_dbg!();
|
||||
}
|
||||
|
||||
mod issue7274 {
|
||||
trait Thing<'b> {
|
||||
fn foo(&self);
|
||||
|
@ -11,7 +11,7 @@ LL | if let Some(n) = n.checked_sub(4) { n } else { n }
|
||||
| ~~~~~~~~~~~~~~~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:9:8
|
||||
--> $DIR/dbg_macro.rs:10:8
|
||||
|
|
||||
LL | if dbg!(n <= 1) {
|
||||
| ^^^^^^^^^^^^
|
||||
@ -22,7 +22,7 @@ LL | if n <= 1 {
|
||||
| ~~~~~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:10:9
|
||||
--> $DIR/dbg_macro.rs:11:9
|
||||
|
|
||||
LL | dbg!(1)
|
||||
| ^^^^^^^
|
||||
@ -33,7 +33,7 @@ LL | 1
|
||||
|
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:12:9
|
||||
--> $DIR/dbg_macro.rs:13:9
|
||||
|
|
||||
LL | dbg!(n * factorial(n - 1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -44,7 +44,7 @@ LL | n * factorial(n - 1)
|
||||
|
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:17:5
|
||||
--> $DIR/dbg_macro.rs:18:5
|
||||
|
|
||||
LL | dbg!(42);
|
||||
| ^^^^^^^^
|
||||
@ -55,7 +55,7 @@ LL | 42;
|
||||
| ~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:18:5
|
||||
--> $DIR/dbg_macro.rs:19:5
|
||||
|
|
||||
LL | dbg!(dbg!(dbg!(42)));
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
@ -66,7 +66,7 @@ LL | dbg!(dbg!(42));
|
||||
| ~~~~~~~~~~~~~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:19:14
|
||||
--> $DIR/dbg_macro.rs:20:14
|
||||
|
|
||||
LL | foo(3) + dbg!(factorial(4));
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
@ -77,7 +77,7 @@ LL | foo(3) + factorial(4);
|
||||
| ~~~~~~~~~~~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:20:5
|
||||
--> $DIR/dbg_macro.rs:21:5
|
||||
|
|
||||
LL | dbg!(1, 2, dbg!(3, 4));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -88,7 +88,7 @@ LL | (1, 2, dbg!(3, 4));
|
||||
| ~~~~~~~~~~~~~~~~~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:21:5
|
||||
--> $DIR/dbg_macro.rs:22:5
|
||||
|
|
||||
LL | dbg!(1, 2, 3, 4, 5);
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
@ -99,7 +99,63 @@ LL | (1, 2, 3, 4, 5);
|
||||
| ~~~~~~~~~~~~~~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:41:9
|
||||
--> $DIR/dbg_macro.rs:42:5
|
||||
|
|
||||
LL | dbg!();
|
||||
| ^^^^^^^
|
||||
|
|
||||
help: remove the invocation before committing it to a version control system
|
||||
|
|
||||
LL - dbg!();
|
||||
LL +
|
||||
|
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:44:13
|
||||
|
|
||||
LL | let _ = dbg!();
|
||||
| ^^^^^^
|
||||
|
|
||||
help: remove the invocation before committing it to a version control system
|
||||
|
|
||||
LL | let _ = ();
|
||||
| ~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:45:9
|
||||
|
|
||||
LL | bar(dbg!());
|
||||
| ^^^^^^
|
||||
|
|
||||
help: remove the invocation before committing it to a version control system
|
||||
|
|
||||
LL | bar(());
|
||||
| ~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:46:10
|
||||
|
|
||||
LL | foo!(dbg!());
|
||||
| ^^^^^^
|
||||
|
|
||||
help: remove the invocation before committing it to a version control system
|
||||
|
|
||||
LL | foo!(());
|
||||
| ~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:47:16
|
||||
|
|
||||
LL | foo2!(foo!(dbg!()));
|
||||
| ^^^^^^
|
||||
|
|
||||
help: remove the invocation before committing it to a version control system
|
||||
|
|
||||
LL | foo2!(foo!(()));
|
||||
| ~~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:68:9
|
||||
|
|
||||
LL | dbg!(2);
|
||||
| ^^^^^^^
|
||||
@ -110,7 +166,7 @@ LL | 2;
|
||||
| ~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:47:5
|
||||
--> $DIR/dbg_macro.rs:74:5
|
||||
|
|
||||
LL | dbg!(1);
|
||||
| ^^^^^^^
|
||||
@ -121,7 +177,7 @@ LL | 1;
|
||||
| ~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:52:5
|
||||
--> $DIR/dbg_macro.rs:79:5
|
||||
|
|
||||
LL | dbg!(1);
|
||||
| ^^^^^^^
|
||||
@ -132,7 +188,7 @@ LL | 1;
|
||||
| ~
|
||||
|
||||
error: the `dbg!` macro is intended as a debugging tool
|
||||
--> $DIR/dbg_macro.rs:58:9
|
||||
--> $DIR/dbg_macro.rs:85:9
|
||||
|
|
||||
LL | dbg!(1);
|
||||
| ^^^^^^^
|
||||
@ -142,5 +198,5 @@ help: remove the invocation before committing it to a version control system
|
||||
LL | 1;
|
||||
| ~
|
||||
|
||||
error: aborting due to 13 previous errors
|
||||
error: aborting due to 18 previous errors
|
||||
|
||||
|
@ -105,6 +105,7 @@ fn main() {
|
||||
// should lint
|
||||
let _ = PhantomData::<usize>;
|
||||
let _: PhantomData<i32> = PhantomData;
|
||||
let _: PhantomData<i32> = std::marker::PhantomData;
|
||||
let _ = UnitStruct;
|
||||
|
||||
// should not lint
|
||||
@ -116,4 +117,21 @@ fn main() {
|
||||
let _ = EmptyStruct::default();
|
||||
let _ = FakeDefault::default();
|
||||
let _ = <FakeDefault as Default>::default();
|
||||
|
||||
macro_rules! in_macro {
|
||||
($i:ident) => {{
|
||||
let _ = UnitStruct::default();
|
||||
let _ = $i::default();
|
||||
}};
|
||||
}
|
||||
|
||||
in_macro!(UnitStruct);
|
||||
|
||||
macro_rules! struct_from_macro {
|
||||
() => {
|
||||
UnitStruct
|
||||
};
|
||||
}
|
||||
|
||||
let _ = <struct_from_macro!()>::default();
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ fn main() {
|
||||
// should lint
|
||||
let _ = PhantomData::<usize>::default();
|
||||
let _: PhantomData<i32> = PhantomData::default();
|
||||
let _: PhantomData<i32> = std::marker::PhantomData::default();
|
||||
let _ = UnitStruct::default();
|
||||
|
||||
// should not lint
|
||||
@ -116,4 +117,21 @@ fn main() {
|
||||
let _ = EmptyStruct::default();
|
||||
let _ = FakeDefault::default();
|
||||
let _ = <FakeDefault as Default>::default();
|
||||
|
||||
macro_rules! in_macro {
|
||||
($i:ident) => {{
|
||||
let _ = UnitStruct::default();
|
||||
let _ = $i::default();
|
||||
}};
|
||||
}
|
||||
|
||||
in_macro!(UnitStruct);
|
||||
|
||||
macro_rules! struct_from_macro {
|
||||
() => {
|
||||
UnitStruct
|
||||
};
|
||||
}
|
||||
|
||||
let _ = <struct_from_macro!()>::default();
|
||||
}
|
||||
|
@ -25,10 +25,16 @@ LL | let _: PhantomData<i32> = PhantomData::default();
|
||||
| ^^^^^^^^^^^ help: remove this call to `default`
|
||||
|
||||
error: use of `default` to create a unit struct
|
||||
--> $DIR/default_constructed_unit_structs.rs:108:23
|
||||
--> $DIR/default_constructed_unit_structs.rs:108:55
|
||||
|
|
||||
LL | let _: PhantomData<i32> = std::marker::PhantomData::default();
|
||||
| ^^^^^^^^^^^ help: remove this call to `default`
|
||||
|
||||
error: use of `default` to create a unit struct
|
||||
--> $DIR/default_constructed_unit_structs.rs:109:23
|
||||
|
|
||||
LL | let _ = UnitStruct::default();
|
||||
| ^^^^^^^^^^^ help: remove this call to `default`
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
error: aborting due to 6 previous errors
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
#![warn(clippy::expl_impl_clone_on_copy)]
|
||||
|
||||
|
||||
#[derive(Copy)]
|
||||
struct Qux;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:8:1
|
||||
--> $DIR/derive.rs:7:1
|
||||
|
|
||||
LL | / impl Clone for Qux {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -9,7 +9,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:8:1
|
||||
--> $DIR/derive.rs:7:1
|
||||
|
|
||||
LL | / impl Clone for Qux {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -20,7 +20,7 @@ LL | | }
|
||||
= note: `-D clippy::expl-impl-clone-on-copy` implied by `-D warnings`
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:32:1
|
||||
--> $DIR/derive.rs:31:1
|
||||
|
|
||||
LL | / impl<'a> Clone for Lt<'a> {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -30,7 +30,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:32:1
|
||||
--> $DIR/derive.rs:31:1
|
||||
|
|
||||
LL | / impl<'a> Clone for Lt<'a> {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -40,7 +40,7 @@ LL | | }
|
||||
| |_^
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:43:1
|
||||
--> $DIR/derive.rs:42:1
|
||||
|
|
||||
LL | / impl Clone for BigArray {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -50,7 +50,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:43:1
|
||||
--> $DIR/derive.rs:42:1
|
||||
|
|
||||
LL | / impl Clone for BigArray {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -60,7 +60,7 @@ LL | | }
|
||||
| |_^
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:54:1
|
||||
--> $DIR/derive.rs:53:1
|
||||
|
|
||||
LL | / impl Clone for FnPtr {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -70,7 +70,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:54:1
|
||||
--> $DIR/derive.rs:53:1
|
||||
|
|
||||
LL | / impl Clone for FnPtr {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -80,7 +80,7 @@ LL | | }
|
||||
| |_^
|
||||
|
||||
error: you are implementing `Clone` explicitly on a `Copy` type
|
||||
--> $DIR/derive.rs:74:1
|
||||
--> $DIR/derive.rs:73:1
|
||||
|
|
||||
LL | / impl<T: Clone> Clone for Generic2<T> {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
@ -90,7 +90,7 @@ LL | | }
|
||||
| |_^
|
||||
|
|
||||
note: consider deriving `Clone` or removing `Copy`
|
||||
--> $DIR/derive.rs:74:1
|
||||
--> $DIR/derive.rs:73:1
|
||||
|
|
||||
LL | / impl<T: Clone> Clone for Generic2<T> {
|
||||
LL | | fn clone(&self) -> Self {
|
||||
|
132
tests/ui/empty_line_after_doc_comments.rs
Normal file
132
tests/ui/empty_line_after_doc_comments.rs
Normal file
@ -0,0 +1,132 @@
|
||||
//@aux-build:proc_macro_attr.rs
|
||||
#![warn(clippy::empty_line_after_doc_comments)]
|
||||
#![allow(clippy::assertions_on_constants)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
#![rustfmt::skip]
|
||||
|
||||
#[macro_use]
|
||||
extern crate proc_macro_attr;
|
||||
|
||||
mod some_mod {
|
||||
//! This doc comment should *NOT* produce a warning
|
||||
|
||||
mod some_inner_mod {
|
||||
fn some_noop() {}
|
||||
}
|
||||
}
|
||||
|
||||
/// This should produce a warning
|
||||
|
||||
fn with_doc_and_newline() { assert!(true)}
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
|
||||
/// some comment
|
||||
fn with_one_newline_and_comment() { assert!(true) }
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
/// some comment
|
||||
fn with_no_newline_and_comment() { assert!(true) }
|
||||
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
|
||||
fn with_one_newline() { assert!(true) }
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
|
||||
|
||||
fn with_two_newlines() { assert!(true) }
|
||||
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
|
||||
enum Baz {
|
||||
One,
|
||||
Two
|
||||
}
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
|
||||
struct Foo {
|
||||
one: isize,
|
||||
two: isize
|
||||
}
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
|
||||
mod foo {
|
||||
}
|
||||
|
||||
/// This doc comment should produce a warning
|
||||
|
||||
/** This is also a doc comment and should produce a warning
|
||||
*/
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(missing_docs)]
|
||||
#[allow(missing_docs)]
|
||||
fn three_attributes() { assert!(true) }
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[doc = "
|
||||
Returns the escaped value of the textual representation of
|
||||
|
||||
"]
|
||||
pub fn function() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FooFighter {
|
||||
Bar1,
|
||||
|
||||
Bar2,
|
||||
|
||||
Bar3,
|
||||
|
||||
Bar4
|
||||
}
|
||||
|
||||
// This should *NOT* produce a warning because the empty line is inside a block comment
|
||||
#[crate_type = "lib"]
|
||||
/*
|
||||
|
||||
*/
|
||||
pub struct S;
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
#[crate_type = "lib"]
|
||||
/* test */
|
||||
pub struct T;
|
||||
|
||||
// This should *NOT* produce a warning
|
||||
// See https://github.com/rust-lang/rust-clippy/issues/5567
|
||||
#[fake_async_trait]
|
||||
pub trait Bazz {
|
||||
fn foo() -> Vec<u8> {
|
||||
let _i = "";
|
||||
|
||||
|
||||
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[dummy(string = "first line
|
||||
|
||||
second line
|
||||
")]
|
||||
pub struct Args;
|
||||
|
||||
fn main() {}
|
36
tests/ui/empty_line_after_doc_comments.stderr
Normal file
36
tests/ui/empty_line_after_doc_comments.stderr
Normal file
@ -0,0 +1,36 @@
|
||||
error: found an empty line after a doc comment. Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?
|
||||
--> $DIR/empty_line_after_doc_comments.rs:18:1
|
||||
|
|
||||
LL | / /// This should produce a warning
|
||||
LL | |
|
||||
LL | | fn with_doc_and_newline() { assert!(true)}
|
||||
| |_
|
||||
|
|
||||
= note: `-D clippy::empty-line-after-doc-comments` implied by `-D warnings`
|
||||
|
||||
error: found an empty line after a doc comment. Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?
|
||||
--> $DIR/empty_line_after_doc_comments.rs:68:1
|
||||
|
|
||||
LL | / /// This doc comment should produce a warning
|
||||
LL | |
|
||||
LL | | /** This is also a doc comment and should produce a warning
|
||||
LL | | */
|
||||
... |
|
||||
LL | | #[allow(missing_docs)]
|
||||
LL | | fn three_attributes() { assert!(true) }
|
||||
| |_
|
||||
|
||||
error: found an empty line after a doc comment. Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?
|
||||
--> $DIR/empty_line_after_doc_comments.rs:70:1
|
||||
|
|
||||
LL | / /** This is also a doc comment and should produce a warning
|
||||
LL | | */
|
||||
LL | |
|
||||
LL | | // This should *NOT* produce a warning
|
||||
... |
|
||||
LL | | #[allow(missing_docs)]
|
||||
LL | | fn three_attributes() { assert!(true) }
|
||||
| |_
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
@ -22,9 +22,9 @@ mod rustc_ok {
|
||||
|
||||
#[expect(illegal_floating_point_literal_pattern)]
|
||||
match x {
|
||||
5.0 => {}
|
||||
6.0 => {}
|
||||
_ => {}
|
||||
5.0 => {},
|
||||
6.0 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -38,9 +38,9 @@ mod rustc_warn {
|
||||
|
||||
#[expect(illegal_floating_point_literal_pattern)]
|
||||
match x {
|
||||
5 => {}
|
||||
6 => {}
|
||||
_ => {}
|
||||
5 => {},
|
||||
6 => {},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)]
|
||||
#![warn(clippy::arithmetic_side_effects, clippy::float_arithmetic)]
|
||||
#![allow(
|
||||
unused,
|
||||
clippy::shadow_reuse,
|
||||
|
@ -1,109 +0,0 @@
|
||||
//@aux-build:proc_macro_derive.rs
|
||||
|
||||
#![warn(clippy::integer_arithmetic, clippy::float_arithmetic)]
|
||||
#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::op_ref)]
|
||||
|
||||
extern crate proc_macro_derive;
|
||||
|
||||
#[derive(proc_macro_derive::ShadowDerive)]
|
||||
pub struct Nothing;
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn main() {
|
||||
let mut i = 1i32;
|
||||
let mut var1 = 13i32;
|
||||
let mut var2 = -1i32;
|
||||
1 + i;
|
||||
i * 2;
|
||||
1 %
|
||||
i / 2; // no error, this is part of the expression in the preceding line
|
||||
i - 2 + 2 - i;
|
||||
-i;
|
||||
i >> 1;
|
||||
i << 1;
|
||||
|
||||
// no error, overflows are checked by `overflowing_literals`
|
||||
-1;
|
||||
-(-1);
|
||||
|
||||
i & 1; // no wrapping
|
||||
i | 1;
|
||||
i ^ 1;
|
||||
|
||||
i += 1;
|
||||
i -= 1;
|
||||
i *= 2;
|
||||
i /= 2;
|
||||
i /= 0;
|
||||
i /= -1;
|
||||
i /= var1;
|
||||
i /= var2;
|
||||
i %= 2;
|
||||
i %= 0;
|
||||
i %= -1;
|
||||
i %= var1;
|
||||
i %= var2;
|
||||
i <<= 3;
|
||||
i >>= 2;
|
||||
|
||||
// no errors
|
||||
i |= 1;
|
||||
i &= 1;
|
||||
i ^= i;
|
||||
|
||||
// No errors for the following items because they are constant expressions
|
||||
enum Foo {
|
||||
Bar = -2,
|
||||
}
|
||||
struct Baz([i32; 1 + 1]);
|
||||
union Qux {
|
||||
field: [i32; 1 + 1],
|
||||
}
|
||||
type Alias = [i32; 1 + 1];
|
||||
|
||||
const FOO: i32 = -2;
|
||||
static BAR: i32 = -2;
|
||||
|
||||
let _: [i32; 1 + 1] = [0, 0];
|
||||
|
||||
let _: [i32; 1 + 1] = {
|
||||
let a: [i32; 1 + 1] = [0, 0];
|
||||
a
|
||||
};
|
||||
|
||||
trait Trait {
|
||||
const ASSOC: i32 = 1 + 1;
|
||||
}
|
||||
|
||||
impl Trait for Foo {
|
||||
const ASSOC: i32 = {
|
||||
let _: [i32; 1 + 1];
|
||||
fn foo() {}
|
||||
1 + 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// warn on references as well! (#5328)
|
||||
pub fn int_arith_ref() {
|
||||
3 + &1;
|
||||
&3 + 1;
|
||||
&3 + &1;
|
||||
}
|
||||
|
||||
pub fn foo(x: &i32) -> i32 {
|
||||
let a = 5;
|
||||
a + x
|
||||
}
|
||||
|
||||
pub fn bar(x: &i32, y: &i32) -> i32 {
|
||||
x + y
|
||||
}
|
||||
|
||||
pub fn baz(x: i32, y: &i32) -> i32 {
|
||||
x + y
|
||||
}
|
||||
|
||||
pub fn qux(x: i32, y: i32) -> i32 {
|
||||
(&x + &y)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user