1
+ use std:: slice;
2
+
1
3
use rustc_data_structures:: fx:: FxIndexMap ;
2
4
use rustc_hir:: intravisit:: { self , Visitor } ;
3
5
use rustc_hir:: { self as hir, LifetimeSource } ;
4
- use rustc_session:: { declare_lint, declare_lint_pass} ;
6
+ use rustc_session:: lint:: Lint ;
7
+ use rustc_session:: { declare_lint, declare_lint_pass, impl_lint_pass} ;
5
8
use rustc_span:: Span ;
6
9
use tracing:: instrument;
7
10
@@ -71,7 +74,84 @@ declare_lint! {
71
74
"detects when a lifetime uses different syntax between arguments and return values"
72
75
}
73
76
74
- declare_lint_pass ! ( LifetimeSyntax => [ MISMATCHED_LIFETIME_SYNTAXES ] ) ;
77
+ declare_lint ! {
78
+ /// The `hidden_lifetimes_in_input_paths` lint detects the use of
79
+ /// hidden lifetime parameters in types occurring as a function
80
+ /// argument.
81
+ ///
82
+ /// ### Example
83
+ ///
84
+ /// ```rust,compile_fail
85
+ /// #![deny(hidden_lifetimes_in_input_paths)]
86
+ ///
87
+ /// struct ContainsLifetime<'a>(&'a i32);
88
+ ///
89
+ /// fn foo(x: ContainsLifetime) {}
90
+ /// ```
91
+ ///
92
+ /// {{produces}}
93
+ ///
94
+ /// ### Explanation
95
+ ///
96
+ /// Hidden lifetime parameters can make it difficult to see at a
97
+ /// glance that borrowing is occurring.
98
+ ///
99
+ /// This lint ensures that lifetime parameters are always
100
+ /// explicitly stated, even if it is the `'_` [placeholder
101
+ /// lifetime].
102
+ ///
103
+ /// This lint is "allow" by default as function arguments by
104
+ /// themselves do not usually cause much confusion.
105
+ ///
106
+ /// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
107
+ pub HIDDEN_LIFETIMES_IN_INPUT_PATHS ,
108
+ Allow ,
109
+ "hidden lifetime parameters in types in function arguments may be confusing"
110
+ }
111
+
112
+ declare_lint ! {
113
+ /// The `hidden_lifetimes_in_output_paths` lint detects the use
114
+ /// of hidden lifetime parameters in types occurring as a function
115
+ /// return value.
116
+ ///
117
+ /// ### Example
118
+ ///
119
+ /// ```rust,compile_fail
120
+ /// #![deny(hidden_lifetimes_in_output_paths)]
121
+ ///
122
+ /// struct ContainsLifetime<'a>(&'a i32);
123
+ ///
124
+ /// fn foo(x: &i32) -> ContainsLifetime {
125
+ /// ContainsLifetime(x)
126
+ /// }
127
+ /// ```
128
+ ///
129
+ /// {{produces}}
130
+ ///
131
+ /// ### Explanation
132
+ ///
133
+ /// Hidden lifetime parameters can make it difficult to see at a
134
+ /// glance that borrowing is occurring. This is especially true
135
+ /// when a type is used as a function's return value: lifetime
136
+ /// elision will link the return value's lifetime to an argument's
137
+ /// lifetime, but no syntax in the function signature indicates
138
+ /// that.
139
+ ///
140
+ /// This lint ensures that lifetime parameters are always
141
+ /// explicitly stated, even if it is the `'_` [placeholder
142
+ /// lifetime].
143
+ ///
144
+ /// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
145
+ pub HIDDEN_LIFETIMES_IN_OUTPUT_PATHS ,
146
+ Allow ,
147
+ "hidden lifetime parameters in types in function return values are deprecated"
148
+ }
149
+
150
+ declare_lint_pass ! ( LifetimeSyntax => [
151
+ MISMATCHED_LIFETIME_SYNTAXES ,
152
+ HIDDEN_LIFETIMES_IN_INPUT_PATHS ,
153
+ HIDDEN_LIFETIMES_IN_OUTPUT_PATHS ,
154
+ ] ) ;
75
155
76
156
impl < ' tcx > LateLintPass < ' tcx > for LifetimeSyntax {
77
157
#[ instrument( skip_all) ]
@@ -96,6 +176,8 @@ impl<'tcx> LateLintPass<'tcx> for LifetimeSyntax {
96
176
}
97
177
98
178
report_mismatches ( cx, & input_map, & output_map) ;
179
+ report_hidden_in_paths ( cx, & input_map, HIDDEN_LIFETIMES_IN_INPUT_PATHS ) ;
180
+ report_hidden_in_paths ( cx, & output_map, HIDDEN_LIFETIMES_IN_OUTPUT_PATHS ) ;
99
181
}
100
182
}
101
183
@@ -407,6 +489,50 @@ fn build_mismatch_suggestion(
407
489
}
408
490
}
409
491
492
+ fn report_hidden_in_paths < ' tcx > (
493
+ cx : & LateContext < ' tcx > ,
494
+ info_map : & LifetimeInfoMap < ' tcx > ,
495
+ lint : & ' static Lint ,
496
+ ) {
497
+ let relevant_lifetimes = info_map
498
+ . iter ( )
499
+ . filter ( |& ( & & res, _) | reportable_lifetime_resolution ( res) )
500
+ . flat_map ( |( _, info) | info)
501
+ . filter ( |info| {
502
+ matches ! ( info. lifetime. source, LifetimeSource :: Path { .. } )
503
+ && info. lifetime . is_implicit ( )
504
+ } ) ;
505
+
506
+ let mut reporting_spans = Vec :: new ( ) ;
507
+ let mut suggestions = Vec :: new ( ) ;
508
+
509
+ for info in relevant_lifetimes {
510
+ reporting_spans. push ( info. reporting_span ( ) ) ;
511
+ suggestions. push ( info. suggestion ( "'_" ) ) ;
512
+ }
513
+
514
+ if reporting_spans. is_empty ( ) {
515
+ return ;
516
+ }
517
+
518
+ cx. emit_span_lint (
519
+ lint,
520
+ reporting_spans,
521
+ lints:: HiddenLifetimeInPath {
522
+ suggestions : lints:: HiddenLifetimeInPathSuggestion { suggestions } ,
523
+ } ,
524
+ ) ;
525
+ }
526
+
527
+ /// We don't care about errors, nor do we care about the lifetime
528
+ /// inside of a trait object.
529
+ fn reportable_lifetime_resolution ( kind : hir:: LifetimeKind ) -> bool {
530
+ matches ! (
531
+ kind,
532
+ hir:: LifetimeKind :: Param ( ..) | hir:: LifetimeKind :: Infer | hir:: LifetimeKind :: Static
533
+ )
534
+ }
535
+
410
536
#[ derive( Debug ) ]
411
537
struct Info < ' tcx > {
412
538
type_span : Span ,
@@ -501,3 +627,115 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
501
627
self . referenced_type_span = old_referenced_type_span;
502
628
}
503
629
}
630
+
631
+ declare_lint ! {
632
+ /// The `hidden_lifetimes_in_type_paths` lint detects the use of
633
+ /// hidden lifetime parameters in types not part of a function's
634
+ /// arguments or return values.
635
+ ///
636
+ /// ### Example
637
+ ///
638
+ /// ```rust,compile_fail
639
+ /// #![deny(hidden_lifetimes_in_type_paths)]
640
+ ///
641
+ /// struct ContainsLifetime<'a>(&'a i32);
642
+ ///
643
+ /// static FOO: ContainsLifetime = ContainsLifetime(&42);
644
+ /// ```
645
+ ///
646
+ /// {{produces}}
647
+ ///
648
+ /// ### Explanation
649
+ ///
650
+ /// Hidden lifetime parameters can make it difficult to see at a
651
+ /// glance that borrowing is occurring.
652
+ ///
653
+ /// This lint ensures that lifetime parameters are always
654
+ /// explicitly stated, even if it is the `'_` [placeholder
655
+ /// lifetime].
656
+ ///
657
+ /// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions
658
+ pub HIDDEN_LIFETIMES_IN_TYPE_PATHS ,
659
+ Allow ,
660
+ "hidden lifetime parameters in types outside function signatures are discouraged"
661
+ }
662
+
663
+ #[ derive( Default ) ]
664
+ pub ( crate ) struct HiddenLifetimesInTypePaths {
665
+ inside_fn_signature : bool ,
666
+ }
667
+
668
+ impl_lint_pass ! ( HiddenLifetimesInTypePaths => [ HIDDEN_LIFETIMES_IN_TYPE_PATHS ] ) ;
669
+
670
+ impl < ' tcx > LateLintPass < ' tcx > for HiddenLifetimesInTypePaths {
671
+ #[ instrument( skip( self , cx) ) ]
672
+ fn check_ty ( & mut self , cx : & LateContext < ' tcx > , ty : & ' tcx hir:: Ty < ' tcx , hir:: AmbigArg > ) {
673
+ if self . inside_fn_signature {
674
+ return ;
675
+ }
676
+
677
+ // Do not lint about usages like `ContainsLifetime::method` or
678
+ // `ContainsLifetimeAndType::<SomeType>::method`.
679
+ if ty. source == hir:: TySource :: ImplicitSelf {
680
+ return ;
681
+ }
682
+
683
+ let hir:: TyKind :: Path ( path) = ty. kind else { return } ;
684
+
685
+ let path_segments = match path {
686
+ hir:: QPath :: Resolved ( _ty, path) => path. segments ,
687
+
688
+ hir:: QPath :: TypeRelative ( _ty, path_segment) => slice:: from_ref ( path_segment) ,
689
+
690
+ hir:: QPath :: LangItem ( ..) => & [ ] ,
691
+ } ;
692
+
693
+ let mut suggestions = Vec :: new ( ) ;
694
+
695
+ for path_segment in path_segments {
696
+ for arg in path_segment. args ( ) . args {
697
+ if let hir:: GenericArg :: Lifetime ( lifetime) = arg
698
+ && lifetime. is_implicit ( )
699
+ && reportable_lifetime_resolution ( lifetime. kind )
700
+ {
701
+ suggestions. push ( lifetime. suggestion ( "'_" ) )
702
+ }
703
+ }
704
+ }
705
+
706
+ if suggestions. is_empty ( ) {
707
+ return ;
708
+ }
709
+
710
+ cx. emit_span_lint (
711
+ HIDDEN_LIFETIMES_IN_TYPE_PATHS ,
712
+ ty. span ,
713
+ lints:: HiddenLifetimeInPath {
714
+ suggestions : lints:: HiddenLifetimeInPathSuggestion { suggestions } ,
715
+ } ,
716
+ ) ;
717
+ }
718
+
719
+ #[ instrument( skip_all) ]
720
+ fn check_fn (
721
+ & mut self ,
722
+ _: & LateContext < ' tcx > ,
723
+ _: hir:: intravisit:: FnKind < ' tcx > ,
724
+ _: & ' tcx hir:: FnDecl < ' tcx > ,
725
+ _: & ' tcx hir:: Body < ' tcx > ,
726
+ _: rustc_span:: Span ,
727
+ _: rustc_span:: def_id:: LocalDefId ,
728
+ ) {
729
+ // We make the assumption that we will visit the function
730
+ // declaration first, before visiting the body.
731
+ self . inside_fn_signature = true ;
732
+ }
733
+
734
+ // This may be a function's body, which would indicate that we are
735
+ // no longer in the signature. Even if it's not, a body cannot
736
+ // occur inside a function signature.
737
+ #[ instrument( skip_all) ]
738
+ fn check_body ( & mut self , _: & LateContext < ' tcx > , _: & hir:: Body < ' tcx > ) {
739
+ self . inside_fn_signature = false ;
740
+ }
741
+ }
0 commit comments