|
| 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)]
|
@@ -123,6 +203,8 @@ fn check_fn_like<'tcx>(cx: &LateContext<'tcx>, fd: &'tcx hir::FnDecl<'tcx>) {
|
123 | 203 | }
|
124 | 204 |
|
125 | 205 | report_mismatches(cx, &input_map, &output_map);
|
| 206 | + report_hidden_in_paths(cx, &input_map, HIDDEN_LIFETIMES_IN_INPUT_PATHS); |
| 207 | + report_hidden_in_paths(cx, &output_map, HIDDEN_LIFETIMES_IN_OUTPUT_PATHS); |
126 | 208 | }
|
127 | 209 |
|
128 | 210 | #[instrument(skip_all)]
|
@@ -512,6 +594,50 @@ fn build_mismatch_suggestion(
|
512 | 594 | }
|
513 | 595 | }
|
514 | 596 |
|
| 597 | +fn report_hidden_in_paths<'tcx>( |
| 598 | + cx: &LateContext<'tcx>, |
| 599 | + info_map: &LifetimeInfoMap<'tcx>, |
| 600 | + lint: &'static Lint, |
| 601 | +) { |
| 602 | + let relevant_lifetimes = info_map |
| 603 | + .iter() |
| 604 | + .filter(|&(&&res, _)| reportable_lifetime_resolution(res)) |
| 605 | + .flat_map(|(_, info)| info) |
| 606 | + .filter(|info| { |
| 607 | + matches!(info.lifetime.source, LifetimeSource::Path { .. }) |
| 608 | + && info.lifetime.is_implicit() |
| 609 | + }); |
| 610 | + |
| 611 | + let mut reporting_spans = Vec::new(); |
| 612 | + let mut suggestions = Vec::new(); |
| 613 | + |
| 614 | + for info in relevant_lifetimes { |
| 615 | + reporting_spans.push(info.reporting_span()); |
| 616 | + suggestions.push(info.suggestion("'_")); |
| 617 | + } |
| 618 | + |
| 619 | + if reporting_spans.is_empty() { |
| 620 | + return; |
| 621 | + } |
| 622 | + |
| 623 | + cx.emit_span_lint( |
| 624 | + lint, |
| 625 | + reporting_spans, |
| 626 | + lints::HiddenLifetimeInPath { |
| 627 | + suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions }, |
| 628 | + }, |
| 629 | + ); |
| 630 | +} |
| 631 | + |
| 632 | +/// We don't care about errors, nor do we care about the lifetime |
| 633 | +/// inside of a trait object. |
| 634 | +fn reportable_lifetime_resolution(kind: hir::LifetimeKind) -> bool { |
| 635 | + matches!( |
| 636 | + kind, |
| 637 | + hir::LifetimeKind::Param(..) | hir::LifetimeKind::Infer | hir::LifetimeKind::Static |
| 638 | + ) |
| 639 | +} |
| 640 | + |
515 | 641 | #[derive(Debug)]
|
516 | 642 | struct Info<'tcx> {
|
517 | 643 | type_span: Span,
|
@@ -614,3 +740,149 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
|
614 | 740 | self.referenced_type_span = old_referenced_type_span;
|
615 | 741 | }
|
616 | 742 | }
|
| 743 | + |
| 744 | +declare_lint! { |
| 745 | + /// The `hidden_lifetimes_in_type_paths` lint detects the use of |
| 746 | + /// hidden lifetime parameters in types not part of a function's |
| 747 | + /// arguments or return values. |
| 748 | + /// |
| 749 | + /// ### Example |
| 750 | + /// |
| 751 | + /// ```rust,compile_fail |
| 752 | + /// #![deny(hidden_lifetimes_in_type_paths)] |
| 753 | + /// |
| 754 | + /// struct ContainsLifetime<'a>(&'a i32); |
| 755 | + /// |
| 756 | + /// static FOO: ContainsLifetime = ContainsLifetime(&42); |
| 757 | + /// ``` |
| 758 | + /// |
| 759 | + /// {{produces}} |
| 760 | + /// |
| 761 | + /// ### Explanation |
| 762 | + /// |
| 763 | + /// Hidden lifetime parameters can make it difficult to see at a |
| 764 | + /// glance that borrowing is occurring. |
| 765 | + /// |
| 766 | + /// This lint ensures that lifetime parameters are always |
| 767 | + /// explicitly stated, even if it is the `'_` [placeholder |
| 768 | + /// lifetime]. |
| 769 | + /// |
| 770 | + /// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions |
| 771 | + pub HIDDEN_LIFETIMES_IN_TYPE_PATHS, |
| 772 | + Allow, |
| 773 | + "hidden lifetime parameters in types outside function signatures are discouraged" |
| 774 | +} |
| 775 | + |
| 776 | +#[derive(Default)] |
| 777 | +pub(crate) struct HiddenLifetimesInTypePaths { |
| 778 | + inside_fn_signature: bool, |
| 779 | +} |
| 780 | + |
| 781 | +impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS]); |
| 782 | + |
| 783 | +// Do not lint about usages such as `ContainsLifetime::method` or |
| 784 | +// `ContainsLifetimeAndType::<SomeType>::method`. |
| 785 | +fn parent_path_implicit_self<'tcx>(cx: &LateContext<'tcx>, parent_hir_id: hir::HirId) -> bool { |
| 786 | + let parent_hir_node = cx.tcx.parent_hir_node(parent_hir_id); |
| 787 | + tracing::debug!(?parent_hir_node, "parent_hir_node"); |
| 788 | + |
| 789 | + fn path_is_implicit_self(path: hir::QPath<'_>) -> bool { |
| 790 | + match path { |
| 791 | + hir::QPath::TypeRelative(_, seg) => seg.implicit_self(), |
| 792 | + _ => false, |
| 793 | + } |
| 794 | + } |
| 795 | + |
| 796 | + if let hir::Node::Expr(expr) = parent_hir_node { |
| 797 | + return match expr.kind { |
| 798 | + hir::ExprKind::Path(path) => path_is_implicit_self(path), |
| 799 | + hir::ExprKind::Struct(path, _, _) => path_is_implicit_self(*path), |
| 800 | + _ => false, |
| 801 | + }; |
| 802 | + } |
| 803 | + |
| 804 | + if let hir::Node::Pat(pat) = parent_hir_node |
| 805 | + && let hir::PatKind::Struct(path, _, _) | hir::PatKind::TupleStruct(path, _, _) = pat.kind |
| 806 | + { |
| 807 | + return path_is_implicit_self(path); |
| 808 | + } |
| 809 | + |
| 810 | + if let hir::Node::PatExpr(expr) = parent_hir_node |
| 811 | + && let hir::PatExprKind::Path(path) = expr.kind |
| 812 | + { |
| 813 | + return path_is_implicit_self(path); |
| 814 | + } |
| 815 | + |
| 816 | + false |
| 817 | +} |
| 818 | + |
| 819 | +impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths { |
| 820 | + #[instrument(skip(self, cx))] |
| 821 | + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { |
| 822 | + if self.inside_fn_signature { |
| 823 | + return; |
| 824 | + } |
| 825 | + |
| 826 | + if parent_path_implicit_self(cx, ty.hir_id) { |
| 827 | + return; |
| 828 | + } |
| 829 | + |
| 830 | + let hir::TyKind::Path(path) = ty.kind else { return }; |
| 831 | + |
| 832 | + let path_segments = match path { |
| 833 | + hir::QPath::Resolved(_ty, path) => path.segments, |
| 834 | + |
| 835 | + hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment), |
| 836 | + |
| 837 | + hir::QPath::LangItem(..) => &[], |
| 838 | + }; |
| 839 | + |
| 840 | + let mut suggestions = Vec::new(); |
| 841 | + |
| 842 | + for path_segment in path_segments { |
| 843 | + for arg in path_segment.args().args { |
| 844 | + if let hir::GenericArg::Lifetime(lifetime) = arg |
| 845 | + && lifetime.is_implicit() |
| 846 | + && reportable_lifetime_resolution(lifetime.kind) |
| 847 | + { |
| 848 | + suggestions.push(lifetime.suggestion("'_")) |
| 849 | + } |
| 850 | + } |
| 851 | + } |
| 852 | + |
| 853 | + if suggestions.is_empty() { |
| 854 | + return; |
| 855 | + } |
| 856 | + |
| 857 | + cx.emit_span_lint( |
| 858 | + HIDDEN_LIFETIMES_IN_TYPE_PATHS, |
| 859 | + ty.span, |
| 860 | + lints::HiddenLifetimeInPath { |
| 861 | + suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions }, |
| 862 | + }, |
| 863 | + ); |
| 864 | + } |
| 865 | + |
| 866 | + #[instrument(skip_all)] |
| 867 | + fn check_fn( |
| 868 | + &mut self, |
| 869 | + _: &LateContext<'tcx>, |
| 870 | + _: hir::intravisit::FnKind<'tcx>, |
| 871 | + _: &'tcx hir::FnDecl<'tcx>, |
| 872 | + _: &'tcx hir::Body<'tcx>, |
| 873 | + _: rustc_span::Span, |
| 874 | + _: rustc_span::def_id::LocalDefId, |
| 875 | + ) { |
| 876 | + // We make the assumption that we will visit the function |
| 877 | + // declaration first, before visiting the body. |
| 878 | + self.inside_fn_signature = true; |
| 879 | + } |
| 880 | + |
| 881 | + // This may be a function's body, which would indicate that we are |
| 882 | + // no longer in the signature. Even if it's not, a body cannot |
| 883 | + // occur inside a function signature. |
| 884 | + #[instrument(skip_all)] |
| 885 | + fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) { |
| 886 | + self.inside_fn_signature = false; |
| 887 | + } |
| 888 | +} |
0 commit comments