@@ -24,7 +24,7 @@ use if_chain::if_chain;
2424use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
2525use rustc_hir:: { self as hir, ExprKind , Item , ItemKind , Mutability } ;
2626use rustc_lint:: { CheckLintNameResult , LateContext , LateLintPass , LintContext , LintId } ;
27- use rustc_middle:: ty:: BorrowKind ;
27+ use rustc_middle:: ty:: { BorrowKind , Ty } ;
2828use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
2929use rustc_span:: { sym, Loc , Span , Symbol } ;
3030use rustc_trait_selection:: infer:: TyCtxtInferExt ;
@@ -37,7 +37,8 @@ use std::path::Path;
3737
3838use crate :: utils:: internal_lints:: is_lint_ref_type;
3939use 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+
150157impl 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
336345fn 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+ }
0 commit comments