Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 141 additions & 46 deletions compiler/rustc_trait_selection/src/error_reporting/traits/ambiguity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ use tracing::{debug, instrument};
use crate::error_reporting::TypeErrCtxt;
use crate::error_reporting::infer::need_type_info::TypeAnnotationNeeded;
use crate::error_reporting::traits::{FindExprBySpan, to_pretty_impl_header};
use crate::traits::ObligationCtxt;
use crate::traits::query::evaluate_obligation::InferCtxtExt;
use crate::traits::{FulfillmentError, ObligationCtxt};

#[derive(Debug)]
pub enum CandidateSource {
Expand Down Expand Up @@ -174,10 +174,43 @@ pub fn compute_applicable_impls_for_diagnostics<'tcx>(
}

impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
/// The term of an ambiguous obligation's predicate that gets blamed for the
/// missing type annotation: the first one still containing inference variables.
///
/// Besides `maybe_report_ambiguity` pointing its diagnostics at this term,
/// `report_fulfillment_errors` merges the ambiguity errors whose blamed terms
/// share an inference variable into a single diagnostic.
pub(super) fn ambiguity_term(&self, predicate: ty::Predicate<'tcx>) -> Option<ty::Term<'tcx>> {
match predicate.kind().skip_binder() {
ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => data
.trait_ref
.args
.iter()
.filter_map(ty::GenericArg::as_term)
.find(|term| term.has_non_region_infer()),
ty::PredicateKind::Clause(ty::ClauseKind::Projection(data)) => data
.projection_term
.args
.iter()
.filter_map(ty::GenericArg::as_term)
.chain([data.term])
.find(|term| term.has_non_region_infer()),
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(term)) => Some(term),
ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(data)) => {
data.walk().filter_map(ty::GenericArg::as_term).find(|term| term.is_infer())
}
ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, _)) => Some(ct.into()),
ty::PredicateKind::Subtype(data) => Some(data.a.into()),
ty::PredicateKind::NormalizesTo(data) if data.term.is_infer() => Some(data.term),
_ => None,
}
}

#[instrument(skip(self), level = "debug")]
pub(super) fn maybe_report_ambiguity(
&self,
obligation: &PredicateObligation<'tcx>,
related: &[&FulfillmentError<'tcx>],
) -> ErrorGuaranteed {
// Unable to successfully determine, probably means
// insufficient type information, but could mean
Expand Down Expand Up @@ -255,12 +288,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
// Pick the first generic parameter that still contains inference variables as the one
// we're going to emit an error for. If there are none (see above), fall back to
// a more general error.
let term = data
.trait_ref
.args
.iter()
.filter_map(ty::GenericArg::as_term)
.find(|s| s.has_non_region_infer());
let term = self.ambiguity_term(predicate);

let mut err = if let Some(term) = term {
let candidates: Vec<_> = self
Expand Down Expand Up @@ -306,34 +334,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
.with_long_ty_path(long_ty_path)
};

let mut ambiguities = compute_applicable_impls_for_diagnostics(
self.infcx,
&obligation.with(self.tcx, trait_pred),
false,
);
let has_non_region_infer = trait_pred
.skip_binder()
.trait_ref
.args
.types()
.any(|t| !t.is_ty_or_numeric_infer());
// It doesn't make sense to talk about applicable impls if there are more than a
// handful of them. If there are a lot of them, but only a few of them have no type
// params, we only show those, as they are more likely to be useful/intended.
if ambiguities.len() > 5 {
let infcx = self.infcx;
if !ambiguities.iter().all(|option| match option {
CandidateSource::DefId(did) => infcx.tcx.generics_of(*did).count() == 0,
CandidateSource::ParamEnv(_) => true,
}) {
// If not all are blanket impls, we filter blanked impls out.
ambiguities.retain(|option| match option {
CandidateSource::DefId(did) => infcx.tcx.generics_of(*did).count() == 0,
CandidateSource::ParamEnv(_) => true,
});
}
}
if ambiguities.len() > 1 && ambiguities.len() < 10 && has_non_region_infer {
if let Some(ambiguities) = self.applicable_impls_to_mention(obligation, trait_pred)
{
if let Some(e) = self.tainted_by_errors()
&& term.is_none()
{
Expand Down Expand Up @@ -590,13 +592,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
// other `Foo` impls are incoherent.
return guar;
}
let term = data
.projection_term
.args
.iter()
.filter_map(ty::GenericArg::as_term)
.chain([data.term])
.find(|g| g.has_non_region_infer());
let term = self.ambiguity_term(predicate);
let predicate = self.tcx.short_string(predicate, &mut long_ty_path);
if let Some(term) = term {
self.emit_inference_failure_err(
Expand All @@ -621,16 +617,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}
}

ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(data)) => {
ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(_)) => {
if let Err(e) = predicate.error_reported() {
return e;
}
if let Some(e) = self.tainted_by_errors() {
return e;
}
let term =
data.walk().filter_map(ty::GenericArg::as_term).find(|term| term.is_infer());
if let Some(term) = term {
if let Some(term) = self.ambiguity_term(predicate) {
self.emit_inference_failure_err(
obligation.cause.body_def_id,
span,
Expand Down Expand Up @@ -713,10 +707,111 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
.with_long_ty_path(long_ty_path)
}
};

// The related obligations are ambiguous because of the same inference variable,
// so they belong to this diagnostic: annotating the variable has to satisfy all
// of them at once. Mention their requirements, except for bookkeeping predicates
// (`WellFormed`, sizedness, ...) whose mention wouldn't be actionable.
let mut mentioned = vec![predicate];
let mut mentioned_strs: Vec<String> = vec![];
for &error in related {
let related_pred = self.resolve_vars_if_possible(error.obligation.predicate);
if mentioned.contains(&related_pred) {
continue;
}
let note = match related_pred.kind().skip_binder() {
ty::PredicateKind::Clause(ty::ClauseKind::Trait(data))
if !matches!(
self.tcx.as_lang_item(data.def_id()),
Some(LangItem::Sized | LangItem::MetaSized | LangItem::PointeeSized)
) =>
{
let clause = related_pred.kind().rebind(data);
if let ty::Infer(_) = clause.self_ty().skip_binder().kind() {
let tr = self.tcx.short_string(
clause.print_modifiers_and_trait_path(),
&mut err.long_ty_path(),
);
format!("the type must also implement `{tr}`")
} else {
let pred = self.tcx.short_string(related_pred, &mut err.long_ty_path());
let note = format!("cannot satisfy `{pred}`");
// The self type is known, so the `impl`s that could have applied to it are
// few and worth pointing at, like the blamed bound does. When it is still
// an inference variable the list is every `impl` of the trait, which is
// why the branch above only names the trait.
//
// `tainted_by_errors` is checked because `annotate_source_of_ambiguity`
// downgrades the whole diagnostic once an error was already emitted.
if !mentioned_strs.contains(&note)
&& self.tainted_by_errors().is_none()
&& let Some(ambiguities) =
self.applicable_impls_to_mention(&error.obligation, clause)
{
self.annotate_source_of_ambiguity(&mut err, &ambiguities, related_pred);
mentioned_strs.push(note);
mentioned.push(related_pred);
continue;
}
note
}
}
ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) => {
let pred = self.tcx.short_string(related_pred, &mut err.long_ty_path());
format!("cannot satisfy `{pred}`")
}
_ => {
mentioned.push(related_pred);
continue;
}
};
// Two predicates can print identically (e.g. `From<?0>` and `From<?1>` both show as
// `From<_>`); only emit each unique note string once.
if !mentioned_strs.contains(&note) {
err.note(note.clone());
mentioned_strs.push(note);
}
mentioned.push(related_pred);
}

self.note_obligation_cause(&mut err, obligation);
err.emit()
}

/// The `impl`s and `where` clauses that could have satisfied `trait_pred`, when listing them
/// is likely to help. `None` means the caller should describe the bound some other way.
fn applicable_impls_to_mention(
&self,
obligation: &PredicateObligation<'tcx>,
trait_pred: ty::PolyTraitPredicate<'tcx>,
) -> Option<Vec<CandidateSource>> {
let mut ambiguities = compute_applicable_impls_for_diagnostics(
self.infcx,
&obligation.with(self.tcx, trait_pred),
false,
);
let has_non_region_infer =
trait_pred.skip_binder().trait_ref.args.types().any(|t| !t.is_ty_or_numeric_infer());
// It doesn't make sense to talk about applicable impls if there are more than a
// handful of them. If there are a lot of them, but only a few of them have no type
// params, we only show those, as they are more likely to be useful/intended.
if ambiguities.len() > 5 {
let infcx = self.infcx;
if !ambiguities.iter().all(|option| match option {
CandidateSource::DefId(did) => infcx.tcx.generics_of(*did).count() == 0,
CandidateSource::ParamEnv(_) => true,
}) {
// If not all are blanket impls, we filter blanked impls out.
ambiguities.retain(|option| match option {
CandidateSource::DefId(did) => infcx.tcx.generics_of(*did).count() == 0,
CandidateSource::ParamEnv(_) => true,
});
}
}
(ambiguities.len() > 1 && ambiguities.len() < 10 && has_non_region_infer)
.then_some(ambiguities)
}

fn annotate_source_of_ambiguity(
&self,
err: &mut Diag<'_>,
Expand Down
96 changes: 92 additions & 4 deletions compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use rustc_crate_store::{ExternCrate, ExternCrateSource};
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::unord::UnordSet;
use rustc_errors::{Applicability, Diag, E0038, E0276, MultiSpan, struct_span_code_err};
use rustc_hir::attrs::lang_items::LangItem;
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, AmbigArg};
Expand All @@ -21,6 +22,7 @@ use rustc_infer::traits::{
};
use rustc_middle::ty::print::{PrintTraitRefExt as _, with_no_trimmed_paths};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt as _};
use rustc_next_trait_solver::solve::TyOrConstInferVar;
use rustc_span::{DesugaringKind, ErrorGuaranteed, ExpnKind, Span};
use thin_vec::ThinVec;
use tracing::{info, instrument};
Expand Down Expand Up @@ -250,12 +252,94 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}
}

// Ambiguity errors blaming the same inference variable describe a single problem:
// annotating that one variable has to satisfy all of them at once. Reporting them
// separately loses all but the first, as the rest get canceled as duplicates once
// the `infcx` is tainted (see `maybe_report_ambiguity`), hiding their requirements
// from the user. Instead, report the first one (the sort above placed the most
// informative obligation first) and mention the requirements of the others in it.
//
// Type variables that are only related through a pending `Coerce` or `Subtype`
// obligation still concern the same annotation, so compare their sub-unification
// roots, like `need_type_info` does when looking for the annotation source.
let ambiguity_infer_var = |error: &FulfillmentError<'tcx>| match error.code {
FulfillmentErrorCode::Ambiguity { overflow: None } => self
.ambiguity_term(self.resolve_vars_if_possible(error.obligation.predicate))
.and_then(|term| {
ty::GenericArg::from(term)
.walk()
.find_map(TyOrConstInferVar::maybe_from_generic_arg::<TyCtxt<'tcx>>)
})
.map(|var| match var {
TyOrConstInferVar::Ty(vid) => {
TyOrConstInferVar::Ty(self.sub_unification_table_root_var(vid))
}
other => other,
}),
_ => None,
};
let infer_vars: Vec<_> = errors.iter().map(ambiguity_infer_var).collect();

let mut reported = None;
let mut merged = vec![None; errors.len()];
let mut reported_as_primary = vec![false; errors.len()];
for from_expansion in [false, true] {
for (error, suppressed) in iter::zip(&errors, &is_suppressed) {
for (index, (error, suppressed)) in iter::zip(&errors, &is_suppressed).enumerate() {
if !suppressed && error.obligation.cause.span.from_expansion() == from_expansion {
if !error.references_error() {
let guar = self.report_fulfillment_error(error);
let guar = if let Some(guar) = merged[index] {
guar
} else {
let group: Vec<usize> = match infer_vars[index] {
Some(var) => (0..errors.len())
.filter(|&other| {
other != index && infer_vars[other] == Some(var)
})
.collect(),
None => vec![],
};
let related: Vec<_> = group
.iter()
.filter(|&&other| {
// Exclude already-reported primaries: they were their own
// canonical error and adding them as notes here would
// produce duplicate information.
!is_suppressed[other]
&& !errors[other].references_error()
&& !reported_as_primary[other]
})
.map(|&other| &errors[other])
.collect();
let guar = self.report_fulfillment_error(error, &related);
for &other in &group {
// Only suppress related errors whose predicates produce
// informative notes in maybe_report_ambiguity (Trait,
// Projection). Predicates we can't represent as notes
// (e.g. const evaluatability) still report separately.
let pred = errors[other].obligation.predicate;
let suppresses = match pred.kind().skip_binder() {
ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => {
!matches!(
self.tcx.as_lang_item(data.def_id()),
Some(
LangItem::Sized
| LangItem::MetaSized
| LangItem::PointeeSized
)
)
}
ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) => {
true
}
_ => false,
};
if suppresses {
merged[other] = Some(guar);
}
}
reported_as_primary[index] = true;
guar
};
self.infcx.set_tainted_by_errors(guar);
reported = Some(guar);
// We want to ignore desugarings here: spans are equivalent even
Expand Down Expand Up @@ -286,7 +370,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
}

#[instrument(skip(self), level = "debug")]
fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) -> ErrorGuaranteed {
fn report_fulfillment_error(
&self,
error: &FulfillmentError<'tcx>,
related: &[&FulfillmentError<'tcx>],
) -> ErrorGuaranteed {
let mut error = FulfillmentError {
obligation: error.obligation.clone(),
code: error.code.clone(),
Expand All @@ -311,7 +399,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
self.report_projection_error(&error.obligation, e)
}
FulfillmentErrorCode::Ambiguity { overflow: None } => {
self.maybe_report_ambiguity(&error.obligation)
self.maybe_report_ambiguity(&error.obligation, related)
}
FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) } => {
self.report_overflow_no_abort(error.obligation.clone(), suggest_increasing_limit)
Expand Down
Loading
Loading