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

Commit 8dca1b8

Browse files
committed
Metadata collection: Collecting Applicability assign values
1 parent 68d702f commit 8dca1b8

File tree

3 files changed

+248
-54
lines changed

3 files changed

+248
-54
lines changed

clippy_lints/src/utils/internal_lints/metadata_collector.rs

Lines changed: 195 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use if_chain::if_chain;
2424
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2525
use rustc_hir::{self as hir, ExprKind, Item, ItemKind, Mutability};
2626
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
27-
use rustc_middle::ty::BorrowKind;
27+
use rustc_middle::ty::{BorrowKind, Ty};
2828
use rustc_session::{declare_tool_lint, impl_lint_pass};
2929
use rustc_span::{sym, Loc, Span, Symbol};
3030
use rustc_trait_selection::infer::TyCtxtInferExt;
@@ -37,7 +37,8 @@ use std::path::Path;
3737

3838
use crate::utils::internal_lints::is_lint_ref_type;
3939
use crate::utils::{
40-
last_path_segment, match_function_call, match_type, path_to_local_id, paths, span_lint, walk_ptrs_ty_depth,
40+
get_enclosing_body, get_parent_expr_for_hir, last_path_segment, match_function_call, match_qpath, match_type,
41+
path_to_local_id, paths, span_lint, walk_ptrs_ty_depth,
4142
};
4243

4344
/// This is the output file of the lint collector.
@@ -147,6 +148,12 @@ struct SerializableSpan {
147148
line: usize,
148149
}
149150

151+
impl std::fmt::Display for SerializableSpan {
152+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153+
write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
154+
}
155+
}
156+
150157
impl SerializableSpan {
151158
fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
152159
Self::from_span(cx, item.ident.span)
@@ -285,52 +292,54 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
285292
/// );
286293
/// ```
287294
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx hir::Local<'tcx>) {
288-
if let Some(tc) = cx.maybe_typeck_results() {
289-
// TODO xFrednet 2021-02-14: support nested applicability (only in tuples)
290-
let local_ty = if let Some(ty) = local.ty {
291-
hir_ty_to_ty(cx.tcx, ty)
292-
} else if let Some(init) = local.init {
293-
tc.expr_ty(init)
294-
} else {
295-
return;
296-
};
297-
298-
if_chain! {
299-
if match_type(cx, local_ty, &paths::APPLICABILITY);
300-
if let Some(body) = get_parent_body(cx, local.hir_id);
301-
then {
302-
let span = SerializableSpan::from_span(cx, local.span);
303-
let local_str = crate::utils::snippet(cx, local.span, "_");
304-
let value_life = format!("{} -- {}:{}\n", local_str, span.path.rsplit('/').next().unwrap_or_default(), span.line);
305-
let value_hir_id = local.pat.hir_id;
306-
let mut tracker = ValueTracker {cx, value_hir_id, value_life};
307-
308-
cx.tcx.infer_ctxt().enter(|infcx| {
309-
let body_owner_id = cx.tcx.hir().body_owner_def_id(body.id());
310-
ExprUseVisitor::new(
311-
&mut tracker,
312-
&infcx,
313-
body_owner_id,
314-
cx.param_env,
315-
cx.typeck_results()
316-
)
317-
.consume_body(body);
318-
});
319-
320-
log_to_file(&tracker.value_life);
321-
lint_collection_error_span(cx, local.span, "Applicability value found");
295+
if_chain! {
296+
if let Some(local_ty) = get_local_type(cx, local);
297+
if match_type(cx, local_ty, &paths::APPLICABILITY);
298+
if let Some(body) = get_enclosing_body(cx, local.hir_id);
299+
then {
300+
// TODO xFrednet: 2021-02-19: Remove debug code
301+
let span = SerializableSpan::from_span(cx, local.span);
302+
let local_str = crate::utils::snippet(cx, local.span, "_");
303+
log_to_file(&format!("{} -- {}\n", local_str, span));
304+
305+
let value_hir_id = local.pat.hir_id;
306+
let mut tracker = ValueTracker::new(cx, value_hir_id);
307+
if let Some(init_expr) = local.init {
308+
tracker.process_assign_expr(init_expr)
322309
}
310+
311+
// TODO xFrednet 2021-02-18: Support nested bodies
312+
// Note: The `ExprUseVisitor` only searches though one body, this means that values
313+
// references in nested bodies like closures are not found by this simple visitor.
314+
cx.tcx.infer_ctxt().enter(|infcx| {
315+
let body_owner_id = cx.tcx.hir().body_owner_def_id(body.id());
316+
ExprUseVisitor::new(
317+
&mut tracker,
318+
&infcx,
319+
body_owner_id,
320+
cx.param_env,
321+
cx.typeck_results()
322+
)
323+
.consume_body(body);
324+
});
325+
326+
log_to_file(&format!("{:?}\n", tracker.value_mutations));
323327
}
324328
}
325329
}
326330
}
327331

328-
fn get_parent_body<'a, 'tcx>(cx: &'a LateContext<'tcx>, id: hir::HirId) -> Option<&'tcx hir::Body<'tcx>> {
329-
let map = cx.tcx.hir();
332+
fn get_local_type<'a>(cx: &'a LateContext<'_>, local: &'a hir::Local<'_>) -> Option<Ty<'a>> {
333+
// TODO xFrednet 2021-02-14: support nested applicability (only in tuples)
334+
if let Some(tc) = cx.maybe_typeck_results() {
335+
if let Some(ty) = local.ty {
336+
return Some(hir_ty_to_ty(cx.tcx, ty));
337+
} else if let Some(init) = local.init {
338+
return Some(tc.expr_ty(init));
339+
}
340+
}
330341

331-
map.parent_iter(id)
332-
.find_map(|(parent, _)| map.maybe_body_owned_by(parent))
333-
.map(|body| map.body(body))
342+
None
334343
}
335344

336345
fn sym_to_string(sym: Symbol) -> String {
@@ -429,42 +438,174 @@ fn extract_emission_info<'tcx>(cx: &LateContext<'tcx>, args: &[hir::Expr<'_>]) -
429438
})
430439
}
431440

432-
struct ValueTracker<'a, 'tcx> {
433-
cx: &'a LateContext<'tcx>,
441+
#[allow(dead_code)]
442+
struct ValueTracker<'a, 'hir> {
443+
cx: &'a LateContext<'hir>,
434444
value_hir_id: hir::HirId,
435-
value_life: String,
445+
value_mutations: Vec<ApplicabilityModifier<'hir>>,
436446
}
437447

438-
impl<'a, 'tcx> ValueTracker<'a, 'tcx> {
448+
impl<'a, 'hir> ValueTracker<'a, 'hir> {
449+
fn new(cx: &'a LateContext<'hir>, value_hir_id: hir::HirId) -> Self {
450+
Self {
451+
cx,
452+
value_hir_id,
453+
value_mutations: Vec::new(),
454+
}
455+
}
456+
439457
fn is_value_expr(&self, expr_id: hir::HirId) -> bool {
440458
match self.cx.tcx.hir().find(expr_id) {
441459
Some(hir::Node::Expr(expr)) => path_to_local_id(expr, self.value_hir_id),
442460
_ => false,
443461
}
444462
}
463+
464+
/// This function extracts possible `ApplicabilityModifier` from an assign statement like this:
465+
///
466+
/// ```rust, ignore
467+
/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv The expression to process
468+
/// let value = Applicability::MachineApplicable;
469+
/// ```
470+
fn process_assign_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
471+
// This is a bit more complicated. I'll therefor settle on the simple solution of
472+
// simplifying the cases we support.
473+
match &expr.kind {
474+
hir::ExprKind::Call(func_expr, ..) => {
475+
// We only deal with resolved paths as this is the usual case. Other expression kinds like closures
476+
// etc. are hard to track but might be a worthy improvement in the future
477+
if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = func_expr.kind {
478+
self.value_mutations.push(ApplicabilityModifier::Producer(path));
479+
} else {
480+
let msg = format!(
481+
"Unsupported Call expression at: {}",
482+
SerializableSpan::from_span(self.cx, func_expr.span)
483+
);
484+
self.value_mutations.push(ApplicabilityModifier::Unknown(msg));
485+
}
486+
},
487+
hir::ExprKind::MethodCall(..) => {
488+
let msg = format!(
489+
"Unsupported MethodCall expression at: {}",
490+
SerializableSpan::from_span(self.cx, expr.span)
491+
);
492+
self.value_mutations.push(ApplicabilityModifier::Unknown(msg));
493+
},
494+
// We can ignore ifs without an else block because those can't be used as an assignment
495+
hir::ExprKind::If(_con, if_block, Some(else_block)) => {
496+
self.process_assign_expr(if_block);
497+
self.process_assign_expr(else_block);
498+
},
499+
hir::ExprKind::Match(_expr, arms, _) => {
500+
for arm in *arms {
501+
self.process_assign_expr(arm.body);
502+
}
503+
},
504+
hir::ExprKind::Loop(block, ..) | hir::ExprKind::Block(block, ..) => {
505+
if let Some(block_expr) = block.expr {
506+
self.process_assign_expr(block_expr);
507+
}
508+
},
509+
hir::ExprKind::Path(path) => {
510+
for enum_value in &paths::APPLICABILITY_VALUES {
511+
if match_qpath(path, enum_value) {
512+
self.value_mutations
513+
.push(ApplicabilityModifier::ConstValue(enum_value[2].to_string()));
514+
}
515+
}
516+
},
517+
// hir::ExprKind::Field(expr, ident) => not supported
518+
// hir::ExprKind::Index(expr, expr) => not supported
519+
_ => {
520+
let msg = format!(
521+
"Unexpected expression at: {}",
522+
SerializableSpan::from_span(self.cx, expr.span)
523+
);
524+
self.value_mutations.push(ApplicabilityModifier::Unknown(msg));
525+
},
526+
}
527+
}
445528
}
446529

447-
impl<'a, 'tcx> Delegate<'tcx> for ValueTracker<'a, 'tcx> {
448-
fn consume(&mut self, _place_with_id: &PlaceWithHirId<'tcx>, expr_id: hir::HirId, _: ConsumeMode) {
530+
impl<'a, 'hir> Delegate<'hir> for ValueTracker<'a, 'hir> {
531+
fn consume(&mut self, _place_with_id: &PlaceWithHirId<'hir>, expr_id: hir::HirId, _: ConsumeMode) {
449532
if self.is_value_expr(expr_id) {
450533
// TODO xFrednet 2021-02-17: Check if lint emission and extract lint ID
451-
todo!();
534+
if let Some(hir::Node::Expr(expr)) = self.cx.tcx.hir().find(expr_id) {
535+
let span = SerializableSpan::from_span(self.cx, expr.span);
536+
log_to_file(&format!("- consume {}\n", span));
537+
}
452538
}
453539
}
454540

455-
fn borrow(&mut self, _place_with_id: &PlaceWithHirId<'tcx>, expr_id: hir::HirId, bk: BorrowKind) {
541+
fn borrow(&mut self, _place_with_id: &PlaceWithHirId<'hir>, expr_id: hir::HirId, bk: BorrowKind) {
456542
if self.is_value_expr(expr_id) {
457543
if let BorrowKind::MutBorrow = bk {
458544
// TODO xFrednet 2021-02-17: Save the function
459-
todo!();
545+
if let Some(hir::Node::Expr(expr)) = self.cx.tcx.hir().find(expr_id) {
546+
let span = SerializableSpan::from_span(self.cx, expr.span);
547+
log_to_file(&format!("- &mut {}\n", span));
548+
}
460549
}
461550
}
462551
}
463552

464-
fn mutate(&mut self, _assignee_place: &PlaceWithHirId<'tcx>, expr_id: hir::HirId) {
465-
if self.is_value_expr(expr_id) {
466-
// TODO xFrednet 2021-02-17: Save the new value as a mutation
467-
todo!();
553+
fn mutate(&mut self, _assignee_place: &PlaceWithHirId<'hir>, expr_id: hir::HirId) {
554+
if_chain! {
555+
if self.is_value_expr(expr_id);
556+
if let Some(expr) = get_parent_expr_for_hir(self.cx, expr_id);
557+
if let hir::ExprKind::Assign(_value_expr, assign_expr, ..) = expr.kind;
558+
then {
559+
self.process_assign_expr(assign_expr);
560+
}
468561
}
469562
}
470563
}
564+
565+
/// The life of a value in Rust is a true adventure. These are the corner stones of such a
566+
/// fairy tale. Let me introduce you to the possible stepping stones a value might have in
567+
/// in our crazy word:
568+
#[derive(Debug)]
569+
#[allow(dead_code)]
570+
enum ApplicabilityModifier<'hir> {
571+
Unknown(String),
572+
/// A simple constant value.
573+
///
574+
/// This is the actual character of a value. It's baseline. This only defines where the value
575+
/// started. As in real life it can still change and fully decide who it wants to be.
576+
ConstValue(String),
577+
/// A producer is a function that returns an applicability value.
578+
///
579+
/// This is the heritage of this value. This value comes from a long family tree and is not
580+
/// just a black piece of paper. The evaluation of this stepping stone needs additional
581+
/// context. We therefore only add a reference. This reference will later be used to ask
582+
/// the librarian about the possible initial character that this value might have.
583+
Producer(&'hir hir::Path<'hir>),
584+
/// A modifier that takes the given applicability and might modify it
585+
///
586+
/// What would an RPG be without it's NPCs. The special thing about modifiers is that they can
587+
/// be actively interested in the story of the value and might make decisions based on the
588+
/// character of this hero. This means that a modifier doesn't just force its way into the life
589+
/// of our hero but it actually asks him how he's been. The possible modification is a result
590+
/// of the situation.
591+
///
592+
/// Take this part of our heroes life very seriously!
593+
Modifier(&'hir hir::Path<'hir>),
594+
/// The actual emission of a lint
595+
///
596+
/// All good things must come to an end. Even the life of your awesome applicability hero. He
597+
/// was the bravest soul that has ever wondered this earth. Songs will be written about his
598+
/// heroic deeds. Castles will be named after him and the world how we know it will never be
599+
/// the same!
600+
///
601+
/// Is this a happy ending? Did he archive what he wanted in his life? Yes, YES, he has lived a
602+
/// life and he will continue to live in all the lint suggestions that can be applied or just
603+
/// displayed by Clippy. He might be no more, but his legacy will serve generations to come.
604+
LintEmit(LintEmission),
605+
}
606+
607+
#[derive(Debug)]
608+
struct LintEmission {
609+
lint: String,
610+
is_multi_line_sugg: bool,
611+
}

clippy_utils/src/paths.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"];
88
#[cfg(feature = "metadata-collector-lint")]
99
pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
10+
#[cfg(feature = "metadata-collector-lint")]
11+
pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
12+
["rustc_lint_defs", "Applicability", "MachineApplicable"],
13+
["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
14+
["rustc_lint_defs", "Applicability", "HasPlaceholders"],
15+
["rustc_lint_defs", "Applicability", "Unspecified"],
16+
];
1017
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
1118
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
1219
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#![deny(clippy::internal)]
2+
#![feature(rustc_private)]
3+
4+
extern crate rustc_ast;
5+
extern crate rustc_errors;
6+
extern crate rustc_lint;
7+
extern crate rustc_session;
8+
extern crate rustc_span;
9+
10+
use rustc_ast::ast::Expr;
11+
use rustc_errors::{Applicability, DiagnosticBuilder};
12+
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
13+
use rustc_session::{declare_lint_pass, declare_tool_lint};
14+
use rustc_span::source_map::Span;
15+
16+
fn producer_fn() -> Applicability {
17+
Applicability::MachineApplicable
18+
}
19+
20+
fn modifier_fn(applicability: &mut Applicability) {
21+
if let Applicability::MaybeIncorrect = applicability {
22+
*applicability = Applicability::HasPlaceholders;
23+
}
24+
}
25+
26+
struct Muh;
27+
28+
impl Muh {
29+
fn producer_method() -> Applicability {
30+
Applicability::MachineApplicable
31+
}
32+
}
33+
34+
fn main() {
35+
let mut applicability = producer_fn();
36+
applicability = Applicability::MachineApplicable;
37+
applicability = Muh::producer_method();
38+
39+
applicability = if true {
40+
Applicability::HasPlaceholders
41+
} else {
42+
Applicability::MaybeIncorrect
43+
};
44+
45+
modifier_fn(&mut applicability);
46+
}

0 commit comments

Comments
 (0)