blob: 0c39aae9ca913cb217ed038a8ed74e01ce5d9fa5 [file] [log] [blame]
use std::collections::hash_map::Entry;
use arrayvec::ArrayVec;
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::{
FormatArgsStorage, FormatParamUsage, MacroCall, find_format_arg_expr, format_arg_removal_span,
format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
root_macro_call_first_node,
};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{SpanRangeExt, snippet};
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test, trait_ref_of_method};
use itertools::Itertools;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
FormatPlaceholder, FormatTrait,
};
use rustc_attr_data_structures::RustcVersion;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode};
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Upcast};
use rustc_session::impl_lint_pass;
use rustc_span::edition::Edition::Edition2021;
use rustc_span::{Span, Symbol, sym};
use rustc_trait_selection::infer::TyCtxtInferExt;
use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext};
declare_clippy_lint! {
/// ### What it does
/// Detects `format!` within the arguments of another macro that does
/// formatting such as `format!` itself, `write!` or `println!`. Suggests
/// inlining the `format!` call.
///
/// ### Why is this bad?
/// The recommended code is both shorter and avoids a temporary allocation.
///
/// ### Example
/// ```no_run
/// # use std::panic::Location;
/// println!("error: {}", format!("something failed at {}", Location::caller()));
/// ```
/// Use instead:
/// ```no_run
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller());
/// ```
#[clippy::version = "1.58.0"]
pub FORMAT_IN_FORMAT_ARGS,
perf,
"`format!` used in a macro that does formatting"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `Debug` formatting (`{:?}`) applied to an `OsStr` or `Path`.
///
/// ### Why is this bad?
/// Rust doesn't guarantee what `Debug` formatting looks like, and it could
/// change in the future. `OsStr`s and `Path`s can be `Display` formatted
/// using their `display` methods.
///
/// Furthermore, with `Debug` formatting, certain characters are escaped.
/// Thus, a `Debug` formatted `Path` is less likely to be clickable.
///
/// ### Example
/// ```no_run
/// # use std::path::Path;
/// let path = Path::new("...");
/// println!("The path is {:?}", path);
/// ```
/// Use instead:
/// ```no_run
/// # use std::path::Path;
/// let path = Path::new("…");
/// println!("The path is {}", path.display());
/// ```
#[clippy::version = "1.87.0"]
pub UNNECESSARY_DEBUG_FORMATTING,
pedantic,
"`Debug` formatting applied to an `OsStr` or `Path` when `.display()` is available"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
/// applied to a type that implements [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
/// in a macro that does formatting.
///
/// ### Why is this bad?
/// Since the type implements `Display`, the use of `to_string` is
/// unnecessary.
///
/// ### Example
/// ```no_run
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller().to_string());
/// ```
/// Use instead:
/// ```no_run
/// # use std::panic::Location;
/// println!("error: something failed at {}", Location::caller());
/// ```
#[clippy::version = "1.58.0"]
pub TO_STRING_IN_FORMAT_ARGS,
perf,
"`to_string` applied to a type that implements `Display` in format args"
}
declare_clippy_lint! {
/// ### What it does
/// Detect when a variable is not inlined in a format string,
/// and suggests to inline it.
///
/// ### Why is this bad?
/// Non-inlined code is slightly more difficult to read and understand,
/// as it requires arguments to be matched against the format string.
/// The inlined syntax, where allowed, is simpler.
///
/// ### Example
/// ```no_run
/// # let var = 42;
/// # let width = 1;
/// # let prec = 2;
/// format!("{}", var);
/// format!("{v:?}", v = var);
/// format!("{0} {0}", var);
/// format!("{0:1$}", var, width);
/// format!("{:.*}", prec, var);
/// ```
/// Use instead:
/// ```no_run
/// # let var = 42;
/// # let width = 1;
/// # let prec = 2;
/// format!("{var}");
/// format!("{var:?}");
/// format!("{var} {var}");
/// format!("{var:width$}");
/// format!("{var:.prec$}");
/// ```
///
/// If `allow-mixed-uninlined-format-args` is set to `false` in clippy.toml,
/// the following code will also trigger the lint:
/// ```no_run
/// # let var = 42;
/// format!("{} {}", var, 1+2);
/// ```
/// Use instead:
/// ```no_run
/// # let var = 42;
/// format!("{var} {}", 1+2);
/// ```
///
/// ### Known Problems
///
/// If a format string contains a numbered argument that cannot be inlined
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
#[clippy::version = "1.66.0"]
pub UNINLINED_FORMAT_ARGS,
style,
"using non-inlined variables in `format!` calls"
}
declare_clippy_lint! {
/// ### What it does
/// Detects [formatting parameters] that have no effect on the output of
/// `format!()`, `println!()` or similar macros.
///
/// ### Why is this bad?
/// Shorter format specifiers are easier to read, it may also indicate that
/// an expected formatting operation such as adding padding isn't happening.
///
/// ### Example
/// ```no_run
/// println!("{:.}", 1.0);
///
/// println!("not padded: {:5}", format_args!("..."));
/// ```
/// Use instead:
/// ```no_run
/// println!("{}", 1.0);
///
/// println!("not padded: {}", format_args!("..."));
/// // OR
/// println!("padded: {:5}", format!("..."));
/// ```
///
/// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters
#[clippy::version = "1.66.0"]
pub UNUSED_FORMAT_SPECS,
complexity,
"use of a format specifier that has no effect"
}
declare_clippy_lint! {
/// ### What it does
/// Detects [pointer format] as well as `Debug` formatting of raw pointers or function pointers
/// or any types that have a derived `Debug` impl that recursively contains them.
///
/// ### Why restrict this?
/// The addresses are only useful in very specific contexts, and certain projects may want to keep addresses of
/// certain data structures or functions from prying hacker eyes as an additional line of security.
///
/// ### Known problems
/// The lint currently only looks through derived `Debug` implementations. Checking whether a manual
/// implementation prints an address is left as an exercise to the next lint implementer.
///
/// ### Example
/// ```no_run
/// let foo = &0_u32;
/// fn bar() {}
/// println!("{:p}", foo);
/// let _ = format!("{:?}", &(bar as fn()));
/// ```
///
/// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
#[clippy::version = "1.88.0"]
pub POINTER_FORMAT,
restriction,
"formatting a pointer"
}
impl_lint_pass!(FormatArgs<'_> => [
FORMAT_IN_FORMAT_ARGS,
TO_STRING_IN_FORMAT_ARGS,
UNINLINED_FORMAT_ARGS,
UNNECESSARY_DEBUG_FORMATTING,
UNUSED_FORMAT_SPECS,
POINTER_FORMAT,
]);
#[allow(clippy::struct_field_names)]
pub struct FormatArgs<'tcx> {
format_args: FormatArgsStorage,
msrv: Msrv,
ignore_mixed: bool,
ty_msrv_map: FxHashMap<Ty<'tcx>, Option<RustcVersion>>,
has_derived_debug: FxHashMap<Ty<'tcx>, bool>,
has_pointer_format: FxHashMap<Ty<'tcx>, bool>,
}
impl<'tcx> FormatArgs<'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, conf: &'static Conf, format_args: FormatArgsStorage) -> Self {
let ty_msrv_map = make_ty_msrv_map(tcx);
Self {
format_args,
msrv: conf.msrv,
ignore_mixed: conf.allow_mixed_uninlined_format_args,
ty_msrv_map,
has_derived_debug: FxHashMap::default(),
has_pointer_format: FxHashMap::default(),
}
}
}
impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& is_format_macro(cx, macro_call.def_id)
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
{
let mut linter = FormatArgsExpr {
cx,
expr,
macro_call: &macro_call,
format_args,
ignore_mixed: self.ignore_mixed,
msrv: &self.msrv,
ty_msrv_map: &self.ty_msrv_map,
has_derived_debug: &mut self.has_derived_debug,
has_pointer_format: &mut self.has_pointer_format,
};
linter.check_templates();
if self.msrv.meets(cx, msrvs::FORMAT_ARGS_CAPTURE) {
linter.check_uninlined_args();
}
}
}
}
struct FormatArgsExpr<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
macro_call: &'a MacroCall,
format_args: &'a rustc_ast::FormatArgs,
ignore_mixed: bool,
msrv: &'a Msrv,
ty_msrv_map: &'a FxHashMap<Ty<'tcx>, Option<RustcVersion>>,
has_derived_debug: &'a mut FxHashMap<Ty<'tcx>, bool>,
has_pointer_format: &'a mut FxHashMap<Ty<'tcx>, bool>,
}
impl<'tcx> FormatArgsExpr<'_, 'tcx> {
fn check_templates(&mut self) {
for piece in &self.format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece
&& let Ok(index) = placeholder.argument.index
&& let Some(arg) = self.format_args.arguments.all_args().get(index)
&& let Some(arg_expr) = find_format_arg_expr(self.expr, arg)
{
self.check_unused_format_specifier(placeholder, arg_expr);
if placeholder.format_trait == FormatTrait::Display
&& placeholder.format_options == FormatOptions::default()
&& !self.is_aliased(index)
{
let name = self.cx.tcx.item_name(self.macro_call.def_id);
self.check_format_in_format_args(name, arg_expr);
self.check_to_string_in_format_args(name, arg_expr);
}
if placeholder.format_trait == FormatTrait::Debug {
let name = self.cx.tcx.item_name(self.macro_call.def_id);
self.check_unnecessary_debug_formatting(name, arg_expr);
if let Some(span) = placeholder.span
&& self.has_pointer_debug(self.cx.typeck_results().expr_ty(arg_expr), 0)
{
span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected");
}
}
if placeholder.format_trait == FormatTrait::Pointer
&& let Some(span) = placeholder.span
{
span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected");
}
}
}
}
fn check_unused_format_specifier(&self, placeholder: &FormatPlaceholder, arg: &Expr<'_>) {
let options = &placeholder.format_options;
if let Some(placeholder_span) = placeholder.span
&& *options != FormatOptions::default()
&& let ty = self.cx.typeck_results().expr_ty(arg).peel_refs()
&& is_type_lang_item(self.cx, ty, LangItem::FormatArguments)
{
span_lint_and_then(
self.cx,
UNUSED_FORMAT_SPECS,
placeholder_span,
"format specifiers have no effect on `format_args!()`",
|diag| {
let mut suggest_format = |spec| {
let message = format!("for the {spec} to apply consider using `format!()`");
if let Some(mac_call) = matching_root_macro_call(self.cx, arg.span, sym::format_args_macro) {
diag.span_suggestion(
self.cx.sess().source_map().span_until_char(mac_call.span, '!'),
message,
"format",
Applicability::MaybeIncorrect,
);
} else {
diag.help(message);
}
};
if options.width.is_some() {
suggest_format("width");
}
if options.precision.is_some() {
suggest_format("precision");
}
if let Some(format_span) = format_placeholder_format_span(placeholder) {
diag.span_suggestion_verbose(
format_span,
"if the current behavior is intentional, remove the format specifiers",
"",
Applicability::MaybeIncorrect,
);
}
},
);
}
}
fn check_uninlined_args(&self) {
if self.format_args.span.from_expansion() {
return;
}
if self.macro_call.span.edition() < Edition2021
&& (is_panic(self.cx, self.macro_call.def_id) || is_assert_macro(self.cx, self.macro_call.def_id))
{
// panic!, assert!, and debug_assert! before 2021 edition considers a single string argument as
// non-format
return;
}
let mut fixes = Vec::new();
// If any of the arguments are referenced by an index number,
// and that argument is not a simple variable and cannot be inlined,
// we cannot remove any other arguments in the format string,
// because the index numbers might be wrong after inlining.
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
for (pos, usage) in self.format_arg_positions() {
if !self.check_one_arg(pos, usage, &mut fixes) {
return;
}
}
if fixes.is_empty() {
return;
}
// multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
// in those cases, make the code suggestion hidden
let multiline_fix = fixes
.iter()
.any(|(span, _)| self.cx.sess().source_map().is_multiline(*span));
// Suggest removing each argument only once, for example in `format!("{0} {0}", arg)`.
fixes.sort_unstable_by_key(|(span, _)| *span);
fixes.dedup_by_key(|(span, _)| *span);
span_lint_and_then(
self.cx,
UNINLINED_FORMAT_ARGS,
self.macro_call.span,
"variables can be used directly in the `format!` string",
|diag| {
diag.multipart_suggestion_with_style(
"change this to",
fixes,
Applicability::MachineApplicable,
if multiline_fix { CompletelyHidden } else { ShowCode },
);
},
);
}
fn check_one_arg(&self, pos: &FormatArgPosition, usage: FormatParamUsage, fixes: &mut Vec<(Span, String)>) -> bool {
let index = pos.index.unwrap();
let arg = &self.format_args.arguments.all_args()[index];
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
&& let [segment] = path.segments.as_slice()
&& segment.args.is_none()
&& let Some(arg_span) = format_arg_removal_span(self.format_args, index)
&& let Some(pos_span) = pos.span
{
let replacement = match usage {
FormatParamUsage::Argument => segment.ident.name.to_string(),
FormatParamUsage::Width => format!("{}$", segment.ident.name),
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
};
fixes.push((pos_span, replacement));
fixes.push((arg_span, String::new()));
true // successful inlining, continue checking
} else {
// Do not continue inlining (return false) in case
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
pos.kind != FormatArgPositionKind::Number
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
}
}
fn check_format_in_format_args(&self, name: Symbol, arg: &Expr<'_>) {
let expn_data = arg.span.ctxt().outer_expn_data();
if expn_data.call_site.from_expansion() {
return;
}
let Some(mac_id) = expn_data.macro_def_id else { return };
if !self.cx.tcx.is_diagnostic_item(sym::format_macro, mac_id) {
return;
}
span_lint_and_then(
self.cx,
FORMAT_IN_FORMAT_ARGS,
self.macro_call.span,
format!("`format!` in `{name}!` args"),
|diag| {
diag.help(format!(
"combine the `format!(..)` arguments with the outer `{name}!(..)` call"
));
diag.help("or consider changing `format!` to `format_args!`");
},
);
}
fn check_to_string_in_format_args(&self, name: Symbol, value: &Expr<'_>) {
let cx = self.cx;
if !value.span.from_expansion()
&& let ExprKind::MethodCall(_, receiver, [], to_string_span) = value.kind
&& let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
&& is_diag_trait_item(cx, method_def_id, sym::ToString)
&& let receiver_ty = cx.typeck_results().expr_ty(receiver)
&& let Some(display_trait_id) = cx.tcx.get_diagnostic_item(sym::Display)
&& let (n_needed_derefs, target) =
count_needed_derefs(receiver_ty, cx.typeck_results().expr_adjustments(receiver).iter())
&& implements_trait(cx, target, display_trait_id, &[])
&& let Some(sized_trait_id) = cx.tcx.lang_items().sized_trait()
&& let Some(receiver_snippet) = receiver.span.source_callsite().get_source_text(cx)
{
let needs_ref = !implements_trait(cx, receiver_ty, sized_trait_id, &[]);
if n_needed_derefs == 0 && !needs_ref {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
to_string_span.with_lo(receiver.span.source_callsite().hi()),
format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"remove this",
String::new(),
Applicability::MachineApplicable,
);
} else {
span_lint_and_sugg(
cx,
TO_STRING_IN_FORMAT_ARGS,
value.span,
format!("`to_string` applied to a type that implements `Display` in `{name}!` args"),
"use this",
format!(
"{}{:*>n_needed_derefs$}{receiver_snippet}",
if needs_ref { "&" } else { "" },
""
),
Applicability::MachineApplicable,
);
}
}
}
fn check_unnecessary_debug_formatting(&self, name: Symbol, value: &Expr<'tcx>) {
let cx = self.cx;
if !is_in_test(cx.tcx, value.hir_id)
&& !value.span.from_expansion()
&& !is_from_proc_macro(cx, value)
&& let ty = cx.typeck_results().expr_ty(value)
&& self.can_display_format(ty)
{
// If the parent function is a method of `Debug`, we don't want to lint
// because it is likely that the user wants to use `Debug` formatting.
let parent_fn = cx.tcx.hir_get_parent_item(value.hir_id);
if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn)
&& let Some(trait_def_id) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::Debug, trait_def_id)
{
return;
}
let snippet = snippet(cx.sess(), value.span, "..");
span_lint_and_then(
cx,
UNNECESSARY_DEBUG_FORMATTING,
value.span,
format!("unnecessary `Debug` formatting in `{name}!` args"),
|diag| {
diag.help(format!(
"use `Display` formatting and change this to `{snippet}.display()`"
));
diag.note(
"switching to `Display` formatting will change how the value is shown; \
escaped characters will no longer be escaped and surrounding quotes will \
be removed",
);
},
);
}
}
fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
self.format_args.template.iter().flat_map(|piece| match piece {
FormatArgsPiece::Placeholder(placeholder) => {
let mut positions = ArrayVec::<_, 3>::new();
positions.push((&placeholder.argument, FormatParamUsage::Argument));
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
positions.push((position, FormatParamUsage::Width));
}
if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
positions.push((position, FormatParamUsage::Precision));
}
positions
},
FormatArgsPiece::Literal(_) => ArrayVec::new(),
})
}
/// Returns true if the format argument at `index` is referred to by multiple format params
fn is_aliased(&self, index: usize) -> bool {
self.format_arg_positions()
.filter(|(position, _)| position.index == Ok(index))
.at_most_one()
.is_err()
}
fn can_display_format(&self, ty: Ty<'tcx>) -> bool {
let ty = ty.peel_refs();
if let Some(msrv) = self.ty_msrv_map.get(&ty)
&& msrv.is_none_or(|msrv| self.msrv.meets(self.cx, msrv))
{
return true;
}
// Even if `ty` is not in `self.ty_msrv_map`, check whether `ty` implements `Deref` with
// a `Target` that is in `self.ty_msrv_map`.
if let Some(deref_trait_id) = self.cx.tcx.lang_items().deref_trait()
&& implements_trait(self.cx, ty, deref_trait_id, &[])
&& let Some(target_ty) = self.cx.get_associated_type(ty, deref_trait_id, sym::Target)
&& let Some(msrv) = self.ty_msrv_map.get(&target_ty)
&& msrv.is_none_or(|msrv| self.msrv.meets(self.cx, msrv))
{
return true;
}
false
}
fn has_pointer_debug(&mut self, ty: Ty<'tcx>, depth: usize) -> bool {
let cx = self.cx;
let tcx = cx.tcx;
if !tcx.recursion_limit().value_within_limit(depth) {
return false;
}
let depth = depth + 1;
let typing_env = cx.typing_env();
let ty = tcx.normalize_erasing_regions(typing_env, ty);
match ty.kind() {
ty::RawPtr(..) | ty::FnPtr(..) | ty::FnDef(..) => true,
ty::Ref(_, t, _) | ty::Slice(t) | ty::Array(t, _) => self.has_pointer_debug(*t, depth),
ty::Tuple(ts) => ts.iter().any(|t| self.has_pointer_debug(t, depth)),
ty::Adt(adt, args) => {
match self.has_pointer_format.entry(ty) {
Entry::Occupied(o) => return *o.get(),
Entry::Vacant(v) => v.insert(false),
};
let derived_debug = if let Some(&known) = self.has_derived_debug.get(&ty) {
known
} else {
let Some(trait_id) = tcx.get_diagnostic_item(sym::Debug) else {
return false;
};
let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
let trait_ref = TraitRef::new(tcx, trait_id, [GenericArg::from(ty)]);
let obligation = Obligation {
cause: ObligationCause::dummy(),
param_env,
recursion_depth: 0,
predicate: trait_ref.upcast(tcx),
};
let selection = SelectionContext::new(&infcx).select(&obligation);
let derived = if let Ok(Some(Selection::UserDefined(data))) = selection {
tcx.has_attr(data.impl_def_id, sym::automatically_derived)
} else {
false
};
self.has_derived_debug.insert(ty, derived);
derived
};
let pointer_debug = derived_debug
&& adt.all_fields().any(|f| {
self.has_pointer_debug(tcx.normalize_erasing_regions(typing_env, f.ty(tcx, args)), depth)
});
self.has_pointer_format.insert(ty, pointer_debug);
pointer_debug
},
_ => false,
}
}
}
fn make_ty_msrv_map(tcx: TyCtxt<'_>) -> FxHashMap<Ty<'_>, Option<RustcVersion>> {
[(sym::OsStr, Some(msrvs::OS_STR_DISPLAY)), (sym::Path, None)]
.into_iter()
.filter_map(|(name, feature)| {
tcx.get_diagnostic_item(name).map(|def_id| {
let ty = Ty::new_adt(tcx, tcx.adt_def(def_id), List::empty());
(ty, feature)
})
})
.collect()
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
where
I: Iterator<Item = &'tcx Adjustment<'tcx>>,
{
let mut n_total = 0;
let mut n_needed = 0;
loop {
if let Some(Adjustment {
kind: Adjust::Deref(overloaded_deref),
target,
}) = iter.next()
{
n_total += 1;
if overloaded_deref.is_some() {
n_needed = n_total;
}
ty = *target;
} else {
return (n_needed, ty);
}
}
}