Merge commit '91496c2ac6abf6454c413bb23e8becf6b6dc20ea' into clippyup

This commit is contained in:
flip1995 2021-10-21 13:11:36 +02:00
commit 9af8e378b9
No known key found for this signature in database
GPG Key ID: 1CA0DF2AF59D68A5
127 changed files with 4335 additions and 629 deletions

View File

@ -2730,11 +2730,13 @@ Released 2018-09-13
[`fn_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
[`fn_params_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_params_excessive_bools
[`fn_to_numeric_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast
[`fn_to_numeric_cast_any`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_any
[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`format_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#format_in_format_args
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
@ -2836,6 +2838,7 @@ Released 2018-09-13
[`match_result_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_result_ok
[`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms
[`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding
[`match_str_case_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_str_case_mismatch
[`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm
[`match_wildcard_for_single_variants`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wildcard_for_single_variants
[`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter
@ -2896,6 +2899,7 @@ Released 2018-09-13
[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
[`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
@ -3012,16 +3016,19 @@ Released 2018-09-13
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
[`toplevel_ref_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#toplevel_ref_arg
[`trailing_empty_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#trailing_empty_array
[`trait_duplication_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#trait_duplication_in_bounds
[`transmute_bytes_to_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_bytes_to_str
[`transmute_float_to_int`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_float_to_int
[`transmute_int_to_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_bool
[`transmute_int_to_char`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_char
[`transmute_int_to_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_int_to_float
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
@ -3031,10 +3038,12 @@ Released 2018-09-13
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
[`uninit_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_vec
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord

View File

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

View File

@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
bytecount = "0.6"
clap = "2.33"
indoc = "1.0"
itertools = "0.10"
opener = "0.5"
regex = "1.5"

View File

@ -28,6 +28,7 @@ fn main() {
matches.value_of("pass"),
matches.value_of("name"),
matches.value_of("category"),
matches.is_present("msrv"),
) {
Ok(_) => update_lints::run(update_lints::UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {}", e),
@ -147,6 +148,11 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
"internal_warn",
])
.takes_value(true),
)
.arg(
Arg::with_name("msrv")
.long("msrv")
.help("Add MSRV config code to the lint"),
),
)
.subcommand(

View File

@ -1,4 +1,5 @@
use crate::clippy_project_root;
use indoc::indoc;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::io::{self, ErrorKind};
@ -32,7 +33,7 @@ fn context<C: AsRef<str>>(self, text: C) -> Self {
/// # Errors
///
/// This function errors out if the files couldn't be created or written to.
pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>) -> io::Result<()> {
pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> {
let lint = LintData {
pass: pass.expect("`pass` argument is validated by clap"),
name: lint_name.expect("`name` argument is validated by clap"),
@ -40,29 +41,12 @@ pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str
project_root: clippy_project_root(),
};
create_lint(&lint).context("Unable to create lint implementation")?;
create_lint(&lint, msrv).context("Unable to create lint implementation")?;
create_test(&lint).context("Unable to create a test for the new lint")
}
fn create_lint(lint: &LintData<'_>) -> io::Result<()> {
let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
"early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
"late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
_ => {
unreachable!("`pass_type` should only ever be `early` or `late`!");
},
};
let camel_case_name = to_camel_case(lint.name);
let lint_contents = get_lint_file_contents(
pass_type,
pass_lifetimes,
lint.name,
&camel_case_name,
lint.category,
pass_import,
context_import,
);
fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
let lint_contents = get_lint_file_contents(lint, enable_msrv);
let lint_path = format!("clippy_lints/src/{}.rs", lint.name);
write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())
@ -122,12 +106,13 @@ fn to_camel_case(name: &str) -> String {
fn get_test_file_contents(lint_name: &str, header_commands: Option<&str>) -> String {
let mut contents = format!(
"#![warn(clippy::{})]
indoc! {"
#![warn(clippy::{})]
fn main() {{
// test code goes here
}}
",
fn main() {{
// test code goes here
}}
"},
lint_name
);
@ -140,64 +125,143 @@ fn main() {{
fn get_manifest_contents(lint_name: &str, hint: &str) -> String {
format!(
r#"
# {}
indoc! {r#"
# {}
[package]
name = "{}"
version = "0.1.0"
publish = false
[package]
name = "{}"
version = "0.1.0"
publish = false
[workspace]
"#,
[workspace]
"#},
hint, lint_name
)
}
fn get_lint_file_contents(
pass_type: &str,
pass_lifetimes: &str,
lint_name: &str,
camel_case_name: &str,
category: &str,
pass_import: &str,
context_import: &str,
) -> String {
format!(
"use rustc_lint::{{{type}, {context_import}}};
use rustc_session::{{declare_lint_pass, declare_tool_lint}};
{pass_import}
fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
let mut result = String::new();
declare_clippy_lint! {{
/// ### What it does
///
/// ### Why is this bad?
///
/// ### Example
/// ```rust
/// // example code where clippy issues a warning
/// ```
/// Use instead:
/// ```rust
/// // example code which does not raise clippy warning
/// ```
pub {name_upper},
{category},
\"default lint description\"
}}
let (pass_type, pass_lifetimes, pass_import, context_import) = match lint.pass {
"early" => ("EarlyLintPass", "", "use rustc_ast::ast::*;", "EarlyContext"),
"late" => ("LateLintPass", "<'_>", "use rustc_hir::*;", "LateContext"),
_ => {
unreachable!("`pass_type` should only ever be `early` or `late`!");
},
};
declare_lint_pass!({name_camel} => [{name_upper}]);
let lint_name = lint.name;
let pass_name = lint.pass;
let category = lint.category;
let name_camel = to_camel_case(lint.name);
let name_upper = lint_name.to_uppercase();
impl {type}{lifetimes} for {name_camel} {{}}
",
type=pass_type,
lifetimes=pass_lifetimes,
name_upper=lint_name.to_uppercase(),
name_camel=camel_case_name,
category=category,
pass_import=pass_import,
context_import=context_import
)
result.push_str(&if enable_msrv {
format!(
indoc! {"
use clippy_utils::msrvs;
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
use rustc_semver::RustcVersion;
use rustc_session::{{declare_tool_lint, impl_lint_pass}};
"},
pass_type = pass_type,
pass_import = pass_import,
context_import = context_import,
)
} else {
format!(
indoc! {"
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}}};
use rustc_session::{{declare_lint_pass, declare_tool_lint}};
"},
pass_import = pass_import,
pass_type = pass_type,
context_import = context_import
)
});
result.push_str(&format!(
indoc! {"
declare_clippy_lint! {{
/// ### What it does
///
/// ### Why is this bad?
///
/// ### Example
/// ```rust
/// // example code where clippy issues a warning
/// ```
/// Use instead:
/// ```rust
/// // example code which does not raise clippy warning
/// ```
pub {name_upper},
{category},
\"default lint description\"
}}
"},
name_upper = name_upper,
category = category,
));
result.push_str(&if enable_msrv {
format!(
indoc! {"
pub struct {name_camel} {{
msrv: Option<RustcVersion>,
}}
impl {name_camel} {{
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {{
Self {{ msrv }}
}}
}}
impl_lint_pass!({name_camel} => [{name_upper}]);
impl {pass_type}{pass_lifetimes} for {name_camel} {{
extract_msrv_attr!({context_import});
}}
// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
// e.g. store.register_{pass_name}_pass(move || Box::new({module_name}::{name_camel}::new(msrv)));
// TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed.
// TODO: Add MSRV test to `tests/ui/min_rust_version_attr.rs`.
// TODO: Update msrv config comment in `clippy_lints/src/utils/conf.rs`
"},
pass_type = pass_type,
pass_lifetimes = pass_lifetimes,
pass_name = pass_name,
name_upper = name_upper,
name_camel = name_camel,
module_name = lint_name,
context_import = context_import,
)
} else {
format!(
indoc! {"
declare_lint_pass!({name_camel} => [{name_upper}]);
impl {pass_type}{pass_lifetimes} for {name_camel} {{}}
//
// TODO: Register the lint pass in `clippy_lints/src/lib.rs`,
// e.g. store.register_{pass_name}_pass(|| Box::new({module_name}::{name_camel}));
"},
pass_type = pass_type,
pass_lifetimes = pass_lifetimes,
pass_name = pass_name,
name_upper = name_upper,
name_camel = name_camel,
module_name = lint_name,
)
});
result
}
#[test]

View File

@ -619,8 +619,8 @@ fn test_gen_changelog_lint_list() {
Lint::new("should_assert_eq2", "group2", "abc", None, "module_name"),
];
let expected = vec![
format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK.to_string()),
format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK.to_string()),
format!("[`should_assert_eq`]: {}#should_assert_eq", DOCS_LINK),
format!("[`should_assert_eq2`]: {}#should_assert_eq2", DOCS_LINK),
];
assert_eq!(expected, gen_changelog_lint_list(lints.iter()));
}

View File

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

View File

@ -0,0 +1,34 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use super::FN_TO_NUMERIC_CAST_ANY;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
// We allow casts from any function type to any function type.
match cast_to.kind() {
ty::FnDef(..) | ty::FnPtr(..) => return,
_ => { /* continue to checks */ },
}
match cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(_) => {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
span_lint_and_sugg(
cx,
FN_TO_NUMERIC_CAST_ANY,
expr.span,
&format!("casting function pointer `{}` to `{}`", from_snippet, cast_to),
"did you mean to invoke the function?",
format!("{}() as {}", from_snippet, cast_to),
applicability,
);
},
_ => {},
}
}

View File

@ -7,6 +7,7 @@
mod cast_sign_loss;
mod char_lit_as_u8;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation;
mod ptr_as_ptr;
mod unnecessary_cast;
@ -251,6 +252,42 @@
"casting a function pointer to a numeric type not wide enough to store the address"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of a function pointer to any integer type.
///
/// ### Why is this bad?
/// Casting a function pointer to an integer can have surprising results and can occur
/// accidentally if parantheses are omitted from a function call. If you aren't doing anything
/// low-level with function pointers then you can opt-out of casting functions to integers in
/// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function
/// pointer casts in your code.
///
/// ### Example
/// ```rust
/// // Bad: fn1 is cast as `usize`
/// fn fn1() -> u16 {
/// 1
/// };
/// let _ = fn1 as usize;
///
/// // Good: maybe you intended to call the function?
/// fn fn2() -> u16 {
/// 1
/// };
/// let _ = fn2() as usize;
///
/// // Good: maybe you intended to cast it to a function type?
/// fn fn3() -> u16 {
/// 1
/// }
/// let _ = fn3 as fn() -> u16;
/// ```
pub FN_TO_NUMERIC_CAST_ANY,
restriction,
"casting a function pointer to any integer type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of `&T` to `&mut T` anywhere in the code.
@ -360,6 +397,7 @@ pub fn new(msrv: Option<RustcVersion>) -> Self {
CAST_REF_TO_MUT,
CAST_PTR_ALIGNMENT,
UNNECESSARY_CAST,
FN_TO_NUMERIC_CAST_ANY,
FN_TO_NUMERIC_CAST,
FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
CHAR_LIT_AS_U8,
@ -385,6 +423,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
return;
}
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {

View File

@ -459,12 +459,10 @@ fn emit_branches_sharing_code_lint(
} else {
sm.stmt_span(block.stmts[block.stmts.len() - end_stmts].span, block.span)
};
let moved_end = block
.expr
.map_or_else(
|| sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span),
|expr| expr.span.source_callsite(),
);
let moved_end = block.expr.map_or_else(
|| sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span),
|expr| expr.span.source_callsite(),
);
let moved_span = moved_start.to(moved_end);
let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None);

View File

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
@ -139,6 +140,13 @@ fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
.fields
.iter()
.all(|field| field.vis.is_accessible_from(module_did, cx.tcx));
let all_fields_are_copy = variant
.fields
.iter()
.all(|field| {
is_copy(cx, cx.tcx.type_of(field.did))
});
if !has_drop(cx, binding_type) || all_fields_are_copy;
then {
(local, variant, ident.name, binding_type, expr.span)
} else {

View File

@ -1,12 +1,14 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{
def::Res, def_id::DefId, Item, ItemKind, PolyTraitRef, PrimTy, TraitBoundModifier, Ty, TyKind, UseKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{Span, Symbol};
use rustc_span::Span;
use crate::utils::conf;
declare_clippy_lint! {
/// ### What it does
@ -19,7 +21,15 @@
/// An example clippy.toml configuration:
/// ```toml
/// # clippy.toml
/// disallowed-types = ["std::collections::BTreeMap"]
/// disallowed-types = [
/// # Can use a string as the path of the disallowed type.
/// "std::collections::BTreeMap",
/// # Can also use an inline table with a `path` key.
/// { path = "std::net::TcpListener" },
/// # When using an inline table, can add a `reason` for why the type
/// # is disallowed.
/// { path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
/// ]
/// ```
///
/// ```rust,ignore
@ -38,33 +48,30 @@
}
#[derive(Clone, Debug)]
pub struct DisallowedType {
disallowed: FxHashSet<Vec<Symbol>>,
def_ids: FxHashSet<DefId>,
prim_tys: FxHashSet<PrimTy>,
conf_disallowed: Vec<conf::DisallowedType>,
def_ids: FxHashMap<DefId, Option<String>>,
prim_tys: FxHashMap<PrimTy, Option<String>>,
}
impl DisallowedType {
pub fn new(disallowed: &FxHashSet<String>) -> Self {
pub fn new(conf_disallowed: Vec<conf::DisallowedType>) -> Self {
Self {
disallowed: disallowed
.iter()
.map(|s| s.split("::").map(Symbol::intern).collect::<Vec<_>>())
.collect(),
def_ids: FxHashSet::default(),
prim_tys: FxHashSet::default(),
conf_disallowed,
def_ids: FxHashMap::default(),
prim_tys: FxHashMap::default(),
}
}
fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
match res {
Res::Def(_, did) => {
if self.def_ids.contains(did) {
emit(cx, &cx.tcx.def_path_str(*did), span);
if let Some(reason) = self.def_ids.get(did) {
emit(cx, &cx.tcx.def_path_str(*did), span, reason.as_deref());
}
},
Res::PrimTy(prim) => {
if self.prim_tys.contains(prim) {
emit(cx, prim.name_str(), span);
if let Some(reason) = self.prim_tys.get(prim) {
emit(cx, prim.name_str(), span, reason.as_deref());
}
},
_ => {},
@ -76,14 +83,21 @@ fn check_res_emit(&self, cx: &LateContext<'_>, res: &Res, span: Span) {
impl<'tcx> LateLintPass<'tcx> for DisallowedType {
fn check_crate(&mut self, cx: &LateContext<'_>) {
for path in &self.disallowed {
let segs = path.iter().map(ToString::to_string).collect::<Vec<_>>();
match clippy_utils::path_to_res(cx, &segs.iter().map(String::as_str).collect::<Vec<_>>()) {
for conf in &self.conf_disallowed {
let (path, reason) = match conf {
conf::DisallowedType::Simple(path) => (path, None),
conf::DisallowedType::WithReason { path, reason } => (
path,
reason.as_ref().map(|reason| format!("{} (from clippy.toml)", reason)),
),
};
let segs: Vec<_> = path.split("::").collect();
match clippy_utils::path_to_res(cx, &segs) {
Res::Def(_, id) => {
self.def_ids.insert(id);
self.def_ids.insert(id, reason);
},
Res::PrimTy(ty) => {
self.prim_tys.insert(ty);
self.prim_tys.insert(ty, reason);
},
_ => {},
}
@ -107,11 +121,16 @@ fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTrait
}
}
fn emit(cx: &LateContext<'_>, name: &str, span: Span) {
span_lint(
fn emit(cx: &LateContext<'_>, name: &str, span: Span, reason: Option<&str>) {
span_lint_and_then(
cx,
DISALLOWED_TYPE,
span,
&format!("`{}` is not allowed according to config", name),
|diag| {
if let Some(reason) = reason {
diag.note(reason);
}
},
);
}

View File

@ -1,3 +1,4 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_note};
use clippy_utils::source::first_line_of_span;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
@ -297,6 +298,17 @@ fn lint_for_missing_headers<'tcx>(
if !cx.access_levels.is_exported(def_id) {
return; // Private functions do not require doc comments
}
// do not lint if any parent has `#[doc(hidden)]` attribute (#7347)
if cx
.tcx
.hir()
.parent_iter(cx.tcx.hir().local_def_id_to_hir_id(def_id))
.any(|(id, _node)| is_doc_hidden(cx.tcx.hir().attrs(id)))
{
return;
}
if !headers.safety && sig.header.unsafety == hir::Unsafety::Unsafe {
span_lint(
cx,

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, in_macro, is_expn_of};
use clippy_utils::{
ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, in_macro, is_expn_of, is_in_test_function,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
@ -81,7 +83,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if macro_args.len() == 2;
let (lhs, rhs) = (macro_args[0], macro_args[1]);
if eq_expr_value(cx, lhs, rhs);
if !is_in_test_function(cx.tcx, e.hir_id);
then {
span_lint(
cx,
@ -108,7 +110,10 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
return;
}
if is_useless_with_eq_exprs(op.node.into()) && eq_expr_value(cx, left, right) {
if is_useless_with_eq_exprs(op.node.into())
&& eq_expr_value(cx, left, right)
&& !is_in_test_function(cx.tcx, e.hir_id)
{
span_lint(
cx,
EQ_OP,

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::implements_trait;
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -77,9 +77,9 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
let pat_str = match pat.kind {
PatKind::Struct(..) => format!(
"({})",
snippet_with_applicability(cx, pat.span, "..", &mut applicability),
snippet_with_context(cx, pat.span, expr.span.ctxt(), "..", &mut applicability).0,
),
_ => snippet_with_applicability(cx, pat.span, "..", &mut applicability).to_string(),
_ => snippet_with_context(cx, pat.span, expr.span.ctxt(), "..", &mut applicability).0.to_string(),
};
span_lint_and_sugg(
cx,
@ -89,7 +89,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
"try",
format!(
"{} == {}",
snippet_with_applicability(cx, exp.span, "..", &mut applicability),
snippet_with_context(cx, exp.span, expr.span.ctxt(), "..", &mut applicability).0,
pat_str,
),
applicability,

View File

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::FormatExpn;
use clippy_utils::last_path_segment;
use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, QPath};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -69,8 +68,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
ty::Str => true,
_ => false,
};
if format_args.args.iter().all(is_display_arg);
if format_args.fmt_expr.map_or(true, check_unformatted);
if let Some(args) = format_args.args();
if args.iter().all(|arg| arg.is_display() && !arg.has_string_formatting());
then {
let is_new_string = match value.kind {
ExprKind::Binary(..) => true,
@ -101,47 +100,3 @@ fn span_useless_format(cx: &LateContext<'_>, span: Span, sugg: String, applicabi
applicability,
);
}
fn is_display_arg(expr: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::Call(_, [_, fmt]) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = fmt.kind;
if let [.., t, _] = path.segments;
if t.ident.name == sym::Display;
then { true } else { false }
}
}
/// Checks if the expression matches
/// ```rust,ignore
/// &[_ {
/// format: _ {
/// width: _::Implied,
/// precision: _::Implied,
/// ...
/// },
/// ...,
/// }]
/// ```
fn check_unformatted(expr: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array([expr]) = expr.kind;
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = expr.kind;
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
// struct `core::fmt::rt::v1::FormatSpec`
if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
if last_path_segment(precision_path).ident.name == sym::Implied;
if let Some(width_field) = fields.iter().find(|f| f.ident.name == sym::width);
if let ExprKind::Path(ref width_qpath) = width_field.expr.kind;
if last_path_segment(width_qpath).ident.name == sym::Implied;
then {
return true;
}
}
false
}

View File

@ -0,0 +1,223 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher::{FormatArgsArg, FormatArgsExpn, FormatExpn};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{is_diag_trait_item, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, BytePos, ExpnData, ExpnKind, Span, Symbol};
declare_clippy_lint! {
/// ### What it does
/// Detects `format!` within the arguments of another macro that does
/// formatting such as `format!` itself, `write!` or `println!`. Suggests
/// inlining the `format!` call.
///
/// ### Why is this bad?
/// The recommended code is both shorter and avoids a temporary allocation.
///
/// ### Example
/// ```rust
/// # use std::panic::Location;
/// println!("error: {}", format!("something failed at {}", Location::caller()));
/// ```
/// Use instead:
/// ```rust
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller());
/// ```
pub FORMAT_IN_FORMAT_ARGS,
perf,
"`format!` used in a macro that does formatting"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
/// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
/// in a macro that does formatting.
///
/// ### Why is this bad?
/// Since the type implements `Display`, the use of `to_string` is
/// unnecessary.
///
/// ### Example
/// ```rust
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller().to_string());
/// ```
/// Use instead:
/// ```rust
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller());
/// ```
pub TO_STRING_IN_FORMAT_ARGS,
perf,
"`to_string` applied to a type that implements `Display` in format args"
}
declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]);
const FORMAT_MACRO_PATHS: &[&[&str]] = &[
&paths::FORMAT_ARGS_MACRO,
&paths::ASSERT_EQ_MACRO,
&paths::ASSERT_MACRO,
&paths::ASSERT_NE_MACRO,
&paths::EPRINT_MACRO,
&paths::EPRINTLN_MACRO,
&paths::PRINT_MACRO,
&paths::PRINTLN_MACRO,
&paths::WRITE_MACRO,
&paths::WRITELN_MACRO,
];
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro];
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if let Some(format_args) = FormatArgsExpn::parse(expr);
let expr_expn_data = expr.span.ctxt().outer_expn_data();
let outermost_expn_data = outermost_expn_data(expr_expn_data);
if let Some(macro_def_id) = outermost_expn_data.macro_def_id;
if FORMAT_MACRO_PATHS
.iter()
.any(|path| match_def_path(cx, macro_def_id, path))
|| FORMAT_MACRO_DIAG_ITEMS
.iter()
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id));
if let ExpnKind::Macro(_, name) = outermost_expn_data.kind;
if let Some(args) = format_args.args();
then {
for (i, arg) in args.iter().enumerate() {
if !arg.is_display() {
continue;
}
if arg.has_string_formatting() {
continue;
}
if is_aliased(&args, i) {
continue;
}
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg);
check_to_string_in_format_args(cx, name, arg);
}
}
}
}
}
fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
if expn_data.call_site.from_expansion() {
outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
} else {
expn_data
}
}
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &FormatArgsArg<'_>) {
if_chain! {
if FormatExpn::parse(arg.value).is_some();
if !arg.value.span.ctxt().outer_expn_data().call_site.from_expansion();
then {
span_lint_and_then(
cx,
FORMAT_IN_FORMAT_ARGS,
trim_semicolon(cx, call_site),
&format!("`format!` in `{}!` args", name),
|diag| {
diag.help(&format!(
"combine the `format!(..)` arguments with the outer `{}!(..)` call",
name
));
diag.help("or consider changing `format!` to `format_args!`");
},
);
}
}
}
fn check_to_string_in_format_args<'tcx>(cx: &LateContext<'tcx>, name: Symbol, arg: &FormatArgsArg<'tcx>) {
let value = arg.value;
if_chain! {
if !value.span.from_expansion();
if let ExprKind::MethodCall(_, _, [receiver], _) = value.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id);
if is_diag_trait_item(cx, method_def_id, sym::ToString);
let receiver_ty = cx.typeck_results().expr_ty(receiver);
if let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display);
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
let (n_needed_derefs, target) = count_needed_derefs(
receiver_ty,
cx.typeck_results().expr_adjustments(receiver).iter(),
);
if implements_trait(cx, target, display_trait_id, &[]) {
if n_needed_derefs == 0 {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span.with_lo(receiver.span.hi()),
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
&format!("`to_string` applied to a type that implements `Display` in `{}!` args", name),
"use this",
format!("{:*>width$}{}", "", receiver_snippet, width = n_needed_derefs),
Applicability::MachineApplicable,
);
}
}
}
}
}
// Returns true if `args[i]` "refers to" or "is referred to by" another argument.
fn is_aliased(args: &[FormatArgsArg<'_>], i: usize) -> bool {
let value = args[i].value;
args.iter()
.enumerate()
.any(|(j, arg)| i != j && std::ptr::eq(value, arg.value))
}
fn trim_semicolon(cx: &LateContext<'_>, span: Span) -> Span {
snippet_opt(cx, span).map_or(span, |snippet| {
let snippet = snippet.trim_end_matches(';');
span.with_hi(span.lo() + BytePos(u32::try_from(snippet.len()).unwrap()))
})
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
where
I: Iterator<Item = &'tcx Adjustment<'tcx>>,
{
let mut n_total = 0;
let mut n_needed = 0;
loop {
if let Some(Adjustment {
kind: Adjust::Deref(overloaded_deref),
target,
}) = iter.next()
{
n_total += 1;
if overloaded_deref.is_some() {
n_needed = n_total;
}
ty = target;
} else {
return (n_needed, ty);
}
}
}

View File

@ -91,11 +91,9 @@
declare_clippy_lint! {
/// ### What it does
/// Checks for a [`#[must_use]`] attribute on
/// Checks for a `#[must_use]` attribute on
/// unit-returning functions and methods.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// ### Why is this bad?
/// Unit values are useless. The attribute is likely
/// a remnant of a refactoring that removed the return type.
@ -112,12 +110,10 @@
declare_clippy_lint! {
/// ### What it does
/// Checks for a [`#[must_use]`] attribute without
/// Checks for a `#[must_use]` attribute without
/// further information on functions and methods that return a type already
/// marked as `#[must_use]`.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// ### Why is this bad?
/// The attribute isn't needed. Not using the result
/// will already be reported. Alternatively, one can add some text to the
@ -138,11 +134,9 @@
declare_clippy_lint! {
/// ### What it does
/// Checks for public functions that have no
/// [`#[must_use]`] attribute, but return something not already marked
/// `#[must_use]` attribute, but return something not already marked
/// must-use, have no mutable arg and mutate no statics.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// ### Why is this bad?
/// Not bad at all, this lint just shows places where
/// you could add the attribute.

View File

@ -66,7 +66,6 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1))
}
#[allow(clippy::cast_possible_wrap)]
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e) {
let check = match *cx.typeck_results().expr_ty(e).kind() {

View File

@ -78,10 +78,10 @@ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Expr{kind: ExprKind::Unary(UnOp::Not, not_expr), ..} = e {
sugg::Sugg::hir_with_applicability(cx, not_expr, "..", &mut applicability).maybe_par().to_string()
} else {
format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par().to_string())
format!("!{}", sugg::Sugg::hir_with_applicability(cx, e, "..", &mut applicability).maybe_par())
}
} else {
format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par().to_string())
format!("!{}", sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par())
};
span_lint_and_sugg(

View File

@ -43,7 +43,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
return;
}
if_chain! {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
if let Some(higher::If { cond, then, r#else: None }) = higher::If::hir(expr);
// Check if the conditional expression is a binary operation
if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;

View File

@ -138,10 +138,10 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
item.span,
&format!(
"type `{}` implements inherent method `to_string(&self) -> String` which shadows the implementation of `Display`",
self_type.to_string()
self_type
),
None,
&format!("remove the inherent method from type `{}`", self_type.to_string()),
&format!("remove the inherent method from type `{}`", self_type),
);
} else {
span_lint_and_help(
@ -150,10 +150,10 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) {
item.span,
&format!(
"implementation of inherent method `to_string(&self) -> String` for type `{}`",
self_type.to_string()
self_type
),
None,
&format!("implement trait `Display` for type `{}` instead", self_type.to_string()),
&format!("implement trait `Display` for type `{}` instead", self_type),
);
}
}

View File

@ -10,12 +10,11 @@
declare_clippy_lint! {
/// ### What it does
/// Checks for `let _ = <expr>`
/// where expr is #[must_use]
/// Checks for `let _ = <expr>` where expr is `#[must_use]`
///
/// ### Why is this bad?
/// It's better to explicitly
/// handle the value of a #[must_use] expr
/// It's better to explicitly handle the value of a `#[must_use]`
/// expr
///
/// ### Example
/// ```rust

View File

@ -60,6 +60,8 @@
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
LintId::of(float_literal::EXCESSIVE_PRECISION),
LintId::of(format::USELESS_FORMAT),
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
@ -118,6 +120,7 @@
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(match_result_ok::MATCH_RESULT_OK),
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
LintId::of(matches::MATCH_AS_REF),
LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
@ -265,6 +268,7 @@
LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
@ -277,6 +281,7 @@
LintId::of(types::VEC_BOX),
LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
LintId::of(unicode::INVISIBLE_CHARACTERS),
LintId::of(uninit_vec::UNINIT_VEC),
LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
LintId::of(unit_types::UNIT_ARG),
LintId::of(unit_types::UNIT_CMP),

View File

@ -82,6 +82,7 @@
LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(types::BORROWED_BOX),
LintId::of(types::TYPE_COMPLEXITY),

View File

@ -36,6 +36,7 @@
LintId::of(loops::ITER_NEXT_LOOP),
LintId::of(loops::NEVER_LOOP),
LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
LintId::of(methods::CLONE_DOUBLE_REF),
LintId::of(methods::ITERATOR_STEP_BY_ZERO),
@ -62,6 +63,7 @@
LintId::of(transmuting_null::TRANSMUTING_NULL),
LintId::of(undropped_manually_drops::UNDROPPED_MANUALLY_DROPS),
LintId::of(unicode::INVISIBLE_CHARACTERS),
LintId::of(uninit_vec::UNINIT_VEC),
LintId::of(unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD),
LintId::of(unit_types::UNIT_CMP),
LintId::of(unnamed_address::FN_ADDRESS_COMPARISONS),

View File

@ -67,6 +67,7 @@
casts::CAST_SIGN_LOSS,
casts::CHAR_LIT_AS_U8,
casts::FN_TO_NUMERIC_CAST,
casts::FN_TO_NUMERIC_CAST_ANY,
casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION,
casts::PTR_AS_PTR,
casts::UNNECESSARY_CAST,
@ -138,6 +139,8 @@
floating_point_arithmetic::IMPRECISE_FLOPS,
floating_point_arithmetic::SUBOPTIMAL_FLOPS,
format::USELESS_FORMAT,
format_args::FORMAT_IN_FORMAT_ARGS,
format_args::TO_STRING_IN_FORMAT_ARGS,
formatting::POSSIBLE_MISSING_COMMA,
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
formatting::SUSPICIOUS_ELSE_FORMATTING,
@ -225,6 +228,7 @@
map_unit_fn::RESULT_MAP_UNIT_FN,
match_on_vec_items::MATCH_ON_VEC_ITEMS,
match_result_ok::MATCH_RESULT_OK,
match_str_case_mismatch::MATCH_STR_CASE_MISMATCH,
matches::INFALLIBLE_DESTRUCTURING_MATCH,
matches::MATCH_AS_REF,
matches::MATCH_BOOL,
@ -359,6 +363,7 @@
neg_multiply::NEG_MULTIPLY,
new_without_default::NEW_WITHOUT_DEFAULT,
no_effect::NO_EFFECT,
no_effect::NO_EFFECT_UNDERSCORE_BINDING,
no_effect::UNNECESSARY_OPERATION,
non_copy_const::BORROW_INTERIOR_MUTABLE_CONST,
non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST,
@ -438,6 +443,7 @@
temporary_assignment::TEMPORARY_ASSIGNMENT,
to_digit_is_some::TO_DIGIT_IS_SOME,
to_string_in_display::TO_STRING_IN_DISPLAY,
trailing_empty_array::TRAILING_EMPTY_ARRAY,
trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS,
trait_bounds::TYPE_REPETITION_IN_BOUNDS,
transmute::CROSSPOINTER_TRANSMUTE,
@ -447,6 +453,7 @@
transmute::TRANSMUTE_INT_TO_BOOL,
transmute::TRANSMUTE_INT_TO_CHAR,
transmute::TRANSMUTE_INT_TO_FLOAT,
transmute::TRANSMUTE_NUM_TO_BYTES,
transmute::TRANSMUTE_PTR_TO_PTR,
transmute::TRANSMUTE_PTR_TO_REF,
transmute::UNSOUND_COLLECTION_TRANSMUTE,
@ -463,10 +470,12 @@
types::REDUNDANT_ALLOCATION,
types::TYPE_COMPLEXITY,
types::VEC_BOX,
undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
undropped_manually_drops::UNDROPPED_MANUALLY_DROPS,
unicode::INVISIBLE_CHARACTERS,
unicode::NON_ASCII_LITERAL,
unicode::UNICODE_NOT_NFC,
uninit_vec::UNINIT_VEC,
unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD,
unit_types::LET_UNIT_VALUE,
unit_types::UNIT_ARG,

View File

@ -25,6 +25,7 @@
LintId::of(regex::TRIVIAL_REGEX),
LintId::of(strings::STRING_LIT_AS_BYTES),
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
LintId::of(transmute::USELESS_TRANSMUTE),
LintId::of(use_self::USE_SELF),
])

View File

@ -72,6 +72,7 @@
LintId::of(needless_continue::NEEDLESS_CONTINUE),
LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
LintId::of(no_effect::NO_EFFECT_UNDERSCORE_BINDING),
LintId::of(non_expressive_names::MANY_SINGLE_CHAR_NAMES),
LintId::of(non_expressive_names::SIMILAR_NAMES),
LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),

View File

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

View File

@ -8,6 +8,7 @@
LintId::of(as_conversions::AS_CONVERSIONS),
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
LintId::of(create_dir::CREATE_DIR),
LintId::of(dbg_macro::DBG_MACRO),
LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
@ -56,6 +57,7 @@
LintId::of(strings::STR_TO_STRING),
LintId::of(types::RC_BUFFER),
LintId::of(types::RC_MUTEX),
LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),
LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS),
LintId::of(unwrap_in_result::UNWRAP_IN_RESULT),
LintId::of(verbose_file_reads::VERBOSE_FILE_READS),

View File

@ -218,6 +218,7 @@ macro_rules! declare_clippy_lint {
mod float_literal;
mod floating_point_arithmetic;
mod format;
mod format_args;
mod formatting;
mod from_over_into;
mod from_str_radix_10;
@ -265,6 +266,7 @@ macro_rules! declare_clippy_lint {
mod map_unit_fn;
mod match_on_vec_items;
mod match_result_ok;
mod match_str_case_mismatch;
mod matches;
mod mem_forget;
mod mem_replace;
@ -352,13 +354,16 @@ macro_rules! declare_clippy_lint {
mod temporary_assignment;
mod to_digit_is_some;
mod to_string_in_display;
mod trailing_empty_array;
mod trait_bounds;
mod transmute;
mod transmuting_null;
mod try_err;
mod types;
mod undocumented_unsafe_blocks;
mod undropped_manually_drops;
mod unicode;
mod uninit_vec;
mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
@ -516,6 +521,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
store.register_late_pass(|| Box::new(collapsible_match::CollapsibleMatch));
store.register_late_pass(|| Box::new(unicode::Unicode));
store.register_late_pass(|| Box::new(uninit_vec::UninitVec));
store.register_late_pass(|| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd));
store.register_late_pass(|| Box::new(strings::StringAdd));
store.register_late_pass(|| Box::new(implicit_return::ImplicitReturn));
@ -754,8 +760,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_early_pass(move || Box::new(module_style::ModStyle));
store.register_late_pass(|| Box::new(unused_async::UnusedAsync));
let disallowed_types = conf.disallowed_types.iter().cloned().collect::<FxHashSet<_>>();
store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(&disallowed_types)));
let disallowed_types = conf.disallowed_types.clone();
store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(disallowed_types.clone())));
let import_renames = conf.enforced_import_renames.clone();
store.register_late_pass(move || Box::new(missing_enforced_import_rename::ImportRename::new(import_renames.clone())));
let scripts = conf.allowed_scripts.clone();
@ -767,6 +773,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(if_then_panic::IfThenPanic));
let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
store.register_late_pass(move || Box::new(format_args::FormatArgs));
store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
}
#[rustfmt::skip]

View File

@ -35,7 +35,7 @@
/// }
///
/// if let Ok(value) = iter.next() {
/// vec.push_value)
/// vec.push(value)
/// }
/// ```
pub MATCH_RESULT_OK,

View File

@ -0,0 +1,171 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::SymbolStr;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Checks for `match` expressions modifying the case of a string with non-compliant arms
///
/// ### Why is this bad?
/// The arm is unreachable, which is likely a mistake
///
/// ### Example
/// ```rust
/// # let text = "Foo";
///
/// match &*text.to_ascii_lowercase() {
/// "foo" => {},
/// "Bar" => {},
/// _ => {},
/// }
/// ```
/// Use instead:
/// ```rust
/// # let text = "Foo";
///
/// match &*text.to_ascii_lowercase() {
/// "foo" => {},
/// "bar" => {},
/// _ => {},
/// }
/// ```
pub MATCH_STR_CASE_MISMATCH,
correctness,
"creation of a case altering match expression with non-compliant arms"
}
declare_lint_pass!(MatchStrCaseMismatch => [MATCH_STR_CASE_MISMATCH]);
#[derive(Debug)]
enum CaseMethod {
LowerCase,
AsciiLowerCase,
UpperCase,
AsciiUppercase,
}
impl LateLintPass<'_> for MatchStrCaseMismatch {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if !in_external_macro(cx.tcx.sess, expr.span);
if let ExprKind::Match(match_expr, arms, MatchSource::Normal) = expr.kind;
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(match_expr).kind();
if let ty::Str = ty.kind();
then {
let mut visitor = MatchExprVisitor {
cx,
case_method: None,
};
visitor.visit_expr(match_expr);
if let Some(case_method) = visitor.case_method {
if let Some((bad_case_span, bad_case_str)) = verify_case(&case_method, arms) {
lint(cx, &case_method, bad_case_span, &bad_case_str);
}
}
}
}
}
}
struct MatchExprVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
case_method: Option<CaseMethod>,
}
impl<'a, 'tcx> Visitor<'tcx> for MatchExprVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
match ex.kind {
ExprKind::MethodCall(segment, _, [receiver], _)
if self.case_altered(&*segment.ident.as_str(), receiver) => {},
_ => walk_expr(self, ex),
}
}
}
impl<'a, 'tcx> MatchExprVisitor<'a, 'tcx> {
fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool {
if let Some(case_method) = get_case_method(segment_ident) {
let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs();
if is_type_diagnostic_item(self.cx, ty, sym::String) || ty.kind() == &ty::Str {
self.case_method = Some(case_method);
return true;
}
}
false
}
}
fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> {
match segment_ident_str {
"to_lowercase" => Some(CaseMethod::LowerCase),
"to_ascii_lowercase" => Some(CaseMethod::AsciiLowerCase),
"to_uppercase" => Some(CaseMethod::UpperCase),
"to_ascii_uppercase" => Some(CaseMethod::AsciiUppercase),
_ => None,
}
}
fn verify_case<'a>(case_method: &'a CaseMethod, arms: &'a [Arm<'_>]) -> Option<(Span, SymbolStr)> {
let case_check = match case_method {
CaseMethod::LowerCase => |input: &str| -> bool { input.chars().all(char::is_lowercase) },
CaseMethod::AsciiLowerCase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'a'..='z')) },
CaseMethod::UpperCase => |input: &str| -> bool { input.chars().all(char::is_uppercase) },
CaseMethod::AsciiUppercase => |input: &str| -> bool { input.chars().all(|c| matches!(c, 'A'..='Z')) },
};
for arm in arms {
if_chain! {
if let PatKind::Lit(Expr {
kind: ExprKind::Lit(lit),
..
}) = arm.pat.kind;
if let LitKind::Str(symbol, _) = lit.node;
let input = symbol.as_str();
if !case_check(&input);
then {
return Some((lit.span, input));
}
}
}
None
}
fn lint(cx: &LateContext<'_>, case_method: &CaseMethod, bad_case_span: Span, bad_case_str: &str) {
let (method_str, suggestion) = match case_method {
CaseMethod::LowerCase => ("to_lower_case", bad_case_str.to_lowercase()),
CaseMethod::AsciiLowerCase => ("to_ascii_lowercase", bad_case_str.to_ascii_lowercase()),
CaseMethod::UpperCase => ("to_uppercase", bad_case_str.to_uppercase()),
CaseMethod::AsciiUppercase => ("to_ascii_uppercase", bad_case_str.to_ascii_uppercase()),
};
span_lint_and_sugg(
cx,
MATCH_STR_CASE_MISMATCH,
bad_case_span,
"this `match` arm has a differing case than its expression",
&*format!("consider changing the case of this arm to respect `{}`", method_str),
format!("\"{}\"", suggestion),
Applicability::MachineApplicable,
);
}

View File

@ -1187,7 +1187,7 @@ fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I,
'b: 'a,
I: Clone + Iterator<Item = &'a Pat<'b>>,
{
if !has_only_ref_pats(pats.clone()) {
if !has_multiple_ref_pats(pats.clone()) {
return;
}
@ -1693,12 +1693,12 @@ fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotat
None
}
fn has_only_ref_pats<'a, 'b, I>(pats: I) -> bool
fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool
where
'b: 'a,
I: Iterator<Item = &'a Pat<'b>>,
{
let mut at_least_one_is_true = false;
let mut ref_count = 0;
for opt in pats.map(|pat| match pat.kind {
PatKind::Ref(..) => Some(true), // &-patterns
PatKind::Wild => Some(false), // an "anything" wildcard is also fine
@ -1706,13 +1706,13 @@ fn has_only_ref_pats<'a, 'b, I>(pats: I) -> bool
}) {
if let Some(inner) = opt {
if inner {
at_least_one_is_true = true;
ref_count += 1;
}
} else {
return false;
}
}
at_least_one_is_true
ref_count > 1
}
pub fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>

View File

@ -1777,14 +1777,13 @@
}
declare_clippy_lint! {
/// **What it does:** Checks for usages of `str::splitn(2, _)`
/// ### What it does
/// Checks for usages of `str::splitn(2, _)`
///
/// **Why is this bad?** `split_once` is both clearer in intent and slightly more efficient.
///
/// **Known problems:** None.
///
/// **Example:**
/// ### Why is this bad?
/// `split_once` is both clearer in intent and slightly more efficient.
///
/// ### Example
/// ```rust,ignore
/// // Bad
/// let (key, value) = _.splitn(2, '=').next_tuple()?;

View File

@ -1,9 +1,8 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{is_expr_path_def_path, match_def_path, paths};
use clippy_utils::{is_expr_path_def_path, paths, ty::is_uninit_value_valid_for_ty};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use super::UNINIT_ASSUMED_INIT;
@ -13,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
if let hir::ExprKind::Call(callee, args) = recv.kind;
if args.is_empty();
if is_expr_path_def_path(cx, callee, &paths::MEM_MAYBEUNINIT_UNINIT);
if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(expr));
if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr));
then {
span_lint(
cx,
@ -24,12 +23,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
}
}
}
fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Array(component, _) => is_maybe_uninit_ty_valid(cx, component),
ty::Tuple(types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)),
ty::Adt(adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT),
_ => false,
}
}

View File

@ -8,7 +8,7 @@
declare_clippy_lint! {
/// ### What it does
/// it lints if an exported function, method, trait method with default impl,
/// It lints if an exported function, method, trait method with default impl,
/// or trait method impl is not `#[inline]`.
///
/// ### Why is this bad?

View File

@ -82,6 +82,10 @@ fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
}
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'_>) {
if in_external_macro(self.cx.sess(), ty.span) {
return;
}
if let hir::TyKind::Rptr(
_,
hir::MutTy {

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
use clippy_utils::is_lint_allowed;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::has_drop;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, Stmt, StmtKind, UnsafeSource};
use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, PatKind, Stmt, StmtKind, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use std::ops::Deref;
@ -13,7 +14,7 @@
/// Checks for statements which have no effect.
///
/// ### Why is this bad?
/// Similar to dead code, these statements are actually
/// Unlike dead code, these statements are actually
/// executed. However, as they have no effect, all they do is make the code less
/// readable.
///
@ -26,6 +27,28 @@
"statements with no effect"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for binding to underscore prefixed variable without side-effects.
///
/// ### Why is this bad?
/// Unlike dead code, these bindings are actually
/// executed. However, as they have no effect and shouldn't be used further on, all they
/// do is make the code less readable.
///
/// ### Known problems
/// Further usage of this variable is not checked, which can lead to false positives if it is
/// used later in the code.
///
/// ### Example
/// ```rust,ignore
/// let _i_serve_no_purpose = 1;
/// ```
pub NO_EFFECT_UNDERSCORE_BINDING,
pedantic,
"binding to `_` prefixed variable with no side-effect"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for expression statements that can be reduced to a
@ -44,6 +67,46 @@
"outer expressions with no effect"
}
declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION, NO_EFFECT_UNDERSCORE_BINDING]);
impl<'tcx> LateLintPass<'tcx> for NoEffect {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
if check_no_effect(cx, stmt) {
return;
}
check_unnecessary_operation(cx, stmt);
}
}
fn check_no_effect(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> bool {
if let StmtKind::Semi(expr) = stmt.kind {
if has_no_effect(cx, expr) {
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
return true;
}
} else if let StmtKind::Local(local) = stmt.kind {
if_chain! {
if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id);
if let Some(init) = local.init;
if !local.pat.span.from_expansion();
if has_no_effect(cx, init);
if let PatKind::Binding(_, _, ident, _) = local.pat.kind;
if ident.name.to_ident_string().starts_with('_');
then {
span_lint_hir(
cx,
NO_EFFECT_UNDERSCORE_BINDING,
init.hir_id,
stmt.span,
"binding to `_` prefixed variable with no side-effect"
);
return true;
}
}
}
false
}
fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if expr.span.from_expansion() {
return false;
@ -88,71 +151,59 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
}
}
declare_lint_pass!(NoEffect => [NO_EFFECT, UNNECESSARY_OPERATION]);
impl<'tcx> LateLintPass<'tcx> for NoEffect {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
if let StmtKind::Semi(expr) = stmt.kind {
if has_no_effect(cx, expr) {
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
} else if let Some(reduced) = reduce_expression(cx, expr) {
for e in &reduced {
if e.span.from_expansion() {
fn check_unnecessary_operation(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
if_chain! {
if let StmtKind::Semi(expr) = stmt.kind;
if let Some(reduced) = reduce_expression(cx, expr);
if !&reduced.iter().any(|e| e.span.from_expansion());
then {
if let ExprKind::Index(..) = &expr.kind {
let snippet;
if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) {
snippet = format!("assert!({}.len() > {});", &arr, &func);
} else {
return;
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be written as",
snippet,
Applicability::MaybeIncorrect,
);
},
);
} else {
let mut snippet = String::new();
for e in reduced {
if let Some(snip) = snippet_opt(cx, e.span) {
snippet.push_str(&snip);
snippet.push(';');
} else {
return;
}
}
if let ExprKind::Index(..) = &expr.kind {
let snippet;
if_chain! {
if let Some(arr) = snippet_opt(cx, reduced[0].span);
if let Some(func) = snippet_opt(cx, reduced[1].span);
then {
snippet = format!("assert!({}.len() > {});", &arr, &func);
} else {
return;
}
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be written as",
snippet,
Applicability::MaybeIncorrect,
);
},
);
} else {
let mut snippet = String::new();
for e in reduced {
if let Some(snip) = snippet_opt(cx, e.span) {
snippet.push_str(&snip);
snippet.push(';');
} else {
return;
}
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be reduced to",
snippet,
Applicability::MachineApplicable,
);
},
);
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be reduced to",
snippet,
Applicability::MachineApplicable,
);
},
);
}
}
}

View File

@ -139,6 +139,7 @@
/// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
/// ```
///
/// ```ignore
/// // Good
/// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
/// ```

View File

@ -4,10 +4,10 @@
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, path_to_local_id};
use clippy_utils::{eq_expr_value, path_to_local, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -48,16 +48,20 @@ impl QuestionMark {
/// }
/// ```
///
/// ```ignore
/// if result.is_err() {
/// return result;
/// }
/// ```
///
/// If it matches, it will suggest to use the question mark operator instead
fn check_is_none_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
if let ExprKind::MethodCall(segment, _, args, _) = &cond.kind;
if segment.ident.name == sym!(is_none);
if Self::expression_returns_none(cx, then);
if let Some(subject) = args.get(0);
if Self::is_option(cx, subject);
if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) ||
(Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err));
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
@ -95,31 +99,24 @@ fn check_is_none_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
}
}
fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
= higher::IfLet::hir(cx, expr);
if Self::is_option(cx, let_expr);
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
if is_lang_ctor(cx, path1, OptionSome);
if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
(Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Block(block, None) = if_then.kind;
if block.stmts.is_empty();
if let Some(trailing_expr) = &block.expr;
if path_to_local_id(trailing_expr, bind_id);
if Self::expression_returns_none(cx, if_else);
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
let replacement = format!(
"{}{}?",
receiver_str,
if by_ref { ".as_ref()" } else { "" },
);
let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
span_lint_and_sugg(
cx,
@ -134,6 +131,14 @@ fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>
}
}
fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
}
fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
}
fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expression);
@ -146,6 +151,12 @@ fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
is_type_diagnostic_item(cx, expr_ty, sym::Option)
}
fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expression);
is_type_diagnostic_item(cx, expr_ty, sym::Result)
}
fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
match expression.kind {
ExprKind::Block(block, _) => {
@ -161,6 +172,27 @@ fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool
}
}
fn expression_returns_unmodified_err(
cx: &LateContext<'_>,
expression: &Expr<'_>,
origin_hir_id: &Expr<'_>,
) -> bool {
match expression.kind {
ExprKind::Block(block, _) => {
if let Some(return_expression) = Self::return_expression(block) {
return Self::expression_returns_unmodified_err(cx, return_expression, origin_hir_id);
}
false
},
ExprKind::Ret(Some(expr)) | ExprKind::Call(expr, _) => {
Self::expression_returns_unmodified_err(cx, expr, origin_hir_id)
},
ExprKind::Path(_) => path_to_local(expression) == path_to_local(origin_hir_id),
_ => false,
}
}
fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
// Check if last expression is a return statement. Then, return the expression
if_chain! {
@ -189,7 +221,7 @@ fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
Self::check_is_none_and_early_return_none(cx, expr);
Self::check_if_let_some_and_early_return_none(cx, expr);
Self::check_is_none_or_err_and_early_return(cx, expr);
Self::check_if_let_some_or_err_and_early_return(cx, expr);
}
}

View File

@ -1,7 +1,7 @@
use crate::rustc_lint::LintContext;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::{in_macro, sugg};
use clippy_utils::sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Block, ExprKind};
@ -39,7 +39,7 @@
impl LateLintPass<'_> for SemicolonIfNothingReturned {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
if_chain! {
if !in_macro(block.span);
if !block.span.from_expansion();
if let Some(expr) = block.expr;
let t_expr = cx.typeck_results().expr_ty(expr);
if t_expr.is_unit();

View File

@ -162,11 +162,7 @@ fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span)
(SHADOW_SAME, msg)
},
Some(expr) if is_local_used(cx, expr, shadowed) => {
let msg = format!(
"`{}` is shadowed by `{}` which reuses the original value",
snippet(cx, pat.span, "_"),
snippet(cx, expr.span, "..")
);
let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
(SHADOW_REUSE, msg)
},
_ => {

View File

@ -678,7 +678,7 @@ fn suggestion_with_swapped_ident(
Some(format!(
"{}{}{}",
snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability),
new_ident.to_string(),
new_ident,
snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability),
))
})

View File

@ -0,0 +1,77 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Const;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute.
///
/// ### Why is this bad?
/// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
///
/// ### Example
/// ```rust
/// struct RarelyUseful {
/// some_field: u32,
/// last: [u32; 0],
/// }
/// ```
///
/// Use instead:
/// ```rust
/// #[repr(C)]
/// struct MoreOftenUseful {
/// some_field: usize,
/// last: [u32; 0],
/// }
/// ```
pub TRAILING_EMPTY_ARRAY,
nursery,
"struct with a trailing zero-sized array but without `#[repr(C)]` or another `repr` attribute"
}
declare_lint_pass!(TrailingEmptyArray => [TRAILING_EMPTY_ARRAY]);
impl<'tcx> LateLintPass<'tcx> for TrailingEmptyArray {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if is_struct_with_trailing_zero_sized_array(cx, item) && !has_repr_attr(cx, item.hir_id()) {
span_lint_and_help(
cx,
TRAILING_EMPTY_ARRAY,
item.span,
"trailing zero-sized array in a struct which is not marked with a `repr` attribute",
None,
&format!(
"consider annotating `{}` with `#[repr(C)]` or another `repr` attribute",
cx.tcx.def_path_str(item.def_id.to_def_id())
),
);
}
}
}
fn is_struct_with_trailing_zero_sized_array(cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) -> bool {
if_chain! {
// First check if last field is an array
if let ItemKind::Struct(data, _) = &item.kind;
if let Some(last_field) = data.fields().last();
if let rustc_hir::TyKind::Array(_, length) = last_field.ty.kind;
// Then check if that that array zero-sized
let length_ldid = cx.tcx.hir().local_def_id(length.hir_id);
let length = Const::from_anon_const(cx.tcx, length_ldid);
let length = length.try_eval_usize(cx.tcx, cx.param_env);
if let Some(length) = length;
then {
length == 0
} else {
false
}
}
}
fn has_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool {
cx.tcx.hir().attrs(hir_id).iter().any(|attr| attr.has_name(sym::repr))
}

View File

@ -3,6 +3,7 @@
mod transmute_int_to_bool;
mod transmute_int_to_char;
mod transmute_int_to_float;
mod transmute_num_to_bytes;
mod transmute_ptr_to_ptr;
mod transmute_ptr_to_ref;
mod transmute_ref_to_ref;
@ -261,6 +262,28 @@
"transmutes from a float to an integer"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a number to an array of `u8`
///
/// ### Why this is bad?
/// Transmutes are dangerous and error-prone, whereas `to_ne_bytes`
/// is intuitive and safe.
///
/// ### Example
/// ```rust
/// unsafe {
/// let x: [u8; 8] = std::mem::transmute(1i64);
/// }
///
/// // should be
/// let x: [u8; 8] = 0i64.to_ne_bytes();
/// ```
pub TRANSMUTE_NUM_TO_BYTES,
complexity,
"transmutes from a number to an array of `u8`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for transmutes from a pointer to a pointer, or
@ -330,6 +353,7 @@
TRANSMUTE_INT_TO_BOOL,
TRANSMUTE_INT_TO_FLOAT,
TRANSMUTE_FLOAT_TO_INT,
TRANSMUTE_NUM_TO_BYTES,
UNSOUND_COLLECTION_TRANSMUTE,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
]);
@ -365,6 +389,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args);
linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context);
linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context);
linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context);
linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty);
if !linted {

View File

@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
diag.span_suggestion(
e.span,
"consider using",
format!("std::char::from_u32({}).unwrap()", arg.to_string()),
format!("std::char::from_u32({}).unwrap()", arg),
Applicability::Unspecified,
);
},

View File

@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(
diag.span_suggestion(
e.span,
"consider using",
format!("{}::from_bits({})", to_ty, arg.to_string()),
format!("{}::from_bits({})", to_ty, arg),
Applicability::Unspecified,
);
},

View File

@ -0,0 +1,49 @@
use super::TRANSMUTE_NUM_TO_BYTES;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, UintTy};
/// Checks for `transmute_int_to_float` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
from_ty: Ty<'tcx>,
to_ty: Ty<'tcx>,
args: &'tcx [Expr<'_>],
const_context: bool,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Int(_) | ty::Uint(_) | ty::Float(_), ty::Array(arr_ty, _)) => {
if !matches!(arr_ty.kind(), ty::Uint(UintTy::U8)) {
return false;
}
if matches!(from_ty.kind(), ty::Float(_)) && const_context {
// TODO: Remove when const_float_bits_conv is stabilized
// rust#72447
return false;
}
span_lint_and_then(
cx,
TRANSMUTE_NUM_TO_BYTES,
e.span,
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|diag| {
let arg = sugg::Sugg::hir(cx, &args[0], "..");
diag.span_suggestion(
e.span,
"consider using `to_ne_bytes()`",
format!("{}.to_ne_bytes()", arg),
Applicability::Unspecified,
);
},
);
true
},
_ => false,
}
}

View File

@ -0,0 +1,225 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
use clippy_utils::{in_macro, is_lint_allowed};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource};
use rustc_lexer::TokenKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::TyCtxt;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{BytePos, Span};
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
/// Checks for `unsafe` blocks without a `// Safety: ` comment
/// explaining why the unsafe operations performed inside
/// the block are safe.
///
/// ### Why is this bad?
/// Undocumented unsafe blocks can make it difficult to
/// read and maintain code, as well as uncover unsoundness
/// and bugs.
///
/// ### Example
/// ```rust
/// use std::ptr::NonNull;
/// let a = &mut 42;
///
/// let ptr = unsafe { NonNull::new_unchecked(a) };
/// ```
/// Use instead:
/// ```rust
/// use std::ptr::NonNull;
/// let a = &mut 42;
///
/// // Safety: references are guaranteed to be non-null.
/// let ptr = unsafe { NonNull::new_unchecked(a) };
/// ```
pub UNDOCUMENTED_UNSAFE_BLOCKS,
restriction,
"creating an unsafe block without explaining why it is safe"
}
impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
#[derive(Default)]
pub struct UndocumentedUnsafeBlocks {
pub local_level: u32,
pub local_span: Option<Span>,
// The local was already checked for an overall safety comment
// There is no need to continue checking the blocks in the local
pub local_checked: bool,
// Since we can only check the blocks from expanded macros
// We have to omit the suggestion due to the actual definition
// Not being available to us
pub macro_expansion: bool,
}
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
if_chain! {
if !self.local_checked;
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
if !in_external_macro(cx.tcx.sess, block.span);
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
if self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, block.span) == Some(false);
then {
let mut span = block.span;
if let Some(local_span) = self.local_span {
span = local_span;
let result = self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, span);
if result.unwrap_or(true) {
self.local_checked = true;
return;
}
}
self.lint(cx, span);
}
}
}
fn check_local(&mut self, cx: &LateContext<'_>, local: &'_ Local<'_>) {
if_chain! {
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, local.hir_id);
if !in_external_macro(cx.tcx.sess, local.span);
if let Some(init) = local.init;
then {
self.visit_expr(init);
if self.local_level > 0 {
self.local_span = Some(local.span);
}
}
}
}
fn check_block_post(&mut self, _: &LateContext<'_>, _: &'_ Block<'_>) {
self.local_level = self.local_level.saturating_sub(1);
if self.local_level == 0 {
self.local_checked = false;
self.local_span = None;
}
}
}
impl<'hir> Visitor<'hir> for UndocumentedUnsafeBlocks {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, ex: &'v Expr<'v>) {
match ex.kind {
ExprKind::Block(_, _) => self.local_level = self.local_level.saturating_add(1),
_ => walk_expr(self, ex),
}
}
}
impl UndocumentedUnsafeBlocks {
fn block_has_safety_comment(&mut self, tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<bool> {
let map = tcx.hir();
let source_map = tcx.sess.source_map();
let enclosing_scope_span = map.opt_span(enclosing_hir_id)?;
let between_span = if in_macro(block_span) {
self.macro_expansion = true;
enclosing_scope_span.with_hi(block_span.hi())
} else {
self.macro_expansion = false;
enclosing_scope_span.to(block_span)
};
let file_name = source_map.span_to_filename(between_span);
let source_file = source_map.get_source_file(&file_name)?;
let lex_start = (between_span.lo().0 + 1) as usize;
let src_str = source_file.src.as_ref()?[lex_start..between_span.hi().0 as usize].to_string();
let mut pos = 0;
let mut comment = false;
for token in rustc_lexer::tokenize(&src_str) {
match token.kind {
TokenKind::LineComment { doc_style: None }
| TokenKind::BlockComment {
doc_style: None,
terminated: true,
} => {
let comment_str = src_str[pos + 2..pos + token.len].to_ascii_uppercase();
if comment_str.contains("SAFETY:") {
comment = true;
}
},
// We need to add all whitespace to `pos` before checking the comment's line number
TokenKind::Whitespace => {},
_ => {
if comment {
// Get the line number of the "comment" (really wherever the trailing whitespace ended)
let comment_line_num = source_file
.lookup_file_pos_with_col_display(BytePos((lex_start + pos).try_into().unwrap()))
.0;
// Find the block/local's line number
let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line;
// Check the comment is immediately followed by the block/local
if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
return Some(true);
}
comment = false;
}
},
}
pos += token.len;
}
Some(false)
}
fn lint(&self, cx: &LateContext<'_>, mut span: Span) {
let source_map = cx.tcx.sess.source_map();
if source_map.is_multiline(span) {
span = source_map.span_until_char(span, '\n');
}
if self.macro_expansion {
span_lint_and_help(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe block in macro expansion missing a safety comment",
None,
"consider adding a safety comment in the macro definition",
);
} else {
let block_indent = indent_of(cx, span);
let suggestion = format!("// Safety: ...\n{}", snippet(cx, span, ".."));
span_lint_and_sugg(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe block missing a safety comment",
"consider adding a safety comment",
reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
Applicability::HasPlaceholders,
);
}
}
}

View File

@ -0,0 +1,223 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
use clippy_utils::ty::{is_type_diagnostic_item, is_uninit_value_valid_for_ty};
use clippy_utils::{is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq};
use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Span};
// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
declare_clippy_lint! {
/// ### What it does
/// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
/// This is commonly caused by calling `set_len()` right after allocating or
/// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
///
/// ### Why is this bad?
/// It creates a `Vec` with uninitialized data, which leads to
/// undefined behavior with most safe operations. Notably, uninitialized
/// `Vec<u8>` must not be used with generic `Read`.
///
/// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
/// creates out-of-bound values that lead to heap memory corruption when used.
///
/// ### Known Problems
/// This lint only checks directly adjacent statements.
///
/// ### Example
/// ```rust,ignore
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
/// unsafe { vec.set_len(1000); }
/// reader.read(&mut vec); // undefined behavior!
/// ```
///
/// ### How to fix?
/// 1. Use an initialized buffer:
/// ```rust,ignore
/// let mut vec: Vec<u8> = vec![0; 1000];
/// reader.read(&mut vec);
/// ```
/// 2. Wrap the content in `MaybeUninit`:
/// ```rust,ignore
/// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
/// vec.set_len(1000); // `MaybeUninit` can be uninitialized
/// ```
/// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
/// ```rust,ignore
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
/// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
/// // perform initialization with `remaining`
/// vec.set_len(...); // Safe to call `set_len()` on initialized part
/// ```
pub UNINIT_VEC,
correctness,
"Vec with uninitialized data"
}
declare_lint_pass!(UninitVec => [UNINIT_VEC]);
// FIXME: update to a visitor-based implementation.
// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
impl<'tcx> LateLintPass<'tcx> for UninitVec {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
if !in_external_macro(cx.tcx.sess, block.span) {
for w in block.stmts.windows(2) {
if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind {
handle_uninit_vec_pair(cx, &w[0], expr);
}
}
if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) {
handle_uninit_vec_pair(cx, stmt, expr);
}
}
}
}
fn handle_uninit_vec_pair(
cx: &LateContext<'tcx>,
maybe_init_or_reserve: &'tcx Stmt<'tcx>,
maybe_set_len: &'tcx Expr<'tcx>,
) {
if_chain! {
if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
if vec.location.eq_expr(cx, set_len_self);
if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
if let ty::Adt(_, substs) = vec_ty.kind();
// `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id);
then {
if vec.has_capacity() {
// with_capacity / reserve -> set_len
// Check T of Vec<T>
if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) {
// FIXME: #7698, false positive of the internal lints
#[allow(clippy::collapsible_span_lint_calls)]
span_lint_and_then(
cx,
UNINIT_VEC,
vec![call_span, maybe_init_or_reserve.span],
"calling `set_len()` immediately after reserving a buffer creates uninitialized values",
|diag| {
diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
},
);
}
} else {
// new / default -> set_len
span_lint(
cx,
UNINIT_VEC,
vec![call_span, maybe_init_or_reserve.span],
"calling `set_len()` on empty `Vec` creates out-of-bound values",
);
}
}
}
}
/// The target `Vec` that is initialized or reserved
#[derive(Clone, Copy)]
struct TargetVec<'tcx> {
location: VecLocation<'tcx>,
/// `None` if `reserve()`
init_kind: Option<VecInitKind>,
}
impl TargetVec<'_> {
pub fn has_capacity(self) -> bool {
!matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default))
}
}
#[derive(Clone, Copy)]
enum VecLocation<'tcx> {
Local(HirId),
Expr(&'tcx Expr<'tcx>),
}
impl<'tcx> VecLocation<'tcx> {
pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
match self {
VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id),
VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr),
}
}
}
/// Finds the target location where the result of `Vec` initialization is stored
/// or `self` expression for `Vec::reserve()`.
fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> {
match stmt.kind {
StmtKind::Local(local) => {
if_chain! {
if let Some(init_expr) = local.init;
if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
if let Some(init_kind) = get_vec_init_kind(cx, init_expr);
then {
return Some(TargetVec {
location: VecLocation::Local(hir_id),
init_kind: Some(init_kind),
})
}
}
},
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
ExprKind::Assign(lhs, rhs, _span) => {
if let Some(init_kind) = get_vec_init_kind(cx, rhs) {
return Some(TargetVec {
location: VecLocation::Expr(lhs),
init_kind: Some(init_kind),
});
}
},
ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
return Some(TargetVec {
location: VecLocation::Expr(self_expr),
init_kind: None,
});
},
_ => (),
},
StmtKind::Item(_) => (),
}
None
}
fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
&& path.ident.name.as_str() == "reserve"
}
/// Returns self if the expression is `Vec::set_len()`
fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> {
// peel unsafe blocks in `unsafe { vec.set_len() }`
let expr = peel_hir_expr_while(expr, |e| {
if let ExprKind::Block(block, _) = e.kind {
// Extract the first statement/expression
match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) {
(None, Some(expr)) => Some(expr),
(Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
_ => None,
}
} else {
None
}
});
match expr.kind {
ExprKind::MethodCall(path, _, [self_expr, _], _) => {
let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
Some((self_expr, expr.span))
} else {
None
}
},
_ => None,
}
}

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
@ -193,10 +193,15 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
let vec_name = Sugg::hir(cx, &args[0], "..").to_string();
let unstable = name == "sort_unstable_by";
if_chain! {
if let ExprKind::Path(QPath::Resolved(_, Path {
segments: [PathSegment { ident: left_name, .. }], ..
})) = &left_expr.kind {
if left_name == left_ident {
})) = &left_expr.kind;
if left_name == left_ident;
if cx.tcx.get_diagnostic_item(sym::Ord).map_or(false, |id| {
implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[])
});
then {
return Some(LintTrigger::Sort(SortDetection { vec_name, unstable }));
}
}

View File

@ -23,6 +23,14 @@ pub enum DisallowedMethod {
WithReason { path: String, reason: Option<String> },
}
/// A single disallowed type, used by the `DISALLOWED_TYPE` lint.
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum DisallowedType {
Simple(String),
WithReason { path: String, reason: Option<String> },
}
/// Conf with parse errors
#[derive(Default)]
pub struct TryConf {
@ -255,7 +263,7 @@ pub(crate) fn get_configuration_metadata() -> Vec<ClippyConfiguration> {
/// Lint: DISALLOWED_TYPE.
///
/// The list of disallowed types, written as fully qualified paths.
(disallowed_types: Vec<String> = Vec::new()),
(disallowed_types: Vec<crate::utils::conf::DisallowedType> = Vec::new()),
/// Lint: UNREADABLE_LITERAL.
///
/// Should the fraction of a decimal be linted to include separators.

View File

@ -770,8 +770,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id();
// Check if the matched type is a diagnostic item
let diag_items = cx.tcx.diagnostic_items(ty_did.krate);
if let Some(item_name) = diag_items.iter().find_map(|(k, v)| if *v == ty_did { Some(k) } else { None });
if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
then {
// TODO: check paths constants from external crates.
let cx_snippet = snippet(cx, context.span, "_");

View File

@ -1,16 +1,14 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_def_path, path_to_local, path_to_local_id, paths};
use clippy_utils::{path_to_local, path_to_local_id};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span};
use std::convert::TryInto;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
@ -41,11 +39,6 @@ pub struct VecInitThenPush {
searcher: Option<VecPushSearcher>,
}
#[derive(Clone, Copy)]
enum VecInitKind {
New,
WithCapacity(u64),
}
struct VecPushSearcher {
local_id: HirId,
init: VecInitKind,
@ -58,7 +51,8 @@ impl VecPushSearcher {
fn display_err(&self, cx: &LateContext<'_>) {
match self.init {
_ if self.found == 0 => return,
VecInitKind::WithCapacity(x) if x > self.found => return,
VecInitKind::WithLiteralCapacity(x) if x > self.found => return,
VecInitKind::WithExprCapacity(_) => return,
_ => (),
};
@ -152,37 +146,3 @@ fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Block<'tcx>) {
}
}
}
fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
if let ExprKind::Call(func, args) = expr.kind {
match func.kind {
ExprKind::Path(QPath::TypeRelative(ty, name))
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
{
if name.ident.name == sym::new {
return Some(VecInitKind::New);
} else if name.ident.name.as_str() == "with_capacity" {
return args.get(0).and_then(|arg| {
if_chain! {
if let ExprKind::Lit(lit) = &arg.kind;
if let LitKind::Int(num, _) = lit.node;
then {
Some(VecInitKind::WithCapacity(num.try_into().ok()?))
} else {
None
}
}
});
}
}
ExprKind::Path(QPath::Resolved(_, path))
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
{
return Some(VecInitKind::New);
}
_ => (),
}
}
None
}

View File

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

View File

@ -2,13 +2,16 @@
#![deny(clippy::missing_docs_in_private_items)]
use crate::{is_expn_of, match_def_path, paths};
use crate::ty::is_type_diagnostic_item;
use crate::{is_expn_of, last_path_segment, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind};
use rustc_hir as hir;
use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp};
use rustc_hir::{
Arm, Block, BorrowKind, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath, StmtKind, UnOp,
};
use rustc_lint::LateContext;
use rustc_span::{sym, ExpnKind, Span, Symbol};
use rustc_span::{sym, symbol, ExpnKind, Span, Symbol};
/// The essential nodes of a desugared for loop as well as the entire span:
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
@ -569,6 +572,106 @@ pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
}
}
}
/// Returns a vector of `FormatArgsArg`.
pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
if let Some(expr) = self.fmt_expr {
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array(exprs) = expr.kind;
then {
exprs.iter().map(|fmt| {
if_chain! {
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = fmt.kind;
if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
if let ExprKind::Lit(lit) = &position_field.expr.kind;
if let LitKind::Int(position, _) = lit.node;
then {
let i = usize::try_from(position).unwrap();
Some(FormatArgsArg { value: self.value_args[i], arg: &self.args[i], fmt: Some(fmt) })
} else {
None
}
}
}).collect()
} else {
None
}
}
} else {
Some(
self.value_args
.iter()
.zip(self.args.iter())
.map(|(value, arg)| FormatArgsArg { value, arg, fmt: None })
.collect(),
)
}
}
}
/// Type representing a `FormatArgsExpn`'s format arguments
pub struct FormatArgsArg<'tcx> {
/// An element of `value_args` according to `position`
pub value: &'tcx Expr<'tcx>,
/// An element of `args` according to `position`
pub arg: &'tcx Expr<'tcx>,
/// An element of `fmt_expn`
pub fmt: Option<&'tcx Expr<'tcx>>,
}
impl<'tcx> FormatArgsArg<'tcx> {
/// Returns true if any formatting parameters are used that would have an effect on strings,
/// like `{:+2}` instead of just `{}`.
pub fn has_string_formatting(&self) -> bool {
self.fmt.map_or(false, |fmt| {
// `!` because these conditions check that `self` is unformatted.
!if_chain! {
// struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, fields, _) = fmt.kind;
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
// struct `core::fmt::rt::v1::FormatSpec`
if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind;
let mut precision_found = false;
let mut width_found = false;
if subfields.iter().all(|field| {
match field.ident.name {
sym::precision => {
precision_found = true;
if let ExprKind::Path(ref precision_path) = field.expr.kind {
last_path_segment(precision_path).ident.name == sym::Implied
} else {
false
}
}
sym::width => {
width_found = true;
if let ExprKind::Path(ref width_qpath) = field.expr.kind {
last_path_segment(width_qpath).ident.name == sym::Implied
} else {
false
}
}
_ => true,
}
});
if precision_found && width_found;
then { true } else { false }
}
})
}
/// Returns true if the argument is formatted using `Display::fmt`.
pub fn is_display(&self) -> bool {
if_chain! {
if let ExprKind::Call(_, [_, format_field]) = self.arg.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = format_field.kind;
if let [.., t, _] = path.segments;
if t.ident.name == sym::Display;
then { true } else { false }
}
}
}
/// Checks if a `let` statement is from a `for` loop desugaring.
@ -631,3 +734,51 @@ pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
}
}
}
/// A parsed `Vec` initialization expression
#[derive(Clone, Copy)]
pub enum VecInitKind {
/// `Vec::new()`
New,
/// `Vec::default()` or `Default::default()`
Default,
/// `Vec::with_capacity(123)`
WithLiteralCapacity(u64),
/// `Vec::with_capacity(slice.len())`
WithExprCapacity(HirId),
}
/// Checks if given expression is an initialization of `Vec` and returns its kind.
pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
if let ExprKind::Call(func, args) = expr.kind {
match func.kind {
ExprKind::Path(QPath::TypeRelative(ty, name))
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
{
if name.ident.name == sym::new {
return Some(VecInitKind::New);
} else if name.ident.name == symbol::kw::Default {
return Some(VecInitKind::Default);
} else if name.ident.name.as_str() == "with_capacity" {
let arg = args.get(0)?;
if_chain! {
if let ExprKind::Lit(lit) = &arg.kind;
if let LitKind::Int(num, _) = lit.node;
then {
return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?))
}
}
return Some(VecInitKind::WithExprCapacity(arg.hir_id));
}
}
ExprKind::Path(QPath::Resolved(_, path))
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
{
return Some(VecInitKind::Default);
}
_ => (),
}
}
None
}

View File

@ -69,11 +69,13 @@
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::itemlikevisit::ItemLikeVisitor;
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, Param, Pat,
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, ForeignItem, GenericArgs,
HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node,
Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
UnOp,
};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export;
@ -251,11 +253,7 @@ pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem
/// Returns `true` if this `span` was expanded by any macro.
#[must_use]
pub fn in_macro(span: Span) -> bool {
if span.from_expansion() {
!matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
} else {
false
}
span.from_expansion() && !matches!(span.ctxt().outer_expn_data().kind, ExpnKind::Desugaring(..))
}
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
@ -1285,10 +1283,9 @@ pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool
}
let enclosing_body = cx.tcx.hir().local_def_id(cx.tcx.hir().enclosing_body_owner(e.hir_id));
if let Some((Constant::Int(v), _)) = constant(cx, cx.tcx.typeck(enclosing_body), e) {
value == v
} else {
false
return value == v;
}
false
}
/// Checks whether the given expression is a constant literal of the given value.
@ -1315,7 +1312,7 @@ pub fn is_adjusted(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
/// Returns the pre-expansion span if is this comes from an expansion of the
/// macro `name`.
/// See also `is_direct_expn_of`.
/// See also [`is_direct_expn_of`].
#[must_use]
pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
loop {
@ -1338,13 +1335,13 @@ pub fn is_expn_of(mut span: Span, name: &str) -> Option<Span> {
/// Returns the pre-expansion span if the span directly comes from an expansion
/// of the macro `name`.
/// The difference with `is_expn_of` is that in
/// ```rust,ignore
/// The difference with [`is_expn_of`] is that in
/// ```rust
/// # macro_rules! foo { ($e:tt) => { $e } }; macro_rules! bar { ($e:expr) => { $e } }
/// foo!(bar!(42));
/// ```
/// `42` is considered expanded from `foo!` and `bar!` by `is_expn_of` but only
/// `bar!` by
/// `is_direct_expn_of`.
/// from `bar!` by `is_direct_expn_of`.
#[must_use]
pub fn is_direct_expn_of(span: Span, name: &str) -> Option<Span> {
if span.from_expansion() {
@ -1467,11 +1464,9 @@ pub fn is_self(slf: &Param<'_>) -> bool {
}
pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool {
if_chain! {
if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind;
if let Res::SelfTy(..) = path.res;
then {
return true
if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind {
if let Res::SelfTy(..) = path.res {
return true;
}
}
false
@ -2062,27 +2057,80 @@ macro_rules! unwrap_cargo_metadata {
}
pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
if_chain! {
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
if let Res::Def(_, def_id) = path.res;
then {
cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr)
} else {
false
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
if let Res::Def(_, def_id) = path.res {
return cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr);
}
}
false
}
/// Checks whether item either has `test` attribute applied, or
/// is a module with `test` in its name.
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
if let Some(def_id) = tcx.hir().opt_local_def_id(item.hir_id()) {
if tcx.has_attr(def_id.to_def_id(), sym::test) {
return true;
struct VisitConstTestStruct<'tcx> {
tcx: TyCtxt<'tcx>,
names: Vec<Symbol>,
found: bool,
}
impl<'hir> ItemLikeVisitor<'hir> for VisitConstTestStruct<'hir> {
fn visit_item(&mut self, item: &Item<'_>) {
if let ItemKind::Const(ty, _body) = item.kind {
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind {
// We could also check for the type name `test::TestDescAndFn`
// and the `#[rustc_test_marker]` attribute?
if let Res::Def(DefKind::Struct, _) = path.res {
let has_test_marker = self
.tcx
.hir()
.attrs(item.hir_id())
.iter()
.any(|a| a.has_name(sym::rustc_test_marker));
if has_test_marker && self.names.contains(&item.ident.name) {
self.found = true;
}
}
}
}
}
fn visit_trait_item(&mut self, _: &TraitItem<'_>) {}
fn visit_impl_item(&mut self, _: &ImplItem<'_>) {}
fn visit_foreign_item(&mut self, _: &ForeignItem<'_>) {}
}
matches!(item.kind, ItemKind::Mod(..)) && item.ident.name.as_str().contains("test")
/// Checks if the function containing the given `HirId` is a `#[test]` function
///
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
let names: Vec<_> = tcx
.hir()
.parent_iter(id)
// Since you can nest functions we need to collect all until we leave
// function scope
.filter_map(|(_id, node)| {
if let Node::Item(item) = node {
if let ItemKind::Fn(_, _, _) = item.kind {
return Some(item.ident.name);
}
}
None
})
.collect();
let parent_mod = tcx.parent_module(id);
let mut vis = VisitConstTestStruct {
tcx,
names,
found: false,
};
tcx.hir().visit_item_likes_in_module(parent_mod, &mut vis);
vis.found
}
/// Checks whether item either has `test` attribute appelied, or
/// is a module with `test` in its name.
///
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
is_in_test_function(tcx, item.hir_id())
|| matches!(item.kind, ItemKind::Mod(..))
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
}
macro_rules! op_utils {

View File

@ -17,6 +17,12 @@
#[cfg(feature = "metadata-collector-lint")]
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const ASSERT_EQ_MACRO: [&str; 3] = ["core", "macros", "assert_eq"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const ASSERT_MACRO: [&str; 4] = ["core", "macros", "builtin", "assert"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const ASSERT_NE_MACRO: [&str; 3] = ["core", "macros", "assert_ne"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
pub(super) const BEGIN_PANIC: [&str; 3] = ["std", "panicking", "begin_panic"];
@ -41,11 +47,17 @@
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
#[cfg(feature = "internal-lints")]
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const EPRINT_MACRO: [&str; 3] = ["std", "macros", "eprint"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const EPRINTLN_MACRO: [&str; 3] = ["std", "macros", "eprintln"];
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
pub const FILE: [&str; 3] = ["std", "fs", "File"];
pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const FORMAT_ARGS_MACRO: [&str; 4] = ["core", "macros", "builtin", "format_args"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
@ -108,6 +120,10 @@
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const PRINT_MACRO: [&str; 3] = ["std", "macros", "print"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const PRINTLN_MACRO: [&str; 3] = ["std", "macros", "println"];
pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
@ -184,3 +200,7 @@
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const WRITE_MACRO: [&str; 3] = ["core", "macros", "write"];
#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros
pub const WRITELN_MACRO: [&str; 3] = ["core", "macros", "writeln"];

View File

@ -367,3 +367,13 @@ pub fn same_type_and_consts(a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
_ => a == b,
}
}
/// Checks if a given type looks safe to be uninitialized.
pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
ty::Tuple(types) => types.types().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did),
_ => false,
}
}

View File

@ -96,6 +96,7 @@ cargo dev setup git-hook
# (experimental) Setup Clippy to work with IntelliJ-Rust
cargo dev setup intellij
```
More about intellij command usage and reasons [here](../CONTRIBUTING.md#intellij-rust)
## lintcheck
`cargo lintcheck` will build and run clippy on a fixed set of crates and generate a log of the results.

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2021-10-07"
channel = "nightly-2021-10-21"
components = ["llvm-tools-preview", "rustc-dev", "rust-src"]

View File

@ -149,6 +149,19 @@ fn run_ui(cfg: &mut compiletest::Config) {
compiletest::run_tests(cfg);
}
fn run_ui_test(cfg: &mut compiletest::Config) {
cfg.mode = TestMode::Ui;
cfg.src_base = Path::new("tests").join("ui_test");
let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
let rustcflags = cfg.target_rustcflags.get_or_insert_with(Default::default);
let len = rustcflags.len();
rustcflags.push_str(" --test");
compiletest::run_tests(cfg);
if let Some(ref mut flags) = &mut cfg.target_rustcflags {
flags.truncate(len);
}
}
fn run_internal_tests(cfg: &mut compiletest::Config) {
// only run internal tests with the internal-tests feature
if !RUN_INTERNAL_TESTS {
@ -312,6 +325,7 @@ fn compile_test() {
prepare_env();
let mut config = default_config();
run_ui(&mut config);
run_ui_test(&mut config);
run_ui_toml(&mut config);
run_ui_cargo(&mut config);
run_internal_tests(&mut config);

View File

@ -27,12 +27,13 @@ error: unnecessary `Symbol` to string conversion
--> $DIR/unnecessary_symbol_str.rs:14:5
|
LL | &*Ident::empty().as_str() == "clippy";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::empty().name == rustc_span::sym::clippy`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::empty().name == rustc_span::sym::clippy`
error: unnecessary `Symbol` to string conversion
--> $DIR/unnecessary_symbol_str.rs:15:5
|
LL | "clippy" == Ident::empty().to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::empty().name`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::empty().name`
error: aborting due to 5 previous errors

View File

@ -7,5 +7,9 @@ disallowed-types = [
"std::time::Instant",
"std::io::Read",
"std::primitive::usize",
"bool"
"bool",
# can give path and reason with an inline table
{ path = "std::net::Ipv4Addr", reason = "no IPv4 allowed" },
# can use an inline table but omit reason
{ path = "std::net::TcpListener" },
]

View File

@ -25,6 +25,10 @@ fn const_generics<const C: usize>() {}
static BAD: foo::atomic::AtomicPtr<()> = foo::atomic::AtomicPtr::new(std::ptr::null_mut());
fn ip(_: std::net::Ipv4Addr) {}
fn listener(_: std::net::TcpListener) {}
#[allow(clippy::diverging_sub_expression)]
fn main() {
let _: std::collections::HashMap<(), ()> = std::collections::HashMap::new();

View File

@ -60,59 +60,73 @@ error: `usize` is not allowed according to config
LL | struct GenArg<const U: usize>([u8; U]);
| ^^^^^
error: `std::net::Ipv4Addr` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:28:10
|
LL | fn ip(_: std::net::Ipv4Addr) {}
| ^^^^^^^^^^^^^^^^^^
|
= note: no IPv4 allowed (from clippy.toml)
error: `std::net::TcpListener` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:30:16
|
LL | fn listener(_: std::net::TcpListener) {}
| ^^^^^^^^^^^^^^^^^^^^^
error: `std::collections::HashMap` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:30:48
--> $DIR/conf_disallowed_type.rs:34:48
|
LL | let _: std::collections::HashMap<(), ()> = std::collections::HashMap::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: `std::collections::HashMap` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:30:12
--> $DIR/conf_disallowed_type.rs:34:12
|
LL | let _: std::collections::HashMap<(), ()> = std::collections::HashMap::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `std::time::Instant` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:31:13
--> $DIR/conf_disallowed_type.rs:35:13
|
LL | let _ = Sneaky::now();
| ^^^^^^
error: `std::sync::atomic::AtomicU32` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:32:13
--> $DIR/conf_disallowed_type.rs:36:13
|
LL | let _ = foo::atomic::AtomicU32::new(0);
| ^^^^^^^^^^^^^^^^^^^^^^
error: `std::sync::atomic::AtomicU32` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:33:17
--> $DIR/conf_disallowed_type.rs:37:17
|
LL | static FOO: std::sync::atomic::AtomicU32 = foo::atomic::AtomicU32::new(1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `std::sync::atomic::AtomicU32` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:33:48
--> $DIR/conf_disallowed_type.rs:37:48
|
LL | static FOO: std::sync::atomic::AtomicU32 = foo::atomic::AtomicU32::new(1);
| ^^^^^^^^^^^^^^^^^^^^^^
error: `syn::TypePath` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:34:43
--> $DIR/conf_disallowed_type.rs:38:43
|
LL | let _: std::collections::BTreeMap<(), syn::TypePath> = Default::default();
| ^^^^^^^^^^^^^
error: `syn::Ident` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:35:13
--> $DIR/conf_disallowed_type.rs:39:13
|
LL | let _ = syn::Ident::new("", todo!());
| ^^^^^^^^^^
error: `usize` is not allowed according to config
--> $DIR/conf_disallowed_type.rs:37:12
--> $DIR/conf_disallowed_type.rs:41:12
|
LL | let _: usize = 64_usize;
| ^^^^^
error: aborting due to 19 previous errors
error: aborting due to 21 previous errors

View File

@ -113,3 +113,10 @@ macro_rules! default_numeric_fallback {
let x = 22;
};
}
#[macro_export]
macro_rules! mut_mut {
() => {
let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32;
};
}

View File

@ -1,7 +1,7 @@
// run-rustfix
#![feature(stmt_expr_attributes)]
#![allow(unused, clippy::no_effect)]
#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)]
#![warn(clippy::deprecated_cfg_attr)]
// This doesn't get linted, see known problems

View File

@ -1,7 +1,7 @@
// run-rustfix
#![feature(stmt_expr_attributes)]
#![allow(unused, clippy::no_effect)]
#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)]
#![warn(clippy::deprecated_cfg_attr)]
// This doesn't get linted, see known problems

View File

@ -115,3 +115,13 @@ fn main() {
drive();
}
}
// do not lint if any parent has `#[doc(hidden)]` attribute
// see #7347
#[doc(hidden)]
pub mod __macro {
pub struct T;
impl T {
pub unsafe fn f() {}
}
}

View File

@ -66,4 +66,13 @@ fn main() {
if g == NotStructuralEq::A {}
if let Some(NotPartialEq::A) = Some(f) {}
if Some(g) == Some(NotStructuralEq::A) {}
macro_rules! m1 {
(x) => {
"abc"
};
}
if "abc" == m1!(x) {
println!("OK");
}
}

View File

@ -66,4 +66,13 @@ fn main() {
if let NotStructuralEq::A = g {}
if let Some(NotPartialEq::A) = Some(f) {}
if let Some(NotStructuralEq::A) = Some(g) {}
macro_rules! m1 {
(x) => {
"abc"
};
}
if let m1!(x) = "abc" {
println!("OK");
}
}

View File

@ -60,5 +60,11 @@ error: this pattern matching can be expressed using equality
LL | if let Some(NotStructuralEq::A) = Some(g) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(g) == Some(NotStructuralEq::A)`
error: aborting due to 10 previous errors
error: this pattern matching can be expressed using equality
--> $DIR/equatable_if_let.rs:75:8
|
LL | if let m1!(x) = "abc" {
| ^^^^^^^^^^^^^^^^^^ help: try: `"abc" == m1!(x)`
error: aborting due to 11 previous errors

View File

@ -1,6 +1,7 @@
// run-rustfix
#![warn(clippy::expect_fun_call)]
#![allow(clippy::to_string_in_format_args)]
/// Checks implementation of the `EXPECT_FUN_CALL` lint

View File

@ -1,6 +1,7 @@
// run-rustfix
#![warn(clippy::expect_fun_call)]
#![allow(clippy::to_string_in_format_args)]
/// Checks implementation of the `EXPECT_FUN_CALL` lint

View File

@ -1,5 +1,5 @@
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:28:26
--> $DIR/expect_fun_call.rs:29:26
|
LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
@ -7,67 +7,67 @@ LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code
= note: `-D clippy::expect-fun-call` implied by `-D warnings`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:31:26
--> $DIR/expect_fun_call.rs:32:26
|
LL | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:41:25
--> $DIR/expect_fun_call.rs:42:25
|
LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:44:25
--> $DIR/expect_fun_call.rs:45:25
|
LL | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:56:17
--> $DIR/expect_fun_call.rs:57:17
|
LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:77:21
--> $DIR/expect_fun_call.rs:78:21
|
LL | Some("foo").expect(&get_string());
| ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:78:21
--> $DIR/expect_fun_call.rs:79:21
|
LL | Some("foo").expect(get_string().as_ref());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:79:21
--> $DIR/expect_fun_call.rs:80:21
|
LL | Some("foo").expect(get_string().as_str());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:81:21
--> $DIR/expect_fun_call.rs:82:21
|
LL | Some("foo").expect(get_static_str());
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:82:21
--> $DIR/expect_fun_call.rs:83:21
|
LL | Some("foo").expect(get_non_static_str(&0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:86:16
--> $DIR/expect_fun_call.rs:87:16
|
LL | Some(true).expect(&format!("key {}, {}", 1, 2));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
error: use of `expect` followed by a function call
--> $DIR/expect_fun_call.rs:92:17
--> $DIR/expect_fun_call.rs:93:17
|
LL | opt_ref.expect(&format!("{:?}", opt_ref));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))`

View File

@ -183,3 +183,67 @@ struct WrapperMulti<T, U> {
i: T,
j: U,
}
mod issue6312 {
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
// do not lint: type implements `Drop` but not all fields are `Copy`
#[derive(Clone, Default)]
pub struct ImplDropNotAllCopy {
name: String,
delay_data_sync: Arc<AtomicBool>,
}
impl Drop for ImplDropNotAllCopy {
fn drop(&mut self) {
self.close()
}
}
impl ImplDropNotAllCopy {
fn new(name: &str) -> Self {
let mut f = ImplDropNotAllCopy::default();
f.name = name.to_owned();
f
}
fn close(&self) {}
}
// lint: type implements `Drop` and all fields are `Copy`
#[derive(Clone, Default)]
pub struct ImplDropAllCopy {
name: usize,
delay_data_sync: bool,
}
impl Drop for ImplDropAllCopy {
fn drop(&mut self) {
self.close()
}
}
impl ImplDropAllCopy {
fn new(name: &str) -> Self {
let mut f = ImplDropAllCopy::default();
f.name = name.len();
f
}
fn close(&self) {}
}
// lint: type does not implement `Drop` though all fields are `Copy`
#[derive(Clone, Default)]
pub struct NoDropAllCopy {
name: usize,
delay_data_sync: bool,
}
impl NoDropAllCopy {
fn new(name: &str) -> Self {
let mut f = NoDropAllCopy::default();
f.name = name.len();
f
}
}
}

View File

@ -107,5 +107,29 @@ note: consider initializing the variable with `WrapperMulti::<i32, i64> { i: 42,
LL | let mut a: WrapperMulti<i32, i64> = Default::default();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 9 previous errors
error: field assignment outside of initializer for an instance created with Default::default()
--> $DIR/field_reassign_with_default.rs:229:13
|
LL | f.name = name.len();
| ^^^^^^^^^^^^^^^^^^^^
|
note: consider initializing the variable with `issue6312::ImplDropAllCopy { name: name.len(), ..Default::default() }` and removing relevant reassignments
--> $DIR/field_reassign_with_default.rs:228:13
|
LL | let mut f = ImplDropAllCopy::default();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: field assignment outside of initializer for an instance created with Default::default()
--> $DIR/field_reassign_with_default.rs:245:13
|
LL | f.name = name.len();
| ^^^^^^^^^^^^^^^^^^^^
|
note: consider initializing the variable with `issue6312::NoDropAllCopy { name: name.len(), ..Default::default() }` and removing relevant reassignments
--> $DIR/field_reassign_with_default.rs:244:13
|
LL | let mut f = NoDropAllCopy::default();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 11 previous errors

View File

@ -0,0 +1,76 @@
#![warn(clippy::fn_to_numeric_cast_any)]
#![allow(clippy::fn_to_numeric_cast, clippy::fn_to_numeric_cast_with_truncation)]
fn foo() -> u8 {
0
}
fn generic_foo<T>(x: T) -> T {
x
}
trait Trait {
fn static_method() -> u32 {
2
}
}
struct Struct;
impl Trait for Struct {}
fn fn_pointer_to_integer() {
let _ = foo as i8;
let _ = foo as i16;
let _ = foo as i32;
let _ = foo as i64;
let _ = foo as i128;
let _ = foo as isize;
let _ = foo as u8;
let _ = foo as u16;
let _ = foo as u32;
let _ = foo as u64;
let _ = foo as u128;
let _ = foo as usize;
}
fn static_method_to_integer() {
let _ = Struct::static_method as usize;
}
fn fn_with_fn_arg(f: fn(i32) -> u32) -> usize {
f as usize
}
fn fn_with_generic_static_trait_method<T: Trait>() -> usize {
T::static_method as usize
}
fn closure_to_fn_to_integer() {
let clos = |x| x * 2_u32;
let _ = (clos as fn(u32) -> u32) as usize;
}
fn fn_to_raw_ptr() {
let _ = foo as *const ();
}
fn cast_fn_to_self() {
// Casting to the same function pointer type should be permitted.
let _ = foo as fn() -> u8;
}
fn cast_generic_to_concrete() {
// Casting to a more concrete function pointer type should be permitted.
let _ = generic_foo as fn(usize) -> usize;
}
fn cast_closure_to_fn() {
// Casting a closure to a function pointer should be permitted.
let id = |x| x;
let _ = id as fn(usize) -> usize;
}
fn main() {}

View File

@ -0,0 +1,106 @@
error: casting function pointer `foo` to `i8`
--> $DIR/fn_to_numeric_cast_any.rs:23:13
|
LL | let _ = foo as i8;
| ^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i8`
|
= note: `-D clippy::fn-to-numeric-cast-any` implied by `-D warnings`
error: casting function pointer `foo` to `i16`
--> $DIR/fn_to_numeric_cast_any.rs:24:13
|
LL | let _ = foo as i16;
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i16`
error: casting function pointer `foo` to `i32`
--> $DIR/fn_to_numeric_cast_any.rs:25:13
|
LL | let _ = foo as i32;
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i32`
error: casting function pointer `foo` to `i64`
--> $DIR/fn_to_numeric_cast_any.rs:26:13
|
LL | let _ = foo as i64;
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i64`
error: casting function pointer `foo` to `i128`
--> $DIR/fn_to_numeric_cast_any.rs:27:13
|
LL | let _ = foo as i128;
| ^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as i128`
error: casting function pointer `foo` to `isize`
--> $DIR/fn_to_numeric_cast_any.rs:28:13
|
LL | let _ = foo as isize;
| ^^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as isize`
error: casting function pointer `foo` to `u8`
--> $DIR/fn_to_numeric_cast_any.rs:30:13
|
LL | let _ = foo as u8;
| ^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u8`
error: casting function pointer `foo` to `u16`
--> $DIR/fn_to_numeric_cast_any.rs:31:13
|
LL | let _ = foo as u16;
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u16`
error: casting function pointer `foo` to `u32`
--> $DIR/fn_to_numeric_cast_any.rs:32:13
|
LL | let _ = foo as u32;
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u32`
error: casting function pointer `foo` to `u64`
--> $DIR/fn_to_numeric_cast_any.rs:33:13
|
LL | let _ = foo as u64;
| ^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u64`
error: casting function pointer `foo` to `u128`
--> $DIR/fn_to_numeric_cast_any.rs:34:13
|
LL | let _ = foo as u128;
| ^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as u128`
error: casting function pointer `foo` to `usize`
--> $DIR/fn_to_numeric_cast_any.rs:35:13
|
LL | let _ = foo as usize;
| ^^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as usize`
error: casting function pointer `Struct::static_method` to `usize`
--> $DIR/fn_to_numeric_cast_any.rs:39:13
|
LL | let _ = Struct::static_method as usize;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `Struct::static_method() as usize`
error: casting function pointer `f` to `usize`
--> $DIR/fn_to_numeric_cast_any.rs:43:5
|
LL | f as usize
| ^^^^^^^^^^ help: did you mean to invoke the function?: `f() as usize`
error: casting function pointer `T::static_method` to `usize`
--> $DIR/fn_to_numeric_cast_any.rs:47:5
|
LL | T::static_method as usize
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `T::static_method() as usize`
error: casting function pointer `(clos as fn(u32) -> u32)` to `usize`
--> $DIR/fn_to_numeric_cast_any.rs:53:13
|
LL | let _ = (clos as fn(u32) -> u32) as usize;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `(clos as fn(u32) -> u32)() as usize`
error: casting function pointer `foo` to `*const ()`
--> $DIR/fn_to_numeric_cast_any.rs:57:13
|
LL | let _ = foo as *const ();
| ^^^^^^^^^^^^^^^^ help: did you mean to invoke the function?: `foo() as *const ()`
error: aborting due to 17 previous errors

View File

@ -1,6 +1,6 @@
// run-rustfix
#![allow(clippy::print_literal, clippy::redundant_clone)]
#![allow(clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args)]
#![warn(clippy::useless_format)]
struct Foo(pub String);

View File

@ -1,6 +1,6 @@
// run-rustfix
#![allow(clippy::print_literal, clippy::redundant_clone)]
#![allow(clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args)]
#![warn(clippy::useless_format)]
struct Foo(pub String);

View File

@ -0,0 +1,105 @@
// run-rustfix
#![allow(unreachable_code)]
#![allow(unused_macros)]
#![allow(unused_variables)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::eq_op)]
#![warn(clippy::to_string_in_format_args)]
use std::io::{stdout, Write};
use std::ops::Deref;
use std::panic::Location;
struct Somewhere;
impl ToString for Somewhere {
fn to_string(&self) -> String {
String::from("somewhere")
}
}
struct X(u32);
impl Deref for X {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
struct Y<'a>(&'a X);
impl<'a> Deref for Y<'a> {
type Target = &'a X;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct Z(u32);
impl Deref for Z {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
impl std::fmt::Display for Z {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Z")
}
}
macro_rules! my_macro {
() => {
// here be dragons, do not enter (or lint)
println!("error: something failed at {}", Location::caller().to_string());
};
}
macro_rules! my_other_macro {
() => {
Location::caller().to_string()
};
}
fn main() {
let x = &X(1);
let x_ref = &x;
let _ = format!("error: something failed at {}", Location::caller());
let _ = write!(
stdout(),
"error: something failed at {}",
Location::caller()
);
let _ = writeln!(
stdout(),
"error: something failed at {}",
Location::caller()
);
print!("error: something failed at {}", Location::caller());
println!("error: something failed at {}", Location::caller());
eprint!("error: something failed at {}", Location::caller());
eprintln!("error: something failed at {}", Location::caller());
let _ = format_args!("error: something failed at {}", Location::caller());
assert!(true, "error: something failed at {}", Location::caller());
assert_eq!(0, 0, "error: something failed at {}", Location::caller());
assert_ne!(0, 0, "error: something failed at {}", Location::caller());
panic!("error: something failed at {}", Location::caller());
println!("{}", *X(1));
println!("{}", ***Y(&X(1)));
println!("{}", Z(1));
println!("{}", **x);
println!("{}", ***x_ref);
println!("error: something failed at {}", Somewhere.to_string());
println!("{} and again {0}", x.to_string());
my_macro!();
println!("error: something failed at {}", my_other_macro!());
}

View File

@ -0,0 +1,105 @@
// run-rustfix
#![allow(unreachable_code)]
#![allow(unused_macros)]
#![allow(unused_variables)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::eq_op)]
#![warn(clippy::to_string_in_format_args)]
use std::io::{stdout, Write};
use std::ops::Deref;
use std::panic::Location;
struct Somewhere;
impl ToString for Somewhere {
fn to_string(&self) -> String {
String::from("somewhere")
}
}
struct X(u32);
impl Deref for X {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
struct Y<'a>(&'a X);
impl<'a> Deref for Y<'a> {
type Target = &'a X;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct Z(u32);
impl Deref for Z {
type Target = u32;
fn deref(&self) -> &u32 {
&self.0
}
}
impl std::fmt::Display for Z {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Z")
}
}
macro_rules! my_macro {
() => {
// here be dragons, do not enter (or lint)
println!("error: something failed at {}", Location::caller().to_string());
};
}
macro_rules! my_other_macro {
() => {
Location::caller().to_string()
};
}
fn main() {
let x = &X(1);
let x_ref = &x;
let _ = format!("error: something failed at {}", Location::caller().to_string());
let _ = write!(
stdout(),
"error: something failed at {}",
Location::caller().to_string()
);
let _ = writeln!(
stdout(),
"error: something failed at {}",
Location::caller().to_string()
);
print!("error: something failed at {}", Location::caller().to_string());
println!("error: something failed at {}", Location::caller().to_string());
eprint!("error: something failed at {}", Location::caller().to_string());
eprintln!("error: something failed at {}", Location::caller().to_string());
let _ = format_args!("error: something failed at {}", Location::caller().to_string());
assert!(true, "error: something failed at {}", Location::caller().to_string());
assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
panic!("error: something failed at {}", Location::caller().to_string());
println!("{}", X(1).to_string());
println!("{}", Y(&X(1)).to_string());
println!("{}", Z(1).to_string());
println!("{}", x.to_string());
println!("{}", x_ref.to_string());
println!("error: something failed at {}", Somewhere.to_string());
println!("{} and again {0}", x.to_string());
my_macro!();
println!("error: something failed at {}", my_other_macro!());
}

View File

@ -0,0 +1,106 @@
error: `to_string` applied to a type that implements `Display` in `format!` args
--> $DIR/format_args.rs:75:72
|
LL | let _ = format!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
|
= note: `-D clippy::to-string-in-format-args` implied by `-D warnings`
error: `to_string` applied to a type that implements `Display` in `write!` args
--> $DIR/format_args.rs:79:27
|
LL | Location::caller().to_string()
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `writeln!` args
--> $DIR/format_args.rs:84:27
|
LL | Location::caller().to_string()
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `print!` args
--> $DIR/format_args.rs:86:63
|
LL | print!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:87:65
|
LL | println!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `eprint!` args
--> $DIR/format_args.rs:88:64
|
LL | eprint!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `eprintln!` args
--> $DIR/format_args.rs:89:66
|
LL | eprintln!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `format_args!` args
--> $DIR/format_args.rs:90:77
|
LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert!` args
--> $DIR/format_args.rs:91:70
|
LL | assert!(true, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert_eq!` args
--> $DIR/format_args.rs:92:73
|
LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert_ne!` args
--> $DIR/format_args.rs:93:73
|
LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `panic!` args
--> $DIR/format_args.rs:94:63
|
LL | panic!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:95:20
|
LL | println!("{}", X(1).to_string());
| ^^^^^^^^^^^^^^^^ help: use this: `*X(1)`
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:96:20
|
LL | println!("{}", Y(&X(1)).to_string());
| ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))`
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:97:24
|
LL | println!("{}", Z(1).to_string());
| ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:98:20
|
LL | println!("{}", x.to_string());
| ^^^^^^^^^^^^^ help: use this: `**x`
error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:99:20
|
LL | println!("{}", x_ref.to_string());
| ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
error: aborting due to 17 previous errors

View File

@ -0,0 +1,60 @@
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::eq_op)]
#![warn(clippy::format_in_format_args)]
#![warn(clippy::to_string_in_format_args)]
use std::io::{stdout, Error, ErrorKind, Write};
use std::ops::Deref;
use std::panic::Location;
macro_rules! my_macro {
() => {
// here be dragons, do not enter (or lint)
println!("error: {}", format!("something failed at {}", Location::caller()));
};
}
macro_rules! my_other_macro {
() => {
format!("something failed at {}", Location::caller())
};
}
fn main() {
let error = Error::new(ErrorKind::Other, "bad thing");
let x = 'x';
println!("error: {}", format!("something failed at {}", Location::caller()));
println!("{}: {}", error, format!("something failed at {}", Location::caller()));
println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
println!("{{}}: {}", format!("something failed at {}", Location::caller()));
println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
println!("error: {}", format!("something failed at {} {0}", Location::caller()));
let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
let _ = write!(
stdout(),
"error: {}",
format!("something failed at {}", Location::caller())
);
let _ = writeln!(
stdout(),
"error: {}",
format!("something failed at {}", Location::caller())
);
print!("error: {}", format!("something failed at {}", Location::caller()));
eprint!("error: {}", format!("something failed at {}", Location::caller()));
eprintln!("error: {}", format!("something failed at {}", Location::caller()));
let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
panic!("error: {}", format!("something failed at {}", Location::caller()));
println!("error: {}", format_args!("something failed at {}", Location::caller()));
println!("error: {:>70}", format!("something failed at {}", Location::caller()));
println!("error: {} {0}", format!("something failed at {}", Location::caller()));
println!("{} and again {0}", format!("hi {}", x));
my_macro!();
println!("error: {}", my_other_macro!());
}

View File

@ -0,0 +1,175 @@
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:27:5
|
LL | println!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::format-in-format-args` implied by `-D warnings`
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:28:5
|
LL | println!("{}: {}", error, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:29:5
|
LL | println!("{:?}: {}", error, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:30:5
|
LL | println!("{{}}: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:31:5
|
LL | println!(r#"error: "{}""#, format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:32:5
|
LL | println!("error: {}", format!(r#"something failed at "{}""#, Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `println!` args
--> $DIR/format_args_unfixable.rs:33:5
|
LL | println!("error: {}", format!("something failed at {} {0}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `println!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `format!` args
--> $DIR/format_args_unfixable.rs:34:13
|
LL | let _ = format!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `format!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `write!` args
--> $DIR/format_args_unfixable.rs:35:13
|
LL | let _ = write!(
| _____________^
LL | | stdout(),
LL | | "error: {}",
LL | | format!("something failed at {}", Location::caller())
LL | | );
| |_____^
|
= help: combine the `format!(..)` arguments with the outer `write!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `writeln!` args
--> $DIR/format_args_unfixable.rs:40:13
|
LL | let _ = writeln!(
| _____________^
LL | | stdout(),
LL | | "error: {}",
LL | | format!("something failed at {}", Location::caller())
LL | | );
| |_____^
|
= help: combine the `format!(..)` arguments with the outer `writeln!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `print!` args
--> $DIR/format_args_unfixable.rs:45:5
|
LL | print!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `print!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `eprint!` args
--> $DIR/format_args_unfixable.rs:46:5
|
LL | eprint!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `eprint!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `eprintln!` args
--> $DIR/format_args_unfixable.rs:47:5
|
LL | eprintln!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `eprintln!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `format_args!` args
--> $DIR/format_args_unfixable.rs:48:13
|
LL | let _ = format_args!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `format_args!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `assert!` args
--> $DIR/format_args_unfixable.rs:49:5
|
LL | assert!(true, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `assert!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `assert_eq!` args
--> $DIR/format_args_unfixable.rs:50:5
|
LL | assert_eq!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `assert_eq!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `assert_ne!` args
--> $DIR/format_args_unfixable.rs:51:5
|
LL | assert_ne!(0, 0, "error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `assert_ne!(..)` call
= help: or consider changing `format!` to `format_args!`
error: `format!` in `panic!` args
--> $DIR/format_args_unfixable.rs:52:5
|
LL | panic!("error: {}", format!("something failed at {}", Location::caller()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: combine the `format!(..)` arguments with the outer `panic!(..)` call
= help: or consider changing `format!` to `format_args!`
error: aborting due to 18 previous errors

View File

@ -157,4 +157,12 @@ fn main() {
if i_64 != 0 {
i_64 -= 1;
}
// issue #7831
// No Lint
if u_32 > 0 {
u_32 -= 1;
} else {
println!("side effect");
}
}

View File

@ -203,4 +203,12 @@ fn main() {
if i_64 != 0 {
i_64 -= 1;
}
// issue #7831
// No Lint
if u_32 > 0 {
u_32 -= 1;
} else {
println!("side effect");
}
}

View File

@ -110,23 +110,6 @@ LL | | _ => false,
LL | | };
| |_________^ help: try this: `matches!(&val, &Some(ref _a))`
error: you don't need to add `&` to both the expression and the patterns
--> $DIR/match_expr_like_matches_macro.rs:166:20
|
LL | let _res = match &val {
| ____________________^
LL | | &Some(ref _a) => true,
LL | | _ => false,
LL | | };
| |_________^
|
= note: `-D clippy::match-ref-pats` implied by `-D warnings`
help: try
|
LL ~ let _res = match val {
LL ~ Some(ref _a) => true,
|
error: match expression looks like `matches!` macro
--> $DIR/match_expr_like_matches_macro.rs:178:20
|
@ -137,21 +120,5 @@ LL | | _ => false,
LL | | };
| |_________^ help: try this: `matches!(&val, &Some(ref _a))`
error: you don't need to add `&` to both the expression and the patterns
--> $DIR/match_expr_like_matches_macro.rs:178:20
|
LL | let _res = match &val {
| ____________________^
LL | | &Some(ref _a) => true,
LL | | _ => false,
LL | | };
| |_________^
|
help: try
|
LL ~ let _res = match val {
LL ~ Some(ref _a) => true,
|
error: aborting due to 14 previous errors
error: aborting due to 12 previous errors

View File

@ -10,98 +10,95 @@ fn overlapping() {
const FOO: u64 = 2;
match 42 {
0..=10 => println!("0 ... 10"),
0..=11 => println!("0 ... 11"),
0..=10 => println!("0..=10"),
0..=11 => println!("0..=11"),
_ => (),
}
match 42 {
0..=5 => println!("0 ... 5"),
6..=7 => println!("6 ... 7"),
FOO..=11 => println!("0 ... 11"),
0..=5 => println!("0..=5"),
6..=7 => println!("6..=7"),
FOO..=11 => println!("FOO..=11"),
_ => (),
}
match 42 {
2 => println!("2"),
0..=5 => println!("0 ... 5"),
0..=5 => println!("0..=5"),
_ => (),
}
match 42 {
2 => println!("2"),
0..=2 => println!("0 ... 2"),
0..=2 => println!("0..=2"),
_ => (),
}
match 42 {
0..=10 => println!("0 ... 10"),
11..=50 => println!("11 ... 50"),
0..=10 => println!("0..=10"),
11..=50 => println!("11..=50"),
_ => (),
}
match 42 {
2 => println!("2"),
0..2 => println!("0 .. 2"),
0..2 => println!("0..2"),
_ => (),
}
match 42 {
0..10 => println!("0 .. 10"),
10..50 => println!("10 .. 50"),
0..10 => println!("0..10"),
10..50 => println!("10..50"),
_ => (),
}
match 42 {
0..11 => println!("0 .. 11"),
0..=11 => println!("0 ... 11"),
0..11 => println!("0..11"),
0..=11 => println!("0..=11"),
_ => (),
}
match 42 {
5..7 => println!("5 .. 7"),
0..10 => println!("0 .. 10"),
5..7 => println!("5..7"),
0..10 => println!("0..10"),
_ => (),
}
match 42 {
5..10 => println!("5 .. 10"),
0..=10 => println!("0 ... 10"),
5..10 => println!("5..10"),
0..=10 => println!("0..=10"),
_ => (),
}
match 42 {
0..14 => println!("0 .. 14"),
5..10 => println!("5 .. 10"),
0..14 => println!("0..14"),
5..10 => println!("5..10"),
_ => (),
}
match 42 {
5..14 => println!("5 .. 14"),
0..=10 => println!("0 ... 10"),
5..14 => println!("5..14"),
0..=10 => println!("0..=10"),
_ => (),
}
match 42 {
0..7 => println!("0 .. 7"),
0..=10 => println!("0 ... 10"),
_ => (),
}
/*
// FIXME(JohnTitor): uncomment this once rustfmt knows half-open patterns
match 42 {
0.. => println!("0 .. 42"),
3.. => println!("3 .. 42"),
0..7 => println!("0..7"),
0..=10 => println!("0..=10"),
_ => (),
}
match 42 {
..=23 => println!("0 ... 23"),
..26 => println!("0 .. 26"),
3.. => println!("3.."),
0.. => println!("0.."),
_ => (),
}
match 42 {
..=23 => println!("..=23"),
..26 => println!("..26"),
_ => (),
}
*/
if let None = Some(42) {
// nothing

View File

@ -1,63 +1,75 @@
error: some ranges overlap
--> $DIR/match_overlapping_arm.rs:13:9
|
LL | 0..=10 => println!("0 ... 10"),
LL | 0..=10 => println!("0..=10"),
| ^^^^^^
|
= note: `-D clippy::match-overlapping-arm` implied by `-D warnings`
note: overlaps with this
--> $DIR/match_overlapping_arm.rs:14:9
|
LL | 0..=11 => println!("0 ... 11"),
LL | 0..=11 => println!("0..=11"),
| ^^^^^^
error: some ranges overlap
--> $DIR/match_overlapping_arm.rs:19:9
|
LL | 0..=5 => println!("0 ... 5"),
LL | 0..=5 => println!("0..=5"),
| ^^^^^
|
note: overlaps with this
--> $DIR/match_overlapping_arm.rs:21:9
|
LL | FOO..=11 => println!("0 ... 11"),
LL | FOO..=11 => println!("FOO..=11"),
| ^^^^^^^^
error: some ranges overlap
--> $DIR/match_overlapping_arm.rs:56:9
|
LL | 0..11 => println!("0 .. 11"),
LL | 0..11 => println!("0..11"),
| ^^^^^
|
note: overlaps with this
--> $DIR/match_overlapping_arm.rs:57:9
|
LL | 0..=11 => println!("0 ... 11"),
LL | 0..=11 => println!("0..=11"),
| ^^^^^^
error: some ranges overlap
--> $DIR/match_overlapping_arm.rs:81:9
|
LL | 0..=10 => println!("0 ... 10"),
LL | 0..=10 => println!("0..=10"),
| ^^^^^^
|
note: overlaps with this
--> $DIR/match_overlapping_arm.rs:80:9
|
LL | 5..14 => println!("5 .. 14"),
LL | 5..14 => println!("5..14"),
| ^^^^^
error: some ranges overlap
--> $DIR/match_overlapping_arm.rs:86:9
|
LL | 0..7 => println!("0 .. 7"),
LL | 0..7 => println!("0..7"),
| ^^^^
|
note: overlaps with this
--> $DIR/match_overlapping_arm.rs:87:9
|
LL | 0..=10 => println!("0 ... 10"),
LL | 0..=10 => println!("0..=10"),
| ^^^^^^
error: aborting due to 5 previous errors
error: some ranges overlap
--> $DIR/match_overlapping_arm.rs:98:9
|
LL | ..=23 => println!("..=23"),
| ^^^^^
|
note: overlaps with this
--> $DIR/match_overlapping_arm.rs:99:9
|
LL | ..26 => println!("..26"),
| ^^^^
error: aborting due to 6 previous errors

View File

@ -72,4 +72,46 @@ fn ice_3719() {
}
}
mod issue_7740 {
macro_rules! foobar_variant(
($idx:expr) => (FooBar::get($idx).unwrap())
);
enum FooBar {
Foo,
Bar,
FooBar,
BarFoo,
}
impl FooBar {
fn get(idx: u8) -> Option<&'static Self> {
match idx {
0 => Some(&FooBar::Foo),
1 => Some(&FooBar::Bar),
2 => Some(&FooBar::FooBar),
3 => Some(&FooBar::BarFoo),
_ => None,
}
}
}
fn issue_7740() {
// Issue #7740
match foobar_variant!(0) {
&FooBar::Foo => println!("Foo"),
&FooBar::Bar => println!("Bar"),
&FooBar::FooBar => println!("FooBar"),
_ => println!("Wild"),
}
// This shouldn't trigger
if let &FooBar::BarFoo = foobar_variant!(3) {
println!("BarFoo");
} else {
println!("Wild");
}
}
}
fn main() {}

View File

@ -15,21 +15,6 @@ LL ~ Some(v) => println!("{:?}", v),
LL ~ None => println!("none"),
|
error: you don't need to add `&` to all patterns
--> $DIR/match_ref_pats.rs:18:5
|
LL | / match tup {
LL | | &(v, 1) => println!("{}", v),
LL | | _ => println!("none"),
LL | | }
| |_____^
|
help: instead of prefixing all patterns with `&`, you can dereference the expression
|
LL ~ match *tup {
LL ~ (v, 1) => println!("{}", v),
|
error: you don't need to add `&` to both the expression and the patterns
--> $DIR/match_ref_pats.rs:24:5
|
@ -54,52 +39,30 @@ LL | if let &None = a {
|
= note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
error: you don't need to add `&` to all patterns
--> $DIR/match_ref_pats.rs:36:5
|
LL | / if let &None = a {
LL | | println!("none");
LL | | }
| |_____^
|
help: instead of prefixing all patterns with `&`, you can dereference the expression
|
LL | if let None = *a {
| ~~~~ ~~
error: redundant pattern matching, consider using `is_none()`
--> $DIR/match_ref_pats.rs:41:12
|
LL | if let &None = &b {
| -------^^^^^----- help: try this: `if b.is_none()`
error: you don't need to add `&` to both the expression and the patterns
--> $DIR/match_ref_pats.rs:41:5
|
LL | / if let &None = &b {
LL | | println!("none");
LL | | }
| |_____^
|
help: try
|
LL | if let None = b {
| ~~~~ ~
error: you don't need to add `&` to all patterns
--> $DIR/match_ref_pats.rs:68:9
--> $DIR/match_ref_pats.rs:101:9
|
LL | / match foo_variant!(0) {
LL | | &Foo::A => println!("A"),
LL | / match foobar_variant!(0) {
LL | | &FooBar::Foo => println!("Foo"),
LL | | &FooBar::Bar => println!("Bar"),
LL | | &FooBar::FooBar => println!("FooBar"),
LL | | _ => println!("Wild"),
LL | | }
| |_________^
|
help: instead of prefixing all patterns with `&`, you can dereference the expression
|
LL ~ match *foo_variant!(0) {
LL ~ Foo::A => println!("A"),
LL ~ match *foobar_variant!(0) {
LL ~ FooBar::Foo => println!("Foo"),
LL ~ FooBar::Bar => println!("Bar"),
LL ~ FooBar::FooBar => println!("FooBar"),
|
error: aborting due to 8 previous errors
error: aborting due to 5 previous errors

View File

@ -0,0 +1,98 @@
#![warn(clippy::match_str_case_mismatch)]
// Valid
fn as_str_match() {
let var = "BAR";
match var.to_ascii_lowercase().as_str() {
"foo" => {},
"bar" => {},
_ => {},
}
}
fn addrof_unary_match() {
let var = "BAR";
match &*var.to_ascii_lowercase() {
"foo" => {},
"bar" => {},
_ => {},
}
}
fn alternating_chain() {
let var = "BAR";
match &*var
.to_ascii_lowercase()
.to_uppercase()
.to_lowercase()
.to_ascii_uppercase()
{
"FOO" => {},
"BAR" => {},
_ => {},
}
}
fn unrelated_method() {
struct Item {
a: String,
}
impl Item {
#[allow(clippy::wrong_self_convention)]
fn to_lowercase(self) -> String {
self.a
}
}
let item = Item { a: String::from("BAR") };
match &*item.to_lowercase() {
"FOO" => {},
"BAR" => {},
_ => {},
}
}
// Invalid
fn as_str_match_mismatch() {
let var = "BAR";
match var.to_ascii_lowercase().as_str() {
"foo" => {},
"Bar" => {},
_ => {},
}
}
fn addrof_unary_match_mismatch() {
let var = "BAR";
match &*var.to_ascii_lowercase() {
"foo" => {},
"Bar" => {},
_ => {},
}
}
fn alternating_chain_mismatch() {
let var = "BAR";
match &*var
.to_ascii_lowercase()
.to_uppercase()
.to_lowercase()
.to_ascii_uppercase()
{
"FOO" => {},
"bAR" => {},
_ => {},
}
}
fn main() {}

View File

@ -0,0 +1,36 @@
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:68:9
|
LL | "Bar" => {},
| ^^^^^
|
= note: `-D clippy::match-str-case-mismatch` implied by `-D warnings`
help: consider changing the case of this arm to respect `to_ascii_lowercase`
|
LL | "bar" => {},
| ~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:78:9
|
LL | "Bar" => {},
| ^^^^^
|
help: consider changing the case of this arm to respect `to_ascii_lowercase`
|
LL | "bar" => {},
| ~~~~~
error: this `match` arm has a differing case than its expression
--> $DIR/match_str_case_mismatch.rs:93:9
|
LL | "bAR" => {},
| ^^^^^
|
help: consider changing the case of this arm to respect `to_ascii_uppercase`
|
LL | "BAR" => {},
| ~~~~~
error: aborting due to 3 previous errors

View File

@ -1,6 +1,11 @@
// aux-build:macro_rules.rs
#![allow(unused, clippy::no_effect, clippy::unnecessary_operation)]
#![warn(clippy::mut_mut)]
#[macro_use]
extern crate macro_rules;
fn fun(x: &mut &mut u32) -> bool {
**x > 0
}
@ -47,3 +52,8 @@ fn issue939() {
println!(":{}", arg);
}
}
fn issue6922() {
// do not lint from an external macro
mut_mut!();
}

View File

@ -1,5 +1,5 @@
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:4:11
--> $DIR/mut_mut.rs:9:11
|
LL | fn fun(x: &mut &mut u32) -> bool {
| ^^^^^^^^^^^^^
@ -7,13 +7,13 @@ LL | fn fun(x: &mut &mut u32) -> bool {
= note: `-D clippy::mut-mut` implied by `-D warnings`
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:20:17
--> $DIR/mut_mut.rs:25:17
|
LL | let mut x = &mut &mut 1u32;
| ^^^^^^^^^^^^^^
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:14:9
--> $DIR/mut_mut.rs:19:9
|
LL | &mut $p
| ^^^^^^^
@ -24,37 +24,37 @@ LL | let mut z = mut_ptr!(&mut 3u32);
= note: this error originates in the macro `mut_ptr` (in Nightly builds, run with -Z macro-backtrace for more info)
error: this expression mutably borrows a mutable reference. Consider reborrowing
--> $DIR/mut_mut.rs:22:21
--> $DIR/mut_mut.rs:27:21
|
LL | let mut y = &mut x;
| ^^^^^^
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:26:32
--> $DIR/mut_mut.rs:31:32
|
LL | let y: &mut &mut u32 = &mut &mut 2;
| ^^^^^^^^^^^
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:26:16
--> $DIR/mut_mut.rs:31:16
|
LL | let y: &mut &mut u32 = &mut &mut 2;
| ^^^^^^^^^^^^^
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:31:37
--> $DIR/mut_mut.rs:36:37
|
LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2;
| ^^^^^^^^^^^^^^^^
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:31:16
--> $DIR/mut_mut.rs:36:16
|
LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2;
| ^^^^^^^^^^^^^^^^^^
error: generally you want to avoid `&mut &mut _` if possible
--> $DIR/mut_mut.rs:31:21
--> $DIR/mut_mut.rs:36:21
|
LL | let y: &mut &mut &mut u32 = &mut &mut &mut 2;
| ^^^^^^^^^^^^^

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