|
| 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)]
|
@@ -433,6 +515,50 @@ fn build_mismatch_suggestion(
|
433 | 515 | }
|
434 | 516 | }
|
435 | 517 |
|
| 518 | +fn report_hidden_in_paths<'tcx>( |
| 519 | + cx: &LateContext<'tcx>, |
| 520 | + info_map: &LifetimeInfoMap<'tcx>, |
| 521 | + lint: &'static Lint, |
| 522 | +) { |
| 523 | + let relevant_lifetimes = info_map |
| 524 | + .iter() |
| 525 | + .filter(|&(&&res, _)| reportable_lifetime_resolution(res)) |
| 526 | + .flat_map(|(_, info)| info) |
| 527 | + .filter(|info| { |
| 528 | + matches!(info.lifetime.source, LifetimeSource::Path { .. }) |
| 529 | + && info.lifetime.is_implicit() |
| 530 | + }); |
| 531 | + |
| 532 | + let mut reporting_spans = Vec::new(); |
| 533 | + let mut suggestions = Vec::new(); |
| 534 | + |
| 535 | + for info in relevant_lifetimes { |
| 536 | + reporting_spans.push(info.reporting_span()); |
| 537 | + suggestions.push(info.suggestion("'_")); |
| 538 | + } |
| 539 | + |
| 540 | + if reporting_spans.is_empty() { |
| 541 | + return; |
| 542 | + } |
| 543 | + |
| 544 | + cx.emit_span_lint( |
| 545 | + lint, |
| 546 | + reporting_spans, |
| 547 | + lints::HiddenLifetimeInPath { |
| 548 | + suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions }, |
| 549 | + }, |
| 550 | + ); |
| 551 | +} |
| 552 | + |
| 553 | +/// We don't care about errors, nor do we care about the lifetime |
| 554 | +/// inside of a trait object. |
| 555 | +fn reportable_lifetime_resolution(kind: hir::LifetimeKind) -> bool { |
| 556 | + matches!( |
| 557 | + kind, |
| 558 | + hir::LifetimeKind::Param(..) | hir::LifetimeKind::Infer | hir::LifetimeKind::Static |
| 559 | + ) |
| 560 | +} |
| 561 | + |
436 | 562 | #[derive(Debug)]
|
437 | 563 | struct Info<'tcx> {
|
438 | 564 | type_span: Span,
|
@@ -527,3 +653,115 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeInfoCollector<'a, 'tcx> {
|
527 | 653 | self.referenced_type_span = old_referenced_type_span;
|
528 | 654 | }
|
529 | 655 | }
|
| 656 | + |
| 657 | +declare_lint! { |
| 658 | + /// The `hidden_lifetimes_in_type_paths` lint detects the use of |
| 659 | + /// hidden lifetime parameters in types not part of a function's |
| 660 | + /// arguments or return values. |
| 661 | + /// |
| 662 | + /// ### Example |
| 663 | + /// |
| 664 | + /// ```rust,compile_fail |
| 665 | + /// #![deny(hidden_lifetimes_in_type_paths)] |
| 666 | + /// |
| 667 | + /// struct ContainsLifetime<'a>(&'a i32); |
| 668 | + /// |
| 669 | + /// static FOO: ContainsLifetime = ContainsLifetime(&42); |
| 670 | + /// ``` |
| 671 | + /// |
| 672 | + /// {{produces}} |
| 673 | + /// |
| 674 | + /// ### Explanation |
| 675 | + /// |
| 676 | + /// Hidden lifetime parameters can make it difficult to see at a |
| 677 | + /// glance that borrowing is occurring. |
| 678 | + /// |
| 679 | + /// This lint ensures that lifetime parameters are always |
| 680 | + /// explicitly stated, even if it is the `'_` [placeholder |
| 681 | + /// lifetime]. |
| 682 | + /// |
| 683 | + /// [placeholder lifetime]: https://doc.rust-lang.org/reference/lifetime-elision.html#lifetime-elision-in-functions |
| 684 | + pub HIDDEN_LIFETIMES_IN_TYPE_PATHS, |
| 685 | + Allow, |
| 686 | + "hidden lifetime parameters in types outside function signatures are discouraged" |
| 687 | +} |
| 688 | + |
| 689 | +#[derive(Default)] |
| 690 | +pub(crate) struct HiddenLifetimesInTypePaths { |
| 691 | + inside_fn_signature: bool, |
| 692 | +} |
| 693 | + |
| 694 | +impl_lint_pass!(HiddenLifetimesInTypePaths => [HIDDEN_LIFETIMES_IN_TYPE_PATHS]); |
| 695 | + |
| 696 | +impl<'tcx> LateLintPass<'tcx> for HiddenLifetimesInTypePaths { |
| 697 | + #[instrument(skip(self, cx))] |
| 698 | + fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { |
| 699 | + if self.inside_fn_signature { |
| 700 | + return; |
| 701 | + } |
| 702 | + |
| 703 | + // Do not lint about usages like `ContainsLifetime::method` or |
| 704 | + // `ContainsLifetimeAndType::<SomeType>::method`. |
| 705 | + if ty.source == hir::TySource::ImplicitSelf { |
| 706 | + return; |
| 707 | + } |
| 708 | + |
| 709 | + let hir::TyKind::Path(path) = ty.kind else { return }; |
| 710 | + |
| 711 | + let path_segments = match path { |
| 712 | + hir::QPath::Resolved(_ty, path) => path.segments, |
| 713 | + |
| 714 | + hir::QPath::TypeRelative(_ty, path_segment) => slice::from_ref(path_segment), |
| 715 | + |
| 716 | + hir::QPath::LangItem(..) => &[], |
| 717 | + }; |
| 718 | + |
| 719 | + let mut suggestions = Vec::new(); |
| 720 | + |
| 721 | + for path_segment in path_segments { |
| 722 | + for arg in path_segment.args().args { |
| 723 | + if let hir::GenericArg::Lifetime(lifetime) = arg |
| 724 | + && lifetime.is_implicit() |
| 725 | + && reportable_lifetime_resolution(lifetime.kind) |
| 726 | + { |
| 727 | + suggestions.push(lifetime.suggestion("'_")) |
| 728 | + } |
| 729 | + } |
| 730 | + } |
| 731 | + |
| 732 | + if suggestions.is_empty() { |
| 733 | + return; |
| 734 | + } |
| 735 | + |
| 736 | + cx.emit_span_lint( |
| 737 | + HIDDEN_LIFETIMES_IN_TYPE_PATHS, |
| 738 | + ty.span, |
| 739 | + lints::HiddenLifetimeInPath { |
| 740 | + suggestions: lints::HiddenLifetimeInPathSuggestion { suggestions }, |
| 741 | + }, |
| 742 | + ); |
| 743 | + } |
| 744 | + |
| 745 | + #[instrument(skip_all)] |
| 746 | + fn check_fn( |
| 747 | + &mut self, |
| 748 | + _: &LateContext<'tcx>, |
| 749 | + _: hir::intravisit::FnKind<'tcx>, |
| 750 | + _: &'tcx hir::FnDecl<'tcx>, |
| 751 | + _: &'tcx hir::Body<'tcx>, |
| 752 | + _: rustc_span::Span, |
| 753 | + _: rustc_span::def_id::LocalDefId, |
| 754 | + ) { |
| 755 | + // We make the assumption that we will visit the function |
| 756 | + // declaration first, before visiting the body. |
| 757 | + self.inside_fn_signature = true; |
| 758 | + } |
| 759 | + |
| 760 | + // This may be a function's body, which would indicate that we are |
| 761 | + // no longer in the signature. Even if it's not, a body cannot |
| 762 | + // occur inside a function signature. |
| 763 | + #[instrument(skip_all)] |
| 764 | + fn check_body(&mut self, _: &LateContext<'tcx>, _: &hir::Body<'tcx>) { |
| 765 | + self.inside_fn_signature = false; |
| 766 | + } |
| 767 | +} |
0 commit comments