1
Fork 0

Lint elided lifetimes in path on the AST.

This commit is contained in:
Camille GILLOT 2022-03-10 23:12:35 +01:00
parent ca57bada05
commit a9e13fa553
24 changed files with 279 additions and 154 deletions

View file

@ -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() {

View file

@ -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() {

View file

@ -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(&lt, 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
}

View file

@ -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 {

View file

@ -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 => {

View file

@ -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,
}
}
}