|
2 | 2 |
|
3 | 3 | use crate::ty::sty::InferTy;
|
4 | 4 | use crate::ty::TyKind::*;
|
5 |
| -use crate::ty::TyS; |
| 5 | +use crate::ty::{TyCtxt, TyS}; |
| 6 | +use rustc_errors::{Applicability, DiagnosticBuilder}; |
| 7 | +use rustc_hir as hir; |
| 8 | +use rustc_hir::def_id::DefId; |
| 9 | +use rustc_hir::{QPath, TyKind, WhereBoundPredicate, WherePredicate}; |
| 10 | +use rustc_span::{BytePos, Span}; |
6 | 11 |
|
7 | 12 | impl<'tcx> TyS<'tcx> {
|
8 | 13 | /// Similar to `TyS::is_primitive`, but also considers inferred numeric values to be primitive.
|
@@ -67,3 +72,180 @@ impl<'tcx> TyS<'tcx> {
|
67 | 72 | }
|
68 | 73 | }
|
69 | 74 | }
|
| 75 | + |
| 76 | +/// Suggest restricting a type param with a new bound. |
| 77 | +pub fn suggest_constraining_type_param( |
| 78 | + tcx: TyCtxt<'_>, |
| 79 | + generics: &hir::Generics<'_>, |
| 80 | + err: &mut DiagnosticBuilder<'_>, |
| 81 | + param_name: &str, |
| 82 | + constraint: &str, |
| 83 | + def_id: Option<DefId>, |
| 84 | +) -> bool { |
| 85 | + let param = generics.params.iter().find(|p| p.name.ident().as_str() == param_name); |
| 86 | + |
| 87 | + let param = if let Some(param) = param { |
| 88 | + param |
| 89 | + } else { |
| 90 | + return false; |
| 91 | + }; |
| 92 | + |
| 93 | + const MSG_RESTRICT_BOUND_FURTHER: &str = "consider further restricting this bound"; |
| 94 | + let msg_restrict_type = format!("consider restricting type parameter `{}`", param_name); |
| 95 | + let msg_restrict_type_further = |
| 96 | + format!("consider further restricting type parameter `{}`", param_name); |
| 97 | + |
| 98 | + if def_id == tcx.lang_items().sized_trait() { |
| 99 | + // Type parameters are already `Sized` by default. |
| 100 | + err.span_label(param.span, &format!("this type parameter needs to be `{}`", constraint)); |
| 101 | + return true; |
| 102 | + } |
| 103 | + let mut suggest_restrict = |span| { |
| 104 | + err.span_suggestion_verbose( |
| 105 | + span, |
| 106 | + MSG_RESTRICT_BOUND_FURTHER, |
| 107 | + format!(" + {}", constraint), |
| 108 | + Applicability::MachineApplicable, |
| 109 | + ); |
| 110 | + }; |
| 111 | + |
| 112 | + if param_name.starts_with("impl ") { |
| 113 | + // If there's an `impl Trait` used in argument position, suggest |
| 114 | + // restricting it: |
| 115 | + // |
| 116 | + // fn foo(t: impl Foo) { ... } |
| 117 | + // -------- |
| 118 | + // | |
| 119 | + // help: consider further restricting this bound with `+ Bar` |
| 120 | + // |
| 121 | + // Suggestion for tools in this case is: |
| 122 | + // |
| 123 | + // fn foo(t: impl Foo) { ... } |
| 124 | + // -------- |
| 125 | + // | |
| 126 | + // replace with: `impl Foo + Bar` |
| 127 | + |
| 128 | + suggest_restrict(param.span.shrink_to_hi()); |
| 129 | + return true; |
| 130 | + } |
| 131 | + |
| 132 | + if generics.where_clause.predicates.is_empty() |
| 133 | + // Given `trait Base<T = String>: Super<T>` where `T: Copy`, suggest restricting in the |
| 134 | + // `where` clause instead of `trait Base<T: Copy = String>: Super<T>`. |
| 135 | + && !matches!(param.kind, hir::GenericParamKind::Type { default: Some(_), .. }) |
| 136 | + { |
| 137 | + if let Some(bounds_span) = param.bounds_span() { |
| 138 | + // If user has provided some bounds, suggest restricting them: |
| 139 | + // |
| 140 | + // fn foo<T: Foo>(t: T) { ... } |
| 141 | + // --- |
| 142 | + // | |
| 143 | + // help: consider further restricting this bound with `+ Bar` |
| 144 | + // |
| 145 | + // Suggestion for tools in this case is: |
| 146 | + // |
| 147 | + // fn foo<T: Foo>(t: T) { ... } |
| 148 | + // -- |
| 149 | + // | |
| 150 | + // replace with: `T: Bar +` |
| 151 | + suggest_restrict(bounds_span.shrink_to_hi()); |
| 152 | + } else { |
| 153 | + // If user hasn't provided any bounds, suggest adding a new one: |
| 154 | + // |
| 155 | + // fn foo<T>(t: T) { ... } |
| 156 | + // - help: consider restricting this type parameter with `T: Foo` |
| 157 | + err.span_suggestion_verbose( |
| 158 | + param.span.shrink_to_hi(), |
| 159 | + &msg_restrict_type, |
| 160 | + format!(": {}", constraint), |
| 161 | + Applicability::MachineApplicable, |
| 162 | + ); |
| 163 | + } |
| 164 | + |
| 165 | + true |
| 166 | + } else { |
| 167 | + // This part is a bit tricky, because using the `where` clause user can |
| 168 | + // provide zero, one or many bounds for the same type parameter, so we |
| 169 | + // have following cases to consider: |
| 170 | + // |
| 171 | + // 1) When the type parameter has been provided zero bounds |
| 172 | + // |
| 173 | + // Message: |
| 174 | + // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... } |
| 175 | + // - help: consider restricting this type parameter with `where X: Bar` |
| 176 | + // |
| 177 | + // Suggestion: |
| 178 | + // fn foo<X, Y>(x: X, y: Y) where Y: Foo { ... } |
| 179 | + // - insert: `, X: Bar` |
| 180 | + // |
| 181 | + // |
| 182 | + // 2) When the type parameter has been provided one bound |
| 183 | + // |
| 184 | + // Message: |
| 185 | + // fn foo<T>(t: T) where T: Foo { ... } |
| 186 | + // ^^^^^^ |
| 187 | + // | |
| 188 | + // help: consider further restricting this bound with `+ Bar` |
| 189 | + // |
| 190 | + // Suggestion: |
| 191 | + // fn foo<T>(t: T) where T: Foo { ... } |
| 192 | + // ^^ |
| 193 | + // | |
| 194 | + // replace with: `T: Bar +` |
| 195 | + // |
| 196 | + // |
| 197 | + // 3) When the type parameter has been provided many bounds |
| 198 | + // |
| 199 | + // Message: |
| 200 | + // fn foo<T>(t: T) where T: Foo, T: Bar {... } |
| 201 | + // - help: consider further restricting this type parameter with `where T: Zar` |
| 202 | + // |
| 203 | + // Suggestion: |
| 204 | + // fn foo<T>(t: T) where T: Foo, T: Bar {... } |
| 205 | + // - insert: `, T: Zar` |
| 206 | + |
| 207 | + let mut param_spans = Vec::new(); |
| 208 | + |
| 209 | + for predicate in generics.where_clause.predicates { |
| 210 | + if let WherePredicate::BoundPredicate(WhereBoundPredicate { |
| 211 | + span, bounded_ty, .. |
| 212 | + }) = predicate |
| 213 | + { |
| 214 | + if let TyKind::Path(QPath::Resolved(_, path)) = &bounded_ty.kind { |
| 215 | + if let Some(segment) = path.segments.first() { |
| 216 | + if segment.ident.to_string() == param_name { |
| 217 | + param_spans.push(span); |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + let where_clause_span = generics.where_clause.span_for_predicates_or_empty_place(); |
| 225 | + // Account for `fn foo<T>(t: T) where T: Foo,` so we don't suggest two trailing commas. |
| 226 | + let mut trailing_comma = false; |
| 227 | + if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(where_clause_span) { |
| 228 | + trailing_comma = snippet.ends_with(','); |
| 229 | + } |
| 230 | + let where_clause_span = if trailing_comma { |
| 231 | + let hi = where_clause_span.hi(); |
| 232 | + Span::new(hi - BytePos(1), hi, where_clause_span.ctxt()) |
| 233 | + } else { |
| 234 | + where_clause_span.shrink_to_hi() |
| 235 | + }; |
| 236 | + |
| 237 | + match ¶m_spans[..] { |
| 238 | + &[¶m_span] => suggest_restrict(param_span.shrink_to_hi()), |
| 239 | + _ => { |
| 240 | + err.span_suggestion_verbose( |
| 241 | + where_clause_span, |
| 242 | + &msg_restrict_type_further, |
| 243 | + format!(", {}: {}", param_name, constraint), |
| 244 | + Applicability::MachineApplicable, |
| 245 | + ); |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + true |
| 250 | + } |
| 251 | +} |
0 commit comments