Metadata collection: processing emission closures (417/455)
This commit is contained in:
parent
5830fa7c60
commit
6658db1044
@ -157,26 +157,16 @@ impl SlowVectorInit {
|
||||
vec_alloc: &VecAllocation<'_>,
|
||||
) {
|
||||
match initialization {
|
||||
InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint(
|
||||
cx,
|
||||
e,
|
||||
vec_alloc,
|
||||
"slow zero-filling initialization",
|
||||
SLOW_VECTOR_INITIALIZATION,
|
||||
),
|
||||
InitializationType::Extend(e) | InitializationType::Resize(e) => {
|
||||
Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization")
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn emit_lint<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
slow_fill: &Expr<'_>,
|
||||
vec_alloc: &VecAllocation<'_>,
|
||||
msg: &str,
|
||||
lint: &'static Lint,
|
||||
) {
|
||||
fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
|
||||
let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
|
||||
|
||||
span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| {
|
||||
span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
|
||||
diag.span_suggestion(
|
||||
vec_alloc.allocation_expr.span,
|
||||
"consider replace allocation with",
|
||||
|
@ -15,16 +15,25 @@
|
||||
|
||||
// # Applicability
|
||||
// - TODO xFrednet 2021-01-17: Find lint emit and collect applicability
|
||||
// - TODO xFrednet 2021-02-27: Link applicability from function parameters
|
||||
// - (Examples: suspicious_operation_groupings:267, needless_bool.rs:311)
|
||||
// - TODO xFrednet 2021-02-27: Tuple if let thingy
|
||||
// - (Examples: unused_unit.rs:140, misc.rs:694)
|
||||
// - TODO xFrednet 2021-02-28: 1x reference to closure
|
||||
// - See clippy_lints/src/needless_pass_by_value.rs@NeedlessPassByValue::check_fn
|
||||
// - TODO xFrednet 2021-02-28: 4x weird emission forwarding
|
||||
// - See clippy_lints/src/enum_variants.rs@EnumVariantNames::check_name
|
||||
// - TODO xFrednet 2021-02-28: 6x emission forwarding with local that is initializes from
|
||||
// function.
|
||||
// - See clippy_lints/src/methods/mod.rs@lint_binary_expr_with_method_call
|
||||
// - TODO xFrednet 2021-02-28: 2x lint from local from function call
|
||||
// - See clippy_lints/src/misc.rs@check_binary
|
||||
// - TODO xFrednet 2021-02-28: 2x lint from local from method call
|
||||
// - See clippy_lints/src/non_copy_const.rs@lint
|
||||
// - TODO xFrednet 2021-02-28: 20x lint from local
|
||||
// - See clippy_lints/src/map_unit_fn.rs@lint_map_unit_fn
|
||||
// # NITs
|
||||
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
|
||||
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::{self as hir, intravisit, ExprKind, Item, ItemKind, Mutability};
|
||||
use rustc_hir::{self as hir, intravisit, ExprKind, Item, ItemKind, Mutability, QPath};
|
||||
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
|
||||
use rustc_middle::hir::map::Map;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
@ -44,18 +53,36 @@ const OUTPUT_FILE: &str = "metadata_collection.json";
|
||||
/// These lints are excluded from the export.
|
||||
const BLACK_LISTED_LINTS: [&str; 2] = ["lint_author", "deep_code_inspection"];
|
||||
/// These groups will be ignored by the lint group matcher
|
||||
const BLACK_LISTED_LINT_GROUP: [&str; 1] = ["clippy::all"];
|
||||
const BLACK_LISTED_LINT_GROUP: [&str; 1] = ["clippy::all", "clippy::internal"];
|
||||
|
||||
// TODO xFrednet 2021-02-15: `span_lint_and_then` & `span_lint_hir_and_then` requires special
|
||||
// handling
|
||||
#[rustfmt::skip]
|
||||
const LINT_EMISSION_FUNCTIONS: [&[&str]; 5] = [
|
||||
const SIMPLE_LINT_EMISSION_FUNCTIONS: [&[&str]; 5] = [
|
||||
&["clippy_utils", "diagnostics", "span_lint"],
|
||||
&["clippy_utils", "diagnostics", "span_lint_and_help"],
|
||||
&["clippy_utils", "diagnostics", "span_lint_and_note"],
|
||||
&["clippy_utils", "diagnostics", "span_lint_hir"],
|
||||
&["clippy_utils", "diagnostics", "span_lint_and_sugg"],
|
||||
];
|
||||
const COMPLEX_LINT_EMISSION_FUNCTIONS: [&[&str]; 2] = [
|
||||
&["clippy_utils", "diagnostics", "span_lint_and_then"],
|
||||
&["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
|
||||
];
|
||||
const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
|
||||
("span_suggestion", false),
|
||||
("span_suggestion_short", false),
|
||||
("span_suggestion_verbose", false),
|
||||
("span_suggestion_hidden", false),
|
||||
("tool_only_span_suggestion", false),
|
||||
("multipart_suggestion", true),
|
||||
("multipart_suggestions", true),
|
||||
("tool_only_multipart_suggestion", true),
|
||||
("span_suggestions", true),
|
||||
];
|
||||
const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
|
||||
&["clippy_utils", "diagnostics", "mutispan_sugg"],
|
||||
&["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
|
||||
];
|
||||
|
||||
/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
|
||||
const APPLICABILITY_NAME_INDEX: usize = 2;
|
||||
@ -177,7 +204,7 @@ struct ApplicabilityInfo {
|
||||
/// Indicates if any of the lint emissions uses multiple spans. This is related to
|
||||
/// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
|
||||
/// currently not be applied automatically.
|
||||
has_multi_suggestion: bool,
|
||||
is_multi_suggestion: bool,
|
||||
applicability: Option<String>,
|
||||
}
|
||||
|
||||
@ -250,6 +277,14 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
|
||||
} else {
|
||||
lint_collection_error_span(cx, expr.span, "I found this but I can't get the lint or applicability");
|
||||
}
|
||||
} else if let Some(args) = match_complex_lint_emission(cx, expr) {
|
||||
if let Some((lint_name, applicability, is_multi_span)) = extract_complex_emission_info(cx, args) {
|
||||
let app_info = self.applicability_into.entry(lint_name).or_default();
|
||||
app_info.applicability = applicability;
|
||||
app_info.is_multi_suggestion = is_multi_span;
|
||||
} else {
|
||||
lint_collection_error_span(cx, expr.span, "Look, here ... I have no clue what todo with it");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,7 +382,16 @@ fn match_simple_lint_emission<'hir>(
|
||||
cx: &LateContext<'hir>,
|
||||
expr: &'hir hir::Expr<'_>,
|
||||
) -> Option<&'hir [hir::Expr<'hir>]> {
|
||||
LINT_EMISSION_FUNCTIONS
|
||||
SIMPLE_LINT_EMISSION_FUNCTIONS
|
||||
.iter()
|
||||
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
|
||||
}
|
||||
|
||||
fn match_complex_lint_emission<'hir>(
|
||||
cx: &LateContext<'hir>,
|
||||
expr: &'hir hir::Expr<'_>,
|
||||
) -> Option<&'hir [hir::Expr<'hir>]> {
|
||||
COMPLEX_LINT_EMISSION_FUNCTIONS
|
||||
.iter()
|
||||
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
|
||||
}
|
||||
@ -376,28 +420,60 @@ fn extract_emission_info<'hir>(
|
||||
lint_name.map(|lint_name| (sym_to_string(lint_name).to_ascii_lowercase(), applicability))
|
||||
}
|
||||
|
||||
fn extract_complex_emission_info<'hir>(
|
||||
cx: &LateContext<'hir>,
|
||||
args: &'hir [hir::Expr<'hir>],
|
||||
) -> Option<(String, Option<String>, bool)> {
|
||||
let mut lint_name = None;
|
||||
let mut applicability = None;
|
||||
let mut multi_span = false;
|
||||
|
||||
for arg in args {
|
||||
let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg));
|
||||
|
||||
if match_type(cx, arg_ty, &paths::LINT) {
|
||||
// If we found the lint arg, extract the lint name
|
||||
if let ExprKind::Path(ref lint_path) = arg.kind {
|
||||
lint_name = Some(last_path_segment(lint_path).ident.name);
|
||||
}
|
||||
} else if arg_ty.is_closure() {
|
||||
if let ExprKind::Closure(_, _, body_id, _, _) = arg.kind {
|
||||
let mut visitor = EmissionClosureVisitor::new(cx);
|
||||
intravisit::walk_body(&mut visitor, cx.tcx.hir().body(body_id));
|
||||
multi_span = visitor.found_multi_span();
|
||||
applicability = visitor.complete();
|
||||
} else {
|
||||
// TODO xfrednet 2021-02-28: linked closures, see: needless_pass_by_value.rs:292
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lint_name.map(|lint_name| (sym_to_string(lint_name).to_ascii_lowercase(), applicability, multi_span))
|
||||
}
|
||||
|
||||
/// This function tries to resolve the linked applicability to the given expression.
|
||||
fn resolve_applicability(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<String> {
|
||||
match expr.kind {
|
||||
// We can ignore ifs without an else block because those can't be used as an assignment
|
||||
hir::ExprKind::If(_con, if_block, Some(else_block)) => {
|
||||
ExprKind::If(_con, if_block, Some(else_block)) => {
|
||||
let mut visitor = ApplicabilityVisitor::new(cx);
|
||||
intravisit::walk_expr(&mut visitor, if_block);
|
||||
intravisit::walk_expr(&mut visitor, else_block);
|
||||
visitor.complete()
|
||||
},
|
||||
hir::ExprKind::Match(_expr, arms, _) => {
|
||||
ExprKind::Match(_expr, arms, _) => {
|
||||
let mut visitor = ApplicabilityVisitor::new(cx);
|
||||
arms.iter()
|
||||
.for_each(|arm| intravisit::walk_expr(&mut visitor, arm.body));
|
||||
visitor.complete()
|
||||
},
|
||||
hir::ExprKind::Loop(block, ..) | hir::ExprKind::Block(block, ..) => {
|
||||
ExprKind::Loop(block, ..) | ExprKind::Block(block, ..) => {
|
||||
let mut visitor = ApplicabilityVisitor::new(cx);
|
||||
intravisit::walk_block(&mut visitor, block);
|
||||
visitor.complete()
|
||||
},
|
||||
ExprKind::Path(hir::QPath::Resolved(_, path)) => {
|
||||
ExprKind::Path(QPath::Resolved(_, path)) => {
|
||||
// direct applicabilities are simple:
|
||||
for enum_value in &paths::APPLICABILITY_VALUES {
|
||||
if match_path(path, enum_value) {
|
||||
@ -477,3 +553,96 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityVisitor<'a, 'hir> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This visitor finds the highest applicability value in the visited expressions
|
||||
struct EmissionClosureVisitor<'a, 'hir> {
|
||||
cx: &'a LateContext<'hir>,
|
||||
/// This is the index of hightest `Applicability` for
|
||||
/// `clippy_utils::paths::APPLICABILITY_VALUES`
|
||||
applicability_index: Option<usize>,
|
||||
suggestion_count: usize,
|
||||
}
|
||||
|
||||
impl<'a, 'hir> EmissionClosureVisitor<'a, 'hir> {
|
||||
fn new(cx: &'a LateContext<'hir>) -> Self {
|
||||
Self {
|
||||
cx,
|
||||
applicability_index: None,
|
||||
suggestion_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_new_index(&mut self, new_index: usize) {
|
||||
self.applicability_index = self
|
||||
.applicability_index
|
||||
.map_or(new_index, |old_index| old_index.min(new_index))
|
||||
.into();
|
||||
}
|
||||
|
||||
fn found_multi_span(&self) -> bool {
|
||||
self.suggestion_count > 1
|
||||
}
|
||||
|
||||
fn complete(self) -> Option<String> {
|
||||
self.applicability_index
|
||||
.map(|index| paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX].to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'hir> intravisit::Visitor<'hir> for EmissionClosureVisitor<'a, 'hir> {
|
||||
type Map = Map<'hir>;
|
||||
|
||||
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
|
||||
intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &hir::Path<'_>, _id: hir::HirId) {
|
||||
for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
|
||||
if match_path(path, enum_value) {
|
||||
self.add_new_index(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
|
||||
match &expr.kind {
|
||||
ExprKind::Call(fn_expr, _args) => {
|
||||
let found_function = SUGGESTION_FUNCTIONS
|
||||
.iter()
|
||||
.any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
|
||||
if found_function {
|
||||
// These functions are all multi part suggestions
|
||||
self.suggestion_count += 2;
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(path, _path_span, arg, _arg_span) => {
|
||||
let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0]));
|
||||
if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
|
||||
let called_method = path.ident.name.as_str().to_string();
|
||||
let found_suggestion =
|
||||
SUGGESTION_DIAGNOSTIC_BUILDER_METHODS
|
||||
.iter()
|
||||
.find_map(|(method_name, is_multi_part)| {
|
||||
if *method_name == called_method {
|
||||
Some(*is_multi_part)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some(multi_part) = found_suggestion {
|
||||
if multi_part {
|
||||
// two is enough to have it marked as a multipart suggestion
|
||||
self.suggestion_count += 2;
|
||||
} else {
|
||||
self.suggestion_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
|
||||
["rustc_lint_defs", "Applicability", "HasPlaceholders"],
|
||||
["rustc_lint_defs", "Applicability", "Unspecified"],
|
||||
];
|
||||
#[cfg(feature = "metadata-collector-lint")]
|
||||
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
|
||||
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
|
||||
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
|
||||
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
|
||||
|
Loading…
x
Reference in New Issue
Block a user