]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_typeck/src/structured_errors/wrong_number_of_generic_args.rs
Rollup merge of #80972 - KodrAus:deprecate/remove_item, r=nagisa
[rust.git] / compiler / rustc_typeck / src / structured_errors / wrong_number_of_generic_args.rs
1 use crate::structured_errors::StructuredDiagnostic;
2 use hir::def::DefKind;
3 use rustc_errors::{pluralize, Applicability, DiagnosticBuilder, DiagnosticId};
4 use rustc_hir as hir;
5 use rustc_middle::ty::{self as ty, TyCtxt};
6 use rustc_session::Session;
7 use rustc_span::Span;
8 use rustc_span::{def_id::DefId, MultiSpan};
9
10 /// Handles the `wrong number of type / lifetime / ... arguments` family of error messages.
11 pub struct WrongNumberOfGenericArgs<'a, 'tcx> {
12     crate tcx: TyCtxt<'tcx>,
13
14     /// "type", "lifetime" etc., put verbatim into the message
15     crate kind: &'static str,
16
17     /// Minimum number of expected generic arguments (e.g. `2` for `HashMap`)
18     crate expected_min: usize,
19
20     /// Maximum number of expected generic arguments (e.g. `3` for `HashMap`)
21     crate expected_max: usize,
22
23     /// Number of generic arguments provided by the user
24     crate provided: usize,
25
26     /// Offset into `gen_params` - depends on the `kind`; might be different than `args_offset` when
27     /// user passed e.g. more arguments than was actually expected
28     crate params_offset: usize,
29
30     /// Offset into `gen_args` - depends on the `kind`
31     crate args_offset: usize,
32
33     /// Offending path segment
34     crate path_segment: &'a hir::PathSegment<'a>,
35
36     /// Generic parameters as expected by type or trait
37     crate gen_params: &'a ty::Generics,
38
39     /// Generic arguments as provided by user
40     crate gen_args: &'a hir::GenericArgs<'a>,
41
42     /// DefId of the generic type
43     crate def_id: DefId,
44
45     /// Offending place where the generic type has been misused
46     crate span: Span,
47 }
48
49 impl<'tcx> WrongNumberOfGenericArgs<'_, 'tcx> {
50     fn quantifier_and_bound(&self) -> (&'static str, usize) {
51         if self.expected_min == self.expected_max {
52             ("", self.expected_min)
53         } else if self.provided < self.expected_min {
54             ("at least ", self.expected_min)
55         } else {
56             ("at most ", self.expected_max)
57         }
58     }
59
60     fn start_diagnostics(&self) -> DiagnosticBuilder<'tcx> {
61         let span = self.path_segment.ident.span;
62
63         let msg = {
64             let def_path = self.tcx.def_path_str(self.def_id);
65             let def_kind = self.tcx.def_kind(self.def_id).descr(self.def_id);
66             let (quantifier, bound) = self.quantifier_and_bound();
67
68             if self.gen_args.span().is_some() {
69                 format!(
70                     "this {} takes {}{} {} argument{} but {}{} {} argument{} {} supplied",
71                     def_kind,
72                     quantifier,
73                     bound,
74                     self.kind,
75                     pluralize!(bound),
76                     if self.provided > 0 && self.provided < self.expected_min {
77                         "only "
78                     } else {
79                         ""
80                     },
81                     self.provided,
82                     self.kind,
83                     pluralize!(self.provided),
84                     if self.provided == 1 { "was" } else { "were" },
85                 )
86             } else {
87                 format!("missing generics for {} `{}`", def_kind, def_path)
88             }
89         };
90
91         self.tcx.sess.struct_span_err_with_code(span, &msg, self.code())
92     }
93
94     /// Builds the `expected 1 type argument / supplied 2 type arguments` message.
95     fn notify(&self, err: &mut DiagnosticBuilder<'_>) {
96         let (quantifier, bound) = self.quantifier_and_bound();
97
98         err.span_label(
99             self.path_segment.ident.span,
100             format!(
101                 "expected {}{} {} argument{}",
102                 quantifier,
103                 bound,
104                 self.kind,
105                 pluralize!(bound),
106             ),
107         );
108
109         // When user's provided too many arguments, we don't highlight each of them, because it
110         // would overlap with the suggestion to remove them:
111         //
112         // ```
113         // type Foo = Bar<usize, usize>;
114         //                -----  ----- supplied 2 type arguments
115         //                     ^^^^^^^ remove this type argument
116         // ```
117         if self.provided > self.expected_max {
118             return;
119         }
120
121         let args = self.gen_args.args.iter().skip(self.args_offset).take(self.provided).enumerate();
122
123         for (i, arg) in args {
124             err.span_label(
125                 arg.span(),
126                 if i + 1 == self.provided {
127                     format!(
128                         "supplied {} {} argument{}",
129                         self.provided,
130                         self.kind,
131                         pluralize!(self.provided)
132                     )
133                 } else {
134                     String::new()
135                 },
136             );
137         }
138     }
139
140     fn suggest(&self, err: &mut DiagnosticBuilder<'_>) {
141         if self.provided == 0 {
142             if self.gen_args.span().is_some() {
143                 self.suggest_adding_args(err);
144             } else {
145                 self.suggest_creating_generics(err);
146             }
147         } else if self.provided < self.expected_min {
148             self.suggest_adding_args(err);
149         } else {
150             self.suggest_removing_args_or_generics(err);
151         }
152     }
153
154     /// Suggests to create generics (`<...>`) when current invocation site contains no generics at
155     /// all:
156     ///
157     /// ```text
158     /// type Map = HashMap;
159     /// ```
160     fn suggest_creating_generics(&self, err: &mut DiagnosticBuilder<'_>) {
161         let params = self
162             .gen_params
163             .params
164             .iter()
165             .skip(self.params_offset)
166             .take(self.expected_min)
167             .map(|param| param.name.to_string())
168             .collect::<Vec<_>>()
169             .join(", ");
170
171         let def_kind = self.tcx.def_kind(self.def_id);
172
173         let sugg = if matches!(def_kind, DefKind::Fn | DefKind::AssocFn) {
174             format!("::<{}>", params)
175         } else {
176             format!("<{}>", params)
177         };
178
179         let msg = format!(
180             "use angle brackets to add missing {} argument{}",
181             self.kind,
182             pluralize!(self.expected_min),
183         );
184
185         err.span_suggestion_verbose(
186             self.path_segment.ident.span.shrink_to_hi(),
187             &msg,
188             sugg,
189             Applicability::HasPlaceholders,
190         );
191     }
192
193     /// Suggests to add missing argument(s) when current invocation site already contains some
194     /// generics:
195     ///
196     /// ```text
197     /// type Map = HashMap<String>;
198     /// ```
199     fn suggest_adding_args(&self, err: &mut DiagnosticBuilder<'_>) {
200         assert!(!self.gen_args.is_empty());
201
202         if self.gen_args.parenthesized {
203             return;
204         }
205
206         let missing_arg_count = self.expected_min - self.provided;
207
208         let (span, sugg_prefix) = if self.args_offset + self.provided == 0 {
209             let span = self.gen_args.args[0].span().shrink_to_lo();
210             (span, "")
211         } else {
212             let span =
213                 self.gen_args.args[self.args_offset + self.provided - 1].span().shrink_to_hi();
214             (span, ", ")
215         };
216
217         let msg = format!("add missing {} argument{}", self.kind, pluralize!(missing_arg_count));
218
219         let sugg = self
220             .gen_params
221             .params
222             .iter()
223             .skip(self.params_offset + self.provided)
224             .take(missing_arg_count)
225             .map(|param| param.name.to_string())
226             .collect::<Vec<_>>()
227             .join(", ");
228
229         let sugg = format!("{}{}", sugg_prefix, sugg);
230
231         err.span_suggestion_verbose(span, &msg, sugg, Applicability::HasPlaceholders);
232     }
233
234     /// Suggests to remove redundant argument(s):
235     ///
236     /// ```text
237     /// type Map = HashMap<String, String, String, String>;
238     /// ```
239     fn suggest_removing_args_or_generics(&self, err: &mut DiagnosticBuilder<'_>) {
240         assert!(self.provided > 0);
241
242         let redundant_args_count = self.provided - self.expected_max;
243         let remove_entire_generics = redundant_args_count >= self.gen_args.args.len();
244
245         let (span, msg) = if remove_entire_generics {
246             let sm = self.tcx.sess.source_map();
247
248             let span = self
249                 .path_segment
250                 .args
251                 .unwrap()
252                 .span_ext(sm)
253                 .unwrap()
254                 .with_lo(self.path_segment.ident.span.hi());
255
256             let msg = format!(
257                 "remove these {}generics",
258                 if self.gen_args.parenthesized { "parenthetical " } else { "" },
259             );
260
261             (span, msg)
262         } else {
263             // When it comes to removing particular argument(s) from the generics, there are two
264             // edge cases we have to consider:
265             //
266             // When the first redundant argument is at the beginning or in the middle of the
267             // generics, like so:
268             //
269             // ```
270             // type Map = HashMap<String, String, String, String>;
271             //                    ^^^^^^^^^^^^^^^^
272             //                    | span must start with the argument
273             // ```
274             //
275             // When the last redundant argument is at the ending of the generics, like so:
276             //
277             // ```
278             // type Map = HashMap<String, String, String, String>;
279             //                                  ^^^^^^^^^^^^^^^^
280             //                                  | span must start with the comma
281             // ```
282
283             // Index of the first redundant argument
284             let from_idx = self.args_offset + self.expected_max;
285
286             // Index of the last redundant argument
287             let to_idx = self.args_offset + self.provided - 1;
288
289             assert!(from_idx <= to_idx);
290
291             let (from, comma_eaten) = {
292                 let first_argument_starts_generics = from_idx == 0;
293                 let last_argument_ends_generics = to_idx + 1 == self.gen_args.args.len();
294
295                 if !first_argument_starts_generics && last_argument_ends_generics {
296                     (self.gen_args.args[from_idx - 1].span().hi(), true)
297                 } else {
298                     (self.gen_args.args[from_idx].span().lo(), false)
299                 }
300             };
301
302             let to = {
303                 let hi = self.gen_args.args[to_idx].span().hi();
304
305                 if comma_eaten {
306                     hi
307                 } else {
308                     self.gen_args.args.get(to_idx + 1).map(|arg| arg.span().lo()).unwrap_or(hi)
309                 }
310             };
311
312             let span = Span::new(from, to, self.span.ctxt());
313
314             let msg = format!(
315                 "remove {} {} argument{}",
316                 if redundant_args_count == 1 { "this" } else { "these" },
317                 self.kind,
318                 pluralize!(redundant_args_count),
319             );
320
321             (span, msg)
322         };
323
324         err.span_suggestion(span, &msg, String::new(), Applicability::MaybeIncorrect);
325     }
326
327     /// Builds the `type defined here` message.
328     fn show_definition(&self, err: &mut DiagnosticBuilder<'_>) {
329         let mut spans: MultiSpan = if let Some(def_span) = self.tcx.def_ident_span(self.def_id) {
330             def_span.into()
331         } else {
332             return;
333         };
334
335         let msg = {
336             let def_kind = self.tcx.def_kind(self.def_id).descr(self.def_id);
337             let (quantifier, bound) = self.quantifier_and_bound();
338
339             let params = if bound == 0 {
340                 String::new()
341             } else {
342                 let params = self
343                     .gen_params
344                     .params
345                     .iter()
346                     .skip(self.params_offset)
347                     .take(bound)
348                     .map(|param| {
349                         let span = self.tcx.def_span(param.def_id);
350                         spans.push_span_label(span, String::new());
351                         param
352                     })
353                     .map(|param| format!("`{}`", param.name))
354                     .collect::<Vec<_>>()
355                     .join(", ");
356
357                 format!(": {}", params)
358             };
359
360             format!(
361                 "{} defined here, with {}{} {} parameter{}{}",
362                 def_kind,
363                 quantifier,
364                 bound,
365                 self.kind,
366                 pluralize!(bound),
367                 params,
368             )
369         };
370
371         err.span_note(spans, &msg);
372     }
373 }
374
375 impl<'tcx> StructuredDiagnostic<'tcx> for WrongNumberOfGenericArgs<'_, 'tcx> {
376     fn session(&self) -> &Session {
377         self.tcx.sess
378     }
379
380     fn code(&self) -> DiagnosticId {
381         rustc_errors::error_code!(E0107)
382     }
383
384     fn diagnostic_common(&self) -> DiagnosticBuilder<'tcx> {
385         let mut err = self.start_diagnostics();
386
387         self.notify(&mut err);
388         self.suggest(&mut err);
389         self.show_definition(&mut err);
390
391         err
392     }
393 }