Skip to content

Commit 9070038

Browse files
Auto merge of #138677 - shepmaster:consistent-elided-lifetime-syntax, r=<try>
Add a new `mismatched-lifetime-syntaxes` lint The lang-team [discussed this](https://hackmd.io/nf4ZUYd7Rp6rq-1svJZSaQ) and I attempted to [summarize](#120808 (comment)) their decision. The summary-of-the-summary is: - Using two different kinds of syntax for elided lifetimes is confusing. In rare cases, it may even [lead to unsound code](#48686)! Some examples: ```rust // Lint will warn about these fn(v: ContainsLifetime) -> ContainsLifetime<'_>; fn(&'static u8) -> &u8; ``` - Matching up references with no lifetime syntax, references with anonymous lifetime syntax, and paths with anonymous lifetime syntax is an exception to the simplest possible rule: ```rust // Lint will not warn about these fn(&u8) -> &'_ u8; fn(&'_ u8) -> &u8; fn(&u8) -> ContainsLifetime<'_>; ``` - Having a lint for consistent syntax of elided lifetimes will make the [future goal](#91639) of warning-by-default for paths participating in elision much simpler. --- This new lint attempts to accomplish the goal of enforcing consistent syntax. In the process, it supersedes and replaces the existing `elided-named-lifetimes` lint, which means it starts out life as warn-by-default.
2 parents 2f17612 + 9b372f3 commit 9070038

File tree

117 files changed

+1893
-677
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+1893
-677
lines changed

compiler/rustc_ast_lowering/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
14061406
};
14071407
let span = self.tcx.sess.source_map().start_point(t.span).shrink_to_hi();
14081408
let region = Lifetime { ident: Ident::new(kw::UnderscoreLifetime, span), id };
1409-
(region, LifetimeSyntax::Hidden)
1409+
(region, LifetimeSyntax::Implicit)
14101410
}
14111411
};
14121412
self.lower_lifetime(&region, LifetimeSource::Reference, syntax)
@@ -1790,7 +1790,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
17901790
id,
17911791
Ident::new(kw::UnderscoreLifetime, span),
17921792
LifetimeSource::Path { angle_brackets },
1793-
LifetimeSyntax::Hidden,
1793+
LifetimeSyntax::Implicit,
17941794
)
17951795
}
17961796

@@ -2422,7 +2422,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
24222422
Ident::new(kw::UnderscoreLifetime, self.lower_span(span)),
24232423
hir::LifetimeKind::ImplicitObjectLifetimeDefault,
24242424
LifetimeSource::Other,
2425-
LifetimeSyntax::Hidden,
2425+
LifetimeSyntax::Implicit,
24262426
);
24272427
debug!("elided_dyn_bound: r={:?}", r);
24282428
self.arena.alloc(r)

compiler/rustc_hir/src/def.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -852,12 +852,7 @@ pub enum LifetimeRes {
852852
/// late resolution. Those lifetimes will be inferred by typechecking.
853853
Infer,
854854
/// `'static` lifetime.
855-
Static {
856-
/// We do not want to emit `elided_named_lifetimes`
857-
/// when we are inside of a const item or a static,
858-
/// because it would get too annoying.
859-
suppress_elision_warning: bool,
860-
},
855+
Static,
861856
/// Resolution failure.
862857
Error,
863858
/// HACK: This is used to recover the NodeId of an elided lifetime.

compiler/rustc_hir/src/hir.rs

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,13 @@ pub enum LifetimeSource {
7272
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable_Generic)]
7373
pub enum LifetimeSyntax {
7474
/// E.g. `&Type`, `ContainsLifetime`
75-
Hidden,
75+
Implicit,
7676

7777
/// E.g. `&'_ Type`, `ContainsLifetime<'_>`, `impl Trait + '_`, `impl Trait + use<'_>`
78-
Anonymous,
78+
ExplicitAnonymous,
7979

8080
/// E.g. `&'a Type`, `ContainsLifetime<'a>`, `impl Trait + 'a`, `impl Trait + use<'a>`
81-
Named,
81+
ExplicitBound,
8282
}
8383

8484
impl From<Ident> for LifetimeSyntax {
@@ -88,10 +88,10 @@ impl From<Ident> for LifetimeSyntax {
8888
if name == sym::empty {
8989
unreachable!("A lifetime name should never be empty");
9090
} else if name == kw::UnderscoreLifetime {
91-
LifetimeSyntax::Anonymous
91+
LifetimeSyntax::ExplicitAnonymous
9292
} else {
9393
debug_assert!(name.as_str().starts_with('\''));
94-
LifetimeSyntax::Named
94+
LifetimeSyntax::ExplicitBound
9595
}
9696
}
9797
}
@@ -102,48 +102,48 @@ impl From<Ident> for LifetimeSyntax {
102102
///
103103
/// ```
104104
/// #[repr(C)]
105-
/// struct S<'a>(&'a u32); // res=Param, name='a, source=Reference, syntax=Named
105+
/// struct S<'a>(&'a u32); // res=Param, name='a, source=Reference, syntax=ExplicitBound
106106
/// unsafe extern "C" {
107-
/// fn f1(s: S); // res=Param, name='_, source=Path, syntax=Hidden
108-
/// fn f2(s: S<'_>); // res=Param, name='_, source=Path, syntax=Anonymous
109-
/// fn f3<'a>(s: S<'a>); // res=Param, name='a, source=Path, syntax=Named
107+
/// fn f1(s: S); // res=Param, name='_, source=Path, syntax=Implicit
108+
/// fn f2(s: S<'_>); // res=Param, name='_, source=Path, syntax=ExplicitAnonymous
109+
/// fn f3<'a>(s: S<'a>); // res=Param, name='a, source=Path, syntax=ExplicitBound
110110
/// }
111111
///
112-
/// struct St<'a> { x: &'a u32 } // res=Param, name='a, source=Reference, syntax=Named
112+
/// struct St<'a> { x: &'a u32 } // res=Param, name='a, source=Reference, syntax=ExplicitBound
113113
/// fn f() {
114-
/// _ = St { x: &0 }; // res=Infer, name='_, source=Path, syntax=Hidden
115-
/// _ = St::<'_> { x: &0 }; // res=Infer, name='_, source=Path, syntax=Anonymous
114+
/// _ = St { x: &0 }; // res=Infer, name='_, source=Path, syntax=Implicit
115+
/// _ = St::<'_> { x: &0 }; // res=Infer, name='_, source=Path, syntax=ExplicitAnonymous
116116
/// }
117117
///
118-
/// struct Name<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=Named
119-
/// const A: Name = Name("a"); // res=Static, name='_, source=Path, syntax=Hidden
120-
/// const B: &str = ""; // res=Static, name='_, source=Reference, syntax=Hidden
121-
/// static C: &'_ str = ""; // res=Static, name='_, source=Reference, syntax=Anonymous
122-
/// static D: &'static str = ""; // res=Static, name='static, source=Reference, syntax=Named
118+
/// struct Name<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=ExplicitBound
119+
/// const A: Name = Name("a"); // res=Static, name='_, source=Path, syntax=Implicit
120+
/// const B: &str = ""; // res=Static, name='_, source=Reference, syntax=Implicit
121+
/// static C: &'_ str = ""; // res=Static, name='_, source=Reference, syntax=ExplicitAnonymous
122+
/// static D: &'static str = ""; // res=Static, name='static, source=Reference, syntax=ExplicitBound
123123
///
124124
/// trait Tr {}
125-
/// fn tr(_: Box<dyn Tr>) {} // res=ImplicitObjectLifetimeDefault, name='_, source=Other, syntax=Hidden
125+
/// fn tr(_: Box<dyn Tr>) {} // res=ImplicitObjectLifetimeDefault, name='_, source=Other, syntax=Implicit
126126
///
127127
/// fn capture_outlives<'a>() ->
128-
/// impl FnOnce() + 'a // res=Param, ident='a, source=OutlivesBound, syntax=Named
128+
/// impl FnOnce() + 'a // res=Param, ident='a, source=OutlivesBound, syntax=ExplicitBound
129129
/// {
130130
/// || {}
131131
/// }
132132
///
133133
/// fn capture_precise<'a>() ->
134-
/// impl FnOnce() + use<'a> // res=Param, ident='a, source=PreciseCapturing, syntax=Named
134+
/// impl FnOnce() + use<'a> // res=Param, ident='a, source=PreciseCapturing, syntax=ExplicitBound
135135
/// {
136136
/// || {}
137137
/// }
138138
///
139139
/// // (commented out because these cases trigger errors)
140-
/// // struct S1<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=Named
141-
/// // struct S2(S1); // res=Error, name='_, source=Path, syntax=Hidden
142-
/// // struct S3(S1<'_>); // res=Error, name='_, source=Path, syntax=Anonymous
143-
/// // struct S4(S1<'a>); // res=Error, name='a, source=Path, syntax=Named
140+
/// // struct S1<'a>(&'a str); // res=Param, name='a, source=Reference, syntax=ExplicitBound
141+
/// // struct S2(S1); // res=Error, name='_, source=Path, syntax=Implicit
142+
/// // struct S3(S1<'_>); // res=Error, name='_, source=Path, syntax=ExplicitAnonymous
143+
/// // struct S4(S1<'a>); // res=Error, name='a, source=Path, syntax=ExplicitBound
144144
/// ```
145145
///
146-
/// Some combinations that cannot occur are `LifetimeSyntax::Hidden` with
146+
/// Some combinations that cannot occur are `LifetimeSyntax::Implicit` with
147147
/// `LifetimeSource::OutlivesBound` or `LifetimeSource::PreciseCapturing`
148148
/// — there's no way to "elide" these lifetimes.
149149
#[derive(Debug, Copy, Clone, HashStable_Generic)]
@@ -206,7 +206,7 @@ impl ParamName {
206206
}
207207
}
208208

209-
#[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable_Generic)]
209+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, HashStable_Generic)]
210210
pub enum LifetimeKind {
211211
/// User-given names or fresh (synthetic) names.
212212
Param(LocalDefId),
@@ -287,12 +287,8 @@ impl Lifetime {
287287
self.ident.name == kw::UnderscoreLifetime
288288
}
289289

290-
pub fn is_syntactically_hidden(&self) -> bool {
291-
matches!(self.syntax, LifetimeSyntax::Hidden)
292-
}
293-
294-
pub fn is_syntactically_anonymous(&self) -> bool {
295-
matches!(self.syntax, LifetimeSyntax::Anonymous)
290+
pub fn is_implicit(&self) -> bool {
291+
matches!(self.syntax, LifetimeSyntax::Implicit)
296292
}
297293

298294
pub fn is_static(&self) -> bool {
@@ -307,28 +303,28 @@ impl Lifetime {
307303

308304
match (self.syntax, self.source) {
309305
// The user wrote `'a` or `'_`.
310-
(Named | Anonymous, _) => (self.ident.span, format!("{new_lifetime}")),
306+
(ExplicitBound | ExplicitAnonymous, _) => (self.ident.span, format!("{new_lifetime}")),
311307

312308
// The user wrote `Path<T>`, and omitted the `'_,`.
313-
(Hidden, Path { angle_brackets: AngleBrackets::Full }) => {
309+
(Implicit, Path { angle_brackets: AngleBrackets::Full }) => {
314310
(self.ident.span, format!("{new_lifetime}, "))
315311
}
316312

317313
// The user wrote `Path<>`, and omitted the `'_`..
318-
(Hidden, Path { angle_brackets: AngleBrackets::Empty }) => {
314+
(Implicit, Path { angle_brackets: AngleBrackets::Empty }) => {
319315
(self.ident.span, format!("{new_lifetime}"))
320316
}
321317

322318
// The user wrote `Path` and omitted the `<'_>`.
323-
(Hidden, Path { angle_brackets: AngleBrackets::Missing }) => {
319+
(Implicit, Path { angle_brackets: AngleBrackets::Missing }) => {
324320
(self.ident.span.shrink_to_hi(), format!("<{new_lifetime}>"))
325321
}
326322

327323
// The user wrote `&type` or `&mut type`.
328-
(Hidden, Reference) => (self.ident.span, format!("{new_lifetime} ")),
324+
(Implicit, Reference) => (self.ident.span, format!("{new_lifetime} ")),
329325

330-
(Hidden, source) => {
331-
unreachable!("can't suggest for a hidden lifetime of {source:?}")
326+
(Implicit, source) => {
327+
unreachable!("can't suggest for a implicit lifetime of {source:?}")
332328
}
333329
}
334330
}

compiler/rustc_hir/src/hir/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ fn trait_object_roundtrips_impl(syntax: TraitObjectSyntax) {
5555
ident: Ident::new(sym::name, DUMMY_SP),
5656
kind: LifetimeKind::Static,
5757
source: LifetimeSource::Other,
58-
syntax: LifetimeSyntax::Hidden,
58+
syntax: LifetimeSyntax::Implicit,
5959
};
6060
let unambig = TyKind::TraitObject::<'_, ()>(&[], TaggedRef::new(&lt, syntax));
6161
let unambig_to_ambig = unsafe { std::mem::transmute::<_, TyKind<'_, AmbigArg>>(unambig) };

compiler/rustc_lint/messages.ftl

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,6 @@ lint_duplicate_macro_attribute =
253253
254254
lint_duplicate_matcher_binding = duplicate matcher binding
255255
256-
lint_elided_named_lifetime = elided lifetime has a name
257-
.label_elided = this elided lifetime gets resolved as `{$name}`
258-
.label_named = lifetime `{$name}` declared here
259-
.suggestion = consider specifying it explicitly
260-
261256
lint_enum_intrinsics_mem_discriminant =
262257
the return value of `mem::discriminant` is unspecified when called with a non-enum type
263258
.note = the argument to `discriminant` should be a reference to an enum, but it was passed a reference to a `{$ty_param}`, which is not an enum
@@ -518,6 +513,28 @@ lint_metavariable_still_repeating = variable `{$name}` is still repeating at thi
518513
519514
lint_metavariable_wrong_operator = meta-variable repeats with different Kleene operator
520515
516+
lint_mismatched_lifetime_syntaxes =
517+
lifetime flowing from input to output with different syntax can be confusing
518+
.label_mismatched_lifetime_syntaxes_inputs =
519+
{$n_inputs ->
520+
[one] this lifetime flows
521+
*[other] these lifetimes flow
522+
} to the output
523+
.label_mismatched_lifetime_syntaxes_outputs =
524+
the {$n_outputs ->
525+
[one] lifetime gets
526+
*[other] lifetimes get
527+
} resolved as `{$lifetime_name}`
528+
529+
lint_mismatched_lifetime_syntaxes_suggestion_explicit =
530+
one option is to consistently use `{$lifetime_name}`
531+
532+
lint_mismatched_lifetime_syntaxes_suggestion_implicit =
533+
one option is to consistently remove the lifetime
534+
535+
lint_mismatched_lifetime_syntaxes_suggestion_mixed =
536+
one option is to remove the lifetime for references and use the anonymous lifetime for paths
537+
521538
lint_missing_fragment_specifier = missing fragment specifier
522539
523540
lint_missing_unsafe_on_extern = extern blocks should be unsafe

compiler/rustc_lint/src/early/diagnostics.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ use rustc_errors::{
1010
use rustc_middle::middle::stability;
1111
use rustc_middle::ty::TyCtxt;
1212
use rustc_session::Session;
13-
use rustc_session::lint::{BuiltinLintDiag, ElidedLifetimeResolution};
14-
use rustc_span::{BytePos, kw};
13+
use rustc_session::lint::BuiltinLintDiag;
14+
use rustc_span::BytePos;
1515
use tracing::debug;
1616

17-
use crate::lints::{self, ElidedNamedLifetime};
17+
use crate::lints;
1818

1919
mod check_cfg;
2020

@@ -471,16 +471,5 @@ pub fn decorate_builtin_lint(
471471
BuiltinLintDiag::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by } => {
472472
lints::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by }.decorate_lint(diag)
473473
}
474-
BuiltinLintDiag::ElidedNamedLifetimes { elided: (span, kind), resolution } => {
475-
match resolution {
476-
ElidedLifetimeResolution::Static => {
477-
ElidedNamedLifetime { span, kind, name: kw::StaticLifetime, declaration: None }
478-
}
479-
ElidedLifetimeResolution::Param(name, declaration) => {
480-
ElidedNamedLifetime { span, kind, name, declaration: Some(declaration) }
481-
}
482-
}
483-
.decorate_lint(diag)
484-
}
485474
}
486475
}

compiler/rustc_lint/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ mod invalid_from_utf8;
5555
mod late;
5656
mod let_underscore;
5757
mod levels;
58+
mod lifetime_syntax;
5859
mod lints;
5960
mod macro_expr_fragment_specifier_2024_migration;
6061
mod map_unit_fn;
@@ -96,6 +97,7 @@ use impl_trait_overcaptures::ImplTraitOvercaptures;
9697
use internal::*;
9798
use invalid_from_utf8::*;
9899
use let_underscore::*;
100+
use lifetime_syntax::*;
99101
use macro_expr_fragment_specifier_2024_migration::*;
100102
use map_unit_fn::*;
101103
use multiple_supertrait_upcastable::*;
@@ -246,6 +248,7 @@ late_lint_methods!(
246248
StaticMutRefs: StaticMutRefs,
247249
UnqualifiedLocalImports: UnqualifiedLocalImports,
248250
CheckTransmutes: CheckTransmutes,
251+
LifetimeSyntax: LifetimeSyntax,
249252
]
250253
]
251254
);
@@ -353,6 +356,7 @@ fn register_builtins(store: &mut LintStore) {
353356
store.register_renamed("unused_tuple_struct_fields", "dead_code");
354357
store.register_renamed("static_mut_ref", "static_mut_refs");
355358
store.register_renamed("temporary_cstring_as_ptr", "dangling_pointers_from_temporaries");
359+
store.register_renamed("elided_named_lifetimes", "mismatched_lifetime_syntaxes");
356360

357361
// These were moved to tool lints, but rustc still sees them when compiling normally, before
358362
// tool lints are registered, so `check_tool_name_for_backwards_compat` doesn't work. Use

0 commit comments

Comments
 (0)