diff options
| author | Camille GILLOT <gillot.camille@gmail.com> | 2022-03-10 23:12:35 +0100 |
|---|---|---|
| committer | Camille GILLOT <gillot.camille@gmail.com> | 2022-04-17 11:03:34 +0200 |
| commit | a9e13fa5533df7fc004aeab00d45822eacb8461e (patch) | |
| tree | 6d759bc7958cc987f346f6e7f1d00b3487fc7b01 /compiler/rustc_resolve/src | |
| parent | ca57bada05373ff227c661cc542e99c92b70c0ee (diff) | |
| download | rust-a9e13fa5533df7fc004aeab00d45822eacb8461e.tar.gz rust-a9e13fa5533df7fc004aeab00d45822eacb8461e.zip | |
Lint elided lifetimes in path on the AST.
Diffstat (limited to 'compiler/rustc_resolve/src')
| -rw-r--r-- | compiler/rustc_resolve/src/build_reduced_graph.rs | 9 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/ident.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/late.rs | 132 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/late/diagnostics.rs | 36 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/late/lifetimes.rs | 24 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/lib.rs | 46 |
6 files changed, 188 insertions, 61 deletions
diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 54e8c03156d..063b15e643d 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -521,11 +521,10 @@ impl<'a, 'b> BuildReducedGraphVisitor<'a, 'b> { // while the current crate doesn't have a valid `crate_name`. if crate_name != kw::Empty { // `crate_name` should not be interpreted as relative. - module_path.push(Segment { - ident: Ident { name: kw::PathRoot, span: source.ident.span }, - id: Some(self.r.next_node_id()), - has_generic_args: false, - }); + module_path.push(Segment::from_ident_and_id( + Ident { name: kw::PathRoot, span: source.ident.span }, + self.r.next_node_id(), + )); source.ident.name = crate_name; } if rename.is_none() { diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 24b6d656981..84fe0ec83d2 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1402,7 +1402,7 @@ impl<'a> Resolver<'a> { let mut allow_super = true; let mut second_binding = None; - for (i, &Segment { ident, id, has_generic_args: _ }) in path.iter().enumerate() { + for (i, &Segment { ident, id, .. }) in path.iter().enumerate() { debug!("resolve_path ident {} {:?} {:?}", i, ident, id); let record_segment_res = |this: &mut Self, res| { if finalize.is_some() { diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index c6cebd1d3b1..9bc5d63ca17 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -21,10 +21,11 @@ use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, DefKind, PartialRes, PerNS}; use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; use rustc_hir::{PrimTy, TraitCandidate}; +use rustc_middle::ty::DefIdTree; use rustc_middle::{bug, span_bug}; use rustc_session::lint; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::Span; +use rustc_span::{BytePos, Span}; use smallvec::{smallvec, SmallVec}; use rustc_span::source_map::{respan, Spanned}; @@ -1167,6 +1168,134 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { self.resolve_anonymous_lifetime(<, true); } + #[tracing::instrument(level = "debug", skip(self))] + fn resolve_elided_lifetimes_in_path( + &mut self, + path_id: NodeId, + partial_res: PartialRes, + path: &[Segment], + source: PathSource<'_>, + finalize: Finalize, + ) { + let Some(path_span) = finalize.path_span() else { + return; + }; + let proj_start = path.len() - partial_res.unresolved_segments(); + for (i, segment) in path.iter().enumerate() { + if segment.has_lifetime_args { + continue; + } + let Some(segment_id) = segment.id else { + continue; + }; + + // Figure out if this is a type/trait segment, + // which may need lifetime elision performed. + let type_def_id = match partial_res.base_res() { + Res::Def(DefKind::AssocTy, def_id) if i + 2 == proj_start => { + self.r.parent(def_id).unwrap() + } + Res::Def(DefKind::Variant, def_id) if i + 1 == proj_start => { + self.r.parent(def_id).unwrap() + } + Res::Def(DefKind::Struct, def_id) + | Res::Def(DefKind::Union, def_id) + | Res::Def(DefKind::Enum, def_id) + | Res::Def(DefKind::TyAlias, def_id) + | Res::Def(DefKind::Trait, def_id) + if i + 1 == proj_start => + { + def_id + } + _ => continue, + }; + + let expected_lifetimes = self.r.item_generics_num_lifetimes(type_def_id); + if expected_lifetimes == 0 { + continue; + } + + let missing = match source { + PathSource::Trait(..) | PathSource::TraitItem(..) | PathSource::Type => true, + PathSource::Expr(..) + | PathSource::Pat + | PathSource::Struct + | PathSource::TupleStruct(..) => false, + }; + let mut error = false; + for rib in self.lifetime_ribs.iter().rev() { + match rib.kind { + // In create-parameter mode we error here because we don't want to support + // deprecated impl elision in new features like impl elision and `async fn`, + // both of which work using the `CreateParameter` mode: + // + // impl Foo for std::cell::Ref<u32> // note lack of '_ + // async fn foo(_: std::cell::Ref<u32>) { ... } + LifetimeRibKind::AnonymousCreateParameter => { + error = true; + break; + } + // `PassThrough` is the normal case. + // `new_error_lifetime`, which would usually be used in the case of `ReportError`, + // is unsuitable here, as these can occur from missing lifetime parameters in a + // `PathSegment`, for which there is no associated `'_` or `&T` with no explicit + // lifetime. Instead, we simply create an implicit lifetime, which will be checked + // later, at which point a suitable error will be emitted. + LifetimeRibKind::AnonymousPassThrough + | LifetimeRibKind::AnonymousReportError + | LifetimeRibKind::Item => break, + _ => {} + } + } + + if !missing { + continue; + } + + let elided_lifetime_span = if segment.has_generic_args { + // If there are brackets, but not generic arguments, then use the opening bracket + segment.args_span.with_hi(segment.args_span.lo() + BytePos(1)) + } else { + // If there are no brackets, use the identifier span. + // 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) + }; + if error { + let sess = self.r.session; + let mut err = rustc_errors::struct_span_err!( + sess, + path_span, + E0726, + "implicit elided lifetime not allowed here" + ); + rustc_errors::add_elided_lifetime_in_path_suggestion( + sess.source_map(), + &mut err, + expected_lifetimes, + path_span, + !segment.has_generic_args, + elided_lifetime_span, + ); + err.note("assuming a `'static` lifetime..."); + err.emit(); + } else { + self.r.lint_buffer.buffer_lint_with_diagnostic( + lint::builtin::ELIDED_LIFETIMES_IN_PATHS, + segment_id, + elided_lifetime_span, + "hidden lifetime parameters in types are deprecated", + lint::BuiltinLintDiagnostics::ElidedLifetimesInPaths( + expected_lifetimes, + path_span, + !segment.has_generic_args, + elided_lifetime_span, + ), + ); + } + } + } + /// Searches the current set of local scopes for labels. Returns the `NodeId` of the resolved /// label and reports an error if the label is not found or is unreachable. fn resolve_label(&self, mut label: Ident) -> Option<NodeId> { @@ -2528,6 +2657,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> { self.r.record_partial_res(id, partial_res); } + self.resolve_elided_lifetimes_in_path(id, partial_res, path, source, finalize); partial_res } diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 51a35f8f26d..0bae141ce26 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1986,38 +1986,6 @@ impl<'tcx> LifetimeContext<'_, 'tcx> { } } - crate fn report_elided_lifetime_in_ty(&self, lifetime_refs: &[&hir::Lifetime]) { - let Some(missing_lifetime) = lifetime_refs.iter().find(|lt| { - lt.name == hir::LifetimeName::Implicit(true) - }) else { return }; - - let mut spans: Vec<_> = lifetime_refs.iter().map(|lt| lt.span).collect(); - spans.sort(); - let mut spans_dedup = spans.clone(); - spans_dedup.dedup(); - let spans_with_counts: Vec<_> = spans_dedup - .into_iter() - .map(|sp| (sp, spans.iter().filter(|nsp| *nsp == &sp).count())) - .collect(); - - self.tcx.struct_span_lint_hir( - rustc_session::lint::builtin::ELIDED_LIFETIMES_IN_PATHS, - missing_lifetime.hir_id, - spans, - |lint| { - let mut db = lint.build("hidden lifetime parameters in types are deprecated"); - self.add_missing_lifetime_specifiers_label( - &mut db, - spans_with_counts, - &FxHashSet::from_iter([kw::UnderscoreLifetime]), - Vec::new(), - &[], - ); - db.emit(); - }, - ); - } - // FIXME(const_generics): This patches over an ICE caused by non-'static lifetimes in const // generics. We are disallowing this until we can decide on how we want to handle non-'static // lifetimes in const generics. See issue #74052 for discussion. @@ -2452,9 +2420,7 @@ impl<'tcx> LifetimeContext<'_, 'tcx> { ); let is_allowed_lifetime = matches!( lifetime_ref.name, - hir::LifetimeName::Implicit(_) - | hir::LifetimeName::Static - | hir::LifetimeName::Underscore + hir::LifetimeName::Implicit | hir::LifetimeName::Static | hir::LifetimeName::Underscore ); if !self.tcx.lazy_normalization() && is_anon_const && !is_allowed_lifetime { diff --git a/compiler/rustc_resolve/src/late/lifetimes.rs b/compiler/rustc_resolve/src/late/lifetimes.rs index 72ca9918e12..d5f2e2db1e3 100644 --- a/compiler/rustc_resolve/src/late/lifetimes.rs +++ b/compiler/rustc_resolve/src/late/lifetimes.rs @@ -921,7 +921,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> { } }); match lifetime.name { - LifetimeName::Implicit(_) => { + LifetimeName::Implicit => { // For types like `dyn Foo`, we should // generate a special form of elided. span_bug!(ty.span, "object-lifetime-default expected, not implicit",); @@ -2955,9 +2955,9 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { let error = loop { match *scope { // Do not assign any resolution, it will be inferred. - Scope::Body { .. } => break Ok(()), + Scope::Body { .. } => return, - Scope::Root => break Err(None), + Scope::Root => break None, Scope::Binder { s, ref lifetimes, scope_type, .. } => { // collect named lifetimes for suggestions @@ -2984,7 +2984,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { self.insert_lifetime(lifetime_ref, lifetime); } - break Ok(()); + return; } Scope::Elision { elide: Elide::Exact(l), .. } => { @@ -2992,7 +2992,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { for lifetime_ref in lifetime_refs { self.insert_lifetime(lifetime_ref, lifetime); } - break Ok(()); + return; } Scope::Elision { elide: Elide::Error(ref e), ref s, .. } => { @@ -3017,10 +3017,10 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { _ => break, } } - break Err(Some(&e[..])); + break Some(&e[..]); } - Scope::Elision { elide: Elide::Forbid, .. } => break Err(None), + Scope::Elision { elide: Elide::Forbid, .. } => break None, Scope::ObjectLifetimeDefault { s, .. } | Scope::Supertrait { s, .. } @@ -3030,14 +3030,6 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { } }; - let error = match error { - Ok(()) => { - self.report_elided_lifetime_in_ty(lifetime_refs); - return; - } - Err(error) => error, - }; - // If we specifically need the `scope_for_path` map, then we're in the // diagnostic pass and we don't want to emit more errors. if self.map.scope_for_path.is_some() { @@ -3174,7 +3166,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> { )) .emit(); } - hir::LifetimeName::Param(_) | hir::LifetimeName::Implicit(_) => { + hir::LifetimeName::Param(_) | hir::LifetimeName::Implicit => { self.resolve_lifetime_ref(lt); } hir::LifetimeName::ImplicitObjectLifetimeDefault => { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 7262d150f4c..eed54370e23 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -28,7 +28,7 @@ pub use rustc_hir::def::{Namespace, PerNS}; use rustc_arena::{DroplessArena, TypedArena}; use rustc_ast::node_id::NodeMap; use rustc_ast::{self as ast, NodeId, CRATE_NODE_ID}; -use rustc_ast::{Crate, Expr, ExprKind, LitKind, Path}; +use rustc_ast::{AngleBracketedArg, Crate, Expr, ExprKind, GenericArg, GenericArgs, LitKind, Path}; use rustc_ast_lowering::ResolverAstLowering; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_data_structures::intern::Interned; @@ -283,6 +283,9 @@ pub struct Segment { /// Signals whether this `PathSegment` has generic arguments. Used to avoid providing /// nonsensical suggestions. has_generic_args: bool, + /// Signals whether this `PathSegment` has lifetime arguments. + has_lifetime_args: bool, + args_span: Span, } impl Segment { @@ -291,7 +294,23 @@ impl Segment { } fn from_ident(ident: Ident) -> Segment { - Segment { ident, id: None, has_generic_args: false } + Segment { + ident, + id: None, + has_generic_args: false, + has_lifetime_args: false, + args_span: DUMMY_SP, + } + } + + fn from_ident_and_id(ident: Ident, id: NodeId) -> Segment { + Segment { + ident, + id: Some(id), + has_generic_args: false, + has_lifetime_args: false, + args_span: DUMMY_SP, + } } fn names_to_string(segments: &[Segment]) -> String { @@ -301,7 +320,28 @@ impl Segment { impl<'a> From<&'a ast::PathSegment> for Segment { fn from(seg: &'a ast::PathSegment) -> Segment { - Segment { ident: seg.ident, id: Some(seg.id), has_generic_args: seg.args.is_some() } + let has_generic_args = seg.args.is_some(); + let (args_span, has_lifetime_args) = if let Some(args) = seg.args.as_deref() { + match args { + GenericArgs::AngleBracketed(args) => { + let found_lifetimes = args + .args + .iter() + .any(|arg| matches!(arg, AngleBracketedArg::Arg(GenericArg::Lifetime(_)))); + (args.span, found_lifetimes) + } + GenericArgs::Parenthesized(args) => (args.span, true), + } + } else { + (DUMMY_SP, false) + }; + Segment { + ident: seg.ident, + id: Some(seg.id), + has_generic_args, + has_lifetime_args, + args_span, + } } } |
