Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
5f031561ef
139 changed files with 2370 additions and 884 deletions
|
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then;
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{AsyncCoroutineKind, Body, BodyId, ExprKind, CoroutineKind, QPath};
|
||||
use rustc_hir::{AsyncCoroutineKind, Body, BodyId, CoroutineKind, ExprKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
|
|
|
|||
|
|
@ -287,5 +287,8 @@ fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
|
|||
}
|
||||
|
||||
fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
|
||||
matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::RefCellRef | sym::RefCellRefMut))
|
||||
matches!(
|
||||
cx.tcx.get_diagnostic_name(def_id),
|
||||
Some(sym::RefCellRef | sym::RefCellRefMut)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ use clippy_utils::macros::macro_backtrace;
|
|||
use clippy_utils::ty::expr_sig;
|
||||
use clippy_utils::{get_parent_node, is_default_equivalent, path_def_id};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::Res;
|
||||
use rustc_hir::intravisit::{walk_ty, Visitor};
|
||||
use rustc_hir::{def::Res, Block, Expr, ExprKind, Local, Node, QPath, TyKind};
|
||||
use rustc_hir::{Block, Expr, ExprKind, Local, Node, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::print::with_forced_trimmed_paths;
|
||||
|
|
|
|||
|
|
@ -154,9 +154,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO,
|
||||
crate::entry::MAP_ENTRY_INFO,
|
||||
crate::enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT_INFO,
|
||||
crate::enum_variants::ENUM_VARIANT_NAMES_INFO,
|
||||
crate::enum_variants::MODULE_INCEPTION_INFO,
|
||||
crate::enum_variants::MODULE_NAME_REPETITIONS_INFO,
|
||||
crate::equatable_if_let::EQUATABLE_IF_LET_INFO,
|
||||
crate::error_impl_error::ERROR_IMPL_ERROR_INFO,
|
||||
crate::escape::BOXED_LOCAL_INFO,
|
||||
|
|
@ -226,6 +223,10 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
|
|||
crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
|
||||
crate::int_plus_one::INT_PLUS_ONE_INFO,
|
||||
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
|
||||
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
|
||||
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
|
||||
crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO,
|
||||
crate::item_name_repetitions::STRUCT_FIELD_NAMES_INFO,
|
||||
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
|
||||
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
|
||||
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::last_path_segment;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::{is_entrypoint_fn};
|
||||
use clippy_utils::is_entrypoint_fn;
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::ty::is_c_void;
|
||||
use clippy_utils::path_def_id;
|
||||
use clippy_utils::ty::is_c_void;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Expr, ExprKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -1,50 +1,104 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_in_test_function;
|
||||
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, HirId};
|
||||
use rustc_hir::{Body, GenericParam, Generics, HirId, ImplItem, ImplItemKind, TraitItem, TraitItemKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{BytePos, Span};
|
||||
|
||||
use super::IMPL_TRAIT_IN_PARAMS;
|
||||
|
||||
fn report(
|
||||
cx: &LateContext<'_>,
|
||||
param: &GenericParam<'_>,
|
||||
ident: &Ident,
|
||||
generics: &Generics<'_>,
|
||||
first_param_span: Span,
|
||||
) {
|
||||
// No generics with nested generics, and no generics like FnMut(x)
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
IMPL_TRAIT_IN_PARAMS,
|
||||
param.span,
|
||||
"`impl Trait` used as a function parameter",
|
||||
|diag| {
|
||||
if let Some(gen_span) = generics.span_for_param_suggestion() {
|
||||
// If there's already a generic param with the same bound, do not lint **this** suggestion.
|
||||
diag.span_suggestion_with_style(
|
||||
gen_span,
|
||||
"add a type parameter",
|
||||
format!(", {{ /* Generic name */ }}: {}", ¶m.name.ident().as_str()[5..]),
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
rustc_errors::SuggestionStyle::ShowAlways,
|
||||
);
|
||||
} else {
|
||||
diag.span_suggestion_with_style(
|
||||
Span::new(
|
||||
first_param_span.lo() - rustc_span::BytePos(1),
|
||||
ident.span.hi(),
|
||||
ident.span.ctxt(),
|
||||
ident.span.parent(),
|
||||
),
|
||||
"add a type parameter",
|
||||
format!("<{{ /* Generic name */ }}: {}>", ¶m.name.ident().as_str()[5..]),
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
rustc_errors::SuggestionStyle::ShowAlways,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
|
||||
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() && !is_in_test_function(cx.tcx, hir_id)
|
||||
{
|
||||
if let FnKind::ItemFn(ident, generics, _) = kind {
|
||||
if_chain! {
|
||||
if let FnKind::ItemFn(ident, generics, _) = kind;
|
||||
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
|
||||
if !is_in_test_function(cx.tcx, hir_id);
|
||||
then {
|
||||
for param in generics.params {
|
||||
if param.is_impl_trait() {
|
||||
// No generics with nested generics, and no generics like FnMut(x)
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
IMPL_TRAIT_IN_PARAMS,
|
||||
param.span,
|
||||
"'`impl Trait` used as a function parameter'",
|
||||
|diag| {
|
||||
if let Some(gen_span) = generics.span_for_param_suggestion() {
|
||||
diag.span_suggestion_with_style(
|
||||
gen_span,
|
||||
"add a type parameter",
|
||||
format!(", {{ /* Generic name */ }}: {}", ¶m.name.ident().as_str()[5..]),
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
rustc_errors::SuggestionStyle::ShowAlways,
|
||||
);
|
||||
} else {
|
||||
diag.span_suggestion_with_style(
|
||||
Span::new(
|
||||
body.params[0].span.lo() - rustc_span::BytePos(1),
|
||||
ident.span.hi(),
|
||||
ident.span.ctxt(),
|
||||
ident.span.parent(),
|
||||
),
|
||||
"add a type parameter",
|
||||
format!("<{{ /* Generic name */ }}: {}>", ¶m.name.ident().as_str()[5..]),
|
||||
rustc_errors::Applicability::HasPlaceholders,
|
||||
rustc_errors::SuggestionStyle::ShowAlways,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
report(cx, param, ident, generics, body.params[0].span);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
|
||||
if_chain! {
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
|
||||
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id());
|
||||
if let hir::ItemKind::Impl(impl_) = item.kind;
|
||||
if let hir::Impl { of_trait, .. } = *impl_;
|
||||
if of_trait.is_none();
|
||||
let body = cx.tcx.hir().body(body_id);
|
||||
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
|
||||
if !is_in_test_function(cx.tcx, impl_item.hir_id());
|
||||
then {
|
||||
for param in impl_item.generics.params {
|
||||
if param.is_impl_trait() {
|
||||
report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
|
||||
if_chain! {
|
||||
if !avoid_breaking_exported_api;
|
||||
if let TraitItemKind::Fn(_, _) = trait_item.kind;
|
||||
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id());
|
||||
// ^^ (Will always be a trait)
|
||||
if !item.vis_span.is_empty(); // Is public
|
||||
if !is_in_test_function(cx.tcx, trait_item.hir_id());
|
||||
then {
|
||||
for param in trait_item.generics.params {
|
||||
if param.is_impl_trait() {
|
||||
let sp = trait_item.ident.span.with_hi(trait_item.ident.span.hi() + BytePos(1));
|
||||
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,18 +360,26 @@ declare_clippy_lint! {
|
|||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
pub struct Functions {
|
||||
too_many_arguments_threshold: u64,
|
||||
too_many_lines_threshold: u64,
|
||||
large_error_threshold: u64,
|
||||
avoid_breaking_exported_api: bool,
|
||||
}
|
||||
|
||||
impl Functions {
|
||||
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64) -> Self {
|
||||
pub fn new(
|
||||
too_many_arguments_threshold: u64,
|
||||
too_many_lines_threshold: u64,
|
||||
large_error_threshold: u64,
|
||||
avoid_breaking_exported_api: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
too_many_arguments_threshold,
|
||||
too_many_lines_threshold,
|
||||
large_error_threshold,
|
||||
avoid_breaking_exported_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -415,6 +423,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
|
|||
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
|
||||
must_use::check_impl_item(cx, item);
|
||||
result::check_impl_item(cx, item, self.large_error_threshold);
|
||||
impl_trait_in_params::check_impl_item(cx, item);
|
||||
}
|
||||
|
||||
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
|
||||
|
|
@ -422,5 +431,6 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
|
|||
not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
|
||||
must_use::check_trait_item(cx, item);
|
||||
result::check_trait_item(cx, item, self.large_error_threshold);
|
||||
impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
//! lint on enum variants that are prefixed or suffixed by the same characters
|
||||
|
||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
|
||||
use clippy_utils::macros::span_is_local;
|
||||
use clippy_utils::source::is_present_in_source;
|
||||
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start};
|
||||
use rustc_hir::{EnumDef, Item, ItemKind, OwnerId, Variant};
|
||||
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
|
||||
use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::Span;
|
||||
|
|
@ -103,32 +104,184 @@ declare_clippy_lint! {
|
|||
style,
|
||||
"modules that have the same name as their parent module"
|
||||
}
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Detects struct fields that are prefixed or suffixed
|
||||
/// by the same characters or the name of the struct itself.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Information common to all struct fields is better represented in the struct name.
|
||||
///
|
||||
/// ### Limitations
|
||||
/// Characters with no casing will be considered when comparing prefixes/suffixes
|
||||
/// This applies to numbers and non-ascii characters without casing
|
||||
/// e.g. `foo1` and `foo2` is considered to have different prefixes
|
||||
/// (the prefixes are `foo1` and `foo2` respectively), as also `bar螃`, `bar蟹`
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Cake {
|
||||
/// cake_sugar: u8,
|
||||
/// cake_flour: u8,
|
||||
/// cake_eggs: u8
|
||||
/// }
|
||||
/// ```
|
||||
/// Use instead:
|
||||
/// ```rust
|
||||
/// struct Cake {
|
||||
/// sugar: u8,
|
||||
/// flour: u8,
|
||||
/// eggs: u8
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.75.0"]
|
||||
pub STRUCT_FIELD_NAMES,
|
||||
pedantic,
|
||||
"structs where all fields share a prefix/postfix or contain the name of the struct"
|
||||
}
|
||||
|
||||
pub struct EnumVariantNames {
|
||||
pub struct ItemNameRepetitions {
|
||||
modules: Vec<(Symbol, String, OwnerId)>,
|
||||
threshold: u64,
|
||||
enum_threshold: u64,
|
||||
struct_threshold: u64,
|
||||
avoid_breaking_exported_api: bool,
|
||||
allow_private_module_inception: bool,
|
||||
}
|
||||
|
||||
impl EnumVariantNames {
|
||||
impl ItemNameRepetitions {
|
||||
#[must_use]
|
||||
pub fn new(threshold: u64, avoid_breaking_exported_api: bool, allow_private_module_inception: bool) -> Self {
|
||||
pub fn new(
|
||||
enum_threshold: u64,
|
||||
struct_threshold: u64,
|
||||
avoid_breaking_exported_api: bool,
|
||||
allow_private_module_inception: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
modules: Vec::new(),
|
||||
threshold,
|
||||
enum_threshold,
|
||||
struct_threshold,
|
||||
avoid_breaking_exported_api,
|
||||
allow_private_module_inception,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(EnumVariantNames => [
|
||||
impl_lint_pass!(ItemNameRepetitions => [
|
||||
ENUM_VARIANT_NAMES,
|
||||
STRUCT_FIELD_NAMES,
|
||||
MODULE_NAME_REPETITIONS,
|
||||
MODULE_INCEPTION
|
||||
]);
|
||||
|
||||
#[must_use]
|
||||
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
|
||||
prefixes.iter().all(|p| p == &"" || p == &"_")
|
||||
}
|
||||
|
||||
fn check_fields(cx: &LateContext<'_>, threshold: u64, item: &Item<'_>, fields: &[FieldDef<'_>]) {
|
||||
if (fields.len() as u64) < threshold {
|
||||
return;
|
||||
}
|
||||
|
||||
check_struct_name_repetition(cx, item, fields);
|
||||
|
||||
// if the SyntaxContext of the identifiers of the fields and struct differ dont lint them.
|
||||
// this prevents linting in macros in which the location of the field identifier names differ
|
||||
if !fields.iter().all(|field| item.ident.span.eq_ctxt(field.ident.span)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut pre: Vec<&str> = match fields.first() {
|
||||
Some(first_field) => first_field.ident.name.as_str().split('_').collect(),
|
||||
None => return,
|
||||
};
|
||||
let mut post = pre.clone();
|
||||
post.reverse();
|
||||
for field in fields {
|
||||
let field_split: Vec<&str> = field.ident.name.as_str().split('_').collect();
|
||||
if field_split.len() == 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
pre = pre
|
||||
.into_iter()
|
||||
.zip(field_split.iter())
|
||||
.take_while(|(a, b)| &a == b)
|
||||
.map(|e| e.0)
|
||||
.collect();
|
||||
post = post
|
||||
.into_iter()
|
||||
.zip(field_split.iter().rev())
|
||||
.take_while(|(a, b)| &a == b)
|
||||
.map(|e| e.0)
|
||||
.collect();
|
||||
}
|
||||
let prefix = pre.join("_");
|
||||
post.reverse();
|
||||
let postfix = match post.last() {
|
||||
Some(&"") => post.join("_") + "_",
|
||||
Some(_) | None => post.join("_"),
|
||||
};
|
||||
if fields.len() > 1 {
|
||||
let (what, value) = match (
|
||||
prefix.is_empty() || prefix.chars().all(|c| c == '_'),
|
||||
postfix.is_empty(),
|
||||
) {
|
||||
(true, true) => return,
|
||||
(false, _) => ("pre", prefix),
|
||||
(true, false) => ("post", postfix),
|
||||
};
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
STRUCT_FIELD_NAMES,
|
||||
item.span,
|
||||
&format!("all fields have the same {what}fix: `{value}`"),
|
||||
None,
|
||||
&format!("remove the {what}fixes"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_struct_name_repetition(cx: &LateContext<'_>, item: &Item<'_>, fields: &[FieldDef<'_>]) {
|
||||
let snake_name = to_snake_case(item.ident.name.as_str());
|
||||
let item_name_words: Vec<&str> = snake_name.split('_').collect();
|
||||
for field in fields {
|
||||
if field.ident.span.eq_ctxt(item.ident.span) {
|
||||
//consider linting only if the field identifier has the same SyntaxContext as the item(struct)
|
||||
let field_words: Vec<&str> = field.ident.name.as_str().split('_').collect();
|
||||
if field_words.len() >= item_name_words.len() {
|
||||
// if the field name is shorter than the struct name it cannot contain it
|
||||
if field_words.iter().zip(item_name_words.iter()).all(|(a, b)| a == b) {
|
||||
span_lint_hir(
|
||||
cx,
|
||||
STRUCT_FIELD_NAMES,
|
||||
field.hir_id,
|
||||
field.span,
|
||||
"field name starts with the struct's name",
|
||||
);
|
||||
}
|
||||
if field_words.len() > item_name_words.len() {
|
||||
// lint only if the end is not covered by the start
|
||||
if field_words
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(item_name_words.iter().rev())
|
||||
.all(|(a, b)| a == b)
|
||||
{
|
||||
span_lint_hir(
|
||||
cx,
|
||||
STRUCT_FIELD_NAMES,
|
||||
field.hir_id,
|
||||
field.span,
|
||||
"field name ends with the struct's name",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
|
||||
let name = variant.ident.name.as_str();
|
||||
let item_name_chars = item_name.chars().count();
|
||||
|
|
@ -218,35 +371,7 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
|
|||
);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
|
||||
prefixes.iter().all(|p| p == &"" || p == &"_")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn to_camel_case(item_name: &str) -> String {
|
||||
let mut s = String::new();
|
||||
let mut up = true;
|
||||
for c in item_name.chars() {
|
||||
if c.is_uppercase() {
|
||||
// we only turn snake case text into CamelCase
|
||||
return item_name.to_string();
|
||||
}
|
||||
if c == '_' {
|
||||
up = true;
|
||||
continue;
|
||||
}
|
||||
if up {
|
||||
up = false;
|
||||
s.extend(c.to_uppercase());
|
||||
} else {
|
||||
s.push(c);
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
impl LateLintPass<'_> for EnumVariantNames {
|
||||
impl LateLintPass<'_> for ItemNameRepetitions {
|
||||
fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) {
|
||||
let last = self.modules.pop();
|
||||
assert!(last.is_some());
|
||||
|
|
@ -303,9 +428,15 @@ impl LateLintPass<'_> for EnumVariantNames {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let ItemKind::Enum(ref def, _) = item.kind {
|
||||
if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id)) {
|
||||
check_variant(cx, self.threshold, def, item_name, item.span);
|
||||
if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id))
|
||||
&& span_is_local(item.span)
|
||||
{
|
||||
match item.kind {
|
||||
ItemKind::Enum(def, _) => check_variant(cx, self.enum_threshold, &def, item_name, item.span),
|
||||
ItemKind::Struct(VariantData::Struct(fields, _), _) => {
|
||||
check_fields(cx, self.struct_threshold, item, fields);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.modules.push((item.ident.name, item_camel, item.owner_id));
|
||||
|
|
@ -9,6 +9,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::{sym, Symbol};
|
||||
use std::iter;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -52,12 +53,13 @@ declare_clippy_lint! {
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// This is the opposite of the `iter_without_into_iter` lint.
|
||||
/// It looks for `IntoIterator for (&|&mut) Type` implementations without an inherent `iter` or `iter_mut` method.
|
||||
/// It looks for `IntoIterator for (&|&mut) Type` implementations without an inherent `iter` or `iter_mut` method
|
||||
/// on the type or on any of the types in its `Deref` chain.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// It's not bad, but having them is idiomatic and allows the type to be used in iterator chains
|
||||
/// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).iter()` syntax
|
||||
/// in case of ambiguity with another `Intoiterator` impl.
|
||||
/// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).into_iter()` syntax
|
||||
/// in case of ambiguity with another `IntoIterator` impl.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
|
|
@ -102,7 +104,20 @@ fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
|
|||
!matches!(ty.kind, TyKind::OpaqueDef(..))
|
||||
}
|
||||
|
||||
fn type_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
|
||||
/// Returns the deref chain of a type, starting with the type itself.
|
||||
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
|
||||
iter::successors(Some(ty), |&ty| {
|
||||
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
|
||||
&& implements_trait(cx, ty, deref_did, &[])
|
||||
{
|
||||
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
|
||||
if let Some(ty_did) = ty.ty_adt_def().map(ty::AdtDef::did) {
|
||||
cx.tcx.inherent_impls(ty_did).iter().any(|&did| {
|
||||
cx.tcx
|
||||
|
|
@ -127,7 +142,11 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
|
|||
Mutability::Mut => sym::iter_mut,
|
||||
Mutability::Not => sym::iter,
|
||||
}
|
||||
&& !type_has_inherent_method(cx, ty, expected_method_name)
|
||||
&& !deref_chain(cx, ty)
|
||||
.any(|ty| {
|
||||
// We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method
|
||||
ty.peel_refs().is_slice() || adt_has_inherent_method(cx, ty, expected_method_name)
|
||||
})
|
||||
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
|
||||
if item.ident.name == sym!(IntoIter) {
|
||||
Some(cx.tcx.hir().impl_item(item.id).expect_type().span)
|
||||
|
|
|
|||
|
|
@ -50,9 +50,6 @@ extern crate clippy_utils;
|
|||
#[macro_use]
|
||||
extern crate declare_clippy_lint;
|
||||
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clippy_utils::msrvs::Msrv;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_lint::{Lint, LintId};
|
||||
|
|
@ -121,7 +118,6 @@ mod empty_structs_with_brackets;
|
|||
mod endian_bytes;
|
||||
mod entry;
|
||||
mod enum_clike;
|
||||
mod enum_variants;
|
||||
mod equatable_if_let;
|
||||
mod error_impl_error;
|
||||
mod escape;
|
||||
|
|
@ -166,6 +162,7 @@ mod inline_fn_without_body;
|
|||
mod instant_subtraction;
|
||||
mod int_plus_one;
|
||||
mod invalid_upcast_comparisons;
|
||||
mod item_name_repetitions;
|
||||
mod items_after_statements;
|
||||
mod items_after_test_module;
|
||||
mod iter_not_returning_iterator;
|
||||
|
|
@ -362,7 +359,6 @@ mod zero_sized_map_values;
|
|||
// end lints modules, do not remove this comment, it’s used in `update_lints`
|
||||
|
||||
use crate::utils::conf::metadata::get_configuration_metadata;
|
||||
use crate::utils::conf::TryConf;
|
||||
pub use crate::utils::conf::{lookup_conf_file, Conf};
|
||||
use crate::utils::FindAll;
|
||||
|
||||
|
|
@ -374,65 +370,13 @@ use crate::utils::FindAll;
|
|||
/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
|
||||
///
|
||||
/// Used in `./src/driver.rs`.
|
||||
pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
|
||||
pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
|
||||
// NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
|
||||
let msrv = Msrv::read(&conf.msrv, sess);
|
||||
let msrv = move || msrv.clone();
|
||||
let msrv = || conf.msrv.clone();
|
||||
|
||||
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv: msrv() }));
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
|
||||
if let Ok((_, warnings)) = path {
|
||||
for warning in warnings {
|
||||
sess.warn(warning.clone());
|
||||
}
|
||||
}
|
||||
let file_name = match path {
|
||||
Ok((Some(path), _)) => path,
|
||||
Ok((None, _)) => return Conf::default(),
|
||||
Err(error) => {
|
||||
sess.err(format!("error finding Clippy's configuration file: {error}"));
|
||||
return Conf::default();
|
||||
},
|
||||
};
|
||||
|
||||
let TryConf { conf, errors, warnings } = utils::conf::read(sess, file_name);
|
||||
// all conf errors are non-fatal, we just use the default conf in case of error
|
||||
for error in errors {
|
||||
if let Some(span) = error.span {
|
||||
sess.span_err(
|
||||
span,
|
||||
format!("error reading Clippy's configuration file: {}", error.message),
|
||||
);
|
||||
} else {
|
||||
sess.err(format!(
|
||||
"error reading Clippy's configuration file `{}`: {}",
|
||||
file_name.display(),
|
||||
error.message
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for warning in warnings {
|
||||
if let Some(span) = warning.span {
|
||||
sess.span_warn(
|
||||
span,
|
||||
format!("error reading Clippy's configuration file: {}", warning.message),
|
||||
);
|
||||
} else {
|
||||
sess.warn(format!(
|
||||
"error reading Clippy's configuration file `{}`: {}",
|
||||
file_name.display(),
|
||||
warning.message
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
conf
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RegistrationGroups {
|
||||
all: Vec<LintId>,
|
||||
|
|
@ -558,7 +502,7 @@ fn register_categories(store: &mut rustc_lint::LintStore) {
|
|||
///
|
||||
/// Used in `./src/driver.rs`.
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
|
||||
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &'static Conf) {
|
||||
register_removed_non_tool_lints(store);
|
||||
register_categories(store);
|
||||
|
||||
|
|
@ -660,8 +604,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
|
||||
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
|
||||
|
||||
let msrv = Msrv::read(&conf.msrv, sess);
|
||||
let msrv = move || msrv.clone();
|
||||
let msrv = || conf.msrv.clone();
|
||||
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
|
||||
let allow_expect_in_tests = conf.allow_expect_in_tests;
|
||||
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
|
||||
|
|
@ -762,6 +705,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
too_many_arguments_threshold,
|
||||
too_many_lines_threshold,
|
||||
large_error_threshold,
|
||||
avoid_breaking_exported_api,
|
||||
))
|
||||
});
|
||||
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
|
||||
|
|
@ -806,7 +750,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
suppress_restriction_lint_in_const,
|
||||
))
|
||||
});
|
||||
store.register_late_pass(|_| Box::new(non_copy_const::NonCopyConst));
|
||||
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
|
||||
store.register_late_pass(move |_| Box::new(non_copy_const::NonCopyConst::new(ignore_interior_mutability.clone())));
|
||||
store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
|
||||
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));
|
||||
store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit));
|
||||
|
|
@ -851,10 +796,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
))
|
||||
});
|
||||
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
|
||||
let struct_field_name_threshold = conf.struct_field_name_threshold;
|
||||
let allow_private_module_inception = conf.allow_private_module_inception;
|
||||
store.register_late_pass(move |_| {
|
||||
Box::new(enum_variants::EnumVariantNames::new(
|
||||
Box::new(item_name_repetitions::ItemNameRepetitions::new(
|
||||
enum_variant_name_threshold,
|
||||
struct_field_name_threshold,
|
||||
avoid_breaking_exported_api,
|
||||
allow_private_module_inception,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ fn get_vec_push<'tcx>(
|
|||
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
|
||||
if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
|
||||
// Figure out the parameters for the method call
|
||||
if let Some(pushed_item) = args.get(0);
|
||||
if let Some(pushed_item) = args.first();
|
||||
// Check that the method being called is push() on a Vec
|
||||
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
|
||||
if path.ident.name.as_str() == "push";
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ struct PathAndSpan {
|
|||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use if_chain::if_chain;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{
|
||||
AsyncCoroutineKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, CoroutineKind, GenericArg, GenericBound,
|
||||
AsyncCoroutineKind, Block, Body, Closure, CoroutineKind, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, GenericBound,
|
||||
ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, TypeBindingKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -103,9 +103,9 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<
|
|||
if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
|
||||
|
||||
if let QPath::Resolved(_, count_func_path) = count_func_qpath;
|
||||
if let Some(segment_zero) = count_func_path.segments.get(0);
|
||||
if let Some(segment_zero) = count_func_path.segments.first();
|
||||
if let Some(args) = segment_zero.args;
|
||||
if let Some(GenericArg::Type(real_ty)) = args.args.get(0);
|
||||
if let Some(GenericArg::Type(real_ty)) = args.args.first();
|
||||
|
||||
if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id);
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ use rustc_span::{sym, Span};
|
|||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Suggests to use dedicated built-in methods,
|
||||
/// `is_ascii_(lowercase|uppercase|digit)` for checking on corresponding ascii range
|
||||
/// `is_ascii_(lowercase|uppercase|digit|hexdigit)` for checking on corresponding
|
||||
/// ascii range
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Using the built-in functions is more readable and makes it
|
||||
/// clear that it's not a specific subset of characters, but all
|
||||
/// ASCII (lowercase|uppercase|digit) characters.
|
||||
/// ASCII (lowercase|uppercase|digit|hexdigit) characters.
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
|
|
@ -28,6 +29,7 @@ declare_clippy_lint! {
|
|||
/// assert!(matches!(b'X', b'A'..=b'Z'));
|
||||
/// assert!(matches!('2', '0'..='9'));
|
||||
/// assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
|
||||
/// assert!(matches!('C', '0'..='9' | 'a'..='f' | 'A'..='F'));
|
||||
///
|
||||
/// ('0'..='9').contains(&'0');
|
||||
/// ('a'..='z').contains(&'a');
|
||||
|
|
@ -41,6 +43,7 @@ declare_clippy_lint! {
|
|||
/// assert!(b'X'.is_ascii_uppercase());
|
||||
/// assert!('2'.is_ascii_digit());
|
||||
/// assert!('x'.is_ascii_alphabetic());
|
||||
/// assert!('C'.is_ascii_hexdigit());
|
||||
///
|
||||
/// '0'.is_ascii_digit();
|
||||
/// 'a'.is_ascii_lowercase();
|
||||
|
|
@ -75,6 +78,12 @@ enum CharRange {
|
|||
FullChar,
|
||||
/// '0..=9'
|
||||
Digit,
|
||||
/// 'a..=f'
|
||||
LowerHexLetter,
|
||||
/// 'A..=F'
|
||||
UpperHexLetter,
|
||||
/// '0..=9' | 'a..=f' | 'A..=F'
|
||||
HexDigit,
|
||||
Otherwise,
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +125,8 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
|
|||
CharRange::LowerChar => Some("is_ascii_lowercase"),
|
||||
CharRange::FullChar => Some("is_ascii_alphabetic"),
|
||||
CharRange::Digit => Some("is_ascii_digit"),
|
||||
CharRange::Otherwise => None,
|
||||
CharRange::HexDigit => Some("is_ascii_hexdigit"),
|
||||
CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => None,
|
||||
} {
|
||||
let default_snip = "..";
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
|
@ -141,6 +151,12 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
|
|||
|
||||
if ranges.len() == 2 && ranges.contains(&CharRange::UpperChar) && ranges.contains(&CharRange::LowerChar) {
|
||||
CharRange::FullChar
|
||||
} else if ranges.len() == 3
|
||||
&& ranges.contains(&CharRange::Digit)
|
||||
&& ranges.contains(&CharRange::LowerHexLetter)
|
||||
&& ranges.contains(&CharRange::UpperHexLetter)
|
||||
{
|
||||
CharRange::HexDigit
|
||||
} else {
|
||||
CharRange::Otherwise
|
||||
}
|
||||
|
|
@ -156,6 +172,8 @@ fn check_range(start: &Expr<'_>, end: &Expr<'_>) -> CharRange {
|
|||
match (&start_lit.node, &end_lit.node) {
|
||||
(Char('a'), Char('z')) | (Byte(b'a'), Byte(b'z')) => CharRange::LowerChar,
|
||||
(Char('A'), Char('Z')) | (Byte(b'A'), Byte(b'Z')) => CharRange::UpperChar,
|
||||
(Char('a'), Char('f')) | (Byte(b'a'), Byte(b'f')) => CharRange::LowerHexLetter,
|
||||
(Char('A'), Char('F')) | (Byte(b'A'), Byte(b'F')) => CharRange::UpperHexLetter,
|
||||
(Char('0'), Char('9')) | (Byte(b'0'), Byte(b'9')) => CharRange::Digit,
|
||||
_ => CharRange::Otherwise,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -967,7 +967,6 @@ declare_clippy_lint! {
|
|||
"checks for unnecessary guards in match expressions"
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Matches {
|
||||
msrv: Msrv,
|
||||
infallible_destructuring_match_linted: bool,
|
||||
|
|
@ -978,7 +977,7 @@ impl Matches {
|
|||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
..Matches::default()
|
||||
infallible_destructuring_match_linted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{is_expr_identity_function, is_trait_method};
|
||||
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -9,7 +9,7 @@ use rustc_span::sym;
|
|||
use super::FILTER_MAP_IDENTITY;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) {
|
||||
if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, filter_map_arg) {
|
||||
if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, filter_map_arg) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FILTER_MAP_IDENTITY,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::{is_expr_identity_function, is_trait_method};
|
||||
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
|||
flat_map_arg: &'tcx hir::Expr<'_>,
|
||||
flat_map_span: Span,
|
||||
) {
|
||||
if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, flat_map_arg) {
|
||||
if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, flat_map_arg) {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
FLAT_MAP_IDENTITY,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_slice_of_primitives;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::LitKind;
|
||||
|
|
@ -20,7 +19,6 @@ pub(super) fn check<'tcx>(
|
|||
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
|
||||
if let Some(impl_id) = cx.tcx.impl_of_method(method_id);
|
||||
if cx.tcx.type_of(impl_id).instantiate_identity().is_slice();
|
||||
if let Some(_) = is_slice_of_primitives(cx, recv);
|
||||
if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind;
|
||||
then {
|
||||
let mut app = Applicability::MachineApplicable;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{is_expr_identity_function, is_trait_method};
|
||||
use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -23,7 +23,7 @@ pub(super) fn check(
|
|||
if is_trait_method(cx, expr, sym::Iterator)
|
||||
|| is_type_diagnostic_item(cx, caller_ty, sym::Result)
|
||||
|| is_type_diagnostic_item(cx, caller_ty, sym::Option);
|
||||
if is_expr_identity_function(cx, map_arg);
|
||||
if is_expr_untyped_identity_function(cx, map_arg);
|
||||
if let Some(sugg_span) = expr.span.trim_start(caller.span);
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub(super) fn check<'tcx>(
|
|||
if search_method == "find";
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind;
|
||||
let closure_body = cx.tcx.hir().body(body);
|
||||
if let Some(closure_arg) = closure_body.params.get(0);
|
||||
if let Some(closure_arg) = closure_body.params.first();
|
||||
then {
|
||||
if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
|
||||
Some(search_snippet.replacen('&', "", 1))
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{eager_or_lazy, is_from_proc_macro, usage};
|
||||
use hir::FnRetTy;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
|
|
@ -27,7 +28,7 @@ pub(super) fn check<'tcx>(
|
|||
let is_bool = cx.typeck_results().expr_ty(recv).is_bool();
|
||||
|
||||
if is_option || is_result || is_bool {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind {
|
||||
if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl, .. }) = arg.kind {
|
||||
let body = cx.tcx.hir().body(body);
|
||||
let body_expr = &body.value;
|
||||
|
||||
|
|
@ -48,7 +49,14 @@ pub(super) fn check<'tcx>(
|
|||
.iter()
|
||||
// bindings are checked to be unused above
|
||||
.all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild))
|
||||
{
|
||||
&& matches!(
|
||||
fn_decl.output,
|
||||
FnRetTy::DefaultReturn(_)
|
||||
| FnRetTy::Return(hir::Ty {
|
||||
kind: hir::TyKind::Infer,
|
||||
..
|
||||
})
|
||||
) {
|
||||
Applicability::MachineApplicable
|
||||
} else {
|
||||
// replacing the lambda may break type inference
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use if_chain::if_chain;
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, GenericArgKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::GenericArgKind;
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::iter;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ impl MissingDoc {
|
|||
if_chain! {
|
||||
if let Some(meta) = meta;
|
||||
if let MetaItemKind::List(list) = meta.kind;
|
||||
if let Some(meta) = list.get(0);
|
||||
if let Some(meta) = list.first();
|
||||
if let Some(name) = meta.ident();
|
||||
then {
|
||||
name.name == sym::include
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ declare_clippy_lint! {
|
|||
/// Checks for imports that do not rename the item as specified
|
||||
/// in the `enforce-import-renames` config option.
|
||||
///
|
||||
/// Note: Even though this lint is warn-by-default, it will only trigger if
|
||||
/// import renames are defined in the clippy.toml file.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// Consistency is important, if a project has defined import
|
||||
/// renames they should be followed. More practically, some item names are too
|
||||
|
|
@ -38,7 +41,7 @@ declare_clippy_lint! {
|
|||
/// ```
|
||||
#[clippy::version = "1.55.0"]
|
||||
pub MISSING_ENFORCED_IMPORT_RENAMES,
|
||||
restriction,
|
||||
style,
|
||||
"enforce import renames"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_path_lang_item;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::visitors::{for_each_expr, Visitable};
|
||||
use clippy_utils::is_path_lang_item;
|
||||
use rustc_ast::LitKind;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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::Span;
|
||||
use rustc_span::{DesugaringKind, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
|
@ -64,7 +64,10 @@ declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK])
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
|
||||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
|
||||
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) {
|
||||
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_))
|
||||
|| in_external_macro(cx.tcx.sess, block.span)
|
||||
|| block.span.is_desugaring(DesugaringKind::Await)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let mut unsafe_ops = vec![];
|
||||
|
|
|
|||
|
|
@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
|||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>)
|
|||
}
|
||||
|
||||
fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
|
||||
block.stmts.get(0).map_or(false, |stmt| match stmt.kind {
|
||||
block.stmts.first().map_or(false, |stmt| match stmt.kind {
|
||||
ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
|
||||
if let ast::ExprKind::Continue(ref l) = e.kind {
|
||||
compare_labels(label, l.as_ref())
|
||||
|
|
@ -434,7 +434,7 @@ fn erode_from_back(s: &str) -> String {
|
|||
}
|
||||
|
||||
fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
|
||||
block.stmts.get(0).map(|stmt| stmt.span)
|
||||
block.stmts.first().map(|stmt| stmt.span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
|||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor};
|
||||
use rustc_hir::{
|
||||
Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath,
|
||||
BlockCheckMode, Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node,
|
||||
PatKind, QPath,
|
||||
};
|
||||
use rustc_hir_typeck::expr_use_visitor as euv;
|
||||
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
|
||||
|
|
@ -139,13 +140,23 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
|||
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
|
||||
let is_async = match kind {
|
||||
FnKind::ItemFn(.., header) => {
|
||||
if header.is_unsafe() {
|
||||
// We don't check unsafe functions.
|
||||
return;
|
||||
}
|
||||
let attrs = cx.tcx.hir().attrs(hir_id);
|
||||
if header.abi != Abi::Rust || requires_exact_signature(attrs) {
|
||||
return;
|
||||
}
|
||||
header.is_async()
|
||||
},
|
||||
FnKind::Method(.., sig) => sig.header.is_async(),
|
||||
FnKind::Method(.., sig) => {
|
||||
if sig.header.is_unsafe() {
|
||||
// We don't check unsafe functions.
|
||||
return;
|
||||
}
|
||||
sig.header.is_async()
|
||||
},
|
||||
FnKind::Closure => return,
|
||||
};
|
||||
|
||||
|
|
@ -186,20 +197,21 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
|
|||
};
|
||||
let infcx = cx.tcx.infer_ctxt().build();
|
||||
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
|
||||
|
||||
let mut checked_closures = FxHashSet::default();
|
||||
|
||||
// We retrieve all the closures declared in the function because they will not be found
|
||||
// by `euv::Delegate`.
|
||||
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
|
||||
for_each_expr_with_closures(cx, body, |expr| {
|
||||
if let ExprKind::Closure(closure) = expr.kind {
|
||||
closures.insert(closure.def_id);
|
||||
}
|
||||
ControlFlow::<()>::Continue(())
|
||||
});
|
||||
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
|
||||
|
||||
if is_async {
|
||||
let mut checked_closures = FxHashSet::default();
|
||||
|
||||
// We retrieve all the closures declared in the async function because they will
|
||||
// not be found by `euv::Delegate`.
|
||||
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
|
||||
for_each_expr_with_closures(cx, body, |expr| {
|
||||
if let ExprKind::Closure(closure) = expr.kind {
|
||||
closures.insert(closure.def_id);
|
||||
}
|
||||
ControlFlow::<()>::Continue(())
|
||||
});
|
||||
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
|
||||
|
||||
while !ctx.async_closures.is_empty() {
|
||||
let async_closures = ctx.async_closures.clone();
|
||||
ctx.async_closures.clear();
|
||||
|
|
@ -304,10 +316,27 @@ impl<'tcx> MutablyUsedVariablesCtxt<'tcx> {
|
|||
}
|
||||
self.aliases.insert(alias, target);
|
||||
}
|
||||
|
||||
// The goal here is to find if the current scope is unsafe or not. It stops when it finds
|
||||
// a function or an unsafe block.
|
||||
fn is_in_unsafe_block(&self, item: HirId) -> bool {
|
||||
let hir = self.tcx.hir();
|
||||
for (parent, node) in hir.parent_iter(item) {
|
||||
if let Some(fn_sig) = hir.fn_sig_by_hir_id(parent) {
|
||||
return fn_sig.header.is_unsafe();
|
||||
} else if let Node::Block(block) = node {
|
||||
if matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
||||
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
|
||||
if let euv::Place {
|
||||
base:
|
||||
euv::PlaceBase::Local(vid)
|
||||
|
|
@ -327,13 +356,18 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
|||
&& matches!(base_ty.ref_mutability(), Some(Mutability::Mut))
|
||||
{
|
||||
self.add_mutably_used_var(*vid);
|
||||
} else if self.is_in_unsafe_block(id) {
|
||||
// If we are in an unsafe block, any operation on this variable must not be warned
|
||||
// upon!
|
||||
self.add_mutably_used_var(*vid);
|
||||
}
|
||||
self.prev_bind = None;
|
||||
self.prev_move_to_closure.remove(vid);
|
||||
}
|
||||
}
|
||||
|
||||
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId, borrow: ty::BorrowKind) {
|
||||
self.prev_bind = None;
|
||||
if let euv::Place {
|
||||
base:
|
||||
|
|
@ -355,6 +389,10 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
|||
|| (borrow == ty::BorrowKind::UniqueImmBorrow && base_ty.ref_mutability() == Some(Mutability::Mut))
|
||||
{
|
||||
self.add_mutably_used_var(*vid);
|
||||
} else if self.is_in_unsafe_block(id) {
|
||||
// If we are in an unsafe block, any operation on this variable must not be warned
|
||||
// upon!
|
||||
self.add_mutably_used_var(*vid);
|
||||
}
|
||||
} else if borrow == ty::ImmBorrow {
|
||||
// If there is an `async block`, it'll contain a call to a closure which we need to
|
||||
|
|
@ -397,7 +435,21 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn copy(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) {
|
||||
fn copy(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
|
||||
if let euv::Place {
|
||||
base:
|
||||
euv::PlaceBase::Local(vid)
|
||||
| euv::PlaceBase::Upvar(UpvarId {
|
||||
var_path: UpvarPath { hir_id: vid },
|
||||
..
|
||||
}),
|
||||
..
|
||||
} = &cmt.place
|
||||
{
|
||||
if self.is_in_unsafe_block(id) {
|
||||
self.add_mutably_used_var(*vid);
|
||||
}
|
||||
}
|
||||
self.prev_bind = None;
|
||||
}
|
||||
|
||||
|
|
@ -427,8 +479,22 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
|
||||
fn bind(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
|
||||
self.prev_bind = Some(id);
|
||||
if let euv::Place {
|
||||
base:
|
||||
euv::PlaceBase::Local(vid)
|
||||
| euv::PlaceBase::Upvar(UpvarId {
|
||||
var_path: UpvarPath { hir_id: vid },
|
||||
..
|
||||
}),
|
||||
..
|
||||
} = &cmt.place
|
||||
{
|
||||
if self.is_in_unsafe_block(id) {
|
||||
self.add_mutably_used_var(*vid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||
use clippy_utils::is_self;
|
||||
use clippy_utils::ptr::get_spans;
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::ty::{
|
||||
implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
|
||||
};
|
||||
use clippy_utils::is_self;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::Attribute;
|
||||
use rustc_errors::{Applicability, Diagnostic};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use clippy_utils::source::snippet;
|
|||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{AsyncCoroutineKind, Block, Body, Expr, ExprKind, CoroutineKind, LangItem, MatchSource, QPath};
|
||||
use rustc_hir::{AsyncCoroutineKind, Block, Body, CoroutineKind, Expr, ExprKind, LangItem, MatchSource, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
use std::ptr;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::in_constant;
|
||||
use clippy_utils::macros::macro_backtrace;
|
||||
use clippy_utils::{def_path_def_ids, in_constant};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{
|
||||
|
|
@ -15,9 +16,10 @@ use rustc_hir::{
|
|||
};
|
||||
use rustc_lint::{LateContext, LateLintPass, Lint};
|
||||
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
|
||||
use rustc_middle::query::Key;
|
||||
use rustc_middle::ty::adjustment::Adjust;
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::{sym, InnerSpan, Span};
|
||||
use rustc_target::abi::VariantIdx;
|
||||
|
||||
|
|
@ -126,128 +128,6 @@ declare_clippy_lint! {
|
|||
"referencing `const` with interior mutability"
|
||||
}
|
||||
|
||||
fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
// Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
|
||||
// making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
|
||||
// 'unfrozen'. However, this code causes a false negative in which
|
||||
// a type contains a layout-unknown type, but also an unsafe cell like `const CELL: Cell<T>`.
|
||||
// Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
|
||||
// since it works when a pointer indirection involves (`Cell<*const T>`).
|
||||
// Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
|
||||
// but I'm not sure whether it's a decent way, if possible.
|
||||
cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx, cx.param_env)
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_raw<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
result: Result<Option<ty::ValTree<'tcx>>, ErrorHandled>,
|
||||
ty: Ty<'tcx>,
|
||||
) -> bool {
|
||||
fn inner<'tcx>(cx: &LateContext<'tcx>, val: ty::ValTree<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
match *ty.kind() {
|
||||
// the fact that we have to dig into every structs to search enums
|
||||
// leads us to the point checking `UnsafeCell` directly is the only option.
|
||||
ty::Adt(ty_def, ..) if ty_def.is_unsafe_cell() => true,
|
||||
// As of 2022-09-08 miri doesn't track which union field is active so there's no safe way to check the
|
||||
// contained value.
|
||||
ty::Adt(def, ..) if def.is_union() => false,
|
||||
ty::Array(ty, _) => val.unwrap_branch().iter().any(|field| inner(cx, *field, ty)),
|
||||
ty::Adt(def, _) if def.is_union() => false,
|
||||
ty::Adt(def, args) if def.is_enum() => {
|
||||
let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap();
|
||||
let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap());
|
||||
fields
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(
|
||||
def.variants()[variant_index]
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.ty(cx.tcx, args)),
|
||||
)
|
||||
.any(|(field, ty)| inner(cx, field, ty))
|
||||
},
|
||||
ty::Adt(def, args) => val
|
||||
.unwrap_branch()
|
||||
.iter()
|
||||
.zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args)))
|
||||
.any(|(field, ty)| inner(cx, *field, ty)),
|
||||
ty::Tuple(tys) => val
|
||||
.unwrap_branch()
|
||||
.iter()
|
||||
.zip(tys)
|
||||
.any(|(field, ty)| inner(cx, *field, ty)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
result.map_or_else(
|
||||
|err| {
|
||||
// Consider `TooGeneric` cases as being unfrozen.
|
||||
// This causes a false positive where an assoc const whose type is unfrozen
|
||||
// have a value that is a frozen variant with a generic param (an example is
|
||||
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
|
||||
// However, it prevents a number of false negatives that is, I think, important:
|
||||
// 1. assoc consts in trait defs referring to consts of themselves (an example is
|
||||
// `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
|
||||
// 2. a path expr referring to assoc consts whose type is doesn't have any frozen variants in trait
|
||||
// defs (i.e. without substitute for `Self`). (e.g. borrowing
|
||||
// `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
|
||||
// 3. similar to the false positive above; but the value is an unfrozen variant, or the type has no
|
||||
// enums. (An example is
|
||||
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT` and
|
||||
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
|
||||
// One might be able to prevent these FNs correctly, and replace this with `false`;
|
||||
// e.g. implementing `has_frozen_variant` described above, and not running this function
|
||||
// when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
|
||||
// case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
|
||||
// similar to 2., but with the a frozen variant) (e.g. borrowing
|
||||
// `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
|
||||
// I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
|
||||
matches!(err, ErrorHandled::TooGeneric(..))
|
||||
},
|
||||
|val| val.map_or(true, |val| inner(cx, val, ty)),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_poly<'tcx>(cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool {
|
||||
let def_id = body_id.hir_id.owner.to_def_id();
|
||||
let args = ty::GenericArgs::identity_for_item(cx.tcx, def_id);
|
||||
let instance = ty::Instance::new(def_id, args);
|
||||
let cid = rustc_middle::mir::interpret::GlobalId {
|
||||
instance,
|
||||
promoted: None,
|
||||
};
|
||||
let param_env = cx.tcx.param_env(def_id).with_reveal_all_normalized(cx.tcx);
|
||||
let result = cx.tcx.const_eval_global_id_for_typeck(param_env, cid, None);
|
||||
is_value_unfrozen_raw(cx, result, ty)
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_expr<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
|
||||
let args = cx.typeck_results().node_args(hir_id);
|
||||
|
||||
let result = const_eval_resolve(cx.tcx, cx.param_env, ty::UnevaluatedConst::new(def_id, args), None);
|
||||
is_value_unfrozen_raw(cx, result, ty)
|
||||
}
|
||||
|
||||
pub fn const_eval_resolve<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ct: ty::UnevaluatedConst<'tcx>,
|
||||
span: Option<Span>,
|
||||
) -> EvalToValTreeResult<'tcx> {
|
||||
match ty::Instance::resolve(tcx, param_env, ct.def, ct.args) {
|
||||
Ok(Some(instance)) => {
|
||||
let cid = GlobalId {
|
||||
instance,
|
||||
promoted: None,
|
||||
};
|
||||
tcx.const_eval_global_id_for_typeck(param_env, cid, span)
|
||||
},
|
||||
Ok(None) => Err(ErrorHandled::TooGeneric(span.unwrap_or(rustc_span::DUMMY_SP))),
|
||||
Err(err) => Err(ErrorHandled::Reported(err.into(), span.unwrap_or(rustc_span::DUMMY_SP))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Source {
|
||||
Item { item: Span },
|
||||
|
|
@ -292,13 +172,178 @@ fn lint(cx: &LateContext<'_>, source: Source) {
|
|||
});
|
||||
}
|
||||
|
||||
declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);
|
||||
#[derive(Clone)]
|
||||
pub struct NonCopyConst {
|
||||
ignore_interior_mutability: Vec<String>,
|
||||
ignore_mut_def_ids: FxHashSet<DefId>,
|
||||
}
|
||||
|
||||
impl_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);
|
||||
|
||||
impl NonCopyConst {
|
||||
pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
|
||||
Self {
|
||||
ignore_interior_mutability,
|
||||
ignore_mut_def_ids: FxHashSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ty_ignored(&self, ty: Ty<'_>) -> bool {
|
||||
matches!(ty.ty_adt_id(), Some(adt_id) if self.ignore_mut_def_ids.contains(&adt_id))
|
||||
}
|
||||
|
||||
fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
// Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
|
||||
// making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
|
||||
// 'unfrozen'. However, this code causes a false negative in which
|
||||
// a type contains a layout-unknown type, but also an unsafe cell like `const CELL: Cell<T>`.
|
||||
// Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
|
||||
// since it works when a pointer indirection involves (`Cell<*const T>`).
|
||||
// Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
|
||||
// but I'm not sure whether it's a decent way, if possible.
|
||||
cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx, cx.param_env)
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_raw_inner<'tcx>(&self, cx: &LateContext<'tcx>, val: ty::ValTree<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
if self.is_ty_ignored(ty) {
|
||||
return false;
|
||||
}
|
||||
match *ty.kind() {
|
||||
// the fact that we have to dig into every structs to search enums
|
||||
// leads us to the point checking `UnsafeCell` directly is the only option.
|
||||
ty::Adt(ty_def, ..) if ty_def.is_unsafe_cell() => true,
|
||||
// As of 2022-09-08 miri doesn't track which union field is active so there's no safe way to check the
|
||||
// contained value.
|
||||
ty::Adt(def, ..) if def.is_union() => false,
|
||||
ty::Array(ty, _) => val
|
||||
.unwrap_branch()
|
||||
.iter()
|
||||
.any(|field| self.is_value_unfrozen_raw_inner(cx, *field, ty)),
|
||||
ty::Adt(def, _) if def.is_union() => false,
|
||||
ty::Adt(def, args) if def.is_enum() => {
|
||||
let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap();
|
||||
let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap());
|
||||
fields
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(
|
||||
def.variants()[variant_index]
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| field.ty(cx.tcx, args)),
|
||||
)
|
||||
.any(|(field, ty)| self.is_value_unfrozen_raw_inner(cx, field, ty))
|
||||
},
|
||||
ty::Adt(def, args) => val
|
||||
.unwrap_branch()
|
||||
.iter()
|
||||
.zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args)))
|
||||
.any(|(field, ty)| self.is_value_unfrozen_raw_inner(cx, *field, ty)),
|
||||
ty::Tuple(tys) => val
|
||||
.unwrap_branch()
|
||||
.iter()
|
||||
.zip(tys)
|
||||
.any(|(field, ty)| self.is_value_unfrozen_raw_inner(cx, *field, ty)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_raw<'tcx>(
|
||||
&self,
|
||||
cx: &LateContext<'tcx>,
|
||||
result: Result<Option<ty::ValTree<'tcx>>, ErrorHandled>,
|
||||
ty: Ty<'tcx>,
|
||||
) -> bool {
|
||||
result.map_or_else(
|
||||
|err| {
|
||||
// Consider `TooGeneric` cases as being unfrozen.
|
||||
// This causes a false positive where an assoc const whose type is unfrozen
|
||||
// have a value that is a frozen variant with a generic param (an example is
|
||||
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
|
||||
// However, it prevents a number of false negatives that is, I think, important:
|
||||
// 1. assoc consts in trait defs referring to consts of themselves (an example is
|
||||
// `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
|
||||
// 2. a path expr referring to assoc consts whose type is doesn't have any frozen variants in trait
|
||||
// defs (i.e. without substitute for `Self`). (e.g. borrowing
|
||||
// `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
|
||||
// 3. similar to the false positive above; but the value is an unfrozen variant, or the type has no
|
||||
// enums. (An example is
|
||||
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT` and
|
||||
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
|
||||
// One might be able to prevent these FNs correctly, and replace this with `false`;
|
||||
// e.g. implementing `has_frozen_variant` described above, and not running this function
|
||||
// when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
|
||||
// case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
|
||||
// similar to 2., but with the a frozen variant) (e.g. borrowing
|
||||
// `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
|
||||
// I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
|
||||
matches!(err, ErrorHandled::TooGeneric(..))
|
||||
},
|
||||
|val| val.map_or(true, |val| self.is_value_unfrozen_raw_inner(cx, val, ty)),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_poly<'tcx>(&self, cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool {
|
||||
let def_id = body_id.hir_id.owner.to_def_id();
|
||||
let args = ty::GenericArgs::identity_for_item(cx.tcx, def_id);
|
||||
let instance = ty::Instance::new(def_id, args);
|
||||
let cid = rustc_middle::mir::interpret::GlobalId {
|
||||
instance,
|
||||
promoted: None,
|
||||
};
|
||||
let param_env = cx.tcx.param_env(def_id).with_reveal_all_normalized(cx.tcx);
|
||||
let result = cx.tcx.const_eval_global_id_for_typeck(param_env, cid, None);
|
||||
self.is_value_unfrozen_raw(cx, result, ty)
|
||||
}
|
||||
|
||||
fn is_value_unfrozen_expr<'tcx>(&self, cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
|
||||
let args = cx.typeck_results().node_args(hir_id);
|
||||
|
||||
let result = Self::const_eval_resolve(cx.tcx, cx.param_env, ty::UnevaluatedConst::new(def_id, args), None);
|
||||
self.is_value_unfrozen_raw(cx, result, ty)
|
||||
}
|
||||
|
||||
pub fn const_eval_resolve<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ct: ty::UnevaluatedConst<'tcx>,
|
||||
span: Option<Span>,
|
||||
) -> EvalToValTreeResult<'tcx> {
|
||||
match ty::Instance::resolve(tcx, param_env, ct.def, ct.args) {
|
||||
Ok(Some(instance)) => {
|
||||
let cid = GlobalId {
|
||||
instance,
|
||||
promoted: None,
|
||||
};
|
||||
tcx.const_eval_global_id_for_typeck(param_env, cid, span)
|
||||
},
|
||||
Ok(None) => Err(ErrorHandled::TooGeneric(span.unwrap_or(rustc_span::DUMMY_SP))),
|
||||
Err(err) => Err(ErrorHandled::Reported(err.into(), span.unwrap_or(rustc_span::DUMMY_SP))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
||||
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
|
||||
self.ignore_mut_def_ids.clear();
|
||||
let mut path = Vec::new();
|
||||
for ty in &self.ignore_interior_mutability {
|
||||
path.extend(ty.split("::"));
|
||||
for id in def_path_def_ids(cx, &path[..]) {
|
||||
self.ignore_mut_def_ids.insert(id);
|
||||
}
|
||||
path.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
|
||||
if let ItemKind::Const(.., body_id) = it.kind {
|
||||
let ty = cx.tcx.type_of(it.owner_id).instantiate_identity();
|
||||
if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) {
|
||||
if !ignored_macro(cx, it)
|
||||
&& !self.is_ty_ignored(ty)
|
||||
&& Self::is_unfrozen(cx, ty)
|
||||
&& self.is_value_unfrozen_poly(cx, body_id, ty)
|
||||
{
|
||||
lint(cx, Source::Item { item: it.span });
|
||||
}
|
||||
}
|
||||
|
|
@ -311,7 +356,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
// Normalize assoc types because ones originated from generic params
|
||||
// bounded other traits could have their bound.
|
||||
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
|
||||
if is_unfrozen(cx, normalized)
|
||||
if !self.is_ty_ignored(ty) && Self::is_unfrozen(cx, normalized)
|
||||
// When there's no default value, lint it only according to its type;
|
||||
// in other words, lint consts whose value *could* be unfrozen, not definitely is.
|
||||
// This feels inconsistent with how the lint treats generic types,
|
||||
|
|
@ -324,7 +369,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
// i.e. having an enum doesn't necessary mean a type has a frozen variant.
|
||||
// And, implementing it isn't a trivial task; it'll probably end up
|
||||
// re-implementing the trait predicate evaluation specific to `Freeze`.
|
||||
&& body_id_opt.map_or(true, |body_id| is_value_unfrozen_poly(cx, body_id, normalized))
|
||||
&& body_id_opt.map_or(true, |body_id| self.is_value_unfrozen_poly(cx, body_id, normalized))
|
||||
{
|
||||
lint(cx, Source::Assoc { item: trait_item.span });
|
||||
}
|
||||
|
|
@ -367,8 +412,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
// e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
|
||||
let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity();
|
||||
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
|
||||
if is_unfrozen(cx, normalized);
|
||||
if is_value_unfrozen_poly(cx, *body_id, normalized);
|
||||
if !self.is_ty_ignored(ty) && Self::is_unfrozen(cx, normalized);
|
||||
if self.is_value_unfrozen_poly(cx, *body_id, normalized);
|
||||
then {
|
||||
lint(
|
||||
cx,
|
||||
|
|
@ -384,7 +429,10 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
// Normalize assoc types originated from generic params.
|
||||
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
|
||||
|
||||
if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, *body_id, normalized) {
|
||||
if !self.is_ty_ignored(ty)
|
||||
&& Self::is_unfrozen(cx, ty)
|
||||
&& self.is_value_unfrozen_poly(cx, *body_id, normalized)
|
||||
{
|
||||
lint(cx, Source::Assoc { item: impl_item.span });
|
||||
}
|
||||
},
|
||||
|
|
@ -478,7 +526,10 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
|
|||
cx.typeck_results().expr_ty(dereferenced_expr)
|
||||
};
|
||||
|
||||
if is_unfrozen(cx, ty) && is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty) {
|
||||
if !self.is_ty_ignored(ty)
|
||||
&& Self::is_unfrozen(cx, ty)
|
||||
&& self.is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty)
|
||||
{
|
||||
lint(cx, Source::Expr { expr: expr.span });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use clippy_utils::is_lint_allowed;
|
||||
use rustc_ast::ImplPolarity;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{FieldDef, Item, ItemKind, Node};
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ impl Usage {
|
|||
/// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the
|
||||
/// `DefId` of the function paired with the parameter's index.
|
||||
#[derive(Default)]
|
||||
#[allow(clippy::struct_field_names)]
|
||||
struct Params {
|
||||
params: Vec<Param>,
|
||||
by_id: HirIdMap<usize>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::path_def_id;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::{implements_trait, is_copy};
|
||||
use clippy_utils::path_def_id;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
|
||||
use rustc_lint::LateContext;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::last_path_segment;
|
||||
use clippy_utils::macros::root_macro_call_first_node;
|
||||
use clippy_utils::source::{indent_of, snippet};
|
||||
use clippy_utils::last_path_segment;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use clippy_utils::peel_blocks;
|
|||
use clippy_utils::source::{snippet, walk_span_to_context};
|
||||
use clippy_utils::visitors::for_each_expr;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{AsyncCoroutineKind, Closure, Expr, ExprKind, CoroutineKind, MatchSource};
|
||||
use rustc_hir::{AsyncCoroutineKind, Closure, CoroutineKind, Expr, ExprKind, MatchSource};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::lint::in_external_macro;
|
||||
use rustc_middle::ty::UpvarCapture;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
|
|||
if let Res::Local(binding_id) = cx.qpath_res(&qpath, expr.hir_id);
|
||||
if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id);
|
||||
// the previous binding has the same mutability
|
||||
if find_binding(binding_pat, ident).unwrap().1 == mutability;
|
||||
if find_binding(binding_pat, ident).is_some_and(|bind| bind.1 == mutability);
|
||||
// the local does not change the effect of assignments to the binding. see #11290
|
||||
if !affects_assignments(cx, mutability, binding_id, local.hir_id);
|
||||
// the local does not affect the code's drop behavior
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
|
|||
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl { .. })
|
||||
&& let item = cx.tcx.hir().item(id)
|
||||
&& let ItemKind::Impl(Impl {
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
items,
|
||||
of_trait,
|
||||
self_ty,
|
||||
..
|
||||
}) = &item.kind
|
||||
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
|
||||
{
|
||||
if !map.contains_key(res) {
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> {
|
|||
|
||||
fn visit_block(&mut self, block: &'tcx Block<'_>) {
|
||||
if self.initialization_found {
|
||||
if let Some(s) = block.stmts.get(0) {
|
||||
if let Some(s) = block.stmts.first() {
|
||||
self.visit_stmt(s);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::path_def_id;
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ impl Types {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct CheckTyContext {
|
||||
is_in_trait_impl: bool,
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Opt
|
|||
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) {
|
||||
match (block.stmts.first().map(|stmt| &stmt.kind), block.expr) {
|
||||
(None, Some(expr)) => Some(expr),
|
||||
(Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
|
||||
_ => None,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
|
|||
let (constructor_path, constructor_item) =
|
||||
if let hir::ExprKind::Call(constructor, constructor_args) = recv.kind
|
||||
&& let hir::ExprKind::Path(constructor_path) = constructor.kind
|
||||
&& let Some(arg) = constructor_args.get(0)
|
||||
&& let Some(arg) = constructor_args.first()
|
||||
{
|
||||
if constructor.span.from_expansion() || arg.span.from_expansion() {
|
||||
return;
|
||||
|
|
@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
|
|||
_ => return,
|
||||
}
|
||||
|
||||
if let Some(map_arg) = args.get(0)
|
||||
if let Some(map_arg) = args.first()
|
||||
&& let hir::ExprKind::Path(fun) = map_arg.kind
|
||||
{
|
||||
if map_arg.span.from_expansion() {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ declare_clippy_lint! {
|
|||
"unnecessary structure name repetition whereas `Self` is applicable"
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UseSelf {
|
||||
msrv: Msrv,
|
||||
stack: Vec<StackItem>,
|
||||
|
|
@ -65,7 +64,7 @@ impl UseSelf {
|
|||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
..Self::default()
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,8 +268,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
|
|||
fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
|
||||
if let QPath::LangItem(lang_item, ..) = *qpath.value {
|
||||
chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
|
||||
} else {
|
||||
chain!(self, "match_qpath({qpath}, &[{}])", path_to_string(qpath.value));
|
||||
} else if let Ok(path) = path_to_string(qpath.value) {
|
||||
chain!(self, "match_qpath({qpath}, &[{}])", path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -738,8 +738,8 @@ fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
|
|||
get_attr(cx.sess(), attrs, "author").count() > 0
|
||||
}
|
||||
|
||||
fn path_to_string(path: &QPath<'_>) -> String {
|
||||
fn inner(s: &mut String, path: &QPath<'_>) {
|
||||
fn path_to_string(path: &QPath<'_>) -> Result<String, ()> {
|
||||
fn inner(s: &mut String, path: &QPath<'_>) -> Result<(), ()> {
|
||||
match *path {
|
||||
QPath::Resolved(_, path) => {
|
||||
for (i, segment) in path.segments.iter().enumerate() {
|
||||
|
|
@ -751,16 +751,18 @@ fn path_to_string(path: &QPath<'_>) -> String {
|
|||
},
|
||||
QPath::TypeRelative(ty, segment) => match &ty.kind {
|
||||
hir::TyKind::Path(inner_path) => {
|
||||
inner(s, inner_path);
|
||||
inner(s, inner_path)?;
|
||||
*s += ", ";
|
||||
write!(s, "{:?}", segment.ident.as_str()).unwrap();
|
||||
},
|
||||
other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
|
||||
},
|
||||
QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"),
|
||||
QPath::LangItem(..) => return Err(()),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
let mut s = String::new();
|
||||
inner(&mut s, path);
|
||||
s
|
||||
inner(&mut s, path)?;
|
||||
Ok(s)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
|
|||
use serde::Deserialize;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::OnceLock;
|
||||
use std::{cmp, env, fmt, fs, io};
|
||||
|
||||
#[rustfmt::skip]
|
||||
|
|
@ -78,62 +79,35 @@ pub struct TryConf {
|
|||
|
||||
impl TryConf {
|
||||
fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
|
||||
ConfError::from_toml(file, error).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfError> for TryConf {
|
||||
fn from(value: ConfError) -> Self {
|
||||
Self {
|
||||
conf: Conf::default(),
|
||||
errors: vec![value],
|
||||
errors: vec![ConfError::from_toml(file, error)],
|
||||
warnings: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for TryConf {
|
||||
fn from(value: io::Error) -> Self {
|
||||
ConfError::from(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfError {
|
||||
pub message: String,
|
||||
pub span: Option<Span>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl ConfError {
|
||||
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
|
||||
if let Some(span) = error.span() {
|
||||
Self::spanned(file, error.message(), span)
|
||||
} else {
|
||||
Self {
|
||||
message: error.message().to_string(),
|
||||
span: None,
|
||||
}
|
||||
}
|
||||
let span = error.span().unwrap_or(0..file.source_len.0 as usize);
|
||||
Self::spanned(file, error.message(), span)
|
||||
}
|
||||
|
||||
fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
span: Some(Span::new(
|
||||
span: Span::new(
|
||||
file.start_pos + BytePos::from_usize(span.start),
|
||||
file.start_pos + BytePos::from_usize(span.end),
|
||||
SyntaxContext::root(),
|
||||
None,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for ConfError {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self {
|
||||
message: value.to_string(),
|
||||
span: None,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -297,7 +271,7 @@ define_Conf! {
|
|||
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE.
|
||||
///
|
||||
/// The minimum rust version that the project supports
|
||||
(msrv: Option<String> = None),
|
||||
(msrv: crate::Msrv = crate::Msrv::empty()),
|
||||
/// DEPRECATED LINT: BLACKLISTED_NAME.
|
||||
///
|
||||
/// Use the Disallowed Names lint instead
|
||||
|
|
@ -360,6 +334,10 @@ define_Conf! {
|
|||
///
|
||||
/// The minimum number of enum variants for the lints about variant names to trigger
|
||||
(enum_variant_name_threshold: u64 = 3),
|
||||
/// Lint: STRUCT_VARIANT_NAMES.
|
||||
///
|
||||
/// The minimum number of struct fields for the lints about field names to trigger
|
||||
(struct_field_name_threshold: u64 = 3),
|
||||
/// Lint: LARGE_ENUM_VARIANT.
|
||||
///
|
||||
/// The maximum size of an enum's variant to avoid box suggestion
|
||||
|
|
@ -641,15 +619,8 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the `toml` configuration file.
|
||||
///
|
||||
/// In case of error, the function tries to continue as much as possible.
|
||||
pub fn read(sess: &Session, path: &Path) -> TryConf {
|
||||
let file = match sess.source_map().load_file(path) {
|
||||
Err(e) => return e.into(),
|
||||
Ok(file) => file,
|
||||
};
|
||||
match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(&file)) {
|
||||
fn deserialize(file: &SourceFile) -> TryConf {
|
||||
match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
|
||||
Ok(mut conf) => {
|
||||
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
|
||||
extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
|
||||
|
|
@ -662,7 +633,7 @@ pub fn read(sess: &Session, path: &Path) -> TryConf {
|
|||
|
||||
conf
|
||||
},
|
||||
Err(e) => TryConf::from_toml_error(&file, &e),
|
||||
Err(e) => TryConf::from_toml_error(file, &e),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -672,6 +643,60 @@ fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
|
|||
}
|
||||
}
|
||||
|
||||
impl Conf {
|
||||
pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
|
||||
static CONF: OnceLock<Conf> = OnceLock::new();
|
||||
CONF.get_or_init(|| Conf::read_inner(sess, path))
|
||||
}
|
||||
|
||||
fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
|
||||
match path {
|
||||
Ok((_, warnings)) => {
|
||||
for warning in warnings {
|
||||
sess.warn(warning.clone());
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
sess.err(format!("error finding Clippy's configuration file: {error}"));
|
||||
},
|
||||
}
|
||||
|
||||
let TryConf {
|
||||
mut conf,
|
||||
errors,
|
||||
warnings,
|
||||
} = match path {
|
||||
Ok((Some(path), _)) => match sess.source_map().load_file(path) {
|
||||
Ok(file) => deserialize(&file),
|
||||
Err(error) => {
|
||||
sess.err(format!("failed to read `{}`: {error}", path.display()));
|
||||
TryConf::default()
|
||||
},
|
||||
},
|
||||
_ => TryConf::default(),
|
||||
};
|
||||
|
||||
conf.msrv.read_cargo(sess);
|
||||
|
||||
// all conf errors are non-fatal, we just use the default conf in case of error
|
||||
for error in errors {
|
||||
sess.span_err(
|
||||
error.span,
|
||||
format!("error reading Clippy's configuration file: {}", error.message),
|
||||
);
|
||||
}
|
||||
|
||||
for warning in warnings {
|
||||
sess.span_warn(
|
||||
warning.span,
|
||||
format!("error reading Clippy's configuration file: {}", warning.message),
|
||||
);
|
||||
}
|
||||
|
||||
conf
|
||||
}
|
||||
}
|
||||
|
||||
const SEPARATOR_WIDTH: usize = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
|
|||
if_chain_local_span(cx, local, if_chain_span),
|
||||
"`let` expression should be above the `if_chain!`",
|
||||
);
|
||||
} else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
|
||||
} else if local.span.eq_ctxt(block.span) && is_if_chain_then(after, block.expr, if_chain_span) {
|
||||
span_lint(
|
||||
cx,
|
||||
IF_CHAIN_STYLE,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use rustc_lint::{LateContext, LateLintPass};
|
|||
use rustc_middle::mir::ConstValue;
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Symbol;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -160,12 +161,8 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
|
|||
|
||||
impl InterningDefinedSymbol {
|
||||
fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
|
||||
static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD];
|
||||
static SYMBOL_STR_PATHS: &[&[&str]] = &[
|
||||
&paths::SYMBOL_AS_STR,
|
||||
&paths::SYMBOL_TO_IDENT_STRING,
|
||||
&paths::TO_STRING_METHOD,
|
||||
];
|
||||
static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR];
|
||||
static SYMBOL_STR_PATHS: &[&[&str]] = &[&paths::SYMBOL_AS_STR, &paths::SYMBOL_TO_IDENT_STRING];
|
||||
let call = if_chain! {
|
||||
if let ExprKind::AddrOf(_, _, e) = expr.kind;
|
||||
if let ExprKind::Unary(UnOp::Deref, e) = e.kind;
|
||||
|
|
@ -186,9 +183,19 @@ impl InterningDefinedSymbol {
|
|||
};
|
||||
// ...which converts it to a string
|
||||
let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS };
|
||||
if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path));
|
||||
if let Some(is_to_owned) = paths
|
||||
.iter()
|
||||
.find_map(|path| if match_def_path(cx, did, path) {
|
||||
Some(path == &paths::SYMBOL_TO_IDENT_STRING)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.or_else(|| if cx.tcx.is_diagnostic_item(sym::to_string_method, did) {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
then {
|
||||
let is_to_owned = path.last().unwrap().ends_with("string");
|
||||
return Some(SymbolStrExpr::Expr {
|
||||
item,
|
||||
is_ident,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue