]>
Commit | Line | Data |
---|---|---|
353b0b11 | 1 | use arrayvec::ArrayVec; |
ed00b5ec | 2 | use clippy_config::msrvs::{self, Msrv}; |
3c0e092e | 3 | use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; |
487cf647 | 4 | use clippy_utils::is_diag_trait_item; |
2b03887a | 5 | use clippy_utils::macros::{ |
31ef2f64 FG |
6 | find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, |
7 | is_panic, matching_root_macro_call, root_macro_call_first_node, FormatArgsStorage, FormatParamUsage, MacroCall, | |
2b03887a | 8 | }; |
3c0e092e | 9 | use clippy_utils::source::snippet_opt; |
9ffffee4 | 10 | use clippy_utils::ty::{implements_trait, is_type_lang_item}; |
f2b60f7d | 11 | use itertools::Itertools; |
353b0b11 FG |
12 | use rustc_ast::{ |
13 | FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, | |
14 | FormatPlaceholder, FormatTrait, | |
15 | }; | |
add651ee FG |
16 | use rustc_errors::Applicability; |
17 | use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode}; | |
353b0b11 | 18 | use rustc_hir::{Expr, ExprKind, LangItem}; |
2b03887a | 19 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
3c0e092e XL |
20 | use rustc_middle::ty::adjustment::{Adjust, Adjustment}; |
21 | use rustc_middle::ty::Ty; | |
4b012472 | 22 | use rustc_session::impl_lint_pass; |
2b03887a | 23 | use rustc_span::edition::Edition::Edition2021; |
353b0b11 | 24 | use rustc_span::{sym, Span, Symbol}; |
3c0e092e XL |
25 | |
26 | declare_clippy_lint! { | |
27 | /// ### What it does | |
28 | /// Detects `format!` within the arguments of another macro that does | |
29 | /// formatting such as `format!` itself, `write!` or `println!`. Suggests | |
30 | /// inlining the `format!` call. | |
31 | /// | |
32 | /// ### Why is this bad? | |
33 | /// The recommended code is both shorter and avoids a temporary allocation. | |
34 | /// | |
35 | /// ### Example | |
ed00b5ec | 36 | /// ```no_run |
3c0e092e XL |
37 | /// # use std::panic::Location; |
38 | /// println!("error: {}", format!("something failed at {}", Location::caller())); | |
39 | /// ``` | |
40 | /// Use instead: | |
ed00b5ec | 41 | /// ```no_run |
3c0e092e XL |
42 | /// # use std::panic::Location; |
43 | /// println!("error: something failed at {}", Location::caller()); | |
44 | /// ``` | |
a2a8927a | 45 | #[clippy::version = "1.58.0"] |
3c0e092e XL |
46 | pub FORMAT_IN_FORMAT_ARGS, |
47 | perf, | |
48 | "`format!` used in a macro that does formatting" | |
49 | } | |
50 | ||
51 | declare_clippy_lint! { | |
52 | /// ### What it does | |
53 | /// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string) | |
54 | /// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) | |
55 | /// in a macro that does formatting. | |
56 | /// | |
57 | /// ### Why is this bad? | |
58 | /// Since the type implements `Display`, the use of `to_string` is | |
59 | /// unnecessary. | |
60 | /// | |
61 | /// ### Example | |
ed00b5ec | 62 | /// ```no_run |
3c0e092e XL |
63 | /// # use std::panic::Location; |
64 | /// println!("error: something failed at {}", Location::caller().to_string()); | |
65 | /// ``` | |
66 | /// Use instead: | |
ed00b5ec | 67 | /// ```no_run |
3c0e092e XL |
68 | /// # use std::panic::Location; |
69 | /// println!("error: something failed at {}", Location::caller()); | |
70 | /// ``` | |
a2a8927a | 71 | #[clippy::version = "1.58.0"] |
3c0e092e XL |
72 | pub TO_STRING_IN_FORMAT_ARGS, |
73 | perf, | |
74 | "`to_string` applied to a type that implements `Display` in format args" | |
75 | } | |
76 | ||
2b03887a FG |
77 | declare_clippy_lint! { |
78 | /// ### What it does | |
79 | /// Detect when a variable is not inlined in a format string, | |
80 | /// and suggests to inline it. | |
81 | /// | |
82 | /// ### Why is this bad? | |
83 | /// Non-inlined code is slightly more difficult to read and understand, | |
84 | /// as it requires arguments to be matched against the format string. | |
85 | /// The inlined syntax, where allowed, is simpler. | |
86 | /// | |
87 | /// ### Example | |
ed00b5ec | 88 | /// ```no_run |
2b03887a FG |
89 | /// # let var = 42; |
90 | /// # let width = 1; | |
91 | /// # let prec = 2; | |
92 | /// format!("{}", var); | |
93 | /// format!("{v:?}", v = var); | |
94 | /// format!("{0} {0}", var); | |
95 | /// format!("{0:1$}", var, width); | |
96 | /// format!("{:.*}", prec, var); | |
97 | /// ``` | |
98 | /// Use instead: | |
ed00b5ec | 99 | /// ```no_run |
2b03887a FG |
100 | /// # let var = 42; |
101 | /// # let width = 1; | |
102 | /// # let prec = 2; | |
103 | /// format!("{var}"); | |
104 | /// format!("{var:?}"); | |
105 | /// format!("{var} {var}"); | |
106 | /// format!("{var:width$}"); | |
107 | /// format!("{var:.prec$}"); | |
108 | /// ``` | |
109 | /// | |
487cf647 FG |
110 | /// If allow-mixed-uninlined-format-args is set to false in clippy.toml, |
111 | /// the following code will also trigger the lint: | |
ed00b5ec | 112 | /// ```no_run |
487cf647 FG |
113 | /// # let var = 42; |
114 | /// format!("{} {}", var, 1+2); | |
115 | /// ``` | |
116 | /// Use instead: | |
ed00b5ec | 117 | /// ```no_run |
487cf647 FG |
118 | /// # let var = 42; |
119 | /// format!("{var} {}", 1+2); | |
2b03887a FG |
120 | /// ``` |
121 | /// | |
487cf647 FG |
122 | /// ### Known Problems |
123 | /// | |
2b03887a FG |
124 | /// If a format string contains a numbered argument that cannot be inlined |
125 | /// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`. | |
9c376795 | 126 | #[clippy::version = "1.66.0"] |
2b03887a FG |
127 | pub UNINLINED_FORMAT_ARGS, |
128 | pedantic, | |
129 | "using non-inlined variables in `format!` calls" | |
130 | } | |
131 | ||
132 | declare_clippy_lint! { | |
133 | /// ### What it does | |
134 | /// Detects [formatting parameters] that have no effect on the output of | |
135 | /// `format!()`, `println!()` or similar macros. | |
136 | /// | |
137 | /// ### Why is this bad? | |
138 | /// Shorter format specifiers are easier to read, it may also indicate that | |
139 | /// an expected formatting operation such as adding padding isn't happening. | |
140 | /// | |
141 | /// ### Example | |
ed00b5ec | 142 | /// ```no_run |
2b03887a FG |
143 | /// println!("{:.}", 1.0); |
144 | /// | |
145 | /// println!("not padded: {:5}", format_args!("...")); | |
146 | /// ``` | |
147 | /// Use instead: | |
ed00b5ec | 148 | /// ```no_run |
2b03887a FG |
149 | /// println!("{}", 1.0); |
150 | /// | |
151 | /// println!("not padded: {}", format_args!("...")); | |
152 | /// // OR | |
153 | /// println!("padded: {:5}", format!("...")); | |
154 | /// ``` | |
155 | /// | |
156 | /// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters | |
157 | #[clippy::version = "1.66.0"] | |
158 | pub UNUSED_FORMAT_SPECS, | |
159 | complexity, | |
160 | "use of a format specifier that has no effect" | |
161 | } | |
162 | ||
163 | impl_lint_pass!(FormatArgs => [ | |
164 | FORMAT_IN_FORMAT_ARGS, | |
165 | TO_STRING_IN_FORMAT_ARGS, | |
166 | UNINLINED_FORMAT_ARGS, | |
167 | UNUSED_FORMAT_SPECS, | |
168 | ]); | |
169 | ||
31ef2f64 | 170 | #[allow(clippy::struct_field_names)] |
2b03887a | 171 | pub struct FormatArgs { |
31ef2f64 | 172 | format_args: FormatArgsStorage, |
487cf647 FG |
173 | msrv: Msrv, |
174 | ignore_mixed: bool, | |
2b03887a FG |
175 | } |
176 | ||
177 | impl FormatArgs { | |
178 | #[must_use] | |
31ef2f64 | 179 | pub fn new(format_args: FormatArgsStorage, msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self { |
487cf647 | 180 | Self { |
31ef2f64 | 181 | format_args, |
487cf647 FG |
182 | msrv, |
183 | ignore_mixed: allow_mixed_uninlined_format_args, | |
184 | } | |
2b03887a FG |
185 | } |
186 | } | |
3c0e092e | 187 | |
3c0e092e XL |
188 | impl<'tcx> LateLintPass<'tcx> for FormatArgs { |
189 | fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | |
781aab86 FG |
190 | if let Some(macro_call) = root_macro_call_first_node(cx, expr) |
191 | && is_format_macro(cx, macro_call.def_id) | |
31ef2f64 | 192 | && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) |
781aab86 | 193 | { |
c620b35d FG |
194 | let linter = FormatArgsExpr { |
195 | cx, | |
196 | expr, | |
197 | macro_call: ¯o_call, | |
31ef2f64 | 198 | format_args, |
c620b35d FG |
199 | ignore_mixed: self.ignore_mixed, |
200 | }; | |
353b0b11 | 201 | |
c620b35d | 202 | linter.check_templates(); |
353b0b11 | 203 | |
487cf647 | 204 | if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) { |
c620b35d | 205 | linter.check_uninlined_args(); |
3c0e092e | 206 | } |
781aab86 | 207 | } |
3c0e092e | 208 | } |
2b03887a FG |
209 | |
210 | extract_msrv_attr!(LateContext); | |
211 | } | |
212 | ||
c620b35d FG |
213 | struct FormatArgsExpr<'a, 'tcx> { |
214 | cx: &'a LateContext<'tcx>, | |
215 | expr: &'tcx Expr<'tcx>, | |
216 | macro_call: &'a MacroCall, | |
217 | format_args: &'a rustc_ast::FormatArgs, | |
218 | ignore_mixed: bool, | |
219 | } | |
2b03887a | 220 | |
c620b35d FG |
221 | impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> { |
222 | fn check_templates(&self) { | |
223 | for piece in &self.format_args.template { | |
224 | if let FormatArgsPiece::Placeholder(placeholder) = piece | |
225 | && let Ok(index) = placeholder.argument.index | |
226 | && let Some(arg) = self.format_args.arguments.all_args().get(index) | |
227 | { | |
228 | let arg_expr = find_format_arg_expr(self.expr, arg); | |
229 | ||
230 | self.check_unused_format_specifier(placeholder, arg_expr); | |
231 | ||
232 | if let Ok(arg_expr) = arg_expr | |
233 | && placeholder.format_trait == FormatTrait::Display | |
234 | && placeholder.format_options == FormatOptions::default() | |
235 | && !self.is_aliased(index) | |
236 | { | |
237 | let name = self.cx.tcx.item_name(self.macro_call.def_id); | |
238 | self.check_format_in_format_args(name, arg_expr); | |
239 | self.check_to_string_in_format_args(name, arg_expr); | |
2b03887a | 240 | } |
c620b35d FG |
241 | } |
242 | } | |
243 | } | |
2b03887a | 244 | |
c620b35d FG |
245 | fn check_unused_format_specifier( |
246 | &self, | |
247 | placeholder: &FormatPlaceholder, | |
248 | arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>, | |
249 | ) { | |
250 | let ty_or_ast_expr = arg_expr.map(|expr| self.cx.typeck_results().expr_ty(expr).peel_refs()); | |
2b03887a | 251 | |
c620b35d FG |
252 | let is_format_args = match ty_or_ast_expr { |
253 | Ok(ty) => is_type_lang_item(self.cx, ty, LangItem::FormatArguments), | |
254 | Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)), | |
255 | }; | |
2b03887a | 256 | |
c620b35d FG |
257 | let options = &placeholder.format_options; |
258 | ||
259 | let arg_span = match arg_expr { | |
260 | Ok(expr) => expr.span, | |
261 | Err(expr) => expr.span, | |
262 | }; | |
263 | ||
264 | if let Some(placeholder_span) = placeholder.span | |
265 | && is_format_args | |
266 | && *options != FormatOptions::default() | |
267 | { | |
268 | span_lint_and_then( | |
269 | self.cx, | |
270 | UNUSED_FORMAT_SPECS, | |
271 | placeholder_span, | |
272 | "format specifiers have no effect on `format_args!()`", | |
273 | |diag| { | |
274 | let mut suggest_format = |spec| { | |
275 | let message = format!("for the {spec} to apply consider using `format!()`"); | |
276 | ||
31ef2f64 | 277 | if let Some(mac_call) = matching_root_macro_call(self.cx, arg_span, sym::format_args_macro) { |
c620b35d FG |
278 | diag.span_suggestion( |
279 | self.cx.sess().source_map().span_until_char(mac_call.span, '!'), | |
280 | message, | |
281 | "format", | |
282 | Applicability::MaybeIncorrect, | |
283 | ); | |
284 | } else { | |
285 | diag.help(message); | |
286 | } | |
287 | }; | |
288 | ||
289 | if options.width.is_some() { | |
290 | suggest_format("width"); | |
291 | } | |
292 | ||
293 | if options.precision.is_some() { | |
294 | suggest_format("precision"); | |
295 | } | |
296 | ||
297 | if let Some(format_span) = format_placeholder_format_span(placeholder) { | |
298 | diag.span_suggestion_verbose( | |
299 | format_span, | |
300 | "if the current behavior is intentional, remove the format specifiers", | |
301 | "", | |
302 | Applicability::MaybeIncorrect, | |
303 | ); | |
304 | } | |
305 | }, | |
306 | ); | |
307 | } | |
2b03887a FG |
308 | } |
309 | ||
c620b35d FG |
310 | fn check_uninlined_args(&self) { |
311 | if self.format_args.span.from_expansion() { | |
312 | return; | |
313 | } | |
314 | if self.macro_call.span.edition() < Edition2021 | |
315 | && (is_panic(self.cx, self.macro_call.def_id) || is_assert_macro(self.cx, self.macro_call.def_id)) | |
316 | { | |
317 | // panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as | |
318 | // non-format | |
353b0b11 FG |
319 | return; |
320 | } | |
353b0b11 | 321 | |
c620b35d FG |
322 | let mut fixes = Vec::new(); |
323 | // If any of the arguments are referenced by an index number, | |
324 | // and that argument is not a simple variable and cannot be inlined, | |
325 | // we cannot remove any other arguments in the format string, | |
326 | // because the index numbers might be wrong after inlining. | |
327 | // Example of an un-inlinable format: print!("{}{1}", foo, 2) | |
328 | for (pos, usage) in self.format_arg_positions() { | |
329 | if !self.check_one_arg(pos, usage, &mut fixes) { | |
330 | return; | |
331 | } | |
332 | } | |
2b03887a | 333 | |
c620b35d FG |
334 | if fixes.is_empty() { |
335 | return; | |
336 | } | |
2b03887a | 337 | |
c620b35d FG |
338 | // multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308 |
339 | // in those cases, make the code suggestion hidden | |
340 | let multiline_fix = fixes | |
341 | .iter() | |
342 | .any(|(span, _)| self.cx.sess().source_map().is_multiline(*span)); | |
3c0e092e | 343 | |
c620b35d FG |
344 | // Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`. |
345 | fixes.sort_unstable_by_key(|(span, _)| *span); | |
346 | fixes.dedup_by_key(|(span, _)| *span); | |
347 | ||
348 | span_lint_and_then( | |
349 | self.cx, | |
350 | UNINLINED_FORMAT_ARGS, | |
351 | self.macro_call.span, | |
352 | "variables can be used directly in the `format!` string", | |
353 | |diag| { | |
354 | diag.multipart_suggestion_with_style( | |
355 | "change this to", | |
356 | fixes, | |
357 | Applicability::MachineApplicable, | |
358 | if multiline_fix { CompletelyHidden } else { ShowCode }, | |
359 | ); | |
360 | }, | |
361 | ); | |
3c0e092e | 362 | } |
3c0e092e | 363 | |
c620b35d FG |
364 | fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool { |
365 | let index = pos.index.unwrap(); | |
366 | let arg = &self.format_args.arguments.all_args()[index]; | |
367 | ||
368 | if !matches!(arg.kind, FormatArgumentKind::Captured(_)) | |
369 | && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind | |
370 | && let [segment] = path.segments.as_slice() | |
371 | && segment.args.is_none() | |
372 | && let Some(arg_span) = format_arg_removal_span(self.format_args, index) | |
373 | && let Some(pos_span) = pos.span | |
374 | { | |
375 | let replacement = match usage { | |
376 | FormatParamUsage::Argument => segment.ident.name.to_string(), | |
377 | FormatParamUsage::Width => format!("{}$", segment.ident.name), | |
378 | FormatParamUsage::Precision => format!(".{}$", segment.ident.name), | |
379 | }; | |
380 | fixes.push((pos_span, replacement)); | |
381 | fixes.push((arg_span, String::new())); | |
382 | true // successful inlining, continue checking | |
4b012472 | 383 | } else { |
c620b35d FG |
384 | // Do not continue inlining (return false) in case |
385 | // * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)` | |
386 | // * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already | |
387 | pos.kind != FormatArgPositionKind::Number | |
388 | && (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_))) | |
3c0e092e XL |
389 | } |
390 | } | |
3c0e092e | 391 | |
c620b35d FG |
392 | fn check_format_in_format_args(&self, name: Symbol, arg: &Expr<'_>) { |
393 | let expn_data = arg.span.ctxt().outer_expn_data(); | |
394 | if expn_data.call_site.from_expansion() { | |
395 | return; | |
396 | } | |
397 | let Some(mac_id) = expn_data.macro_def_id else { return }; | |
398 | if !self.cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) { | |
399 | return; | |
400 | } | |
401 | span_lint_and_then( | |
402 | self.cx, | |
403 | FORMAT_IN_FORMAT_ARGS, | |
404 | self.macro_call.span, | |
e8be2606 | 405 | format!("`format!` in `{name}!` args"), |
c620b35d FG |
406 | |diag| { |
407 | diag.help(format!( | |
408 | "combine the `format!(..)` arguments with the outer `{name}!(..)` call" | |
409 | )); | |
410 | diag.help("or consider changing `format!` to `format_args!`"); | |
411 | }, | |
412 | ); | |
413 | } | |
353b0b11 | 414 | |
c620b35d FG |
415 | fn check_to_string_in_format_args(&self, name: Symbol, value: &Expr<'_>) { |
416 | let cx = self.cx; | |
417 | if !value.span.from_expansion() | |
418 | && let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind | |
419 | && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id) | |
420 | && is_diag_trait_item(cx, method_def_id, sym::ToString) | |
421 | && let receiver_ty = cx.typeck_results().expr_ty(receiver) | |
422 | && let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display) | |
423 | && let (n_needed_derefs, target) = | |
424 | count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter()) | |
425 | && implements_trait(cx, target, display_trait_id, &[]) | |
426 | && let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait() | |
427 | && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) | |
428 | { | |
429 | let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]); | |
430 | if n_needed_derefs == 0 && !needs_ref { | |
431 | span_lint_and_sugg( | |
432 | cx, | |
433 | TO_STRING_IN_FORMAT_ARGS, | |
434 | to_string_span.with_lo(receiver.span.hi()), | |
e8be2606 | 435 | format!("`to_string` applied to a type that implements `Display` in `{name}!` args"), |
c620b35d FG |
436 | "remove this", |
437 | String::new(), | |
438 | Applicability::MachineApplicable, | |
439 | ); | |
440 | } else { | |
441 | span_lint_and_sugg( | |
442 | cx, | |
443 | TO_STRING_IN_FORMAT_ARGS, | |
444 | value.span, | |
e8be2606 | 445 | format!("`to_string` applied to a type that implements `Display` in `{name}!` args"), |
c620b35d FG |
446 | "use this", |
447 | format!( | |
448 | "{}{:*>n_needed_derefs$}{receiver_snippet}", | |
449 | if needs_ref { "&" } else { "" }, | |
450 | "" | |
451 | ), | |
452 | Applicability::MachineApplicable, | |
453 | ); | |
353b0b11 | 454 | } |
c620b35d FG |
455 | } |
456 | } | |
353b0b11 | 457 | |
c620b35d FG |
458 | fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> { |
459 | self.format_args.template.iter().flat_map(|piece| match piece { | |
460 | FormatArgsPiece::Placeholder(placeholder) => { | |
461 | let mut positions = ArrayVec::<_, 3>::new(); | |
353b0b11 | 462 | |
c620b35d FG |
463 | positions.push((&placeholder.argument, FormatParamUsage::Argument)); |
464 | if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width { | |
465 | positions.push((position, FormatParamUsage::Width)); | |
466 | } | |
467 | if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision { | |
468 | positions.push((position, FormatParamUsage::Precision)); | |
469 | } | |
470 | ||
471 | positions | |
472 | }, | |
473 | FormatArgsPiece::Literal(_) => ArrayVec::new(), | |
474 | }) | |
475 | } | |
476 | ||
477 | /// Returns true if the format argument at `index` is referred to by multiple format params | |
478 | fn is_aliased(&self, index: usize) -> bool { | |
479 | self.format_arg_positions() | |
480 | .filter(|(position, _)| position.index == Ok(index)) | |
481 | .at_most_one() | |
482 | .is_err() | |
483 | } | |
3c0e092e XL |
484 | } |
485 | ||
3c0e092e XL |
486 | fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>) |
487 | where | |
488 | I: Iterator<Item = &'tcx Adjustment<'tcx>>, | |
489 | { | |
490 | let mut n_total = 0; | |
491 | let mut n_needed = 0; | |
492 | loop { | |
353b0b11 FG |
493 | if let Some(Adjustment { |
494 | kind: Adjust::Deref(overloaded_deref), | |
495 | target, | |
496 | }) = iter.next() | |
497 | { | |
3c0e092e XL |
498 | n_total += 1; |
499 | if overloaded_deref.is_some() { | |
500 | n_needed = n_total; | |
501 | } | |
5099ac24 | 502 | ty = *target; |
3c0e092e XL |
503 | } else { |
504 | return (n_needed, ty); | |
505 | } | |
506 | } | |
507 | } |