about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_hir/src/hir.rs36
-rw-r--r--compiler/rustc_middle/src/ty/diagnostics.rs35
2 files changed, 63 insertions, 8 deletions
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 15118073b33..76971d7ad3a 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -17,7 +17,7 @@ use rustc_error_messages::MultiSpan;
 use rustc_index::vec::IndexVec;
 use rustc_macros::HashStable_Generic;
 use rustc_span::hygiene::MacroKind;
-use rustc_span::source_map::Spanned;
+use rustc_span::source_map::{SourceMap, Spanned};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{def_id::LocalDefId, BytePos, Span, DUMMY_SP};
 use rustc_target::asm::InlineAsmRegOrRegClass;
@@ -523,6 +523,40 @@ impl<'hir> GenericParam<'hir> {
             })
             .map(|sp| sp.shrink_to_hi())
     }
+
+    /// Returns the span of `:` after a generic parameter.
+    ///
+    /// For example:
+    ///
+    /// ```text
+    /// fn a<T:>()
+    ///       ^
+    ///       |      here
+    ///       here   |
+    ///              v
+    /// fn b<T       :>()
+    ///
+    /// fn c<T
+    ///
+    /// :>()
+    /// ^
+    /// |
+    /// here
+    /// ```
+    pub fn colon_span_for_suggestions(&self, source_map: &SourceMap) -> Option<Span> {
+        let sp = source_map
+            .span_extend_while(self.span.shrink_to_hi(), |c| c.is_whitespace() || c == ':')
+            .ok()?;
+
+        let snippet = source_map.span_to_snippet(sp).ok()?;
+        let offset = snippet.find(':')?;
+
+        let colon_sp = sp
+            .with_lo(BytePos(sp.lo().0 + offset as u32))
+            .with_hi(BytePos(sp.lo().0 + (offset + ':'.len_utf8()) as u32));
+
+        Some(colon_sp)
+    }
 }
 
 #[derive(Default)]
diff --git a/compiler/rustc_middle/src/ty/diagnostics.rs b/compiler/rustc_middle/src/ty/diagnostics.rs
index 49d0ce52052..3b044b19259 100644
--- a/compiler/rustc_middle/src/ty/diagnostics.rs
+++ b/compiler/rustc_middle/src/ty/diagnostics.rs
@@ -336,10 +336,14 @@ pub fn suggest_constraining_type_params<'a>(
         }
 
         let constraint = constraints.iter().map(|&(c, _)| c).collect::<Vec<_>>().join(" + ");
-        let mut suggest_restrict = |span| {
+        let mut suggest_restrict = |span, bound_list_non_empty| {
             suggestions.push((
                 span,
-                format!(" + {}", constraint),
+                if bound_list_non_empty {
+                    format!(" + {}", constraint)
+                } else {
+                    format!(" {}", constraint)
+                },
                 SuggestChangingConstraintsMessage::RestrictBoundFurther,
             ))
         };
@@ -360,7 +364,10 @@ pub fn suggest_constraining_type_params<'a>(
             //             |
             //             replace with: `impl Foo + Bar`
 
-            suggest_restrict(param.span.shrink_to_hi());
+            // `impl Trait` must have at least one trait in the list
+            let bound_list_non_empty = true;
+
+            suggest_restrict(param.span.shrink_to_hi(), bound_list_non_empty);
             continue;
         }
 
@@ -383,15 +390,25 @@ pub fn suggest_constraining_type_params<'a>(
                 //          --
                 //          |
                 //          replace with: `T: Bar +`
-                suggest_restrict(span);
+
+                // `bounds_span_for_suggestions` returns `None` if the list is empty
+                let bound_list_non_empty = true;
+
+                suggest_restrict(span, bound_list_non_empty);
             } else {
+                let (colon, span) = match param.colon_span_for_suggestions(tcx.sess.source_map()) {
+                    // If there is already a colon after generic, do not suggest adding it again
+                    Some(sp) => ("", sp.shrink_to_hi()),
+                    None => (":", param.span.shrink_to_hi()),
+                };
+
                 // If user hasn't provided any bounds, suggest adding a new one:
                 //
                 //   fn foo<T>(t: T) { ... }
                 //          - help: consider restricting this type parameter with `T: Foo`
                 suggestions.push((
-                    param.span.shrink_to_hi(),
-                    format!(": {}", constraint),
+                    span,
+                    format!("{colon} {constraint}"),
                     SuggestChangingConstraintsMessage::RestrictType { ty: param_name },
                 ));
             }
@@ -459,17 +476,21 @@ pub fn suggest_constraining_type_params<'a>(
                 ));
             } else {
                 let mut param_spans = Vec::new();
+                let mut non_empty = false;
 
                 for predicate in generics.where_clause.predicates {
                     if let WherePredicate::BoundPredicate(WhereBoundPredicate {
                         span,
                         bounded_ty,
+                        bounds,
                         ..
                     }) = predicate
                     {
                         if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind {
                             if let Some(segment) = path.segments.first() {
                                 if segment.ident.to_string() == param_name {
+                                    non_empty = !bounds.is_empty();
+
                                     param_spans.push(span);
                                 }
                             }
@@ -478,7 +499,7 @@ pub fn suggest_constraining_type_params<'a>(
                 }
 
                 match param_spans[..] {
-                    [&param_span] => suggest_restrict(param_span.shrink_to_hi()),
+                    [&param_span] => suggest_restrict(param_span.shrink_to_hi(), non_empty),
                     _ => {
                         suggestions.push((
                             generics.where_clause.tail_span_for_suggestion(),