Rollup merge of #136583 - Jarcho:fn_ctxt2, r=compiler-errors
Only highlight unmatchable parameters at the definition site Followup to #136497 This generally results more focused messages in the same vein as #99635 (see `test/ui/argument-suggestions/complex.rs`). There are still some cases (e.g. `test/ui/argument-suggestions/permuted_arguments.rs`) where it might be worth highlighting the arguments. This is mitigated by the fact that a suggestion with a suggested rearrangement is given. r? `@compiler-errors`
This commit is contained in:
commit
736f902581
28 changed files with 246 additions and 312 deletions
|
@ -1,4 +1,4 @@
|
|||
use std::{iter, mem};
|
||||
use std::{fmt, iter, mem};
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
|
@ -13,7 +13,7 @@ use rustc_hir::{ExprKind, HirId, Node, QPath};
|
|||
use rustc_hir_analysis::check::intrinsicck::InlineAsmCtxt;
|
||||
use rustc_hir_analysis::check::potentially_plural_count;
|
||||
use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer;
|
||||
use rustc_index::{Idx, IndexVec};
|
||||
use rustc_index::IndexVec;
|
||||
use rustc_infer::infer::{DefineOpaqueTypes, InferOk, TypeTrace};
|
||||
use rustc_middle::ty::adjustment::AllowTwoPhase;
|
||||
use rustc_middle::ty::error::TypeError;
|
||||
|
@ -25,6 +25,7 @@ use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
|
|||
use rustc_trait_selection::error_reporting::infer::{FailureCode, ObligationCauseExt};
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt, SelectionContext};
|
||||
use smallvec::SmallVec;
|
||||
use tracing::debug;
|
||||
use {rustc_ast as ast, rustc_hir as hir};
|
||||
|
||||
|
@ -44,6 +45,12 @@ use crate::{
|
|||
struct_span_code_err,
|
||||
};
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
#[orderable]
|
||||
#[debug_format = "GenericIdx({})"]
|
||||
pub(crate) struct GenericIdx {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) enum DivergingBlockBehavior {
|
||||
/// This is the current stable behavior:
|
||||
|
@ -2291,7 +2298,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// If we're calling a method of a Fn/FnMut/FnOnce trait object implicitly
|
||||
// (eg invoking a closure) we want to point at the underlying callable,
|
||||
// not the method implicitly invoked (eg call_once).
|
||||
if let Some(assoc_item) = self.tcx.opt_associated_item(def_id)
|
||||
// TupleArguments is set only when this is an implicit call (my_closure(...)) rather than explicit (my_closure.call(...))
|
||||
if tuple_arguments == TupleArguments
|
||||
&& let Some(assoc_item) = self.tcx.opt_associated_item(def_id)
|
||||
// Since this is an associated item, it might point at either an impl or a trait item.
|
||||
// We want it to always point to the trait item.
|
||||
// If we're pointing at an inherent function, we don't need to do anything,
|
||||
|
@ -2301,8 +2310,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// Just an easy way to check "trait_def_id == Fn/FnMut/FnOnce"
|
||||
&& let Some(call_kind) = self.tcx.fn_trait_kind_from_def_id(maybe_trait_def_id)
|
||||
&& let Some(callee_ty) = callee_ty
|
||||
// TupleArguments is set only when this is an implicit call (my_closure(...)) rather than explicit (my_closure.call(...))
|
||||
&& tuple_arguments == TupleArguments
|
||||
{
|
||||
let callee_ty = callee_ty.peel_refs();
|
||||
match *callee_ty.kind() {
|
||||
|
@ -2371,174 +2378,136 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
&& !def_span.is_dummy()
|
||||
{
|
||||
let mut spans: MultiSpan = def_span.into();
|
||||
|
||||
if let Some(params_with_generics) = self.get_hir_params_with_generics(def_id, is_method)
|
||||
if let Some((params_with_generics, hir_generics)) =
|
||||
self.get_hir_param_info(def_id, is_method)
|
||||
{
|
||||
struct MismatchedParam<'a> {
|
||||
idx: ExpectedIdx,
|
||||
generic: GenericIdx,
|
||||
param: &'a FnParam<'a>,
|
||||
deps: SmallVec<[ExpectedIdx; 4]>,
|
||||
}
|
||||
|
||||
debug_assert_eq!(params_with_generics.len(), matched_inputs.len());
|
||||
|
||||
let mut generics_with_unmatched_params = Vec::new();
|
||||
|
||||
let check_for_matched_generics = || {
|
||||
if matched_inputs.iter().any(|x| x.is_some())
|
||||
&& params_with_generics.iter().any(|(x, _)| x.is_some())
|
||||
{
|
||||
for (idx, (generic, _)) in params_with_generics.iter_enumerated() {
|
||||
// Param has to have a generic and be matched to be relevant
|
||||
if matched_inputs[idx].is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(generic) = generic else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for unmatching_idx in
|
||||
idx.plus(1)..ExpectedIdx::from_usize(params_with_generics.len())
|
||||
{
|
||||
if matched_inputs[unmatching_idx].is_none()
|
||||
&& let Some(unmatched_idx_param_generic) =
|
||||
params_with_generics[unmatching_idx].0
|
||||
&& unmatched_idx_param_generic.name.ident()
|
||||
== generic.name.ident()
|
||||
{
|
||||
// We found a parameter that didn't match that needed to
|
||||
return true;
|
||||
// Gather all mismatched parameters with generics.
|
||||
let mut mismatched_params = Vec::<MismatchedParam<'_>>::new();
|
||||
if let Some(expected_idx) = expected_idx {
|
||||
let expected_idx = ExpectedIdx::from_usize(expected_idx);
|
||||
let &(expected_generic, ref expected_param) =
|
||||
¶ms_with_generics[expected_idx];
|
||||
if let Some(expected_generic) = expected_generic {
|
||||
mismatched_params.push(MismatchedParam {
|
||||
idx: expected_idx,
|
||||
generic: expected_generic,
|
||||
param: expected_param,
|
||||
deps: SmallVec::new(),
|
||||
});
|
||||
} else {
|
||||
// Still mark the mismatched parameter
|
||||
spans.push_span_label(expected_param.span(), "");
|
||||
}
|
||||
} else {
|
||||
mismatched_params.extend(
|
||||
params_with_generics.iter_enumerated().zip(matched_inputs).filter_map(
|
||||
|((idx, &(generic, ref param)), matched_idx)| {
|
||||
if matched_idx.is_some() {
|
||||
None
|
||||
} else if let Some(generic) = generic {
|
||||
Some(MismatchedParam {
|
||||
idx,
|
||||
generic,
|
||||
param,
|
||||
deps: SmallVec::new(),
|
||||
})
|
||||
} else {
|
||||
// Still mark mismatched parameters
|
||||
spans.push_span_label(param.span(), "");
|
||||
None
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if !mismatched_params.is_empty() {
|
||||
// For each mismatched paramter, create a two-way link to each matched parameter
|
||||
// of the same type.
|
||||
let mut dependants = IndexVec::<ExpectedIdx, _>::from_fn_n(
|
||||
|_| SmallVec::<[u32; 4]>::new(),
|
||||
params_with_generics.len(),
|
||||
);
|
||||
let mut generic_uses = IndexVec::<GenericIdx, _>::from_fn_n(
|
||||
|_| SmallVec::<[ExpectedIdx; 4]>::new(),
|
||||
hir_generics.params.len(),
|
||||
);
|
||||
for (idx, param) in mismatched_params.iter_mut().enumerate() {
|
||||
for ((other_idx, &(other_generic, _)), &other_matched_idx) in
|
||||
params_with_generics.iter_enumerated().zip(matched_inputs)
|
||||
{
|
||||
if other_generic == Some(param.generic) && other_matched_idx.is_some() {
|
||||
generic_uses[param.generic].extend([param.idx, other_idx]);
|
||||
dependants[other_idx].push(idx as u32);
|
||||
param.deps.push(other_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
let check_for_matched_generics = check_for_matched_generics();
|
||||
|
||||
for (idx, &(generic_param, param)) in
|
||||
params_with_generics.iter_enumerated().filter(|&(idx, _)| {
|
||||
check_for_matched_generics
|
||||
|| expected_idx
|
||||
.is_none_or(|expected_idx| expected_idx == idx.as_usize())
|
||||
})
|
||||
{
|
||||
let Some(generic_param) = generic_param else {
|
||||
spans.push_span_label(param.span(), "");
|
||||
continue;
|
||||
};
|
||||
|
||||
let other_params_matched: Vec<(ExpectedIdx, FnParam<'_>)> =
|
||||
params_with_generics
|
||||
.iter_enumerated()
|
||||
.filter(|&(other_idx, &(other_generic_param, _))| {
|
||||
if other_idx == idx {
|
||||
return false;
|
||||
}
|
||||
let Some(other_generic_param) = other_generic_param else {
|
||||
return false;
|
||||
};
|
||||
if matched_inputs[idx].is_none()
|
||||
&& matched_inputs[other_idx].is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if matched_inputs[idx].is_some()
|
||||
&& matched_inputs[other_idx].is_some()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
other_generic_param.name.ident() == generic_param.name.ident()
|
||||
})
|
||||
.map(|(other_idx, &(_, other_param))| (other_idx, other_param))
|
||||
.collect();
|
||||
|
||||
if !other_params_matched.is_empty() {
|
||||
let other_param_matched_names: Vec<String> = other_params_matched
|
||||
.iter()
|
||||
.map(|(idx, other_param)| {
|
||||
if let Some(name) = other_param.name() {
|
||||
format!("`{name}`")
|
||||
} else {
|
||||
format!("parameter #{}", idx.as_u32() + 1)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let matched_ty = self
|
||||
.resolve_vars_if_possible(formal_and_expected_inputs[idx].1)
|
||||
.sort_string(self.tcx);
|
||||
|
||||
if matched_inputs[idx].is_some() {
|
||||
// Highlight each mismatched type along with a note about which other parameters
|
||||
// the type depends on (if any).
|
||||
for param in &mismatched_params {
|
||||
if let Some(deps_list) = listify(¶m.deps, |&dep| {
|
||||
params_with_generics[dep].1.display(dep.as_usize()).to_string()
|
||||
}) {
|
||||
spans.push_span_label(
|
||||
param.span(),
|
||||
param.param.span(),
|
||||
format!(
|
||||
"{} need{} to match the {} type of this parameter",
|
||||
listify(&other_param_matched_names, |n| n.to_string())
|
||||
.unwrap_or_default(),
|
||||
pluralize!(if other_param_matched_names.len() == 1 {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}),
|
||||
matched_ty,
|
||||
"this parameter needs to match the {} type of {deps_list}",
|
||||
self.resolve_vars_if_possible(
|
||||
formal_and_expected_inputs[param.deps[0]].1
|
||||
)
|
||||
.sort_string(self.tcx),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Still mark mismatched parameters
|
||||
spans.push_span_label(param.param.span(), "");
|
||||
}
|
||||
}
|
||||
// Highligh each parameter being depended on for a generic type.
|
||||
for ((&(_, param), deps), &(_, expected_ty)) in
|
||||
params_with_generics.iter().zip(&dependants).zip(formal_and_expected_inputs)
|
||||
{
|
||||
if let Some(deps_list) = listify(deps, |&dep| {
|
||||
let param = &mismatched_params[dep as usize];
|
||||
param.param.display(param.idx.as_usize()).to_string()
|
||||
}) {
|
||||
spans.push_span_label(
|
||||
param.span(),
|
||||
format!(
|
||||
"this parameter needs to match the {} type of {}",
|
||||
matched_ty,
|
||||
listify(&other_param_matched_names, |n| n.to_string())
|
||||
.unwrap_or_default(),
|
||||
"{deps_list} need{} to match the {} type of this parameter",
|
||||
pluralize!((deps.len() != 1) as u32),
|
||||
self.resolve_vars_if_possible(expected_ty)
|
||||
.sort_string(self.tcx),
|
||||
),
|
||||
);
|
||||
}
|
||||
generics_with_unmatched_params.push(generic_param);
|
||||
} else {
|
||||
spans.push_span_label(param.span(), "");
|
||||
}
|
||||
}
|
||||
|
||||
for generic_param in self
|
||||
.tcx
|
||||
.hir()
|
||||
.get_if_local(def_id)
|
||||
.and_then(|node| node.generics())
|
||||
.into_iter()
|
||||
.flat_map(|x| x.params)
|
||||
.filter(|x| {
|
||||
generics_with_unmatched_params
|
||||
.iter()
|
||||
.any(|y| x.name.ident() == y.name.ident())
|
||||
})
|
||||
{
|
||||
let param_idents_matching: Vec<String> = params_with_generics
|
||||
.iter_enumerated()
|
||||
.filter(|&(_, &(generic, _))| {
|
||||
if let Some(generic) = generic {
|
||||
generic.name.ident() == generic_param.name.ident()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|(idx, &(_, param))| {
|
||||
if let Some(name) = param.name() {
|
||||
format!("`{name}`")
|
||||
} else {
|
||||
format!("parameter #{}", idx.as_u32() + 1)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !param_idents_matching.is_empty() {
|
||||
spans.push_span_label(
|
||||
generic_param.span,
|
||||
format!(
|
||||
"{} {} reference this parameter `{}`",
|
||||
listify(¶m_idents_matching, |n| n.to_string())
|
||||
.unwrap_or_default(),
|
||||
if param_idents_matching.len() == 2 { "both" } else { "all" },
|
||||
generic_param.name.ident().name,
|
||||
),
|
||||
);
|
||||
// Highlight each generic parameter in use.
|
||||
for (param, uses) in hir_generics.params.iter().zip(&mut generic_uses) {
|
||||
uses.sort();
|
||||
uses.dedup();
|
||||
if let Some(param_list) = listify(uses, |&idx| {
|
||||
params_with_generics[idx].1.display(idx.as_usize()).to_string()
|
||||
}) {
|
||||
spans.push_span_label(
|
||||
param.span,
|
||||
format!(
|
||||
"{param_list} {} reference this parameter `{}`",
|
||||
if uses.len() == 2 { "both" } else { "all" },
|
||||
param.name.ident().name,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2614,7 +2583,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
return;
|
||||
};
|
||||
|
||||
if let Some(params_with_generics) = self.get_hir_params_with_generics(def_id, is_method) {
|
||||
if let Some((params_with_generics, _)) = self.get_hir_param_info(def_id, is_method) {
|
||||
debug_assert_eq!(params_with_generics.len(), matched_inputs.len());
|
||||
for (idx, (generic_param, _)) in params_with_generics.iter_enumerated() {
|
||||
if matched_inputs[idx].is_none() {
|
||||
|
@ -2642,7 +2611,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
if matched_inputs[other_idx].is_some() {
|
||||
return false;
|
||||
}
|
||||
other_generic_param.name.ident() == generic_param.name.ident()
|
||||
other_generic_param == generic_param
|
||||
})
|
||||
.count();
|
||||
|
||||
|
@ -2674,11 +2643,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
/// Returns the parameters of a function, with their generic parameters if those are the full
|
||||
/// type of that parameter. Returns `None` if the function has no generics or the body is
|
||||
/// unavailable (eg is an instrinsic).
|
||||
fn get_hir_params_with_generics(
|
||||
fn get_hir_param_info(
|
||||
&self,
|
||||
def_id: DefId,
|
||||
is_method: bool,
|
||||
) -> Option<IndexVec<ExpectedIdx, (Option<&hir::GenericParam<'_>>, FnParam<'_>)>> {
|
||||
) -> Option<(IndexVec<ExpectedIdx, (Option<GenericIdx>, FnParam<'_>)>, &hir::Generics<'_>)>
|
||||
{
|
||||
let (sig, generics, body_id, param_names) = match self.tcx.hir().get_if_local(def_id)? {
|
||||
hir::Node::TraitItem(&hir::TraitItem {
|
||||
generics,
|
||||
|
@ -2708,7 +2678,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
&hir::Path { res: Res::Def(_, res_def_id), .. },
|
||||
)) = param.kind
|
||||
{
|
||||
generics.params.iter().find(|param| param.def_id.to_def_id() == res_def_id)
|
||||
generics
|
||||
.params
|
||||
.iter()
|
||||
.position(|param| param.def_id.to_def_id() == res_def_id)
|
||||
.map(GenericIdx::from_usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -2720,12 +2694,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
let params =
|
||||
params.get(is_method as usize..params.len() - sig.decl.c_variadic as usize)?;
|
||||
debug_assert_eq!(params.len(), fn_inputs.len());
|
||||
Some(fn_inputs.zip(params.iter().map(|param| FnParam::Param(param))).collect())
|
||||
Some((
|
||||
fn_inputs.zip(params.iter().map(|param| FnParam::Param(param))).collect(),
|
||||
generics,
|
||||
))
|
||||
}
|
||||
(None, Some(params)) => {
|
||||
let params = params.get(is_method as usize..)?;
|
||||
debug_assert_eq!(params.len(), fn_inputs.len());
|
||||
Some(fn_inputs.zip(params.iter().map(|param| FnParam::Name(param))).collect())
|
||||
Some((
|
||||
fn_inputs.zip(params.iter().map(|param| FnParam::Name(param))).collect(),
|
||||
generics,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2773,4 +2753,18 @@ impl FnParam<'_> {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn display(&self, idx: usize) -> impl '_ + fmt::Display {
|
||||
struct D<'a>(FnParam<'a>, usize);
|
||||
impl fmt::Display for D<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(name) = self.0.name() {
|
||||
write!(f, "`{name}`")
|
||||
} else {
|
||||
write!(f, "parameter #{}", self.1 + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
D(*self, idx)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue