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
2534use if_chain:: if_chain;
2635use 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 } ;
2837use rustc_lint:: { CheckLintNameResult , LateContext , LateLintPass , LintContext , LintId } ;
2938use rustc_middle:: hir:: map:: Map ;
3039use 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.
4554const 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`
6188const 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.
380456fn 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+ }
0 commit comments