Skip to content

Commit

Permalink
Rollup merge of rust-lang#68583 - estebank:hrlt, r=oli-obk
Browse files Browse the repository at this point in the history
Account for HR lifetimes when suggesting introduction of named lifetime

```
error[E0106]: missing lifetime specifier
 --> src/test/ui/suggestions/fn-missing-lifetime-in-item.rs:2:32
  |
2 | struct S2<F: Fn(&i32, &i32) -> &i32>(F);
  |                 ----  ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from argument 1 or argument 2
  = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
  |
2 | struct S2<F: for<'a> Fn(&'a i32, &'a i32) -> &'a i32>(F);
  |              ^^^^^^^    ^^^^^^^  ^^^^^^^     ^^^
help: consider introducing a named lifetime parameter
  |
2 | struct S2<'a, F: Fn(&'a i32, &'a i32) -> &'a i32>(F);=
  |           ^^^       ^^^^^^^  ^^^^^^^     ^^^
```

Follow up to rust-lang#68267. Addresses the diagnostics part of rust-lang#49287.
  • Loading branch information
Centril committed Feb 5, 2020
2 parents 4ff8fb9 + 4310b74 commit bdeefc6
Show file tree
Hide file tree
Showing 33 changed files with 558 additions and 190 deletions.
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

0 comments on commit bdeefc6

Please sign in to comment.