diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 43bf951eddc6c..7da4fac882ac5 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -5,7 +5,7 @@ use rustc_ast::*; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; -use rustc_hir::{self as hir, HirId, IsAnonInPath, PredicateOrigin}; +use rustc_hir::{self as hir, HirId, PredicateOrigin}; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::ty::{ResolverAstLowering, TyCtxt}; use rustc_span::edit_distance::find_best_match_for_name; @@ -1823,7 +1823,7 @@ impl<'hir> LoweringContext<'_, 'hir> { } GenericParamKind::Lifetime => { let lt_id = self.next_node_id(); - let lifetime = self.new_named_lifetime(id, lt_id, ident, IsAnonInPath::No); + let lifetime = self.new_named_lifetime(id, lt_id, ident, None); hir::WherePredicateKind::RegionPredicate(hir::WhereRegionPredicate { lifetime, bounds, diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index d5d6dcd8d631d..75629d1c2077d 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -55,8 +55,7 @@ use rustc_errors::{DiagArgFromDisplay, DiagCtxtHandle, StashKey}; use rustc_hir::def::{DefKind, LifetimeRes, Namespace, PartialRes, PerNS, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId}; use rustc_hir::{ - self as hir, ConstArg, GenericArg, HirId, IsAnonInPath, ItemLocalMap, LangItem, ParamName, - TraitCandidate, + self as hir, ConstArg, GenericArg, HirId, ItemLocalMap, LangItem, ParamName, TraitCandidate, }; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_macros::extension; @@ -1756,11 +1755,21 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { } fn lower_lifetime(&mut self, l: &Lifetime) -> &'hir hir::Lifetime { - self.new_named_lifetime(l.id, l.id, l.ident, IsAnonInPath::No) + self.new_named_lifetime(l.id, l.id, l.ident, None) } - fn lower_lifetime_anon_in_path(&mut self, id: NodeId, span: Span) -> &'hir hir::Lifetime { - self.new_named_lifetime(id, id, Ident::new(kw::UnderscoreLifetime, span), IsAnonInPath::Yes) + fn lower_lifetime_anon_in_path( + &mut self, + id: NodeId, + span: Span, + angle_brackets: hir::AngleBrackets, + ) -> &'hir hir::Lifetime { + self.new_named_lifetime( + id, + id, + Ident::new(kw::UnderscoreLifetime, span), + Some(angle_brackets), + ) } #[instrument(level = "debug", skip(self))] @@ -1769,7 +1778,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { id: NodeId, new_id: NodeId, ident: Ident, - is_anon_in_path: IsAnonInPath, + is_anon_in_path: hir::IsAnonInPath, ) -> &'hir hir::Lifetime { debug_assert_ne!(ident.name, kw::Empty); let res = self.resolver.get_lifetime_res(id).unwrap_or(LifetimeRes::Error); @@ -1795,7 +1804,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { }; #[cfg(debug_assertions)] - if is_anon_in_path == IsAnonInPath::Yes { + if is_anon_in_path.is_some() { debug_assert_eq!(ident.name, kw::UnderscoreLifetime); } @@ -2393,7 +2402,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { self.next_id(), Ident::new(kw::UnderscoreLifetime, self.lower_span(span)), hir::LifetimeName::ImplicitObjectLifetimeDefault, - IsAnonInPath::No, + None, ); debug!("elided_dyn_bound: r={:?}", r); self.arena.alloc(r) diff --git a/compiler/rustc_ast_lowering/src/path.rs b/compiler/rustc_ast_lowering/src/path.rs index c464c159c34cd..8786d1a92cd8d 100644 --- a/compiler/rustc_ast_lowering/src/path.rs +++ b/compiler/rustc_ast_lowering/src/path.rs @@ -433,24 +433,34 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { // Note: these spans are used for diagnostics when they can't be inferred. // See rustc_resolve::late::lifetimes::LifetimeContext::add_missing_lifetime_specifiers_label - let elided_lifetime_span = if generic_args.span.is_empty() { - // If there are no brackets, use the identifier span. + let (elided_lifetime_span, angle_brackets) = if generic_args.span.is_empty() { + // No brackets, e.g. `Path`: use an empty span just past the end of the identifier. // HACK: we use find_ancestor_inside to properly suggest elided spans in paths // originating from macros, since the segment's span might be from a macro arg. - segment_ident_span.find_ancestor_inside(path_span).unwrap_or(path_span) - } else if generic_args.is_empty() { - // If there are brackets, but not generic arguments, then use the opening bracket - generic_args.span.with_hi(generic_args.span.lo() + BytePos(1)) + ( + segment_ident_span + .find_ancestor_inside(path_span) + .unwrap_or(path_span) + .shrink_to_hi(), + hir::AngleBrackets::Missing, + ) } else { - // Else use an empty span right after the opening bracket. - generic_args.span.with_lo(generic_args.span.lo() + BytePos(1)).shrink_to_lo() + // Brackets, e.g. `Path<>` or `Path`: use an empty span just after the `<`. + ( + generic_args.span.with_lo(generic_args.span.lo() + BytePos(1)).shrink_to_lo(), + if generic_args.is_empty() { + hir::AngleBrackets::Empty + } else { + hir::AngleBrackets::Full + }, + ) }; generic_args.args.insert_many( 0, (start.as_u32()..end.as_u32()).map(|i| { let id = NodeId::from_u32(i); - let l = self.lower_lifetime_anon_in_path(id, elided_lifetime_span); + let l = self.lower_lifetime_anon_in_path(id, elided_lifetime_span, angle_brackets); GenericArg::Lifetime(l) }), ); diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index e3e96894ed1f7..090abb23cd15f 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -36,42 +36,50 @@ pub(crate) use crate::hir_id::{HirId, ItemLocalId, ItemLocalMap, OwnerId}; use crate::intravisit::{FnKind, VisitorExt}; #[derive(Debug, Copy, Clone, PartialEq, Eq, HashStable_Generic)] -pub enum IsAnonInPath { - No, - Yes, +pub enum AngleBrackets { + /// E.g. `Path`. + Missing, + /// E.g. `Path<>`. + Empty, + /// E.g. `Path`. + Full, } +pub type IsAnonInPath = Option; + /// A lifetime. The valid field combinations are non-obvious. The following /// example shows some of them. See also the comments on `LifetimeName`. /// ``` /// #[repr(C)] -/// struct S<'a>(&'a u32); // res=Param, name='a, IsAnonInPath::No +/// struct S<'a>(&'a u32); // res=Param, name='a, is_anon_in_path=None /// unsafe extern "C" { -/// fn f1(s: S); // res=Param, name='_, IsAnonInPath::Yes -/// fn f2(s: S<'_>); // res=Param, name='_, IsAnonInPath::No -/// fn f3<'a>(s: S<'a>); // res=Param, name='a, IsAnonInPath::No +/// fn f1(s: S); // res=Param, name='_, is_anon_in_path=Some(Missing) +/// fn f2(s: S<>); // res=Param, name='_, is_anon_in_path=Some(Empty) +/// fn f3(s: S<'_>); // res=Param, name='_, is_anon_in_path=None +/// fn f4<'a>(s: S<'a>); // res=Param, name='a, is_anon_in_path=None /// } /// -/// struct St<'a> { x: &'a u32 } // res=Param, name='a, IsAnonInPath::No +/// struct St<'a, T> { x: &'a T } // res=Param, name='a, is_anon_in_path=None /// fn f() { -/// _ = St { x: &0 }; // res=Infer, name='_, IsAnonInPath::Yes -/// _ = St::<'_> { x: &0 }; // res=Infer, name='_, IsAnonInPath::No +/// _ = St:: { x: &0 }; // res=Infer, name='_, is_anon_in_path=Some(Full) +/// _ = St::<'_, u8> { x: &0 }; // res=Infer, name='_, is_anon_in_path=None /// } /// -/// struct Name<'a>(&'a str); // res=Param, name='a, IsAnonInPath::No -/// const A: Name = Name("a"); // res=Static, name='_, IsAnonInPath::Yes -/// const B: &str = ""; // res=Static, name='_, IsAnonInPath::No -/// static C: &'_ str = ""; // res=Static, name='_, IsAnonInPath::No -/// static D: &'static str = ""; // res=Static, name='static, IsAnonInPath::No +/// struct Name<'a>(&'a str); // res=Param, name='a, is_anon_in_path=None +/// const A: Name = Name("a"); // res=Static, name='_, is_anon_in_path=Some(Missing) +/// const B: &str = ""; // res=Static, name='_, is_anon_in_path=None +/// static C: &'_ str = ""; // res=Static, name='_, is_anon_in_path=None +/// static D: &'static str = ""; // res=Static, name='static, is_anon_in_path=None /// /// trait Tr {} -/// fn tr(_: Box) {} // res=ImplicitObjectLifetimeDefault, name='_, IsAnonInPath::No +/// fn tr(_: Box) {} // res=ImplicitObjectLifetimeDefault, +/// // name='_, is_anon_in_path=None /// /// // (commented out because these cases trigger errors) -/// // struct S1<'a>(&'a str); // res=Param, name='a, IsAnonInPath::No -/// // struct S2(S1); // res=Error, name='_, IsAnonInPath::Yes -/// // struct S3(S1<'_>); // res=Error, name='_, IsAnonInPath::No -/// // struct S4(S1<'a>); // res=Error, name='a, IsAnonInPath::No +/// // struct S1<'a>(&'a str); // res=Param, name='a, is_anon_in_path=None +/// // struct S2(S1<>); // res=Error, name='_, is_anon_in_path=Some(Empty) +/// // struct S3(S1<'_>); // res=Error, name='_, is_anon_in_path=None +/// // struct S4(S1<'a>); // res=Error, name='a, is_anon_in_path=None /// ``` #[derive(Debug, Copy, Clone, HashStable_Generic)] pub struct Lifetime { @@ -86,8 +94,9 @@ pub struct Lifetime { /// Semantics of this lifetime. pub res: LifetimeName, - /// Is the lifetime anonymous and in a path? Used only for error - /// suggestions. See `Lifetime::suggestion` for example use. + /// Is the lifetime anonymous and in a path? And if so, what do the path + /// generics look like? Used only for error suggestions. See + /// `Lifetime::suggestion` for example use. pub is_anon_in_path: IsAnonInPath, } @@ -212,21 +221,31 @@ impl Lifetime { pub fn suggestion(&self, new_lifetime: &str) -> (Span, String) { debug_assert!(new_lifetime.starts_with('\'')); - match (self.is_anon_in_path, self.ident.span.is_empty()) { - // The user wrote `Path`, and omitted the `'_,`. - (IsAnonInPath::Yes, true) => (self.ident.span, format!("{new_lifetime}, ")), - - // The user wrote `Path` and omitted the `<'_>`. - (IsAnonInPath::Yes, false) => { - (self.ident.span.shrink_to_hi(), format!("<{new_lifetime}>")) + let s = match self.is_anon_in_path { + Some(AngleBrackets::Missing) => { + // `Path`: insert suggestion just after the identifier. + format!("<{new_lifetime}>") } - - // The user wrote `&type` or `&mut type`. - (IsAnonInPath::No, true) => (self.ident.span, format!("{new_lifetime} ")), - - // The user wrote `'a` or `'_`. - (IsAnonInPath::No, false) => (self.ident.span, format!("{new_lifetime}")), - } + Some(AngleBrackets::Empty) => { + // `Path<>`: insert suggestion just after the `<`. + format!("{new_lifetime}") + } + Some(AngleBrackets::Full) => { + // `Path`: insert suggestion just after the `<`. + format!("{new_lifetime}, ") + } + None => { + if self.ident.span.is_empty() { + // The user wrote `&type` or `&mut type`. + // njn: does this get the mut case right? `&mut 'atype`? + format!("{new_lifetime} ") + } else { + // The user wrote `'a` or `'_`. + format!("{new_lifetime}") + } + } + }; + (self.ident.span, s) } } diff --git a/compiler/rustc_hir/src/hir/tests.rs b/compiler/rustc_hir/src/hir/tests.rs index 62ef02d2f500c..57fdb18dcbe44 100644 --- a/compiler/rustc_hir/src/hir/tests.rs +++ b/compiler/rustc_hir/src/hir/tests.rs @@ -58,7 +58,7 @@ fn trait_object_roundtrips_impl(syntax: TraitObjectSyntax) { hir_id: HirId::INVALID, ident: Ident::new(sym::name, DUMMY_SP), res: LifetimeName::Static, - is_anon_in_path: IsAnonInPath::No, + is_anon_in_path: None, } }, syntax, diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs index b30390a9330eb..7856d96e42acd 100644 --- a/compiler/rustc_trait_selection/src/errors.rs +++ b/compiler/rustc_trait_selection/src/errors.rs @@ -9,7 +9,7 @@ use rustc_errors::{ use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{Visitor, VisitorExt, walk_ty}; -use rustc_hir::{self as hir, AmbigArg, FnRetTy, GenericParamKind, IsAnonInPath, Node}; +use rustc_hir::{self as hir, AmbigArg, FnRetTy, GenericParamKind, Node}; use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_middle::ty::print::{PrintTraitRefExt as _, TraitRefPrintOnlyTraitPath}; use rustc_middle::ty::{self, Binder, ClosureKind, FnSig, Region, Ty, TyCtxt}; @@ -567,19 +567,8 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> { impl<'v> Visitor<'v> for ImplicitLifetimeFinder { fn visit_ty(&mut self, ty: &'v hir::Ty<'v, AmbigArg>) { - let make_suggestion = |lifetime: &hir::Lifetime| { - if lifetime.is_anon_in_path == IsAnonInPath::Yes - && lifetime.ident.span.is_empty() - { - format!("{}, ", self.suggestion_param_name) - } else if lifetime.ident.name == kw::UnderscoreLifetime - && lifetime.ident.span.is_empty() - { - format!("{} ", self.suggestion_param_name) - } else { - self.suggestion_param_name.clone() - } - }; + let make_suggestion = + |lifetime: &hir::Lifetime| lifetime.suggestion(&self.suggestion_param_name); match ty.kind { hir::TyKind::Path(hir::QPath::Resolved(_, path)) => { for segment in path.segments { @@ -588,7 +577,7 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> { matches!( arg, hir::GenericArg::Lifetime(lifetime) - if lifetime.is_anon_in_path == IsAnonInPath::Yes + if lifetime.is_anon_in_path.is_some() ) }) { self.suggestions.push(( @@ -607,10 +596,7 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> { if let hir::GenericArg::Lifetime(lifetime) = arg && lifetime.is_anonymous() { - self.suggestions.push(( - lifetime.ident.span, - make_suggestion(lifetime), - )); + self.suggestions.push(make_suggestion(lifetime)); } } } @@ -618,7 +604,7 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> { } } hir::TyKind::Ref(lifetime, ..) if lifetime.is_anonymous() => { - self.suggestions.push((lifetime.ident.span, make_suggestion(lifetime))); + self.suggestions.push(make_suggestion(lifetime)); } _ => {} } diff --git a/tests/ui/suggestions/impl-trait-missing-lifetime-gated.rs b/tests/ui/suggestions/impl-trait-missing-lifetime-gated.rs index 443a7e3835e3e..f5c3da847c72f 100644 --- a/tests/ui/suggestions/impl-trait-missing-lifetime-gated.rs +++ b/tests/ui/suggestions/impl-trait-missing-lifetime-gated.rs @@ -49,6 +49,17 @@ mod alone_in_path { //~| ERROR missing lifetime specifier } +mod alone_in_path2 { + trait Foo<'a> { fn next(&mut self) -> Option<&'a ()>; } + + fn f(_: impl Foo<>) {} + //~^ ERROR anonymous lifetimes in `impl Trait` are unstable + + fn g(mut x: impl Foo<>) -> Option<&()> { x.next() } + //~^ ERROR anonymous lifetimes in `impl Trait` are unstable + //~| ERROR missing lifetime specifier +} + mod in_path { trait Foo<'a, T> { fn next(&mut self) -> Option<&'a T>; } diff --git a/tests/ui/suggestions/impl-trait-missing-lifetime-gated.stderr b/tests/ui/suggestions/impl-trait-missing-lifetime-gated.stderr index 24013c85c8758..3a15be928c37d 100644 --- a/tests/ui/suggestions/impl-trait-missing-lifetime-gated.stderr +++ b/tests/ui/suggestions/impl-trait-missing-lifetime-gated.stderr @@ -108,7 +108,28 @@ LL + fn g(mut x: impl Foo) -> Option<()> { x.next() } | error[E0106]: missing lifetime specifier - --> $DIR/impl-trait-missing-lifetime-gated.rs:58:41 + --> $DIR/impl-trait-missing-lifetime-gated.rs:58:39 + | +LL | fn g(mut x: impl Foo<>) -> Option<&()> { x.next() } + | ^ expected named lifetime parameter + | + = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from +help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static` + | +LL | fn g(mut x: impl Foo<>) -> Option<&'static ()> { x.next() } + | +++++++ +help: consider introducing a named lifetime parameter + | +LL | fn g<'a>(mut x: impl Foo<>) -> Option<&'a ()> { x.next() } + | ++++ ++ +help: alternatively, you might want to return an owned value + | +LL - fn g(mut x: impl Foo<>) -> Option<&()> { x.next() } +LL + fn g(mut x: impl Foo<>) -> Option<()> { x.next() } + | + +error[E0106]: missing lifetime specifier + --> $DIR/impl-trait-missing-lifetime-gated.rs:69:41 | LL | fn g(mut x: impl Foo<()>) -> Option<&()> { x.next() } | ^ expected named lifetime parameter @@ -129,7 +150,7 @@ LL + fn g(mut x: impl Foo<()>) -> Option<()> { x.next() } | warning: elided lifetime has a name - --> $DIR/impl-trait-missing-lifetime-gated.rs:64:57 + --> $DIR/impl-trait-missing-lifetime-gated.rs:75:57 | LL | fn resolved_anonymous<'a, T: 'a>(f: impl Fn(&'a str) -> &T) { | -- lifetime `'a` declared here ^ this elided lifetime gets resolved as `'a` @@ -191,10 +212,10 @@ LL + fn g<'a>(mut x: impl Iterator) -> Option<&'_ ()> { x.nex | error[E0658]: anonymous lifetimes in `impl Trait` are unstable - --> $DIR/impl-trait-missing-lifetime-gated.rs:44:18 + --> $DIR/impl-trait-missing-lifetime-gated.rs:44:21 | LL | fn f(_: impl Foo) {} - | ^^^ expected named lifetime parameter + | ^ expected named lifetime parameter | = help: add `#![feature(anonymous_lifetime_in_impl_trait)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date @@ -204,10 +225,10 @@ LL | fn f<'a>(_: impl Foo<'a>) {} | ++++ ++++ error[E0658]: anonymous lifetimes in `impl Trait` are unstable - --> $DIR/impl-trait-missing-lifetime-gated.rs:47:22 + --> $DIR/impl-trait-missing-lifetime-gated.rs:47:25 | LL | fn g(mut x: impl Foo) -> Option<&()> { x.next() } - | ^^^ expected named lifetime parameter + | ^ expected named lifetime parameter | = help: add `#![feature(anonymous_lifetime_in_impl_trait)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date @@ -219,6 +240,32 @@ LL | fn g<'a>(mut x: impl Foo<'a>) -> Option<&()> { x.next() } error[E0658]: anonymous lifetimes in `impl Trait` are unstable --> $DIR/impl-trait-missing-lifetime-gated.rs:55:22 | +LL | fn f(_: impl Foo<>) {} + | ^ expected named lifetime parameter + | + = help: add `#![feature(anonymous_lifetime_in_impl_trait)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: consider introducing a named lifetime parameter + | +LL | fn f<'a>(_: impl Foo<'a>) {} + | ++++ ++ + +error[E0658]: anonymous lifetimes in `impl Trait` are unstable + --> $DIR/impl-trait-missing-lifetime-gated.rs:58:26 + | +LL | fn g(mut x: impl Foo<>) -> Option<&()> { x.next() } + | ^ expected named lifetime parameter + | + = help: add `#![feature(anonymous_lifetime_in_impl_trait)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +help: consider introducing a named lifetime parameter + | +LL | fn g<'a>(mut x: impl Foo<'a>) -> Option<&()> { x.next() } + | ++++ ++ + +error[E0658]: anonymous lifetimes in `impl Trait` are unstable + --> $DIR/impl-trait-missing-lifetime-gated.rs:66:22 + | LL | fn f(_: impl Foo<()>) {} | ^ expected named lifetime parameter | @@ -230,7 +277,7 @@ LL | fn f<'a>(_: impl Foo<'a, ()>) {} | ++++ +++ error[E0658]: anonymous lifetimes in `impl Trait` are unstable - --> $DIR/impl-trait-missing-lifetime-gated.rs:58:26 + --> $DIR/impl-trait-missing-lifetime-gated.rs:69:26 | LL | fn g(mut x: impl Foo<()>) -> Option<&()> { x.next() } | ^ expected named lifetime parameter @@ -242,7 +289,7 @@ help: consider introducing a named lifetime parameter LL | fn g<'a>(mut x: impl Foo<'a, ()>) -> Option<&()> { x.next() } | ++++ +++ -error: aborting due to 14 previous errors; 1 warning emitted +error: aborting due to 17 previous errors; 1 warning emitted Some errors have detailed explanations: E0106, E0658. For more information about an error, try `rustc --explain E0106`.