Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account for HR lifetimes when suggesting introduction of named lifetime #68583

Merged
merged 12 commits into from
Feb 6, 2020
4 changes: 2 additions & 2 deletions src/librustc_hir/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2260,10 +2260,10 @@ impl TraitRef<'_> {

#[derive(RustcEncodable, RustcDecodable, Debug, HashStable_Generic)]
pub struct PolyTraitRef<'hir> {
/// The `'a` in `<'a> Foo<&'a T>`.
/// The `'a` in `for<'a> Foo<&'a T>`.
pub bound_generic_params: &'hir [GenericParam<'hir>],

/// The `Foo<&'a T>` in `<'a> Foo<&'a T>`.
/// The `Foo<&'a T>` in `for<'a> Foo<&'a T>`.
pub trait_ref: TraitRef<'hir>,

pub span: Span,
Expand Down
293 changes: 231 additions & 62 deletions src/librustc_resolve/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use syntax::ast::{self, Ident, Path};
use syntax::util::lev_distance::find_best_match_for_name;

use crate::imports::{ImportDirective, ImportDirectiveSubclass, ImportResolver};
use crate::lifetimes::{ElisionFailureInfo, LifetimeContext};
use crate::path_names_to_string;
use crate::{AmbiguityError, AmbiguityErrorMisc, AmbiguityKind};
use crate::{BindingError, CrateLint, HasGenericParams, LegacyScope, Module, ModuleOrUniformRoot};
Expand Down Expand Up @@ -48,6 +49,40 @@ crate struct ImportSuggestion {
pub path: Path,
}

crate enum MissingLifetimeSpot<'tcx> {
Generics(&'tcx hir::Generics<'tcx>),
HigherRanked { span: Span, span_type: ForLifetimeSpanType },
}

crate enum ForLifetimeSpanType {
BoundEmpty,
BoundTail,
TypeEmpty,
TypeTail,
}

impl ForLifetimeSpanType {
crate fn descr(&self) -> &'static str {
match self {
Self::BoundEmpty | Self::BoundTail => "bound",
Self::TypeEmpty | Self::TypeTail => "type",
}
}

crate fn suggestion(&self, sugg: &str) -> String {
match self {
Self::BoundEmpty | Self::TypeEmpty => format!("for<{}> ", sugg),
Self::BoundTail | Self::TypeTail => format!(", {}", sugg),
}
}
}

impl<'tcx> Into<MissingLifetimeSpot<'tcx>> for &'tcx hir::Generics<'tcx> {
fn into(self) -> MissingLifetimeSpot<'tcx> {
MissingLifetimeSpot::Generics(self)
}
}

/// Adjust the impl span so that just the `impl` keyword is taken by removing
/// everything after `<` (`"impl<T> Iterator for A<T> {}" -> "impl"`) and
/// everything after the first whitespace (`"impl Iterator for A" -> "impl"`).
Expand Down Expand Up @@ -1457,72 +1492,206 @@ crate fn show_candidates(
}
}

crate fn report_missing_lifetime_specifiers(
sess: &Session,
span: Span,
count: usize,
) -> DiagnosticBuilder<'_> {
struct_span_err!(sess, span, E0106, "missing lifetime specifier{}", pluralize!(count))
}
impl<'tcx> LifetimeContext<'_, 'tcx> {
crate fn report_missing_lifetime_specifiers(
&self,
span: Span,
count: usize,
) -> DiagnosticBuilder<'tcx> {
struct_span_err!(
self.tcx.sess,
span,
E0106,
"missing lifetime specifier{}",
pluralize!(count)
)
}

crate fn add_missing_lifetime_specifiers_label(
err: &mut DiagnosticBuilder<'_>,
span: Span,
count: usize,
lifetime_names: &FxHashSet<ast::Ident>,
snippet: Option<&str>,
missing_named_lifetime_spots: &[&hir::Generics<'_>],
) {
if count > 1 {
err.span_label(span, format!("expected {} lifetime parameters", count));
} else {
let suggest_existing = |err: &mut DiagnosticBuilder<'_>, sugg| {
err.span_suggestion(
span,
"consider using the named lifetime",
sugg,
Applicability::MaybeIncorrect,
);
};
let suggest_new = |err: &mut DiagnosticBuilder<'_>, sugg| {
err.span_label(span, "expected named lifetime parameter");

if let Some(generics) = missing_named_lifetime_spots.iter().last() {
let mut introduce_suggestion = vec![];
introduce_suggestion.push(match &generics.params {
[] => (generics.span, "<'lifetime>".to_string()),
[param, ..] => (param.span.shrink_to_lo(), "'lifetime, ".to_string()),
});
introduce_suggestion.push((span, sugg));
err.multipart_suggestion(
"consider introducing a named lifetime parameter",
introduce_suggestion,
Applicability::MaybeIncorrect,
);
crate fn emit_undeclared_lifetime_error(&self, lifetime_ref: &hir::Lifetime) {
let mut err = struct_span_err!(
self.tcx.sess,
lifetime_ref.span,
E0261,
"use of undeclared lifetime name `{}`",
lifetime_ref
);
err.span_label(lifetime_ref.span, "undeclared lifetime");
for missing in &self.missing_named_lifetime_spots {
match missing {
MissingLifetimeSpot::Generics(generics) => {
let (span, sugg) = if let Some(param) = generics
.params
.iter()
.filter(|p| match p.kind {
hir::GenericParamKind::Type {
synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
..
} => false,
_ => true,
})
.next()
{
(param.span.shrink_to_lo(), format!("{}, ", lifetime_ref))
} else {
(generics.span, format!("<{}>", lifetime_ref))
};
err.span_suggestion(
span,
&format!("consider introducing lifetime `{}` here", lifetime_ref),
sugg,
Applicability::MaybeIncorrect,
);
}
MissingLifetimeSpot::HigherRanked { span, span_type } => {
err.span_suggestion(
*span,
&format!(
"consider making the {} lifetime-generic with a new `{}` lifetime",
span_type.descr(),
lifetime_ref
),
span_type.suggestion(&lifetime_ref.to_string()),
Applicability::MaybeIncorrect,
);
err.note(
"for more information on higher-ranked polymorphism, visit \
https://doc.rust-lang.org/nomicon/hrtb.html",
);
}
}
};
}
err.emit();
}

match (lifetime_names.len(), lifetime_names.iter().next(), snippet) {
(1, Some(name), Some("&")) => {
suggest_existing(err, format!("&{} ", name));
}
(1, Some(name), Some("'_")) => {
suggest_existing(err, name.to_string());
}
(1, Some(name), Some(snippet)) if !snippet.ends_with(">") => {
suggest_existing(err, format!("{}<{}>", snippet, name));
}
(0, _, Some("&")) => {
suggest_new(err, "&'lifetime ".to_string());
}
(0, _, Some("'_")) => {
suggest_new(err, "'lifetime".to_string());
}
(0, _, Some(snippet)) if !snippet.ends_with(">") => {
suggest_new(err, format!("{}<'lifetime>", snippet));
crate fn is_trait_ref_fn_scope(&mut self, trait_ref: &'tcx hir::PolyTraitRef<'tcx>) -> bool {
if let def::Res::Def(_, did) = trait_ref.trait_ref.path.res {
if [
self.tcx.lang_items().fn_once_trait(),
self.tcx.lang_items().fn_trait(),
self.tcx.lang_items().fn_mut_trait(),
]
.contains(&Some(did))
{
let (span, span_type) = match &trait_ref.bound_generic_params {
[] => (trait_ref.span.shrink_to_lo(), ForLifetimeSpanType::BoundEmpty),
[.., bound] => (bound.span.shrink_to_hi(), ForLifetimeSpanType::BoundTail),
};
self.missing_named_lifetime_spots
.push(MissingLifetimeSpot::HigherRanked { span, span_type });
return true;
}
_ => {
err.span_label(span, "expected lifetime parameter");
};
false
}

crate fn add_missing_lifetime_specifiers_label(
&self,
err: &mut DiagnosticBuilder<'_>,
span: Span,
count: usize,
lifetime_names: &FxHashSet<ast::Ident>,
params: &[ElisionFailureInfo],
) {
if count > 1 {
err.span_label(span, format!("expected {} lifetime parameters", count));
} else {
let snippet = self.tcx.sess.source_map().span_to_snippet(span).ok();
let suggest_existing = |err: &mut DiagnosticBuilder<'_>, sugg| {
err.span_suggestion(
span,
"consider using the named lifetime",
sugg,
Applicability::MaybeIncorrect,
);
};
let suggest_new =
|err: &mut DiagnosticBuilder<'_>, sugg: &str| {
err.span_label(span, "expected named lifetime parameter");

for missing in self.missing_named_lifetime_spots.iter().rev() {
let mut introduce_suggestion = vec![];
let msg;
let should_break;
introduce_suggestion.push(match missing {
MissingLifetimeSpot::Generics(generics) => {
msg = "consider introducing a named lifetime parameter".to_string();
should_break = true;
if let Some(param) = generics.params.iter().filter(|p| match p.kind {
hir::GenericParamKind::Type {
synthetic: Some(hir::SyntheticTyParamKind::ImplTrait),
..
} => false,
_ => true,
}).next() {
(param.span.shrink_to_lo(), "'a, ".to_string())
} else {
(generics.span, "<'a>".to_string())
}
}
MissingLifetimeSpot::HigherRanked { span, span_type } => {
msg = format!(
"consider making the {} lifetime-generic with a new `'a` lifetime",
span_type.descr(),
);
should_break = false;
err.note(
"for more information on higher-ranked polymorphism, visit \
https://doc.rust-lang.org/nomicon/hrtb.html",
);
(*span, span_type.suggestion("'a"))
}
});
for param in params {
if let Ok(snippet) =
self.tcx.sess.source_map().span_to_snippet(param.span)
{
if snippet.starts_with("&") && !snippet.starts_with("&'") {
introduce_suggestion
.push((param.span, format!("&'a {}", &snippet[1..])));
} else if snippet.starts_with("&'_ ") {
introduce_suggestion
.push((param.span, format!("&'a {}", &snippet[4..])));
}
}
}
introduce_suggestion.push((span, sugg.to_string()));
err.multipart_suggestion(
&msg,
introduce_suggestion,
Applicability::MaybeIncorrect,
);
if should_break {
break;
}
}
};

match (
lifetime_names.len(),
lifetime_names.iter().next(),
snippet.as_ref().map(|s| s.as_str()),
) {
(1, Some(name), Some("&")) => {
suggest_existing(err, format!("&{} ", name));
}
(1, Some(name), Some("'_")) => {
suggest_existing(err, name.to_string());
}
(1, Some(name), Some(snippet)) if !snippet.ends_with(">") => {
suggest_existing(err, format!("{}<{}>", snippet, name));
}
(0, _, Some("&")) => {
suggest_new(err, "&'a ");
}
(0, _, Some("'_")) => {
suggest_new(err, "'a");
}
(0, _, Some(snippet)) if !snippet.ends_with(">") => {
suggest_new(err, &format!("{}<'a>", snippet));
}
_ => {
err.span_label(span, "expected lifetime parameter");
}
}
}
}
Expand Down
Loading