Skip to content

Commit

Permalink
Auto merge of #95779 - cjgillot:ast-lifetimes-undeclared, r=petrochenkov
Browse files Browse the repository at this point in the history
Report undeclared lifetimes during late resolution.

First step in #91557

We reuse the rib design of the current resolution framework. Specific `LifetimeRib` and `LifetimeRibKind` types are introduced. The most important variant is `LifetimeRibKind::Generics`, which happens each time we encounter something which may introduce generic lifetime parameters. It can be an item or a `for<...>` binder. The `LifetimeBinderKind` specifies how this rib behaves with respect to in-band lifetimes.

r? `@petrochenkov`
  • Loading branch information
bors committed Apr 17, 2022
2 parents af68f71 + e628df9 commit 1ec2c13
Show file tree
Hide file tree
Showing 54 changed files with 1,859 additions and 782 deletions.
19 changes: 9 additions & 10 deletions compiler/rustc_ast/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub enum FnCtxt {
#[derive(Copy, Clone, Debug)]
pub enum FnKind<'a> {
/// E.g., `fn foo()`, `fn foo(&self)`, or `extern "Abi" fn foo()`.
Fn(FnCtxt, Ident, &'a FnSig, &'a Visibility, Option<&'a Block>),
Fn(FnCtxt, Ident, &'a FnSig, &'a Visibility, &'a Generics, Option<&'a Block>),

/// E.g., `|x, y| body`.
Closure(&'a FnDecl, &'a Expr),
Expand All @@ -44,7 +44,7 @@ pub enum FnKind<'a> {
impl<'a> FnKind<'a> {
pub fn header(&self) -> Option<&'a FnHeader> {
match *self {
FnKind::Fn(_, _, sig, _, _) => Some(&sig.header),
FnKind::Fn(_, _, sig, _, _, _) => Some(&sig.header),
FnKind::Closure(_, _) => None,
}
}
Expand All @@ -58,7 +58,7 @@ impl<'a> FnKind<'a> {

pub fn decl(&self) -> &'a FnDecl {
match self {
FnKind::Fn(_, _, sig, _, _) => &sig.decl,
FnKind::Fn(_, _, sig, _, _, _) => &sig.decl,
FnKind::Closure(decl, _) => decl,
}
}
Expand Down Expand Up @@ -295,8 +295,8 @@ pub fn walk_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a Item) {
walk_list!(visitor, visit_expr, expr);
}
ItemKind::Fn(box Fn { defaultness: _, ref generics, ref sig, ref body }) => {
visitor.visit_generics(generics);
let kind = FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, body.as_deref());
let kind =
FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, generics, body.as_deref());
visitor.visit_fn(kind, item.span, item.id)
}
ItemKind::Mod(_unsafety, ref mod_kind) => match mod_kind {
Expand Down Expand Up @@ -561,8 +561,7 @@ pub fn walk_foreign_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a ForeignI
walk_list!(visitor, visit_expr, expr);
}
ForeignItemKind::Fn(box Fn { defaultness: _, ref generics, ref sig, ref body }) => {
visitor.visit_generics(generics);
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, body.as_deref());
let kind = FnKind::Fn(FnCtxt::Foreign, ident, sig, vis, generics, body.as_deref());
visitor.visit_fn(kind, span, id);
}
ForeignItemKind::TyAlias(box TyAlias { generics, bounds, ty, .. }) => {
Expand Down Expand Up @@ -644,7 +643,8 @@ pub fn walk_fn_decl<'a, V: Visitor<'a>>(visitor: &mut V, function_declaration: &

pub fn walk_fn<'a, V: Visitor<'a>>(visitor: &mut V, kind: FnKind<'a>, _span: Span) {
match kind {
FnKind::Fn(_, _, sig, _, body) => {
FnKind::Fn(_, _, sig, _, generics, body) => {
visitor.visit_generics(generics);
visitor.visit_fn_header(&sig.header);
walk_fn_decl(visitor, &sig.decl);
walk_list!(visitor, visit_block, body);
Expand All @@ -667,8 +667,7 @@ pub fn walk_assoc_item<'a, V: Visitor<'a>>(visitor: &mut V, item: &'a AssocItem,
walk_list!(visitor, visit_expr, expr);
}
AssocItemKind::Fn(box Fn { defaultness: _, ref generics, ref sig, ref body }) => {
visitor.visit_generics(generics);
let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, body.as_deref());
let kind = FnKind::Fn(FnCtxt::Assoc(ctxt), ident, sig, vis, generics, body.as_deref());
visitor.visit_fn(kind, span, id);
}
AssocItemKind::TyAlias(box TyAlias { generics, bounds, ty, .. }) => {
Expand Down
42 changes: 14 additions & 28 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ enum ParenthesizedGenericArgs {
/// an "elided" or "underscore" lifetime name. In the future, we probably want to move
/// everything into HIR lowering.
#[derive(Copy, Clone, Debug)]
enum AnonymousLifetimeMode {
pub enum AnonymousLifetimeMode {
/// For **Modern** cases, create a new anonymous region parameter
/// and reference that.
///
Expand Down Expand Up @@ -1835,7 +1835,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// Output lifetime like `'_`:
for (span, node_id) in lifetimes_to_define {
let param = this.fresh_lifetime_to_generic_param(span, node_id);
lifetime_params.push((span, hir::LifetimeName::Implicit(false)));
lifetime_params.push((span, hir::LifetimeName::Implicit));
generic_params.push(param);
}
let generic_params = this.arena.alloc_from_iter(generic_params);
Expand Down Expand Up @@ -2017,16 +2017,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
});
let param_name = match lt.name {
hir::LifetimeName::Param(param_name) => param_name,
hir::LifetimeName::Implicit(_)
| hir::LifetimeName::Underscore
| hir::LifetimeName::Static => hir::ParamName::Plain(lt.name.ident()),
hir::LifetimeName::Implicit | hir::LifetimeName::Underscore => {
hir::ParamName::Plain(lt.name.ident())
}
hir::LifetimeName::ImplicitObjectLifetimeDefault => {
self.sess.diagnostic().span_bug(
param.ident.span,
"object-lifetime-default should not occur here",
);
}
hir::LifetimeName::Error => ParamName::Error,
hir::LifetimeName::Static | hir::LifetimeName::Error => ParamName::Error,
};

let kind =
Expand Down Expand Up @@ -2397,27 +2397,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {

AnonymousLifetimeMode::ReportError => self.new_error_lifetime(None, span),

AnonymousLifetimeMode::PassThrough => self.new_implicit_lifetime(span, false),
AnonymousLifetimeMode::PassThrough => self.new_implicit_lifetime(span),
}
}

/// Report an error on illegal use of `'_` or a `&T` with no explicit lifetime;
/// return an "error lifetime".
fn new_error_lifetime(&mut self, id: Option<NodeId>, span: Span) -> hir::Lifetime {
let (id, msg, label) = match id {
Some(id) => (id, "`'_` cannot be used here", "`'_` is a reserved lifetime name"),

None => (
self.resolver.next_node_id(),
"`&` without an explicit lifetime name cannot be used here",
"explicit lifetime name needed here",
),
};

let mut err = struct_span_err!(self.sess, span, E0637, "{}", msg,);
err.span_label(span, label);
err.emit();

let id = id.unwrap_or_else(|| self.resolver.next_node_id());
self.new_named_lifetime(id, span, hir::LifetimeName::Error)
}

Expand All @@ -2429,12 +2416,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
&'s mut self,
span: Span,
count: usize,
param_mode: ParamMode,
) -> impl Iterator<Item = hir::Lifetime> + Captures<'a> + Captures<'s> + Captures<'hir> {
(0..count).map(move |_| self.elided_path_lifetime(span, param_mode))
(0..count).map(move |_| self.elided_path_lifetime(span))
}

fn elided_path_lifetime(&mut self, span: Span, param_mode: ParamMode) -> hir::Lifetime {
fn elided_path_lifetime(&mut self, span: Span) -> hir::Lifetime {
match self.anonymous_lifetime_mode {
AnonymousLifetimeMode::CreateParameter => {
// We should have emitted E0726 when processing this path above
Expand All @@ -2450,7 +2436,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
// lifetime. Instead, we simply create an implicit lifetime, which will be checked
// later, at which point a suitable error will be emitted.
AnonymousLifetimeMode::PassThrough | AnonymousLifetimeMode::ReportError => {
self.new_implicit_lifetime(span, param_mode == ParamMode::Explicit)
self.new_implicit_lifetime(span)
}
}
}
Expand Down Expand Up @@ -2493,11 +2479,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
r
}

fn new_implicit_lifetime(&mut self, span: Span, missing: bool) -> hir::Lifetime {
fn new_implicit_lifetime(&mut self, span: Span) -> hir::Lifetime {
hir::Lifetime {
hir_id: self.next_id(),
span: self.lower_span(span),
name: hir::LifetimeName::Implicit(missing),
name: hir::LifetimeName::Implicit,
}
}
}
Expand Down Expand Up @@ -2600,7 +2586,7 @@ fn lifetimes_from_impl_trait_bounds(

fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) {
let name = match lifetime.name {
hir::LifetimeName::Implicit(_) | hir::LifetimeName::Underscore => {
hir::LifetimeName::Implicit | hir::LifetimeName::Underscore => {
if self.collect_elided_lifetimes {
// Use `'_` for both implicit and underscore lifetimes in
// `type Foo<'_> = impl SomeTrait<'_>;`.
Expand Down
39 changes: 4 additions & 35 deletions compiler/rustc_ast_lowering/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,47 +290,16 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
generic_args.span.with_lo(generic_args.span.lo() + BytePos(1)).shrink_to_lo()
};
generic_args.args = self
.elided_path_lifetimes(elided_lifetime_span, expected_lifetimes, param_mode)
.elided_path_lifetimes(elided_lifetime_span, expected_lifetimes)
.map(GenericArg::Lifetime)
.chain(generic_args.args.into_iter())
.collect();
// 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>) { ... }
if let (ParamMode::Explicit, AnonymousLifetimeMode::CreateParameter) =
(param_mode, self.anonymous_lifetime_mode)
{
let anon_lt_suggestion = vec!["'_"; expected_lifetimes].join(", ");
let no_non_lt_args = generic_args.args.len() == expected_lifetimes;
let no_bindings = generic_args.bindings.is_empty();
let (incl_angl_brckt, suggestion) = if no_non_lt_args && no_bindings {
// If there are no generic args, our suggestion can include the angle brackets.
(true, format!("<{}>", anon_lt_suggestion))
} else {
// Otherwise we'll insert a `'_, ` right after the opening bracket.
(false, format!("{}, ", anon_lt_suggestion))
};
let insertion_sp = elided_lifetime_span.shrink_to_hi();
let mut err = struct_span_err!(
self.sess,
path_span,
E0726,
"implicit elided lifetime not allowed here"
);
rustc_errors::add_elided_lifetime_in_path_suggestion(
&self.sess.source_map(),
&mut err,
expected_lifetimes,
path_span,
incl_angl_brckt,
insertion_sp,
suggestion,
);
err.note("assuming a `'static` lifetime...");
err.emit();
// Late resolver should have issued the error.
self.sess
.delay_span_bug(elided_lifetime_span, "implicit lifetime not allowed here");
}
}

Expand Down
40 changes: 24 additions & 16 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,18 @@ impl<'a> AstValidator<'a> {
self.is_impl_trait_banned = old;
}

fn with_tilde_const_allowed(&mut self, f: impl FnOnce(&mut Self)) {
let old = mem::replace(&mut self.is_tilde_const_allowed, true);
fn with_tilde_const(&mut self, allowed: bool, f: impl FnOnce(&mut Self)) {
let old = mem::replace(&mut self.is_tilde_const_allowed, allowed);
f(self);
self.is_tilde_const_allowed = old;
}

fn with_tilde_const_allowed(&mut self, f: impl FnOnce(&mut Self)) {
self.with_tilde_const(true, f)
}

fn with_banned_tilde_const(&mut self, f: impl FnOnce(&mut Self)) {
let old = mem::replace(&mut self.is_tilde_const_allowed, false);
f(self);
self.is_tilde_const_allowed = old;
self.with_tilde_const(false, f)
}

fn with_let_management(
Expand Down Expand Up @@ -1202,12 +1204,8 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
}
self.visit_vis(&item.vis);
self.visit_ident(item.ident);
if let Const::Yes(_) = sig.header.constness {
self.with_tilde_const_allowed(|this| this.visit_generics(generics));
} else {
self.visit_generics(generics);
}
let kind = FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, body.as_deref());
let kind =
FnKind::Fn(FnCtxt::Free, item.ident, sig, &item.vis, generics, body.as_deref());
self.visit_fn(kind, item.span, item.id);
walk_list!(self, visit_attribute, &item.attrs);
return; // Avoid visiting again.
Expand Down Expand Up @@ -1555,13 +1553,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
FnSig { span: sig_span, header: FnHeader { ext: Extern::Implicit, .. }, .. },
_,
_,
_,
) = fk
{
self.maybe_lint_missing_abi(*sig_span, id);
}

// Functions without bodies cannot have patterns.
if let FnKind::Fn(ctxt, _, sig, _, None) = fk {
if let FnKind::Fn(ctxt, _, sig, _, _, None) = fk {
Self::check_decl_no_pat(&sig.decl, |span, ident, mut_ident| {
let (code, msg, label) = match ctxt {
FnCtxt::Foreign => (
Expand Down Expand Up @@ -1596,7 +1595,11 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
});
}

visit::walk_fn(self, fk, span);
let tilde_const_allowed =
matches!(fk.header(), Some(FnHeader { constness: Const::Yes(_), .. }))
|| matches!(fk.ctxt(), Some(FnCtxt::Assoc(_)));

self.with_tilde_const(tilde_const_allowed, |this| visit::walk_fn(this, fk, span));
}

fn visit_assoc_item(&mut self, item: &'a AssocItem, ctxt: AssocCtxt) {
Expand Down Expand Up @@ -1670,9 +1673,14 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
{
self.visit_vis(&item.vis);
self.visit_ident(item.ident);
self.with_tilde_const_allowed(|this| this.visit_generics(generics));
let kind =
FnKind::Fn(FnCtxt::Assoc(ctxt), item.ident, sig, &item.vis, body.as_deref());
let kind = FnKind::Fn(
FnCtxt::Assoc(ctxt),
item.ident,
sig,
&item.vis,
generics,
body.as_deref(),
);
self.visit_fn(kind, item.span, item.id);
}
_ => self
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/diagnostics/region_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
Some(RegionNameHighlight::MatchedAdtAndSegment(lifetime_span))
}

hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Implicit(_) => {
hir::LifetimeName::ImplicitObjectLifetimeDefault | hir::LifetimeName::Implicit => {
// In this case, the user left off the lifetime; so
// they wrote something like:
//
Expand Down
38 changes: 10 additions & 28 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1511,35 +1511,17 @@ pub fn add_elided_lifetime_in_path_suggestion(
path_span: Span,
incl_angl_brckt: bool,
insertion_span: Span,
anon_lts: String,
) {
let (replace_span, suggestion) = if incl_angl_brckt {
(insertion_span, anon_lts)
} else {
// When possible, prefer a suggestion that replaces the whole
// `Path<T>` expression with `Path<'_, T>`, rather than inserting `'_, `
// at a point (which makes for an ugly/confusing label)
if let Ok(snippet) = source_map.span_to_snippet(path_span) {
// But our spans can get out of whack due to macros; if the place we think
// we want to insert `'_` isn't even within the path expression's span, we
// should bail out of making any suggestion rather than panicking on a
// subtract-with-overflow or string-slice-out-out-bounds (!)
// FIXME: can we do better?
if insertion_span.lo().0 < path_span.lo().0 {
return;
}
let insertion_index = (insertion_span.lo().0 - path_span.lo().0) as usize;
if insertion_index > snippet.len() {
return;
}
let (before, after) = snippet.split_at(insertion_index);
(path_span, format!("{}{}{}", before, anon_lts, after))
} else {
(insertion_span, anon_lts)
}
};
diag.span_suggestion(
replace_span,
diag.span_label(path_span, format!("expected lifetime parameter{}", pluralize!(n)));
if source_map.span_to_snippet(insertion_span).is_err() {
// Do not try to suggest anything if generated by a proc-macro.
return;
}
let anon_lts = vec!["'_"; n].join(", ");
let suggestion =
if incl_angl_brckt { format!("<{}>", anon_lts) } else { format!("{}, ", anon_lts) };
diag.span_suggestion_verbose(
insertion_span.shrink_to_hi(),
&format!("indicate the anonymous lifetime{}", pluralize!(n)),
suggestion,
Applicability::MachineApplicable,
Expand Down
Loading

0 comments on commit 1ec2c13

Please sign in to comment.