about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_error_messages/locales/en-US/hir_typeck.ftl8
-rw-r--r--compiler/rustc_error_messages/src/lib.rs1
-rw-r--r--compiler/rustc_errors/src/lib.rs3
-rw-r--r--compiler/rustc_hir_typeck/src/errors.rs39
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs51
5 files changed, 90 insertions, 12 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/hir_typeck.ftl b/compiler/rustc_error_messages/locales/en-US/hir_typeck.ftl
new file mode 100644
index 00000000000..2ce417a8c78
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/hir_typeck.ftl
@@ -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
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index d7f9148091c..1b35021db3d 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -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",
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index f3f1c7534b0..f8747386c04 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -467,6 +467,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) {}
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index afac6e7d94a..32265bcca45 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -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,
+        );
+    }
+}
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 752d2e0ff78..2764de751b0 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -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,
@@ -1616,10 +1617,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();
             }
         }
 
@@ -1877,19 +1884,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,
+            });
         }
     }