Add a detailed note for missing comma in FRU syntax typo
This commit is contained in:
parent
fd3bfb3551
commit
bb0cb9ae9f
10 changed files with 155 additions and 31 deletions
|
|
@ -0,0 +1,8 @@
|
|||
hir_typeck_fru_note = this expression may have been misinterpreted as a `..` range expression
|
||||
hir_typeck_fru_expr = this expression does not end in a comma...
|
||||
hir_typeck_fru_expr2 = ... so this is interpreted as a `..` range expression, instead of functional record update syntax
|
||||
hir_typeck_fru_suggestion =
|
||||
to set the remaining fields{$expr ->
|
||||
[NONE]{""}
|
||||
*[other] {" "}from `{$expr}`
|
||||
}, separate the last named field with a comma
|
||||
|
|
@ -51,6 +51,7 @@ fluent_messages! {
|
|||
errors => "../locales/en-US/errors.ftl",
|
||||
expand => "../locales/en-US/expand.ftl",
|
||||
hir_analysis => "../locales/en-US/hir_analysis.ftl",
|
||||
hir_typeck => "../locales/en-US/hir_typeck.ftl",
|
||||
infer => "../locales/en-US/infer.ftl",
|
||||
interface => "../locales/en-US/interface.ftl",
|
||||
lint => "../locales/en-US/lint.ftl",
|
||||
|
|
|
|||
|
|
@ -466,6 +466,9 @@ pub enum StashKey {
|
|||
/// When an invalid lifetime e.g. `'2` should be reinterpreted
|
||||
/// as a char literal in the parser
|
||||
LifetimeIsChar,
|
||||
/// Maybe there was a typo where a comma was forgotten before
|
||||
/// FRU syntax
|
||||
MaybeFruTypo,
|
||||
}
|
||||
|
||||
fn default_track_diagnostic(_: &Diagnostic) {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Errors emitted by `rustc_hir_analysis`.
|
||||
use rustc_errors::{AddToDiagnostic, Applicability, Diagnostic, MultiSpan, SubdiagnosticMessage};
|
||||
use rustc_macros::{Diagnostic, Subdiagnostic};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{symbol::Ident, Span};
|
||||
|
|
@ -133,3 +134,41 @@ pub struct OpMethodGenericParams {
|
|||
pub span: Span,
|
||||
pub method_name: String,
|
||||
}
|
||||
|
||||
pub struct TypeMismatchFruTypo {
|
||||
/// Span of the LHS of the range
|
||||
pub expr_span: Span,
|
||||
/// Span of the `..RHS` part of the range
|
||||
pub fru_span: Span,
|
||||
/// Rendered expression of the RHS of the range
|
||||
pub expr: Option<String>,
|
||||
}
|
||||
|
||||
impl AddToDiagnostic for TypeMismatchFruTypo {
|
||||
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
|
||||
where
|
||||
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
|
||||
{
|
||||
diag.set_arg("expr", self.expr.as_deref().unwrap_or("NONE"));
|
||||
|
||||
// Only explain that `a ..b` is a range if it's split up
|
||||
if self.expr_span.between(self.fru_span).is_empty() {
|
||||
diag.span_note(
|
||||
self.expr_span.to(self.fru_span),
|
||||
rustc_errors::fluent::hir_typeck_fru_note,
|
||||
);
|
||||
} else {
|
||||
let mut multispan: MultiSpan = vec![self.expr_span, self.fru_span].into();
|
||||
multispan.push_span_label(self.expr_span, rustc_errors::fluent::hir_typeck_fru_expr);
|
||||
multispan.push_span_label(self.fru_span, rustc_errors::fluent::hir_typeck_fru_expr2);
|
||||
diag.span_note(multispan, rustc_errors::fluent::hir_typeck_fru_note);
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
self.expr_span.shrink_to_hi(),
|
||||
rustc_errors::fluent::hir_typeck_fru_suggestion,
|
||||
", ",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
use crate::cast;
|
||||
use crate::coercion::CoerceMany;
|
||||
use crate::coercion::DynamicCoerceMany;
|
||||
use crate::errors::TypeMismatchFruTypo;
|
||||
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
|
||||
use crate::errors::{
|
||||
FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct,
|
||||
|
|
@ -1614,10 +1615,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
self.demand_coerce_diag(&field.expr, ty, field_type, None, AllowTwoPhase::No);
|
||||
|
||||
if let Some(mut diag) = diag {
|
||||
if idx == ast_fields.len() - 1 && remaining_fields.is_empty() {
|
||||
self.suggest_fru_from_range(field, variant, substs, &mut diag);
|
||||
if idx == ast_fields.len() - 1 {
|
||||
if remaining_fields.is_empty() {
|
||||
self.suggest_fru_from_range(field, variant, substs, &mut diag);
|
||||
diag.emit();
|
||||
} else {
|
||||
diag.stash(field.span, StashKey::MaybeFruTypo);
|
||||
}
|
||||
} else {
|
||||
diag.emit();
|
||||
}
|
||||
diag.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1875,19 +1882,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
.map(|adt| adt.did())
|
||||
!= range_def_id
|
||||
{
|
||||
let instead = self
|
||||
// Suppress any range expr type mismatches
|
||||
if let Some(mut diag) = self
|
||||
.tcx
|
||||
.sess
|
||||
.diagnostic()
|
||||
.steal_diagnostic(last_expr_field.span, StashKey::MaybeFruTypo)
|
||||
{
|
||||
diag.delay_as_bug();
|
||||
}
|
||||
|
||||
// Use a (somewhat arbitrary) filtering heuristic to avoid printing
|
||||
// expressions that are either too long, or have control character
|
||||
//such as newlines in them.
|
||||
let expr = self
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.span_to_snippet(range_end.expr.span)
|
||||
.map(|s| format!(" from `{s}`"))
|
||||
.unwrap_or_default();
|
||||
err.span_suggestion(
|
||||
range_start.span.shrink_to_hi(),
|
||||
&format!("to set the remaining fields{instead}, separate the last named field with a comma"),
|
||||
",",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
.ok()
|
||||
.filter(|s| s.len() < 25 && !s.contains(|c: char| c.is_control()));
|
||||
|
||||
let fru_span = self
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.span_extend_while(range_start.span, |c| c.is_whitespace())
|
||||
.unwrap_or(range_start.span).shrink_to_hi().to(range_end.span);
|
||||
|
||||
err.subdiagnostic(TypeMismatchFruTypo {
|
||||
expr_span: range_start.span,
|
||||
fru_span,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue