15
15
16
16
// # Applicability
17
17
// - 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
22
31
// # NITs
23
32
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
24
33
25
34
use if_chain:: if_chain;
26
35
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 } ;
28
37
use rustc_lint:: { CheckLintNameResult , LateContext , LateLintPass , LintContext , LintId } ;
29
38
use rustc_middle:: hir:: map:: Map ;
30
39
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
@@ -44,18 +53,36 @@ const OUTPUT_FILE: &str = "metadata_collection.json";
44
53
/// These lints are excluded from the export.
45
54
const BLACK_LISTED_LINTS : [ & str ; 2 ] = [ "lint_author" , "deep_code_inspection" ] ;
46
55
/// 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" ] ;
48
57
49
58
// TODO xFrednet 2021-02-15: `span_lint_and_then` & `span_lint_hir_and_then` requires special
50
59
// handling
51
- #[ rustfmt:: skip]
52
- const LINT_EMISSION_FUNCTIONS : [ & [ & str ] ; 5 ] = [
60
+ const SIMPLE_LINT_EMISSION_FUNCTIONS : [ & [ & str ] ; 5 ] = [
53
61
& [ "clippy_utils" , "diagnostics" , "span_lint" ] ,
54
62
& [ "clippy_utils" , "diagnostics" , "span_lint_and_help" ] ,
55
63
& [ "clippy_utils" , "diagnostics" , "span_lint_and_note" ] ,
56
64
& [ "clippy_utils" , "diagnostics" , "span_lint_hir" ] ,
57
65
& [ "clippy_utils" , "diagnostics" , "span_lint_and_sugg" ] ,
58
66
] ;
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
+ ] ;
59
86
60
87
/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
61
88
const APPLICABILITY_NAME_INDEX : usize = 2 ;
@@ -177,7 +204,7 @@ struct ApplicabilityInfo {
177
204
/// Indicates if any of the lint emissions uses multiple spans. This is related to
178
205
/// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
179
206
/// currently not be applied automatically.
180
- has_multi_suggestion : bool ,
207
+ is_multi_suggestion : bool ,
181
208
applicability : Option < String > ,
182
209
}
183
210
@@ -250,6 +277,14 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
250
277
} else {
251
278
lint_collection_error_span ( cx, expr. span , "I found this but I can't get the lint or applicability" ) ;
252
279
}
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
+ }
253
288
}
254
289
}
255
290
}
@@ -347,7 +382,16 @@ fn match_simple_lint_emission<'hir>(
347
382
cx : & LateContext < ' hir > ,
348
383
expr : & ' hir hir:: Expr < ' _ > ,
349
384
) -> 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
351
395
. iter ( )
352
396
. find_map ( |emission_fn| match_function_call ( cx, expr, emission_fn) )
353
397
}
@@ -376,28 +420,60 @@ fn extract_emission_info<'hir>(
376
420
lint_name. map ( |lint_name| ( sym_to_string ( lint_name) . to_ascii_lowercase ( ) , applicability) )
377
421
}
378
422
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
+
379
455
/// This function tries to resolve the linked applicability to the given expression.
380
456
fn resolve_applicability ( cx : & LateContext < ' hir > , expr : & ' hir hir:: Expr < ' hir > ) -> Option < String > {
381
457
match expr. kind {
382
458
// 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) ) => {
384
460
let mut visitor = ApplicabilityVisitor :: new ( cx) ;
385
461
intravisit:: walk_expr ( & mut visitor, if_block) ;
386
462
intravisit:: walk_expr ( & mut visitor, else_block) ;
387
463
visitor. complete ( )
388
464
} ,
389
- hir :: ExprKind :: Match ( _expr, arms, _) => {
465
+ ExprKind :: Match ( _expr, arms, _) => {
390
466
let mut visitor = ApplicabilityVisitor :: new ( cx) ;
391
467
arms. iter ( )
392
468
. for_each ( |arm| intravisit:: walk_expr ( & mut visitor, arm. body ) ) ;
393
469
visitor. complete ( )
394
470
} ,
395
- hir :: ExprKind :: Loop ( block, ..) | hir :: ExprKind :: Block ( block, ..) => {
471
+ ExprKind :: Loop ( block, ..) | ExprKind :: Block ( block, ..) => {
396
472
let mut visitor = ApplicabilityVisitor :: new ( cx) ;
397
473
intravisit:: walk_block ( & mut visitor, block) ;
398
474
visitor. complete ( )
399
475
} ,
400
- ExprKind :: Path ( hir :: QPath :: Resolved ( _, path) ) => {
476
+ ExprKind :: Path ( QPath :: Resolved ( _, path) ) => {
401
477
// direct applicabilities are simple:
402
478
for enum_value in & paths:: APPLICABILITY_VALUES {
403
479
if match_path ( path, enum_value) {
@@ -477,3 +553,96 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityVisitor<'a, 'hir> {
477
553
}
478
554
}
479
555
}
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
+ }
0 commit comments