Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 6658db1

Browse files
committed
Metadata collection: processing emission closures (417/455)
1 parent 5830fa7 commit 6658db1

File tree

3 files changed

+190
-29
lines changed

3 files changed

+190
-29
lines changed

clippy_lints/src/slow_vector_initialization.rs

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -157,26 +157,16 @@ impl SlowVectorInit {
157157
vec_alloc: &VecAllocation<'_>,
158158
) {
159159
match initialization {
160-
InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint(
161-
cx,
162-
e,
163-
vec_alloc,
164-
"slow zero-filling initialization",
165-
SLOW_VECTOR_INITIALIZATION,
166-
),
160+
InitializationType::Extend(e) | InitializationType::Resize(e) => {
161+
Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization")
162+
},
167163
};
168164
}
169165

170-
fn emit_lint<'tcx>(
171-
cx: &LateContext<'tcx>,
172-
slow_fill: &Expr<'_>,
173-
vec_alloc: &VecAllocation<'_>,
174-
msg: &str,
175-
lint: &'static Lint,
176-
) {
166+
fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
177167
let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
178168

179-
span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| {
169+
span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
180170
diag.span_suggestion(
181171
vec_alloc.allocation_expr.span,
182172
"consider replace allocation with",

clippy_lints/src/utils/internal_lints/metadata_collector.rs

Lines changed: 183 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,25 @@
1515
1616
// # Applicability
1717
// - TODO xFrednet 2021-01-17: Find lint emit and collect applicability
18-
// - TODO xFrednet 2021-02-27: Link applicability from function parameters
19-
// - (Examples: suspicious_operation_groupings:267, needless_bool.rs:311)
20-
// - TODO xFrednet 2021-02-27: Tuple if let thingy
21-
// - (Examples: unused_unit.rs:140, misc.rs:694)
18+
// - TODO xFrednet 2021-02-28: 1x reference to closure
19+
// - See clippy_lints/src/needless_pass_by_value.rs@NeedlessPassByValue::check_fn
20+
// - TODO xFrednet 2021-02-28: 4x weird emission forwarding
21+
// - See clippy_lints/src/enum_variants.rs@EnumVariantNames::check_name
22+
// - TODO xFrednet 2021-02-28: 6x emission forwarding with local that is initializes from
23+
// function.
24+
// - See clippy_lints/src/methods/mod.rs@lint_binary_expr_with_method_call
25+
// - TODO xFrednet 2021-02-28: 2x lint from local from function call
26+
// - See clippy_lints/src/misc.rs@check_binary
27+
// - TODO xFrednet 2021-02-28: 2x lint from local from method call
28+
// - See clippy_lints/src/non_copy_const.rs@lint
29+
// - TODO xFrednet 2021-02-28: 20x lint from local
30+
// - See clippy_lints/src/map_unit_fn.rs@lint_map_unit_fn
2231
// # NITs
2332
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
2433

2534
use if_chain::if_chain;
2635
use rustc_data_structures::fx::FxHashMap;
27-
use rustc_hir::{self as hir, intravisit, ExprKind, Item, ItemKind, Mutability};
36+
use rustc_hir::{self as hir, intravisit, ExprKind, Item, ItemKind, Mutability, QPath};
2837
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
2938
use rustc_middle::hir::map::Map;
3039
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -44,18 +53,36 @@ const OUTPUT_FILE: &str = "metadata_collection.json";
4453
/// These lints are excluded from the export.
4554
const BLACK_LISTED_LINTS: [&str; 2] = ["lint_author", "deep_code_inspection"];
4655
/// These groups will be ignored by the lint group matcher
47-
const BLACK_LISTED_LINT_GROUP: [&str; 1] = ["clippy::all"];
56+
const BLACK_LISTED_LINT_GROUP: [&str; 1] = ["clippy::all", "clippy::internal"];
4857

4958
// TODO xFrednet 2021-02-15: `span_lint_and_then` & `span_lint_hir_and_then` requires special
5059
// handling
51-
#[rustfmt::skip]
52-
const LINT_EMISSION_FUNCTIONS: [&[&str]; 5] = [
60+
const SIMPLE_LINT_EMISSION_FUNCTIONS: [&[&str]; 5] = [
5361
&["clippy_utils", "diagnostics", "span_lint"],
5462
&["clippy_utils", "diagnostics", "span_lint_and_help"],
5563
&["clippy_utils", "diagnostics", "span_lint_and_note"],
5664
&["clippy_utils", "diagnostics", "span_lint_hir"],
5765
&["clippy_utils", "diagnostics", "span_lint_and_sugg"],
5866
];
67+
const COMPLEX_LINT_EMISSION_FUNCTIONS: [&[&str]; 2] = [
68+
&["clippy_utils", "diagnostics", "span_lint_and_then"],
69+
&["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
70+
];
71+
const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
72+
("span_suggestion", false),
73+
("span_suggestion_short", false),
74+
("span_suggestion_verbose", false),
75+
("span_suggestion_hidden", false),
76+
("tool_only_span_suggestion", false),
77+
("multipart_suggestion", true),
78+
("multipart_suggestions", true),
79+
("tool_only_multipart_suggestion", true),
80+
("span_suggestions", true),
81+
];
82+
const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
83+
&["clippy_utils", "diagnostics", "mutispan_sugg"],
84+
&["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
85+
];
5986

6087
/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
6188
const APPLICABILITY_NAME_INDEX: usize = 2;
@@ -177,7 +204,7 @@ struct ApplicabilityInfo {
177204
/// Indicates if any of the lint emissions uses multiple spans. This is related to
178205
/// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
179206
/// currently not be applied automatically.
180-
has_multi_suggestion: bool,
207+
is_multi_suggestion: bool,
181208
applicability: Option<String>,
182209
}
183210

@@ -250,6 +277,14 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
250277
} else {
251278
lint_collection_error_span(cx, expr.span, "I found this but I can't get the lint or applicability");
252279
}
280+
} else if let Some(args) = match_complex_lint_emission(cx, expr) {
281+
if let Some((lint_name, applicability, is_multi_span)) = extract_complex_emission_info(cx, args) {
282+
let app_info = self.applicability_into.entry(lint_name).or_default();
283+
app_info.applicability = applicability;
284+
app_info.is_multi_suggestion = is_multi_span;
285+
} else {
286+
lint_collection_error_span(cx, expr.span, "Look, here ... I have no clue what todo with it");
287+
}
253288
}
254289
}
255290
}
@@ -347,7 +382,16 @@ fn match_simple_lint_emission<'hir>(
347382
cx: &LateContext<'hir>,
348383
expr: &'hir hir::Expr<'_>,
349384
) -> Option<&'hir [hir::Expr<'hir>]> {
350-
LINT_EMISSION_FUNCTIONS
385+
SIMPLE_LINT_EMISSION_FUNCTIONS
386+
.iter()
387+
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
388+
}
389+
390+
fn match_complex_lint_emission<'hir>(
391+
cx: &LateContext<'hir>,
392+
expr: &'hir hir::Expr<'_>,
393+
) -> Option<&'hir [hir::Expr<'hir>]> {
394+
COMPLEX_LINT_EMISSION_FUNCTIONS
351395
.iter()
352396
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
353397
}
@@ -376,28 +420,60 @@ fn extract_emission_info<'hir>(
376420
lint_name.map(|lint_name| (sym_to_string(lint_name).to_ascii_lowercase(), applicability))
377421
}
378422

423+
fn extract_complex_emission_info<'hir>(
424+
cx: &LateContext<'hir>,
425+
args: &'hir [hir::Expr<'hir>],
426+
) -> Option<(String, Option<String>, bool)> {
427+
let mut lint_name = None;
428+
let mut applicability = None;
429+
let mut multi_span = false;
430+
431+
for arg in args {
432+
let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg));
433+
434+
if match_type(cx, arg_ty, &paths::LINT) {
435+
// If we found the lint arg, extract the lint name
436+
if let ExprKind::Path(ref lint_path) = arg.kind {
437+
lint_name = Some(last_path_segment(lint_path).ident.name);
438+
}
439+
} else if arg_ty.is_closure() {
440+
if let ExprKind::Closure(_, _, body_id, _, _) = arg.kind {
441+
let mut visitor = EmissionClosureVisitor::new(cx);
442+
intravisit::walk_body(&mut visitor, cx.tcx.hir().body(body_id));
443+
multi_span = visitor.found_multi_span();
444+
applicability = visitor.complete();
445+
} else {
446+
// TODO xfrednet 2021-02-28: linked closures, see: needless_pass_by_value.rs:292
447+
return None;
448+
}
449+
}
450+
}
451+
452+
lint_name.map(|lint_name| (sym_to_string(lint_name).to_ascii_lowercase(), applicability, multi_span))
453+
}
454+
379455
/// This function tries to resolve the linked applicability to the given expression.
380456
fn resolve_applicability(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<String> {
381457
match expr.kind {
382458
// We can ignore ifs without an else block because those can't be used as an assignment
383-
hir::ExprKind::If(_con, if_block, Some(else_block)) => {
459+
ExprKind::If(_con, if_block, Some(else_block)) => {
384460
let mut visitor = ApplicabilityVisitor::new(cx);
385461
intravisit::walk_expr(&mut visitor, if_block);
386462
intravisit::walk_expr(&mut visitor, else_block);
387463
visitor.complete()
388464
},
389-
hir::ExprKind::Match(_expr, arms, _) => {
465+
ExprKind::Match(_expr, arms, _) => {
390466
let mut visitor = ApplicabilityVisitor::new(cx);
391467
arms.iter()
392468
.for_each(|arm| intravisit::walk_expr(&mut visitor, arm.body));
393469
visitor.complete()
394470
},
395-
hir::ExprKind::Loop(block, ..) | hir::ExprKind::Block(block, ..) => {
471+
ExprKind::Loop(block, ..) | ExprKind::Block(block, ..) => {
396472
let mut visitor = ApplicabilityVisitor::new(cx);
397473
intravisit::walk_block(&mut visitor, block);
398474
visitor.complete()
399475
},
400-
ExprKind::Path(hir::QPath::Resolved(_, path)) => {
476+
ExprKind::Path(QPath::Resolved(_, path)) => {
401477
// direct applicabilities are simple:
402478
for enum_value in &paths::APPLICABILITY_VALUES {
403479
if match_path(path, enum_value) {
@@ -477,3 +553,96 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityVisitor<'a, 'hir> {
477553
}
478554
}
479555
}
556+
557+
/// This visitor finds the highest applicability value in the visited expressions
558+
struct EmissionClosureVisitor<'a, 'hir> {
559+
cx: &'a LateContext<'hir>,
560+
/// This is the index of hightest `Applicability` for
561+
/// `clippy_utils::paths::APPLICABILITY_VALUES`
562+
applicability_index: Option<usize>,
563+
suggestion_count: usize,
564+
}
565+
566+
impl<'a, 'hir> EmissionClosureVisitor<'a, 'hir> {
567+
fn new(cx: &'a LateContext<'hir>) -> Self {
568+
Self {
569+
cx,
570+
applicability_index: None,
571+
suggestion_count: 0,
572+
}
573+
}
574+
575+
fn add_new_index(&mut self, new_index: usize) {
576+
self.applicability_index = self
577+
.applicability_index
578+
.map_or(new_index, |old_index| old_index.min(new_index))
579+
.into();
580+
}
581+
582+
fn found_multi_span(&self) -> bool {
583+
self.suggestion_count > 1
584+
}
585+
586+
fn complete(self) -> Option<String> {
587+
self.applicability_index
588+
.map(|index| paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX].to_string())
589+
}
590+
}
591+
592+
impl<'a, 'hir> intravisit::Visitor<'hir> for EmissionClosureVisitor<'a, 'hir> {
593+
type Map = Map<'hir>;
594+
595+
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
596+
intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
597+
}
598+
599+
fn visit_path(&mut self, path: &hir::Path<'_>, _id: hir::HirId) {
600+
for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
601+
if match_path(path, enum_value) {
602+
self.add_new_index(index);
603+
break;
604+
}
605+
}
606+
}
607+
608+
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
609+
match &expr.kind {
610+
ExprKind::Call(fn_expr, _args) => {
611+
let found_function = SUGGESTION_FUNCTIONS
612+
.iter()
613+
.any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
614+
if found_function {
615+
// These functions are all multi part suggestions
616+
self.suggestion_count += 2;
617+
}
618+
},
619+
ExprKind::MethodCall(path, _path_span, arg, _arg_span) => {
620+
let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0]));
621+
if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
622+
let called_method = path.ident.name.as_str().to_string();
623+
let found_suggestion =
624+
SUGGESTION_DIAGNOSTIC_BUILDER_METHODS
625+
.iter()
626+
.find_map(|(method_name, is_multi_part)| {
627+
if *method_name == called_method {
628+
Some(*is_multi_part)
629+
} else {
630+
None
631+
}
632+
});
633+
if let Some(multi_part) = found_suggestion {
634+
if multi_part {
635+
// two is enough to have it marked as a multipart suggestion
636+
self.suggestion_count += 2;
637+
} else {
638+
self.suggestion_count += 1;
639+
}
640+
}
641+
}
642+
},
643+
_ => {},
644+
}
645+
646+
intravisit::walk_expr(self, expr);
647+
}
648+
}

clippy_utils/src/paths.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
1414
["rustc_lint_defs", "Applicability", "HasPlaceholders"],
1515
["rustc_lint_defs", "Applicability", "Unspecified"],
1616
];
17+
#[cfg(feature = "metadata-collector-lint")]
18+
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
1719
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
1820
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
1921
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];

0 commit comments

Comments
 (0)