Use smaller spans for some structured suggestions

Use more accurate suggestion spans for

* argument parse error
* fully qualified path
* missing code block type
* numeric casts
* E0212
This commit is contained in:
Esteban Kuber
2021-08-10 10:53:43 +00:00
parent eb2226b1f1
commit 34d19634f5
60 changed files with 1200 additions and 958 deletions

View File

@@ -298,6 +298,21 @@ impl Diagnostic {
)
}
/// Show a suggestion that has multiple parts to it, always as it's own subdiagnostic.
/// In other words, multiple changes need to be applied as part of this suggestion.
pub fn multipart_suggestion_verbose(
&mut self,
msg: &str,
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
self.multipart_suggestion_with_style(
msg,
suggestion,
applicability,
SuggestionStyle::ShowAlways,
)
}
/// [`Diagnostic::multipart_suggestion()`] but you can set the [`SuggestionStyle`].
pub fn multipart_suggestion_with_style(
&mut self,

View File

@@ -257,6 +257,20 @@ impl<'a> DiagnosticBuilder<'a> {
self
}
/// See [`Diagnostic::multipart_suggestion()`].
pub fn multipart_suggestion_verbose(
&mut self,
msg: &str,
suggestion: Vec<(Span, String)>,
applicability: Applicability,
) -> &mut Self {
if !self.0.allow_suggestions {
return self;
}
self.0.diagnostic.multipart_suggestion_verbose(msg, suggestion, applicability);
self
}
/// See [`Diagnostic::tool_only_multipart_suggestion()`].
pub fn tool_only_multipart_suggestion(
&mut self,

View File

@@ -1618,50 +1618,57 @@ impl<'a> Parser<'a> {
{
let rfc_note = "anonymous parameters are removed in the 2018 edition (see RFC 1685)";
let (ident, self_sugg, param_sugg, type_sugg) = match pat.kind {
PatKind::Ident(_, ident, _) => (
ident,
format!("self: {}", ident),
format!("{}: TypeName", ident),
format!("_: {}", ident),
),
// Also catches `fn foo(&a)`.
PatKind::Ref(ref pat, mutab)
if matches!(pat.clone().into_inner().kind, PatKind::Ident(..)) =>
{
match pat.clone().into_inner().kind {
PatKind::Ident(_, ident, _) => {
let mutab = mutab.prefix_str();
(
ident,
format!("self: &{}{}", mutab, ident),
format!("{}: &{}TypeName", ident, mutab),
format!("_: &{}{}", mutab, ident),
)
let (ident, self_sugg, param_sugg, type_sugg, self_span, param_span, type_span) =
match pat.kind {
PatKind::Ident(_, ident, _) => (
ident,
"self: ".to_string(),
": TypeName".to_string(),
"_: ".to_string(),
pat.span.shrink_to_lo(),
pat.span.shrink_to_hi(),
pat.span.shrink_to_lo(),
),
// Also catches `fn foo(&a)`.
PatKind::Ref(ref inner_pat, mutab)
if matches!(inner_pat.clone().into_inner().kind, PatKind::Ident(..)) =>
{
match inner_pat.clone().into_inner().kind {
PatKind::Ident(_, ident, _) => {
let mutab = mutab.prefix_str();
(
ident,
"self: ".to_string(),
format!("{}: &{}TypeName", ident, mutab),
"_: ".to_string(),
pat.span.shrink_to_lo(),
pat.span,
pat.span.shrink_to_lo(),
)
}
_ => unreachable!(),
}
_ => unreachable!(),
}
}
_ => {
// Otherwise, try to get a type and emit a suggestion.
if let Some(ty) = pat.to_ty() {
err.span_suggestion_verbose(
pat.span,
"explicitly ignore the parameter name",
format!("_: {}", pprust::ty_to_string(&ty)),
Applicability::MachineApplicable,
);
err.note(rfc_note);
}
_ => {
// Otherwise, try to get a type and emit a suggestion.
if let Some(ty) = pat.to_ty() {
err.span_suggestion_verbose(
pat.span,
"explicitly ignore the parameter name",
format!("_: {}", pprust::ty_to_string(&ty)),
Applicability::MachineApplicable,
);
err.note(rfc_note);
}
return None;
}
};
return None;
}
};
// `fn foo(a, b) {}`, `fn foo(a<x>, b<y>) {}` or `fn foo(usize, usize) {}`
if first_param {
err.span_suggestion(
pat.span,
self_span,
"if this is a `self` type, give it a parameter name",
self_sugg,
Applicability::MaybeIncorrect,
@@ -1671,14 +1678,14 @@ impl<'a> Parser<'a> {
// `fn foo(HashMap: TypeName<u32>)`.
if self.token != token::Lt {
err.span_suggestion(
pat.span,
param_span,
"if this is a parameter name, give it a type",
param_sugg,
Applicability::HasPlaceholders,
);
}
err.span_suggestion(
pat.span,
type_span,
"if this is a type, explicitly ignore the parameter name",
type_sugg,
Applicability::MachineApplicable,

View File

@@ -12,7 +12,7 @@ use rustc_ast::{
};
use rustc_ast_pretty::pprust::path_segment_to_string;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder, SuggestionStyle};
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
use rustc_hir as hir;
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
@@ -1950,11 +1950,10 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
introduce_suggestion.push((*span, formatter(&lt_name)));
}
}
err.multipart_suggestion_with_style(
err.multipart_suggestion_verbose(
&msg,
introduce_suggestion,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
}
@@ -1966,14 +1965,13 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
})
.map(|(formatter, span)| (*span, formatter(name)))
.collect();
err.multipart_suggestion_with_style(
err.multipart_suggestion_verbose(
&format!(
"consider using the `{}` lifetime",
lifetime_names.iter().next().unwrap()
),
spans_suggs,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
};
let suggest_new = |err: &mut DiagnosticBuilder<'_>, suggs: Vec<Option<String>>| {
@@ -2064,11 +2062,10 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
};
spans_suggs.push((span, sugg.to_string()));
}
err.multipart_suggestion_with_style(
err.multipart_suggestion_verbose(
"consider using the `'static` lifetime",
spans_suggs,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
continue;
}
@@ -2088,11 +2085,10 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
introduce_suggestion.push((span, sugg.to_string()));
}
}
err.multipart_suggestion_with_style(
err.multipart_suggestion_verbose(
&msg,
introduce_suggestion,
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
if should_break {
break;
@@ -2167,11 +2163,10 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
if spans_suggs.len() > 0 {
// This happens when we have `Foo<T>` where we point at the space before `T`,
// but this can be confusing so we give a suggestion with placeholders.
err.multipart_suggestion_with_style(
err.multipart_suggestion_verbose(
"consider using one of the available lifetimes here",
spans_suggs,
Applicability::HasPlaceholders,
SuggestionStyle::ShowAlways,
);
}
}

View File

@@ -1648,14 +1648,13 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
constraint=constraint,
));
} else {
err.span_suggestion(
span,
err.span_suggestion_verbose(
span.with_hi(assoc_name.span.lo()),
"use fully qualified syntax to disambiguate",
format!(
"<{} as {}>::{}",
"<{} as {}>::",
ty_param_name(),
bound.print_only_trait_path(),
assoc_name,
),
Applicability::MaybeIncorrect,
);

View File

@@ -17,7 +17,6 @@ use rustc_span::{BytePos, Span};
use super::method::probe;
use std::fmt;
use std::iter;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -771,9 +770,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// For now, don't suggest casting with `as`.
let can_cast = false;
let prefix = if let Some(hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Struct(_, fields, _),
..
let mut sugg = vec![];
if let Some(hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Struct(_, fields, _), ..
})) = self.tcx.hir().find(self.tcx.hir().get_parent_node(expr.hir_id))
{
// `expr` is a literal field for a struct, only suggest if appropriate
@@ -782,12 +782,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.find(|field| field.expr.hir_id == expr.hir_id && field.is_shorthand)
{
// This is a field literal
Some(field) => format!("{}: ", field.ident),
Some(field) => {
sugg.push((field.ident.span.shrink_to_lo(), format!("{}: ", field.ident)));
}
// Likely a field was meant, but this field wasn't found. Do not suggest anything.
None => return false,
}
} else {
String::new()
};
if let hir::ExprKind::Call(path, args) = &expr.kind {
@@ -842,28 +842,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
checked_ty, expected_ty,
);
let with_opt_paren: fn(&dyn fmt::Display) -> String =
if expr.precedence().order() < PREC_POSTFIX {
|s| format!("({})", s)
} else {
|s| s.to_string()
};
let close_paren = if expr.precedence().order() < PREC_POSTFIX {
sugg.push((expr.span.shrink_to_lo(), "(".to_string()));
")"
} else {
""
};
let cast_suggestion = format!("{}{} as {}", prefix, with_opt_paren(&src), expected_ty);
let into_suggestion = format!("{}{}.into()", prefix, with_opt_paren(&src));
let suffix_suggestion = with_opt_paren(&format_args!(
"{}{}",
let mut cast_suggestion = sugg.clone();
cast_suggestion
.push((expr.span.shrink_to_hi(), format!("{} as {}", close_paren, expected_ty)));
let mut into_suggestion = sugg.clone();
into_suggestion.push((expr.span.shrink_to_hi(), format!("{}.into()", close_paren)));
let mut suffix_suggestion = sugg.clone();
suffix_suggestion.push((
if matches!(
(&expected_ty.kind(), &checked_ty.kind()),
(ty::Int(_) | ty::Uint(_), ty::Float(_))
) {
// Remove fractional part from literal, for example `42.0f32` into `42`
let src = src.trim_end_matches(&checked_ty.to_string());
src.split('.').next().unwrap()
let len = src.split('.').next().unwrap().len();
expr.span.with_lo(expr.span.lo() + BytePos(len as u32))
} else {
src.trim_end_matches(&checked_ty.to_string())
let len = src.trim_end_matches(&checked_ty.to_string()).len();
expr.span.with_lo(expr.span.lo() + BytePos(len as u32))
},
if expr.precedence().order() < PREC_POSTFIX {
// Readd `)`
format!("{})", expected_ty)
} else {
expected_ty.to_string()
},
expected_ty,
));
let literal_is_ty_suffixed = |expr: &hir::Expr<'_>| {
if let hir::ExprKind::Lit(lit) = &expr.kind { lit.node.is_suffixed() } else { false }
@@ -890,22 +900,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.ok()
.map(|src| (expr, src))
});
let (span, msg, suggestion) = if let (Some((lhs_expr, lhs_src)), false) =
let (msg, suggestion) = if let (Some((lhs_expr, lhs_src)), false) =
(lhs_expr_and_src, exp_to_found_is_fallible)
{
let msg = format!(
"you can convert `{}` from `{}` to `{}`, matching the type of `{}`",
lhs_src, expected_ty, checked_ty, src
);
let suggestion = format!("{}::from({})", checked_ty, lhs_src);
(lhs_expr.span, msg, suggestion)
let suggestion = vec![
(lhs_expr.span.shrink_to_lo(), format!("{}::from(", checked_ty)),
(lhs_expr.span.shrink_to_hi(), ")".to_string()),
];
(msg, suggestion)
} else {
let msg = format!("{} and panic if the converted value doesn't fit", msg);
let suggestion =
format!("{}{}.try_into().unwrap()", prefix, with_opt_paren(&src));
(expr.span, msg, suggestion)
let mut suggestion = sugg.clone();
suggestion.push((
expr.span.shrink_to_hi(),
format!("{}.try_into().unwrap()", close_paren),
));
(msg, suggestion)
};
err.span_suggestion(span, &msg, suggestion, Applicability::MachineApplicable);
err.multipart_suggestion_verbose(
&msg,
suggestion,
Applicability::MachineApplicable,
);
};
let suggest_to_change_suffix_or_into =
@@ -943,7 +963,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else {
into_suggestion.clone()
};
err.span_suggestion(expr.span, msg, suggestion, Applicability::MachineApplicable);
err.multipart_suggestion_verbose(msg, suggestion, Applicability::MachineApplicable);
};
match (&expected_ty.kind(), &checked_ty.kind()) {
@@ -997,16 +1017,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if found.bit_width() < exp.bit_width() {
suggest_to_change_suffix_or_into(err, false, true);
} else if literal_is_ty_suffixed(expr) {
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&lit_msg,
suffix_suggestion,
Applicability::MachineApplicable,
);
} else if can_cast {
// Missing try_into implementation for `f64` to `f32`
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&format!("{}, producing the closest possible value", cast_msg),
cast_suggestion,
Applicability::MaybeIncorrect, // lossy conversion
@@ -1016,16 +1034,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
(&ty::Uint(_) | &ty::Int(_), &ty::Float(_)) => {
if literal_is_ty_suffixed(expr) {
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&lit_msg,
suffix_suggestion,
Applicability::MachineApplicable,
);
} else if can_cast {
// Missing try_into implementation for `{float}` to `{integer}`
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&format!("{}, rounding the float towards zero", msg),
cast_suggestion,
Applicability::MaybeIncorrect, // lossy conversion
@@ -1036,8 +1052,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
(&ty::Float(ref exp), &ty::Uint(ref found)) => {
// if `found` is `None` (meaning found is `usize`), don't suggest `.into()`
if exp.bit_width() > found.bit_width().unwrap_or(256) {
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&format!(
"{}, producing the floating point representation of the integer",
msg,
@@ -1046,16 +1061,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
} else if literal_is_ty_suffixed(expr) {
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&lit_msg,
suffix_suggestion,
Applicability::MachineApplicable,
);
} else {
// Missing try_into implementation for `{integer}` to `{float}`
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&format!(
"{}, producing the floating point representation of the integer,
rounded if necessary",
@@ -1070,8 +1083,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
(&ty::Float(ref exp), &ty::Int(ref found)) => {
// if `found` is `None` (meaning found is `isize`), don't suggest `.into()`
if exp.bit_width() > found.bit_width().unwrap_or(256) {
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&format!(
"{}, producing the floating point representation of the integer",
&msg,
@@ -1080,16 +1092,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Applicability::MachineApplicable,
);
} else if literal_is_ty_suffixed(expr) {
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&lit_msg,
suffix_suggestion,
Applicability::MachineApplicable,
);
} else {
// Missing try_into implementation for `{integer}` to `{float}`
err.span_suggestion(
expr.span,
err.multipart_suggestion_verbose(
&format!(
"{}, producing the floating point representation of the integer, \
rounded if necessary",

View File

@@ -452,9 +452,9 @@ impl AstConv<'tcx> for ItemCtxt<'tcx> {
let suggestions = vec![
(lt_sp, sugg),
(
span,
span.with_hi(item_segment.ident.span.lo()),
format!(
"{}::{}",
"{}::",
// Replace the existing lifetimes with a new named lifetime.
self.tcx
.replace_late_bound_regions(poly_trait_ref, |_| {
@@ -467,7 +467,6 @@ impl AstConv<'tcx> for ItemCtxt<'tcx> {
))
})
.0,
item_segment.ident
),
),
];
@@ -489,14 +488,13 @@ impl AstConv<'tcx> for ItemCtxt<'tcx> {
| hir::Node::ForeignItem(_)
| hir::Node::TraitItem(_)
| hir::Node::ImplItem(_) => {
err.span_suggestion(
span,
err.span_suggestion_verbose(
span.with_hi(item_segment.ident.span.lo()),
"use a fully qualified path with inferred lifetimes",
format!(
"{}::{}",
"{}::",
// Erase named lt, we want `<A as B<'_>::C`, not `<A as B<'a>::C`.
self.tcx.anonymize_late_bound_regions(poly_trait_ref).skip_binder(),
item_segment.ident
),
Applicability::MaybeIncorrect,
);