@@ -24,7 +24,7 @@ use if_chain::if_chain;
24
24
use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
25
25
use rustc_hir:: { self as hir, ExprKind , Item , ItemKind , Mutability } ;
26
26
use rustc_lint:: { CheckLintNameResult , LateContext , LateLintPass , LintContext , LintId } ;
27
- use rustc_middle:: ty:: BorrowKind ;
27
+ use rustc_middle:: ty:: { BorrowKind , Ty } ;
28
28
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
29
29
use rustc_span:: { sym, Loc , Span , Symbol } ;
30
30
use rustc_trait_selection:: infer:: TyCtxtInferExt ;
@@ -37,7 +37,8 @@ use std::path::Path;
37
37
38
38
use crate :: utils:: internal_lints:: is_lint_ref_type;
39
39
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,
41
42
} ;
42
43
43
44
/// This is the output file of the lint collector.
@@ -147,6 +148,12 @@ struct SerializableSpan {
147
148
line : usize ,
148
149
}
149
150
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
+
150
157
impl SerializableSpan {
151
158
fn from_item ( cx : & LateContext < ' _ > , item : & Item < ' _ > ) -> Self {
152
159
Self :: from_span ( cx, item. ident . span )
@@ -285,52 +292,54 @@ impl<'tcx> LateLintPass<'tcx> for MetadataCollector {
285
292
/// );
286
293
/// ```
287
294
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)
322
309
}
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) ) ;
323
327
}
324
328
}
325
329
}
326
330
}
327
331
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
+ }
330
341
331
- map. parent_iter ( id)
332
- . find_map ( |( parent, _) | map. maybe_body_owned_by ( parent) )
333
- . map ( |body| map. body ( body) )
342
+ None
334
343
}
335
344
336
345
fn sym_to_string ( sym : Symbol ) -> String {
@@ -429,42 +438,174 @@ fn extract_emission_info<'tcx>(cx: &LateContext<'tcx>, args: &[hir::Expr<'_>]) -
429
438
} )
430
439
}
431
440
432
- struct ValueTracker < ' a , ' tcx > {
433
- cx : & ' a LateContext < ' tcx > ,
441
+ #[ allow( dead_code) ]
442
+ struct ValueTracker < ' a , ' hir > {
443
+ cx : & ' a LateContext < ' hir > ,
434
444
value_hir_id : hir:: HirId ,
435
- value_life : String ,
445
+ value_mutations : Vec < ApplicabilityModifier < ' hir > > ,
436
446
}
437
447
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
+
439
457
fn is_value_expr ( & self , expr_id : hir:: HirId ) -> bool {
440
458
match self . cx . tcx . hir ( ) . find ( expr_id) {
441
459
Some ( hir:: Node :: Expr ( expr) ) => path_to_local_id ( expr, self . value_hir_id ) ,
442
460
_ => false ,
443
461
}
444
462
}
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
+ }
445
528
}
446
529
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 ) {
449
532
if self . is_value_expr ( expr_id) {
450
533
// 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
+ }
452
538
}
453
539
}
454
540
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 ) {
456
542
if self . is_value_expr ( expr_id) {
457
543
if let BorrowKind :: MutBorrow = bk {
458
544
// 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
+ }
460
549
}
461
550
}
462
551
}
463
552
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
+ }
468
561
}
469
562
}
470
563
}
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